mirror of
https://github.com/oscarhermoso/terraform-provider-binarylane
synced 2026-10-18 13:38:08 +11:00
🚛 load balancer resource (#17)
* 🚛 load balancer resource
* Fix whitespace in server resource
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
name: Terraform Provider Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/test.yaml'
|
||||
- '**.go'
|
||||
|
||||
permissions:
|
||||
# Permission for checking out code
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
acceptance:
|
||||
name: Acceptance Tests
|
||||
runs-on: ubuntu-latest
|
||||
environment: test
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: '1.5.*'
|
||||
terraform_wrapper: false
|
||||
- run: go test -v -cover ./...
|
||||
env:
|
||||
TF_ACC: '1'
|
||||
BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }}
|
||||
# unit:
|
||||
# name: Unit Tests
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: actions/setup-go@v4
|
||||
# with:
|
||||
# go-version-file: 'go.mod'
|
||||
# - run: go test -v -cover ./...
|
||||
+16
-3
@@ -131,7 +131,7 @@ Add a `Configure` method to the resource:
|
||||
+ }
|
||||
```
|
||||
|
||||
Add the new data source/resource to `provider.go`:
|
||||
Add the new resource to `provider.go`:
|
||||
|
||||
```diff
|
||||
return []func() resource.Resource{
|
||||
@@ -204,12 +204,25 @@ Use the generated schema to define the data source schema.
|
||||
- },
|
||||
- },
|
||||
- }
|
||||
+ ds, err := convertResourceSchemaToDataSourceSchema(resources.ExampleResourceSchema(ctx))
|
||||
+ ds, err := convertResourceSchemaToDataSourceSchema(
|
||||
+ resources.ExampleResourceSchema(ctx)
|
||||
+ AttributeConfig{
|
||||
+ RequiredAttributes: &[]string{"id"},
|
||||
+ },
|
||||
+ )
|
||||
+ if err != nil {
|
||||
+ resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
|
||||
+ return
|
||||
+ }
|
||||
+ resp.Schema = *ds
|
||||
+ resp.Schema.Description = "TODO"
|
||||
+ // resp.Schema.Description = "TODO"
|
||||
}
|
||||
```
|
||||
|
||||
Add the data source to `provider.go`:
|
||||
|
||||
```diff
|
||||
return []func() datasource.DataSource{
|
||||
# ...
|
||||
+ NewExampleDataSource,
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ Planned features:
|
||||
- [ ] Alerts
|
||||
- [x] SSH Keys
|
||||
- [x] Virtual Private Cloud
|
||||
- [x] Load Balancers
|
||||
- [ ] Images
|
||||
- [ ] Load Balancers
|
||||
- [ ] DNS
|
||||
- [x] Docs
|
||||
- [x] Generated docs
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_load_balancer Data Source - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# binarylane_load_balancer (Data Source)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `id` (Number) The ID of the load balancer to fetch.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `forwarding_rules` (List of Object) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
|
||||
- `health_check` (Object) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
|
||||
- `name` (String) The hostname of the load balancer.
|
||||
- `region` (String) Leave null to create an anycast load balancer.
|
||||
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
|
||||
|
||||
<a id="nestedatt--forwarding_rules"></a>
|
||||
### Nested Schema for `forwarding_rules`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `entry_protocol` (String)
|
||||
|
||||
|
||||
<a id="nestedatt--health_check"></a>
|
||||
### Nested Schema for `health_check`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `path` (String)
|
||||
- `protocol` (String)
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "binarylane_load_balancer Resource - terraform-provider-binarylane"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# binarylane_load_balancer (Resource)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `name` (String) The hostname of the load balancer.
|
||||
|
||||
### Optional
|
||||
|
||||
- `forwarding_rules` (Attributes List) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
|
||||
- `health_check` (Attributes) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
|
||||
- `region` (String) Leave null to create an anycast load balancer.
|
||||
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (Number) The ID of the load balancer to fetch.
|
||||
|
||||
<a id="nestedatt--forwarding_rules"></a>
|
||||
### Nested Schema for `forwarding_rules`
|
||||
|
||||
Required:
|
||||
|
||||
- `entry_protocol` (String) The protocol that traffic must match for this load balancer to forward traffic according to this rule.
|
||||
|
||||
|
||||
<a id="nestedatt--health_check"></a>
|
||||
### Nested Schema for `health_check`
|
||||
|
||||
Optional:
|
||||
|
||||
- `path` (String) Leave null to accept the default '/' path.
|
||||
- `protocol` (String) Leave null to accept the default HTTP protocol.
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
This WIP example shows how to create a Kubernetes cluster on Binary Lane.
|
||||
|
||||
Prior art:
|
||||
## Prior art
|
||||
|
||||
- [MartinHodges/create_k8s](https://github.com/MartinHodges/create_k8s) repo and the [medium article](https://medium.com/@martin.hodges/creating-a-kubernetes-cluster-from-scratch-in-1-hour-using-automation-a25e387be547) that goes with it.
|
||||
- https://github.com/inscapist/terraform-k3s-private-cloud
|
||||
- https://github.com/schnerring/schnerring.github.io/blob/07d06fb40e3d01f2483a739f516ac4d711a5742c/content/blog/use-terraform-to-deploy-an-azure-kubernetes-service-aks-cluster-traefik-2-cert-manager-and-lets-encrypt-certificates/index.md
|
||||
|
||||
## Remaining TODOs
|
||||
|
||||
- [ ] Consider rewriting module to use `microk8s` instead of `k3s`
|
||||
- [ ] Use `kustomize` instead of `helm`
|
||||
- [ ] Use the `binarylane_load_balancer` resource instead of `kubectl port-forward` (or use the load balancer in another example)
|
||||
|
||||
@@ -107,7 +107,7 @@ require (
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.66.0 // indirect
|
||||
google.golang.org/grpc v1.66.2 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -395,8 +395,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
@@ -21,7 +21,7 @@ cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del
|
||||
# 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
|
||||
# Add x-oapi-codegen-extra-tags so structs can be reflected
|
||||
|
||||
## RouteEntryRequest
|
||||
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.destination += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
@@ -35,3 +35,8 @@ cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.action += {"x-o
|
||||
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_ports += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_ports"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.source_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "source_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
|
||||
## Load Balancer
|
||||
cat <<<$(jq '.components.schemas.CreateLoadBalancerRequest.properties.forwarding_rules += {"x-oapi-codegen-extra-tags": {"tfsdk": "forwarding_rules"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
cat <<<$(jq '.components.schemas.ForwardingRule.properties.entry_protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "entry_protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
cat <<<$(jq '.components.schemas.HealthCheckProtocol |= del(.enum)' $OPENAPI_FILE) >$OPENAPI_FILE
|
||||
|
||||
@@ -10057,7 +10057,10 @@
|
||||
"$ref": "#/components/schemas/ForwardingRule"
|
||||
},
|
||||
"description": "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
|
||||
"nullable": true
|
||||
"nullable": true,
|
||||
"x-oapi-codegen-extra-tags": {
|
||||
"tfsdk": "forwarding_rules"
|
||||
}
|
||||
},
|
||||
"health_check": {
|
||||
"allOf": [
|
||||
@@ -10998,7 +11001,10 @@
|
||||
"$ref": "#/components/schemas/LoadBalancerRuleProtocol"
|
||||
}
|
||||
],
|
||||
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule."
|
||||
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
|
||||
"x-oapi-codegen-extra-tags": {
|
||||
"tfsdk": "entry_protocol"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11038,11 +11044,6 @@
|
||||
}
|
||||
},
|
||||
"HealthCheckProtocol": {
|
||||
"enum": [
|
||||
"http",
|
||||
"https",
|
||||
"both"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "\n| Value | Description |\n| ----- | ----------- |\n| http | The health check will be performed via HTTP. |\n| https | The health check will be performed via HTTPS. |\n| both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |\n\n",
|
||||
"x-enum-descriptions": [
|
||||
|
||||
@@ -230,13 +230,6 @@ const (
|
||||
EnableIpv6TypeEnableIpv6 EnableIpv6Type = "enable_ipv6"
|
||||
)
|
||||
|
||||
// Defines values for HealthCheckProtocol.
|
||||
const (
|
||||
HealthCheckProtocolBoth HealthCheckProtocol = "both"
|
||||
HealthCheckProtocolHttp HealthCheckProtocol = "http"
|
||||
HealthCheckProtocolHttps HealthCheckProtocol = "https"
|
||||
)
|
||||
|
||||
// Defines values for ImageQueryType.
|
||||
const (
|
||||
ImageQueryTypeBackup ImageQueryType = "backup"
|
||||
@@ -265,8 +258,8 @@ const (
|
||||
|
||||
// Defines values for LoadBalancerRuleProtocol.
|
||||
const (
|
||||
LoadBalancerRuleProtocolHttp LoadBalancerRuleProtocol = "http"
|
||||
LoadBalancerRuleProtocolHttps LoadBalancerRuleProtocol = "https"
|
||||
Http LoadBalancerRuleProtocol = "http"
|
||||
Https LoadBalancerRuleProtocol = "https"
|
||||
)
|
||||
|
||||
// Defines values for LoadBalancerStatus.
|
||||
@@ -1131,7 +1124,7 @@ type ConsoleResponse struct {
|
||||
// CreateLoadBalancerRequest defines model for CreateLoadBalancerRequest.
|
||||
type CreateLoadBalancerRequest struct {
|
||||
// ForwardingRules The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule.
|
||||
ForwardingRules *[]ForwardingRule `json:"forwarding_rules"`
|
||||
ForwardingRules *[]ForwardingRule `json:"forwarding_rules" tfsdk:"forwarding_rules"`
|
||||
|
||||
// HealthCheck The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.
|
||||
HealthCheck *HealthCheck `json:"health_check"`
|
||||
@@ -1538,7 +1531,7 @@ type FailoverIpsResponse struct {
|
||||
// ForwardingRule defines model for ForwardingRule.
|
||||
type ForwardingRule struct {
|
||||
// EntryProtocol The protocol that traffic must match for this load balancer to forward traffic according to this rule.
|
||||
EntryProtocol LoadBalancerRuleProtocol `json:"entry_protocol"`
|
||||
EntryProtocol LoadBalancerRuleProtocol `json:"entry_protocol" tfsdk:"entry_protocol"`
|
||||
}
|
||||
|
||||
// ForwardingRulesRequest defines model for ForwardingRulesRequest.
|
||||
@@ -1562,7 +1555,7 @@ type HealthCheck struct {
|
||||
// | http | The health check will be performed via HTTP. |
|
||||
// | https | The health check will be performed via HTTPS. |
|
||||
// | both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |
|
||||
type HealthCheckProtocol string
|
||||
type HealthCheckProtocol = string
|
||||
|
||||
// Host defines model for Host.
|
||||
type Host struct {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"terraform-provider-binarylane/internal/resources"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
)
|
||||
|
||||
var (
|
||||
_ datasource.DataSource = &loadBalancerDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &loadBalancerDataSource{}
|
||||
)
|
||||
|
||||
func NewLoadBalancerDataSource() datasource.DataSource {
|
||||
return &loadBalancerDataSource{}
|
||||
}
|
||||
|
||||
type loadBalancerDataSource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type loadBalancerDataSourceModel struct {
|
||||
resources.LoadBalancerModel
|
||||
}
|
||||
|
||||
func (d *loadBalancerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_load_balancer"
|
||||
}
|
||||
|
||||
func (d *loadBalancerDataSource) Configure(
|
||||
_ context.Context,
|
||||
req datasource.ConfigureRequest,
|
||||
resp *datasource.ConfigureResponse,
|
||||
) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
bc, ok := req.ProviderData.(BinarylaneClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Data Source Configure Type",
|
||||
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
|
||||
return
|
||||
}
|
||||
|
||||
d.bc = &bc
|
||||
}
|
||||
|
||||
func (d *loadBalancerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
ds, err := convertResourceSchemaToDataSourceSchema(
|
||||
resources.LoadBalancerResourceSchema(ctx),
|
||||
AttributeConfig{
|
||||
RequiredAttributes: &[]string{"id"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
|
||||
return
|
||||
}
|
||||
resp.Schema = *ds
|
||||
// resp.Schema.Description = "TODO"
|
||||
}
|
||||
|
||||
func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data loadBalancerDataSourceModel
|
||||
|
||||
// Read Terraform prior state data into the model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
lbResp, err := d.bc.client.GetLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code reading load balancer",
|
||||
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Save updated data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
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/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.Resource = &loadBalancerResource{}
|
||||
_ resource.ResourceWithConfigure = &loadBalancerResource{}
|
||||
_ resource.ResourceWithImportState = &loadBalancerResource{}
|
||||
)
|
||||
|
||||
func NewLoadBalancerResource() resource.Resource {
|
||||
return &loadBalancerResource{}
|
||||
}
|
||||
|
||||
type loadBalancerResource struct {
|
||||
bc *BinarylaneClient
|
||||
}
|
||||
|
||||
type loadBalancerResourceModel struct {
|
||||
resources.LoadBalancerModel
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_load_balancer"
|
||||
}
|
||||
|
||||
func (d *loadBalancerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
bc, ok := req.ProviderData.(BinarylaneClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Resource Configure Type",
|
||||
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
d.bc = &bc
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = resources.LoadBalancerResourceSchema(ctx)
|
||||
// resp.Schema.Description = "TODO"
|
||||
|
||||
// Overrides
|
||||
id := resp.Schema.Attributes["id"]
|
||||
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
|
||||
Description: id.GetDescription(),
|
||||
MarkdownDescription: id.GetMarkdownDescription(),
|
||||
// read only
|
||||
Optional: false,
|
||||
Required: false,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
|
||||
region := resp.Schema.Attributes["region"]
|
||||
resp.Schema.Attributes["region"] = &schema.StringAttribute{
|
||||
Description: region.GetDescription(),
|
||||
MarkdownDescription: region.GetMarkdownDescription(),
|
||||
Optional: true,
|
||||
Computed: false, // region is not computed, defined at creation
|
||||
PlanModifiers: []planmodifier.String{ // region is not allowed to be changed
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var data loadBalancerResourceModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Create API call logic
|
||||
forwardingRules := []binarylane.ForwardingRule{}
|
||||
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, &forwardingRules, false)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
serverIds := []int64{}
|
||||
diags = data.ServerIds.ElementsAs(ctx, &serverIds, false)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
body := binarylane.CreateLoadBalancerRequest{
|
||||
Name: data.Name.ValueString(),
|
||||
ForwardingRules: &forwardingRules,
|
||||
ServerIds: &serverIds,
|
||||
}
|
||||
|
||||
if data.HealthCheck.Path.ValueStringPointer() != nil || data.HealthCheck.Protocol.ValueStringPointer() != nil {
|
||||
body.HealthCheck = &binarylane.HealthCheck{
|
||||
Path: data.HealthCheck.Path.ValueStringPointer(),
|
||||
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
|
||||
}
|
||||
}
|
||||
|
||||
tflog.Info(ctx, fmt.Sprintf("Creating Load Balancer: name=%s", data.Name.ValueString()))
|
||||
|
||||
lbResp, err := r.bc.client.PostLoadBalancersWithResponse(ctx, body)
|
||||
if err != nil {
|
||||
tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body))
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error sending request to create load balancer: name=%s", data.Name.ValueString()),
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusOK {
|
||||
tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body))
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code creating load balancer",
|
||||
fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body))
|
||||
return
|
||||
}
|
||||
|
||||
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Save data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var data loadBalancerResourceModel
|
||||
|
||||
// Read Terraform prior state data into the model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Read API call logic
|
||||
lbResp, err := r.bc.client.GetLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code reading load balancer",
|
||||
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Save updated data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var data loadBalancerResourceModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Update API call logic
|
||||
forwardingRules := &[]binarylane.ForwardingRule{}
|
||||
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, forwardingRules, true)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
serverIds := &[]int64{}
|
||||
diags = data.ServerIds.ElementsAs(ctx, serverIds, true)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, fmt.Sprintf("Updating Load Balancer: name=%s", data.Name.ValueString()))
|
||||
|
||||
body := binarylane.UpdateLoadBalancerRequest{
|
||||
Name: data.Name.ValueString(),
|
||||
ForwardingRules: forwardingRules,
|
||||
HealthCheck: &binarylane.HealthCheck{
|
||||
Path: data.HealthCheck.Path.ValueStringPointer(),
|
||||
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
|
||||
},
|
||||
ServerIds: serverIds,
|
||||
}
|
||||
|
||||
lbResp, err := r.bc.client.PutLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64(), body)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code reading load balancer",
|
||||
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Save updated data into Terraform state
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var data loadBalancerResourceModel
|
||||
|
||||
// Read Terraform prior state data into the model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete API call logic
|
||||
tflog.Debug(ctx, fmt.Sprintf("Deleting Load Balancer: name=%s", data.Name.ValueString()))
|
||||
|
||||
lbResp, err := r.bc.client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
fmt.Sprintf("Error deleting load balancer: name=%s", data.Name.ValueString()),
|
||||
err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusNoContent {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code deleting load balancer",
|
||||
fmt.Sprintf("Received %s deleting load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *loadBalancerResource) ImportState(
|
||||
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
|
||||
) {
|
||||
// Import by ID
|
||||
id, err := strconv.ParseInt(req.ID, 10, 32)
|
||||
if err == nil {
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
|
||||
resp.Diagnostics.Append(diags...)
|
||||
return
|
||||
}
|
||||
|
||||
// Import by name
|
||||
var page int32 = 1
|
||||
perPage := int32(200)
|
||||
var loadBalancer binarylane.LoadBalancer
|
||||
var nextPage bool = true
|
||||
|
||||
for nextPage { // Need to paginate because the API does not support filtering by name
|
||||
params := binarylane.GetLoadBalancersParams{
|
||||
Page: &page,
|
||||
PerPage: &perPage,
|
||||
}
|
||||
|
||||
lbResp, err := r.bc.client.GetLoadBalancersWithResponse(ctx, ¶ms)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(fmt.Sprintf("Error getting load balancer for import: name=%s", req.ID), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if lbResp.StatusCode() != http.StatusOK {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected HTTP status code getting load balancer for import",
|
||||
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), req.ID,
|
||||
lbResp.Body))
|
||||
return
|
||||
}
|
||||
|
||||
loadBalancers := *lbResp.JSON200.LoadBalancers
|
||||
for _, lb := range loadBalancers {
|
||||
if *lb.Name == req.ID {
|
||||
loadBalancer = lb
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil {
|
||||
nextPage = false
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
if loadBalancer.Id == nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Could not find load balancer by name",
|
||||
fmt.Sprintf("Error finding load balancer: name=%s", req.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("id"), *loadBalancer.Id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
func SetLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancerModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
|
||||
var diags, diag diag.Diagnostics
|
||||
|
||||
data.Id = types.Int64Value(*lb.Id)
|
||||
data.Name = types.StringValue(*lb.Name)
|
||||
|
||||
if lb.Region == nil {
|
||||
data.Region = types.StringNull()
|
||||
} else {
|
||||
data.Region = types.StringValue(*lb.Region.Slug)
|
||||
}
|
||||
|
||||
data.ServerIds, diags = types.ListValueFrom(ctx, types.Int64Type, lb.ServerIds)
|
||||
|
||||
if lb.HealthCheck == nil {
|
||||
data.HealthCheck = resources.NewHealthCheckValueNull()
|
||||
} else {
|
||||
data.HealthCheck, diag = resources.NewHealthCheckValue(
|
||||
resources.HealthCheckValue{}.AttributeTypes(ctx),
|
||||
map[string]attr.Value{
|
||||
"path": types.StringValue(*lb.HealthCheck.Path),
|
||||
"protocol": types.StringValue(*lb.HealthCheck.Protocol),
|
||||
},
|
||||
)
|
||||
diags.Append(diag...)
|
||||
}
|
||||
|
||||
data.ForwardingRules, diag = types.ListValueFrom(ctx,
|
||||
types.ObjectType{AttrTypes: resources.ForwardingRulesValue{}.AttributeTypes(ctx)},
|
||||
lb.ForwardingRules,
|
||||
)
|
||||
diags.Append(diag...)
|
||||
|
||||
return diags
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestLoadBalancerResource(t *testing.T) {
|
||||
// Must assign a password to the server or Binary Lane will send emails
|
||||
pw_bytes := make([]byte, 12)
|
||||
_, err := rand.Read(pw_bytes)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to generate password: %s", err)
|
||||
return
|
||||
}
|
||||
password := base64.URLEncoding.EncodeToString(pw_bytes)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
// Create and Read testing
|
||||
{
|
||||
Config: providerConfig + `
|
||||
resource "binarylane_server" "test" {
|
||||
count = 2
|
||||
name = "tf-test-lb-server-${count.index}"
|
||||
region = "per"
|
||||
image = "debian-12"
|
||||
size = "std-min"
|
||||
password = "` + password + `"
|
||||
wait_for_create = 60
|
||||
}
|
||||
|
||||
resource "binarylane_load_balancer" "test" {
|
||||
name = "tf-test-lb"
|
||||
server_ids = [binarylane_server.test.0.id, binarylane_server.test.1.id]
|
||||
forwarding_rules = [{ entry_protocol = "http" }]
|
||||
}
|
||||
|
||||
data "binarylane_load_balancer" "test" {
|
||||
depends_on = [binarylane_load_balancer.test]
|
||||
|
||||
id = binarylane_load_balancer.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Verify resource values
|
||||
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "name", "tf-test-lb"),
|
||||
resource.TestCheckNoResourceAttr("binarylane_load_balancer.test", "region"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "server_ids.#", "2"),
|
||||
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
|
||||
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.#", "1"),
|
||||
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
|
||||
|
||||
// Verify data source values
|
||||
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "id", "binarylane_load_balancer.test", "id"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "name", "tf-test-lb"),
|
||||
resource.TestCheckNoResourceAttr("data.binarylane_load_balancer.test", "region"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "server_ids.#", "2"),
|
||||
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
|
||||
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.#", "1"),
|
||||
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
|
||||
),
|
||||
},
|
||||
// Test import by ID
|
||||
{
|
||||
ResourceName: "binarylane_load_balancer.test",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// Test import by name
|
||||
{
|
||||
ResourceName: "binarylane_load_balancer.test",
|
||||
ImportState: true,
|
||||
ImportStateId: "tf-test-lb",
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -147,6 +147,7 @@ func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.
|
||||
NewSshKeyDataSource,
|
||||
NewVpcDataSource,
|
||||
NewVpcRouteEntriesDataSource,
|
||||
NewLoadBalancerDataSource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,5 +158,6 @@ func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Reso
|
||||
NewSshKeyResource,
|
||||
NewVpcResource,
|
||||
NewVpcRouteEntriesResource,
|
||||
NewLoadBalancerResource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type serverModel struct {
|
||||
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
|
||||
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
|
||||
Permalink types.String `tfsdk:"permalink"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
}
|
||||
|
||||
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
@@ -92,15 +93,6 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
|
||||
},
|
||||
}
|
||||
|
||||
pw := resp.Schema.Attributes["password"]
|
||||
resp.Schema.Attributes["password"] = &schema.StringAttribute{
|
||||
Description: pw.GetDescription(),
|
||||
MarkdownDescription: pw.GetMarkdownDescription(),
|
||||
Optional: pw.IsOptional(),
|
||||
Computed: false, // Computed must be false to allow server to be created without password
|
||||
Sensitive: true, // Mark password as sensitive
|
||||
}
|
||||
|
||||
backups := resp.Schema.Attributes["backups"]
|
||||
resp.Schema.Attributes["backups"] = &schema.BoolAttribute{
|
||||
Description: backups.GetDescription(),
|
||||
@@ -152,6 +144,18 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
|
||||
}
|
||||
|
||||
// Additional attributes
|
||||
pwDescription :=
|
||||
"If this is provided the specified or default remote user's account password will be set to this value. " +
|
||||
"Only valid if the server supports password change actions. If omitted and the server supports password " +
|
||||
"change actions a random password will be generated and emailed to the account email address."
|
||||
resp.Schema.Attributes["password"] = &schema.StringAttribute{
|
||||
Description: pwDescription,
|
||||
MarkdownDescription: pwDescription,
|
||||
Optional: true, // Password optional, if not set will be emailed to user
|
||||
Computed: false, // Computed must be false to allow server to be created without password
|
||||
Sensitive: true, // Mark password as sensitive
|
||||
}
|
||||
|
||||
waitDescription := "The number of seconds to wait for the server to be created, after which, a timeout error will " +
|
||||
"be reported. If `wait_seconds` is left empty or set to 0, Terraform will succeed without waiting for the " +
|
||||
"server creation to complete."
|
||||
|
||||
@@ -91,12 +91,14 @@ echo "Hello World" > /var/tmp/output.txt
|
||||
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"),
|
||||
),
|
||||
},
|
||||
// Test import by ID
|
||||
{
|
||||
ResourceName: "binarylane_server.test",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "user_data", "wait_for_create", "public_ipv4_count"},
|
||||
},
|
||||
// Test import by name
|
||||
{
|
||||
ResourceName: "binarylane_server.test",
|
||||
ImportState: true,
|
||||
|
||||
+64
-44
@@ -3,8 +3,8 @@ package provider
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
r_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
@@ -24,110 +24,130 @@ func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema, cfg AttributeCo
|
||||
MarkdownDescription: rs.GetMarkdownDescription(),
|
||||
DeprecationMessage: rs.GetDeprecationMessage(),
|
||||
}
|
||||
for name, attr := range rs.Attributes {
|
||||
for name, attribute := range rs.Attributes {
|
||||
if cfg.ExcludedAttributes != nil && slices.Contains(*cfg.ExcludedAttributes, name) {
|
||||
continue
|
||||
}
|
||||
|
||||
required := cfg.RequiredAttributes != nil && slices.Contains(*cfg.RequiredAttributes, name)
|
||||
optional := cfg.OptionalAttributes != nil && slices.Contains(*cfg.OptionalAttributes, name)
|
||||
switch attr.GetType() {
|
||||
switch attribute.GetType() {
|
||||
case types.BoolType:
|
||||
ds.Attributes[name] = d_schema.BoolAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.DynamicType:
|
||||
ds.Attributes[name] = d_schema.DynamicAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Float32Type:
|
||||
ds.Attributes[name] = d_schema.Float32Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Float64Type:
|
||||
ds.Attributes[name] = d_schema.Float64Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Int32Type:
|
||||
ds.Attributes[name] = d_schema.Int32Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.Int64Type:
|
||||
ds.Attributes[name] = d_schema.Int64Attribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.NumberType:
|
||||
ds.Attributes[name] = d_schema.NumberAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
case types.StringType:
|
||||
ds.Attributes[name] = d_schema.StringAttribute{
|
||||
Description: attr.GetDescription(),
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
default:
|
||||
// Feel free to to raise a PR and remove this hack
|
||||
if strings.HasPrefix(attr.GetType().String(), "types.ListType") {
|
||||
if listType, isList := attribute.GetType().(types.ListType); isList {
|
||||
ds.Attributes[name] = d_schema.ListAttribute{
|
||||
ElementType: attr.GetType().(types.ListType).ElemType,
|
||||
Description: attr.GetDescription(),
|
||||
ElementType: listType.ElemType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attr.IsSensitive(),
|
||||
MarkdownDescription: attr.GetMarkdownDescription(),
|
||||
DeprecationMessage: attr.GetDeprecationMessage(),
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to convert resource schema attribute to data source schema attribute: name=%s, type=%s", name, attr.GetType())
|
||||
continue
|
||||
}
|
||||
|
||||
if objType, isObject := attribute.(r_schema.SingleNestedAttribute); isObject {
|
||||
attributeTypes := make(map[string]attr.Type, len(objType.GetAttributes()))
|
||||
for name, attribute := range objType.GetAttributes() {
|
||||
attributeTypes[name] = attribute.GetType()
|
||||
}
|
||||
|
||||
ds.Attributes[name] = d_schema.ObjectAttribute{
|
||||
CustomType: objType.CustomType,
|
||||
Description: attribute.GetDescription(),
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
Computed: !required,
|
||||
Sensitive: attribute.IsSensitive(),
|
||||
MarkdownDescription: attribute.GetMarkdownDescription(),
|
||||
DeprecationMessage: attribute.GetDeprecationMessage(),
|
||||
AttributeTypes: attributeTypes,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to convert resource schema attribute to data source schema attribute: name=%s, type=%s", name, attribute.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,819 @@
|
||||
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
)
|
||||
|
||||
func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
|
||||
return schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"forwarding_rules": schema.ListNestedAttribute{
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"entry_protocol": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
|
||||
MarkdownDescription: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf(
|
||||
"http",
|
||||
"https",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
CustomType: ForwardingRulesType{
|
||||
ObjectType: types.ObjectType{
|
||||
AttrTypes: ForwardingRulesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
|
||||
MarkdownDescription: "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
|
||||
},
|
||||
"health_check": schema.SingleNestedAttribute{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"path": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Leave null to accept the default '/' path.",
|
||||
MarkdownDescription: "Leave null to accept the default '/' path.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(regexp.MustCompile("/[A-Za-z0-9/.?=&+%_-]*"), ""),
|
||||
},
|
||||
},
|
||||
"protocol": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Leave null to accept the default HTTP protocol.",
|
||||
MarkdownDescription: "Leave null to accept the default HTTP protocol.",
|
||||
},
|
||||
},
|
||||
CustomType: HealthCheckType{
|
||||
ObjectType: types.ObjectType{
|
||||
AttrTypes: HealthCheckValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.",
|
||||
MarkdownDescription: "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.",
|
||||
},
|
||||
"id": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "The ID of the load balancer to fetch.",
|
||||
MarkdownDescription: "The ID of the load balancer to fetch.",
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The hostname of the load balancer.",
|
||||
MarkdownDescription: "The hostname of the load balancer.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthAtLeast(1),
|
||||
},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Leave null to create an anycast load balancer.",
|
||||
MarkdownDescription: "Leave null to create an anycast load balancer.",
|
||||
},
|
||||
"server_ids": schema.ListAttribute{
|
||||
ElementType: types.Int64Type,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "A list of server IDs to assign to this load balancer.",
|
||||
MarkdownDescription: "A list of server IDs to assign to this load balancer.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type LoadBalancerModel struct {
|
||||
ForwardingRules types.List `tfsdk:"forwarding_rules"`
|
||||
HealthCheck HealthCheckValue `tfsdk:"health_check"`
|
||||
Id types.Int64 `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
ServerIds types.List `tfsdk:"server_ids"`
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectTypable = ForwardingRulesType{}
|
||||
|
||||
type ForwardingRulesType struct {
|
||||
basetypes.ObjectType
|
||||
}
|
||||
|
||||
func (t ForwardingRulesType) Equal(o attr.Type) bool {
|
||||
other, ok := o.(ForwardingRulesType)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.ObjectType.Equal(other.ObjectType)
|
||||
}
|
||||
|
||||
func (t ForwardingRulesType) String() string {
|
||||
return "ForwardingRulesType"
|
||||
}
|
||||
|
||||
func (t ForwardingRulesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributes := in.Attributes()
|
||||
|
||||
entryProtocolAttribute, ok := attributes["entry_protocol"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`entry_protocol is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
entryProtocolVal, ok := entryProtocolAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`entry_protocol expected to be basetypes.StringValue, was: %T`, entryProtocolAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return ForwardingRulesValue{
|
||||
EntryProtocol: entryProtocolVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewForwardingRulesValueNull() ForwardingRulesValue {
|
||||
return ForwardingRulesValue{
|
||||
state: attr.ValueStateNull,
|
||||
}
|
||||
}
|
||||
|
||||
func NewForwardingRulesValueUnknown() ForwardingRulesValue {
|
||||
return ForwardingRulesValue{
|
||||
state: attr.ValueStateUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
func NewForwardingRulesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ForwardingRulesValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
|
||||
ctx := context.Background()
|
||||
|
||||
for name, attributeType := range attributeTypes {
|
||||
attribute, ok := attributes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Missing ForwardingRulesValue Attribute Value",
|
||||
"While creating a ForwardingRulesValue value, a missing attribute value was detected. "+
|
||||
"A ForwardingRulesValue must contain values for all attributes, even if null or unknown. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !attributeType.Equal(attribute.Type(ctx)) {
|
||||
diags.AddError(
|
||||
"Invalid ForwardingRulesValue Attribute Type",
|
||||
"While creating a ForwardingRulesValue value, an invalid attribute value was detected. "+
|
||||
"A ForwardingRulesValue must use a matching attribute type for the value. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
|
||||
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for name := range attributes {
|
||||
_, ok := attributeTypes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Extra ForwardingRulesValue Attribute Value",
|
||||
"While creating a ForwardingRulesValue value, an extra attribute value was detected. "+
|
||||
"A ForwardingRulesValue must not contain values beyond the expected attribute types. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("Extra ForwardingRulesValue Attribute Name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewForwardingRulesValueUnknown(), diags
|
||||
}
|
||||
|
||||
entryProtocolAttribute, ok := attributes["entry_protocol"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`entry_protocol is missing from object`)
|
||||
|
||||
return NewForwardingRulesValueUnknown(), diags
|
||||
}
|
||||
|
||||
entryProtocolVal, ok := entryProtocolAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`entry_protocol expected to be basetypes.StringValue, was: %T`, entryProtocolAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewForwardingRulesValueUnknown(), diags
|
||||
}
|
||||
|
||||
return ForwardingRulesValue{
|
||||
EntryProtocol: entryProtocolVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewForwardingRulesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ForwardingRulesValue {
|
||||
object, diags := NewForwardingRulesValue(attributeTypes, attributes)
|
||||
|
||||
if diags.HasError() {
|
||||
// This could potentially be added to the diag package.
|
||||
diagsStrings := make([]string, 0, len(diags))
|
||||
|
||||
for _, diagnostic := range diags {
|
||||
diagsStrings = append(diagsStrings, fmt.Sprintf(
|
||||
"%s | %s | %s",
|
||||
diagnostic.Severity(),
|
||||
diagnostic.Summary(),
|
||||
diagnostic.Detail()))
|
||||
}
|
||||
|
||||
panic("NewForwardingRulesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (t ForwardingRulesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
|
||||
if in.Type() == nil {
|
||||
return NewForwardingRulesValueNull(), nil
|
||||
}
|
||||
|
||||
if !in.Type().Equal(t.TerraformType(ctx)) {
|
||||
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
|
||||
}
|
||||
|
||||
if !in.IsKnown() {
|
||||
return NewForwardingRulesValueUnknown(), nil
|
||||
}
|
||||
|
||||
if in.IsNull() {
|
||||
return NewForwardingRulesValueNull(), nil
|
||||
}
|
||||
|
||||
attributes := map[string]attr.Value{}
|
||||
|
||||
val := map[string]tftypes.Value{}
|
||||
|
||||
err := in.As(&val)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range val {
|
||||
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attributes[k] = a
|
||||
}
|
||||
|
||||
return NewForwardingRulesValueMust(ForwardingRulesValue{}.AttributeTypes(ctx), attributes), nil
|
||||
}
|
||||
|
||||
func (t ForwardingRulesType) ValueType(ctx context.Context) attr.Value {
|
||||
return ForwardingRulesValue{}
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectValuable = ForwardingRulesValue{}
|
||||
|
||||
type ForwardingRulesValue struct {
|
||||
EntryProtocol basetypes.StringValue `tfsdk:"entry_protocol"`
|
||||
state attr.ValueState
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
|
||||
attrTypes := make(map[string]tftypes.Type, 1)
|
||||
|
||||
var val tftypes.Value
|
||||
var err error
|
||||
|
||||
attrTypes["entry_protocol"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
|
||||
objectType := tftypes.Object{AttributeTypes: attrTypes}
|
||||
|
||||
switch v.state {
|
||||
case attr.ValueStateKnown:
|
||||
vals := make(map[string]tftypes.Value, 1)
|
||||
|
||||
val, err = v.EntryProtocol.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["entry_protocol"] = val
|
||||
|
||||
if err := tftypes.ValidateValue(objectType, vals); err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
return tftypes.NewValue(objectType, vals), nil
|
||||
case attr.ValueStateNull:
|
||||
return tftypes.NewValue(objectType, nil), nil
|
||||
case attr.ValueStateUnknown:
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
|
||||
}
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) IsNull() bool {
|
||||
return v.state == attr.ValueStateNull
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) IsUnknown() bool {
|
||||
return v.state == attr.ValueStateUnknown
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) String() string {
|
||||
return "ForwardingRulesValue"
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributeTypes := map[string]attr.Type{
|
||||
"entry_protocol": basetypes.StringType{},
|
||||
}
|
||||
|
||||
if v.IsNull() {
|
||||
return types.ObjectNull(attributeTypes), diags
|
||||
}
|
||||
|
||||
if v.IsUnknown() {
|
||||
return types.ObjectUnknown(attributeTypes), diags
|
||||
}
|
||||
|
||||
objVal, diags := types.ObjectValue(
|
||||
attributeTypes,
|
||||
map[string]attr.Value{
|
||||
"entry_protocol": v.EntryProtocol,
|
||||
})
|
||||
|
||||
return objVal, diags
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) Equal(o attr.Value) bool {
|
||||
other, ok := o.(ForwardingRulesValue)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != other.state {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != attr.ValueStateKnown {
|
||||
return true
|
||||
}
|
||||
|
||||
if !v.EntryProtocol.Equal(other.EntryProtocol) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) Type(ctx context.Context) attr.Type {
|
||||
return ForwardingRulesType{
|
||||
basetypes.ObjectType{
|
||||
AttrTypes: v.AttributeTypes(ctx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v ForwardingRulesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
|
||||
return map[string]attr.Type{
|
||||
"entry_protocol": basetypes.StringType{},
|
||||
}
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectTypable = HealthCheckType{}
|
||||
|
||||
type HealthCheckType struct {
|
||||
basetypes.ObjectType
|
||||
}
|
||||
|
||||
func (t HealthCheckType) Equal(o attr.Type) bool {
|
||||
other, ok := o.(HealthCheckType)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.ObjectType.Equal(other.ObjectType)
|
||||
}
|
||||
|
||||
func (t HealthCheckType) String() string {
|
||||
return "HealthCheckType"
|
||||
}
|
||||
|
||||
func (t HealthCheckType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributes := in.Attributes()
|
||||
|
||||
pathAttribute, ok := attributes["path"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`path is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
pathVal, ok := pathAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`path expected to be basetypes.StringValue, was: %T`, pathAttribute))
|
||||
}
|
||||
|
||||
protocolAttribute, ok := attributes["protocol"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`protocol is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return HealthCheckValue{
|
||||
Path: pathVal,
|
||||
Protocol: protocolVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewHealthCheckValueNull() HealthCheckValue {
|
||||
return HealthCheckValue{
|
||||
state: attr.ValueStateNull,
|
||||
}
|
||||
}
|
||||
|
||||
func NewHealthCheckValueUnknown() HealthCheckValue {
|
||||
return HealthCheckValue{
|
||||
state: attr.ValueStateUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
func NewHealthCheckValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (HealthCheckValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
|
||||
ctx := context.Background()
|
||||
|
||||
for name, attributeType := range attributeTypes {
|
||||
attribute, ok := attributes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Missing HealthCheckValue Attribute Value",
|
||||
"While creating a HealthCheckValue value, a missing attribute value was detected. "+
|
||||
"A HealthCheckValue must contain values for all attributes, even if null or unknown. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !attributeType.Equal(attribute.Type(ctx)) {
|
||||
diags.AddError(
|
||||
"Invalid HealthCheckValue Attribute Type",
|
||||
"While creating a HealthCheckValue value, an invalid attribute value was detected. "+
|
||||
"A HealthCheckValue must use a matching attribute type for the value. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
|
||||
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for name := range attributes {
|
||||
_, ok := attributeTypes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Extra HealthCheckValue Attribute Value",
|
||||
"While creating a HealthCheckValue value, an extra attribute value was detected. "+
|
||||
"A HealthCheckValue must not contain values beyond the expected attribute types. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("Extra HealthCheckValue Attribute Name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewHealthCheckValueUnknown(), diags
|
||||
}
|
||||
|
||||
pathAttribute, ok := attributes["path"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`path is missing from object`)
|
||||
|
||||
return NewHealthCheckValueUnknown(), diags
|
||||
}
|
||||
|
||||
pathVal, ok := pathAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`path expected to be basetypes.StringValue, was: %T`, pathAttribute))
|
||||
}
|
||||
|
||||
protocolAttribute, ok := attributes["protocol"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`protocol is missing from object`)
|
||||
|
||||
return NewHealthCheckValueUnknown(), diags
|
||||
}
|
||||
|
||||
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewHealthCheckValueUnknown(), diags
|
||||
}
|
||||
|
||||
return HealthCheckValue{
|
||||
Path: pathVal,
|
||||
Protocol: protocolVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewHealthCheckValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) HealthCheckValue {
|
||||
object, diags := NewHealthCheckValue(attributeTypes, attributes)
|
||||
|
||||
if diags.HasError() {
|
||||
// This could potentially be added to the diag package.
|
||||
diagsStrings := make([]string, 0, len(diags))
|
||||
|
||||
for _, diagnostic := range diags {
|
||||
diagsStrings = append(diagsStrings, fmt.Sprintf(
|
||||
"%s | %s | %s",
|
||||
diagnostic.Severity(),
|
||||
diagnostic.Summary(),
|
||||
diagnostic.Detail()))
|
||||
}
|
||||
|
||||
panic("NewHealthCheckValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (t HealthCheckType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
|
||||
if in.Type() == nil {
|
||||
return NewHealthCheckValueNull(), nil
|
||||
}
|
||||
|
||||
if !in.Type().Equal(t.TerraformType(ctx)) {
|
||||
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
|
||||
}
|
||||
|
||||
if !in.IsKnown() {
|
||||
return NewHealthCheckValueUnknown(), nil
|
||||
}
|
||||
|
||||
if in.IsNull() {
|
||||
return NewHealthCheckValueNull(), nil
|
||||
}
|
||||
|
||||
attributes := map[string]attr.Value{}
|
||||
|
||||
val := map[string]tftypes.Value{}
|
||||
|
||||
err := in.As(&val)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range val {
|
||||
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attributes[k] = a
|
||||
}
|
||||
|
||||
return NewHealthCheckValueMust(HealthCheckValue{}.AttributeTypes(ctx), attributes), nil
|
||||
}
|
||||
|
||||
func (t HealthCheckType) ValueType(ctx context.Context) attr.Value {
|
||||
return HealthCheckValue{}
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectValuable = HealthCheckValue{}
|
||||
|
||||
type HealthCheckValue struct {
|
||||
Path basetypes.StringValue `tfsdk:"path"`
|
||||
Protocol basetypes.StringValue `tfsdk:"protocol"`
|
||||
state attr.ValueState
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
|
||||
attrTypes := make(map[string]tftypes.Type, 2)
|
||||
|
||||
var val tftypes.Value
|
||||
var err error
|
||||
|
||||
attrTypes["path"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
attrTypes["protocol"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
|
||||
objectType := tftypes.Object{AttributeTypes: attrTypes}
|
||||
|
||||
switch v.state {
|
||||
case attr.ValueStateKnown:
|
||||
vals := make(map[string]tftypes.Value, 2)
|
||||
|
||||
val, err = v.Path.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["path"] = val
|
||||
|
||||
val, err = v.Protocol.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["protocol"] = val
|
||||
|
||||
if err := tftypes.ValidateValue(objectType, vals); err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
return tftypes.NewValue(objectType, vals), nil
|
||||
case attr.ValueStateNull:
|
||||
return tftypes.NewValue(objectType, nil), nil
|
||||
case attr.ValueStateUnknown:
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
|
||||
}
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) IsNull() bool {
|
||||
return v.state == attr.ValueStateNull
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) IsUnknown() bool {
|
||||
return v.state == attr.ValueStateUnknown
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) String() string {
|
||||
return "HealthCheckValue"
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributeTypes := map[string]attr.Type{
|
||||
"path": basetypes.StringType{},
|
||||
"protocol": basetypes.StringType{},
|
||||
}
|
||||
|
||||
if v.IsNull() {
|
||||
return types.ObjectNull(attributeTypes), diags
|
||||
}
|
||||
|
||||
if v.IsUnknown() {
|
||||
return types.ObjectUnknown(attributeTypes), diags
|
||||
}
|
||||
|
||||
objVal, diags := types.ObjectValue(
|
||||
attributeTypes,
|
||||
map[string]attr.Value{
|
||||
"path": v.Path,
|
||||
"protocol": v.Protocol,
|
||||
})
|
||||
|
||||
return objVal, diags
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) Equal(o attr.Value) bool {
|
||||
other, ok := o.(HealthCheckValue)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != other.state {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != attr.ValueStateKnown {
|
||||
return true
|
||||
}
|
||||
|
||||
if !v.Path.Equal(other.Path) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Protocol.Equal(other.Protocol) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) Type(ctx context.Context) attr.Type {
|
||||
return HealthCheckType{
|
||||
basetypes.ObjectType{
|
||||
AttrTypes: v.AttributeTypes(ctx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v HealthCheckValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
|
||||
return map[string]attr.Type{
|
||||
"path": basetypes.StringType{},
|
||||
"protocol": basetypes.StringType{},
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,6 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
|
||||
Description: "The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.",
|
||||
MarkdownDescription: "The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.",
|
||||
},
|
||||
"password": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions. 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. 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,
|
||||
@@ -96,7 +90,6 @@ type ServerModel struct {
|
||||
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"`
|
||||
|
||||
+118
-7
@@ -3,6 +3,124 @@
|
||||
"name": "binarylane"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "load_balancer",
|
||||
"schema": {
|
||||
"attributes": [
|
||||
{
|
||||
"name": "forwarding_rules",
|
||||
"list_nested": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"nested_object": {
|
||||
"attributes": [
|
||||
{
|
||||
"name": "entry_protocol",
|
||||
"string": {
|
||||
"computed_optional_required": "required",
|
||||
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
|
||||
"validators": [
|
||||
{
|
||||
"custom": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
}
|
||||
],
|
||||
"schema_definition": "stringvalidator.OneOf(\n\"http\",\n\"https\",\n)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "health_check",
|
||||
"single_nested": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"attributes": [
|
||||
{
|
||||
"name": "path",
|
||||
"string": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"description": "Leave null to accept the default '/' path.",
|
||||
"validators": [
|
||||
{
|
||||
"custom": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "regexp"
|
||||
},
|
||||
{
|
||||
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
}
|
||||
],
|
||||
"schema_definition": "stringvalidator.RegexMatches(regexp.MustCompile(\"/[A-Za-z0-9/.?=\u0026+%_-]*\"), \"\")"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "protocol",
|
||||
"string": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"description": "Leave null to accept the default HTTP protocol."
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"string": {
|
||||
"computed_optional_required": "required",
|
||||
"description": "The hostname of the load balancer.",
|
||||
"validators": [
|
||||
{
|
||||
"custom": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
}
|
||||
],
|
||||
"schema_definition": "stringvalidator.LengthAtLeast(1)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "region",
|
||||
"string": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"description": "Leave null to create an anycast load balancer."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "server_ids",
|
||||
"list": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"element_type": {
|
||||
"int64": {}
|
||||
},
|
||||
"description": "A list of server IDs to assign to this load balancer."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"int64": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"description": "The ID of the load balancer to fetch."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "server",
|
||||
"schema": {
|
||||
@@ -28,13 +146,6 @@
|
||||
"description": "The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"string": {
|
||||
"computed_optional_required": "computed_optional",
|
||||
"description": "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions. 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": {
|
||||
|
||||
+21
-5
@@ -34,11 +34,6 @@ resources:
|
||||
value if provided. Setting this to false has no effect.
|
||||
image:
|
||||
description: The slug of the selected operating system.
|
||||
password:
|
||||
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. If omitted and the server supports password
|
||||
change actions a random password will be generated and emailed to the account email address.
|
||||
ssh_keys:
|
||||
description: >
|
||||
This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as
|
||||
@@ -50,6 +45,27 @@ resources:
|
||||
- links
|
||||
- server
|
||||
- options
|
||||
- password
|
||||
load_balancer:
|
||||
create:
|
||||
path: /load_balancers
|
||||
method: POST
|
||||
read:
|
||||
path: /load_balancers/{load_balancer_id}
|
||||
method: GET
|
||||
update:
|
||||
path: /load_balancers/{load_balancer_id}
|
||||
method: PUT
|
||||
delete:
|
||||
path: /load_balancers/{load_balancer_id}
|
||||
method: DELETE
|
||||
schema:
|
||||
attributes:
|
||||
aliases:
|
||||
load_balancer_id: id
|
||||
ignores:
|
||||
- links
|
||||
- load_balancer
|
||||
server_firewall_rules:
|
||||
create:
|
||||
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
|
||||
|
||||
Reference in New Issue
Block a user