From c11122bc622d95e60e711d1538f8c04db9110112 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 26 Oct 2023 00:06:40 -0400 Subject: [PATCH] Begin work --- docs/data-sources/server.md | 76 +++ docs/resources/ssh.md | 1 - internal/provider/provider.go | 10 +- .../provider/virtfusion_server_data_source.go | 433 ++++++++++++++++++ 4 files changed, 515 insertions(+), 5 deletions(-) create mode 100644 docs/data-sources/server.md create mode 100644 internal/provider/virtfusion_server_data_source.go diff --git a/docs/data-sources/server.md b/docs/data-sources/server.md new file mode 100644 index 0000000..0b62b81 --- /dev/null +++ b/docs/data-sources/server.md @@ -0,0 +1,76 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "virtfusion_server Data Source - terraform-provider-virtfusion" +subcategory: "" +description: |- + The Virtfusion Server data source allows access to the details of a server. This data source can be used to read information about an existing server. +--- + +# virtfusion_server (Data Source) + +The Virtfusion Server data source allows access to the details of a server. This data source can be used to read information about an existing server. + + + + +## Schema + +### Required + +- `id` (Number) Server ID + +### Read-Only + +- `build_failed` (Boolean) Build failed +- `built` (String) Built +- `commission_status` (Number) Commission status +- `config_level` (Number) Config level +- `created` (String) Created +- `delete_level` (Number) Delete level +- `hostname` (String) Server hostname +- `hypervisor_id` (Number) Hypervisor ID +- `migrate_level` (Number) Migrate level +- `name` (String) Server name +- `network` (Attributes) Network interfaces (see [below for nested schema](#nestedatt--network)) +- `owner_id` (Number) Owner ID +- `primary_network_dhcp4` (Boolean) Primary network DHCP4 +- `primary_network_dhcp6` (Boolean) Primary network DHCP6 +- `protected` (Boolean) Protected +- `rebuild` (Boolean) Rebuild +- `state` (String) Server state +- `suspended` (Boolean) Suspended +- `updated` (String) Updated +- `uuid` (String) Server UUID + + +### Nested Schema for `network` + +Read-Only: + +- `interfaces` (Attributes) Network interfaces (see [below for nested schema](#nestedatt--network--interfaces)) + + +### Nested Schema for `network.interfaces` + +Read-Only: + +- `enabled` (Boolean) Enabled +- `ipv4` (Attributes) IPv4 (see [below for nested schema](#nestedatt--network--interfaces--ipv4)) +- `mac` (String) MAC +- `name` (String) Name +- `type` (String) Type + + +### Nested Schema for `network.interfaces.ipv4` + +Read-Only: + +- `address` (String) Address +- `enabled` (Boolean) Enabled +- `gateway` (String) Gateway +- `id` (Number) ID +- `mac` (String) MAC +- `netmask` (String) Netmask +- `rdns` (String) RDNS +- `resolver_one` (String) Resolver one +- `resolver_two` (String) Resolver two diff --git a/docs/resources/ssh.md b/docs/resources/ssh.md index 530acf9..03ba82d 100644 --- a/docs/resources/ssh.md +++ b/docs/resources/ssh.md @@ -36,4 +36,3 @@ resource "virtfusion_ssh" "dummy_key" { ### Read-Only - `id` (Number) SSH Key ID -- `public_key_hash` (String) Public Key Hash diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7ff2a2d..6c65512 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -27,8 +27,8 @@ type VirtfusionProvider struct { version string } -// ScaffoldingProviderModel describes the provider data model. -type ScaffoldingProviderModel struct { +// VirtfusionProviderModel describes the provider data model. +type VirtfusionProviderModel struct { Endpoint types.String `tfsdk:"endpoint"` ApiToken types.String `tfsdk:"api_token"` } @@ -58,7 +58,7 @@ func (p *VirtfusionProvider) Configure(ctx context.Context, req provider.Configu apiToken := os.Getenv("VIRTFUSION_API_TOKEN") endpoint := os.Getenv("VIRTFUSION_ENDPOINT") - var data ScaffoldingProviderModel + var data VirtfusionProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -121,7 +121,9 @@ func (p *VirtfusionProvider) Resources(ctx context.Context) []func() resource.Re } func (p *VirtfusionProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + NewVirtfusionServerDataSource, + } } type CustomTransport struct { diff --git a/internal/provider/virtfusion_server_data_source.go b/internal/provider/virtfusion_server_data_source.go new file mode 100644 index 0000000..92fc86b --- /dev/null +++ b/internal/provider/virtfusion_server_data_source.go @@ -0,0 +1,433 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &VirtfusionServerDataSource{} + +func NewVirtfusionServerDataSource() datasource.DataSource { + return &VirtfusionServerDataSource{} +} + +// VirtfusionServerDataSource defines the data source implementation. +type VirtfusionServerDataSource struct { + client *http.Client +} + +// VirtfusionServerDataSourceModel describes the data source data model. +type VirtfusionServerDataSourceModel struct { + Id types.Int64 `tfsdk:"id" json:"id"` + OwnerId types.Int64 `tfsdk:"owner_id" json:"ownerId"` + HypervisorId types.Int64 `tfsdk:"hypervisor_id" json:"hypervisorId"` + Name types.String `tfsdk:"name" json:"name"` + Hostname types.String `tfsdk:"hostname" json:"hostname"` + CommissionStatus types.Int64 `tfsdk:"commission_status" json:"commissionStatus"` + Uuid types.String `tfsdk:"uuid" json:"uuid"` + State types.String `tfsdk:"state" json:"state"` + MigrateLevel types.Int64 `tfsdk:"migrate_level" json:"migrateLevel"` + DeleteLevel types.Int64 `tfsdk:"delete_level" json:"deleteLevel"` + ConfigLevel types.Int64 `tfsdk:"config_level" json:"configLevel"` + Rebuild types.Bool `tfsdk:"rebuild" json:"rebuild"` + Suspended types.Bool `tfsdk:"suspended" json:"suspended"` + Protected types.Bool `tfsdk:"protected" json:"protected"` + BuildFailed types.Bool `tfsdk:"build_failed" json:"buildFailed"` + PrimaryNetworkDhcp4 types.Bool `tfsdk:"primary_network_dhcp4" json:"primaryNetworkDhcp4"` + PrimaryNetworkDhcp6 types.Bool `tfsdk:"primary_network_dhcp6" json:"primaryNetworkDhcp6"` + Built types.String `tfsdk:"built" json:"built"` + Created types.String `tfsdk:"created" json:"created"` + Updated types.String `tfsdk:"updated" json:"updated"` + //Settings struct { + // OsTemplateInstall types.Bool `tfsdk:"os_template_install" json:"osTemplateInstall"` + // OsTemplateInstallId types.Int64 `tfsdk:"os_template_install_id" json:"osTemplateInstallId"` + // EncryptedPassword types.String `tfsdk:"encrypted_password" json:"encryptedPassword"` + // BackupPlan types.Int64 `tfsdk:"backup_plan" json:"backupPlan"` + // Uefi types.Bool `tfsdk:"uefi" json:"uefi"` + // CloudInit types.Bool `tfsdk:"cloud_init" json:"cloudInit"` + // CloudInitType types.Int64 `tfsdk:"cloud_init_type" json:"cloudInitType"` + // Resources struct { + // Memory types.Int64 `tfsdk:"memory" json:"memory"` + // Cores types.Int64 `tfsdk:"cores" json:"cpuCores"` + // Storage types.Int64 `tfsdk:"storage" json:"storage"` + // Traffic types.Int64 `tfsdk:"traffic" json:"traffic"` + // } `tfsdk:"resources" json:"resources"` + //} `tfsdk:"settings" json:"settings"` + Network struct { + Interfaces []struct { + Enabled types.Bool `tfsdk:"enabled" json:"enabled"` + Name types.String `tfsdk:"name" json:"name"` + Type types.String `tfsdk:"type" json:"type"` + Mac types.String `tfsdk:"mac" json:"mac"` + Ipv4 []struct { + Id types.Int64 `tfsdk:"id" json:"id"` + Enabled types.Bool `tfsdk:"enabled" json:"enabled"` + Address types.String `tfsdk:"address" json:"address"` + Netmask types.String `tfsdk:"netmask" json:"netmask"` + Gateway types.String `tfsdk:"gateway" json:"gateway"` + ResolverOne types.String `tfsdk:"resolver_one" json:"resolverOne"` + ResolverTwo types.String `tfsdk:"resolver_two" json:"resolverTwo"` + Rdns types.String `tfsdk:"rdns" json:"rdns"` + Mac types.String `tfsdk:"mac" json:"mac"` + } `tfsdk:"ipv4" json:"ipv4"` + Ipv6 []struct { + } `tfsdk:"ipv6" json:"ipv6"` + } `tfsdk:"interfaces" json:"interfaces"` + } `tfsdk:"network" json:"network"` + //Owner struct { + // Id types.Int64 `tfsdk:"id" json:"id"` + // Name types.String `tfsdk:"name" json:"name"` + // Email types.String `tfsdk:"email" json:"email"` + // ExtId types.Int64 `tfsdk:"ext_id" json:"extRelationId"` + // Timezone types.String `tfsdk:"timezone" json:"timezone"` + // Suspended types.Bool `tfsdk:"suspended" json:"suspended"` + // Created types.String `tfsdk:"created" json:"created"` + // Updated types.String `tfsdk:"updated" json:"updated"` + // TwoFactorAuth types.Bool `tfsdk:"two_factor_auth" json:"twoFactorAuth"` + //} `tfsdk:"owner" json:"owner"` +} + +func (d *VirtfusionServerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_server" +} + +func (d *VirtfusionServerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "The Virtfusion Server data source allows access to the details of a server." + + " This data source can be used to read information about an existing server.", + + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + MarkdownDescription: "Server ID", + Required: true, + }, + "owner_id": schema.Int64Attribute{ + MarkdownDescription: "Owner ID", + Computed: true, + }, + "hypervisor_id": schema.Int64Attribute{ + MarkdownDescription: "Hypervisor ID", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Server name", + Computed: true, + }, + "hostname": schema.StringAttribute{ + MarkdownDescription: "Server hostname", + Computed: true, + }, + "commission_status": schema.Int64Attribute{ + MarkdownDescription: "Commission status", + Computed: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "Server UUID", + Computed: true, + }, + "state": schema.StringAttribute{ + MarkdownDescription: "Server state", + Computed: true, + }, + "migrate_level": schema.Int64Attribute{ + MarkdownDescription: "Migrate level", + Computed: true, + }, + "delete_level": schema.Int64Attribute{ + MarkdownDescription: "Delete level", + Computed: true, + }, + "config_level": schema.Int64Attribute{ + MarkdownDescription: "Config level", + Computed: true, + }, + "rebuild": schema.BoolAttribute{ + MarkdownDescription: "Rebuild", + Computed: true, + }, + "suspended": schema.BoolAttribute{ + MarkdownDescription: "Suspended", + Computed: true, + }, + "protected": schema.BoolAttribute{ + MarkdownDescription: "Protected", + Computed: true, + }, + "build_failed": schema.BoolAttribute{ + MarkdownDescription: "Build failed", + Computed: true, + }, + "primary_network_dhcp4": schema.BoolAttribute{ + MarkdownDescription: "Primary network DHCP4", + Computed: true, + }, + "primary_network_dhcp6": schema.BoolAttribute{ + MarkdownDescription: "Primary network DHCP6", + Computed: true, + }, + "built": schema.StringAttribute{ + MarkdownDescription: "Built", + Computed: true, + }, + "created": schema.StringAttribute{ + MarkdownDescription: "Created", + Computed: true, + }, + "updated": schema.StringAttribute{ + MarkdownDescription: "Updated", + Computed: true, + }, + "network": schema.SingleNestedAttribute{ + MarkdownDescription: "Network interfaces", + Computed: true, + Attributes: map[string]schema.Attribute{ + "interfaces": schema.SingleNestedAttribute{ + MarkdownDescription: "Network interfaces", + Computed: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enabled", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "Type", + Computed: true, + }, + "mac": schema.StringAttribute{ + MarkdownDescription: "MAC", + Computed: true, + }, + "ipv4": schema.SingleNestedAttribute{ + MarkdownDescription: "IPv4", + Computed: true, + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + MarkdownDescription: "ID", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enabled", + Computed: true, + }, + "address": schema.StringAttribute{ + MarkdownDescription: "Address", + Computed: true, + }, + "netmask": schema.StringAttribute{ + MarkdownDescription: "Netmask", + Computed: true, + }, + "gateway": schema.StringAttribute{ + MarkdownDescription: "Gateway", + Computed: true, + }, + "resolver_one": schema.StringAttribute{ + MarkdownDescription: "Resolver one", + Computed: true, + }, + "resolver_two": schema.StringAttribute{ + MarkdownDescription: "Resolver two", + Computed: true, + }, + "rdns": schema.StringAttribute{ + MarkdownDescription: "RDNS", + Computed: true, + }, + "mac": schema.StringAttribute{ + MarkdownDescription: "MAC", + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (d *VirtfusionServerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *VirtfusionServerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data VirtfusionServerDataSourceModel + var responseData struct { + Data struct { + Id int64 `json:"id"` + OwnerId int64 `json:"owner_id"` + HypervisorId int64 `json:"hypervisor_id"` + Name string `json:"name"` + Hostname string `json:"hostname"` + CommissionStatus int64 `json:"commission_status"` + Uuid string `json:"uuid"` + State string `json:"state"` + MigrateLevel int64 `json:"migrate_level"` + DeleteLevel int64 `json:"delete_level"` + ConfigLevel int64 `json:"config_level"` + Rebuild bool `json:"rebuild"` + Suspended bool `json:"suspended"` + Protected bool `json:"protected"` + BuildFailed bool `json:"build_failed"` + PrimaryNetworkDhcp4 bool `json:"primary_network_dhcp4"` + PrimaryNetworkDhcp6 bool `json:"primary_network_dhcp6"` + Built string `json:"built"` + Created string `json:"created"` + Updated string `json:"updated"` + //Settings struct { + // OsTemplateInstall types.Bool `json:"os_template_install"` + // OsTemplateInstallId types.Int64 `json:"os_template_install_id"` + // EncryptedPassword types.String `json:"encrypted_password"` + // BackupPlan types.Int64 `json:"backup_plan"` + // Uefi types.Bool `json:"uefi"` + // CloudInit types.Bool `json:"cloud_init"` + // CloudInitType types.Int64 `json:"cloud_init_type"` + // Resources struct { + // Memory types.Int64 `json:"memory"` + // Cores types.Int64 `json:"cpu_cores"` + // Storage types.Int64 `json:"storage"` + // Traffic types.Int64 `json:"traffic"` + // } `json:"resources"` + //} `json:"settings"` + Network struct { + Interfaces []struct { + Enabled bool `json:"enabled"` + Name string `json:"name"` + Type string `json:"type"` + Mac string `json:"mac"` + Ipv4 []struct { + Id int64 `json:"id"` + Enabled bool `json:"enabled"` + Address string `json:"address"` + Netmask string `json:"netmask"` + Gateway string `json:"gateway"` + ResolverOne string `json:"resolver_one"` + ResolverTwo string `json:"resolver_two"` + Rdns string `json:"rdns"` + Mac string `json:"mac"` + } `json:"ipv4"` + Ipv6 []struct { + } `json:"ipv6"` + } `json:"interfaces"` + } `json:"network"` + //Owner struct { + // Id types.Int64 `json:"id"` + // Name types.String `json:"name"` + // Email types.String `json:"email"` + // ExtId types.Int64 `json:"ext_id"` + // Timezone types.String `json:"timezone"` + // Suspended types.Bool `json:"suspended"` + // Created types.String `json:"created"` + // Updated types.String `json:"updated"` + // TwoFactorAuth types.Bool `json:"two_factor_auth"` + //} `json:"owner"` + } `json:"data"` + } + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Make an API request using the configured client + httpReq, err := http.NewRequest("GET", fmt.Sprintf("/servers/%d", data.Id.ValueInt64()), nil) + + if err != nil { + resp.Diagnostics.AddError( + "Failed to Create Request", + fmt.Sprintf("Failed to create a new HTTP request: %s", err.Error()), + ) + return + } + + // If the resource returns a 404, then the resource has been deleted. Return an empty state. + httpResponse, err := d.client.Do(httpReq) + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + resp.Diagnostics.AddError( + "Failed to close response body", + fmt.Sprintf("Failed to close response body: %s", err.Error()), + ) + } + }(httpResponse.Body) + + if err != nil { + resp.Diagnostics.AddError( + "Failed to Execute Request", + fmt.Sprintf("Failed to execute HTTP request: %s", err.Error()), + ) + return + } + + if httpResponse.StatusCode == 404 { + resp.State.RemoveResource(ctx) + return + } + + err = json.NewDecoder(httpResponse.Body).Decode(&responseData) + + if err != nil { + resp.Diagnostics.AddError( + "Failed to decode response body", + fmt.Sprintf("Failed to decode response body: %s", err.Error()), + ) + return + } + + data.Id = types.Int64Value(responseData.Data.Id) + data.OwnerId = types.Int64Value(responseData.Data.OwnerId) + data.HypervisorId = types.Int64Value(responseData.Data.HypervisorId) + data.Name = types.StringValue(responseData.Data.Name) + data.Hostname = types.StringValue(responseData.Data.Hostname) + data.CommissionStatus = types.Int64Value(responseData.Data.CommissionStatus) + data.Uuid = types.StringValue(responseData.Data.Uuid) + data.State = types.StringValue(responseData.Data.State) + data.MigrateLevel = types.Int64Value(responseData.Data.MigrateLevel) + data.DeleteLevel = types.Int64Value(responseData.Data.DeleteLevel) + data.ConfigLevel = types.Int64Value(responseData.Data.ConfigLevel) + data.Rebuild = types.BoolValue(responseData.Data.Rebuild) + data.Suspended = types.BoolValue(responseData.Data.Suspended) + data.Protected = types.BoolValue(responseData.Data.Protected) + data.BuildFailed = types.BoolValue(responseData.Data.BuildFailed) + data.PrimaryNetworkDhcp4 = types.BoolValue(responseData.Data.PrimaryNetworkDhcp4) + data.PrimaryNetworkDhcp6 = types.BoolValue(responseData.Data.PrimaryNetworkDhcp6) + data.Built = types.StringValue(responseData.Data.Built) + data.Created = types.StringValue(responseData.Data.Created) + data.Updated = types.StringValue(responseData.Data.Updated) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +}