Skip to content

Commit 359bd6b

Browse files
authored
Merge pull request #23 from ScaleComputing/hypercore_remote_connection_data_source
Implement hypercore_remote_cluster_connection data source
2 parents 16fae8c + 5d89d11 commit 359bd6b

File tree

10 files changed

+313
-41
lines changed

10 files changed

+313
-41
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "hypercore_remote_cluster_connection Data Source - hypercore"
4+
subcategory: ""
5+
description: |-
6+
7+
---
8+
9+
# hypercore_remote_cluster_connection (Data Source)
10+
11+
12+
13+
## Example Usage
14+
15+
```terraform
16+
data "hypercore_remote_cluster_connection" "all_clusters" {}
17+
18+
data "hypercore_remote_cluster_connection" "cluster-a" {
19+
remote_cluster_name = "cluster-a"
20+
}
21+
22+
output "all_remote_clusters" {
23+
value = jsonencode(data.hypercore_remote_cluster_connection.all_clusters)
24+
}
25+
26+
output "filtered_remote_cluster" {
27+
value = jsonencode(data.hypercore_remote_cluster_connection.cluster-a)
28+
}
29+
```
30+
31+
<!-- schema generated by tfplugindocs -->
32+
## Schema
33+
34+
### Optional
35+
36+
- `remote_cluster_name` (String)
37+
38+
### Read-Only
39+
40+
- `remote_clusters` (Attributes List) (see [below for nested schema](#nestedatt--remote_clusters))
41+
42+
<a id="nestedatt--remote_clusters"></a>
43+
### Nested Schema for `remote_clusters`
44+
45+
Read-Only:
46+
47+
- `cluster_name` (String)
48+
- `connection_status` (String)
49+
- `remote_node_ips` (List of String)
50+
- `remote_node_uuids` (List of String)
51+
- `replication_ok` (Boolean)
52+
- `uuid` (String)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
data "hypercore_remote_cluster_connection" "all_clusters" {}
2+
3+
data "hypercore_remote_cluster_connection" "cluster-a" {
4+
remote_cluster_name = "cluster-a"
5+
}
6+
7+
output "all_remote_clusters" {
8+
value = jsonencode(data.hypercore_remote_cluster_connection.all_clusters)
9+
}
10+
11+
output "filtered_remote_cluster" {
12+
value = jsonencode(data.hypercore_remote_cluster_connection.cluster-a)
13+
}

internal/provider/hypercore_node_data_source.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ func (d *hypercoreNodeDataSource) Read(ctx context.Context, req datasource.ReadR
119119
"/rest/v1/Node",
120120
query,
121121
-1.0,
122+
false,
122123
)
123124
tflog.Info(ctx, fmt.Sprintf("TTRT: filter_peer_id=%v node_count=%d\n", filter_peer_id, len(hc3_nodes)))
124125

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
// https://developer.hashicorp.com/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-data-source-read
5+
// https://developer.hashicorp.com/terraform/plugin/framework/migrating
6+
7+
package provider
8+
9+
import (
10+
"context"
11+
"fmt"
12+
13+
"github.com/hashicorp/terraform-plugin-framework/attr"
14+
"github.com/hashicorp/terraform-plugin-framework/datasource"
15+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
"github.com/hashicorp/terraform-plugin-log/tflog"
18+
"github.com/hashicorp/terraform-provider-hypercore/internal/utils"
19+
)
20+
21+
// Ensure the implementation satisfies the expected interfaces.
22+
var (
23+
_ datasource.DataSource = &hypercoreRemoteClusterConnectionDataSource{}
24+
_ datasource.DataSourceWithConfigure = &hypercoreRemoteClusterConnectionDataSource{}
25+
)
26+
27+
// NewHypercoreRemoteClusterConnectionDataSource is a helper function to simplify the provider implementation.
28+
func NewHypercoreRemoteClusterConnectionDataSource() datasource.DataSource {
29+
return &hypercoreRemoteClusterConnectionDataSource{}
30+
}
31+
32+
// hypercoreRemoteClusterConnectionDataSource is the data source implementation.
33+
type hypercoreRemoteClusterConnectionDataSource struct {
34+
client *utils.RestClient
35+
}
36+
37+
// coffeesDataSourceModel maps the data source schema data.
38+
type hypercoreRemoteClusterConnectionsDataSourceModel struct {
39+
FilterRemoteClusterName types.String `tfsdk:"remote_cluster_name"`
40+
RemoteClusterConnections []hypercoreRemoteClusterConnectionModel `tfsdk:"remote_clusters"`
41+
}
42+
43+
// hypercoreVMModel maps VM schema data.
44+
type hypercoreRemoteClusterConnectionModel struct {
45+
UUID types.String `tfsdk:"uuid"`
46+
ClusterName types.String `tfsdk:"cluster_name"`
47+
ConnectionStatus types.String `tfsdk:"connection_status"`
48+
ReplicationOk types.Bool `tfsdk:"replication_ok"`
49+
RemoteNodeIPs types.List `tfsdk:"remote_node_ips"`
50+
RemoteNodeUUIDs types.List `tfsdk:"remote_node_uuids"`
51+
}
52+
53+
// Metadata returns the data source type name.
54+
func (d *hypercoreRemoteClusterConnectionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
55+
resp.TypeName = req.ProviderTypeName + "_remote_cluster_connection"
56+
}
57+
58+
// Schema defines the schema for the data source.
59+
func (d *hypercoreRemoteClusterConnectionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
60+
resp.Schema = schema.Schema{
61+
Attributes: map[string]schema.Attribute{
62+
"remote_cluster_name": schema.StringAttribute{
63+
Optional: true,
64+
},
65+
"remote_clusters": schema.ListNestedAttribute{
66+
Computed: true,
67+
NestedObject: schema.NestedAttributeObject{
68+
Attributes: map[string]schema.Attribute{
69+
"uuid": schema.StringAttribute{
70+
Computed: true,
71+
},
72+
"cluster_name": schema.StringAttribute{
73+
Computed: true,
74+
},
75+
"connection_status": schema.StringAttribute{
76+
Computed: true,
77+
},
78+
"replication_ok": schema.BoolAttribute{
79+
Computed: true,
80+
},
81+
"remote_node_ips": schema.ListAttribute{
82+
ElementType: types.StringType,
83+
Computed: true,
84+
},
85+
"remote_node_uuids": schema.ListAttribute{
86+
ElementType: types.StringType,
87+
Computed: true,
88+
},
89+
},
90+
},
91+
},
92+
},
93+
}
94+
}
95+
96+
// Configure adds the provider configured client to the data source.
97+
func (d *hypercoreRemoteClusterConnectionDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
98+
// Add a nil check when handling ProviderData because Terraform
99+
// sets that data after it calls the ConfigureProvider RPC.
100+
if req.ProviderData == nil {
101+
return
102+
}
103+
104+
restClient, ok := req.ProviderData.(*utils.RestClient)
105+
if !ok {
106+
resp.Diagnostics.AddError(
107+
"Unexpected Data Source Configure Type",
108+
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
109+
)
110+
return
111+
}
112+
113+
d.client = restClient
114+
}
115+
116+
// Read refreshes the Terraform state with the latest data.
117+
func (d *hypercoreRemoteClusterConnectionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
118+
var conf hypercoreRemoteClusterConnectionsDataSourceModel
119+
req.Config.Get(ctx, &conf)
120+
121+
filterName := conf.FilterRemoteClusterName.ValueString()
122+
123+
query := map[string]any{}
124+
if filterName != "" {
125+
query = map[string]any{
126+
"remoteClusterInfo": map[string]any{
127+
"clusterName": filterName,
128+
},
129+
}
130+
}
131+
132+
hc3RemoteClusters := d.client.ListRecords(
133+
"/rest/v1/RemoteClusterConnection",
134+
query,
135+
-1.0,
136+
true,
137+
)
138+
139+
tflog.Info(ctx, fmt.Sprintf("TTRT: filter_name=%v remote_cluster_count=%d\n", filterName, len(hc3RemoteClusters)))
140+
141+
var state hypercoreRemoteClusterConnectionsDataSourceModel
142+
for _, remoteCluster := range hc3RemoteClusters {
143+
remoteClusterInfo := utils.AnyToMap(remoteCluster["remoteClusterInfo"])
144+
145+
remoteNodeIPs := utils.AnyToListOfStrings(remoteCluster["remoteNodeIPs"])
146+
remoteNodeUUIDs := utils.AnyToListOfStrings(remoteCluster["remoteNodeUUIDs"])
147+
148+
// Go list of string to Terraform list of string
149+
remoteNodeIPsValues := make([]attr.Value, len(remoteNodeIPs))
150+
for i, remoteIP := range remoteNodeIPs {
151+
remoteNodeIPsValues[i] = types.StringValue(remoteIP)
152+
}
153+
remoteNodeUUIDsValues := make([]attr.Value, len(remoteNodeUUIDs))
154+
for i, remoteUUID := range remoteNodeUUIDs {
155+
remoteNodeUUIDsValues[i] = types.StringValue(remoteUUID)
156+
}
157+
158+
tfRemoteNodeIPs, _diag := types.ListValue(types.StringType, remoteNodeIPsValues)
159+
if _diag.HasError() {
160+
resp.Diagnostics.Append(_diag...)
161+
return
162+
}
163+
tfRemoteNodeUUIDs, _diag := types.ListValue(types.StringType, remoteNodeUUIDsValues)
164+
if _diag.HasError() {
165+
resp.Diagnostics.Append(_diag...)
166+
return
167+
}
168+
169+
// Save into state
170+
hypercoreRemoteClusterConnectionState := hypercoreRemoteClusterConnectionModel{
171+
UUID: types.StringValue(utils.AnyToString(remoteCluster["uuid"])),
172+
ClusterName: types.StringValue(utils.AnyToString(remoteClusterInfo["clusterName"])),
173+
ConnectionStatus: types.StringValue(utils.AnyToString(remoteCluster["connectionStatus"])),
174+
ReplicationOk: types.BoolValue(utils.AnyToBool(remoteCluster["replicationOK"])),
175+
RemoteNodeIPs: tfRemoteNodeIPs,
176+
RemoteNodeUUIDs: tfRemoteNodeUUIDs,
177+
}
178+
state.RemoteClusterConnections = append(state.RemoteClusterConnections, hypercoreRemoteClusterConnectionState)
179+
}
180+
181+
// Set state
182+
diags := resp.State.Set(ctx, &state)
183+
resp.Diagnostics.Append(diags...)
184+
if resp.Diagnostics.HasError() {
185+
return
186+
}
187+
}

internal/provider/hypercore_vm_data_source.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func (d *hypercoreVMDataSource) Read(ctx context.Context, req datasource.ReadReq
183183
"/rest/v1/VirDomain",
184184
query,
185185
-1.0,
186+
false,
186187
)
187188
tflog.Info(ctx, fmt.Sprintf("TTRT: filter_name=%s vm_count=%d\n", filter_name, len(hc3_vms)))
188189

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ func (p *HypercoreProvider) DataSources(ctx context.Context) []func() datasource
181181
return []func() datasource.DataSource{
182182
NewHypercoreVMDataSource,
183183
NewHypercoreNodeDataSource,
184+
NewHypercoreRemoteClusterConnectionDataSource,
184185
}
185186
}
186187

internal/utils/helper.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,49 @@ func filterResults(results []map[string]any, filterData map[string]any) []map[st
4040
return filtered
4141
}
4242

43+
func isSupersetRecursive(superset map[string]any, candidate map[string]any) bool {
44+
if candidate == nil {
45+
return true
46+
}
47+
48+
for key, value := range candidate {
49+
supValue, ok := superset[key]
50+
if !ok {
51+
return false
52+
}
53+
54+
switch v := value.(type) {
55+
case map[string]any:
56+
// recursive check if map
57+
if subMap, ok := supValue.(map[string]any); ok {
58+
if !isSupersetRecursive(subMap, v) {
59+
return false
60+
}
61+
} else {
62+
return false
63+
}
64+
default:
65+
// do normal check if not a map
66+
if supValue != v {
67+
return false
68+
}
69+
}
70+
}
71+
return true
72+
}
73+
74+
func filterResultsRecursive(results []map[string]any, filterData map[string]any) []map[string]any {
75+
filtered := []map[string]any{}
76+
77+
for _, element := range results {
78+
if isSupersetRecursive(element, filterData) {
79+
filtered = append(filtered, element)
80+
}
81+
}
82+
83+
return filtered
84+
}
85+
4386
// nolint:unused
4487
func filterMap(input map[string]any, fieldNames ...string) map[string]any {
4588
output := map[string]any{}

internal/utils/rest_client.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func (rc *RestClient) Login() {
216216
}
217217
}
218218

219-
func (rc *RestClient) ListRecords(endpoint string, query map[string]any, timeout float64) []map[string]any {
219+
func (rc *RestClient) ListRecords(endpoint string, query map[string]any, timeout float64, recursiveFiltering bool) []map[string]any {
220220
useTimeout := timeout
221221
if timeout == -1 {
222222
useTimeout = rc.Timeout
@@ -242,6 +242,9 @@ func (rc *RestClient) ListRecords(endpoint string, query map[string]any, timeout
242242
}
243243

244244
records := rc.ToJsonObjectList(resp)
245+
if recursiveFiltering {
246+
return filterResultsRecursive(records, query)
247+
}
245248
return filterResults(records, query)
246249
}
247250

@@ -251,7 +254,7 @@ func (rc *RestClient) GetRecord(endpoint string, query map[string]any, mustExist
251254
useTimeout = rc.Timeout
252255
}
253256

254-
records := rc.ListRecords(endpoint, query, useTimeout)
257+
records := rc.ListRecords(endpoint, query, useTimeout, false)
255258
if len(records) > 1 {
256259
panic(fmt.Sprintf("%d records from endpoint %s match the %v query.", len(records), endpoint, query))
257260
}

internal/utils/vm.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ func GetOneVM(uuid string, restClient RestClient) map[string]any {
484484
url,
485485
map[string]any{},
486486
-1.0,
487+
false,
487488
)
488489

489490
if len(records) == 0 {
@@ -513,6 +514,7 @@ func GetVMOrFail(query map[string]any, restClient RestClient) []map[string]any {
513514
"/rest/v1/VirDomain",
514515
query,
515516
-1.0,
517+
false,
516518
)
517519

518520
if len(records) == 0 {
@@ -527,6 +529,7 @@ func GetVM(query map[string]any, restClient RestClient) []map[string]any {
527529
"/rest/v1/VirDomain",
528530
query,
529531
-1.0,
532+
false,
530533
)
531534

532535
if len(records) == 0 {

0 commit comments

Comments
 (0)