Compare commits

...

6 Commits

Author SHA1 Message Date
Oscar Hermoso 1f24f45af7 ☁️ Add VPC resource (#4)
* WIP commit - Adding VPC resource

* Tests passing for VPC resource

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

* Define `wait_for_create` attribute to make waiting for server opt-in
2024-08-19 11:46:18 +08:00
Oscar Hermoso f1b7b9853e Initialize VMs with user_data script, create cloud-init examples 2024-08-17 17:38:32 +08:00
Oscar Hermoso c8cbcd4f19 Fix data sources for servers, add tests (tests still WIP) 2024-08-17 14:55:19 +08:00
47 changed files with 2589 additions and 268 deletions
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'gomod'
directory: '/'
schedule:
interval: 'weekly'
+1 -1
View File
@@ -30,4 +30,4 @@ tmp
# Normally, you wouldn't want to exclude lock files, but in this case, we're
# using Terraform's lock file to store the provider version, so we need to
# ignore it.
terraform.lock.hcl
.terraform.lock.hcl
+98 -26
View File
@@ -12,7 +12,6 @@ Based on [this example from the terraform docs](https://developer.hashicorp.com/
```hcl
provider_installation {
dev_overrides {
# Example GOBIN path, will need to be replaced with your own GOBIN path. Default is $GOPATH/bin
"oscarhermoso/binarylane" = "/home/oscarhermoso/Git/terraform-provider-binarylane/bin"
@@ -32,6 +31,13 @@ terraform plan
terraform apply
```
### Testing
```sh
TF_ACC=1 go test -v ./...
```
### Adding resources/data sources to the provider
1. Make any changes to `provider_gen_config.yml` (see https://developer.hashicorp.com/terraform/plugin/code-generation/openapi-generator#generator-config)
@@ -57,31 +63,42 @@ tfplugingen-framework scaffold data-source \
4. Populate the template scaffolding:
Define the interfaces that the resource/data source should implement:
#### Resource
Define the interfaces that the resource should implement:
```diff
- var _ resource.Resource = (*replaceMeResource)(nil)
+ // Ensure the implementation satisfies the expected interfaces.
- var _ resource.Resource = (*exampleResource)(nil)
+ var (
+ _ resource.Resource = &replaceMeResource{}
+ _ resource.ResourceWithConfigure = &replaceMeResource{}
+ // _ resource.ResourceWithImportState = &replaceMeResource{}
+ _ resource.Resource = &exampleResource{}
+ _ resource.ResourceWithConfigure = &exampleResource{}
+ // _ resource.ResourceWithImportState = &exampleResource{}
+ )
```
Pass the API client to the resource:
Add the API client to the resource:
```diff
- type replaceMeResource struct{}
+ type replaceMeResource struct {
- type exampleResource struct{}
+ type exampleResource struct {
+ bc *BinarylaneClient
+ }
```
Extend the resource model from the generated `resources.*Model`.
```diff
type exampleResourceModel struct {
- Id types.String `tfsdk:"id"`
+ resources.ExampleModel
+ // Add any additional fields here
}
```
Add a `Configure` method to the resource:
```diff
+ func (d *replaceMeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ func (d *exampleResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
@@ -100,25 +117,80 @@ Add a `Configure` method to the resource:
+ }
```
Delete the local `*ResourceModel` struct, and import the generated `resources.*Model` instead.
```diff
- type replaceMeResourceModel struct {
- Id types.String `tfsdk:"id"`
- }
```
```diff
func (r *replaceMeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data replaceMeResourceModel
+ var data resources.ReplaceMeModel
```
Add the new data source/resource to `provider.go`:
```diff
return []func() resource.Resource{
# ...
+ NewReplaceMeResource,
+ NewExampleResource,
}
```
#### Data Source
Define the interfaces that the data source should implement:
```diff
- var _ resource.Resource = (*exampleDataSource)(nil)
+ var (
+ _ datasource.DataSource = &exampleDataSource{}
+ _ datasource.DataSourceWithConfigure = &exampleDataSource{}
+ )
Add the API client to the data source:
```diff
- type exampleResource struct{}
+ type exampleResource struct {
+ bc *BinarylaneClient
+ }
```
Derive the data source model from the generated `resources.*Model`.
```diff
type exampleDataSourceModel struct {
- Id types.String `tfsdk:"id"`
+ resources.ExampleModel
}
```
Add a `Configure` method to the data source.
```diff
+ func (d *vpcDataSource) Configure(
+ _ context.Context,
+ req datasource.ConfigureRequest,
+ resp *datasource.ConfigureResponse,
+ ) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ bc, ok := req.ProviderData.(BinarylaneClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
+ return
+ }
+
+ d.bc = &bc
+ }
```
Use the generated schema to define the data source schema.
```diff
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Attributes: map[string]schema.Attribute{
- "id": schema.StringAttribute{
- Computed: true,
- },
- },
- }
+ resp.Schema = *convertResourceSchemaToDataSourceSchema(ctx, resources.ExampleResourceSchema(ctx))
+ resp.Schema.Description = "TODO"
}
```
-1
View File
@@ -4,7 +4,6 @@ See the examples in the [examples directory](./examples/basic/main.tf).
```terraform
resource "binarylane_server" "example" {
name = "example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
+17 -3
View File
@@ -3,18 +3,32 @@
page_title: "binarylane_server Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_server (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `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.
- `user_data` (String) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
### Read-Only
- `id` (String) The ID of this resource.
- `image` (String) The slug of the selected operating system.
- `region` (String) The slug of the selected region.
- `size` (String) The slug of the selected size.
+9 -3
View File
@@ -3,18 +3,24 @@
page_title: "binarylane_ssh_key Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_ssh_key (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### 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.
### Read-Only
- `id` (String) The ID of this resource.
- `name` (String) A name to help you identify the key.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format.
+25
View File
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_vpc (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### 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
- `name` (String) A name to help identify this VPC.
+7 -1
View File
@@ -23,7 +23,13 @@ Provides a Binary Lane Server resource. This can be used to create and delete se
### 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.
- `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.
- `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.
- `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server.
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
- `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.
- `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.
+25
View File
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_vpc (Resource)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) A name to help identify this VPC.
### 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.
+36
View File
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc_route_entries Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_vpc_route_entries (Resource)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `vpc_id` (Number) The target vpc id.
### Optional
- `route_entries` (Attributes List) Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save. (see [below for nested schema](#nestedatt--route_entries))
<a id="nestedatt--route_entries"></a>
### Nested Schema for `route_entries`
Required:
- `destination` (String) The destination address for this route entry. This may be in CIDR format.
- `router` (String) The server that will receive traffic sent to the destination property in this VPC.
Optional:
- `description` (String) An optional description for the route.
+9
View File
@@ -0,0 +1,9 @@
# examples
In order of increasing complexity, these examples show how to use the provider.
- [./basic](./basic/main.tf) - A basic example that creates a server.
- [./ssh](./ssh/main.tf) - Create a server and registers an SSH key as a global default.
- [./cloud-init](./cloud-init/main.tf) - Create a server with a cloud-init configuration.
- [./nix](./nix/main.tf) - Create a server with a NixOS configuration.
- [./vpc](./vpc/main.tf) - Create a server with a Virtual Private Cloud.
-24
View File
@@ -1,24 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/oscarhermoso/binarylane" {
version = "0.1.0"
hashes = [
"h1:M/GVWGdH+W26pw8o6O2zvzorVZvi0hZbaKCT8scoqT8=",
"zh:07c855b20ad789e78602918ce1565b5e1ac411df92b1f9f4a654ef3da77bcd1e",
"zh:09530d6532dcff2ccdf48de853d7144f0c66d7be203883461b256f52e719c06d",
"zh:2c118c8c7572f60739b4f0ac5cecbadd56636fa63f7f4e7f0c6cdec427fbd609",
"zh:3a51045b18ec6f7bd0ff642370f45b7ba936afa9d0e79ff52481a447adb99b66",
"zh:64608398b9da27f5b1efa2d164fc20e4cb924ebef6cdf80da7b4329f344dbf13",
"zh:a9808dacc075dd53774fe559862f22b9ab02a22b789dde59cff0ea3b42f37c73",
"zh:b22dc91a867a470da75f8887f432938cf5db314943c1f90203eff39ce8e24e85",
"zh:c110e831eafd8a138f9af9efc1730b8c3295cec54561918676744b0f6968498c",
"zh:cd4a98509c740beff50979c1e50c00e5b3915bf35185c89b9f306a2432b54a2c",
"zh:cf3b046ec96b5140fb6dacceead8d9ed723debcf580462ab93a5ed1578eaeab8",
"zh:d395d9a1d681980f0068e2146a4961e0a39c572ffdf131d5d5c1f3a91296a5e8",
"zh:eaf6c069dc35c9a6c97502b1b283d4a815730f70b0cfb637d26e29f2a70c2a51",
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
"zh:f31a253fc4c7f0d37ec2bfe2ca2e40c7c51a0db355039d56123160c97bdf7c90",
"zh:fdbadc51fc6c9ac6f2a68d5846fdb743be7b0344307c9dbf6d90252753894633",
]
}
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+47
View File
@@ -0,0 +1,47 @@
# examples/cloud-init
Binary lane servers initialize cloud-init configurations to the server upon creation.
If you want to deploy multiple parts of a cloud-init configuration, you can use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config) data source.
```terraform
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
data "cloudinit_config" "example" {
gzip = false
base64_encode = false
part {
filename = "hello-script.sh"
content_type = "text/x-shellscript"
content = file("./hello-script.sh")
}
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = file("./cloud-config.yml")
}
}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = data.cloudinit_config.example.rendered
}
```
+22
View File
@@ -0,0 +1,22 @@
#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,
once,
'example-done',
sh,
-c,
'echo Finished installing, Terraform will now proceed:`date` >> /var/tmp/user_data.log',
]
runcmd:
- [sh, -c, 'sleep 99999'] # The provider won't wait for `runcmd` to complete
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_server" "example" {
name = "tf-cloud-init-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = file("./cloud-config.yml")
wait_for_create = 300 # 5 mins
}
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+3
View File
@@ -0,0 +1,3 @@
# examples/nix
WIP
+14
View File
@@ -0,0 +1,14 @@
#cloud-config
# yaml-language-server: $schema=https://raw.githubusercontent.com/canonical/cloud-init/main/cloudinit/config/schemas/schema-cloud-config-v1.json
#cloud-config
write_files:
- path: /etc/nixos/host.nix
permissions: '0644'
content: |
{pkgs, ...}:
{
environment.systemPackages = with pkgs; [ vim ];
}
bootcmd:
- cloud-init-per once "infect" curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-23.05 bash 2>&1 | tee /var/tmp/infect.log
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "debian-12"
size = "std-min"
user_data = file("./cloud-config.yml")
wait_for_create = 300 # 5 mins
}
+37
View File
@@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
+63
View File
@@ -0,0 +1,63 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_vpc" "example" {
name = "tf-example"
ip_range = "10.240.0.0/16"
}
# Web & NAT server
resource "binarylane_server" "web" {
name = "tf-vpc-example-web"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
}
# Database server
resource "binarylane_server" "db" {
name = "tf-vpc-example-db"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 0
}
# VPN server
resource "binarylane_server" "vpn" {
name = "tf-vpc-example-vpn"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
}
resource "binarylane_vpc_route_entries" "example" {
vpc_id = binarylane_vpc.example.id
route_entries = [
{
description = "NAT"
destination = "0.0.0.0/0"
router = binarylane_server.web.private_ipv4_addresses.0
},
{
description = "VPN"
destination = "192.168.1.0/24"
router = binarylane_server.vpn.private_ipv4_addresses.0
}
]
}
+17 -6
View File
@@ -9,7 +9,9 @@ require (
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-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
)
@@ -21,6 +23,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
@@ -37,11 +40,13 @@ require (
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
@@ -49,10 +54,12 @@ require (
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/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
github.com/hashicorp/terraform-json v0.22.1 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1 // indirect
github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
@@ -66,6 +73,8 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/oklog/run v1.0.0 // indirect
@@ -76,6 +85,7 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
@@ -88,16 +98,17 @@ require (
github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.26.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
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+39 -10
View File
@@ -16,8 +16,11 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
@@ -77,6 +80,7 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -109,6 +113,8 @@ github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuD
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
@@ -125,6 +131,10 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
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/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=
@@ -145,6 +155,10 @@ github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/12
github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
@@ -195,6 +209,10 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@@ -260,6 +278,9 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
@@ -285,6 +306,8 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -294,8 +317,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/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=
@@ -310,8 +333,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
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=
@@ -349,9 +372,11 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
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=
@@ -365,10 +390,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -377,9 +405,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+4
View File
@@ -17,3 +17,7 @@ cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del
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
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
+12 -3
View File
@@ -12765,18 +12765,27 @@
"router": {
"minLength": 1,
"type": "string",
"description": "The server that will receive traffic sent to the destination property in this VPC."
"description": "The server that will receive traffic sent to the destination property in this VPC.",
"x-oapi-codegen-extra-tags": {
"tfsdk": "router"
}
},
"destination": {
"minLength": 1,
"type": "string",
"description": "The destination address for this route entry. This may be in CIDR format."
"description": "The destination address for this route entry. This may be in CIDR format.",
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination"
}
},
"description": {
"maxLength": 250,
"type": "string",
"description": "An optional description for the route.",
"nullable": true
"nullable": true,
"x-oapi-codegen-extra-tags": {
"tfsdk": "description"
}
}
}
},
+3 -3
View File
@@ -2349,13 +2349,13 @@ type RouteEntry struct {
// RouteEntryRequest defines model for RouteEntryRequest.
type RouteEntryRequest struct {
// Description An optional description for the route.
Description *string `json:"description"`
Description *string `json:"description" tfsdk:"description"`
// Destination The destination address for this route entry. This may be in CIDR format.
Destination string `json:"destination"`
Destination string `json:"destination" tfsdk:"destination"`
// Router The server that will receive traffic sent to the destination property in this VPC.
Router string `json:"router"`
Router string `json:"router" tfsdk:"router"`
}
// Sample defines model for Sample.
@@ -1,26 +0,0 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package data_sources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func ServerDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Required: true,
Description: "The ID of the server to fetch.",
MarkdownDescription: "The ID of the server to fetch.",
},
},
}
}
type ServerModel struct {
Id types.Int64 `tfsdk:"id"`
}
@@ -1,26 +0,0 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package data_sources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func SshKeyDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Required: true,
Description: "The ID or fingerprint of the SSH Key to fetch.",
MarkdownDescription: "The ID or fingerprint of the SSH Key to fetch.",
},
},
}
}
type SshKeyModel struct {
Id types.Int64 `tfsdk:"id"`
}
+3
View File
@@ -144,6 +144,7 @@ func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.
return []func() datasource.DataSource{
NewServerDataSource,
NewSshKeyDataSource,
NewVpcDataSource,
}
}
@@ -151,5 +152,7 @@ func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Reso
return []func() resource.Resource{
NewServerResource,
NewSshKeyResource,
NewVpcResource,
NewVpcRouteEntriesResource,
}
}
+25
View File
@@ -0,0 +1,25 @@
package provider
import (
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
const (
providerConfig = `
provider "binarylane" {
# api_token = "test123"
# api_endpoint = "https://test.binarylane.internal.au/v2"
}
`
)
var (
// testAccProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach.
testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"binarylane": providerserver.NewProtocol6WithError(New("test")()),
}
)
+90 -14
View File
@@ -2,36 +2,71 @@ package provider
import (
"context"
"terraform-provider-binarylane/internal/data_sources"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var _ datasource.DataSource = (*serverDataSource)(nil)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &serverDataSource{}
_ datasource.DataSourceWithConfigure = &serverDataSource{}
)
func NewServerDataSource() datasource.DataSource {
return &serverDataSource{}
}
type serverDataSource struct{}
type serverDataSource struct {
bc *BinarylaneClient
}
func (d *serverDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (d *serverDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server"
}
func (d *serverDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
},
ds, err := convertResourceSchemaToDataSourceSchema(resources.ServerResourceSchema(ctx))
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
}
}
func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data data_sources.ServerModel
var data resources.ServerModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
@@ -40,10 +75,51 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
// Read API call logic
// TODO: Use go routines here to make requests concurrently
// Example data value setting
// data.Id = types.StringValue("example-id")
// 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()),
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),
)
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.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)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+170 -22
View File
@@ -6,10 +6,16 @@ import (
"net/http"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"time"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
"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/int32default"
"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"
)
@@ -30,6 +36,14 @@ type serverResource struct {
bc *BinarylaneClient
}
type serverModel struct {
resources.ServerModel
WaitForCreateSeconds int32 `tfsdk:"wait_for_create"`
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
}
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -61,23 +75,84 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
resp.Schema.Attributes["password"] = &schema.StringAttribute{
Description: pw.GetDescription(),
MarkdownDescription: pw.GetMarkdownDescription(),
Optional: true,
Computed: true,
Sensitive: true, // Add sensitive to password
Optional: pw.IsOptional(),
Computed: false, // Computed must be false to allow server to be created without password
Sensitive: true, // Mark password as sensitive
}
backups := resp.Schema.Attributes["backups"]
resp.Schema.Attributes["backups"] = &schema.BoolAttribute{
Description: backups.GetDescription(),
MarkdownDescription: backups.GetMarkdownDescription(),
Optional: backups.IsOptional(),
Computed: backups.IsComputed(),
Default: booldefault.StaticBool(false), // Add default to backups
}
user_data := resp.Schema.Attributes["user_data"]
resp.Schema.Attributes["user_data"] = &schema.StringAttribute{
Description: user_data.GetDescription(),
MarkdownDescription: user_data.GetMarkdownDescription(),
Optional: true, // Optional as not all servers have an initialization script
Computed: false, // User defined
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(), // If user changes init script, assume they want to replace server
},
}
vpc_id := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpc_id.GetDescription(),
MarkdownDescription: vpc_id.GetMarkdownDescription(),
Computed: false,
Optional: true,
}
// Additional attributes
waitDescription := "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."
resp.Schema.Attributes["wait_for_create"] = &schema.Int32Attribute{
Description: waitDescription,
MarkdownDescription: waitDescription,
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false), // Add default to backups
Default: int32default.StaticInt32(0),
}
publicIpv4CountDescription := "The number of public IPv4 addresses to assign to the server. If this is not provided, the " +
"server will be created with the default number of public IPv4 addresses."
resp.Schema.Attributes["public_ipv4_count"] = &schema.Int32Attribute{
Description: publicIpv4CountDescription,
MarkdownDescription: publicIpv4CountDescription,
Optional: true,
Computed: true,
Validators: []validator.Int32{
int32validator.AtLeast(0),
},
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
resp.Schema.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
Optional: true,
Computed: true,
ElementType: types.StringType,
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
resp.Schema.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
Description: privateIpv4AddressesDescription,
MarkdownDescription: privateIpv4AddressesDescription,
Optional: true,
Computed: true,
ElementType: types.StringType,
}
}
func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
@@ -90,10 +165,15 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
tflog.Debug(ctx, fmt.Sprintf("Creating server: name=%s", data.Name.ValueString()))
body := binarylane.CreateServerRequest{
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
UserData: data.UserData.ValueStringPointer(),
VpcId: data.VpcId.ValueInt64Pointer(),
Options: &binarylane.SizeOptionsRequest{
Ipv4Addresses: data.PublicIpv4Count.ValueInt32Pointer(),
},
}
if data.Password.IsNull() {
@@ -118,24 +198,84 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
return
}
assignInt64(serverResp.JSON200.Server.Id, &data.Id)
assignStr(serverResp.JSON200.Server.Name, &data.Name)
assignStr(serverResp.JSON200.Server.Image.Slug, &data.Image)
assignStr(serverResp.JSON200.Server.Region.Slug, &data.Region)
assignStr(serverResp.JSON200.Server.Size.Slug, &data.Size)
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
// assignBool(&serverResp.JSON200.Server.Networks.PortBlocking, &plan.PortBlocking)
if resp.Diagnostics.HasError() {
return
publicV4AddressCount := 0
var publicIpv4Addresses []string
var privateIpv4Addresses []string
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicV4AddressCount++
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Count = types.Int32Value(int32(publicV4AddressCount))
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
if data.WaitForCreateSeconds <= 0 {
return
}
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for server to be created, links.actions with rel=create missing from response",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
return
}
timeLimit := time.Now().Add(time.Duration(data.WaitForCreateSeconds) * time.Second)
for {
tflog.Info(ctx, "Waiting for server to be ready...")
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, data.Id.ValueInt64(), createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be ready", err.Error())
return
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
tflog.Info(ctx, "Server is ready")
break
}
if time.Now().After(timeLimit) {
resp.Diagnostics.AddError(
"Timed out waiting for server to be ready",
fmt.Sprintf(
"Timed out waiting for server to be created, as `wait_for_create` was surpassed without "+
"recieving a `completed_at` in response: name=%s, status=%s, details: %s",
data.Name.ValueString(), readyResp.Status(), readyResp.Body,
),
)
return
}
tflog.Debug(ctx, fmt.Sprintf("Waiting for server to be ready: name=%s, status=%s, details: %s", data.Name.ValueString(), readyResp.Status(), readyResp.Body))
time.Sleep(time.Second * 5)
}
}
func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -162,8 +302,11 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
return
}
assignInt64(serverResp.JSON200.Server.Id, &data.Id)
assignStr(serverResp.JSON200.Server.Name, &data.Name)
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
if resp.Diagnostics.HasError() {
return
@@ -174,7 +317,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
}
func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@@ -191,7 +334,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -224,3 +367,8 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
return
}
}
// func (r *serverResource) 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)
// }
+74
View File
@@ -0,0 +1,74 @@
package provider
import (
"crypto/rand"
"encoding/base64"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestServerResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
rand.Read(pw_bytes)
password := base64.URLEncoding.EncodeToString(pw_bytes)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-server-resource-test"
ip_range = "10.240.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-server-resource-test"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
vpc_id = binarylane_vpc.test.id
public_ipv4_count = 0
user_data = <<EOT
#cloud-config
echo "Hello World" > /var/tmp/output.txt
EOT
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-server-resource-test"),
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "0"),
resource.TestCheckResourceAttr("binarylane_server.test", "password", password),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
`),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "0"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "private_ipv4_addresses.0"),
// Verify data source values
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "name", "tf-server-resource-test"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
),
},
},
})
}
+29 -8
View File
@@ -7,11 +7,14 @@ import (
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var _ datasource.DataSource = (*sshKeyDataSource)(nil)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &sshKeyDataSource{}
_ datasource.DataSourceWithConfigure = &sshKeyDataSource{}
)
func NewSshKeyDataSource() datasource.DataSource {
return &sshKeyDataSource{}
@@ -21,18 +24,36 @@ type sshKeyDataSource struct {
bc *BinarylaneClient
}
func (d *sshKeyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (d *sshKeyDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_ssh_key"
}
func (d *sshKeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
},
ds, err := convertResourceSchemaToDataSourceSchema(resources.SshKeyResourceSchema(ctx))
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
}
func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+39 -6
View File
@@ -10,6 +10,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -55,14 +57,22 @@ func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest,
resp.Schema.Description = "TODO"
// Overrides
defaultAttr := resp.Schema.Attributes["default"]
default_ := resp.Schema.Attributes["default"]
resp.Schema.Attributes["default"] = schema.BoolAttribute{
Optional: true,
Computed: true,
Description: defaultAttr.GetDescription(),
MarkdownDescription: defaultAttr.GetMarkdownDescription(),
Description: default_.GetDescription(),
MarkdownDescription: default_.GetMarkdownDescription(),
Default: booldefault.StaticBool(false), // Add default to backups
}
public_key := resp.Schema.Attributes["public_key"]
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
Required: true,
Description: public_key.GetDescription(),
MarkdownDescription: public_key.GetMarkdownDescription(),
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
}
}
func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -113,6 +123,8 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
// 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
}
@@ -138,7 +150,7 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
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 = types.StringValue(*sshResp.JSON200.SshKey.PublicKey) // don't set or it will force replacement every time
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -149,13 +161,29 @@ func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest,
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
// TODO
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
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -188,3 +216,8 @@ func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest,
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)
// }
@@ -0,0 +1,67 @@
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestSshKeyResource(t *testing.T) {
resource.Test(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"
}
data "binarylane_ssh_key" "test" {
depends_on = [binarylane_ssh_key.test]
id = binarylane_ssh_key.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf_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", "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", "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
},
})
}
+104 -27
View File
@@ -1,36 +1,113 @@
package provider
import (
"fmt"
"strings"
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
r_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
func assignStr(source *string, target *basetypes.StringValue) {
if source != nil {
*target = types.StringValue(*source)
func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema) (*d_schema.Schema, error) {
ds := d_schema.Schema{
Attributes: make(map[string]d_schema.Attribute, len(rs.Attributes)),
Blocks: make(map[string]d_schema.Block),
Description: rs.GetDescription(),
MarkdownDescription: rs.GetMarkdownDescription(),
DeprecationMessage: rs.GetDeprecationMessage(),
}
}
// func assignInt(source *int, target *basetypes.Int64Value) {
// if source != nil {
// *target = types.Int64Value(int64(*source))
// }
// }
func assignInt64(source *int64, target *basetypes.Int64Value) {
if source != nil {
*target = types.Int64Value(int64(*source))
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())
}
}
}
return &ds, nil
}
// func assignFloat(source *float32, target *basetypes.Float64Value) {
// if source != nil {
// *target = types.Float64Value(float64(*source))
// }
// }
// func assignBool(source *bool, target *basetypes.BoolValue) {
// if source != nil {
// *target = types.BoolValue(bool(*source))
// }
// }
+100
View File
@@ -0,0 +1,100 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
_ datasource.DataSource = &sshKeyDataSource{}
_ datasource.DataSourceWithConfigure = &sshKeyDataSource{}
)
func NewVpcDataSource() datasource.DataSource {
return &vpcDataSource{}
}
type vpcDataSource struct {
bc *BinarylaneClient
}
type vpcDataSourceModel struct {
resources.VpcModel
}
func (d *vpcDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
func (d *vpcDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc"
}
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(resources.VpcResourceSchema(ctx))
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
}
func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data vpcDataSourceModel
// Read Terraform configuration data into the model
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := d.bc.client.GetVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
// resp.Diagnostics.Append(routeEntriesDiags...)
// if resp.Diagnostics.HasError() {
// return
// }
// data.RouteEntries = routeEntries
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
+194
View File
@@ -0,0 +1,194 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
// _ resource.ResourceWithImportState = &exampleResource{}
)
func NewVpcResource() resource.Resource {
return &vpcResource{}
}
type vpcResource struct {
bc *BinarylaneClient
}
type vpcResourceModel struct {
resources.VpcModel
}
func (r *vpcResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
r.bc = &bc
}
func (r *vpcResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc"
}
func (r *vpcResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.VpcResourceSchema(ctx)
resp.Schema.Description = "TODO"
}
func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data vpcResourceModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
tflog.Debug(ctx, fmt.Sprintf("Creating VPC: name=%s", data.Name.ValueString()))
body := binarylane.CreateVpcRequest{
Name: data.Name.ValueString(),
IpRange: data.IpRange.ValueStringPointer(),
}
vpcResp, err := r.bc.client.PostVpcsWithResponse(ctx, body)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC",
fmt.Sprintf("Received %s creating new VPC: name=%s. Details: %s", vpcResp.Status(), data.Name.ValueString(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data vpcResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := r.bc.client.GetVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data vpcResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append()
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", data.Id.ValueInt64()))
vpcResp, err := r.bc.client.PutVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64(), binarylane.UpdateVpcRequest{
Name: data.Name.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating VPC",
fmt.Sprintf("Received %s updating VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data vpcResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
tflog.Debug(ctx, fmt.Sprintf("Deleting VPC: id=%d", data.Id.ValueInt64()))
vpcResp, err := r.bc.client.DeleteVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusNoContent {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting VPC",
fmt.Sprintf("Received %s deleting VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
}
+58
View File
@@ -0,0 +1,58 @@
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestVpcResource(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-vpc"
ip_range = "10.240.0.0/16"
}
data "binarylane_vpc" "test" {
depends_on = [binarylane_vpc.test]
id = binarylane_vpc.test.id
}
resource "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc.test.id
route_entries = [
{
description = "test"
destination = "0.0.0.0/0"
router = "10.240.0.1"
}
]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify data source values
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("data.binarylane_vpc.test", "id"),
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
),
},
},
})
}
@@ -0,0 +1,255 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
// _ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
)
func NewVpcRouteEntriesResource() resource.Resource {
return &vpcRouteEntriesResource{}
}
type vpcRouteEntriesResource struct {
bc *BinarylaneClient
}
type vpcRouteEntriesResourceModel struct {
resources.VpcRouteEntriesModel
}
func (r *vpcRouteEntriesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc_route_entries"
}
func (r *vpcRouteEntriesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.VpcRouteEntriesResourceSchema(ctx)
resp.Schema.Description = "TODO"
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
}
}
func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
var routeEntries []binarylane.RouteEntryRequest
diags := data.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC route entries",
fmt.Sprintf("Received %s creating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntriesState
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := r.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntries
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
var routeEntries []binarylane.RouteEntryRequest
diags := data.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC route entries",
fmt.Sprintf("Received %s creating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntriesState
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcRouteEntriesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
var routeEntries []binarylane.RouteEntryRequest
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting VPC route entries",
fmt.Sprintf("Received %s deleting VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
}
func (d *vpcRouteEntriesResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func getRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteEntry) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics
routeEntriesValue := resources.RouteEntriesValue{}
var routeEntriesValues []resources.RouteEntriesValue
if *routeEntries == nil || len(*routeEntries) == 0 {
routeEntriesValue = resources.RouteEntriesValue{}
} else {
for _, route := range *routeEntries {
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringValue(*route.Description),
"destination": types.StringValue(*route.Destination),
"router": types.StringValue(*route.Router),
})
routeEntriesValues = append(routeEntriesValues, r)
}
}
routeEntriesListValue, diag := types.ListValueFrom(ctx, routeEntriesValue.Type(ctx), routeEntriesValues)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(routeEntriesValue.Type(ctx)), diags
}
return routeEntriesListValue, diags
}
+19 -2
View File
@@ -17,8 +17,8 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
"backups": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n",
MarkdownDescription: "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n",
Description: "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n",
MarkdownDescription: "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n",
},
"id": schema.Int64Attribute{
Optional: true,
@@ -59,6 +59,21 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
stringvalidator.LengthAtLeast(1),
},
},
"user_data": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
MarkdownDescription: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
Validators: []validator.String{
stringvalidator.LengthAtMost(65536),
},
},
"vpc_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "Leave null to use default (public) network for the selected region.",
MarkdownDescription: "Leave null to use default (public) network for the selected region.",
},
},
}
}
@@ -71,4 +86,6 @@ type ServerModel struct {
Password types.String `tfsdk:"password"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
}
+45
View File
@@ -0,0 +1,45 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func VpcResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The target vpc id.",
MarkdownDescription: "The target vpc id.",
},
"ip_range": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.",
MarkdownDescription: "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.",
},
"name": schema.StringAttribute{
Required: true,
Description: "A name to help identify this VPC.",
MarkdownDescription: "A name to help identify this VPC.",
Validators: []validator.String{
stringvalidator.LengthBetween(0, 250),
},
},
},
}
}
type VpcModel struct {
Id types.Int64 `tfsdk:"id"`
IpRange types.String `tfsdk:"ip_range"`
Name types.String `tfsdk:"name"`
}
@@ -0,0 +1,510 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func VpcRouteEntriesResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"route_entries": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "An optional description for the route.",
MarkdownDescription: "An optional description for the route.",
Validators: []validator.String{
stringvalidator.LengthAtMost(250),
},
},
"destination": schema.StringAttribute{
Required: true,
Description: "The destination address for this route entry. This may be in CIDR format.",
MarkdownDescription: "The destination address for this route entry. This may be in CIDR format.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"router": schema.StringAttribute{
Required: true,
Description: "The server that will receive traffic sent to the destination property in this VPC.",
MarkdownDescription: "The server that will receive traffic sent to the destination property in this VPC.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
},
CustomType: RouteEntriesType{
ObjectType: types.ObjectType{
AttrTypes: RouteEntriesValue{}.AttributeTypes(ctx),
},
},
},
Optional: true,
Computed: true,
Description: "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save.",
MarkdownDescription: "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save.",
},
"vpc_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The target vpc id.",
MarkdownDescription: "The target vpc id.",
},
},
}
}
type VpcRouteEntriesModel struct {
RouteEntries types.List `tfsdk:"route_entries"`
VpcId types.Int64 `tfsdk:"vpc_id"`
}
var _ basetypes.ObjectTypable = RouteEntriesType{}
type RouteEntriesType struct {
basetypes.ObjectType
}
func (t RouteEntriesType) Equal(o attr.Type) bool {
other, ok := o.(RouteEntriesType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t RouteEntriesType) String() string {
return "RouteEntriesType"
}
func (t RouteEntriesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return nil, diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAttribute, ok := attributes["destination"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination is missing from object`)
return nil, diags
}
destinationVal, ok := destinationAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination expected to be basetypes.StringValue, was: %T`, destinationAttribute))
}
routerAttribute, ok := attributes["router"]
if !ok {
diags.AddError(
"Attribute Missing",
`router is missing from object`)
return nil, diags
}
routerVal, ok := routerAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router expected to be basetypes.StringValue, was: %T`, routerAttribute))
}
if diags.HasError() {
return nil, diags
}
return RouteEntriesValue{
Description: descriptionVal,
Destination: destinationVal,
Router: routerVal,
state: attr.ValueStateKnown,
}, diags
}
func NewRouteEntriesValueNull() RouteEntriesValue {
return RouteEntriesValue{
state: attr.ValueStateNull,
}
}
func NewRouteEntriesValueUnknown() RouteEntriesValue {
return RouteEntriesValue{
state: attr.ValueStateUnknown,
}
}
func NewRouteEntriesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (RouteEntriesValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing RouteEntriesValue Attribute Value",
"While creating a RouteEntriesValue value, a missing attribute value was detected. "+
"A RouteEntriesValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid RouteEntriesValue Attribute Type",
"While creating a RouteEntriesValue value, an invalid attribute value was detected. "+
"A RouteEntriesValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra RouteEntriesValue Attribute Value",
"While creating a RouteEntriesValue value, an extra attribute value was detected. "+
"A RouteEntriesValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra RouteEntriesValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewRouteEntriesValueUnknown(), diags
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAttribute, ok := attributes["destination"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
destinationVal, ok := destinationAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination expected to be basetypes.StringValue, was: %T`, destinationAttribute))
}
routerAttribute, ok := attributes["router"]
if !ok {
diags.AddError(
"Attribute Missing",
`router is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
routerVal, ok := routerAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router expected to be basetypes.StringValue, was: %T`, routerAttribute))
}
if diags.HasError() {
return NewRouteEntriesValueUnknown(), diags
}
return RouteEntriesValue{
Description: descriptionVal,
Destination: destinationVal,
Router: routerVal,
state: attr.ValueStateKnown,
}, diags
}
func NewRouteEntriesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) RouteEntriesValue {
object, diags := NewRouteEntriesValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewRouteEntriesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t RouteEntriesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewRouteEntriesValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewRouteEntriesValueUnknown(), nil
}
if in.IsNull() {
return NewRouteEntriesValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewRouteEntriesValueMust(RouteEntriesValue{}.AttributeTypes(ctx), attributes), nil
}
func (t RouteEntriesType) ValueType(ctx context.Context) attr.Value {
return RouteEntriesValue{}
}
var _ basetypes.ObjectValuable = RouteEntriesValue{}
type RouteEntriesValue struct {
Description basetypes.StringValue `tfsdk:"description"`
Destination basetypes.StringValue `tfsdk:"destination"`
Router basetypes.StringValue `tfsdk:"router"`
state attr.ValueState
}
func (v RouteEntriesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 3)
var val tftypes.Value
var err error
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["destination"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["router"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 3)
val, err = v.Description.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["description"] = val
val, err = v.Destination.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination"] = val
val, err = v.Router.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["router"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v RouteEntriesValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v RouteEntriesValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v RouteEntriesValue) String() string {
return "RouteEntriesValue"
}
func (v RouteEntriesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"description": basetypes.StringType{},
"destination": basetypes.StringType{},
"router": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"description": v.Description,
"destination": v.Destination,
"router": v.Router,
})
return objVal, diags
}
func (v RouteEntriesValue) Equal(o attr.Value) bool {
other, ok := o.(RouteEntriesValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Description.Equal(other.Description) {
return false
}
if !v.Destination.Equal(other.Destination) {
return false
}
if !v.Router.Equal(other.Router) {
return false
}
return true
}
func (v RouteEntriesValue) Type(ctx context.Context) attr.Type {
return RouteEntriesType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v RouteEntriesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"description": basetypes.StringType{},
"destination": basetypes.StringType{},
"router": basetypes.StringType{},
}
}
+149 -31
View File
@@ -1,34 +1,4 @@
{
"datasources": [
{
"name": "server",
"schema": {
"attributes": [
{
"name": "id",
"int64": {
"computed_optional_required": "required",
"description": "The ID of the server to fetch."
}
}
]
}
},
{
"name": "ssh_key",
"schema": {
"attributes": [
{
"name": "id",
"int64": {
"computed_optional_required": "required",
"description": "The ID or fingerprint of the SSH Key to fetch."
}
}
]
}
}
],
"provider": {
"name": "binarylane"
},
@@ -41,7 +11,7 @@
"name": "backups",
"bool": {
"computed_optional_required": "computed_optional",
"description": "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n"
"description": "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n"
}
},
{
@@ -103,6 +73,32 @@
]
}
},
{
"name": "user_data",
"string": {
"computed_optional_required": "computed_optional",
"description": "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtMost(65536)"
}
}
]
}
},
{
"name": "vpc_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "Leave null to use default (public) network for the selected region."
}
},
{
"name": "id",
"int64": {
@@ -171,6 +167,128 @@
}
]
}
},
{
"name": "vpc",
"schema": {
"attributes": [
{
"name": "ip_range",
"string": {
"computed_optional_required": "computed_optional",
"description": "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like."
}
},
{
"name": "name",
"string": {
"computed_optional_required": "required",
"description": "A name to help identify this VPC.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthBetween(0, 250)"
}
}
]
}
},
{
"name": "id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The target vpc id."
}
}
]
}
},
{
"name": "vpc_route_entries",
"schema": {
"attributes": [
{
"name": "route_entries",
"list_nested": {
"computed_optional_required": "computed_optional",
"nested_object": {
"attributes": [
{
"name": "description",
"string": {
"computed_optional_required": "computed_optional",
"description": "An optional description for the route.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtMost(250)"
}
}
]
}
},
{
"name": "destination",
"string": {
"computed_optional_required": "required",
"description": "The destination address for this route entry. This may be in CIDR format.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
},
{
"name": "router",
"string": {
"computed_optional_required": "required",
"description": "The server that will receive traffic sent to the destination property in this VPC.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
}
]
},
"description": "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save."
}
},
{
"name": "vpc_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The target vpc id."
}
}
]
}
}
],
"version": "0.1"
+34 -25
View File
@@ -27,11 +27,13 @@ resources:
attributes:
aliases:
server_id: id
ipv4_addresses: options.ipv4_addresses
overrides:
backups:
type: bool
description: >
If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.
If true this will enable two daily backups for the server. `options.daily_backups` will override this
value if provided. Setting this to false has no effect.
optional: true
default:
static: false
@@ -39,7 +41,7 @@ resources:
image:
type: string
description: The slug of the selected operating system.
example: "ubuntu-24.04"
example: 'ubuntu-24.04'
password:
type: string
description: >
@@ -53,12 +55,10 @@ resources:
- ipv6
- licenses
- links
- options
- server
- ssh_keys
- vpc_id
- user_data
- port_blocking
- options
ssh_key:
create:
path: /account/keys
@@ -75,33 +75,42 @@ resources:
key_id: id
ignores:
- ssh_key
data_sources:
server:
vpc:
create:
path: /vpcs
method: POST
read:
path: /servers/{server_id}
path: /vpcs/{vpc_id}
method: GET
update:
path: /vpcs/{vpc_id}
method: PATCH
delete:
path: /vpcs/{vpc_id}
method: DELETE
schema:
attributes:
aliases:
server_id: id
vpc_id: id
ignores:
- ipv6
- licenses
- links
- options
- server
- ssh_keys
- vpc_id
- user_data
- port_blocking
ssh_key:
- route_entries
- vpc
vpc_route_entries:
create:
path: /vpcs/{vpc_id}
method: PATCH
read:
path: /account/keys/{key_id}
path: /vpcs/{vpc_id}
method: GET
update:
path: /vpcs/{vpc_id}
method: PATCH
delete:
path: /vpcs/{vpc_id}
method: DELETE
schema:
attributes:
aliases:
key_id: id
ignores:
- ssh_key
- name
- vpc
# data_sources:
# Currently, don't need to generate data sources as they use the same schema as resources