Compare commits

...

2 Commits

Author SHA1 Message Date
Oscar Hermoso f73528497f ⏳ Wait for server creation with wait_for_create attribute (#2)
* Wait for cloud-init to complete

* Define `wait_for_create` attribute to make waiting for server opt-in
2024-08-19 11:46:18 +08:00
Oscar Hermoso f1b7b9853e Initialize VMs with user_data script, create cloud-init examples 2024-08-17 17:38:32 +08:00
21 changed files with 421 additions and 121 deletions
+18
View File
@@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'gomod'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'terraform'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'docker'
directory: '/'
schedule:
interval: 'weekly'
+1 -1
View File
@@ -30,4 +30,4 @@ tmp
# Normally, you wouldn't want to exclude lock files, but in this case, we're
# using Terraform's lock file to store the provider version, so we need to
# ignore it.
terraform.lock.hcl
.terraform.lock.hcl
+13 -18
View File
@@ -12,7 +12,6 @@ Based on [this example from the terraform docs](https://developer.hashicorp.com/
```hcl
provider_installation {
dev_overrides {
# Example GOBIN path, will need to be replaced with your own GOBIN path. Default is $GOPATH/bin
"oscarhermoso/binarylane" = "/home/oscarhermoso/Git/terraform-provider-binarylane/bin"
@@ -67,20 +66,20 @@ tfplugingen-framework scaffold data-source \
Define the interfaces that the resource/data source should implement:
```diff
- var _ resource.Resource = (*replaceMeResource)(nil)
- var _ resource.Resource = (*exampleResource)(nil)
+ // Ensure the implementation satisfies the expected interfaces.
+ var (
+ _ resource.Resource = &replaceMeResource{}
+ _ resource.ResourceWithConfigure = &replaceMeResource{}
+ // _ resource.ResourceWithImportState = &replaceMeResource{}
+ _ resource.Resource = &exampleResource{}
+ _ resource.ResourceWithConfigure = &exampleResource{}
+ // _ resource.ResourceWithImportState = &exampleResource{}
+ )
```
Pass the API client to the resource:
```diff
- type replaceMeResource struct{}
+ type replaceMeResource struct {
- type exampleResource struct{}
+ type exampleResource struct {
+ bc *BinarylaneClient
+ }
```
@@ -88,7 +87,7 @@ Pass the API client to the resource:
Add a `Configure` method to the resource:
```diff
+ func (d *replaceMeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ func (d *exampleResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
@@ -107,18 +106,14 @@ Add a `Configure` method to the resource:
+ }
```
Delete the local `*ResourceModel` struct, and import the generated `resources.*Model` instead.
Extend the resource model from the generated `resources.*Model`.
```diff
- type replaceMeResourceModel struct {
type exampleResourceModel struct {
- Id types.String `tfsdk:"id"`
- }
```
```diff
func (r *replaceMeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data replaceMeResourceModel
+ var data resources.ReplaceMeModel
+ *resources.ExampleModel
+ // Add any additional fields here
}
```
Add the new data source/resource to `provider.go`:
@@ -126,6 +121,6 @@ Add the new data source/resource to `provider.go`:
```diff
return []func() resource.Resource{
# ...
+ NewReplaceMeResource,
+ NewExampleResource,
}
```
-1
View File
@@ -4,7 +4,6 @@ See the examples in the [examples directory](./examples/basic/main.tf).
```terraform
resource "binarylane_server" "example" {
name = "example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
+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.
- `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.
### Read-Only
+2
View File
@@ -27,3 +27,5 @@ 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.
- `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.
- `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.
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+47
View File
@@ -0,0 +1,47 @@
# examples/cloud-init
Binary lane servers initialize cloud-init configurations to the server upon creation.
If you want to deploy multiple parts of a cloud-init configuration, you can use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config) data source.
```terraform
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
data "cloudinit_config" "example" {
gzip = false
base64_encode = false
part {
filename = "hello-script.sh"
content_type = "text/x-shellscript"
content = file("./hello-script.sh")
}
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = file("./cloud-config.yml")
}
}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = data.cloudinit_config.example.rendered
}
```
+22
View File
@@ -0,0 +1,22 @@
#cloud-config
bootcmd:
# By default, the provider won't wait for the server installation to complete.
#
# If you want Terraform to wait until the server has fully provisioned, set
# the `wait_for_create` attribute on the `binarylane_server` resource.
#
# If you have additional installation steps that need to be awaited, define
# these in the `bootcmd` section and they will also be awaited.
#
- [cloud-init-per, once, 'example-installing', sh, -c, 'sleep 60'] # pretend this is a slow installation step
- [
cloud-init-per,
once,
'example-done',
sh,
-c,
'echo Finished installing, Terraform will now proceed:`date` >> /var/tmp/user_data.log',
]
runcmd:
- [sh, -c, 'sleep 99999'] # The provider won't wait for `runcmd` to complete
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_server" "example" {
name = "tf-cloud-init-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = file("./cloud-config.yml")
wait_for_create = 300 # 5 mins
}
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+3
View File
@@ -0,0 +1,3 @@
# examples/nix
WIP
+14
View File
@@ -0,0 +1,14 @@
#cloud-config
# yaml-language-server: $schema=https://raw.githubusercontent.com/canonical/cloud-init/main/cloudinit/config/schemas/schema-cloud-config-v1.json
#cloud-config
write_files:
- path: /etc/nixos/host.nix
permissions: '0644'
content: |
{pkgs, ...}:
{
environment.systemPackages = with pkgs; [ vim ];
}
bootcmd:
- cloud-init-per once "infect" curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-23.05 bash 2>&1 | tee /var/tmp/infect.log
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "debian-12"
size = "std-min"
user_data = file("./cloud-config.yml")
wait_for_create = 300 # 5 mins
}
+31 -12
View File
@@ -3,12 +3,12 @@ package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -70,7 +70,9 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
// Read API call logic
// TODO: Use go routines here to make requests concurrently
// Read server
serverResp, err := d.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
@@ -79,23 +81,40 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status code reading server: name=%s", data.Id.String()),
fmt.Sprintf("Unexpected HTTP status code reading server: %s", serverResp.Body),
)
return
}
// TODO: Fetch user data
// Read user data
// userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
// if err != nil {
// resp.Diagnostics.AddError(
// fmt.Sprintf("Error reading server user data: name=%s", data.Id.String()),
// err.Error(),
// )
// return
// }
// if userDataResp.StatusCode() != http.StatusOK {
// resp.Diagnostics.AddError(
// fmt.Sprintf("Unexpected HTTP status code reading server user data: name=%s", data.Id.String()),
// fmt.Sprintf("Unexpected HTTP status code reading server user data: %s", serverResp.Body),
// )
// return
// }
// Set data values
tflog.Trace(ctx,
fmt.Sprintf(
"Reading server: id=%d, name=%s, image=%s, region=%s, size=%s",
*serverResp.JSON200.Server.Id,
*serverResp.JSON200.Server.Name,
*serverResp.JSON200.Server.Image.Slug,
*serverResp.JSON200.Server.Region.Slug,
*serverResp.JSON200.Server.Size.Slug,
),
)
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
// data.UserData = types.StringValue(*userDataResp.JSON200.UserData)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+93 -18
View File
@@ -6,10 +6,14 @@ import (
"net/http"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"time"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
@@ -30,6 +34,11 @@ type serverResource struct {
bc *BinarylaneClient
}
type serverModel struct {
resources.ServerModel
WaitForCreateSeconds int32 `tfsdk:"wait_for_create"`
}
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -74,10 +83,33 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
Computed: backups.IsOptional(),
Default: booldefault.StaticBool(false), // Add default to backups
}
user_data := resp.Schema.Attributes["user_data"]
resp.Schema.Attributes["user_data"] = &schema.StringAttribute{
Description: user_data.GetDescription(),
MarkdownDescription: user_data.GetMarkdownDescription(),
Optional: user_data.IsComputed(),
Computed: user_data.IsOptional(),
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
}
// Additional attributes
waitDescription := "The number of seconds to wait for the server to be created, after which, a timeout error will " +
"be reported. If `wait_seconds` is left empty or set to 0, Terraform will succeed without waiting for the " +
"server creation to complete."
resp.Schema.Attributes["wait_for_create"] = &schema.Int32Attribute{
Description: waitDescription,
MarkdownDescription: waitDescription,
Optional: true,
Computed: true,
Default: int32default.StaticInt32(0),
}
}
func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
@@ -90,10 +122,11 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
tflog.Debug(ctx, fmt.Sprintf("Creating server: name=%s", data.Name.ValueString()))
body := binarylane.CreateServerRequest{
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
UserData: data.UserData.ValueStringPointer(),
}
if data.Password.IsNull() {
@@ -118,24 +151,66 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
return
}
assignInt64(serverResp.JSON200.Server.Id, &data.Id)
assignStr(serverResp.JSON200.Server.Name, &data.Name)
assignStr(serverResp.JSON200.Server.Image.Slug, &data.Image)
assignStr(serverResp.JSON200.Server.Region.Slug, &data.Region)
assignStr(serverResp.JSON200.Server.Size.Slug, &data.Size)
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
// assignBool(&serverResp.JSON200.Server.Networks.PortBlocking, &plan.PortBlocking)
if resp.Diagnostics.HasError() {
return
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
if data.WaitForCreateSeconds <= 0 {
return
}
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for server to be created, links.actions with rel=create missing from response",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
return
}
timeLimit := time.Now().Add(time.Duration(data.WaitForCreateSeconds) * time.Second)
for {
tflog.Info(ctx, "Waiting for server to be ready...")
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, data.Id.ValueInt64(), createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be ready", err.Error())
return
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
tflog.Info(ctx, "Server is ready")
break
}
if time.Now().After(timeLimit) {
resp.Diagnostics.AddError(
"Timed out waiting for server to be ready",
fmt.Sprintf(
"Timed out waiting for server to be created, as `wait_for_create` was surpassed without "+
"recieving a `completed_at` in response: name=%s, status=%s, details: %s",
data.Name.ValueString(), readyResp.Status(), readyResp.Body,
),
)
return
}
tflog.Debug(ctx, fmt.Sprintf("Waiting for server to be ready: name=%s, status=%s, details: %s", data.Name.ValueString(), readyResp.Status(), readyResp.Body))
time.Sleep(time.Second * 5)
}
}
func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -177,7 +252,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
}
func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@@ -194,7 +269,7 @@ func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest,
}
func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+21 -39
View File
@@ -1,12 +1,19 @@
package provider
import (
"crypto/rand"
"encoding/base64"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestServerResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
rand.Read(pw_bytes)
password := base64.URLEncoding.EncodeToString(pw_bytes)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
@@ -16,8 +23,13 @@ func TestServerResource(t *testing.T) {
resource "binarylane_server" "test" {
name = "tf-server-resource-test"
region = "per"
image = "ubuntu-24.04"
image = "debian-12"
size = "std-min"
password = "` + password + `"
user_data = <<EOT
#cloud-config
echo "Hello World" > /var/tmp/output.txt
EOT
}
data "binarylane_server" "test" {
@@ -28,54 +40,24 @@ data "binarylane_server" "test" {
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-server-resource-test"),
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "ubuntu-24.04"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "password", password),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
`),
// Verify data source values
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "name", "tf-server-resource-test"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "ubuntu-24.04"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("data.binarylane_server.test", "id"),
),
},
// ImportState testing
// {
// ResourceName: "binarylane_server.test",
// ImportState: true,
// ImportStateVerify: true,
// ImportStateVerifyIgnore: []string{}, // nothing to ignore
// },
// TODO: Update and Read testing
// {
// Config: providerConfig + `
// resource "binarylane_server" "test" {
// items = [
// {
// coffee = {
// id = 2
// }
// quantity = 2
// },
// ]
// }
// `,
// Check: resource.ComposeAggregateTestCheckFunc(
// // Verify first order item updated
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.quantity", "2"),
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.id", "2"),
// // Verify first coffee item has Computed attributes updated.
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.description", ""),
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.image", "/packer.png"),
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.name", "Packer Spiced Latte"),
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.price", "350"),
// resource.TestCheckResourceAttr("binarylane_server.test", "items.0.coffee.teaser", "Packed with goodness to spice up your images"),
// ),
// },
// Delete testing automatically occurs in TestCase
},
})
}
-31
View File
@@ -6,40 +6,9 @@ import (
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
r_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
func assignStr(source *string, target *basetypes.StringValue) {
if source != nil {
*target = types.StringValue(*source)
}
}
// func assignInt(source *int, target *basetypes.Int64Value) {
// if source != nil {
// *target = types.Int64Value(int64(*source))
// }
// }
func assignInt64(source *int64, target *basetypes.Int64Value) {
if source != nil {
*target = types.Int64Value(int64(*source))
}
}
// func assignFloat(source *float32, target *basetypes.Float64Value) {
// if source != nil {
// *target = types.Float64Value(float64(*source))
// }
// }
// func assignBool(source *bool, target *basetypes.BoolValue) {
// if source != nil {
// *target = types.BoolValue(bool(*source))
// }
// }
func convertResourceSchemaToDataSourceSchema(ctx context.Context, rs r_schema.Schema) *d_schema.Schema {
ds := d_schema.Schema{
Attributes: make(map[string]d_schema.Attribute, len(rs.Attributes)),
+10
View File
@@ -59,6 +59,15 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
stringvalidator.LengthAtLeast(1),
},
},
"user_data": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
MarkdownDescription: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
Validators: []validator.String{
stringvalidator.LengthAtMost(65536),
},
},
},
}
}
@@ -71,4 +80,5 @@ type ServerModel struct {
Password types.String `tfsdk:"password"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
UserData types.String `tfsdk:"user_data"`
}
+19
View File
@@ -73,6 +73,25 @@
]
}
},
{
"name": "user_data",
"string": {
"computed_optional_required": "computed_optional",
"description": "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtMost(65536)"
}
}
]
}
},
{
"name": "id",
"int64": {
-1
View File
@@ -57,7 +57,6 @@ resources:
- server
- ssh_keys
- vpc_id
- user_data
- port_blocking
ssh_key:
create: