forked from mirrors/terraform-provider-binarylane
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a58a8112c | |||
| 08a37caf51 | |||
| 709fb3ce28 | |||
| 5313e1c7ab | |||
| ce349f53f0 | |||
| 9d3d3e9af3 | |||
| a0a227c88c | |||
| f8edcd0e06 | |||
| 04552e0fcf | |||
| 66fcb6f85e | |||
| 806bdebaf8 | |||
| dd06380c63 | |||
| 322a8fda80 | |||
| a50daac8e9 | |||
| 424828123d | |||
| 4c50298eca | |||
| cbef2be0fe | |||
| 5767bb6739 | |||
| 74eac97fe8 | |||
| a30b6b14a7 | |||
| 0a4156269f | |||
| 0adff101c6 | |||
| 5e3b30a49b | |||
| 45d7ab450c | |||
| c5b250becf | |||
| 10070095e1 | |||
| 24f05a6266 | |||
| 86b980c91b | |||
| 21b43d92b9 | |||
| e70e10c4c9 | |||
| 75a387e19d | |||
| 11e0302e71 | |||
| 4677e34166 | |||
| 3f518b2ca3 | |||
| 90b94254f1 | |||
| 46117cf31d | |||
| 394638ff57 | |||
| 239e48e940 | |||
| 2221f8c853 | |||
| 95487045c4 | |||
| 7f5bae6b15 | |||
| 4ef9df6edf | |||
| 547763d639 | |||
| f5fecdd69c | |||
| cd85f67e28 | |||
| 50d06b967b | |||
| e6c50a470d | |||
| 2bc281d177 | |||
| 2a6ba27340 | |||
| fecd42d4a9 | |||
| c5a44b7694 | |||
| 2c639837ef | |||
| 200b2ee3a1 | |||
| a135bceee8 | |||
| 103f4cc2b0 | |||
| db948a103c | |||
| e18d2424d0 | |||
| e1aac7ec1f | |||
| 6c3520bbda | |||
| 22ea042e33 | |||
| b63f052a9f | |||
| a5731e0a12 | |||
| e5450d9f27 | |||
| 53b1643f30 | |||
| 1084c27ab6 | |||
| ffcadb74b3 | |||
| d1adea205b | |||
| 57ed8ff440 | |||
| 702aae5b2f | |||
| 9747587128 | |||
| 44772aa55d | |||
| 600e3f56e7 | |||
| 7a978731bb | |||
| 54dd00fa72 | |||
| e93659373d | |||
| dd3aaa2d75 | |||
| 6cafba5330 | |||
| 63632d3abb |
@@ -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
|
||||
@@ -1,10 +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'
|
||||
|
||||
@@ -14,28 +14,39 @@ 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
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# Allow goreleaser to access older tag information.
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
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@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.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@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||
with:
|
||||
args: release --clean
|
||||
env:
|
||||
# GitHub sets the GITHUB_TOKEN secret automatically.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
|
||||
- name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: gh release edit "${{ github.ref }}" --draft=false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -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@v5
|
||||
- uses: actions/setup-go@v6
|
||||
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@v5
|
||||
# - uses: actions/setup-go@v6
|
||||
# with:
|
||||
# go-version-file: 'go.mod'
|
||||
# - run: go test -v -cover ./...
|
||||
+2
-3
@@ -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.
|
||||
# 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
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"go.testEnvFile": "${workspaceFolder}/.env",
|
||||
"go.testEnvVars": {
|
||||
"TF_LOG": "TRACE"
|
||||
}
|
||||
}
|
||||
+35
-7
@@ -35,7 +35,22 @@ terraform apply
|
||||
|
||||
|
||||
```sh
|
||||
TF_ACC=1 go test -v ./...
|
||||
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
|
||||
@@ -52,7 +67,7 @@ go generate
|
||||
```sh
|
||||
tfplugingen-framework scaffold resource \
|
||||
--output-dir ./internal/provider \
|
||||
--name REPLACE_ME
|
||||
--name REPLACE_ME # eg. server
|
||||
```
|
||||
|
||||
```sh
|
||||
@@ -125,7 +140,7 @@ Add a `Configure` method to the resource:
|
||||
+ }
|
||||
```
|
||||
|
||||
Add the new data source/resource to `provider.go`:
|
||||
Add the new resource to `provider.go`:
|
||||
|
||||
```diff
|
||||
return []func() resource.Resource{
|
||||
@@ -148,8 +163,8 @@ Define the interfaces that the data source should implement:
|
||||
Add the API client to the data source:
|
||||
|
||||
```diff
|
||||
- type exampleResource struct{}
|
||||
+ type exampleResource struct {
|
||||
- type exampleDataSource struct{}
|
||||
+ type exampleDataSource struct {
|
||||
+ bc *BinarylaneClient
|
||||
+ }
|
||||
```
|
||||
@@ -198,12 +213,25 @@ Use the generated schema to define the data source schema.
|
||||
- },
|
||||
- },
|
||||
- }
|
||||
+ ds, err := convertResourceSchemaToDataSourceSchema(resources.ExampleResourceSchema(ctx))
|
||||
+ 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"
|
||||
+ // resp.Schema.Description = "TODO"
|
||||
}
|
||||
```
|
||||
|
||||
Add the data source to `provider.go`:
|
||||
|
||||
```diff
|
||||
return []func() datasource.DataSource{
|
||||
# ...
|
||||
+ NewExampleDataSource,
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -1,35 +1,82 @@
|
||||
# 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" {
|
||||
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
|
||||
- [x] [Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest/docs/resources/server)
|
||||
- [x] OpenTofu
|
||||
- [ ] Deploy the rest of the owl
|
||||
- [x] Servers
|
||||
- [x] Firewall Rules
|
||||
- [x] Cloud Init
|
||||
- [x] Updating server properties
|
||||
- [x] "Advanced Features"
|
||||
- [x] IPV6
|
||||
- [ ] Customised Backups
|
||||
- [ ] Software
|
||||
- [ ] 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
|
||||
- [x] K3s
|
||||
- [ ] NixOS
|
||||
- [ ] Virtual Private Cloud
|
||||
|
||||
## Parameters
|
||||
## Server parameters
|
||||
|
||||
Images:
|
||||
### Regions
|
||||
|
||||
<details>
|
||||
<summary>curl for regions</summary>
|
||||
|
||||
```sh
|
||||
curl -X GET "https://api.binarylane.com.au/v2/regions" \
|
||||
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/regions.json
|
||||
|
||||
jq '[ .regions[] | .slug ] | sort' tmp/regions.json
|
||||
```
|
||||
</details>
|
||||
|
||||
```json
|
||||
[
|
||||
"bne",
|
||||
"mel",
|
||||
"per",
|
||||
"sin",
|
||||
"syd"
|
||||
]
|
||||
```
|
||||
|
||||
### Images
|
||||
|
||||
<details>
|
||||
<summary>curl for images</summary>
|
||||
|
||||
```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
|
||||
```
|
||||
</details>
|
||||
|
||||
```json
|
||||
[
|
||||
@@ -59,33 +106,18 @@ jq '[ .images[] | .slug ] | sort' tmp/images.json
|
||||
]
|
||||
```
|
||||
|
||||
Regions:
|
||||
### Sizes
|
||||
|
||||
```sh
|
||||
curl -X GET "https://api.binarylane.com.au/v2/regions" \
|
||||
-H "Authorization: Bearer **********"" > tmp/regions.json
|
||||
|
||||
jq '[ .regions[] | .slug ] | sort' tmp/regions.json
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
"bne",
|
||||
"mel",
|
||||
"per",
|
||||
"sin",
|
||||
"syd"
|
||||
]
|
||||
```
|
||||
|
||||
Sizes (not all sizes are available in all regions):
|
||||
<details>
|
||||
<summary>curl for sizes</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
|
||||
[
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_images Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# binarylane_images (Data Source)
|
||||
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_images" "example" {
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
|
||||
- `type` (String) Queries for distribution will include images that have pre-installed applications.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `images` (Attributes List) (see [below for nested schema](#nestedatt--images))
|
||||
|
||||
<a id="nestedatt--images"></a>
|
||||
### Nested Schema for `images`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `backup_info` (Attributes) If this image is a backup, this object will provide further information. (see [below for nested schema](#nestedatt--images--backup_info))
|
||||
- `created_at` (String) If this is a backup image this is the date and time in ISO8601 format when the image was created.
|
||||
- `description` (String) A description that may provide further details or warnings about the image.
|
||||
- `distribution` (String) If this is an operating system image, this is the name of the distribution. If this is a backup image, this is the name of the distribution the server is using.
|
||||
- `distribution_info` (Attributes) This object may provide further information about the distribution. (see [below for nested schema](#nestedatt--images--distribution_info))
|
||||
- `distribution_surcharges` (Attributes) If this is not null the use of this image may incur surcharges above the base cost of the server. All costs are in AU$. (see [below for nested schema](#nestedatt--images--distribution_surcharges))
|
||||
- `error_message` (String) If the image creation failed this may provide further information.
|
||||
- `full_name` (String) If this is an operating system image, this is the name and version of the distribution. If this is a backup image, this is the server hostname and label of the backup if it exists, otherwise it is the server hostname and UTC timestamp of the creation of the image.
|
||||
- `id` (Number) The ID of this image.
|
||||
- `min_disk_size` (Number) For a distribution image this is the minimum disk size in GB required to install the operating system. For a backup image this is the minimum total disk size in GB required to restore the backup.
|
||||
- `min_memory_megabytes` (Number) This is minimum memory in MB necessary to support this operating system (or the base operating system for a backup image).
|
||||
- `name` (String) If this is an operating system image, this is the name of the operating system version. If this is a backup image, this is the label of the backup if it exists, otherwise it is the UTC timestamp of the creation of the image.
|
||||
- `public` (Boolean) A public image is available to all users. A private image is available only to the account that created the image.
|
||||
- `regions` (List of String) The slugs of the regions where the image is available for use.
|
||||
- `size_gigabytes` (Number) For a distribution image this is the disk size used in GB by the operating system on initial install. For a backup image this is the size of the compressed backup image in GB.
|
||||
- `slug` (String) If this is an operating system image this is a slug which may be used as an alternative to the ID as a reference.
|
||||
- `status` (String) | Value | Description |
|
||||
| ----- | ----------- |
|
||||
| NEW | The image is new. |
|
||||
| available | The image is available for use. |
|
||||
| pending | The image is pending and is not yet available for use. |
|
||||
| deleted | The image has been deleted and is no longer available for use. |
|
||||
- `type` (String) | Value | Description |
|
||||
| ----- | ----------- |
|
||||
| custom | An image uploaded by a user. |
|
||||
| snapshot | A snapshot. Snapshot creation is not currently supported so only distribution images will have this value. |
|
||||
| backup | A backup of a server. |
|
||||
|
||||
<a id="nestedatt--images--backup_info"></a>
|
||||
### Nested Schema for `images.backup_info`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `backup_disks` (Attributes List) A list of the individual disks that make up this backup. (see [below for nested schema](#nestedatt--images--backup_info--backup_disks))
|
||||
- `iso` (Boolean) If this is true the backup is an ISO image and cannot be restored or downloaded. ISO images may only be attached for use as a boot disk or an additional disk.
|
||||
- `locked` (Boolean) If this is true the backup is locked and cannot be replaced.
|
||||
- `offsite` (Boolean) If this is true, an attempt to create an offsite copy was made. This does not mean that the offsite copy attempt was successful or that the copy still exists.
|
||||
- `server_id` (Number) The server ID that was used to create this backup.
|
||||
- `type` (String) | Value | Description |
|
||||
| ----- | ----------- |
|
||||
| daily | A backup which is scheduled to be taken each day. |
|
||||
| weekly | A backup which is scheduled to be taken each week. |
|
||||
| monthly | A backup which is scheduled to be taken each month. |
|
||||
| temporary | A backup which is created on demand and only retained for a maximum of seven days. |
|
||||
|
||||
<a id="nestedatt--images--backup_info--backup_disks"></a>
|
||||
### Nested Schema for `images.backup_info.backup_disks`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `description` (String) A description of this disk.
|
||||
- `id` (Number) The ID of this backup disk.
|
||||
- `min_disk_size` (Number) This is the minimum disk size in GB required to restore this disk image.
|
||||
- `size_gigabytes` (Number) This is the compressed size of the disk image in GB.
|
||||
|
||||
|
||||
|
||||
<a id="nestedatt--images--distribution_info"></a>
|
||||
### Nested Schema for `images.distribution_info`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `features` (List of String) Features supported by this distribution.
|
||||
- `image_id` (Number) If this is a backup image, the operating system ID of the server at the time the backup was created.
|
||||
- `password_recovery` (String) Supported methods of password recovery.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| manual | Password must be reset manually using the recovery console and rescue disk. |
|
||||
| offline-clear | Password can be cleared for the admin/root user only. New password needs to be provided on login via the console (Requires restart). |
|
||||
| offline-change | Password can be reset and new credentials sent (Requires restart). |
|
||||
| online-change | Password may be reset without requiring a reboot via installed QEMU Guest Agent. |
|
||||
- `remote_access_user` (String) User name to use when connecting via remote access (RDP or SSH).
|
||||
|
||||
|
||||
<a id="nestedatt--images--distribution_surcharges"></a>
|
||||
### Nested Schema for `images.distribution_surcharges`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `surcharge_base_cost` (Number) The additional cost for using this operating system as the base image for a size.
|
||||
- `surcharge_min_vcpu` (Number) The minimum vcpu count for surcharge calculations.
|
||||
- `surcharge_per_memory_max_megabytes` (Number) The maximum memory in MB that counts towards the surcharge_per_memory_mb. Any memory above this cap does not attract the surcharge.
|
||||
- `surcharge_per_memory_megabyte` (Number) The additional cost per MB for using this operating system as the base image for a size.
|
||||
- `surcharge_per_vcpu` (Number) The additional cost per vcpu for using this operating system as the base image for a size.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_load_balancer Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Retrieve details about a BinaryLane Load Balancer.
|
||||
---
|
||||
|
||||
# binarylane_load_balancer (Data Source)
|
||||
|
||||
Retrieve details about a BinaryLane Load Balancer.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_load_balancer" "example" {
|
||||
id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `id` (Number) The ID of the load balancer to fetch.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `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` (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))
|
||||
- `ip` (String) The IPv4 address of the load balancer.
|
||||
- `name` (String) The hostname of the load balancer.
|
||||
- `region` (String) Leave null to create an anycast load balancer.
|
||||
- `server_ids` (Set 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) The protocol that traffic must match for this load balancer to forward traffic according to this rule.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| http | The load balancer will forward HTTP traffic that matches this rule. |
|
||||
| https | The load balancer will forward HTTPS traffic that matches this rule. |
|
||||
|
||||
|
||||
<a id="nestedatt--health_check"></a>
|
||||
### Nested Schema for `health_check`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `path` (String)
|
||||
- `protocol` (String)
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_regions Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# binarylane_regions (Data Source)
|
||||
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_regions" "example" {
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `regions` (Attributes List) (see [below for nested schema](#nestedatt--regions))
|
||||
|
||||
<a id="nestedatt--regions"></a>
|
||||
### Nested Schema for `regions`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `available` (Boolean) Whether this region is available for the allocation of new resources.
|
||||
- `features` (List of String) A list of features available for resources in this region.
|
||||
- `name` (String) The name of this region.
|
||||
- `name_servers` (List of String) A list of nameservers available for resources in this region.
|
||||
- `sizes` (List of String) The slugs of the sizes available in this region.
|
||||
- `slug` (String) The unique slug for this region.
|
||||
+49
-12
@@ -3,14 +3,20 @@
|
||||
page_title: "binarylane_server Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
Retrieve details about a BinaryLane Server.
|
||||
---
|
||||
|
||||
# binarylane_server (Data Source)
|
||||
|
||||
TODO
|
||||
Retrieve details about a BinaryLane Server.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_server" "example" {
|
||||
id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -19,17 +25,48 @@ TODO
|
||||
|
||||
- `id` (Number) The ID of the server to fetch.
|
||||
|
||||
### 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.
|
||||
- `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) 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.
|
||||
- `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.
|
||||
- `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
|
||||
|
||||
- `image` (String) The slug of the selected operating system.
|
||||
- `advanced_features` (Object) (see [below for nested schema](#nestedatt--advanced_features))
|
||||
- `backups` (Boolean) If `true` this will enable two daily backups for the server. By default, backups are disabled.
|
||||
- `disk` (Number) The total storage in GB for this server. Leave null to accept the default for the size Valid values:
|
||||
- must be a multiple of 5
|
||||
- \> 60 GB must be a multiple of 10
|
||||
- \> 200 GB must be a multiple of 100
|
||||
- `image` (String) The slug of the selected operating system, such as `debian-12`. You can fetch a full list of images from the BinaryLane API.
|
||||
- `ipv6` (Boolean) If `true` this will add a public and private IPv6 address to the server. By default, IPv6 is disabled.
|
||||
- `memory` (Number) The total memory in MB for this server. Leave null to accept the default size. Valid values:
|
||||
- must be a multiple of 128
|
||||
- \> 2048 MB must be a multiple of 1024
|
||||
- \> 16384 MB must be a multiple of 2048
|
||||
- \> 24576 MB must be a multiple of 4096
|
||||
- `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.
|
||||
- `private_ipv6_addresses` (List of String) The private IPv6 addresses assigned to the server.
|
||||
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
|
||||
- `public_ipv6_addresses` (List of String) The public IPv6 addresses assigned to the server.
|
||||
- `region` (String) The slug of the selected region.
|
||||
- `size` (String) The slug of the selected size.
|
||||
- `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding.
|
||||
- `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) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
|
||||
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
|
||||
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
|
||||
|
||||
<a id="nestedatt--advanced_features"></a>
|
||||
### Nested Schema for `advanced_features`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `cloud_init` (Boolean)
|
||||
- `driver_disk` (Boolean)
|
||||
- `emulated_devices` (Boolean)
|
||||
- `emulated_hyperv` (Boolean)
|
||||
- `emulated_tpm` (Boolean)
|
||||
- `local_rtc` (Boolean)
|
||||
- `nested_virt` (Boolean)
|
||||
- `qemu_guest_agent` (Boolean)
|
||||
- `uefi_boot` (Boolean)
|
||||
- `unset_uuid` (Boolean)
|
||||
|
||||
@@ -3,34 +3,52 @@
|
||||
page_title: "binarylane_server_firewall_rules Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
Retrieve details about the External Firewall Rules assigned to a BinaryLane server.
|
||||
---
|
||||
|
||||
# binarylane_server_firewall_rules (Data Source)
|
||||
|
||||
TODO
|
||||
Retrieve details about the External Firewall Rules assigned to a BinaryLane server.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_server_firewall_rules" "example" {
|
||||
server_id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
### 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))
|
||||
- `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))
|
||||
|
||||
<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)
|
||||
- `action` (String) The action to take when there is a match on this rule.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| drop | Traffic matching this rule will be dropped. |
|
||||
| accept | Traffic matching this rule will be accepted. |
|
||||
- `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_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.
|
||||
- `destination_ports` (List of String) The destination ports to match for this rule. Leave null or empty to match on all ports.
|
||||
- `protocol` (String) The protocol to match for this rule.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| all | This rule will match any protocol. |
|
||||
| icmp | This rule will match ICMP traffic only. |
|
||||
| tcp | This rule will match TCP traffic only. |
|
||||
| udp | This rule will match UDP traffic only. |
|
||||
- `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.
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_sizes Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# binarylane_sizes (Data Source)
|
||||
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_sizes" "example" {
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
|
||||
- `image` (String) If null or not provided regions that support the size are included in the returned objects regardless of operating system. If this is provided it must be the id or slug of an operating system image and will cause only valid regions for the size and operating system to be included in the returned objects.
|
||||
- `server_id` (Number) If supplied only sizes available for a resize the specified server will be returned. This parameter is only available when authenticated.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `sizes` (Attributes List) (see [below for nested schema](#nestedatt--sizes))
|
||||
|
||||
<a id="nestedatt--sizes"></a>
|
||||
### Nested Schema for `sizes`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `available` (Boolean) If this is false the size is not available for new servers.
|
||||
- `cpu_description` (String) A description of the CPU provided in this size.
|
||||
- `description` (String) A description of this size.
|
||||
- `disk` (Number) The included storage for this size in GB.
|
||||
- `excess_transfer_cost_per_gigabyte` (Number) The excess charged for any transfer above the included data transfer in AU$ per GB.
|
||||
- `memory` (Number) The included memory for this size in MB.
|
||||
- `options` (Attributes) Available add-ons (optional features not included in the base price) for the size. All costs are in AU$ per month (pro-rated). (see [below for nested schema](#nestedatt--sizes--options))
|
||||
- `price_hourly` (Number) Hourly price in AU$.
|
||||
- `price_monthly` (Number) Monthly Price in AU$.
|
||||
- `regions` (List of String) A list of region slugs where this size is available regardless of stock.
|
||||
If this a response to a query that included a selected operating system this response will only include regions where that operating system is available on this size,
|
||||
otherwise not all regions listed will support all operating systems on this size.
|
||||
- `regions_out_of_stock` (List of String) A list of region slugs where the size is normally available but is currently not available due to lack of stock.
|
||||
- `size_type` (Attributes) The type of this size, generally used to differentiate sizes optimized for different usages. (see [below for nested schema](#nestedatt--sizes--size_type))
|
||||
- `slug` (String) The slug of this size.
|
||||
- `storage_description` (String) A description of the storage provided in this size.
|
||||
- `transfer` (Number) The included data transfer for this size in TB.
|
||||
- `vcpu_units` (String) This is the unit that the vcpus field counts, e.g. "core" or "thread".
|
||||
- `vcpus` (Number) The count of virtual CPUs for this size. See vcpu_units for a description of how each virtual CPU maps to the underlying hardware.
|
||||
|
||||
<a id="nestedatt--sizes--options"></a>
|
||||
### Nested Schema for `sizes.options`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `backups_cost_per_backup_per_gigabyte` (Number) The cost per GB of storage of each selected backup. See the API support document for how to calculate the final cost of backups based on the options selected.
|
||||
- `daily_backups` (Number) The number of daily backups included in the base size cost.
|
||||
- `discount_for_no_public_ipv4` (Number) This is the discount (if any) that is applied if no public IPv4 addresses are selected.
|
||||
- `disk_cost_per_additional_gigabyte` (Number) The additional cost per GB per month for additional storage space.
|
||||
- `disk_max` (Number) The maximum storage in GB permitted on this size.
|
||||
- `disk_min` (Number) The minimum storage in GB permitted on this size.
|
||||
- `ipv4_addresses_cost_per_address` (Number) The additional cost per public IPv4 address per month for additional IPv4 addresses.
|
||||
- `ipv4_addresses_max` (Number) The maximum number of IPv4 addresses permitted on this size.
|
||||
- `memory_cost_per_additional_megabyte` (Number) The additional cost per MB per month for additional memory.
|
||||
- `memory_max` (Number) The maximum memory in MB permitted on this size.
|
||||
- `monthly_backups` (Number) The number of monthly backups included in the base size cost.
|
||||
- `offsite_backup_frequency_cost` (Attributes) The additional cost per GB of storage for enabling offsite backups based on highest frequency of backups currently enabled. All costs are in AU$. (see [below for nested schema](#nestedatt--sizes--options--offsite_backup_frequency_cost))
|
||||
- `offsite_backups_cost_per_gigabyte` (Number) The additional cost per GB of storage for enabling offsite backups. See the API support document for how to calculate the final cost of backups based on the options selected.
|
||||
- `restricted_disk_values` (List of Number) If this is null the normal valid values in the documentation for SizeOptionsRequest are used, otherwise only these values (in GB) are permitted.
|
||||
- `transfer_cost_per_additional_gigabyte` (Number) The additional cost per GB per month for additional included transfer.
|
||||
- `transfer_max` (Number) The maximum transfer in TB permitted for this size.
|
||||
- `weekly_backups` (Number) The number of weekly backups included in the base size cost.
|
||||
|
||||
<a id="nestedatt--sizes--options--offsite_backup_frequency_cost"></a>
|
||||
### Nested Schema for `sizes.options.offsite_backup_frequency_cost`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `daily_per_gigabyte` (Number) The additional cost per GB per month for enabling daily offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.
|
||||
- `monthly_per_gigabyte` (Number) The additional cost per GB per month for enabling monthly offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.
|
||||
- `weekly_per_gigabyte` (Number) The additional cost per GB per month for enabling weekly offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.
|
||||
|
||||
|
||||
|
||||
<a id="nestedatt--sizes--size_type"></a>
|
||||
### Nested Schema for `sizes.size_type`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `description` (String) The description of this size type, generally used to indicate the intended use case for sizes of this type.
|
||||
- `name` (String) The name of this size type.
|
||||
- `slug` (String) The slug of this size type.
|
||||
@@ -3,24 +3,31 @@
|
||||
page_title: "binarylane_ssh_key Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_ssh_key (Data Source)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_ssh_key" "example" {
|
||||
id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
### Required
|
||||
|
||||
- `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
|
||||
|
||||
- `default` (Boolean) If `true`, this SSH key will be included on all new server installations (if the operating system supports SSH key injection).
|
||||
- `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.
|
||||
- `public_key` (String) The public key in OpenSSH "authorized_keys" format. This should be a valid public key, such as one generated by `ssh-keygen`.
|
||||
|
||||
@@ -3,23 +3,29 @@
|
||||
page_title: "binarylane_vpc Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_vpc (Data Source)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_vpc" "example" {
|
||||
id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
### Required
|
||||
|
||||
- `id` (Number) The target vpc id.
|
||||
- `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
|
||||
|
||||
- `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.
|
||||
|
||||
@@ -3,14 +3,20 @@
|
||||
page_title: "binarylane_vpc_route_entries Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_vpc_route_entries (Data Source)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "binarylane_vpc_route_entries" "example" {
|
||||
vpc_id = 123456
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -19,15 +25,15 @@ TODO
|
||||
|
||||
- `vpc_id` (Number) The target vpc id.
|
||||
|
||||
### Optional
|
||||
### 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))
|
||||
- `route_entries` (Attributes List) The route entries that control how network traffic is directed through the VPC environment. (see [below for nested schema](#nestedatt--route_entries))
|
||||
|
||||
<a id="nestedatt--route_entries"></a>
|
||||
### Nested Schema for `route_entries`
|
||||
|
||||
Optional:
|
||||
Read-Only:
|
||||
|
||||
- `description` (String)
|
||||
- `destination` (String)
|
||||
- `router` (String)
|
||||
- `description` (String) An optional description for the route.
|
||||
- `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.
|
||||
|
||||
+18
-3
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane Provider"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
@@ -10,12 +9,28 @@ description: |-
|
||||
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
terraform {
|
||||
required_providers {
|
||||
binarylane = {
|
||||
source = "oscarhermoso/binarylane"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "binarylane" {
|
||||
api_token = var.binarylane_api_token
|
||||
|
||||
# Or, set environment variable BINARYLANE_API_TOKEN
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Optional
|
||||
|
||||
- `api_endpoint` (String) Binary Lane API endpoint. If not set checks env for `BINARYLANE_API_ENDPOINT`. Default: `https://api.binarylane.com.au/v2`.
|
||||
- `api_token` (String, Sensitive) Binary Lane API token. If not set checks env for `BINARYLANE_API_TOKEN`.
|
||||
- `api_endpoint` (String) Binary Lane API endpoint. Defaults to `https://api.binarylane.com.au/v2`, but can be overridden by setting this attribute or the `BINARYLANE_API_ENDPOINT` environment variable.
|
||||
- `api_token` (String, Sensitive) Binary Lane API token. If not defined, will default to `BINARYLANE_API_TOKEN` environment variable.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_load_balancer Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Provides a BinaryLane Load Balancer resource. This can be used to create, update, and delete load balancers. See the docs https://support.binarylane.com.au/support/solutions/articles/1000025661-load-balancer for more information.
|
||||
---
|
||||
|
||||
# binarylane_load_balancer (Resource)
|
||||
|
||||
Provides a BinaryLane Load Balancer resource. This can be used to create, update, and delete load balancers. See [the docs](https://support.binarylane.com.au/support/solutions/articles/1000025661-load-balancer) for more information.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "binarylane_server" "example" {
|
||||
count = 2
|
||||
# ...
|
||||
}
|
||||
|
||||
resource "binarylane_load_balancer" "example" {
|
||||
name = "tf-example-lb"
|
||||
forwarding_rules = [{ entry_protocol = "https" }]
|
||||
server_ids = [
|
||||
binarylane_server.example.0.id,
|
||||
binarylane_server.example.1.id
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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` (Set 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.
|
||||
- `ip` (String) The IPv4 address of the load balancer.
|
||||
|
||||
<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.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| http | The load balancer will forward HTTP traffic that matches this rule. |
|
||||
| https | The load balancer will forward HTTPS traffic that matches 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.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| 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. |
|
||||
|
||||
|
||||
<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).
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_load_balancer.example "<name or id>"
|
||||
```
|
||||
@@ -3,37 +3,107 @@
|
||||
page_title: "binarylane_server Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Provides a Binary Lane Server resource. This can be used to create and delete servers.
|
||||
|
||||
---
|
||||
|
||||
# binarylane_server (Resource)
|
||||
|
||||
Provides a Binary Lane Server resource. This can be used to create and delete servers.
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
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
|
||||
public_ipv4_count = 1
|
||||
|
||||
# Accepts a cloud-init script or cloud-config YAML file to configure the server
|
||||
# See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
|
||||
user_data = file("./init.sh")
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `image` (String) The slug of the selected operating system.
|
||||
- `image` (String) The slug of the selected operating system, such as `debian-12`. You can fetch a full list of images from the BinaryLane API.
|
||||
- `public_ipv4_count` (Number) The number of public IPv4 addresses to assign to the server.
|
||||
- `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.
|
||||
- `advanced_features` (Attributes) (see [below for nested schema](#nestedatt--advanced_features))
|
||||
- `backups` (Boolean) If `true` this will enable two daily backups for the server. By default, backups are disabled.
|
||||
- `disk` (Number) The total storage in GB for this server. Leave null to accept the default for the size Valid values:
|
||||
- must be a multiple of 5
|
||||
- \> 60 GB must be a multiple of 10
|
||||
- \> 200 GB must be a multiple of 100
|
||||
- `ipv6` (Boolean) If `true` this will add a public and private IPv6 address to the server. By default, IPv6 is disabled.
|
||||
- `memory` (Number) The total memory in MB for this server. Leave null to accept the default size. Valid values:
|
||||
- must be a multiple of 128
|
||||
- \> 2048 MB must be a multiple of 1024
|
||||
- \> 16384 MB must be a multiple of 2048
|
||||
- \> 24576 MB must be a multiple of 4096
|
||||
- `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.
|
||||
- `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.
|
||||
- `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.
|
||||
- `source_and_destination_check` (Boolean) This attribute can only be set if your server also has a `vpc_id` attribute set. When enabled (which is `true` by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. When `source_and_destination_check` is `false`, your Cloud Server will be able to send and receive packets addressed to any server. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding.
|
||||
- `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) A script or cloud-config YAML file to configure the server. Can only be specified if the OS image supports UserData (i.e. not Windows). See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
|
||||
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
|
||||
- `wait_for_create` (Number) The number of seconds to wait for the server to be created, after which, a timeout error will be reported. If `wait_seconds` is left empty or set to 0, Terraform will succeed without waiting for the server creation to complete.
|
||||
- `vpc_ipv4_address` (String) If provided this will be the IPv4 address for the server's private VPC network adapter. If this is unspecified, then an unused IPv4 address will be assigned. This field is only valid when `vpc_id` is provided.
|
||||
|
||||
### 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.
|
||||
- `private_ipv6_addresses` (List of String) The private IPv6 addresses assigned to the server.
|
||||
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
|
||||
- `public_ipv6_addresses` (List of String) The public IPv6 addresses assigned to the server.
|
||||
|
||||
<a id="nestedatt--advanced_features"></a>
|
||||
### Nested Schema for `advanced_features`
|
||||
|
||||
Optional:
|
||||
|
||||
- `driver_disk` (Boolean) When this option is enabled a copy of the KVM driver disc for Windows ("virtio-win.iso") will be attached to your server as a virtual CD. This option can also be used in combination with your own attached backup when installing Windows.
|
||||
- `emulated_devices` (Boolean) When emulated devices is enabled, the KVM specific "VirtIO" disk drive and network devices are removed, and replaced with emulated versions of physical hardware: an old IDE HDD and an Intel E1000 network card. Emulated devices are much slower than the VirtIO devices, and so this option should not be enabled unless absolutely necessary.
|
||||
- `emulated_hyperv` (Boolean) Enable HyperV (a hypervisor produced by Microsoft) support. Enabled by default on Windows servers, generally of no value for non-Windows servers.
|
||||
- `emulated_tpm` (Boolean) When enabled this provides an emulated TPM v1.2 device to your Cloud Server. Warning: the TPM state is not backed up.
|
||||
- `local_rtc` (Boolean) When a server is booted the virtual BIOS receives the current date and time from the host node. The BIOS does not have an explicit timezone, so the timezone used is implicit and must be understood by the operating system. Most operating systems other than Windows expect the time to be UTC since it allows the operating system to control the timezone used when displaying the time. Our Windows installations have also been customized to use UTC, but when using your own installation of Windows this should be set to the host node's local timezone.
|
||||
- `nested_virt` (Boolean) When this option is enabled the functionality necessary to run your own KVM servers within your server is enabled. Note that all the networking limits - one MAC address per VPS, restricted to specific IPs - still apply to public cloud so this feature is generally only useful in combination with Virtual Private Cloud.
|
||||
- `unset_uuid` (Boolean) When this option is NOT enabled a 128-bit unique identifier is exposed to your server through the virtual BIOS. Each server receives a different UUID. Some proprietary licensed software utilize this identifier to "tie" the license to a specific server.
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `cloud_init` (Boolean) When this option is enabled the Cloud Server will be provided a datasource for the cloud-init service.
|
||||
- `qemu_guest_agent` (Boolean) When this option is enabled the server will allow QEMU Guest Agent to perform password reset without rebooting.
|
||||
- `uefi_boot` (Boolean) When this option is enabled the Cloud Server will use UEFI instead of legacy PC BIOS.
|
||||
|
||||
|
||||
<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).
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_server.example "<server name or id>"
|
||||
```
|
||||
|
||||
@@ -3,14 +3,34 @@
|
||||
page_title: "binarylane_server_firewall_rules Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
Retrieve details about the External Firewall Rules assigned to a BinaryLane server.
|
||||
---
|
||||
|
||||
# binarylane_server_firewall_rules (Resource)
|
||||
|
||||
TODO
|
||||
Retrieve details about the External Firewall Rules assigned to a BinaryLane server.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
}
|
||||
|
||||
resource "binarylane_server_firewall_rules" "example" {
|
||||
server_id = binarylane_server.example.id
|
||||
firewall_rules = [
|
||||
{
|
||||
description = "Allow SSH"
|
||||
protocol = "tcp"
|
||||
source_addresses = ["0.0.0.0/0"]
|
||||
destination_addresses = [binarylane_server.example.private_ipv4_addresses.0]
|
||||
destination_ports = ["22"]
|
||||
action = "accept"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -18,9 +38,6 @@ TODO
|
||||
### 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))
|
||||
|
||||
### Optional
|
||||
|
||||
- `server_id` (Number) The ID of the server for which firewall rules should be listed.
|
||||
|
||||
<a id="nestedatt--firewall_rules"></a>
|
||||
@@ -29,11 +46,33 @@ TODO
|
||||
Required:
|
||||
|
||||
- `action` (String) The action to take when there is a match on this rule.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| drop | Traffic matching this rule will be dropped. |
|
||||
| accept | Traffic matching this rule will be accepted. |
|
||||
- `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.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| all | This rule will match any protocol. |
|
||||
| icmp | This rule will match ICMP traffic only. |
|
||||
| tcp | This rule will match TCP traffic only. |
|
||||
| udp | This rule will match UDP traffic only. |
|
||||
- `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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_server_firewall_rules.example "<server name or id>"
|
||||
```
|
||||
|
||||
@@ -3,14 +3,28 @@
|
||||
page_title: "binarylane_ssh_key Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_ssh_key (Resource)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "binarylane_ssh_key" "example" {
|
||||
name = "tf-example"
|
||||
|
||||
# Generate with: ssh-keygen -t ed25519 -C "name@example.com"
|
||||
public_key = file("~/.ssh/id_ed25519.pub")
|
||||
}
|
||||
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
ssh_keys = [binarylane_ssh_key.example.id]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -18,9 +32,23 @@ TODO
|
||||
### Required
|
||||
|
||||
- `name` (String) A name to help you identify the key.
|
||||
- `public_key` (String) The public key in OpenSSH "authorized_keys" format.
|
||||
- `public_key` (String) The public key in OpenSSH "authorized_keys" format. This should be a valid public key, such as one generated by `ssh-keygen`.
|
||||
|
||||
### Optional
|
||||
|
||||
- `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.
|
||||
- `default` (Boolean) If `true`, this SSH key will be included on all new server installations (if the operating system supports SSH key injection). Default is `false`.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `fingerprint` (String) The fingerprint of the SSH key.
|
||||
- `id` (Number) The numeric ID of the SSH key.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_ssh_key.example "<SSH Key ID or fingerprint>"
|
||||
```
|
||||
|
||||
+28
-3
@@ -3,14 +3,26 @@
|
||||
page_title: "binarylane_vpc Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_vpc (Resource)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = "tf-example-vpc"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -21,5 +33,18 @@ TODO
|
||||
|
||||
### Optional
|
||||
|
||||
- `id` (Number) The target vpc id.
|
||||
- `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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_vpc.example "<vpc_id>"
|
||||
```
|
||||
|
||||
@@ -3,14 +3,42 @@
|
||||
page_title: "binarylane_vpc_route_entries Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
TODO
|
||||
|
||||
---
|
||||
|
||||
# binarylane_vpc_route_entries (Resource)
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = "tf-example-vpc"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "web" {
|
||||
# ...
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
@@ -34,3 +62,13 @@ Required:
|
||||
Optional:
|
||||
|
||||
- `description` (String) An optional description for the route.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||
|
||||
```shell
|
||||
terraform import binarylane_vpc_route_entries.example "<vpc_id>"
|
||||
```
|
||||
|
||||
+2
-1
@@ -3,8 +3,9 @@
|
||||
In order of increasing complexity, these examples show how to use the provider.
|
||||
|
||||
- [./basic](./basic/main.tf) - A basic example that creates a server.
|
||||
- [./offerings](./offerings/main.tf) - Use data sources to list available images, regions, and sizes.
|
||||
- [./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.
|
||||
- [./k3s](./k3s/main.tf) - Create Kubernetes cluster using k3s.
|
||||
- [./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.
|
||||
|
||||
@@ -11,10 +11,10 @@ provider "binarylane" {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
#cloud-config
|
||||
bootcmd:
|
||||
# By default, the provider won't wait for the server installation to complete.
|
||||
#
|
||||
# If you want Terraform to wait until the server has fully provisioned, set
|
||||
# the `wait_for_create` attribute on the `binarylane_server` resource.
|
||||
#
|
||||
# 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, instance, 'example-installing', sh, -c, 'sleep 60'] # pretend this is a slow installation step
|
||||
- [
|
||||
cloud-init-per,
|
||||
once,
|
||||
instance,
|
||||
'example-done',
|
||||
sh,
|
||||
-c,
|
||||
|
||||
@@ -9,10 +9,10 @@ terraform {
|
||||
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")
|
||||
wait_for_create = 300 # 5 mins
|
||||
name = "tf-cloud-init-example"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
user_data = file("./cloud-config.yml")
|
||||
public_ipv4_count = 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
data "binarylane_images" "example" {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_load_balancer" "example" {
|
||||
id = 123456
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
data "binarylane_regions" "example" {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_server" "example" {
|
||||
id = 123456
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_server_firewall_rules" "example" {
|
||||
server_id = 123456
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
data "binarylane_sizes" "example" {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_ssh_key" "example" {
|
||||
id = 123456
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_vpc" "example" {
|
||||
id = 123456
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "binarylane_vpc_route_entries" "example" {
|
||||
vpc_id = 123456
|
||||
}
|
||||
@@ -35,3 +35,7 @@ override.tf.json
|
||||
# Ignore CLI configuration files
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# .kube/config contains credentials
|
||||
.kube
|
||||
.id_ed25519*
|
||||
|
||||
+14
-3
@@ -1,7 +1,18 @@
|
||||
# WIP - examples/k8s
|
||||
# examples/k3s
|
||||
|
||||
This WIP example shows how to create a Kubernetes cluster on Binary Lane.
|
||||
This example shows how to create a K3s cluster on Binary Lane.
|
||||
|
||||
After setting up the cluster by running `terraform apply`, the kubeconfig file will be downloaded to the `.kube/config` file in the current directory. You can then use `kubectl` to interact with the cluster, for example:
|
||||
|
||||
The SSH key used to access the servers is stored in the `.id_ed25519` file. You can use it to SSH into the servers:
|
||||
|
||||
```sh
|
||||
ssh -o "IdentitiesOnly=yes" -i ./.id_ed25519 root@neptune-stand.bnr.la
|
||||
```
|
||||
|
||||
## Prior art
|
||||
|
||||
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
|
||||
- https://github.com/xunleii/terraform-module-k3s
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/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
|
||||
|
||||
echo "K3s Node Join Completed"
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/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
|
||||
|
||||
echo "K3s Setup Completed"
|
||||
@@ -0,0 +1,50 @@
|
||||
module "k3s" {
|
||||
# source = "xunleii/k3s/module" # Uncomment after https://github.com/xunleii/terraform-module-k3s/pull/207 is merged
|
||||
source = "github.com/dvdmuckle/terraform-module-k3s?ref=62dca7731b78f1120a141a2703e3ed14470276c0"
|
||||
|
||||
depends_on_ = binarylane_server.agents
|
||||
k3s_version = "latest"
|
||||
cluster_domain = "cluster.local"
|
||||
managed_fields = ["label"]
|
||||
|
||||
// These subnets are used by the Flannel network interface in K3s, and are NOT the same as the VPC address space
|
||||
cidr = {
|
||||
pods = "10.42.0.0/16",
|
||||
services = "10.43.0.0/16",
|
||||
}
|
||||
|
||||
servers = {
|
||||
for i in range(length(binarylane_server.servers)) :
|
||||
binarylane_server.servers[i].name => {
|
||||
ip = binarylane_server.servers[i].private_ipv4_addresses[0]
|
||||
name = binarylane_server.servers[i].name
|
||||
connection = {
|
||||
host = binarylane_server.servers[i].permalink
|
||||
private_key = trimspace(tls_private_key.ed25519_provisioning.private_key_pem)
|
||||
}
|
||||
labels = { "node.kubernetes.io/type" = "master" }
|
||||
flags = [
|
||||
"--tls-san ${binarylane_server.servers[0].private_ipv4_addresses[0]}",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
agents = {
|
||||
for i in range(length(binarylane_server.agents)) :
|
||||
binarylane_server.agents[i].name => {
|
||||
ip = binarylane_server.agents[i].private_ipv4_addresses[0]
|
||||
name = binarylane_server.agents[i].name
|
||||
connection = {
|
||||
host = binarylane_server.agents[i].permalink
|
||||
private_key = trimspace(tls_private_key.ed25519_provisioning.private_key_pem)
|
||||
}
|
||||
labels = { "node.kubernetes.io/pool" = "general" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "local_sensitive_file" "kube_config" {
|
||||
content = module.k3s.kube_config
|
||||
filename = ".kube/config"
|
||||
file_permission = "0644"
|
||||
}
|
||||
+58
-49
@@ -1,68 +1,60 @@
|
||||
locals {
|
||||
cluster_id = "tf-example-k8s"
|
||||
custom_domain = "tf-example-k8s.internal"
|
||||
cluster_id = "tf-example-k3s"
|
||||
custom_domain = "tf-example-k3s.internal"
|
||||
}
|
||||
|
||||
resource "random_password" "binarylane" {
|
||||
length = 18
|
||||
}
|
||||
|
||||
resource "random_password" "cluster_token" {
|
||||
length = 64
|
||||
special = false
|
||||
# SSH Key
|
||||
# -------
|
||||
resource "tls_private_key" "ed25519_provisioning" {
|
||||
algorithm = "ED25519"
|
||||
}
|
||||
|
||||
resource "local_sensitive_file" "ssh_private_key" {
|
||||
content = tls_private_key.ed25519_provisioning.private_key_openssh
|
||||
filename = ".id_ed25519"
|
||||
file_permission = "0600"
|
||||
}
|
||||
|
||||
resource "local_file" "ssh_public_key" {
|
||||
content = tls_private_key.ed25519_provisioning.public_key_openssh
|
||||
filename = ".id_ed25519.pub"
|
||||
file_permission = "0644"
|
||||
}
|
||||
|
||||
resource "binarylane_ssh_key" "example" {
|
||||
name = "tf-example-k3s"
|
||||
public_key = tls_private_key.ed25519_provisioning.public_key_openssh
|
||||
}
|
||||
|
||||
# Virtual Private Cloud
|
||||
# ---------------------
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = local.cluster_id
|
||||
ip_range = "10.240.0.0/16"
|
||||
ip_range = "10.0.0.0/8"
|
||||
}
|
||||
|
||||
# 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" {
|
||||
resource "binarylane_server" "servers" {
|
||||
count = 1
|
||||
|
||||
name = "${local.cluster_id}-server-${count.index + 1}"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
size = "std-1vcpu" # 2GB memory, 1 vCPU
|
||||
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)
|
||||
}
|
||||
|
||||
# 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" {
|
||||
resource "binarylane_server" "agents" {
|
||||
count = 2
|
||||
|
||||
name = "${local.cluster_id}-agent-${count.index + 1}"
|
||||
@@ -70,14 +62,14 @@ resource "binarylane_server" "agent" {
|
||||
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" {
|
||||
resource "binarylane_vpc_route_entries" "example" { # TODO
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
route_entries = [
|
||||
# {
|
||||
@@ -88,23 +80,24 @@ resource "binarylane_vpc_route_entries" "example" {
|
||||
]
|
||||
}
|
||||
|
||||
locals {
|
||||
agent_ips = flatten([for _, a in binarylane_server.agents : a.private_ipv4_addresses])
|
||||
server_ips = flatten([for _, s in binarylane_server.servers : s.private_ipv4_addresses])
|
||||
}
|
||||
|
||||
resource "binarylane_server_firewall_rules" "example" {
|
||||
for_each = { for server in flatten([binarylane_server.server, binarylane_server.agent]) : server.id => server }
|
||||
for_each = { for server in concat(binarylane_server.servers, binarylane_server.agents) : server.name => server }
|
||||
|
||||
server_id = each.value.id
|
||||
|
||||
firewall_rules = [
|
||||
{
|
||||
description = "K3s supervisor and Kubernetes API Server"
|
||||
protocol = "tcp"
|
||||
source_addresses = flatten([ # agents
|
||||
for _, agent in binarylane_server.agent : agent.private_ipv4_addresses
|
||||
])
|
||||
destination_addresses = flatten([ # server_ips
|
||||
for _, server in binarylane_server.server : server.private_ipv4_addresses
|
||||
])
|
||||
destination_ports = ["6443"]
|
||||
action = "accept"
|
||||
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"
|
||||
@@ -122,5 +115,21 @@ resource "binarylane_server_firewall_rules" "example" {
|
||||
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"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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
|
||||
|
||||
# Ignore SSH keys
|
||||
.id_ed25519*
|
||||
.kube
|
||||
@@ -9,10 +9,10 @@ terraform {
|
||||
provider "binarylane" {}
|
||||
|
||||
resource "binarylane_server" "example" {
|
||||
name = "tf-nix-example"
|
||||
region = "per"
|
||||
image = "debian-12"
|
||||
size = "std-min"
|
||||
user_data = file("./cloud-config.yml")
|
||||
wait_for_create = 300 # 5 mins
|
||||
name = "tf-nix-example"
|
||||
region = "per"
|
||||
image = "debian-12"
|
||||
size = "std-min"
|
||||
user_data = file("./cloud-config.yml")
|
||||
public_ipv4_count = 1
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
# examples/offerings
|
||||
|
||||
You can follow this example to list the available offerings for servers.
|
||||
|
||||
Feel free to customize this example further to filter by specific regions, operating systems, or server sizes to suit your needs.
|
||||
|
||||
|
||||
```sh
|
||||
terraform init
|
||||
terraform apply -refresh-only
|
||||
terraform output -json | jq -r 'with_entries(.value |= .value)'
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```json
|
||||
{
|
||||
"images": [
|
||||
"alma-8",
|
||||
"alma-9",
|
||||
"byo-os",
|
||||
"byo-os-virtio-disabled",
|
||||
"debian-12",
|
||||
"ubuntu-20.04-neon-desktop",
|
||||
"rocky-8",
|
||||
"rocky-9",
|
||||
"ubuntu-22.04",
|
||||
"ubuntu-24.04",
|
||||
"cpanel-plus-whm",
|
||||
"debian-11",
|
||||
"ubuntu-20.04.6",
|
||||
"ubuntu-22.04-desktop",
|
||||
"windows-2022",
|
||||
"windows-2022-sql-2019-web",
|
||||
"windows-2022-sql-2019-std",
|
||||
"windows-2016",
|
||||
"windows-2019",
|
||||
"windows-2016-sql-2016-web",
|
||||
"windows-2019-sql-2017-web",
|
||||
"windows-2019-sql-2017-std"
|
||||
],
|
||||
"regions": [
|
||||
"syd",
|
||||
"mel",
|
||||
"bne",
|
||||
"per",
|
||||
"sin"
|
||||
],
|
||||
"sizes": [
|
||||
"std-min",
|
||||
"std-1vcpu",
|
||||
"std-2vcpu",
|
||||
"std-4vcpu",
|
||||
"std-6vcpu",
|
||||
"std-8vcpu",
|
||||
"cpu-2thr",
|
||||
"cpu-4thr",
|
||||
"cpu-6thr",
|
||||
"cpu-8thr",
|
||||
"hdd-500gb",
|
||||
"hdd-1000gb",
|
||||
"hdd-2000gb",
|
||||
"ded-e2136-400gb",
|
||||
"ded-e2136-800gb",
|
||||
"ded-e2288g-800gb",
|
||||
"ded-3900x-1600gb"
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
binarylane = {
|
||||
source = "oscarhermoso/binarylane"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "binarylane" {
|
||||
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
|
||||
}
|
||||
|
||||
data "binarylane_images" "example" {}
|
||||
|
||||
data "binarylane_sizes" "example" {}
|
||||
|
||||
data "binarylane_regions" "example" {}
|
||||
|
||||
output "images" {
|
||||
value = data.binarylane_images.example.images[*].slug
|
||||
}
|
||||
|
||||
output "sizes" {
|
||||
value = data.binarylane_sizes.example.sizes[*].slug
|
||||
}
|
||||
|
||||
output "regions" {
|
||||
value = data.binarylane_regions.example.regions[*].slug
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
binarylane = {
|
||||
source = "oscarhermoso/binarylane"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "binarylane" {
|
||||
api_token = var.binarylane_api_token
|
||||
|
||||
# Or, set environment variable BINARYLANE_API_TOKEN
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_load_balancer.example "<name or id>"
|
||||
@@ -0,0 +1,13 @@
|
||||
resource "binarylane_server" "example" {
|
||||
count = 2
|
||||
# ...
|
||||
}
|
||||
|
||||
resource "binarylane_load_balancer" "example" {
|
||||
name = "tf-example-lb"
|
||||
forwarding_rules = [{ entry_protocol = "https" }]
|
||||
server_ids = [
|
||||
binarylane_server.example.0.id,
|
||||
binarylane_server.example.1.id
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_server.example "<server name or id>"
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
public_ipv4_count = 1
|
||||
|
||||
# Accepts a cloud-init script or cloud-config YAML file to configure the server
|
||||
# See more: https://cloudinit.readthedocs.io/en/latest/explanation/format.html#user-data-script
|
||||
user_data = file("./init.sh")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_server_firewall_rules.example "<server name or id>"
|
||||
@@ -0,0 +1,17 @@
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
}
|
||||
|
||||
resource "binarylane_server_firewall_rules" "example" {
|
||||
server_id = binarylane_server.example.id
|
||||
firewall_rules = [
|
||||
{
|
||||
description = "Allow SSH"
|
||||
protocol = "tcp"
|
||||
source_addresses = ["0.0.0.0/0"]
|
||||
destination_addresses = [binarylane_server.example.private_ipv4_addresses.0]
|
||||
destination_ports = ["22"]
|
||||
action = "accept"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_ssh_key.example "<SSH Key ID or fingerprint>"
|
||||
@@ -0,0 +1,11 @@
|
||||
resource "binarylane_ssh_key" "example" {
|
||||
name = "tf-example"
|
||||
|
||||
# Generate with: ssh-keygen -t ed25519 -C "name@example.com"
|
||||
public_key = file("~/.ssh/id_ed25519.pub")
|
||||
}
|
||||
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
ssh_keys = [binarylane_ssh_key.example.id]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_vpc.example "<vpc_id>"
|
||||
@@ -0,0 +1,9 @@
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = "tf-example-vpc"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "example" {
|
||||
# ...
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import binarylane_vpc_route_entries.example "<vpc_id>"
|
||||
@@ -0,0 +1,25 @@
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = "tf-example-vpc"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "web" {
|
||||
# ...
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
-24
@@ -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",
|
||||
]
|
||||
}
|
||||
+25
-11
@@ -8,20 +8,34 @@ terraform {
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
+10
-9
@@ -9,13 +9,13 @@ terraform {
|
||||
provider "binarylane" {}
|
||||
|
||||
resource "binarylane_vpc" "example" {
|
||||
name = "tf-example"
|
||||
name = "tf-example-vpc"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
# Web & NAT server
|
||||
resource "binarylane_server" "web" {
|
||||
name = "tf-vpc-example-web"
|
||||
name = "tf-example-vpc-web"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
@@ -25,7 +25,7 @@ resource "binarylane_server" "web" {
|
||||
|
||||
# Database server
|
||||
resource "binarylane_server" "db" {
|
||||
name = "tf-vpc-example-db"
|
||||
name = "tf-example-vpc-db"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
@@ -35,12 +35,13 @@ resource "binarylane_server" "db" {
|
||||
|
||||
# VPN server
|
||||
resource "binarylane_server" "vpn" {
|
||||
name = "tf-vpc-example-vpn"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
public_ipv4_count = 1
|
||||
name = "tf-example-vpc-vpn"
|
||||
region = "per"
|
||||
image = "ubuntu-24.04"
|
||||
size = "std-min"
|
||||
vpc_id = binarylane_vpc.example.id
|
||||
public_ipv4_count = 1
|
||||
source_and_destination_check = false
|
||||
}
|
||||
|
||||
resource "binarylane_vpc_route_entries" "example" {
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
//go:generate ./internal/binarylane/fetch-openapi.sh
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=internal/binarylane/client.cfg.yml internal/binarylane/openapi.json
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=internal/binarylane/types.cfg.yml internal/binarylane/openapi.json
|
||||
//go:generate go run github.com/hashicorp/terraform-plugin-codegen-openapi/cmd/tfplugingen-openapi generate --config=./provider_gen_config.yml internal/binarylane/openapi.json --output=./provider_code_spec.json
|
||||
//go:generate ./provider-transform.sh
|
||||
//go:generate ./scripts/fetch-openapi.sh
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=internal/binarylane/client.cfg.yml ./openapi.json
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=internal/binarylane/types.cfg.yml ./openapi.json
|
||||
//go:generate go run github.com/hashicorp/terraform-plugin-codegen-openapi/cmd/tfplugingen-openapi generate --config=./provider_gen_config.yml ./openapi.json --output=./provider_code_spec.json
|
||||
//go:generate ./scripts/extend-provider.sh
|
||||
//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
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
module terraform-provider-binarylane
|
||||
|
||||
go 1.22.3
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
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-go v0.23.0
|
||||
github.com/hashicorp/terraform-plugin-docs v0.23.0
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1
|
||||
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0
|
||||
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0
|
||||
github.com/hashicorp/terraform-plugin-testing v1.10.0
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/hashicorp/terraform-plugin-testing v1.13.3
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0
|
||||
github.com/oapi-codegen/runtime v1.1.2
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,53 +25,52 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
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/ProtonMail/go-crypto v1.1.6 // 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.9.1 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // 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.132.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/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/cli v1.1.6 // indirect
|
||||
github.com/hashicorp/cli v1.1.7 // 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-cty v1.5.0 // 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.7.0 // 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/hcl/v2 v2.21.0 // indirect
|
||||
github.com/hashicorp/hc-install v0.9.2 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.23.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-sdk/v2 v2.34.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.23.1 // indirect
|
||||
github.com/hashicorp/terraform-json v0.27.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0 // 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/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -77,13 +79,17 @@ require (
|
||||
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/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/oklog/run v1.2.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/jsonpath v0.6.0 // indirect
|
||||
github.com/speakeasy-api/openapi-overlay v0.10.2 // 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
|
||||
@@ -93,22 +99,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
|
||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.27.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/tools v0.22.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -11,10 +11,10 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
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/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/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=
|
||||
@@ -30,23 +30,24 @@ 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/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/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepmap/oapi-codegen v1.16.3 h1:GT9G86SbQtT1r8ZB+4Cybi9VGdu1P5ieNvNdEoCSbrA=
|
||||
github.com/deepmap/oapi-codegen v1.16.3/go.mod h1:JD6ErqeX0nYnhdciLc61Konj3NBASREMlkHOgHn8WAM=
|
||||
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
||||
@@ -55,31 +56,35 @@ 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.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
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-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
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/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
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=
|
||||
@@ -97,14 +102,14 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8=
|
||||
github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4=
|
||||
github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
|
||||
github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -113,15 +118,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-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0=
|
||||
github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM=
|
||||
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.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
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=
|
||||
@@ -129,42 +134,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/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/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24=
|
||||
github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||
github.com/hashicorp/hcl/v2 v2.23.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-exec v0.23.1 h1:diK5NSSDXDKqHEOIQefBMu9ny+FhzwlwV0xgUTB7VTo=
|
||||
github.com/hashicorp/terraform-exec v0.23.1/go.mod h1:e4ZEg9BJDRaSalGm2z8vvrPONt0XWG0/tXpmzYTf+dM=
|
||||
github.com/hashicorp/terraform-json v0.27.0 h1:REIlFzMMkIyTbhq69NC30bYiUYLv7iVhwM8ObnLo0p8=
|
||||
github.com/hashicorp/terraform-json v0.27.0/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
|
||||
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.23.0 h1:sipnfD4/9EJBg9zekym+s1H6qmLAKJHhGWBwvN9v/hE=
|
||||
github.com/hashicorp/terraform-plugin-docs v0.23.0/go.mod h1:J4b5AtMRgJlDrwCQz+G4hKABgHY5m56PnsRmdAzBwW8=
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA=
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
|
||||
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0 h1:Vv16e7EW4nT9668IV0RhdpEmnLl0im7BZx6J+QMlUkg=
|
||||
github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0/go.mod h1:rpHo9hZLn4vEkvNL5xsSdLRdaDZKSinuc0xL+BdOpVA=
|
||||
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw=
|
||||
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA=
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
|
||||
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-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM=
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA=
|
||||
github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo=
|
||||
github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE=
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
|
||||
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=
|
||||
@@ -173,12 +180,10 @@ 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/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=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
||||
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
@@ -195,11 +200,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
@@ -221,12 +225,16 @@ 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/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/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU=
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w=
|
||||
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
|
||||
github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
|
||||
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=
|
||||
@@ -244,26 +252,31 @@ github.com/pb33f/libopenapi v0.15.0 h1:AoBYIY3HXqDDF8O9kcudlqWaRFZZJmgtueE649oHz
|
||||
github.com/pb33f/libopenapi v0.15.0/go.mod h1:m+4Pwri31UvcnZjuP8M7TlbR906DXJmMvYsbis234xg=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
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=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
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/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
||||
github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
|
||||
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
|
||||
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
|
||||
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=
|
||||
@@ -274,8 +287,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.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=
|
||||
@@ -300,29 +313,41 @@ 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 v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
|
||||
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
|
||||
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=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
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=
|
||||
@@ -333,14 +358,14 @@ 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -363,40 +388,40 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
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=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
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-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
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=
|
||||
@@ -405,8 +430,8 @@ 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.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
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=
|
||||
@@ -425,6 +450,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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+325
-2189
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
OPENAPI_FILE=$(dirname "$0")/openapi.json
|
||||
|
||||
# Fetch the latest OpenAPI spec
|
||||
curl https://api.binarylane.com.au/reference/openapi.json --output $OPENAPI_FILE
|
||||
|
||||
# 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 '.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 Go 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
|
||||
+670
-999
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,776 @@
|
||||
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
|
||||
|
||||
package data_sources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"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/datasource/schema"
|
||||
)
|
||||
|
||||
func RegionsDataSourceSchema(ctx context.Context) schema.Schema {
|
||||
return schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"regions": schema.ListNestedAttribute{
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"available": schema.BoolAttribute{
|
||||
Computed: true,
|
||||
Description: "Whether this region is available for the allocation of new resources.",
|
||||
MarkdownDescription: "Whether this region is available for the allocation of new resources.",
|
||||
},
|
||||
"features": schema.ListAttribute{
|
||||
ElementType: types.StringType,
|
||||
Computed: true,
|
||||
Description: "A list of features available for resources in this region.",
|
||||
MarkdownDescription: "A list of features available for resources in this region.",
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The name of this region.",
|
||||
MarkdownDescription: "The name of this region.",
|
||||
},
|
||||
"name_servers": schema.ListAttribute{
|
||||
ElementType: types.StringType,
|
||||
Computed: true,
|
||||
Description: "A list of nameservers available for resources in this region.",
|
||||
MarkdownDescription: "A list of nameservers available for resources in this region.",
|
||||
},
|
||||
"sizes": schema.ListAttribute{
|
||||
ElementType: types.StringType,
|
||||
Computed: true,
|
||||
Description: "The slugs of the sizes available in this region.",
|
||||
MarkdownDescription: "The slugs of the sizes available in this region.",
|
||||
},
|
||||
"slug": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The unique slug for this region.",
|
||||
MarkdownDescription: "The unique slug for this region.",
|
||||
},
|
||||
},
|
||||
CustomType: RegionsType{
|
||||
ObjectType: types.ObjectType{
|
||||
AttrTypes: RegionsValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
},
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type RegionsModel struct {
|
||||
Regions types.List `tfsdk:"regions"`
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectTypable = RegionsType{}
|
||||
|
||||
type RegionsType struct {
|
||||
basetypes.ObjectType
|
||||
}
|
||||
|
||||
func (t RegionsType) Equal(o attr.Type) bool {
|
||||
other, ok := o.(RegionsType)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.ObjectType.Equal(other.ObjectType)
|
||||
}
|
||||
|
||||
func (t RegionsType) String() string {
|
||||
return "RegionsType"
|
||||
}
|
||||
|
||||
func (t RegionsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributes := in.Attributes()
|
||||
|
||||
availableAttribute, ok := attributes["available"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`available is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
availableVal, ok := availableAttribute.(basetypes.BoolValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`available expected to be basetypes.BoolValue, was: %T`, availableAttribute))
|
||||
}
|
||||
|
||||
featuresAttribute, ok := attributes["features"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`features is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
featuresVal, ok := featuresAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`features expected to be basetypes.ListValue, was: %T`, featuresAttribute))
|
||||
}
|
||||
|
||||
nameAttribute, ok := attributes["name"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`name is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
nameVal, ok := nameAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute))
|
||||
}
|
||||
|
||||
nameServersAttribute, ok := attributes["name_servers"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`name_servers is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
nameServersVal, ok := nameServersAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`name_servers expected to be basetypes.ListValue, was: %T`, nameServersAttribute))
|
||||
}
|
||||
|
||||
sizesAttribute, ok := attributes["sizes"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`sizes is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
sizesVal, ok := sizesAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`sizes expected to be basetypes.ListValue, was: %T`, sizesAttribute))
|
||||
}
|
||||
|
||||
slugAttribute, ok := attributes["slug"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`slug is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
slugVal, ok := slugAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`slug expected to be basetypes.StringValue, was: %T`, slugAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return RegionsValue{
|
||||
Available: availableVal,
|
||||
Features: featuresVal,
|
||||
Name: nameVal,
|
||||
NameServers: nameServersVal,
|
||||
Sizes: sizesVal,
|
||||
Slug: slugVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewRegionsValueNull() RegionsValue {
|
||||
return RegionsValue{
|
||||
state: attr.ValueStateNull,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRegionsValueUnknown() RegionsValue {
|
||||
return RegionsValue{
|
||||
state: attr.ValueStateUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRegionsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (RegionsValue, 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 RegionsValue Attribute Value",
|
||||
"While creating a RegionsValue value, a missing attribute value was detected. "+
|
||||
"A RegionsValue 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("RegionsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !attributeType.Equal(attribute.Type(ctx)) {
|
||||
diags.AddError(
|
||||
"Invalid RegionsValue Attribute Type",
|
||||
"While creating a RegionsValue value, an invalid attribute value was detected. "+
|
||||
"A RegionsValue 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("RegionsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
|
||||
fmt.Sprintf("RegionsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for name := range attributes {
|
||||
_, ok := attributeTypes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Extra RegionsValue Attribute Value",
|
||||
"While creating a RegionsValue value, an extra attribute value was detected. "+
|
||||
"A RegionsValue 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 RegionsValue Attribute Name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
availableAttribute, ok := attributes["available"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`available is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
availableVal, ok := availableAttribute.(basetypes.BoolValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`available expected to be basetypes.BoolValue, was: %T`, availableAttribute))
|
||||
}
|
||||
|
||||
featuresAttribute, ok := attributes["features"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`features is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
featuresVal, ok := featuresAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`features expected to be basetypes.ListValue, was: %T`, featuresAttribute))
|
||||
}
|
||||
|
||||
nameAttribute, ok := attributes["name"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`name is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
nameVal, ok := nameAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute))
|
||||
}
|
||||
|
||||
nameServersAttribute, ok := attributes["name_servers"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`name_servers is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
nameServersVal, ok := nameServersAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`name_servers expected to be basetypes.ListValue, was: %T`, nameServersAttribute))
|
||||
}
|
||||
|
||||
sizesAttribute, ok := attributes["sizes"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`sizes is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
sizesVal, ok := sizesAttribute.(basetypes.ListValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`sizes expected to be basetypes.ListValue, was: %T`, sizesAttribute))
|
||||
}
|
||||
|
||||
slugAttribute, ok := attributes["slug"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`slug is missing from object`)
|
||||
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
slugVal, ok := slugAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`slug expected to be basetypes.StringValue, was: %T`, slugAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewRegionsValueUnknown(), diags
|
||||
}
|
||||
|
||||
return RegionsValue{
|
||||
Available: availableVal,
|
||||
Features: featuresVal,
|
||||
Name: nameVal,
|
||||
NameServers: nameServersVal,
|
||||
Sizes: sizesVal,
|
||||
Slug: slugVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewRegionsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) RegionsValue {
|
||||
object, diags := NewRegionsValue(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("NewRegionsValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (t RegionsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
|
||||
if in.Type() == nil {
|
||||
return NewRegionsValueNull(), 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 NewRegionsValueUnknown(), nil
|
||||
}
|
||||
|
||||
if in.IsNull() {
|
||||
return NewRegionsValueNull(), 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 NewRegionsValueMust(RegionsValue{}.AttributeTypes(ctx), attributes), nil
|
||||
}
|
||||
|
||||
func (t RegionsType) ValueType(ctx context.Context) attr.Value {
|
||||
return RegionsValue{}
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectValuable = RegionsValue{}
|
||||
|
||||
type RegionsValue struct {
|
||||
Available basetypes.BoolValue `tfsdk:"available"`
|
||||
Features basetypes.ListValue `tfsdk:"features"`
|
||||
Name basetypes.StringValue `tfsdk:"name"`
|
||||
NameServers basetypes.ListValue `tfsdk:"name_servers"`
|
||||
Sizes basetypes.ListValue `tfsdk:"sizes"`
|
||||
Slug basetypes.StringValue `tfsdk:"slug"`
|
||||
state attr.ValueState
|
||||
}
|
||||
|
||||
func (v RegionsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
|
||||
attrTypes := make(map[string]tftypes.Type, 6)
|
||||
|
||||
var val tftypes.Value
|
||||
var err error
|
||||
|
||||
attrTypes["available"] = basetypes.BoolType{}.TerraformType(ctx)
|
||||
attrTypes["features"] = basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
}.TerraformType(ctx)
|
||||
attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
attrTypes["name_servers"] = basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
}.TerraformType(ctx)
|
||||
attrTypes["sizes"] = basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
}.TerraformType(ctx)
|
||||
attrTypes["slug"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
|
||||
objectType := tftypes.Object{AttributeTypes: attrTypes}
|
||||
|
||||
switch v.state {
|
||||
case attr.ValueStateKnown:
|
||||
vals := make(map[string]tftypes.Value, 6)
|
||||
|
||||
val, err = v.Available.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["available"] = val
|
||||
|
||||
val, err = v.Features.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["features"] = val
|
||||
|
||||
val, err = v.Name.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["name"] = val
|
||||
|
||||
val, err = v.NameServers.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["name_servers"] = val
|
||||
|
||||
val, err = v.Sizes.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["sizes"] = val
|
||||
|
||||
val, err = v.Slug.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["slug"] = 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 RegionsValue) IsNull() bool {
|
||||
return v.state == attr.ValueStateNull
|
||||
}
|
||||
|
||||
func (v RegionsValue) IsUnknown() bool {
|
||||
return v.state == attr.ValueStateUnknown
|
||||
}
|
||||
|
||||
func (v RegionsValue) String() string {
|
||||
return "RegionsValue"
|
||||
}
|
||||
|
||||
func (v RegionsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
var featuresVal basetypes.ListValue
|
||||
switch {
|
||||
case v.Features.IsUnknown():
|
||||
featuresVal = types.ListUnknown(types.StringType)
|
||||
case v.Features.IsNull():
|
||||
featuresVal = types.ListNull(types.StringType)
|
||||
default:
|
||||
var d diag.Diagnostics
|
||||
featuresVal, d = types.ListValue(types.StringType, v.Features.Elements())
|
||||
diags.Append(d...)
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return types.ObjectUnknown(map[string]attr.Type{
|
||||
"available": basetypes.BoolType{},
|
||||
"features": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"name": basetypes.StringType{},
|
||||
"name_servers": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"sizes": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"slug": basetypes.StringType{},
|
||||
}), diags
|
||||
}
|
||||
|
||||
var nameServersVal basetypes.ListValue
|
||||
switch {
|
||||
case v.NameServers.IsUnknown():
|
||||
nameServersVal = types.ListUnknown(types.StringType)
|
||||
case v.NameServers.IsNull():
|
||||
nameServersVal = types.ListNull(types.StringType)
|
||||
default:
|
||||
var d diag.Diagnostics
|
||||
nameServersVal, d = types.ListValue(types.StringType, v.NameServers.Elements())
|
||||
diags.Append(d...)
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return types.ObjectUnknown(map[string]attr.Type{
|
||||
"available": basetypes.BoolType{},
|
||||
"features": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"name": basetypes.StringType{},
|
||||
"name_servers": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"sizes": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"slug": basetypes.StringType{},
|
||||
}), diags
|
||||
}
|
||||
|
||||
var sizesVal basetypes.ListValue
|
||||
switch {
|
||||
case v.Sizes.IsUnknown():
|
||||
sizesVal = types.ListUnknown(types.StringType)
|
||||
case v.Sizes.IsNull():
|
||||
sizesVal = types.ListNull(types.StringType)
|
||||
default:
|
||||
var d diag.Diagnostics
|
||||
sizesVal, d = types.ListValue(types.StringType, v.Sizes.Elements())
|
||||
diags.Append(d...)
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return types.ObjectUnknown(map[string]attr.Type{
|
||||
"available": basetypes.BoolType{},
|
||||
"features": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"name": basetypes.StringType{},
|
||||
"name_servers": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"sizes": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"slug": basetypes.StringType{},
|
||||
}), diags
|
||||
}
|
||||
|
||||
attributeTypes := map[string]attr.Type{
|
||||
"available": basetypes.BoolType{},
|
||||
"features": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"name": basetypes.StringType{},
|
||||
"name_servers": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"sizes": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"slug": 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{
|
||||
"available": v.Available,
|
||||
"features": featuresVal,
|
||||
"name": v.Name,
|
||||
"name_servers": nameServersVal,
|
||||
"sizes": sizesVal,
|
||||
"slug": v.Slug,
|
||||
})
|
||||
|
||||
return objVal, diags
|
||||
}
|
||||
|
||||
func (v RegionsValue) Equal(o attr.Value) bool {
|
||||
other, ok := o.(RegionsValue)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != other.state {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != attr.ValueStateKnown {
|
||||
return true
|
||||
}
|
||||
|
||||
if !v.Available.Equal(other.Available) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Features.Equal(other.Features) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Name.Equal(other.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.NameServers.Equal(other.NameServers) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Sizes.Equal(other.Sizes) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Slug.Equal(other.Slug) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v RegionsValue) Type(ctx context.Context) attr.Type {
|
||||
return RegionsType{
|
||||
basetypes.ObjectType{
|
||||
AttrTypes: v.AttributeTypes(ctx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v RegionsValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
|
||||
return map[string]attr.Type{
|
||||
"available": basetypes.BoolType{},
|
||||
"features": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"name": basetypes.StringType{},
|
||||
"name_servers": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"sizes": basetypes.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
"slug": basetypes.StringType{},
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"terraform-provider-binarylane/internal/data_sources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ datasource.DataSource = &imagesDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &imagesDataSource{}
|
||||
)
|
||||
|
||||
func NewImagesDataSource() datasource.DataSource {
|
||||
return &imagesDataSource{}
|
||||
}
|
||||
|
||||
type imagesDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type imagesDataSourceModel struct {
|
||||
data_sources.ImagesModel
|
||||
}
|
||||
|
||||
func (d *imagesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_images"
|
||||
}
|
||||
|
||||
func (d *imagesDataSource) 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 *imagesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = data_sources.ImagesDataSourceSchema(ctx)
|
||||
}
|
||||
|
||||
func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data imagesDataSourceModel
|
||||
|
||||
// Read Terraform configuration data into the model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
var page int32 = 1
|
||||
perPage := int32(200)
|
||||
var nextPage bool = true
|
||||
var imgResults []binarylane.Image
|
||||
|
||||
for nextPage {
|
||||
params := binarylane.GetImagesParams{
|
||||
Page: &page,
|
||||
PerPage: &perPage,
|
||||
Type: data.Type.ValueStringPointer(),
|
||||
}
|
||||
listResp, err := d.bc.client.GetImagesWithResponse(ctx, ¶ms)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error listing images", err.Error())
|
||||
return
|
||||
}
|
||||
if listResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError("Unexpected status code listing images", string(listResp.Body))
|
||||
return
|
||||
}
|
||||
imgResults = append(imgResults, listResp.JSON200.Images...)
|
||||
|
||||
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
images, diags := types.ListValueFrom(ctx, data.Images.ElementType(ctx), imgResults)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
data.Images = images
|
||||
|
||||
// Save data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestImagesDataSource(t *testing.T) {
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Create and Read testing
|
||||
{
|
||||
Config: providerConfig + `
|
||||
data "binarylane_images" "test" {
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Verify data source values
|
||||
resource.TestCheckResourceAttrWith("data.binarylane_images.test", "images.#", func(value string) error {
|
||||
count, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
return fmt.Errorf("expected at least one image, got: %d", count)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_images.test", "images.0.id"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
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 = &loadBalancerDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &loadBalancerDataSource{}
|
||||
)
|
||||
|
||||
func NewLoadBalancerDataSource() datasource.DataSource {
|
||||
return &loadBalancerDataSource{}
|
||||
}
|
||||
|
||||
type loadBalancerDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type loadBalancerDataModel struct {
|
||||
resources.LoadBalancerModel
|
||||
Ip types.String `tfsdk:"ip"`
|
||||
}
|
||||
|
||||
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(
|
||||
loadBalancerSchema(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 = "Retrieve details about a BinaryLane Load Balancer."
|
||||
resp.Schema.MarkdownDescription = resp.Schema.Description
|
||||
}
|
||||
|
||||
func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data loadBalancerDataModel
|
||||
|
||||
// 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, &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)...)
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
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-validators/listvalidator"
|
||||
"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/listdefault"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.Resource = &loadBalancerResource{}
|
||||
_ resource.ResourceWithConfigure = &loadBalancerResource{}
|
||||
_ resource.ResourceWithImportState = &loadBalancerResource{}
|
||||
)
|
||||
|
||||
func NewLoadBalancerResource() resource.Resource {
|
||||
return &loadBalancerResource{}
|
||||
}
|
||||
|
||||
type loadBalancerResource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type loadBalancerResourceModel struct {
|
||||
loadBalancerDataModel
|
||||
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 loadBalancerSchema(ctx context.Context) schema.Schema {
|
||||
s := resources.LoadBalancerResourceSchema(ctx)
|
||||
s.Description = "Provides a BinaryLane Load Balancer resource. This can be used to create, update, and delete load balancers."
|
||||
s.MarkdownDescription = s.Description +
|
||||
" See [the docs](https://support.binarylane.com.au/support/solutions/articles/1000025661-load-balancer) for more information."
|
||||
|
||||
// Overrides
|
||||
id := s.Attributes["id"]
|
||||
s.Attributes["id"] = schema.Int64Attribute{
|
||||
Description: id.GetDescription(),
|
||||
MarkdownDescription: id.GetMarkdownDescription(),
|
||||
// read only
|
||||
Optional: false,
|
||||
Required: false,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
|
||||
region := s.Attributes["region"]
|
||||
s.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(),
|
||||
},
|
||||
}
|
||||
|
||||
forwardingRules := s.Attributes["forwarding_rules"].(schema.ListNestedAttribute)
|
||||
s.Attributes["forwarding_rules"] = schema.ListNestedAttribute{
|
||||
NestedObject: forwardingRules.NestedObject,
|
||||
Description: forwardingRules.GetDescription(),
|
||||
MarkdownDescription: forwardingRules.GetMarkdownDescription(),
|
||||
Required: false,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: listdefault.StaticValue(types.ListValueMust(
|
||||
types.ObjectType{
|
||||
AttrTypes: resources.ForwardingRulesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
[]attr.Value{
|
||||
types.ObjectValueMust(
|
||||
map[string]attr.Type{
|
||||
"entry_protocol": types.StringType,
|
||||
},
|
||||
map[string]attr.Value{
|
||||
"entry_protocol": types.StringValue("http"),
|
||||
},
|
||||
),
|
||||
},
|
||||
)),
|
||||
Validators: []validator.List{
|
||||
listvalidator.SizeAtLeast(1),
|
||||
},
|
||||
}
|
||||
|
||||
// Additional attributes
|
||||
s.Attributes["ip"] = schema.StringAttribute{
|
||||
Description: "The IPv4 address of the load balancer.",
|
||||
MarkdownDescription: "The IPv4 address of the load balancer.",
|
||||
Required: false,
|
||||
Optional: false,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = loadBalancerSchema(ctx)
|
||||
|
||||
resp.Schema.Attributes["timeouts"] =
|
||||
timeouts.Attributes(ctx, timeouts.Opts{
|
||||
Create: true,
|
||||
Update: 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
|
||||
}
|
||||
|
||||
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
|
||||
forwardingRules := []binarylane.ForwardingRuleRequest{}
|
||||
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.HealthCheckRequest{
|
||||
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.loadBalancerDataModel, &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 load balancer to be ready
|
||||
var actionId int64
|
||||
for _, action := range lbResp.JSON200.Links.Actions {
|
||||
if action.Rel == "create_load_balancer" {
|
||||
actionId = action.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = r.waitForAction(ctx, actionId)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error waiting for load balancer to be created", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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.StatusNotFound {
|
||||
tflog.Warn(ctx, fmt.Sprintf("Load balancer not found, removing from state: id=%s, name=%s", data.Id.String(), data.Name.ValueString()))
|
||||
resp.State.RemoveResource(ctx)
|
||||
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.loadBalancerDataModel, &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.ForwardingRuleRequest{}
|
||||
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.HealthCheckRequest{
|
||||
Path: data.HealthCheck.Path.ValueStringPointer(),
|
||||
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
|
||||
},
|
||||
ServerIds: serverIds,
|
||||
}
|
||||
|
||||
timeout, diags := data.Timeouts.Update(ctx, 20*time.Minute)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
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.loadBalancerDataModel, &lbResp.JSON200.LoadBalancer)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for load balancer to be ready
|
||||
var actionId int64
|
||||
for _, action := range lbResp.JSON200.Links.Actions {
|
||||
if action.Rel == "update_load_balancer" {
|
||||
actionId = action.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = r.waitForAction(ctx, actionId)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error waiting for load balancer to be updated", err.Error())
|
||||
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, ¶ms)
|
||||
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.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("id"), loadBalancer.Id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
func setLoadBalancerModelState(ctx context.Context, data *loadBalancerDataModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
|
||||
var diags, diag diag.Diagnostics
|
||||
|
||||
data.Id = types.Int64Value(lb.Id)
|
||||
data.Name = types.StringValue(lb.Name)
|
||||
data.Ip = types.StringValue(lb.Ip)
|
||||
|
||||
if lb.Region == nil {
|
||||
data.Region = types.StringNull()
|
||||
} else {
|
||||
data.Region = types.StringValue(lb.Region.Slug)
|
||||
}
|
||||
|
||||
data.ServerIds, diags = types.SetValueFrom(ctx, types.Int64Type, lb.ServerIds)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) waitForAction(ctx context.Context, actionId int64) error {
|
||||
var lastReadyResp *binarylane.GetActionsActionIdResponse
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if lastReadyResp == nil {
|
||||
return fmt.Errorf("timed out waiting for action: action_id=%d", actionId)
|
||||
} else {
|
||||
return fmt.Errorf("timed out waiting for action: action_id=%d, last response was status=%s, body: %s",
|
||||
actionId, lastReadyResp.Status(), lastReadyResp.Body)
|
||||
}
|
||||
default:
|
||||
readyResp, err := r.bc.client.GetActionsActionIdWithResponse(ctx, actionId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error waiting for action: action_id=%d, error: %w", actionId, err)
|
||||
}
|
||||
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.Status == binarylane.Errored {
|
||||
return fmt.Errorf("action failed with error: action_id=%d, error: %s", actionId, readyResp.Body)
|
||||
}
|
||||
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
|
||||
return nil
|
||||
}
|
||||
lastReadyResp = readyResp
|
||||
tflog.Debug(ctx,
|
||||
fmt.Sprintf("waiting for action for action_id=%d: last response was status=%s, details: %s",
|
||||
actionId, readyResp.Status(), readyResp.Body,
|
||||
),
|
||||
)
|
||||
}
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
)
|
||||
|
||||
func TestLoadBalancerResource(t *testing.T) {
|
||||
// Must assign a password to the server or Binary Lane will send emails
|
||||
password := GenerateTestPassword(t)
|
||||
|
||||
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]
|
||||
# initially, skip defining forwarding_rules, health_check, to test default values
|
||||
}
|
||||
|
||||
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"),
|
||||
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
|
||||
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "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"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.path", "/"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.protocol", "http"),
|
||||
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "ip"),
|
||||
|
||||
// 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"),
|
||||
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
|
||||
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "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"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.path", "/"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.protocol", "http"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_load_balancer.test", "ip"),
|
||||
),
|
||||
},
|
||||
// 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,
|
||||
},
|
||||
{
|
||||
Taint: []string{"binarylane_server.test[0]"},
|
||||
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]
|
||||
# updated
|
||||
forwarding_rules = [{ entry_protocol = "https" }]
|
||||
health_check = {
|
||||
path = "/test-health"
|
||||
protocol = "https"
|
||||
}
|
||||
}
|
||||
|
||||
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"),
|
||||
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
|
||||
testCheckIfResourceAttrContainsAttr("binarylane_load_balancer.test", "server_ids", "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", "https"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.path", "/test-health"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "health_check.protocol", "https"),
|
||||
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "ip"),
|
||||
|
||||
// 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"),
|
||||
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "binarylane_server.test.0", "id"),
|
||||
testCheckIfResourceAttrContainsAttr("data.binarylane_load_balancer.test", "server_ids", "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", "https"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.path", "/test-health"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "health_check.protocol", "https"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_load_balancer.test", "ip"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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, ¶ms)
|
||||
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.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) {
|
||||
ms := s.RootModule()
|
||||
|
||||
rs, ok := ms.Resources[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Not found: %s in %s, resources: %v", name, ms.Path, slices.Collect(maps.Keys(ms.Resources)))
|
||||
}
|
||||
is := rs.Primary
|
||||
if is == nil {
|
||||
return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path)
|
||||
}
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func testCheckIfResourceAttrContainsAttr(collectionKey, collectionPath, otherResourceKey, otherAttrPath string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
collectionState, err := primaryInstanceState(s, collectionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elementState, err := primaryInstanceState(s, otherResourceKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if collectionKey == otherResourceKey && collectionPath == otherAttrPath {
|
||||
return fmt.Errorf(
|
||||
"comparing self: resource %s attribute %s",
|
||||
collectionKey,
|
||||
collectionPath,
|
||||
)
|
||||
}
|
||||
|
||||
vElement, okElement := elementState.Attributes[otherAttrPath]
|
||||
if !okElement {
|
||||
return fmt.Errorf("%s: Attribute %q not set, cannot be contained in %q from %s", otherResourceKey, otherAttrPath, collectionPath, collectionKey)
|
||||
}
|
||||
|
||||
vCollection, okCollection := collectionState.Attributes[collectionPath+".#"]
|
||||
if !okCollection {
|
||||
return fmt.Errorf("%s: Attribute %q not a collection, cannot contain %q from %s", collectionKey, collectionPath, otherAttrPath, otherResourceKey)
|
||||
}
|
||||
|
||||
for attrKey, attrValue := range collectionState.Attributes {
|
||||
if strings.HasPrefix(attrKey, collectionPath+".") &&
|
||||
!(strings.HasSuffix(attrKey, ".%")) &&
|
||||
!strings.HasSuffix(attrKey, ".#") &&
|
||||
attrValue == vElement {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"%s: Attribute '%s' expected to contain %#v, got %#v",
|
||||
collectionKey,
|
||||
collectionPath,
|
||||
vElement,
|
||||
vCollection)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -45,12 +41,12 @@ func (p *binarylaneProvider) Schema(_ context.Context, _ provider.SchemaRequest,
|
||||
resp.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"api_endpoint": schema.StringAttribute{
|
||||
MarkdownDescription: "Binary Lane API endpoint. If not set checks env for `BINARYLANE_API_ENDPOINT`. " +
|
||||
"Default: `https://api.binarylane.com.au/v2`.",
|
||||
MarkdownDescription: "Binary Lane API endpoint. Defaults to `https://api.binarylane.com.au/v2`, but can be " +
|
||||
"overridden by setting this attribute or the `BINARYLANE_API_ENDPOINT` environment variable.",
|
||||
Optional: true,
|
||||
},
|
||||
"api_token": schema.StringAttribute{
|
||||
MarkdownDescription: "Binary Lane API token. If not set checks env for `BINARYLANE_API_TOKEN`.",
|
||||
MarkdownDescription: "Binary Lane API token. If not defined, will default to `BINARYLANE_API_TOKEN` environment variable.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
@@ -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
|
||||
@@ -147,6 +92,10 @@ func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.
|
||||
NewSshKeyDataSource,
|
||||
NewVpcDataSource,
|
||||
NewVpcRouteEntriesDataSource,
|
||||
NewLoadBalancerDataSource,
|
||||
NewImagesDataSource,
|
||||
NewRegionsDataSource,
|
||||
NewSizesDataSource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,5 +106,6 @@ func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Reso
|
||||
NewSshKeyResource,
|
||||
NewVpcResource,
|
||||
NewVpcRouteEntriesResource,
|
||||
NewLoadBalancerResource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ provider "binarylane" {
|
||||
# api_token = "test123"
|
||||
# api_endpoint = "https://test.binarylane.internal.au/v2"
|
||||
}
|
||||
|
||||
# End of provider config
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"terraform-provider-binarylane/internal/data_sources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ datasource.DataSource = ®ionsDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = ®ionsDataSource{}
|
||||
)
|
||||
|
||||
func NewRegionsDataSource() datasource.DataSource {
|
||||
return ®ionsDataSource{}
|
||||
}
|
||||
|
||||
type regionsDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type regionsDataSourceModel struct {
|
||||
data_sources.RegionsModel
|
||||
}
|
||||
|
||||
func (d *regionsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_regions"
|
||||
}
|
||||
|
||||
func (d *regionsDataSource) 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 *regionsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = data_sources.RegionsDataSourceSchema(ctx)
|
||||
}
|
||||
|
||||
func (d *regionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data regionsDataSourceModel
|
||||
|
||||
// Read Terraform configuration data into the model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
var page int32 = 1
|
||||
perPage := int32(200)
|
||||
var nextPage bool = true
|
||||
var regResults []binarylane.Region
|
||||
|
||||
for nextPage {
|
||||
params := binarylane.GetRegionsParams{
|
||||
Page: &page,
|
||||
PerPage: &perPage,
|
||||
}
|
||||
listResp, err := d.bc.client.GetRegionsWithResponse(ctx, ¶ms)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error listing regions", err.Error())
|
||||
return
|
||||
}
|
||||
if listResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError("Unexpected status code listing regions", string(listResp.Body))
|
||||
return
|
||||
}
|
||||
regResults = append(regResults, listResp.JSON200.Regions...)
|
||||
|
||||
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
regions, diags := types.ListValueFrom(ctx, data.Regions.ElementType(ctx), regResults)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
data.Regions = regions
|
||||
|
||||
// Save data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestRegionsDataSource(t *testing.T) {
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Create and Read testing
|
||||
{
|
||||
Config: providerConfig + `
|
||||
data "binarylane_regions" "test" {
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Verify data source values
|
||||
resource.TestCheckResourceAttrWith("data.binarylane_regions.test", "regions.#", func(value string) error {
|
||||
count, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
return fmt.Errorf("expected at least one region, got: %d", count)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_regions.test", "regions.0.slug"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"terraform-provider-binarylane/internal/resources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -25,6 +28,18 @@ type serverDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type serverDataModel struct {
|
||||
resources.ServerModel
|
||||
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
|
||||
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
|
||||
PublicIpv6Addresses types.List `tfsdk:"public_ipv6_addresses"`
|
||||
PrivateIpv6Addresses types.List `tfsdk:"private_ipv6_addresses"`
|
||||
Permalink types.String `tfsdk:"permalink"`
|
||||
Memory types.Int32 `tfsdk:"memory"`
|
||||
Disk types.Int32 `tfsdk:"disk"`
|
||||
SourceAndDestinationCheck types.Bool `tfsdk:"source_and_destination_check"`
|
||||
}
|
||||
|
||||
func (d *serverDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
@@ -48,78 +63,165 @@ func (d *serverDataSource) Metadata(ctx context.Context, req datasource.Metadata
|
||||
}
|
||||
|
||||
func (d *serverDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(resources.ServerResourceSchema(ctx))
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(
|
||||
serverSchema(ctx),
|
||||
AttributeConfig{
|
||||
RequiredAttributes: &[]string{"id"},
|
||||
ExcludedAttributes: &[]string{"password", "public_ipv4_count", "password_change_supported", "timeouts"},
|
||||
})
|
||||
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
|
||||
id := resp.Schema.Attributes["id"]
|
||||
resp.Schema.Attributes["id"] = d_schema.Int64Attribute{
|
||||
Description: id.GetDescription(),
|
||||
MarkdownDescription: id.GetMarkdownDescription(),
|
||||
Required: true, // ID is required to find the server
|
||||
}
|
||||
resp.Schema.Description = "Retrieve details about a BinaryLane Server."
|
||||
}
|
||||
|
||||
func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data resources.ServerModel
|
||||
var diag diag.Diagnostics
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: Use go routines here to make requests concurrently
|
||||
|
||||
// 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", data.Id.String()),
|
||||
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", data.Id.String()),
|
||||
fmt.Sprintf("Unexpected HTTP status code reading server: %s", serverResp.Body),
|
||||
fmt.Sprintf("Unexpected HTTP status code reading server: name=%s, id=%s", data.Name.ValueString(), data.Id.String()),
|
||||
string(serverResp.Body),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Fetch user data
|
||||
|
||||
// Read user data
|
||||
// userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
|
||||
// if err != nil {
|
||||
// resp.Diagnostics.AddError(
|
||||
// fmt.Sprintf("Error reading server user data: name=%s", data.Id.String()),
|
||||
// err.Error(),
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
// if userDataResp.StatusCode() != http.StatusOK {
|
||||
// resp.Diagnostics.AddError(
|
||||
// fmt.Sprintf("Unexpected HTTP status code reading server user data: name=%s", data.Id.String()),
|
||||
// fmt.Sprintf("Unexpected HTTP status code reading server user data: %s", serverResp.Body),
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
|
||||
// Set data values
|
||||
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
|
||||
data.Name = types.StringValue(*serverResp.JSON200.Server.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.UserData = types.StringValue(*userDataResp.JSON200.UserData)
|
||||
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.Ipv6 = types.BoolValue(len(serverResp.JSON200.Server.Networks.V6) > 0)
|
||||
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.Memory = types.Int32Value(serverResp.JSON200.Server.Memory)
|
||||
data.Disk = types.Int32Value(serverResp.JSON200.Server.Disk)
|
||||
data.SourceAndDestinationCheck = types.BoolPointerValue(serverResp.JSON200.Server.Networks.SourceAndDestinationCheck)
|
||||
|
||||
if serverResp.JSON200.Server.VpcId == nil {
|
||||
data.VpcIpv4Address = types.StringNull()
|
||||
} else if len(serverResp.JSON200.Server.Networks.V4) > 0 {
|
||||
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
|
||||
// Skip addresses in 172.21.0.0/16, these are BL internal addresses that are not part of the user's VPC
|
||||
if v4address.Type == "private" && !strings.HasPrefix(v4address.IpAddress, "172.21.") {
|
||||
data.VpcIpv4Address = types.StringValue(v4address.IpAddress)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advFeat := serverResp.JSON200.Server.AdvancedFeatures.EnabledAdvancedFeatures
|
||||
data.AdvancedFeatures, diags = resources.NewAdvancedFeaturesValue(
|
||||
resources.AdvancedFeaturesValue{}.AttributeTypes(ctx),
|
||||
map[string]attr.Value{
|
||||
"emulated_hyperv": types.BoolValue(slices.Contains(advFeat, "emulated-hyperv")),
|
||||
"emulated_devices": types.BoolValue(slices.Contains(advFeat, "emulated-devices")),
|
||||
"nested_virt": types.BoolValue(slices.Contains(advFeat, "nested-virt")),
|
||||
"driver_disk": types.BoolValue(slices.Contains(advFeat, "driver-disk")),
|
||||
"unset_uuid": types.BoolValue(slices.Contains(advFeat, "unset-uuid")),
|
||||
"local_rtc": types.BoolValue(slices.Contains(advFeat, "local-rtc")),
|
||||
"emulated_tpm": types.BoolValue(slices.Contains(advFeat, "emulated-tpm")),
|
||||
"cloud_init": types.BoolValue(slices.Contains(advFeat, "cloud-init")),
|
||||
"qemu_guest_agent": types.BoolValue(slices.Contains(advFeat, "qemu-guest-agent")),
|
||||
"uefi_boot": types.BoolValue(slices.Contains(advFeat, "uefi-boot")),
|
||||
})
|
||||
if diags.HasError() {
|
||||
data.AdvancedFeatures = resources.NewAdvancedFeaturesValueUnknown()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
tfPublicIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
|
||||
diags.Append(diag...)
|
||||
if diag.HasError() {
|
||||
data.PublicIpv4Addresses = types.ListUnknown(data.PublicIpv4Addresses.ElementType(ctx))
|
||||
} else {
|
||||
data.PublicIpv4Addresses = tfPublicIpv4Addresses
|
||||
}
|
||||
|
||||
tfPrivateIpv4Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
|
||||
diags.Append(diag...)
|
||||
if diag.HasError() {
|
||||
data.PrivateIPv4Addresses = types.ListUnknown(data.PrivateIPv4Addresses.ElementType(ctx))
|
||||
} else {
|
||||
data.PrivateIPv4Addresses = tfPrivateIpv4Addresses
|
||||
}
|
||||
|
||||
publicIpv6Addresses := []string{}
|
||||
privateIpv6Addresses := []string{}
|
||||
|
||||
for _, v6address := range serverResp.JSON200.Server.Networks.V6 {
|
||||
if v6address.Type == "public" {
|
||||
publicIpv6Addresses = append(publicIpv6Addresses, v6address.IpAddress)
|
||||
} else {
|
||||
privateIpv6Addresses = append(privateIpv6Addresses, v6address.IpAddress)
|
||||
}
|
||||
}
|
||||
|
||||
tfPublicIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, publicIpv6Addresses)
|
||||
diags.Append(diag...)
|
||||
if diag.HasError() {
|
||||
data.PublicIpv6Addresses = types.ListUnknown(data.PublicIpv6Addresses.ElementType(ctx))
|
||||
} else {
|
||||
data.PublicIpv6Addresses = tfPublicIpv6Addresses
|
||||
}
|
||||
|
||||
tfPrivateIpv6Addresses, diag := types.ListValueFrom(ctx, types.StringType, privateIpv6Addresses)
|
||||
diags.Append(diag...)
|
||||
if diag.HasError() {
|
||||
data.PrivateIpv6Addresses = types.ListUnknown(data.PrivateIpv6Addresses.ElementType(ctx))
|
||||
} else {
|
||||
data.PrivateIpv6Addresses = tfPrivateIpv6Addresses
|
||||
}
|
||||
|
||||
// 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)...)
|
||||
|
||||
@@ -32,13 +32,18 @@ func (d *serverFirewallRulesDataSource) Metadata(ctx context.Context, req dataso
|
||||
}
|
||||
|
||||
func (d *serverFirewallRulesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(resources.ServerFirewallRulesResourceSchema(ctx))
|
||||
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"
|
||||
resp.Schema.Description = "Retrieve details about the External Firewall Rules assigned to a BinaryLane server."
|
||||
}
|
||||
|
||||
func (d *serverFirewallRulesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
|
||||
@@ -8,10 +8,13 @@ import (
|
||||
"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"
|
||||
@@ -56,7 +59,18 @@ func (r *serverFirewallRulesResource) Metadata(ctx context.Context, req resource
|
||||
|
||||
func (r *serverFirewallRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = resources.ServerFirewallRulesResourceSchema(ctx)
|
||||
resp.Schema.Description = "TODO"
|
||||
resp.Schema.Description = "Retrieve details about the External Firewall Rules assigned to a BinaryLane server."
|
||||
|
||||
// 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) {
|
||||
@@ -64,13 +78,12 @@ func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.C
|
||||
|
||||
// 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{}
|
||||
firewallRules := []binarylane.AdvancedFirewallRuleRequest{}
|
||||
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -82,7 +95,7 @@ func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.C
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, fmt.Sprintf("Creating server firewall rules: server_id=%s", data.ServerId.String()))
|
||||
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
|
||||
fwResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
|
||||
ctx,
|
||||
data.ServerId.ValueInt64(),
|
||||
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
|
||||
@@ -96,10 +109,15 @@ func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.C
|
||||
)
|
||||
return
|
||||
}
|
||||
if serverResp.StatusCode() != http.StatusOK {
|
||||
if fwResp.StatusCode() == http.StatusNotFound {
|
||||
tflog.Warn(ctx, fmt.Sprintf("Server firewall rules not found, removing from state: server_id=%s", data.ServerId.String()))
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
if fwResp.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))
|
||||
fmt.Sprintf("Received %s creating server firewall rules: server_id=%s. Details: %s", fwResp.Status(), data.ServerId.String(), fwResp.Body))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -156,7 +174,7 @@ func (r *serverFirewallRulesResource) Update(ctx context.Context, req resource.U
|
||||
}
|
||||
|
||||
// Update API call logic
|
||||
firewallRules := []binarylane.AdvancedFirewallRule{}
|
||||
firewallRules := []binarylane.AdvancedFirewallRuleRequest{}
|
||||
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -210,7 +228,7 @@ func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.D
|
||||
data.ServerId.ValueInt64(),
|
||||
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
|
||||
Type: "change_advanced_firewall_rules",
|
||||
FirewallRules: []binarylane.AdvancedFirewallRule{},
|
||||
FirewallRules: []binarylane.AdvancedFirewallRuleRequest{},
|
||||
})
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
@@ -227,30 +245,13 @@ func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.D
|
||||
}
|
||||
}
|
||||
|
||||
func (r *serverFirewallRulesResource) 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("server_id"), id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(*firewallRules) == 0 {
|
||||
firewallRulesValues = []resources.FirewallRulesValue{}
|
||||
} else {
|
||||
for _, rule := range *firewallRules {
|
||||
@@ -270,6 +271,7 @@ func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.Adva
|
||||
return basetypes.NewListUnknown(firewallRulesValue.Type(ctx)), diags
|
||||
}
|
||||
|
||||
// TODO: This panics if invalid, should be handled better
|
||||
ruleValue := resources.NewFirewallRulesValueMust(firewallRulesValue.AttributeTypes(ctx), map[string]attr.Value{
|
||||
"description": types.StringPointerValue(rule.Description),
|
||||
"action": types.StringValue(string(rule.Action)),
|
||||
@@ -290,3 +292,20 @@ func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.Adva
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
@@ -1,46 +1,44 @@
|
||||
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)
|
||||
password := GenerateTestPassword(t)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
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 + `"
|
||||
wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules
|
||||
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"
|
||||
}
|
||||
]
|
||||
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" {
|
||||
@@ -75,6 +73,60 @@ data "binarylane_server_firewall_rules" "test" {
|
||||
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
|
||||
),
|
||||
},
|
||||
// ImportState testing
|
||||
{
|
||||
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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+1411
-164
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,15 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
@@ -10,30 +17,50 @@ import (
|
||||
|
||||
func TestServerResource(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)
|
||||
password1 := GenerateTestPassword(t)
|
||||
password2 := GenerateTestPassword(t)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
sshPublicKeyInitial := GenerateTestPublicKey(t)
|
||||
sshPublicKeyUpdated := GenerateTestPublicKey(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"
|
||||
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-12"
|
||||
size = "std-min"
|
||||
password = "` + password + `"
|
||||
vpc_id = binarylane_vpc.test.id
|
||||
public_ipv4_count = 0
|
||||
user_data = <<EOT
|
||||
name = "tf-test-server-resource"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
memory = 1152
|
||||
password = "` + password1 + `"
|
||||
vpc_id = binarylane_vpc.test.id
|
||||
public_ipv4_count = 1
|
||||
ssh_keys = [binarylane_ssh_key.initial.id]
|
||||
source_and_destination_check = false
|
||||
backups = true
|
||||
port_blocking = false
|
||||
user_data = <<EOT
|
||||
#cloud-config
|
||||
echo "Hello World" > /var/tmp/output.txt
|
||||
EOT
|
||||
@@ -42,7 +69,7 @@ EOT
|
||||
data "binarylane_server" "test" {
|
||||
depends_on = [binarylane_server.test]
|
||||
|
||||
id = binarylane_server.test.id
|
||||
id = binarylane_server.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
@@ -50,26 +77,467 @@ data "binarylane_server" "test" {
|
||||
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-12"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-11"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "memory", "1152"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "disk", "20"),
|
||||
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "0"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "password", password),
|
||||
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_ipv4_address"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "password", password1),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
|
||||
echo "Hello World" > /var/tmp/output.txt
|
||||
`),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "0"),
|
||||
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", "port_blocking", "false"),
|
||||
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"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "source_and_destination_check", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "backups", "true"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_hyperv", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_devices", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.nested_virt", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.driver_disk", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.unset_uuid", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.local_rtc", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.emulated_tpm", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.cloud_init", "true"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.qemu_guest_agent", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "advanced_features.uefi_boot", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "ipv6", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv6_addresses.#", "0"),
|
||||
|
||||
// Verify data source values
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "id"),
|
||||
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-12"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-11"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_id"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "vpc_ipv4_address"),
|
||||
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
|
||||
`),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "memory", "1152"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "disk", "20"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "backups", "true"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "port_blocking", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_hyperv", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_devices", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.nested_virt", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.driver_disk", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.unset_uuid", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.local_rtc", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.emulated_tpm", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.cloud_init", "true"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.qemu_guest_agent", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "advanced_features.uefi_boot", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "ipv6", "false"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "public_ipv6_addresses.#", "0"),
|
||||
),
|
||||
},
|
||||
// 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"
|
||||
disk = "45"
|
||||
password = "` + password1 + `"
|
||||
vpc_id = null
|
||||
public_ipv4_count = 0
|
||||
ssh_keys = [binarylane_ssh_key.updated.id]
|
||||
advanced_features = {
|
||||
emulated_hyperv = true
|
||||
}
|
||||
ipv6 = true
|
||||
|
||||
# source_and_destination_check = null # defaults to null
|
||||
# backups = false # defaults to false
|
||||
# port_blocking = true # defaults to true
|
||||
|
||||
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", "memory", "2048"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "disk", "45"),
|
||||
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.TestCheckNoResourceAttr("binarylane_server.test", "vpc_id"),
|
||||
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", "advanced_features.emulated_hyperv", "true"),
|
||||
resource.TestCheckNoResourceAttr("binarylane_server.test", "source_and_destination_check"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "backups", "false"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
|
||||
`#cloud-config
|
||||
echo "Hello Whitespace" > /var/tmp/output.txt
|
||||
|
||||
|
||||
`),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "ipv6", "true"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv6_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv6_addresses.#", "1"),
|
||||
),
|
||||
},
|
||||
// Change password testing (Cannot run at same time as Rebuild operation, so it has it's own test)
|
||||
{
|
||||
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"
|
||||
disk = "45"
|
||||
password = "` + password2 + `"
|
||||
vpc_id = null
|
||||
public_ipv4_count = 0
|
||||
ssh_keys = [binarylane_ssh_key.updated.id]
|
||||
|
||||
# source_and_destination_check = null # defaults to null
|
||||
# backups = false # defaults to false
|
||||
# port_blocking = true # defaults to true
|
||||
|
||||
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", "password", password2),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerResourceRename(t *testing.T) {
|
||||
// Must assign a password to the server or Binary Lane will send emails
|
||||
password := GenerateTestPassword(t)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Setup
|
||||
{
|
||||
Config: providerConfig + `
|
||||
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-rename-1"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Rename
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-rename-2"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-test-server-rename-2"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-11"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "password", password),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerVpcIpv4Change(t *testing.T) {
|
||||
// Must assign a password to the server or Binary Lane will send emails
|
||||
password := GenerateTestPassword(t)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Plan should fail if vpc_ipv4_address is set without vpc_id
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
vpc_ipv4_address = "10.241.69.69"
|
||||
}
|
||||
`,
|
||||
PlanOnly: true,
|
||||
ExpectError: regexp.MustCompile("Attribute \"vpc_id\" must be specified when \"vpc_ipv4_address\" is specified"),
|
||||
},
|
||||
// Setup
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_vpc" "test1" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_vpc" "test2" {
|
||||
name = "tf-test-server-vpcipv4-2"
|
||||
ip_range = "10.241.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
vpc_id = binarylane_vpc.test1.id
|
||||
vpc_ipv4_address = "10.240.0.69"
|
||||
}
|
||||
|
||||
data "binarylane_server" "test" {
|
||||
depends_on = [binarylane_server.test]
|
||||
|
||||
id = binarylane_server.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.0.69"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.0.69"),
|
||||
),
|
||||
},
|
||||
// Change IP without changing VPC
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_vpc" "test1" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_vpc" "test2" {
|
||||
name = "tf-test-server-vpcipv4-2"
|
||||
ip_range = "10.241.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
vpc_id = binarylane_vpc.test1.id
|
||||
vpc_ipv4_address = "10.240.4.20"
|
||||
}
|
||||
|
||||
data "binarylane_server" "test" {
|
||||
depends_on = [binarylane_server.test]
|
||||
|
||||
id = binarylane_server.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test1", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.240.4.20"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.240.4.20"),
|
||||
),
|
||||
},
|
||||
// Change VPC (should also change IP)
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_vpc" "test1" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
ip_range = "10.240.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_vpc" "test2" {
|
||||
name = "tf-test-server-vpcipv4-2"
|
||||
ip_range = "10.241.0.0/16"
|
||||
}
|
||||
|
||||
resource "binarylane_server" "test" {
|
||||
name = "tf-test-server-vpcipv4-1"
|
||||
region = "per"
|
||||
image = "debian-11"
|
||||
size = "std-min"
|
||||
public_ipv4_count = 0
|
||||
password = "` + password + `"
|
||||
vpc_id = binarylane_vpc.test2.id
|
||||
vpc_ipv4_address = "10.241.69.69"
|
||||
}
|
||||
|
||||
data "binarylane_server" "test" {
|
||||
depends_on = [binarylane_server.test]
|
||||
|
||||
id = binarylane_server.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("binarylane_server.test", "vpc_id", "binarylane_vpc.test2", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "vpc_ipv4_address", "10.241.69.69"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.#", "1"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "private_ipv4_addresses.0", "10.241.69.69"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func GenerateTestPassword(t *testing.T) string {
|
||||
t.Helper()
|
||||
pwBytes := make([]byte, 12)
|
||||
_, err := rand.Read(pwBytes)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to generate password: %s", err)
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(pwBytes)
|
||||
}
|
||||
|
||||
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, ¶ms)
|
||||
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, ¶ms)
|
||||
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.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"terraform-provider-binarylane/internal/binarylane"
|
||||
"terraform-provider-binarylane/internal/data_sources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ datasource.DataSource = &sizesDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &sizesDataSource{}
|
||||
)
|
||||
|
||||
func NewSizesDataSource() datasource.DataSource {
|
||||
return &sizesDataSource{}
|
||||
}
|
||||
|
||||
type sizesDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type sizesDataSourceModel struct {
|
||||
data_sources.SizesModel
|
||||
}
|
||||
|
||||
func (d *sizesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_sizes"
|
||||
}
|
||||
|
||||
func (d *sizesDataSource) 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 *sizesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = data_sources.SizesDataSourceSchema(ctx)
|
||||
}
|
||||
|
||||
func (d *sizesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data sizesDataSourceModel
|
||||
|
||||
// Read Terraform configuration data into the model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
var page int32 = 1
|
||||
perPage := int32(200)
|
||||
var nextPage bool = true
|
||||
var regResults []binarylane.Size
|
||||
|
||||
for nextPage {
|
||||
params := binarylane.GetSizesParams{
|
||||
Page: &page,
|
||||
PerPage: &perPage,
|
||||
}
|
||||
listResp, err := d.bc.client.GetSizesWithResponse(ctx, ¶ms)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error listing sizes", err.Error())
|
||||
return
|
||||
}
|
||||
if listResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError("Unexpected status code listing sizes", string(listResp.Body))
|
||||
return
|
||||
}
|
||||
regResults = append(regResults, listResp.JSON200.Sizes...)
|
||||
|
||||
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
sizes, diags := types.ListValueFrom(ctx, data.Sizes.ElementType(ctx), regResults)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
data.Sizes = sizes
|
||||
|
||||
// Save data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestSizesDataSource(t *testing.T) {
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Create and Read testing
|
||||
{
|
||||
Config: providerConfig + `
|
||||
data "binarylane_sizes" "test" {
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Verify data source values
|
||||
resource.TestCheckResourceAttrWith("data.binarylane_sizes.test", "sizes.#", func(value string) error {
|
||||
count, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
return fmt.Errorf("expected at least one size, got: %d", count)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_sizes.test", "sizes.0.slug"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"terraform-provider-binarylane/internal/resources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -47,17 +48,51 @@ func (d *sshKeyDataSource) Metadata(ctx context.Context, req datasource.Metadata
|
||||
}
|
||||
|
||||
func (d *sshKeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(resources.SshKeyResourceSchema(ctx))
|
||||
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"
|
||||
|
||||
// Overrides
|
||||
defaultDescription := "If `true`, this SSH key will be included on all new server installations (if the operating " +
|
||||
"system supports SSH key injection)."
|
||||
resp.Schema.Attributes["default"] = schema.BoolAttribute{
|
||||
Description: defaultDescription,
|
||||
MarkdownDescription: defaultDescription,
|
||||
Optional: false,
|
||||
Required: false,
|
||||
Computed: true,
|
||||
}
|
||||
|
||||
// Additional attributes
|
||||
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
|
||||
"such as one generated by `ssh-keygen`."
|
||||
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
|
||||
Description: publicKeyDescription,
|
||||
MarkdownDescription: publicKeyDescription,
|
||||
CustomType: TrimmedStringType{},
|
||||
Computed: true,
|
||||
}
|
||||
|
||||
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)...)
|
||||
@@ -83,11 +118,13 @@ 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.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.PublicKey = TrimmedStringValue{
|
||||
StringValue: 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)...)
|
||||
|
||||
@@ -4,22 +4,26 @@ 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/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"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
@@ -30,6 +34,12 @@ type sshKeyResource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type sshKeyModel struct {
|
||||
resources.SshKeyModel
|
||||
Fingerprint types.String `tfsdk:"fingerprint"`
|
||||
PublicKey TrimmedStringValue `tfsdk:"public_key"`
|
||||
}
|
||||
|
||||
func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
@@ -54,29 +64,61 @@ 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"
|
||||
|
||||
// Overrides
|
||||
default_ := resp.Schema.Attributes["default"]
|
||||
idDescription := "The numeric ID of the SSH key."
|
||||
resp.Schema.Attributes["id"] = schema.Int64Attribute{
|
||||
Required: false,
|
||||
Optional: false,
|
||||
Computed: true,
|
||||
Description: idDescription,
|
||||
MarkdownDescription: idDescription,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
|
||||
defaultDescription := "If `true`, this SSH key will be included on all new server installations (if the operating " +
|
||||
"system supports SSH key injection). Default is `false`."
|
||||
resp.Schema.Attributes["default"] = schema.BoolAttribute{
|
||||
Required: false,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: default_.GetDescription(),
|
||||
MarkdownDescription: default_.GetMarkdownDescription(),
|
||||
Description: defaultDescription,
|
||||
MarkdownDescription: defaultDescription,
|
||||
Default: booldefault.StaticBool(false), // Add default to backups
|
||||
}
|
||||
|
||||
public_key := resp.Schema.Attributes["public_key"]
|
||||
// Additional attributes
|
||||
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
|
||||
"such as one generated by `ssh-keygen`."
|
||||
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: public_key.GetDescription(),
|
||||
MarkdownDescription: public_key.GetMarkdownDescription(),
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
Optional: false,
|
||||
Computed: false,
|
||||
Description: publicKeyDescription,
|
||||
MarkdownDescription: publicKeyDescription,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
RequiresReplaceForTrimmedStringModifier{},
|
||||
},
|
||||
CustomType: TrimmedStringType{}, // Ignore leading/trailing whitespace
|
||||
}
|
||||
|
||||
fingerprintDescription := "The fingerprint of the SSH key."
|
||||
resp.Schema.Attributes["fingerprint"] = schema.StringAttribute{
|
||||
Description: fingerprintDescription,
|
||||
MarkdownDescription: fingerprintDescription,
|
||||
Optional: false,
|
||||
Required: false,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)...)
|
||||
@@ -85,7 +127,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(),
|
||||
@@ -98,16 +139,22 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
if sshResp.StatusCode() == http.StatusNotFound {
|
||||
tflog.Warn(ctx, fmt.Sprintf("SSH Key not found, removing from state: name=%s", data.Name.String()))
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
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.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
|
||||
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
@@ -118,13 +165,10 @@ 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
|
||||
}
|
||||
@@ -140,70 +184,82 @@ 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
|
||||
}
|
||||
|
||||
// Set data values
|
||||
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
|
||||
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
|
||||
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) // don't set or it will force replacement every time
|
||||
data.PublicKey = TrimmedStringValue{
|
||||
StringValue: 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
|
||||
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(data.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
|
||||
Name: data.Name.ValueString(),
|
||||
Default: data.Default.ValueBoolPointer(),
|
||||
})
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error updating SSH Key: name=%s", data.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(), data.Name.ValueString(), sshResp.Body),
|
||||
)
|
||||
return
|
||||
if !state.Name.Equal(plan.Name) || !state.Default.Equal(plan.Default) {
|
||||
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
|
||||
}
|
||||
state.Name = plan.Name
|
||||
state.Default = plan.Default
|
||||
}
|
||||
|
||||
// Save updated data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -211,13 +267,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) {
|
||||
// // Retrieve import ID and save to id attribute
|
||||
// resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
// }
|
||||
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, ¶ms)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(fmt.Sprintf("Error getting SSH keys 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.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
if sshKey.Id == 0 {
|
||||
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...)
|
||||
}
|
||||
|
||||
@@ -1,67 +1,175 @@
|
||||
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/plancheck"
|
||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
)
|
||||
|
||||
func TestSshKeyResource(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
publicKey := GenerateTestPublicKey(t)
|
||||
|
||||
resource.ParallelTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Create and Read testing
|
||||
{
|
||||
Config: providerConfig + `
|
||||
|
||||
resource "binarylane_ssh_key" "test" {
|
||||
name = "tf_ssh_key_resource_test"
|
||||
public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal"
|
||||
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
|
||||
id = binarylane_ssh_key.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Verify resource values
|
||||
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf_ssh_key_resource_test"),
|
||||
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "public_key", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal"),
|
||||
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
|
||||
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "public_key", publicKey),
|
||||
resource.TestCheckResourceAttrSet("binarylane_ssh_key.test", "fingerprint"),
|
||||
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_ssh_key_resource_test"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "public_key", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "public_key", publicKey),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "fingerprint"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "false"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "id"),
|
||||
),
|
||||
},
|
||||
// ImportState testing
|
||||
// TODO
|
||||
// {
|
||||
// ResourceName: "binarylane_ssh_key.test",
|
||||
// ImportState: true,
|
||||
// ImportStateVerify: true,
|
||||
// ImportStateVerifyIgnore: []string{}, // nothing to ignore
|
||||
// },
|
||||
// TODO: Update and Read testing
|
||||
// {
|
||||
// Config: providerConfig + `
|
||||
// resource "binarylane_ssh_key" "test" {
|
||||
// name = "tf_ssh_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_ssh_key_resource_test"),
|
||||
// resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "true"),
|
||||
// ),
|
||||
// },
|
||||
// Delete testing automatically occurs in TestCase
|
||||
{
|
||||
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
|
||||
},
|
||||
// Whitespace changes should not recreate the resource
|
||||
{
|
||||
Config: `
|
||||
resource "binarylane_ssh_key" "test" {
|
||||
name = "tf-test-key-resource-test"
|
||||
# Test public key with a newline to ensure it does not get recreated
|
||||
public_key = <<EOT
|
||||
` + publicKey + `
|
||||
EOT
|
||||
}`,
|
||||
ConfigPlanChecks: resource.ConfigPlanChecks{
|
||||
PreApply: []plancheck.PlanCheck{
|
||||
plancheck.ExpectEmptyPlan(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func GenerateTestPublicKey(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, ¶ms)
|
||||
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.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
resource.TestMain(m)
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// TrimmedStringType
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces
|
||||
var _ basetypes.StringTypable = TrimmedStringType{}
|
||||
|
||||
type TrimmedStringType struct {
|
||||
basetypes.StringType
|
||||
}
|
||||
|
||||
func (t TrimmedStringType) Equal(o attr.Type) bool {
|
||||
other, ok := o.(TrimmedStringType)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.StringType.Equal(other.StringType)
|
||||
}
|
||||
|
||||
func (t TrimmedStringType) String() string {
|
||||
return "TrimmedStringType"
|
||||
}
|
||||
|
||||
func (t TrimmedStringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
|
||||
value := TrimmedStringValue{
|
||||
StringValue: in,
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (t TrimmedStringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
|
||||
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stringValue, ok := attrValue.(basetypes.StringValue)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
|
||||
}
|
||||
|
||||
stringValuable, diags := t.ValueFromString(ctx, stringValue)
|
||||
if diags.HasError() {
|
||||
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
|
||||
}
|
||||
|
||||
return stringValuable, nil
|
||||
}
|
||||
|
||||
func (t TrimmedStringType) ValueType(ctx context.Context) attr.Value {
|
||||
return TrimmedStringValue{}
|
||||
}
|
||||
|
||||
// TrimmedStringValue
|
||||
|
||||
var _ basetypes.StringValuable = TrimmedStringValue{}
|
||||
var _ basetypes.StringValuableWithSemanticEquals = TrimmedStringValue{}
|
||||
|
||||
type TrimmedStringValue struct {
|
||||
basetypes.StringValue
|
||||
}
|
||||
|
||||
func (v TrimmedStringValue) Equal(o attr.Value) bool {
|
||||
other, ok := o.(TrimmedStringValue)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.StringValue.Equal(other.StringValue)
|
||||
}
|
||||
|
||||
func (v TrimmedStringValue) Type(ctx context.Context) attr.Type {
|
||||
return TrimmedStringType{}
|
||||
}
|
||||
|
||||
func (v TrimmedStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
// The framework should always pass the correct value type, but always check
|
||||
newValue, ok := newValuable.(TrimmedStringValue)
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Semantic Equality Check Error",
|
||||
"An unexpected value type was received while performing semantic equality checks. "+
|
||||
"Please report this to the provider developers.\n\n"+
|
||||
"Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
|
||||
"Got Value Type: "+fmt.Sprintf("%T", newValuable),
|
||||
)
|
||||
|
||||
return false, diags
|
||||
}
|
||||
|
||||
priorString := strings.TrimSpace(v.StringValue.ValueString())
|
||||
newString := strings.TrimSpace(newValue.ValueString())
|
||||
|
||||
// If the strings are equivalent, keep the prior value
|
||||
return priorString == newString, diags
|
||||
}
|
||||
|
||||
// PlanModifier
|
||||
var _ planmodifier.String = RequiresReplaceForTrimmedStringModifier{}
|
||||
|
||||
type RequiresReplaceForTrimmedStringModifier struct{}
|
||||
|
||||
func (m RequiresReplaceForTrimmedStringModifier) Description(_ context.Context) string {
|
||||
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
|
||||
}
|
||||
|
||||
func (m RequiresReplaceForTrimmedStringModifier) MarkdownDescription(_ context.Context) string {
|
||||
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
|
||||
}
|
||||
|
||||
func (m RequiresReplaceForTrimmedStringModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
|
||||
// Do not replace on resource creation.
|
||||
if req.State.Raw.IsNull() {
|
||||
return
|
||||
}
|
||||
// Do not replace on resource destroy.
|
||||
if req.Plan.Raw.IsNull() {
|
||||
return
|
||||
}
|
||||
// Do not replace if the plan and state values are equal.
|
||||
if req.PlanValue.Equal(req.StateValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create Normalized values for the state and plan.
|
||||
stateNormalized := TrimmedStringValue{
|
||||
StringValue: req.StateValue,
|
||||
}
|
||||
planNormalized := TrimmedStringValue{
|
||||
StringValue: req.PlanValue,
|
||||
}
|
||||
|
||||
// Perform semantic equality check.
|
||||
semanticallyEqual, diags := stateNormalized.StringSemanticEquals(ctx, planNormalized)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if semanticallyEqual {
|
||||
tflog.Info(ctx, "Plan and state values are semantically equal. Suppressing differences.")
|
||||
resp.PlanValue = req.StateValue
|
||||
} else {
|
||||
resp.RequiresReplace = true
|
||||
}
|
||||
}
|
||||
+246
-91
@@ -1,15 +1,23 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema) (*d_schema.Schema, error) {
|
||||
type AttributeConfig struct {
|
||||
RequiredAttributes *[]string
|
||||
OptionalAttributes *[]string
|
||||
ExcludedAttributes *[]string
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -17,97 +25,244 @@ func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema) (*d_schema.Sche
|
||||
MarkdownDescription: rs.GetMarkdownDescription(),
|
||||
DeprecationMessage: rs.GetDeprecationMessage(),
|
||||
}
|
||||
for name, attr := range rs.Attributes {
|
||||
switch attr.GetType() {
|
||||
case types.BoolType:
|
||||
ds.Attributes[name] = d_schema.BoolAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.DynamicType:
|
||||
ds.Attributes[name] = d_schema.DynamicAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Float32Type:
|
||||
ds.Attributes[name] = d_schema.Float32Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Float64Type:
|
||||
ds.Attributes[name] = d_schema.Float64Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Int32Type:
|
||||
ds.Attributes[name] = d_schema.Int32Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Int64Type:
|
||||
ds.Attributes[name] = d_schema.Int64Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.NumberType:
|
||||
ds.Attributes[name] = d_schema.NumberAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
case types.StringType:
|
||||
ds.Attributes[name] = d_schema.StringAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
default:
|
||||
// Feel free to to raise a PR and remove this hack
|
||||
if strings.HasPrefix(attr.GetType().String(), "types.ListType") {
|
||||
ds.Attributes[name] = d_schema.ListAttribute{
|
||||
ElementType: attr.GetType().(types.ListType).ElemType,
|
||||
Description: attr.GetDescription(),
|
||||
Optional: attr.IsOptional(),
|
||||
Computed: true,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to convert resource schema attribute to data source schema attribute: name=%s, type=%s", name, attr.GetType())
|
||||
}
|
||||
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)
|
||||
|
||||
attr, err := convertResourceAttrToDataSourceAttr(name, attribute, required, optional)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert resource schema attribute to data source schema attribute: %w", err)
|
||||
}
|
||||
ds.Attributes[name] = attr
|
||||
}
|
||||
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
func convertResourceAttrToDataSourceAttr(name string, attribute r_schema.Attribute, required, optional bool) (d_schema.Attribute, error) {
|
||||
switch attribute.GetType() {
|
||||
case types.BoolType:
|
||||
return d_schema.BoolAttribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.DynamicType:
|
||||
return d_schema.DynamicAttribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.Float32Type:
|
||||
return d_schema.Float32Attribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.Float64Type:
|
||||
return d_schema.Float64Attribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.Int32Type:
|
||||
return d_schema.Int32Attribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.Int64Type:
|
||||
return d_schema.Int64Attribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.NumberType:
|
||||
return d_schema.NumberAttribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
case types.StringType:
|
||||
return d_schema.StringAttribute{
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
|
||||
default:
|
||||
if t, isSet := attribute.(r_schema.SetNestedAttribute); isSet {
|
||||
nestedObjectAttrs := make(map[string]d_schema.Attribute)
|
||||
for name, attribute := range t.NestedObject.Attributes {
|
||||
nestedAttribute, err := convertResourceAttrToDataSourceAttr(name, attribute, required, optional)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nestedObjectAttrs[name] = nestedAttribute
|
||||
}
|
||||
return d_schema.SetNestedAttribute{
|
||||
NestedObject: d_schema.NestedAttributeObject{
|
||||
Attributes: nestedObjectAttrs,
|
||||
CustomType: t.NestedObject.CustomType,
|
||||
Validators: t.NestedObject.Validators,
|
||||
},
|
||||
CustomType: t.CustomType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if t, isSet := attribute.GetType().(types.SetType); isSet {
|
||||
return d_schema.SetAttribute{
|
||||
ElementType: t.ElemType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if t, isList := attribute.(r_schema.ListNestedAttribute); isList {
|
||||
nestedObjectAttrs := make(map[string]d_schema.Attribute)
|
||||
for name, attribute := range t.NestedObject.Attributes {
|
||||
nestedAttribute, err := convertResourceAttrToDataSourceAttr(name, attribute, required, optional)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nestedObjectAttrs[name] = nestedAttribute
|
||||
}
|
||||
return d_schema.ListNestedAttribute{
|
||||
NestedObject: d_schema.NestedAttributeObject{
|
||||
Attributes: nestedObjectAttrs,
|
||||
CustomType: t.NestedObject.CustomType,
|
||||
Validators: t.NestedObject.Validators,
|
||||
},
|
||||
CustomType: t.CustomType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if t, isList := attribute.GetType().(types.ListType); isList {
|
||||
return d_schema.ListAttribute{
|
||||
ElementType: t.ElemType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if t, isObject := attribute.(r_schema.SingleNestedAttribute); isObject {
|
||||
attributeTypes := make(map[string]attr.Type, len(t.GetAttributes()))
|
||||
for name, attribute := range t.GetAttributes() {
|
||||
attributeTypes[name] = attribute.GetType()
|
||||
}
|
||||
|
||||
return d_schema.ObjectAttribute{
|
||||
CustomType: t.CustomType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
AttributeTypes: attributeTypes,
|
||||
}, nil
|
||||
}
|
||||
if t, isObject := attribute.GetType().(types.ObjectType); isObject {
|
||||
return d_schema.ObjectAttribute{
|
||||
AttributeTypes: t.AttrTypes,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("conversion of attribute type is not implemented: name=%s, type=%s", name, attribute.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func listContainsUnknown(ctx context.Context, list types.List) bool {
|
||||
// If the whole list is unknown, return true
|
||||
if list.IsUnknown() {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func Pointer[T any](d T) *T {
|
||||
return &d
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
)
|
||||
|
||||
var (
|
||||
_ validator.Int32 = &MultipleOfValidator{}
|
||||
)
|
||||
|
||||
type MultipleOfValidator struct {
|
||||
Multiple, RangeFrom, RangeTo int32
|
||||
}
|
||||
|
||||
func (v MultipleOfValidator) Description(ctx context.Context) string {
|
||||
if v.RangeFrom == 0 {
|
||||
return fmt.Sprintf(`must be a multiple of %d`, v.Multiple)
|
||||
}
|
||||
return fmt.Sprintf("when greater than %d, must be a multiple of %d", v.RangeFrom, v.Multiple)
|
||||
}
|
||||
|
||||
func (v MultipleOfValidator) MarkdownDescription(ctx context.Context) string {
|
||||
if v.RangeFrom == 0 {
|
||||
return fmt.Sprintf(`must be a multiple of %d`, v.Multiple)
|
||||
}
|
||||
return fmt.Sprintf("when greater than %d, must be a multiple of %d", v.RangeFrom, v.Multiple)
|
||||
}
|
||||
|
||||
func (v MultipleOfValidator) ValidateInt32(ctx context.Context, req validator.Int32Request, resp *validator.Int32Response) {
|
||||
if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
|
||||
return
|
||||
}
|
||||
memory := req.ConfigValue.ValueInt32()
|
||||
|
||||
if memory%v.Multiple == 0 || v.RangeFrom != 0 && memory < v.RangeFrom || v.RangeTo != 0 && memory >= v.RangeTo {
|
||||
return
|
||||
}
|
||||
|
||||
if v.RangeFrom != 0 {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
req.Path,
|
||||
"Not a Multiple",
|
||||
fmt.Sprintf("if greater than %d, value must be a multiple of %d", v.RangeFrom, v.Multiple),
|
||||
)
|
||||
} else {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
req.Path,
|
||||
"Not a Multiple",
|
||||
fmt.Sprintf("value must be a multiple of %d", v.Multiple),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -50,14 +50,19 @@ func (d *vpcDataSource) Metadata(ctx context.Context, req datasource.MetadataReq
|
||||
}
|
||||
|
||||
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(resources.VpcResourceSchema(ctx))
|
||||
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"
|
||||
// resp.Schema.Description = "TODO"
|
||||
}
|
||||
|
||||
func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
@@ -85,8 +90,8 @@ func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
|
||||
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)
|
||||
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)...)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user