Compare commits

...

2 Commits

Author SHA1 Message Date
Oscar Hermoso d5f45788b2 🚒 Create server_firewall_rules resource (#7) 2024-08-26 17:45:48 +08:00
Oscar Hermoso d0716f58af Add data source for VPC Route Entries, imports for VPC, fix update/tests (#6) 2024-08-25 12:47:14 +08:00
39 changed files with 2132 additions and 122 deletions
+20 -7
View File
@@ -70,9 +70,9 @@ Define the interfaces that the resource should implement:
```diff
- var _ resource.Resource = (*exampleResource)(nil)
+ var (
+ _ resource.Resource = &exampleResource{}
+ _ resource.ResourceWithConfigure = &exampleResource{}
+ // _ resource.ResourceWithImportState = &exampleResource{}
+ _ resource.Resource = &exampleResource{}
+ _ resource.ResourceWithConfigure = &exampleResource{}
+ _ resource.ResourceWithImportState = &exampleResource{}
+ )
```
@@ -106,7 +106,7 @@ Add a `Configure` method to the resource:
+ bc, ok := req.ProviderData.(BinarylaneClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
+ )
@@ -117,6 +117,14 @@ Add a `Configure` method to the resource:
+ }
```
```diff
+ func (r *exampleResource) ImportState(
+ ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
+ ) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+ }
```
Add the new data source/resource to `provider.go`:
```diff
@@ -158,7 +166,7 @@ Derive the data source model from the generated `resources.*Model`.
Add a `Configure` method to the data source.
```diff
+ func (d *vpcDataSource) Configure(
+ func (d *exampleDataSource) Configure(
+ _ context.Context,
+ req datasource.ConfigureRequest,
+ resp *datasource.ConfigureResponse,
@@ -182,7 +190,7 @@ Add a `Configure` method to the data source.
Use the generated schema to define the data source schema.
```diff
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Attributes: map[string]schema.Attribute{
- "id": schema.StringAttribute{
@@ -190,7 +198,12 @@ Use the generated schema to define the data source schema.
- },
- },
- }
+ resp.Schema = *convertResourceSchemaToDataSourceSchema(ctx, resources.ExampleResourceSchema(ctx))
+ ds, err := convertResourceSchemaToDataSourceSchema(resources.ExampleResourceSchema(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"
}
```
+3 -2
View File
@@ -15,8 +15,9 @@ resource "binarylane_server" "example" {
If somehow you use this in production I would be pretty impressed.
- [x] Create/delete a server when runing locally
- [x] [Publish to Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest/docs/resources/server)
- [ ] (and maybe OpenTofu?)
- [x] Publish
- [x] [Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest/docs/resources/server)
- [x] OpenTofu
- [ ] Deploy the rest of the owl
## Parameters
+1
View File
@@ -24,6 +24,7 @@ TODO
- `backups` (Boolean) If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.
- `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.
- `password` (String) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.
- `user_data` (String) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_server_firewall_rules Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_server_firewall_rules (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Optional
- `server_id` (Number) The ID of the server for which firewall rules should be listed.
### Read-Only
- `firewall_rules` (List of Object) A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules. (see [below for nested schema](#nestedatt--firewall_rules))
<a id="nestedatt--firewall_rules"></a>
### Nested Schema for `firewall_rules`
Read-Only:
- `action` (String)
- `description` (String)
- `destination_addresses` (List of String)
- `destination_ports` (List of String)
- `protocol` (String)
- `source_addresses` (List of String)
+33
View File
@@ -0,0 +1,33 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc_route_entries Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_vpc_route_entries (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `vpc_id` (Number) The target vpc id.
### Optional
- `route_entries` (List of Object) Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save. (see [below for nested schema](#nestedatt--route_entries))
<a id="nestedatt--route_entries"></a>
### Nested Schema for `route_entries`
Optional:
- `description` (String)
- `destination` (String)
- `router` (String)
+6 -2
View File
@@ -27,9 +27,13 @@ Provides a Binary Lane Server resource. This can be used to create and delete se
- `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.
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.
- `public_ipv4_count` (Number) The number of public IPv4 addresses to assign to the server. If this is not provided, the server will be created with the default number of public IPv4 addresses.
- `user_data` (String) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
- `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.
### Read-Only
- `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.
+39
View File
@@ -0,0 +1,39 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_server_firewall_rules Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_server_firewall_rules (Resource)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `firewall_rules` (Attributes List) A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules. (see [below for nested schema](#nestedatt--firewall_rules))
### Optional
- `server_id` (Number) The ID of the server for which firewall rules should be listed.
<a id="nestedatt--firewall_rules"></a>
### Nested Schema for `firewall_rules`
Required:
- `action` (String) The action to take when there is a match on this rule.
- `destination_addresses` (List of String) The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
- `protocol` (String) The protocol to match for this rule.
- `source_addresses` (List of String) The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
Optional:
- `description` (String) A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".
- `destination_ports` (List of String) The destination ports to match for this rule. Leave null or empty to match on all ports.
+3 -2
View File
@@ -5,5 +5,6 @@ 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.
- [./nix](./nix/main.tf) - WIP - Create a server with a NixOS configuration.
- [./vpc](./vpc/main.tf) - WIP - Create a server with a Virtual Private Cloud.
- [./k3s](./k3s/main.tf) - WIP - Create Kubernetes cluster using k3s.
-1
View File
@@ -7,7 +7,6 @@ terraform {
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
+1 -1
View File
@@ -14,7 +14,7 @@ terraform {
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_url = "" # Defaults to "https://api.binarylane.com.au/v2"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
+1 -4
View File
@@ -6,10 +6,7 @@ terraform {
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
provider "binarylane" {}
resource "binarylane_server" "example" {
name = "tf-cloud-init-example"
+37
View File
@@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
+7
View File
@@ -0,0 +1,7 @@
# WIP - examples/k8s
This WIP example shows how to create a Kubernetes cluster on Binary Lane.
Prior art:
- [MartinHodges/create_k8s](https://github.com/MartinHodges/create_k8s) repo and the [medium article](https://medium.com/@martin.hodges/creating-a-kubernetes-cluster-from-scratch-in-1-hour-using-automation-a25e387be547) that goes with it.
- https://github.com/inscapist/terraform-k3s-private-cloud
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash -x
exec >/tmp/k3s-agent-join-debug.log 2>&1
export K3S_TOKEN="${cluster_token}"
export K3S_URL="https://${cluster_server}:6443"
curl -sfL https://get.k3s.io | sh -s - agent \
--node-name="$(hostname -f)"
unset K3S_TOKEN
unset K3S_URL
echo "K3s Node Join Completed"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash -x
exec >/tmp/k3s-server-install-debug.log 2>&1
export INSTALL_K3S_NAME="${cluster_id}"
export K3S_TOKEN="${cluster_token}"
export K3S_KUBECONFIG_MODE="644"
curl -sfL https://get.k3s.io | sh -s - server \
--node-name="$(hostname -f)"
# --disable-cloud-controller \
# --disable servicelb \
# --disable local-storage \
# --disable traefik \
unset INSTALL_K3S_NAME
unset K3S_TOKEN
unset K3S_KUBECONFIG_MODE
echo "Installing Helm"
curl -sfL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
echo "K3s Setup Completed"
+126
View File
@@ -0,0 +1,126 @@
locals {
cluster_id = "tf-example-k8s"
custom_domain = "tf-example-k8s.internal"
}
resource "random_password" "binarylane" {
length = 18
}
resource "random_password" "cluster_token" {
length = 64
special = false
}
# Virtual Private Cloud
# ---------------------
resource "binarylane_vpc" "example" {
name = local.cluster_id
ip_range = "10.240.0.0/16"
}
# k3s Servers
# -----------
data "cloudinit_config" "server" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-server-install.sh", {
cluster_id = local.cluster_id,
cluster_token = random_password.cluster_token.result,
})
}
}
resource "binarylane_server" "server" {
count = 1
name = "${local.cluster_id}-server-${count.index + 1}"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
password = random_password.binarylane.result
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.server.rendered)
}
# k3s Agents
# ----------
data "cloudinit_config" "agent" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-agent-join.sh", {
cluster_token = random_password.cluster_token.result,
cluster_server = binarylane_server.server.0.private_ipv4_addresses.0
})
}
}
resource "binarylane_server" "agent" {
count = 2
name = "${local.cluster_id}-agent-${count.index + 1}"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
password = random_password.binarylane.result
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.agent.rendered)
}
# Virtual Private Cloud Routing
# -----------------------------
resource "binarylane_vpc_route_entries" "example" {
vpc_id = binarylane_vpc.example.id
route_entries = [
# {
# description = "NAT"
# destination = "0.0.0.0/0"
# router = binarylane_server.gateway.private_ipv4_addresses.0
# }
]
}
resource "binarylane_server_firewall_rules" "example" {
for_each = { for server in flatten([binarylane_server.server, binarylane_server.agent]) : server.id => server }
server_id = each.value.id
firewall_rules = [
{
description = "K3s supervisor and Kubernetes API Server"
protocol = "tcp"
source_addresses = flatten([ # agents
for _, agent in binarylane_server.agent : agent.private_ipv4_addresses
])
destination_addresses = flatten([ # server_ips
for _, server in binarylane_server.server : server.private_ipv4_addresses
])
destination_ports = ["6443"]
action = "accept"
},
{
description = "Flannel VXLAN"
protocol = "udp"
source_addresses = [binarylane_vpc.example.ip_range]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["8472"]
action = "accept"
},
{
description = "Kubelet metrics"
protocol = "tcp"
source_addresses = [binarylane_vpc.example.ip_range]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["10250"]
action = "accept"
},
]
}
+9
View File
@@ -0,0 +1,9 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {}
+1 -4
View File
@@ -6,10 +6,7 @@ terraform {
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
provider "binarylane" {}
resource "binarylane_server" "example" {
name = "tf-nix-example"
+1 -4
View File
@@ -6,10 +6,7 @@ terraform {
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
provider "binarylane" {}
resource "binarylane_ssh_key" "example" {
name = "test-user"
+1 -4
View File
@@ -6,10 +6,7 @@ terraform {
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
provider "binarylane" {}
resource "binarylane_vpc" "example" {
name = "tf-example"
+16 -3
View File
@@ -4,10 +4,11 @@ OPENAPI_FILE=$(dirname "$0")/openapi.json
# Fetch the latest OpenAPI spec
curl https://api.binarylane.com.au/reference/openapi.json --output $OPENAPI_FILE
# Remove the /v2 prefix from the paths
cat <<<$(jq '.paths |= with_entries(.key |= sub("/v2/"; "/"))' $OPENAPI_FILE) >$OPENAPI_FILE
# Set the base URL with a /v2 suffix
# Move the /v2 prefix to the base URL and remove it from the paths
cat <<<$(jq '.servers[0].url = "https://api.binarylane.com.au/v2"' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths |= with_entries(.key |= sub("/v2/"; "/"))' $OPENAPI_FILE) >$OPENAPI_FILE
# Terraform can't handle oneOf types, so we need to replace them with basic types
cat <<<$(jq '.components.schemas.CreateServerRequest.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
@@ -15,9 +16,21 @@ cat <<<$(jq '.paths["/account/keys/{key_id}"].put.parameters[0].schema |= del(.o
cat <<<$(jq '.paths["/account/keys/{key_id}"].get.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
# Remove the "/paths/{image_id}" path because its duplicated by "/images/{image_id_or_slug}"
cat <<<$(jq 'del(.paths."/images/{image_id}")' $OPENAPI_FILE) >$OPENAPI_FILE
# Add x-oapi-codegen-extra-tags so Go structs can be reflected
## RouteEntryRequest
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.destination += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.description += {"x-oapi-codegen-extra-tags": {"tfsdk": "description"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.router += {"x-oapi-codegen-extra-tags": {"tfsdk": "router"}}' $OPENAPI_FILE) >$OPENAPI_FILE
## AdvancedFirewallRule
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.description += {"x-oapi-codegen-extra-tags": {"tfsdk": "description"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.action += {"x-oapi-codegen-extra-tags": {"tfsdk": "action"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_ports += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_ports"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.source_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "source_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
+24 -6
View File
@@ -8811,7 +8811,10 @@
"type": "string"
},
"description": "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"x-cli-format": 1
"x-cli-format": 1,
"x-oapi-codegen-extra-tags": {
"tfsdk": "source_addresses"
}
},
"destination_addresses": {
"minItems": 1,
@@ -8820,7 +8823,10 @@
"type": "string"
},
"description": "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"x-cli-format": 2
"x-cli-format": 2,
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination_addresses"
}
},
"destination_ports": {
"type": "array",
@@ -8829,7 +8835,10 @@
},
"description": "The destination ports to match for this rule. Leave null or empty to match on all ports.",
"nullable": true,
"x-cli-format": 4
"x-cli-format": 4,
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination_ports"
}
},
"protocol": {
"allOf": [
@@ -8838,7 +8847,10 @@
}
],
"description": "The protocol to match for this rule.",
"x-cli-format": 3
"x-cli-format": 3,
"x-oapi-codegen-extra-tags": {
"tfsdk": "protocol"
}
},
"action": {
"allOf": [
@@ -8847,7 +8859,10 @@
}
],
"description": "The action to take when there is a match on this rule.",
"x-cli-format": 5
"x-cli-format": 5,
"x-oapi-codegen-extra-tags": {
"tfsdk": "action"
}
},
"description": {
"maxLength": 250,
@@ -8855,7 +8870,10 @@
"type": "string",
"description": "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
"nullable": true,
"x-cli-format": 6
"x-cli-format": 6,
"x-oapi-codegen-extra-tags": {
"tfsdk": "description"
}
}
}
},
+6 -6
View File
@@ -593,22 +593,22 @@ type AdvancedFeature string
// AdvancedFirewallRule defines model for AdvancedFirewallRule.
type AdvancedFirewallRule struct {
// Action The action to take when there is a match on this rule.
Action AdvancedFirewallRuleAction `json:"action"`
Action AdvancedFirewallRuleAction `json:"action" tfsdk:"action"`
// Description A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".
Description *string `json:"description"`
Description *string `json:"description" tfsdk:"description"`
// DestinationAddresses The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
DestinationAddresses []string `json:"destination_addresses"`
DestinationAddresses []string `json:"destination_addresses" tfsdk:"destination_addresses"`
// DestinationPorts The destination ports to match for this rule. Leave null or empty to match on all ports.
DestinationPorts *[]string `json:"destination_ports"`
DestinationPorts *[]string `json:"destination_ports" tfsdk:"destination_ports"`
// Protocol The protocol to match for this rule.
Protocol AdvancedFirewallRuleProtocol `json:"protocol"`
Protocol AdvancedFirewallRuleProtocol `json:"protocol" tfsdk:"protocol"`
// SourceAddresses The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
SourceAddresses []string `json:"source_addresses"`
SourceAddresses []string `json:"source_addresses" tfsdk:"source_addresses"`
}
// AdvancedFirewallRuleAction
+3
View File
@@ -143,14 +143,17 @@ func (p *binarylaneProvider) Metadata(_ context.Context, _ provider.MetadataRequ
func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewServerDataSource,
NewServerFirewallRulesDataSource,
NewSshKeyDataSource,
NewVpcDataSource,
NewVpcRouteEntriesDataSource,
}
}
func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewServerResource,
NewServerFirewallRulesResource,
NewSshKeyResource,
NewVpcResource,
NewVpcRouteEntriesResource,
@@ -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-log/tflog"
)
var (
_ datasource.DataSource = &serverFirewallRulesDataSource{}
_ datasource.DataSourceWithConfigure = &serverFirewallRulesDataSource{}
)
func NewServerFirewallRulesDataSource() datasource.DataSource {
return &serverFirewallRulesDataSource{}
}
type serverFirewallRulesDataSource struct {
bc *BinarylaneClient
}
type serverFirewallRulesDataSourceModel struct {
resources.ServerFirewallRulesModel
}
func (d *serverFirewallRulesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server_firewall_rules"
}
func (d *serverFirewallRulesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(resources.ServerFirewallRulesResourceSchema(ctx))
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
}
func (d *serverFirewallRulesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data serverFirewallRulesDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()))
fwResp, err := d.bc.client.GetServersServerIdAdvancedFirewallRulesWithResponse(ctx, data.ServerId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()),
err.Error(),
)
return
}
if fwResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server firewall rules",
fmt.Sprintf("Received %s reading server firewall rules: server_id=%d. Details: %s", fwResp.Status(), data.ServerId.ValueInt64(), fwResp.Body))
return
}
firewallRulesValue, diags := GetFirewallRulesState(ctx, &fwResp.JSON200.FirewallRules)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.FirewallRules = firewallRulesValue
// Save into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (d *serverFirewallRulesDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
@@ -0,0 +1,292 @@
package provider
import (
"context"
"fmt"
"net/http"
"strconv"
"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/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &serverFirewallRulesResource{}
_ resource.ResourceWithConfigure = &serverFirewallRulesResource{}
_ resource.ResourceWithImportState = &serverFirewallRulesResource{}
)
func NewServerFirewallRulesResource() resource.Resource {
return &serverFirewallRulesResource{}
}
type serverFirewallRulesResource struct {
bc *BinarylaneClient
}
type serverFirewallRulesResourceModel struct {
resources.ServerFirewallRulesModel
}
func (d *serverFirewallRulesResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (r *serverFirewallRulesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server_firewall_rules"
}
func (r *serverFirewallRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.ServerFirewallRulesResourceSchema(ctx)
resp.Schema.Description = "TODO"
}
func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
firewallRules := []binarylane.AdvancedFirewallRule{}
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if len(firewallRules) == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Creating server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: firewallRules,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating server firewall rules",
fmt.Sprintf("Received %s creating server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()))
fwResp, err := r.bc.client.GetServersServerIdAdvancedFirewallRulesWithResponse(ctx, data.ServerId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()),
err.Error(),
)
return
}
if fwResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server firewall rules",
fmt.Sprintf("Received %s reading server firewall rules: server_id=%d. Details: %s", fwResp.Status(), data.ServerId.ValueInt64(), fwResp.Body))
return
}
firewallRulesValue, diags := GetFirewallRulesState(ctx, &fwResp.JSON200.FirewallRules)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.FirewallRules = firewallRulesValue
// Save into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
firewallRules := []binarylane.AdvancedFirewallRule{}
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if len(firewallRules) == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: firewallRules,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating server firewall rules",
fmt.Sprintf("Received %s updating server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
tflog.Debug(ctx, fmt.Sprintf("Deleting server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: []binarylane.AdvancedFirewallRule{},
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting server firewall rules",
fmt.Sprintf("Received %s deleting server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
}
func (r *serverFirewallRulesResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC route entries",
"Could not import VPC route entries, unexpected error (ID should be an integer): "+err.Error(),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("server_id"), id)
resp.Diagnostics.Append(diags...)
}
func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.AdvancedFirewallRule) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics
firewallRulesValue := resources.FirewallRulesValue{}
var firewallRulesValues []resources.FirewallRulesValue
if *firewallRules == nil || len(*firewallRules) == 0 {
firewallRulesValues = []resources.FirewallRulesValue{}
} else {
for _, rule := range *firewallRules {
destinationAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.DestinationAddresses)
diags.Append(diag...)
var destinationPorts basetypes.ListValue
if rule.DestinationPorts != nil {
destinationPorts, diag = types.ListValueFrom(ctx, types.StringType, rule.DestinationPorts)
diags.Append(diag...)
}
sourceAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.SourceAddresses)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(firewallRulesValue.Type(ctx)), diags
}
ruleValue := resources.NewFirewallRulesValueMust(firewallRulesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringPointerValue(rule.Description),
"action": types.StringValue(string(rule.Action)),
"protocol": types.StringValue(string(rule.Protocol)),
"destination_addresses": destinationAddresses,
"destination_ports": destinationPorts,
"source_addresses": sourceAddresses,
})
firewallRulesValues = append(firewallRulesValues, ruleValue)
}
}
firewallRulesListValue, diag := types.ListValueFrom(ctx, firewallRulesValue.Type(ctx), firewallRulesValues)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(firewallRulesValue.Type(ctx)), diags
}
return firewallRulesListValue, diags
}
@@ -0,0 +1,80 @@
package provider
import (
"crypto/rand"
"encoding/base64"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestServerFirewallRulesResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
rand.Read(pw_bytes)
password := base64.URLEncoding.EncodeToString(pw_bytes)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_server" "test" {
name = "tf-test-server-fw-rules"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules
}
resource "binarylane_server_firewall_rules" "test" {
server_id = binarylane_server.test.id
firewall_rules = [
{
description = "Allow SSH"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.test.private_ipv4_addresses.0]
destination_ports = ["22"]
action = "accept"
}
]
}
data "binarylane_server_firewall_rules" "test" {
server_id = binarylane_server_firewall_rules.test.server_id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "server_id"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow SSH"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "22"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
// Verify data source values
resource.TestCheckResourceAttrSet("data.binarylane_server_firewall_rules.test", "server_id"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow SSH"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "22"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
),
},
},
})
}
+24 -13
View File
@@ -52,7 +52,7 @@ func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequ
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
@@ -100,12 +100,21 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
},
}
vpc_id := resp.Schema.Attributes["vpc_id"]
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpc_id.GetDescription(),
MarkdownDescription: vpc_id.GetMarkdownDescription(),
Computed: false,
Optional: true,
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Optional: vpcId.IsOptional(),
Computed: false, // VPC ID is not computed, defined at creation
}
portBlocking := resp.Schema.Attributes["port_blocking"]
resp.Schema.Attributes["port_blocking"] = &schema.BoolAttribute{
Description: portBlocking.GetDescription(),
MarkdownDescription: portBlocking.GetMarkdownDescription(),
Optional: portBlocking.IsOptional(),
Computed: portBlocking.IsComputed(),
Default: booldefault.StaticBool(true), // Add default to port_blocking
}
// Additional attributes
@@ -136,18 +145,20 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
resp.Schema.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
Optional: true,
Computed: true,
ElementType: types.StringType,
Optional: false,
Required: false,
Computed: true, // "read only" with computed=true and required/optional=false
}
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,
Optional: false,
Required: false,
Computed: true, // "read only" with computed=true and required/optional=false
}
}
@@ -284,12 +295,12 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server: name=%s", data.Id.String()))
tflog.Debug(ctx, fmt.Sprintf("Reading server: id=%s, name=%s", data.Id.String(), data.Name.ValueString()))
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server: name=%s", data.Id.String()),
fmt.Sprintf("Error reading server: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
@@ -298,7 +309,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server",
fmt.Sprintf("Received %s reading server: name=%s. Details: %s", serverResp.Status(), data.Id.String(), serverResp.Body))
fmt.Sprintf("Received %s reading server: id=%s, name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), data.Id.String(), serverResp.Body))
return
}
+5 -4
View File
@@ -21,12 +21,12 @@ func TestServerResource(t *testing.T) {
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-server-resource-test"
name = "tf-test-server-resource"
ip_range = "10.240.0.0/16"
}
resource "binarylane_server" "test" {
name = "tf-server-resource-test"
name = "tf-test-server-resource"
region = "per"
image = "debian-12"
size = "std-min"
@@ -48,7 +48,7 @@ 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", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
@@ -60,10 +60,11 @@ 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"),
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
// 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", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
+1 -1
View File
@@ -38,7 +38,7 @@ func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequ
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
-7
View File
@@ -88,13 +88,6 @@ func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
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)...)
}
+39 -14
View File
@@ -4,18 +4,21 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
// _ resource.ResourceWithImportState = &exampleResource{}
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
_ resource.ResourceWithImportState = &vpcResource{}
)
func NewVpcResource() resource.Resource {
@@ -121,6 +124,7 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
@@ -129,23 +133,31 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
}
func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data vpcResourceModel
var plan, state vpcResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append()
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", data.Id.ValueInt64()))
patchRequest := binarylane.PatchVpcRequest{}
vpcResp, err := r.bc.client.PutVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64(), binarylane.UpdateVpcRequest{
Name: data.Name.ValueString(),
})
if !plan.Name.Equal(state.Name) {
patchRequest.Name = plan.Name.ValueStringPointer()
}
if reflect.ValueOf(plan).NumField() == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", state.Id.ValueInt64()))
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, state.Id.ValueInt64(), patchRequest)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating VPC: id=%d", data.Id.ValueInt64()),
fmt.Sprintf("Error updating VPC: id=%d", state.Id.ValueInt64()),
err.Error(),
)
return
@@ -154,15 +166,15 @@ func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, re
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))
fmt.Sprintf("Received %s updating VPC: id=%d. Details: %s", vpcResp.Status(), state.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
@@ -192,3 +204,16 @@ func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, re
return
}
}
func (r *vpcResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC",
fmt.Sprintf("Could not import VPC, unable to parse ID (should be an integer): %s ", err.Error()),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), id)
resp.Diagnostics.Append(diags...)
}
+62 -4
View File
@@ -4,6 +4,7 @@ import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestVpcResource(t *testing.T) {
@@ -34,25 +35,82 @@ resource "binarylane_vpc_route_entries" "test" {
}
]
}
data "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc_route_entries.test.vpc_id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
// Verify binarylane_vpc resource values
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify data source values
// Verify binarylane_vpc data source values
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("data.binarylane_vpc.test", "id"),
// Verify resource values
// Verify binarylane_vpc_route_entries resource values
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
// Verify binarylane_vpc_route_entries data source values
resource.TestCheckResourceAttrSet("data.binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
),
},
// ImportState testing
{
ResourceName: "binarylane_vpc.test",
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "binarylane_vpc_route_entries.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "vpc_id",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
resourceState := s.RootModule().Resources["binarylane_vpc_route_entries.test"]
return resourceState.Primary.Attributes["vpc_id"], nil
},
},
// Update and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-vpc-renamed"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc.test.id
route_entries = [
{
description = "test-renamed"
destination = "0.0.0.0/0"
router = "10.240.0.2"
}
]
}`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify binarylane_vpc resource values updated
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc-renamed"), // Updated name
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify binarylane_vpc_route_entries resource values updated
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.2"), // Updated router
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test-renamed"), // Updated description
),
},
// Delete testing automatically occurs in TestCase
},
})
}
@@ -0,0 +1,109 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
)
var (
_ datasource.DataSource = &vpcRouteEntriesDataSource{}
_ datasource.DataSourceWithConfigure = &vpcRouteEntriesDataSource{}
)
func NewVpcRouteEntriesDataSource() datasource.DataSource {
return &vpcRouteEntriesDataSource{}
}
type vpcRouteEntriesDataSource struct {
bc *BinarylaneClient
}
type vpcRouteEntriesDataSourceModel struct {
resources.VpcRouteEntriesModel
}
func (d *vpcRouteEntriesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc_route_entries"
}
func (d *vpcRouteEntriesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(resources.VpcRouteEntriesResourceSchema(ctx))
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
}
}
func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data vpcRouteEntriesDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := d.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntries
// Example data value setting
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (d *vpcRouteEntriesDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
+46 -23
View File
@@ -4,21 +4,25 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
// _ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
_ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
)
func NewVpcRouteEntriesResource() resource.Resource {
@@ -47,6 +51,9 @@ func (r *vpcRouteEntriesResource) Schema(ctx context.Context, req resource.Schem
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
}
}
@@ -61,7 +68,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
}
// Create API call logic
var routeEntries []binarylane.RouteEntryRequest
routeEntries := []binarylane.RouteEntryRequest{}
diags := data.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -86,7 +93,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -111,19 +118,19 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
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()),
fmt.Sprintf("Error reading VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
"Unexpected HTTP status code reading VPC route entries",
fmt.Sprintf("Received %s reading VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -135,28 +142,29 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
}
func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data vpcRouteEntriesResourceModel
var plan, state vpcRouteEntriesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
var routeEntries []binarylane.RouteEntryRequest
diags := data.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
routeEntries := []binarylane.RouteEntryRequest{}
diags := plan.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, state.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()),
fmt.Sprintf("Error updating VPC route entries: vpc_id=%d", state.VpcId.ValueInt64()),
err.Error(),
)
return
@@ -164,20 +172,20 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
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))
"Unexpected HTTP status code updating VPC route entries",
fmt.Sprintf("Received %s updating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), state.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntriesState
state.RouteEntries = routeEntriesState
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *vpcRouteEntriesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
@@ -218,7 +226,7 @@ func (d *vpcRouteEntriesResource) Configure(_ context.Context, req resource.Conf
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
@@ -226,14 +234,14 @@ func (d *vpcRouteEntriesResource) Configure(_ context.Context, req resource.Conf
d.bc = &bc
}
func getRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteEntry) (basetypes.ListValue, diag.Diagnostics) {
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{}
routeEntriesValues = []resources.RouteEntriesValue{}
} else {
for _, route := range *routeEntries {
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
@@ -253,3 +261,18 @@ func getRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteE
return routeEntriesListValue, diags
}
func (r *vpcRouteEntriesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC route entries",
"Could not import VPC route entries, unexpected error (ID should be an integer): "+err.Error(),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("vpc_id"), id)
resp.Diagnostics.Append(diags...)
}
@@ -0,0 +1,789 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func ServerFirewallRulesResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"firewall_rules": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"action": schema.StringAttribute{
Required: true,
Description: "The action to take when there is a match on this rule.",
MarkdownDescription: "The action to take when there is a match on this rule.",
Validators: []validator.String{
stringvalidator.OneOf(
"drop",
"accept",
),
},
},
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
MarkdownDescription: "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
Validators: []validator.String{
stringvalidator.LengthBetween(0, 250),
},
},
"destination_addresses": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Description: "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
MarkdownDescription: "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
},
"destination_ports": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
Computed: true,
Description: "The destination ports to match for this rule. Leave null or empty to match on all ports.",
MarkdownDescription: "The destination ports to match for this rule. Leave null or empty to match on all ports.",
},
"protocol": schema.StringAttribute{
Required: true,
Description: "The protocol to match for this rule.",
MarkdownDescription: "The protocol to match for this rule.",
Validators: []validator.String{
stringvalidator.OneOf(
"all",
"icmp",
"tcp",
"udp",
),
},
},
"source_addresses": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Description: "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
MarkdownDescription: "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
},
},
CustomType: FirewallRulesType{
ObjectType: types.ObjectType{
AttrTypes: FirewallRulesValue{}.AttributeTypes(ctx),
},
},
},
Required: true,
Description: "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules.",
MarkdownDescription: "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules.",
},
"server_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The ID of the server for which firewall rules should be listed.",
MarkdownDescription: "The ID of the server for which firewall rules should be listed.",
},
},
}
}
type ServerFirewallRulesModel struct {
FirewallRules types.List `tfsdk:"firewall_rules"`
ServerId types.Int64 `tfsdk:"server_id"`
}
var _ basetypes.ObjectTypable = FirewallRulesType{}
type FirewallRulesType struct {
basetypes.ObjectType
}
func (t FirewallRulesType) Equal(o attr.Type) bool {
other, ok := o.(FirewallRulesType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t FirewallRulesType) String() string {
return "FirewallRulesType"
}
func (t FirewallRulesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
actionAttribute, ok := attributes["action"]
if !ok {
diags.AddError(
"Attribute Missing",
`action is missing from object`)
return nil, diags
}
actionVal, ok := actionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`action expected to be basetypes.StringValue, was: %T`, actionAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return nil, diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAddressesAttribute, ok := attributes["destination_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_addresses is missing from object`)
return nil, diags
}
destinationAddressesVal, ok := destinationAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_addresses expected to be basetypes.ListValue, was: %T`, destinationAddressesAttribute))
}
destinationPortsAttribute, ok := attributes["destination_ports"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_ports is missing from object`)
return nil, diags
}
destinationPortsVal, ok := destinationPortsAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_ports expected to be basetypes.ListValue, was: %T`, destinationPortsAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return nil, diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
sourceAddressesAttribute, ok := attributes["source_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`source_addresses is missing from object`)
return nil, diags
}
sourceAddressesVal, ok := sourceAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`source_addresses expected to be basetypes.ListValue, was: %T`, sourceAddressesAttribute))
}
if diags.HasError() {
return nil, diags
}
return FirewallRulesValue{
Action: actionVal,
Description: descriptionVal,
DestinationAddresses: destinationAddressesVal,
DestinationPorts: destinationPortsVal,
Protocol: protocolVal,
SourceAddresses: sourceAddressesVal,
state: attr.ValueStateKnown,
}, diags
}
func NewFirewallRulesValueNull() FirewallRulesValue {
return FirewallRulesValue{
state: attr.ValueStateNull,
}
}
func NewFirewallRulesValueUnknown() FirewallRulesValue {
return FirewallRulesValue{
state: attr.ValueStateUnknown,
}
}
func NewFirewallRulesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FirewallRulesValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing FirewallRulesValue Attribute Value",
"While creating a FirewallRulesValue value, a missing attribute value was detected. "+
"A FirewallRulesValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid FirewallRulesValue Attribute Type",
"While creating a FirewallRulesValue value, an invalid attribute value was detected. "+
"A FirewallRulesValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra FirewallRulesValue Attribute Value",
"While creating a FirewallRulesValue value, an extra attribute value was detected. "+
"A FirewallRulesValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra FirewallRulesValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewFirewallRulesValueUnknown(), diags
}
actionAttribute, ok := attributes["action"]
if !ok {
diags.AddError(
"Attribute Missing",
`action is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
actionVal, ok := actionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`action expected to be basetypes.StringValue, was: %T`, actionAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAddressesAttribute, ok := attributes["destination_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_addresses is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
destinationAddressesVal, ok := destinationAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_addresses expected to be basetypes.ListValue, was: %T`, destinationAddressesAttribute))
}
destinationPortsAttribute, ok := attributes["destination_ports"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_ports is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
destinationPortsVal, ok := destinationPortsAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_ports expected to be basetypes.ListValue, was: %T`, destinationPortsAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
sourceAddressesAttribute, ok := attributes["source_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`source_addresses is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
sourceAddressesVal, ok := sourceAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`source_addresses expected to be basetypes.ListValue, was: %T`, sourceAddressesAttribute))
}
if diags.HasError() {
return NewFirewallRulesValueUnknown(), diags
}
return FirewallRulesValue{
Action: actionVal,
Description: descriptionVal,
DestinationAddresses: destinationAddressesVal,
DestinationPorts: destinationPortsVal,
Protocol: protocolVal,
SourceAddresses: sourceAddressesVal,
state: attr.ValueStateKnown,
}, diags
}
func NewFirewallRulesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FirewallRulesValue {
object, diags := NewFirewallRulesValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewFirewallRulesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t FirewallRulesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewFirewallRulesValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewFirewallRulesValueUnknown(), nil
}
if in.IsNull() {
return NewFirewallRulesValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewFirewallRulesValueMust(FirewallRulesValue{}.AttributeTypes(ctx), attributes), nil
}
func (t FirewallRulesType) ValueType(ctx context.Context) attr.Value {
return FirewallRulesValue{}
}
var _ basetypes.ObjectValuable = FirewallRulesValue{}
type FirewallRulesValue struct {
Action basetypes.StringValue `tfsdk:"action"`
Description basetypes.StringValue `tfsdk:"description"`
DestinationAddresses basetypes.ListValue `tfsdk:"destination_addresses"`
DestinationPorts basetypes.ListValue `tfsdk:"destination_ports"`
Protocol basetypes.StringValue `tfsdk:"protocol"`
SourceAddresses basetypes.ListValue `tfsdk:"source_addresses"`
state attr.ValueState
}
func (v FirewallRulesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 6)
var val tftypes.Value
var err error
attrTypes["action"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["destination_addresses"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["destination_ports"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["protocol"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["source_addresses"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 6)
val, err = v.Action.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["action"] = val
val, err = v.Description.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["description"] = val
val, err = v.DestinationAddresses.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination_addresses"] = val
val, err = v.DestinationPorts.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination_ports"] = val
val, err = v.Protocol.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["protocol"] = val
val, err = v.SourceAddresses.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["source_addresses"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v FirewallRulesValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v FirewallRulesValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v FirewallRulesValue) String() string {
return "FirewallRulesValue"
}
func (v FirewallRulesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
destinationAddressesVal, d := types.ListValue(types.StringType, v.DestinationAddresses.Elements())
diags.Append(d...)
if d.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
destinationPortsVal, d := types.ListValue(types.StringType, v.DestinationPorts.Elements())
diags.Append(d...)
if d.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
sourceAddressesVal, d := types.ListValue(types.StringType, v.SourceAddresses.Elements())
diags.Append(d...)
if d.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
attributeTypes := map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"action": v.Action,
"description": v.Description,
"destination_addresses": destinationAddressesVal,
"destination_ports": destinationPortsVal,
"protocol": v.Protocol,
"source_addresses": sourceAddressesVal,
})
return objVal, diags
}
func (v FirewallRulesValue) Equal(o attr.Value) bool {
other, ok := o.(FirewallRulesValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Action.Equal(other.Action) {
return false
}
if !v.Description.Equal(other.Description) {
return false
}
if !v.DestinationAddresses.Equal(other.DestinationAddresses) {
return false
}
if !v.DestinationPorts.Equal(other.DestinationPorts) {
return false
}
if !v.Protocol.Equal(other.Protocol) {
return false
}
if !v.SourceAddresses.Equal(other.SourceAddresses) {
return false
}
return true
}
func (v FirewallRulesValue) Type(ctx context.Context) attr.Type {
return FirewallRulesType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v FirewallRulesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}
}
+16 -9
View File
@@ -43,6 +43,12 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n",
MarkdownDescription: "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n",
},
"port_blocking": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.",
MarkdownDescription: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.",
},
"region": schema.StringAttribute{
Required: true,
Description: "The slug of the selected region.",
@@ -79,13 +85,14 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
}
type ServerModel struct {
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
PortBlocking types.Bool `tfsdk:"port_blocking"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
}
+143
View File
@@ -35,6 +35,13 @@
"description": "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n"
}
},
{
"name": "port_blocking",
"bool": {
"computed_optional_required": "computed_optional",
"description": "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts."
}
},
{
"name": "region",
"string": {
@@ -109,6 +116,142 @@
]
}
},
{
"name": "server_firewall_rules",
"schema": {
"attributes": [
{
"name": "firewall_rules",
"list_nested": {
"computed_optional_required": "required",
"nested_object": {
"attributes": [
{
"name": "action",
"string": {
"computed_optional_required": "required",
"description": "The action to take when there is a match on this rule.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"drop\",\n\"accept\",\n)"
}
}
]
}
},
{
"name": "description",
"string": {
"computed_optional_required": "computed_optional",
"description": "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthBetween(0, 250)"
}
}
]
}
},
{
"name": "destination_addresses",
"list": {
"computed_optional_required": "required",
"element_type": {
"string": {}
},
"description": "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
}
],
"schema_definition": "listvalidator.SizeAtLeast(1)"
}
}
]
}
},
{
"name": "destination_ports",
"list": {
"computed_optional_required": "computed_optional",
"element_type": {
"string": {}
},
"description": "The destination ports to match for this rule. Leave null or empty to match on all ports."
}
},
{
"name": "protocol",
"string": {
"computed_optional_required": "required",
"description": "The protocol to match for this rule.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"all\",\n\"icmp\",\n\"tcp\",\n\"udp\",\n)"
}
}
]
}
},
{
"name": "source_addresses",
"list": {
"computed_optional_required": "required",
"element_type": {
"string": {}
},
"description": "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
}
],
"schema_definition": "listvalidator.SizeAtLeast(1)"
}
}
]
}
}
]
},
"description": "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules."
}
},
{
"name": "server_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The ID of the server for which firewall rules should be listed."
}
}
]
}
},
{
"name": "ssh_key",
"schema": {
+17 -1
View File
@@ -57,8 +57,24 @@ resources:
- links
- server
- ssh_keys
- port_blocking
- options
server_firewall_rules:
create:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
read:
path: /servers/{server_id}/advanced_firewall_rules
method: GET
update:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
delete:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
schema:
ignores:
- type
- action
ssh_key:
create:
path: /account/keys