forked from mirrors/terraform-provider-binarylane
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f24f45af7 | |||
| 924e785de2 | |||
| f4c91d2f94 | |||
| f73528497f | |||
| f1b7b9853e |
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
- package-ecosystem: 'gomod'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
+1
-1
@@ -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
|
||||
|
||||
+91
-26
@@ -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"
|
||||
@@ -64,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
|
||||
+ }
|
||||
@@ -107,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"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -21,9 +21,11 @@ TODO
|
||||
|
||||
### 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.
|
||||
- `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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Generated
-24
@@ -1,24 +0,0 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/oscarhermoso/binarylane" {
|
||||
version = "0.1.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",
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
# examples/nix
|
||||
|
||||
WIP
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Local .terraform directories
|
||||
**/.terraform/*
|
||||
|
||||
# .tfstate files
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
|
||||
# Crash log files
|
||||
crash.log
|
||||
crash.*.log
|
||||
|
||||
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
|
||||
# password, private keys, and other secrets. These should not be part of version
|
||||
# control as they are data points which are potentially sensitive and subject
|
||||
# to change depending on the environment.
|
||||
*.tfvars
|
||||
*.tfvars.json
|
||||
|
||||
# Ignore override files as they are usually used to override resources locally and so
|
||||
# are not checked in
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
|
||||
# Ignore transient lock info files created by terraform apply
|
||||
.terraform.tfstate.lock.info
|
||||
|
||||
# Include override files you do wish to add to version control using negated pattern
|
||||
# !example_override.tf
|
||||
|
||||
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
|
||||
# example: *tfplan*
|
||||
|
||||
# Ignore CLI configuration files
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
@@ -0,0 +1,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,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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"terraform-provider-binarylane/internal/resources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
@@ -48,7 +48,12 @@ func (d *serverDataSource) Metadata(ctx context.Context, req datasource.Metadata
|
||||
}
|
||||
|
||||
func (d *serverDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = *convertResourceSchemaToDataSourceSchema(ctx, resources.ServerResourceSchema(ctx))
|
||||
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
|
||||
@@ -70,7 +75,9 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
// TODO: Use go routines here to make requests concurrently
|
||||
|
||||
// Read server
|
||||
serverResp, err := d.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
@@ -79,23 +86,40 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
)
|
||||
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
|
||||
tflog.Trace(ctx,
|
||||
fmt.Sprintf(
|
||||
"Reading server: id=%d, name=%s, image=%s, region=%s, size=%s",
|
||||
*serverResp.JSON200.Server.Id,
|
||||
*serverResp.JSON200.Server.Name,
|
||||
*serverResp.JSON200.Server.Image.Slug,
|
||||
*serverResp.JSON200.Server.Region.Slug,
|
||||
*serverResp.JSON200.Server.Size.Slug,
|
||||
),
|
||||
)
|
||||
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)...)
|
||||
|
||||
@@ -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
|
||||
@@ -70,14 +84,75 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
|
||||
resp.Schema.Attributes["backups"] = &schema.BoolAttribute{
|
||||
Description: backups.GetDescription(),
|
||||
MarkdownDescription: backups.GetMarkdownDescription(),
|
||||
Optional: backups.IsComputed(),
|
||||
Computed: backups.IsOptional(),
|
||||
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: 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)...)
|
||||
@@ -177,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)...)
|
||||
@@ -194,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)...)
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
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 = "ubuntu-24.04"
|
||||
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" {
|
||||
@@ -28,54 +47,28 @@ data "binarylane_server" "test" {
|
||||
`,
|
||||
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", "ubuntu-24.04"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
|
||||
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
|
||||
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
|
||||
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", "ubuntu-24.04"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
|
||||
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "id"),
|
||||
),
|
||||
},
|
||||
// ImportState testing
|
||||
// {
|
||||
// ResourceName: "binarylane_server.test",
|
||||
// ImportState: true,
|
||||
// ImportStateVerify: true,
|
||||
// ImportStateVerifyIgnore: []string{}, // nothing to ignore
|
||||
// },
|
||||
// TODO: Update and Read testing
|
||||
// {
|
||||
// Config: providerConfig + `
|
||||
// resource "binarylane_server" "test" {
|
||||
// items = [
|
||||
// {
|
||||
// coffee = {
|
||||
// id = 2
|
||||
// }
|
||||
// quantity = 2
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
// `,
|
||||
// Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// // Verify first order item updated
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.quantity", "2"),
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.id", "2"),
|
||||
// // Verify first coffee item has Computed attributes updated.
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.description", ""),
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.image", "/packer.png"),
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.name", "Packer Spiced Latte"),
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.price", "350"),
|
||||
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.teaser", "Packed with goodness to spice up your images"),
|
||||
// ),
|
||||
// },
|
||||
// Delete testing automatically occurs in TestCase
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,7 +47,12 @@ func (d *sshKeyDataSource) Metadata(ctx context.Context, req datasource.Metadata
|
||||
}
|
||||
|
||||
func (d *sshKeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = *convertResourceSchemaToDataSourceSchema(ctx, resources.SshKeyResourceSchema(ctx))
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
+18
-39
@@ -1,46 +1,15 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
func assignStr(source *string, target *basetypes.StringValue) {
|
||||
if source != nil {
|
||||
*target = types.StringValue(*source)
|
||||
}
|
||||
}
|
||||
|
||||
// func assignInt(source *int, target *basetypes.Int64Value) {
|
||||
// if source != nil {
|
||||
// *target = types.Int64Value(int64(*source))
|
||||
// }
|
||||
// }
|
||||
|
||||
func assignInt64(source *int64, target *basetypes.Int64Value) {
|
||||
if source != nil {
|
||||
*target = types.Int64Value(int64(*source))
|
||||
}
|
||||
}
|
||||
|
||||
// func 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))
|
||||
// }
|
||||
// }
|
||||
|
||||
func convertResourceSchemaToDataSourceSchema(ctx context.Context, rs r_schema.Schema) *d_schema.Schema {
|
||||
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),
|
||||
@@ -123,12 +92,22 @@ func convertResourceSchemaToDataSourceSchema(ctx context.Context, rs r_schema.Sc
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
}
|
||||
default:
|
||||
tflog.Error(ctx, "Failed to convert resource schema attribute to data source schema attribute", map[string]interface{}{
|
||||
"attribute": name,
|
||||
"type": attr.GetType(),
|
||||
})
|
||||
// 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
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
@@ -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)...)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -11,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -73,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": {
|
||||
@@ -141,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"
|
||||
|
||||
+43
-35
@@ -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,34 +75,42 @@ resources:
|
||||
key_id: id
|
||||
ignores:
|
||||
- ssh_key
|
||||
#
|
||||
# Currently don't need to generate data sources as they use the same schema as resources
|
||||
vpc:
|
||||
create:
|
||||
path: /vpcs
|
||||
method: POST
|
||||
read:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: GET
|
||||
update:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: PATCH
|
||||
delete:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: DELETE
|
||||
schema:
|
||||
attributes:
|
||||
aliases:
|
||||
vpc_id: id
|
||||
ignores:
|
||||
- route_entries
|
||||
- vpc
|
||||
vpc_route_entries:
|
||||
create:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: PATCH
|
||||
read:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: GET
|
||||
update:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: PATCH
|
||||
delete:
|
||||
path: /vpcs/{vpc_id}
|
||||
method: DELETE
|
||||
schema:
|
||||
ignores:
|
||||
- name
|
||||
- vpc
|
||||
# data_sources:
|
||||
# server:
|
||||
# read:
|
||||
# path: /servers/{server_id}
|
||||
# method: GET
|
||||
# schema:
|
||||
# attributes:
|
||||
# aliases:
|
||||
# server_id: id
|
||||
# ignores:
|
||||
# - ipv6
|
||||
# - licenses
|
||||
# - links
|
||||
# - options
|
||||
# - server
|
||||
# - ssh_keys
|
||||
# - vpc_id
|
||||
# - user_data
|
||||
# - port_blocking
|
||||
# ssh_key:
|
||||
# read:
|
||||
# path: /account/keys/{key_id}
|
||||
# method: GET
|
||||
# schema:
|
||||
# attributes:
|
||||
# aliases:
|
||||
# key_id: id
|
||||
# ignores:
|
||||
# - ssh_key
|
||||
# Currently, don't need to generate data sources as they use the same schema as resources
|
||||
|
||||
Reference in New Issue
Block a user