diff --git a/hcloud/plugin_provider.go b/hcloud/plugin_provider.go index c4f5b12ee..3c2048f69 100644 --- a/hcloud/plugin_provider.go +++ b/hcloud/plugin_provider.go @@ -19,6 +19,7 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" "github.com/hetznercloud/terraform-provider-hcloud/internal/datacenter" + "github.com/hetznercloud/terraform-provider-hcloud/internal/loadbalancertype" "github.com/hetznercloud/terraform-provider-hcloud/internal/location" "github.com/hetznercloud/terraform-provider-hcloud/internal/servertype" "github.com/hetznercloud/terraform-provider-hcloud/internal/sshkey" @@ -171,6 +172,8 @@ func (p *PluginProvider) DataSources(_ context.Context) []func() datasource.Data return []func() datasource.DataSource{ datacenter.NewDataSource, datacenter.NewDataSourceList, + loadbalancertype.NewDataSource, + loadbalancertype.NewDataSourceList, location.NewDataSource, location.NewDataSourceList, servertype.NewDataSource, diff --git a/internal/loadbalancertype/data_source.go b/internal/loadbalancertype/data_source.go new file mode 100644 index 000000000..89951da3b --- /dev/null +++ b/internal/loadbalancertype/data_source.go @@ -0,0 +1,295 @@ +package loadbalancertype + +import ( + "context" + _ "embed" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/path" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hetznercloud/hcloud-go/hcloud" + "github.com/hetznercloud/terraform-provider-hcloud/internal/util/datasourceutil" + "github.com/hetznercloud/terraform-provider-hcloud/internal/util/hcloudutil" +) + +const ( + // DataSourceType is the type name of the Hetzner Cloud load balancer type datasource. + DataSourceType = "hcloud_load_balancer_type" + + // DataSourceListType is the type name of the Hetzner Cloud load balancer type list datasource. + DataSourceListType = "hcloud_load_balancer_types" +) + +type resourceData struct { + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + MaxAssignedCertificates types.Int64 `tfsdk:"max_assigned_certificates"` + MaxConnections types.Int64 `tfsdk:"max_connections"` + MaxServices types.Int64 `tfsdk:"max_services"` + MaxTargets types.Int64 `tfsdk:"max_targets"` +} + +var resourceDataAttrTypes = map[string]attr.Type{ + "id": types.Int64Type, + "name": types.StringType, + "description": types.StringType, + "max_assigned_certificates": types.Int64Type, + "max_connections": types.Int64Type, + "max_services": types.Int64Type, + "max_targets": types.Int64Type, +} + +func newResourceData(_ context.Context, in *hcloud.LoadBalancerType) (resourceData, diag.Diagnostics) { //nolint:unparam + var data resourceData + var diags diag.Diagnostics + + data.ID = types.Int64Value(int64(in.ID)) + data.Name = types.StringValue(in.Name) + data.Description = types.StringValue(in.Description) + + data.MaxAssignedCertificates = types.Int64Value(int64(in.MaxAssignedCertificates)) + data.MaxConnections = types.Int64Value(int64(in.MaxConnections)) + data.MaxServices = types.Int64Value(int64(in.MaxServices)) + data.MaxTargets = types.Int64Value(int64(in.MaxTargets)) + + return data, diags +} + +func getCommonDataSchema() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + Computed: true, + }, + "max_assigned_certificates": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "max_connections": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "max_services": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + "max_targets": schema.Int64Attribute{ + Optional: true, + Computed: true, + }, + } +} + +// Single +var _ datasource.DataSource = (*dataSource)(nil) +var _ datasource.DataSourceWithConfigure = (*dataSource)(nil) +var _ datasource.DataSourceWithConfigValidators = (*dataSource)(nil) + +type dataSource struct { + client *hcloud.Client +} + +func NewDataSource() datasource.DataSource { + return &dataSource{} +} + +// Metadata should return the full name of the data source. +func (d *dataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = DataSourceType +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined DataSource type. It is separately executed for each +// ReadDataSource RPC. +func (d *dataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var newDiags diag.Diagnostics + + d.client, newDiags = hcloudutil.ConfigureClient(req.ProviderData) + resp.Diagnostics.Append(newDiags...) + if resp.Diagnostics.HasError() { + return + } +} + +//go:embed data_source.md +var dataSourceMarkdownDescription string + +// Schema should return the schema for this data source. +func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema.Attributes = getCommonDataSchema() + resp.Schema.MarkdownDescription = dataSourceMarkdownDescription +} + +// ConfigValidators returns a list of ConfigValidators. Each ConfigValidator's Validate method will be called when validating the data source. +func (d *dataSource) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("id"), + path.MatchRoot("name"), + ), + } +} + +// Read is called when the provider must read data source values in +// order to update state. Config values should be read from the +// ReadRequest and new state values set on the ReadResponse. +func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data resourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var result *hcloud.LoadBalancerType + var err error + + switch { + case !data.ID.IsNull(): + result, _, err = d.client.LoadBalancerType.GetByID(ctx, int(data.ID.ValueInt64())) + if err != nil { + resp.Diagnostics.Append(hcloudutil.APIErrorDiagnostics(err)...) + return + } + if result == nil { + resp.Diagnostics.Append(hcloudutil.NotFoundDiagnostic("load balancer type", "id", data.ID.String())) + return + } + case !data.Name.IsNull(): + result, _, err = d.client.LoadBalancerType.GetByName(ctx, data.Name.ValueString()) + if err != nil { + resp.Diagnostics.Append(hcloudutil.APIErrorDiagnostics(err)...) + return + } + if result == nil { + resp.Diagnostics.Append(hcloudutil.NotFoundDiagnostic("load balancer type", "name", data.Name.String())) + return + } + default: + // Should not happen, see [dataSource.ConfigValidators] + resp.Diagnostics.AddError("Unexpected internal error", "") + return + } + + data, diags := newResourceData(ctx, result) + resp.Diagnostics.Append(diags...) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// List +var _ datasource.DataSource = (*dataSourceList)(nil) +var _ datasource.DataSourceWithConfigure = (*dataSourceList)(nil) + +type dataSourceList struct { + client *hcloud.Client +} + +func NewDataSourceList() datasource.DataSource { + return &dataSourceList{} +} + +// Metadata should return the full name of the data source. +func (d *dataSourceList) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = DataSourceListType +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined DataSource type. It is separately executed for each +// ReadDataSource RPC. +func (d *dataSourceList) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var newDiags diag.Diagnostics + + d.client, newDiags = hcloudutil.ConfigureClient(req.ProviderData) + resp.Diagnostics.Append(newDiags...) + if resp.Diagnostics.HasError() { + return + } +} + +//go:embed data_source_list.md +var dataSourceListMarkdownDescription string + +// Schema should return the schema for this data source. +func (d *dataSourceList) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema.Attributes = map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + }, + "load_balancer_types": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: getCommonDataSchema(), + }, + Computed: true, + }, + } + + resp.Schema.MarkdownDescription = dataSourceListMarkdownDescription +} + +type resourceDataList struct { + ID types.String `tfsdk:"id"` + LoadBalancerTypes types.List `tfsdk:"load_balancer_types"` +} + +func newResourceDataList(ctx context.Context, in []*hcloud.LoadBalancerType) (resourceDataList, diag.Diagnostics) { + var data resourceDataList + var diags diag.Diagnostics + var newDiags diag.Diagnostics + + ids := make([]int64, len(in)) + tfItems := make([]resourceData, len(in)) + for i, item := range in { + ids[i] = int64(item.ID) + + tfItem, newDiags := newResourceData(ctx, item) + diags.Append(newDiags...) + tfItems[i] = tfItem + } + + data.ID = types.StringValue(datasourceutil.ListID(ids)) + + data.LoadBalancerTypes, newDiags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: resourceDataAttrTypes}, tfItems) + diags.Append(newDiags...) + + return data, diags +} + +// Read is called when the provider must read data source values in +// order to update state. Config values should be read from the +// ReadRequest and new state values set on the ReadResponse. +func (d *dataSourceList) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data resourceDataList + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var result []*hcloud.LoadBalancerType + var err error + + result, err = d.client.LoadBalancerType.All(ctx) + if err != nil { + resp.Diagnostics.Append(hcloudutil.APIErrorDiagnostics(err)...) + return + } + + data, diags := newResourceDataList(ctx, result) + resp.Diagnostics.Append(diags...) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/loadbalancertype/data_source.md b/internal/loadbalancertype/data_source.md new file mode 100644 index 000000000..841bfc637 --- /dev/null +++ b/internal/loadbalancertype/data_source.md @@ -0,0 +1,15 @@ +Provides details about a specific Hetzner Cloud Load Balancer Type. + +Use this resource to get detailed information about specific Load Balancer Type. + +## Example Usage + +```hcl +data "hcloud_load_balancer_type" "by_name" { + name = "lb11" +} + +data "hcloud_load_balancer_type" "by_id" { + id = 1 +} +``` diff --git a/internal/loadbalancertype/data_source_list.md b/internal/loadbalancertype/data_source_list.md new file mode 100644 index 000000000..256437247 --- /dev/null +++ b/internal/loadbalancertype/data_source_list.md @@ -0,0 +1,7 @@ +Provides a list of available Hetzner Cloud Load Balancer Types. + +## Example Usage + +```hcl +data "hcloud_load_balancer_types" "all" {} +``` diff --git a/internal/loadbalancertype/data_source_test.go b/internal/loadbalancertype/data_source_test.go new file mode 100644 index 000000000..80b01af71 --- /dev/null +++ b/internal/loadbalancertype/data_source_test.go @@ -0,0 +1,81 @@ +package loadbalancertype_test + +import ( + "testing" + + "github.com/hetznercloud/terraform-provider-hcloud/internal/loadbalancertype" + "github.com/hetznercloud/terraform-provider-hcloud/internal/teste2e" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hetznercloud/terraform-provider-hcloud/internal/testtemplate" +) + +func TestAccDataSource(t *testing.T) { + tmplMan := testtemplate.Manager{} + + byName := &loadbalancertype.DData{LoadBalancerTypeName: teste2e.TestLoadBalancerType} + byName.SetRName("by_name") + + byID := &loadbalancertype.DData{LoadBalancerTypeID: "1"} + byID.SetRName("by_id") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: teste2e.PreCheck(t), + ProtoV6ProviderFactories: teste2e.ProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: tmplMan.Render(t, + "testdata/d/hcloud_load_balancer_type", byName, + "testdata/d/hcloud_load_balancer_type", byID, + ), + + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(byName.TFID(), "id", "1"), + resource.TestCheckResourceAttr(byName.TFID(), "name", "lb11"), + resource.TestCheckResourceAttr(byName.TFID(), "description", "LB11"), + resource.TestCheckResourceAttr(byName.TFID(), "max_assigned_certificates", "10"), + resource.TestCheckResourceAttr(byName.TFID(), "max_connections", "10000"), + resource.TestCheckResourceAttr(byName.TFID(), "max_services", "5"), + resource.TestCheckResourceAttr(byName.TFID(), "max_targets", "25"), + + resource.TestCheckResourceAttr(byID.TFID(), "id", "1"), + resource.TestCheckResourceAttr(byID.TFID(), "name", "lb11"), + resource.TestCheckResourceAttr(byID.TFID(), "description", "LB11"), + resource.TestCheckResourceAttr(byID.TFID(), "max_assigned_certificates", "10"), + resource.TestCheckResourceAttr(byID.TFID(), "max_connections", "10000"), + resource.TestCheckResourceAttr(byID.TFID(), "max_services", "5"), + resource.TestCheckResourceAttr(byID.TFID(), "max_targets", "25"), + ), + }, + }, + }) +} + +func TestAccDataSourceList(t *testing.T) { + tmplMan := testtemplate.Manager{} + + all := &loadbalancertype.DDataList{} + all.SetRName("all") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: teste2e.PreCheck(t), + ProtoV6ProviderFactories: teste2e.ProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: tmplMan.Render(t, + "testdata/d/hcloud_load_balancer_types", all, + ), + + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.id", "1"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.name", "lb11"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.description", "LB11"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.max_assigned_certificates", "10"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.max_connections", "10000"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.max_services", "5"), + resource.TestCheckResourceAttr(all.TFID(), "load_balancer_types.0.max_targets", "25"), + ), + }, + }, + }) +} diff --git a/internal/loadbalancertype/testing.go b/internal/loadbalancertype/testing.go new file mode 100644 index 000000000..47e35fe49 --- /dev/null +++ b/internal/loadbalancertype/testing.go @@ -0,0 +1,32 @@ +package loadbalancertype + +import ( + "fmt" + + "github.com/hetznercloud/terraform-provider-hcloud/internal/testtemplate" +) + +// DData defines the fields for the "testdata/d/hcloud_load_balancer_type" +// template. +type DData struct { + testtemplate.DataCommon + + LoadBalancerTypeID string + LoadBalancerTypeName string +} + +// TFID returns the data source identifier. +func (d *DData) TFID() string { + return fmt.Sprintf("data.%s.%s", DataSourceType, d.RName()) +} + +// DDataList defines the fields for the "testdata/d/hcloud_load_balancer_types" +// template. +type DDataList struct { + testtemplate.DataCommon +} + +// TFID returns the data source identifier. +func (d *DDataList) TFID() string { + return fmt.Sprintf("data.%s.%s", DataSourceListType, d.RName()) +} diff --git a/internal/testdata/d/hcloud_load_balancer_type.tf.tmpl b/internal/testdata/d/hcloud_load_balancer_type.tf.tmpl new file mode 100644 index 000000000..7ad449d04 --- /dev/null +++ b/internal/testdata/d/hcloud_load_balancer_type.tf.tmpl @@ -0,0 +1,6 @@ +{{- /* vim: set ft=terraform: */ -}} + +data "hcloud_load_balancer_type" "{{ .RName }}" { + {{ if .LoadBalancerTypeID -}} id = "{{ .LoadBalancerTypeID }}"{{ end }} + {{ if .LoadBalancerTypeName -}} name = "{{ .LoadBalancerTypeName }}"{{ end }} +} diff --git a/internal/testdata/d/hcloud_load_balancer_types.tf.tmpl b/internal/testdata/d/hcloud_load_balancer_types.tf.tmpl new file mode 100644 index 000000000..0f2761be5 --- /dev/null +++ b/internal/testdata/d/hcloud_load_balancer_types.tf.tmpl @@ -0,0 +1,3 @@ +{{- /* vim: set ft=terraform: */ -}} + +data "hcloud_load_balancer_types" "{{ .RName }}" {} diff --git a/website/docs/d/load_balancer_type.html.md b/website/docs/d/load_balancer_type.html.md new file mode 100644 index 000000000..feaad3c6f --- /dev/null +++ b/website/docs/d/load_balancer_type.html.md @@ -0,0 +1,45 @@ +--- +layout: "hcloud" +page_title: "Hetzner Cloud: hcloud_load_balancer_type" +sidebar_current: "docs-hcloud-datasource-load-balancer-type" +description: |- + Provides details about a specific Hetzner Cloud Load Balancer Type. +--- + +# Data Source: hcloud_load_balancer_type + +Provides details about a specific Hetzner Cloud Load Balancer Type. +Use this resource to get detailed information about specific Load Balancer Type. + +## Example Usage + +```hcl +data "hcloud_load_balancer_type" "by_name" { + name = "cx22" +} + +data "hcloud_load_balancer_type" "by_id" { + id = 1 +} + +resource "hcloud_load_balancer" "load_balancer" { + name = "my-load-balancer" + load_balancer_type = data.hcloud_load_balancer_type.name + location = "nbg1" +} +``` + +## Argument Reference + +- `id` - (Optional, string) ID of the load_balancer_type. +- `name` - (Optional, string) Name of the load_balancer_type. + +## Attributes Reference + +- `id` - (int) Unique ID of the load_balancer_type. +- `name` - (string) Name of the load_balancer_type. +- `description` - (string) Description of the load_balancer_type. +- `max_assigned_certificates` - (int) Maximum number of SSL Certificates that can be assigned to the Load Balancer of this type. +- `max_connections` - (int) Maximum number of simultaneous open connections for the Load Balancer of this type. +- `max_services` - (int) Maximum number of services for the Load Balancer of this type. +- `max_targets` - (int) Maximum number of targets for the Load Balancer of this type. diff --git a/website/docs/d/load_balancer_types.html.md b/website/docs/d/load_balancer_types.html.md new file mode 100644 index 000000000..d012f905e --- /dev/null +++ b/website/docs/d/load_balancer_types.html.md @@ -0,0 +1,21 @@ +--- +layout: "hcloud" +page_title: "Hetzner Cloud: hcloud_load_balancer_types" +sidebar_current: "docs-hcloud-datasource-load-balancer-types" +description: |- + List all available Hetzner Cloud Load Balancer Types. +--- + +# Data Source: hcloud_load_balancer_types + +Provides a list of available Hetzner Cloud Load Balancer Types. + +## Example Usage + +```hcl +data "hcloud_load_balancer_types" "all" {} +``` + +## Attributes Reference + +- `load_balancer_types` - (list) List of all load balancer types. See `data.hcloud_load_balancer_type` for the schema. diff --git a/website/hcloud.erb b/website/hcloud.erb index f2138cd83..5c57698c2 100644 --- a/website/hcloud.erb +++ b/website/hcloud.erb @@ -48,6 +48,12 @@ > hcloud_images + > + hcloud_load_balancer_type + + > + hcloud_load_balancer_types + > hcloud_load_balancer