From d8c6f418143c439be354cf463ccdb5cce5a180f2 Mon Sep 17 00:00:00 2001 From: xueqingz Date: Wed, 5 Jun 2024 07:30:34 +0000 Subject: [PATCH 1/2] CP-49366: Implement xenserver_sr provider data source Signed-off-by: xueqingz --- docs/data-sources/sr.md | 62 ++++ .../data-sources/xenserver_sr/data-source.tf | 7 + examples/terraform-main/main.tf | 8 + go.sum | 6 - xenserver/provider.go | 1 + xenserver/sr_data_source.go | 314 ++++++++++++++++++ xenserver/sr_data_source_test.go | 21 ++ xenserver/test_config.go | 8 + 8 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 docs/data-sources/sr.md create mode 100644 examples/data-sources/xenserver_sr/data-source.tf create mode 100644 xenserver/sr_data_source.go create mode 100644 xenserver/sr_data_source_test.go diff --git a/docs/data-sources/sr.md b/docs/data-sources/sr.md new file mode 100644 index 0000000..f7dd87c --- /dev/null +++ b/docs/data-sources/sr.md @@ -0,0 +1,62 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "xenserver_sr Data Source - xenserver" +subcategory: "" +description: |- + The data source of XenServer storage repository +--- + +# xenserver_sr (Data Source) + +The data source of XenServer storage repository + +## Example Usage + +```terraform +data "xenserver_sr" "sr" { + name_label = "Local storage" +} + +output "local_storage_output" { + value = data.xenserver_sr.sr.data_items +} +``` + + +## Schema + +### Optional + +- `name_label` (String) The name of the storage repository +- `uuid` (String) The UUID of the storage repository + +### Read-Only + +- `data_items` (Attributes List) The return items of storage repositories (see [below for nested schema](#nestedatt--data_items)) + + +### Nested Schema for `data_items` + +Read-Only: + +- `allowed_operations` (List of String) The list of the operations allowed in this state +- `blobs` (Map of String) The binary blobs associated with this SR +- `clustered` (Boolean) True if the SR is using aggregated local storage +- `content_type` (String) The type of the SR's content, if required (e.g. ISOs) +- `current_operations` (Map of String) The links each of the running tasks using this object (by reference) to a current_operation enum which describes the nature of the task +- `introduced_by` (String) The disaster recovery task which introduced this SR +- `is_tools_sr` (Boolean) True if this is the SR that contains the Tools ISO VDIs +- `local_cache_enabled` (Boolean) True if this SR is assigned to be the local cache for its host +- `name_description` (String) The human-readable description of the storage repository +- `name_label` (String) The name of the storage repository +- `other_config` (Map of String) The additional configuration +- `pbds` (List of String) Describes how particular hosts can see this storage repository +- `physical_size` (Number) The total physical size of the storage repository (in bytes) +- `physical_utilisation` (Number) The physical space currently utilised on this storage repository (in bytes) +- `shared` (Boolean) True if this SR is (capable of being) shared between multiple hosts +- `sm_config` (Map of String) The SM dependent data +- `tags` (List of String) The user-specified tags for categorization purposes +- `type` (String) The type of the storage repository +- `uuid` (String) The UUID of the storage repository +- `vdis` (List of String) The all virtual disks known to this storage repository +- `virtual_allocation` (Number) The sum of virtual_sizes of all VDIs in this storage repository (in bytes) diff --git a/examples/data-sources/xenserver_sr/data-source.tf b/examples/data-sources/xenserver_sr/data-source.tf new file mode 100644 index 0000000..6cbbec6 --- /dev/null +++ b/examples/data-sources/xenserver_sr/data-source.tf @@ -0,0 +1,7 @@ +data "xenserver_sr" "sr" { + name_label = "Local storage" +} + +output "local_storage_output" { + value = data.xenserver_sr.sr.data_items +} \ No newline at end of file diff --git a/examples/terraform-main/main.tf b/examples/terraform-main/main.tf index e12a580..b02bcb9 100644 --- a/examples/terraform-main/main.tf +++ b/examples/terraform-main/main.tf @@ -36,3 +36,11 @@ resource "xenserver_vm" "vm" { output "vm_out" { value = xenserver_vm.vm } + +data "xenserver_sr" "sr" { + name_label = "Local storage" +} + +output "local_storage_output" { + value = data.xenserver_sr.sr.data_items +} diff --git a/go.sum b/go.sum index 8932373..04f9d20 100644 --- a/go.sum +++ b/go.sum @@ -98,14 +98,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-docs v0.19.2 h1:YjdKa1vuqt9EnPYkkrv9HnGZz175HhSJ7Vsn8yZeWus= -github.com/hashicorp/terraform-plugin-docs v0.19.2/go.mod h1:gad2aP6uObFKhgNE8DR9nsEuEQnibp7il0jZYYOunWY= -github.com/hashicorp/terraform-plugin-docs v0.19.3 h1:xoxpeIuBfnoGxXY0dTajdj4GjEv6TihZdj0lHNXbKew= -github.com/hashicorp/terraform-plugin-docs v0.19.3/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= -github.com/hashicorp/terraform-plugin-framework v1.8.0 h1:P07qy8RKLcoBkCrY2RHJer5AEvJnDuXomBgou6fD8kI= -github.com/hashicorp/terraform-plugin-framework v1.8.0/go.mod h1:/CpTukO88PcL/62noU7cuyaSJ4Rsim+A/pa+3rUVufY= github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU= github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= diff --git a/xenserver/provider.go b/xenserver/provider.go index c461bb9..f8b1ed7 100644 --- a/xenserver/provider.go +++ b/xenserver/provider.go @@ -194,6 +194,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewPIFDataSource, + NewSRDataSource, } } diff --git a/xenserver/sr_data_source.go b/xenserver/sr_data_source.go new file mode 100644 index 0000000..9eb1875 --- /dev/null +++ b/xenserver/sr_data_source.go @@ -0,0 +1,314 @@ +package xenserver + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + + "xenapi" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &SRDataSource{} + _ datasource.DataSourceWithConfigure = &SRDataSource{} +) + +// NewSRDataSource is a helper function to simplify the provider implementation. +func NewSRDataSource() datasource.DataSource { + return &SRDataSource{} +} + +// SRDataSource is the data source implementation. +type SRDataSource struct { + session *xenapi.Session +} + +// SRDataSourceModel describes the data source data model. +type SRDataSourceModel struct { + NameLabel types.String `tfsdk:"name_label"` + UUID types.String `tfsdk:"uuid"` + DataItems []SRRecordData `tfsdk:"data_items"` +} + +type SRRecordData struct { + UUID types.String `tfsdk:"uuid"` + NameLabel types.String `tfsdk:"name_label"` + NameDescription types.String `tfsdk:"name_description"` + AllowedOperations types.List `tfsdk:"allowed_operations"` + CurrentOperations types.Map `tfsdk:"current_operations"` + VDIs types.List `tfsdk:"vdis"` + PBDs types.List `tfsdk:"pbds"` + VirtualAllocation types.Int64 `tfsdk:"virtual_allocation"` + PhysicalUtilisation types.Int64 `tfsdk:"physical_utilisation"` + PhysicalSize types.Int64 `tfsdk:"physical_size"` + Type types.String `tfsdk:"type"` + ContentType types.String `tfsdk:"content_type"` + Shared types.Bool `tfsdk:"shared"` + OtherConfig types.Map `tfsdk:"other_config"` + Tags types.List `tfsdk:"tags"` + SmConfig types.Map `tfsdk:"sm_config"` + Blobs types.Map `tfsdk:"blobs"` + LocalCacheEnabled types.Bool `tfsdk:"local_cache_enabled"` + IntroducedBy types.String `tfsdk:"introduced_by"` + Clustered types.Bool `tfsdk:"clustered"` + IsToolsSr types.Bool `tfsdk:"is_tools_sr"` +} + +// Metadata returns the data source type name. +func (d *SRDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_sr" +} + +// Schema defines the schema for the data source. +func (d *SRDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The data source of XenServer storage repository", + + Attributes: map[string]schema.Attribute{ + "name_label": schema.StringAttribute{ + MarkdownDescription: "The name of the storage repository", + Optional: true, + }, + "uuid": schema.StringAttribute{ + MarkdownDescription: "The UUID of the storage repository", + Optional: true, + }, + "data_items": schema.ListNestedAttribute{ + MarkdownDescription: "The return items of storage repositories", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + MarkdownDescription: "The UUID of the storage repository", + Computed: true, + }, + "name_label": schema.StringAttribute{ + MarkdownDescription: "The name of the storage repository", + Computed: true, + }, + "name_description": schema.StringAttribute{ + MarkdownDescription: "The human-readable description of the storage repository", + Computed: true, + }, + "allowed_operations": schema.ListAttribute{ + MarkdownDescription: "The list of the operations allowed in this state", + Computed: true, + ElementType: types.StringType, + }, + "current_operations": schema.MapAttribute{ + MarkdownDescription: "The links each of the running tasks using this object (by reference) to a current_operation enum which describes the nature of the task", + Computed: true, + ElementType: types.StringType, + }, + "vdis": schema.ListAttribute{ + MarkdownDescription: "The all virtual disks known to this storage repository", + Computed: true, + ElementType: types.StringType, + }, + "pbds": schema.ListAttribute{ + MarkdownDescription: "Describes how particular hosts can see this storage repository", + Computed: true, + ElementType: types.StringType, + }, + "virtual_allocation": schema.Int64Attribute{ + MarkdownDescription: "The sum of virtual_sizes of all VDIs in this storage repository (in bytes)", + Computed: true, + }, + "physical_utilisation": schema.Int64Attribute{ + MarkdownDescription: "The physical space currently utilised on this storage repository (in bytes)", + Computed: true, + }, + "physical_size": schema.Int64Attribute{ + MarkdownDescription: "The total physical size of the storage repository (in bytes)", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the storage repository", + Computed: true, + }, + "content_type": schema.StringAttribute{ + MarkdownDescription: "The type of the SR's content, if required (e.g. ISOs)", + Computed: true, + }, + "shared": schema.BoolAttribute{ + MarkdownDescription: "True if this SR is (capable of being) shared between multiple hosts", + Computed: true, + }, + "other_config": schema.MapAttribute{ + MarkdownDescription: "The additional configuration", + Computed: true, + ElementType: types.StringType, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: "The user-specified tags for categorization purposes", + Computed: true, + ElementType: types.StringType, + }, + "sm_config": schema.MapAttribute{ + MarkdownDescription: "The SM dependent data", + Computed: true, + ElementType: types.StringType, + }, + "blobs": schema.MapAttribute{ + MarkdownDescription: "The binary blobs associated with this SR", + Computed: true, + ElementType: types.StringType, + }, + "local_cache_enabled": schema.BoolAttribute{ + MarkdownDescription: "True if this SR is assigned to be the local cache for its host", + Computed: true, + }, + "introduced_by": schema.StringAttribute{ + MarkdownDescription: "The disaster recovery task which introduced this SR", + Computed: true, + }, + "clustered": schema.BoolAttribute{ + MarkdownDescription: "True if the SR is using aggregated local storage", + Computed: true, + }, + "is_tools_sr": schema.BoolAttribute{ + MarkdownDescription: "True if this is the SR that contains the Tools ISO VDIs", + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *SRDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + session, ok := req.ProviderData.(*xenapi.Session) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *xenapi.Session, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.session = session +} + +// Read refreshes the Terraform state with the latest data. +func (d *SRDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SRDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + srRecords, err := xenapi.SR.GetAllRecords(d.session) + if err != nil { + resp.Diagnostics.AddError( + "Unable to get SR records", + err.Error(), + ) + return + } + + var srItems []SRRecordData + var diags diag.Diagnostics + for _, srRecord := range srRecords { + if !data.NameLabel.IsNull() && srRecord.NameLabel != data.NameLabel.ValueString() { + continue + } + if !data.UUID.IsNull() && srRecord.UUID != data.UUID.ValueString() { + continue + } + var srData SRRecordData + srData.UUID = types.StringValue(srRecord.UUID) + srData.NameLabel = types.StringValue(srRecord.NameLabel) + srData.NameDescription = types.StringValue(srRecord.NameDescription) + srData.AllowedOperations, diags = types.ListValueFrom(ctx, types.StringType, srRecord.AllowedOperations) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR allowed operations", + err.Error(), + ) + return + } + srData.CurrentOperations, diags = types.MapValueFrom(ctx, types.StringType, srRecord.CurrentOperations) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR current operations", + err.Error(), + ) + return + } + srData.VDIs, diags = types.ListValueFrom(ctx, types.StringType, srRecord.VDIs) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR VDIs", + err.Error(), + ) + return + } + srData.PBDs, diags = types.ListValueFrom(ctx, types.StringType, srRecord.PBDs) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR PBDs", + err.Error(), + ) + return + } + srData.VirtualAllocation = types.Int64Value(int64(srRecord.VirtualAllocation)) + srData.PhysicalUtilisation = types.Int64Value(int64(srRecord.PhysicalUtilisation)) + srData.PhysicalSize = types.Int64Value(int64(srRecord.PhysicalSize)) + srData.Type = types.StringValue(srRecord.Type) + srData.ContentType = types.StringValue(srRecord.ContentType) + srData.Shared = types.BoolValue(srRecord.Shared) + srData.OtherConfig, diags = types.MapValueFrom(ctx, types.StringType, srRecord.OtherConfig) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR other config", + err.Error(), + ) + return + } + srData.Tags, diags = types.ListValueFrom(ctx, types.StringType, srRecord.Tags) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to Read SR Tags", + err.Error(), + ) + return + } + srData.SmConfig, diags = types.MapValueFrom(ctx, types.StringType, srRecord.SmConfig) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR SM config", + err.Error(), + ) + return + } + srData.Blobs, diags = types.MapValueFrom(ctx, types.StringType, srRecord.Blobs) + if diags.HasError() { + resp.Diagnostics.AddError( + "Unable to read SR Blobs", + err.Error(), + ) + return + } + srData.LocalCacheEnabled = types.BoolValue(srRecord.LocalCacheEnabled) + srData.IntroducedBy = types.StringValue(string(srRecord.IntroducedBy)) + srData.Clustered = types.BoolValue(srRecord.Clustered) + srData.IsToolsSr = types.BoolValue(srRecord.IsToolsSr) + + srItems = append(srItems, srData) + } + data.DataItems = srItems + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/xenserver/sr_data_source_test.go b/xenserver/sr_data_source_test.go new file mode 100644 index 0000000..e1cb943 --- /dev/null +++ b/xenserver/sr_data_source_test.go @@ -0,0 +1,21 @@ +package xenserver + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSRDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + testAccSRDataSourceConfig("Local storage"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.xenserver_sr.test_sr_data", "name_label", "Local storage"), + ), + }, + }, + }) +} diff --git a/xenserver/test_config.go b/xenserver/test_config.go index 3a4a149..ec63e02 100644 --- a/xenserver/test_config.go +++ b/xenserver/test_config.go @@ -35,3 +35,11 @@ resource "xenserver_vm" "test_vm" { } `, name_label) } + +func testAccSRDataSourceConfig(name_label string) string { + return fmt.Sprintf(` +data "xenserver_sr" "test_sr_data" { + name_label = "%s" +} +`, name_label) +} From f37d764b80fc3a240aed2400293a10c5a44f4ba8 Mon Sep 17 00:00:00 2001 From: xueqingz Date: Wed, 12 Jun 2024 06:34:04 +0000 Subject: [PATCH 2/2] CP-49366: code format Signed-off-by: xueqingz --- DEVELOP.md | 26 +++--- xenserver/pif_data_source.go | 27 +++--- xenserver/pif_data_source_test.go | 10 +++ xenserver/pif_utils.go | 12 +++ xenserver/provider.go | 34 ++++--- xenserver/provider_test.go | 13 +++ xenserver/sr_data_source.go | 135 ++++------------------------ xenserver/sr_data_source_test.go | 9 ++ xenserver/sr_utils.go | 92 +++++++++++++++++++ xenserver/test_config.go | 45 ---------- xenserver/vm_resouce.go | 131 +++++++++++++-------------- xenserver/vm_resource_test.go | 13 +++ xenserver/{utils.go => vm_utils.go} | 33 ++++--- 13 files changed, 290 insertions(+), 290 deletions(-) create mode 100644 xenserver/pif_utils.go create mode 100644 xenserver/sr_utils.go delete mode 100644 xenserver/test_config.go rename xenserver/{utils.go => vm_utils.go} (54%) diff --git a/DEVELOP.md b/DEVELOP.md index a886fed..7e1a9d4 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -12,9 +12,11 @@ This is the best practice document for XenServer terraform provider development, 2. For each new added component, add the according "NewXX" function into the return value of provider function `Resources`, `DataSources` or `Functions` under `xenserver/provider.go`. -3. For each new added component, create an acceptance test file `__test.go` under folder `xenserver/`. The test configuration can be written together under `xenserver/test.config.go`. +3. For each new added component, create an acceptance test file `__test.go` under folder `xenserver/`. -4. For each new added component, requires to add an example for it under folder `examples/`. +4. For each new added component, create a utils file `_utils.go` under folder `xenserver/` to store the type definitions and common functions. + +5. For each new added component, requires to add an example for it under folder `examples/`. - `provider` @@ -32,7 +34,7 @@ This is the best practice document for XenServer terraform provider development, create a file `function.tf` under folder `examples/functions//` to show how to use this function. -5. Generate new documents base on changes, run `go generate ./...`. +6. Generate new documents base on changes, run `go generate ./...`. ### Local Checking and Testing @@ -55,20 +57,24 @@ golangci-lint run --config=/app/.golangci.yml - component name, like resource, data-source, function, follow `xenserver_`. eg. -```shell +``` xenserver_vm ``` -- function name follow `Pascal`, eg. +- function name and var name follow `Camel-Case`, eg. -```shell -func GetFirstTemplate(){} +``` +func getFirstTemplate(){} +var srRef string ``` -- var name follow `Camel-Case`, eg. +- struct key follow `Pascal`, eg. -```shell -var dataState VMResourceModel +``` +type vmResourceModel struct { + NameLabel types.String `tfsdk:"name_label"` + TemplateName types.String `tfsdk:"template_name"`` +} ``` ## Development Process For Community Contributors diff --git a/xenserver/pif_data_source.go b/xenserver/pif_data_source.go index 8c205b7..62006e9 100644 --- a/xenserver/pif_data_source.go +++ b/xenserver/pif_data_source.go @@ -16,34 +16,27 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ datasource.DataSource = &PIFDataSource{} - _ datasource.DataSourceWithConfigure = &PIFDataSource{} + _ datasource.DataSource = &pifDataSource{} + _ datasource.DataSourceWithConfigure = &pifDataSource{} ) // NewPIFDataSource is a helper function to simplify the provider implementation. func NewPIFDataSource() datasource.DataSource { - return &PIFDataSource{} + return &pifDataSource{} } -// PIFDataSource is the data source implementation. -type PIFDataSource struct { +// pifDataSource is the data source implementation. +type pifDataSource struct { session *xenapi.Session } -// PIFDataSourceModel describes the data source data model. -type PIFDataSourceModel struct { - Device types.String `tfsdk:"device"` - Management types.Bool `tfsdk:"management"` - Network types.String `tfsdk:"network"` -} - // Metadata returns the data source type name. -func (d *PIFDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { +func (d *pifDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_pif" } // Schema defines the schema for the data source. -func (d *PIFDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *pifDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ // This description is used by the documentation generator and the language server. MarkdownDescription: "PIF data source", @@ -65,7 +58,7 @@ func (d *PIFDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, re } } -func (d *PIFDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { +func (d *pifDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -81,8 +74,8 @@ func (d *PIFDataSource) Configure(_ context.Context, req datasource.ConfigureReq } // Read refreshes the Terraform state with the latest data. -func (d *PIFDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data PIFDataSourceModel +func (d *pifDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data pifDataSourceModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) diff --git a/xenserver/pif_data_source_test.go b/xenserver/pif_data_source_test.go index 958f191..ac6baa2 100644 --- a/xenserver/pif_data_source_test.go +++ b/xenserver/pif_data_source_test.go @@ -1,11 +1,21 @@ package xenserver import ( + "fmt" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +func testAccPifDataSourceConfig(device string) string { + return fmt.Sprintf(` +data "xenserver_pif" "test_pif_data" { + device = "%s" + management = true +} +`, device) +} + func TestAccPifDataSource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, diff --git a/xenserver/pif_utils.go b/xenserver/pif_utils.go new file mode 100644 index 0000000..8750bcf --- /dev/null +++ b/xenserver/pif_utils.go @@ -0,0 +1,12 @@ +package xenserver + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// pifDataSourceModel describes the data source data model. +type pifDataSourceModel struct { + Device types.String `tfsdk:"device"` + Management types.Bool `tfsdk:"management"` + Network types.String `tfsdk:"network"` +} diff --git a/xenserver/provider.go b/xenserver/provider.go index f8b1ed7..6d974a7 100644 --- a/xenserver/provider.go +++ b/xenserver/provider.go @@ -17,11 +17,11 @@ import ( ) // Ensure Provider satisfies various provider interfaces. -var _ provider.Provider = &Provider{} -var _ provider.ProviderWithFunctions = &Provider{} +var _ provider.Provider = &xsProvider{} +var _ provider.ProviderWithFunctions = &xsProvider{} -// Provider defines the provider implementation. -type Provider struct { +// xsProvider defines the provider implementation. +type xsProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance // testing. @@ -30,25 +30,25 @@ type Provider struct { func New(version string) func() provider.Provider { return func() provider.Provider { - return &Provider{ + return &xsProvider{ version: version, } } } -// ProviderModel describes the provider data model. -type ProviderModel struct { +// providerModel describes the provider data model. +type providerModel struct { Host types.String `tfsdk:"host"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` } -func (p *Provider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { +func (p *xsProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "xenserver" resp.Version = p.version } -func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { +func (p *xsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "host": schema.StringAttribute{ @@ -68,9 +68,9 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro } } -func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { +func (p *xsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { tflog.Debug(ctx, "Configuring XenServer Client") - var data ProviderModel + var data providerModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -171,34 +171,32 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, _, err := session.LoginWithPassword(username, password, "1.0", "terraform provider") if err != nil { resp.Diagnostics.AddError( - "Unable to Create XENSERVER API Client", + "Unable to create XENSERVER API client", "An unexpected error occurred when creating the XENSERVER API client. "+ "If the error is not clear, please contact the provider developers.\n\n"+ "XENSERVER client Error: "+err.Error(), ) return } - tflog.Debug(ctx, "api version: "+session.APIVersion.String()) - tflog.Debug(ctx, "xapi rpm version: "+session.XAPIVersion) + resp.DataSourceData = session resp.ResourceData = session } -func (p *Provider) Resources(_ context.Context) []func() resource.Resource { - // return nil +func (p *xsProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ NewVMResource, } } -func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource { +func (p *xsProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewPIFDataSource, NewSRDataSource, } } -func (p *Provider) Functions(_ context.Context) []func() function.Function { +func (p *xsProvider) Functions(_ context.Context) []func() function.Function { return nil // return []func() function.Function{ // NewExampleFunction, diff --git a/xenserver/provider_test.go b/xenserver/provider_test.go index d058d06..b39fe39 100644 --- a/xenserver/provider_test.go +++ b/xenserver/provider_test.go @@ -1,6 +1,9 @@ package xenserver import ( + "fmt" + "os" + "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) @@ -12,3 +15,13 @@ import ( var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "xenserver": providerserver.NewProtocol6WithError(New("test")()), } + +var ( + providerConfig = fmt.Sprintf(` +provider "xenserver" { + host = "%s" + username = "%s" + password = "%s" +} +`, os.Getenv("XENSERVER_HOST"), os.Getenv("XENSERVER_USERNAME"), os.Getenv("XENSERVER_PASSWORD")) +) diff --git a/xenserver/sr_data_source.go b/xenserver/sr_data_source.go index 9eb1875..c74a743 100644 --- a/xenserver/sr_data_source.go +++ b/xenserver/sr_data_source.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "xenapi" @@ -14,58 +13,27 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ datasource.DataSource = &SRDataSource{} - _ datasource.DataSourceWithConfigure = &SRDataSource{} + _ datasource.DataSource = &srDataSource{} + _ datasource.DataSourceWithConfigure = &srDataSource{} ) // NewSRDataSource is a helper function to simplify the provider implementation. func NewSRDataSource() datasource.DataSource { - return &SRDataSource{} + return &srDataSource{} } -// SRDataSource is the data source implementation. -type SRDataSource struct { +// srDataSource is the data source implementation. +type srDataSource struct { session *xenapi.Session } -// SRDataSourceModel describes the data source data model. -type SRDataSourceModel struct { - NameLabel types.String `tfsdk:"name_label"` - UUID types.String `tfsdk:"uuid"` - DataItems []SRRecordData `tfsdk:"data_items"` -} - -type SRRecordData struct { - UUID types.String `tfsdk:"uuid"` - NameLabel types.String `tfsdk:"name_label"` - NameDescription types.String `tfsdk:"name_description"` - AllowedOperations types.List `tfsdk:"allowed_operations"` - CurrentOperations types.Map `tfsdk:"current_operations"` - VDIs types.List `tfsdk:"vdis"` - PBDs types.List `tfsdk:"pbds"` - VirtualAllocation types.Int64 `tfsdk:"virtual_allocation"` - PhysicalUtilisation types.Int64 `tfsdk:"physical_utilisation"` - PhysicalSize types.Int64 `tfsdk:"physical_size"` - Type types.String `tfsdk:"type"` - ContentType types.String `tfsdk:"content_type"` - Shared types.Bool `tfsdk:"shared"` - OtherConfig types.Map `tfsdk:"other_config"` - Tags types.List `tfsdk:"tags"` - SmConfig types.Map `tfsdk:"sm_config"` - Blobs types.Map `tfsdk:"blobs"` - LocalCacheEnabled types.Bool `tfsdk:"local_cache_enabled"` - IntroducedBy types.String `tfsdk:"introduced_by"` - Clustered types.Bool `tfsdk:"clustered"` - IsToolsSr types.Bool `tfsdk:"is_tools_sr"` -} - // Metadata returns the data source type name. -func (d *SRDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { +func (d *srDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_sr" } // Schema defines the schema for the data source. -func (d *SRDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *srDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "The data source of XenServer storage repository", @@ -182,7 +150,7 @@ func (d *SRDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, res } } -func (d *SRDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { +func (d *srDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -198,8 +166,8 @@ func (d *SRDataSource) Configure(_ context.Context, req datasource.ConfigureRequ } // Read refreshes the Terraform state with the latest data. -func (d *SRDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data SRDataSourceModel +func (d *srDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data srDataSourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -214,8 +182,8 @@ func (d *SRDataSource) Read(ctx context.Context, req datasource.ReadRequest, res return } - var srItems []SRRecordData - var diags diag.Diagnostics + var srItems []srRecordData + for _, srRecord := range srRecords { if !data.NameLabel.IsNull() && srRecord.NameLabel != data.NameLabel.ValueString() { continue @@ -223,85 +191,16 @@ func (d *SRDataSource) Read(ctx context.Context, req datasource.ReadRequest, res if !data.UUID.IsNull() && srRecord.UUID != data.UUID.ValueString() { continue } - var srData SRRecordData - srData.UUID = types.StringValue(srRecord.UUID) - srData.NameLabel = types.StringValue(srRecord.NameLabel) - srData.NameDescription = types.StringValue(srRecord.NameDescription) - srData.AllowedOperations, diags = types.ListValueFrom(ctx, types.StringType, srRecord.AllowedOperations) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR allowed operations", - err.Error(), - ) - return - } - srData.CurrentOperations, diags = types.MapValueFrom(ctx, types.StringType, srRecord.CurrentOperations) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR current operations", - err.Error(), - ) - return - } - srData.VDIs, diags = types.ListValueFrom(ctx, types.StringType, srRecord.VDIs) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR VDIs", - err.Error(), - ) - return - } - srData.PBDs, diags = types.ListValueFrom(ctx, types.StringType, srRecord.PBDs) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR PBDs", - err.Error(), - ) - return - } - srData.VirtualAllocation = types.Int64Value(int64(srRecord.VirtualAllocation)) - srData.PhysicalUtilisation = types.Int64Value(int64(srRecord.PhysicalUtilisation)) - srData.PhysicalSize = types.Int64Value(int64(srRecord.PhysicalSize)) - srData.Type = types.StringValue(srRecord.Type) - srData.ContentType = types.StringValue(srRecord.ContentType) - srData.Shared = types.BoolValue(srRecord.Shared) - srData.OtherConfig, diags = types.MapValueFrom(ctx, types.StringType, srRecord.OtherConfig) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR other config", - err.Error(), - ) - return - } - srData.Tags, diags = types.ListValueFrom(ctx, types.StringType, srRecord.Tags) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to Read SR Tags", - err.Error(), - ) - return - } - srData.SmConfig, diags = types.MapValueFrom(ctx, types.StringType, srRecord.SmConfig) - if diags.HasError() { - resp.Diagnostics.AddError( - "Unable to read SR SM config", - err.Error(), - ) - return - } - srData.Blobs, diags = types.MapValueFrom(ctx, types.StringType, srRecord.Blobs) - if diags.HasError() { + + var srData srRecordData + err = updateSRRecordData(ctx, srRecord, &srData) + if err != nil { resp.Diagnostics.AddError( - "Unable to read SR Blobs", + "Unable to update SR record data", err.Error(), ) return } - srData.LocalCacheEnabled = types.BoolValue(srRecord.LocalCacheEnabled) - srData.IntroducedBy = types.StringValue(string(srRecord.IntroducedBy)) - srData.Clustered = types.BoolValue(srRecord.Clustered) - srData.IsToolsSr = types.BoolValue(srRecord.IsToolsSr) - srItems = append(srItems, srData) } data.DataItems = srItems diff --git a/xenserver/sr_data_source_test.go b/xenserver/sr_data_source_test.go index e1cb943..1740c77 100644 --- a/xenserver/sr_data_source_test.go +++ b/xenserver/sr_data_source_test.go @@ -1,11 +1,20 @@ package xenserver import ( + "fmt" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +func testAccSRDataSourceConfig(name_label string) string { + return fmt.Sprintf(` +data "xenserver_sr" "test_sr_data" { + name_label = "%s" +} +`, name_label) +} + func TestAccSRDataSource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, diff --git a/xenserver/sr_utils.go b/xenserver/sr_utils.go new file mode 100644 index 0000000..5cfabe7 --- /dev/null +++ b/xenserver/sr_utils.go @@ -0,0 +1,92 @@ +package xenserver + +import ( + "context" + "errors" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + + "xenapi" +) + +// srDataSourceModel describes the data source data model. +type srDataSourceModel struct { + NameLabel types.String `tfsdk:"name_label"` + UUID types.String `tfsdk:"uuid"` + DataItems []srRecordData `tfsdk:"data_items"` +} + +type srRecordData struct { + UUID types.String `tfsdk:"uuid"` + NameLabel types.String `tfsdk:"name_label"` + NameDescription types.String `tfsdk:"name_description"` + AllowedOperations types.List `tfsdk:"allowed_operations"` + CurrentOperations types.Map `tfsdk:"current_operations"` + VDIs types.List `tfsdk:"vdis"` + PBDs types.List `tfsdk:"pbds"` + VirtualAllocation types.Int64 `tfsdk:"virtual_allocation"` + PhysicalUtilisation types.Int64 `tfsdk:"physical_utilisation"` + PhysicalSize types.Int64 `tfsdk:"physical_size"` + Type types.String `tfsdk:"type"` + ContentType types.String `tfsdk:"content_type"` + Shared types.Bool `tfsdk:"shared"` + OtherConfig types.Map `tfsdk:"other_config"` + Tags types.List `tfsdk:"tags"` + SmConfig types.Map `tfsdk:"sm_config"` + Blobs types.Map `tfsdk:"blobs"` + LocalCacheEnabled types.Bool `tfsdk:"local_cache_enabled"` + IntroducedBy types.String `tfsdk:"introduced_by"` + Clustered types.Bool `tfsdk:"clustered"` + IsToolsSr types.Bool `tfsdk:"is_tools_sr"` +} + +func updateSRRecordData(ctx context.Context, record xenapi.SRRecord, data *srRecordData) error { + data.UUID = types.StringValue(record.UUID) + data.NameLabel = types.StringValue(record.NameLabel) + data.NameDescription = types.StringValue(record.NameDescription) + var diags diag.Diagnostics + data.AllowedOperations, diags = types.ListValueFrom(ctx, types.StringType, record.AllowedOperations) + if diags.HasError() { + return errors.New("unable to read SR allowed operations") + } + data.CurrentOperations, diags = types.MapValueFrom(ctx, types.StringType, record.CurrentOperations) + if diags.HasError() { + return errors.New("unable to read SR current operation") + } + data.VDIs, diags = types.ListValueFrom(ctx, types.StringType, record.VDIs) + if diags.HasError() { + return errors.New("unable to read SR VDIs") + } + data.PBDs, diags = types.ListValueFrom(ctx, types.StringType, record.PBDs) + if diags.HasError() { + return errors.New("unable to read SR PBDs") + } + data.VirtualAllocation = types.Int64Value(int64(record.VirtualAllocation)) + data.PhysicalUtilisation = types.Int64Value(int64(record.PhysicalUtilisation)) + data.PhysicalSize = types.Int64Value(int64(record.PhysicalSize)) + data.Type = types.StringValue(record.Type) + data.ContentType = types.StringValue(record.ContentType) + data.Shared = types.BoolValue(record.Shared) + data.OtherConfig, diags = types.MapValueFrom(ctx, types.StringType, record.OtherConfig) + if diags.HasError() { + return errors.New("unable to read SR other config") + } + data.Tags, diags = types.ListValueFrom(ctx, types.StringType, record.Tags) + if diags.HasError() { + return errors.New("unable to read SR tags") + } + data.SmConfig, diags = types.MapValueFrom(ctx, types.StringType, record.SmConfig) + if diags.HasError() { + return errors.New("unable to read SR SM config") + } + data.Blobs, diags = types.MapValueFrom(ctx, types.StringType, record.Blobs) + if diags.HasError() { + return errors.New("unable to read SR blobs") + } + data.LocalCacheEnabled = types.BoolValue(record.LocalCacheEnabled) + data.IntroducedBy = types.StringValue(string(record.IntroducedBy)) + data.Clustered = types.BoolValue(record.Clustered) + data.IsToolsSr = types.BoolValue(record.IsToolsSr) + return nil +} diff --git a/xenserver/test_config.go b/xenserver/test_config.go deleted file mode 100644 index ec63e02..0000000 --- a/xenserver/test_config.go +++ /dev/null @@ -1,45 +0,0 @@ -package xenserver - -import ( - "fmt" - "os" -) - -var ( - providerConfig = fmt.Sprintf(` -provider "xenserver" { - host = "%s" - username = "%s" - password = "%s" -} -`, os.Getenv("XENSERVER_HOST"), os.Getenv("XENSERVER_USERNAME"), os.Getenv("XENSERVER_PASSWORD")) -) - -func testAccPifDataSourceConfig(device string) string { - return fmt.Sprintf(` -data "xenserver_pif" "test_pif_data" { - device = "%s" - management = true -} -`, device) -} - -func testAccVMResourceConfig(name_label string) string { - return fmt.Sprintf(` -resource "xenserver_vm" "test_vm" { - name_label = "%s" - template_name = "CentOS 7" - other_config = { - flag = "1" - } -} -`, name_label) -} - -func testAccSRDataSourceConfig(name_label string) string { - return fmt.Sprintf(` -data "xenserver_sr" "test_sr_data" { - name_label = "%s" -} -`, name_label) -} diff --git a/xenserver/vm_resouce.go b/xenserver/vm_resouce.go index ac51326..b0be724 100644 --- a/xenserver/vm_resouce.go +++ b/xenserver/vm_resouce.go @@ -19,36 +19,27 @@ import ( // Ensure provider defined types fully satisfy framework interfaces. var ( - _ resource.Resource = &VMResource{} - _ resource.ResourceWithConfigure = &VMResource{} - _ resource.ResourceWithImportState = &VMResource{} + _ resource.Resource = &vmResource{} + _ resource.ResourceWithConfigure = &vmResource{} + _ resource.ResourceWithImportState = &vmResource{} ) func NewVMResource() resource.Resource { - return &VMResource{} + return &vmResource{} } -// VMResource defines the resource implementation. -type VMResource struct { +// vmResource defines the resource implementation. +type vmResource struct { session *xenapi.Session } -// VMResourceModel describes the resource data model. -type VMResourceModel struct { - NameLabel types.String `tfsdk:"name_label"` - TemplateName types.String `tfsdk:"template_name"` - OtherConfig types.Map `tfsdk:"other_config"` - Snapshots types.List `tfsdk:"snapshots"` - UUID types.String `tfsdk:"id"` -} - // Set the resource name -func (r *VMResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *vmResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_vm" } -// Set the defined datamodel of the resource -func (r *VMResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { +// Set the defined data model of the resource +func (r *vmResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ // This description is used by the documentation generator and the language server. MarkdownDescription: "VM resource", @@ -92,7 +83,7 @@ func (r *VMResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *r } // Set the parameter of the resource, pass value from provider -func (r *VMResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *vmResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return @@ -110,48 +101,48 @@ func (r *VMResource) Configure(_ context.Context, req resource.ConfigureRequest, // Read data from Plan, create resource, get data from new source, set to State // terraform plan/apply -func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data VMResourceModel +func (r *vmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data vmResourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - // create new reource + // create new resource tflog.Debug(ctx, "Get a template") - templateRef, err := GetFirstTemplate(r.session, data.TemplateName.ValueString()) + templateRef, err := getFirstTemplate(r.session, data.TemplateName.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error get template Ref", - "Could not find a template Ref, unexpected error: "+err.Error(), + "Unable to get template Ref", + err.Error(), ) return } - tflog.Debug(ctx, "Clone vm from a template") + tflog.Debug(ctx, "Clone VM from a template") vmRef, err := xenapi.VM.Clone(r.session, templateRef, data.NameLabel.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error clone vm from template", - "Could not clone vm, unexpected error: "+err.Error(), + "Unable to clone VM from template", + err.Error(), ) return } // Set some configure field - otherConfig, err := GetVMOtherConfig(ctx, data) + otherConfig, err := getVMOtherConfig(ctx, data) if err != nil { resp.Diagnostics.AddError( - "Error on other config", - "Unexpected error: "+err.Error(), + "Unable to get VM other config", + err.Error(), ) return } err = xenapi.VM.SetOtherConfig(r.session, vmRef, otherConfig) if err != nil { resp.Diagnostics.AddError( - "Error set other config", - "Could not set other config, unexpected error: "+err.Error(), + "Unable to set VM other config", + err.Error(), ) return } @@ -160,17 +151,17 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res vmRecord, err := xenapi.VM.GetRecord(r.session, vmRef) if err != nil { resp.Diagnostics.AddError( - "Error get vm record", - "Could not get vm record, unexpected error: "+err.Error(), + "Unable to get VM record", + err.Error(), ) return } // Set all computed values data.UUID = types.StringValue(vmRecord.UUID) - err = UpdateVMResourceModelComputed(ctx, vmRecord, &data) + err = updateVMResourceModelComputed(ctx, vmRecord, &data) if err != nil { resp.Diagnostics.AddError( - "Error update data", + "Unable to update VM resource model computed fields", err.Error(), ) return @@ -178,7 +169,7 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a vm resource") + tflog.Trace(ctx, "VM created") // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -186,8 +177,8 @@ func (r *VMResource) Create(ctx context.Context, req resource.CreateRequest, res // Read data from State, retrieve the resource's information, update to State // terraform import -func (r *VMResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data VMResourceModel +func (r *vmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data vmResourceModel // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -198,23 +189,23 @@ func (r *VMResource) Read(ctx context.Context, req resource.ReadRequest, resp *r vmRef, err := xenapi.VM.GetByUUID(r.session, data.UUID.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error get vm ref", - "Could not get vm ref, unexpected error: "+err.Error(), + "Unable to get VM ref", + err.Error(), ) return } vmRecord, err := xenapi.VM.GetRecord(r.session, vmRef) if err != nil { resp.Diagnostics.AddError( - "Error get vm record", - "Could not get vm record, unexpected error: "+err.Error(), + "Unable to get VM record", + err.Error(), ) return } - err = UpdateVMResourceModel(ctx, vmRecord, &data) + err = updateVMResourceModel(ctx, vmRecord, &data) if err != nil { resp.Diagnostics.AddError( - "Error update data", + "Unable to update VM resource model data", err.Error(), ) return @@ -225,22 +216,22 @@ func (r *VMResource) Read(ctx context.Context, req resource.ReadRequest, resp *r // Read data from Plan, update resource configuration, Set to State // terraform plan/apply (+2) -func (r *VMResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data VMResourceModel +func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data vmResourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - var dataState VMResourceModel + var dataState vmResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &dataState)...) if resp.Diagnostics.HasError() { return } if data.TemplateName != dataState.TemplateName { resp.Diagnostics.AddError( - "Error change template name", + "Unable to change template name", "The template name doesn't expected to be updated", ) return @@ -250,8 +241,8 @@ func (r *VMResource) Update(ctx context.Context, req resource.UpdateRequest, res vmRef, err := xenapi.VM.GetByUUID(r.session, data.UUID.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error get vm ref", - "Could not get vm ref, unexpected error: "+err.Error(), + "Unable to get VM ref", + err.Error(), ) return } @@ -259,24 +250,24 @@ func (r *VMResource) Update(ctx context.Context, req resource.UpdateRequest, res err = xenapi.VM.SetNameLabel(r.session, vmRef, data.NameLabel.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error set name label", - "Could not set name label, unexpected error: "+err.Error(), + "Unable to set VM name label", + err.Error(), ) return } - otherConfig, err := GetVMOtherConfig(ctx, data) + otherConfig, err := getVMOtherConfig(ctx, data) if err != nil { resp.Diagnostics.AddError( - "Error on other config", - "Unexpected error: "+err.Error(), + "Unable to get VM other config", + err.Error(), ) return } err = xenapi.VM.SetOtherConfig(r.session, vmRef, otherConfig) if err != nil { resp.Diagnostics.AddError( - "Error set other config", - "Could not set other config, unexpected error: "+err.Error(), + "Unable to set VM other config", + err.Error(), ) return } @@ -285,15 +276,15 @@ func (r *VMResource) Update(ctx context.Context, req resource.UpdateRequest, res vmRecord, err := xenapi.VM.GetRecord(r.session, vmRef) if err != nil { resp.Diagnostics.AddError( - "Error get vm record", - "Could not get vm record, unexpected error: "+err.Error(), + "Unable to get VM record", + err.Error(), ) return } - err = UpdateVMResourceModelComputed(ctx, vmRecord, &data) + err = updateVMResourceModelComputed(ctx, vmRecord, &data) if err != nil { resp.Diagnostics.AddError( - "Error update data", + "Unable to update VM resource model computed fields", err.Error(), ) return @@ -305,8 +296,8 @@ func (r *VMResource) Update(ctx context.Context, req resource.UpdateRequest, res // Read data from State, delete resource // terraform destroy -func (r *VMResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data VMResourceModel +func (r *vmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data vmResourceModel // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -317,16 +308,16 @@ func (r *VMResource) Delete(ctx context.Context, req resource.DeleteRequest, res vmRef, err := xenapi.VM.GetByUUID(r.session, data.UUID.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Error get vm ref", - "Could not get vm ref, unexpected error: "+err.Error(), + "Unable to get VM ref", + err.Error(), ) return } err = xenapi.VM.Destroy(r.session, vmRef) if err != nil { resp.Diagnostics.AddError( - "Error destroy vm", - "Could not destroy vm, unexpected error: "+err.Error(), + "Unable to destroy VM", + err.Error(), ) return } @@ -334,6 +325,6 @@ func (r *VMResource) Delete(ctx context.Context, req resource.DeleteRequest, res // Import existing resource with id, call Read() // terraform import -func (r *VMResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *vmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/xenserver/vm_resource_test.go b/xenserver/vm_resource_test.go index d81013d..5995f77 100644 --- a/xenserver/vm_resource_test.go +++ b/xenserver/vm_resource_test.go @@ -1,11 +1,24 @@ package xenserver import ( + "fmt" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +func testAccVMResourceConfig(name_label string) string { + return fmt.Sprintf(` +resource "xenserver_vm" "test_vm" { + name_label = "%s" + template_name = "CentOS 7" + other_config = { + flag = "1" + } +} +`, name_label) +} + func TestAccVMResource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, diff --git a/xenserver/utils.go b/xenserver/vm_utils.go similarity index 54% rename from xenserver/utils.go rename to xenserver/vm_utils.go index f6dc048..3ecbd41 100644 --- a/xenserver/utils.go +++ b/xenserver/vm_utils.go @@ -11,7 +11,16 @@ import ( "xenapi" ) -func GetFirstTemplate(session *xenapi.Session, templateName string) (xenapi.VMRef, error) { +// vmResourceModel describes the resource data model. +type vmResourceModel struct { + NameLabel types.String `tfsdk:"name_label"` + TemplateName types.String `tfsdk:"template_name"` + OtherConfig types.Map `tfsdk:"other_config"` + Snapshots types.List `tfsdk:"snapshots"` + UUID types.String `tfsdk:"id"` +} + +func getFirstTemplate(session *xenapi.Session, templateName string) (xenapi.VMRef, error) { records, err := xenapi.VM.GetAllRecords(session) if err != nil { return "", errors.New(err.Error()) @@ -22,45 +31,45 @@ func GetFirstTemplate(session *xenapi.Session, templateName string) (xenapi.VMRe return ref, nil } } - return "", errors.New("no VM template found") + return "", errors.New("unable to find VM template ref") } -// Get VMResourceModel OtherConfig base on data -func GetVMOtherConfig(ctx context.Context, data VMResourceModel) (map[string]string, error) { +// Get vmResourceModel OtherConfig base on data +func getVMOtherConfig(ctx context.Context, data vmResourceModel) (map[string]string, error) { otherConfig := make(map[string]string) if !data.OtherConfig.IsNull() { diags := data.OtherConfig.ElementsAs(ctx, &otherConfig, false) if diags.HasError() { - return nil, errors.New("error accessing vm other_config") + return nil, errors.New("unable to read VM other config") } } otherConfig["template_name"] = data.TemplateName.ValueString() return otherConfig, nil } -// Update VMResourceModel base on new vmRecord, except uuid -func UpdateVMResourceModel(ctx context.Context, vmRecord xenapi.VMRecord, data *VMResourceModel) error { +// Update vmResourceModel base on new vmRecord, except uuid +func updateVMResourceModel(ctx context.Context, vmRecord xenapi.VMRecord, data *vmResourceModel) error { data.NameLabel = types.StringValue(vmRecord.NameLabel) data.TemplateName = types.StringValue(vmRecord.OtherConfig["template_name"]) var diags diag.Diagnostics delete(vmRecord.OtherConfig, "template_name") data.OtherConfig, diags = types.MapValueFrom(ctx, types.StringType, vmRecord.OtherConfig) if diags.HasError() { - return errors.New("error update data for vm other_config") + return errors.New("unable to read VM other config") } - err := UpdateVMResourceModelComputed(ctx, vmRecord, data) + err := updateVMResourceModelComputed(ctx, vmRecord, data) if err != nil { return err } return nil } -// Update VMResourceModel computed field base on new vmRecord, except uuid -func UpdateVMResourceModelComputed(ctx context.Context, vmRecord xenapi.VMRecord, data *VMResourceModel) error { +// Update vmResourceModel computed field base on new vmRecord, except uuid +func updateVMResourceModelComputed(ctx context.Context, vmRecord xenapi.VMRecord, data *vmResourceModel) error { var diags diag.Diagnostics data.Snapshots, diags = types.ListValueFrom(ctx, types.StringType, vmRecord.Snapshots) if diags.HasError() { - return errors.New("error update data for vm snaphots") + return errors.New("unable to read VM snapshots") } return nil }