Skip to content

Commit dea5add

Browse files
authored
refactor: license resource and data source (#2436)
Refactors `r/license` and `d/license` and introduces the use of `hashicorp/terraform-plugin-log` for logging. Signed-off-by: Ryan Johnson <ryan.johnson@broadcom.com>
1 parent 3026008 commit dea5add

File tree

5 files changed

+436
-190
lines changed

5 files changed

+436
-190
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.23.8
44

55
require (
66
github.com/davecgh/go-spew v1.1.1
7+
github.com/hashicorp/terraform-plugin-log v0.9.0
78
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
89
github.com/hashicorp/terraform-plugin-testing v1.12.0
910
github.com/mitchellh/copystructure v1.2.0
@@ -34,7 +35,6 @@ require (
3435
github.com/hashicorp/terraform-exec v0.22.0 // indirect
3536
github.com/hashicorp/terraform-json v0.24.0 // indirect
3637
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
37-
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
3838
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
3939
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
4040
github.com/hashicorp/yamux v0.1.2 // indirect

vsphere/data_source_vsphere_license.go

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,112 @@
55
package vsphere
66

77
import (
8-
"log"
8+
"context"
9+
"fmt"
910

11+
"github.com/hashicorp/terraform-plugin-log/tflog"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1013
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1114
"github.com/vmware/govmomi/license"
15+
helper "github.com/vmware/terraform-provider-vsphere/vsphere/internal/helper/license"
1216
)
1317

18+
// dataSourceVSphereLicense defines a data source for retrieving licenses.
1419
func dataSourceVSphereLicense() *schema.Resource {
1520
return &schema.Resource{
16-
Read: dataSourceVSphereLicenseRead,
21+
ReadContext: dataSourceVSphereLicenseRead,
1722

1823
Schema: map[string]*schema.Schema{
1924
"license_key": {
20-
Type: schema.TypeString,
21-
Required: true,
25+
Type: schema.TypeString,
26+
Description: "The license key value.",
27+
Required: true,
2228
},
23-
24-
// computed properties returned by the API
2529
"id": {
26-
Type: schema.TypeString,
27-
Computed: true,
30+
Type: schema.TypeString,
31+
Description: "The license key ID.",
32+
Computed: true,
2833
},
2934
"labels": {
30-
Type: schema.TypeMap,
31-
Computed: true,
32-
Elem: &schema.Schema{Type: schema.TypeString},
35+
Type: schema.TypeMap,
36+
Description: "A map of labels applied to the license key.",
37+
Computed: true,
38+
Elem: &schema.Schema{Type: schema.TypeString},
3339
},
3440
"edition_key": {
35-
Type: schema.TypeString,
36-
Computed: true,
41+
Type: schema.TypeString,
42+
Description: "The product edition of the license key.",
43+
Computed: true,
3744
},
3845
"name": {
39-
Type: schema.TypeString,
40-
Computed: true,
46+
Type: schema.TypeString,
47+
Description: "The display name for the license key.",
48+
Computed: true,
4149
},
4250
"total": {
43-
Type: schema.TypeInt,
44-
Computed: true,
51+
Type: schema.TypeInt,
52+
Description: "The total number of units contained in the license key.",
53+
Computed: true,
4554
},
4655
"used": {
47-
Type: schema.TypeInt,
48-
Computed: true,
56+
Type: schema.TypeInt,
57+
Description: "The number of units assigned to this license key.",
58+
Computed: true,
4959
},
5060
},
5161
}
5262
}
5363

54-
func dataSourceVSphereLicenseRead(d *schema.ResourceData, meta interface{}) error {
55-
64+
// dataSourceVSphereLicenseRead retrieves and populates license details for a license key.
65+
func dataSourceVSphereLicenseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
66+
var diags diag.Diagnostics
5667
client := meta.(*Client).vimClient
5768
manager := license.NewManager(client.Client)
5869
licenseKey := d.Get("license_key").(string)
59-
if info := getLicenseInfoFromKey(d.Get("license_key").(string), manager); info != nil {
60-
log.Println("[INFO] Setting the values")
61-
d.Set("edition_key", info.EditionKey)
62-
d.Set("total", info.Total)
63-
d.Set("used", info.Used)
64-
d.Set("name", info.Name)
65-
if err := d.Set("labels", keyValuesToMap(info.Labels)); err != nil {
66-
return err
67-
}
68-
d.SetId(licenseKey)
69-
return nil
70-
} else {
71-
return ErrNoSuchKeyFound
70+
71+
helper.MaskedLicenseKeyLogOperation(ctx, "Reading license data source", licenseKey, nil)
72+
73+
info := helper.GetLicenseInfoFromKey(ctx, licenseKey, manager)
74+
if info == nil {
75+
return diag.FromErr(ErrKeyNotFound)
76+
}
77+
78+
tflog.Debug(ctx, "Setting license attributes in data source")
79+
80+
if err := d.Set("labels", helper.KeyValuesToMap(ctx, info.Labels)); err != nil {
81+
tflog.Error(ctx, "Failed to set labels attribute", map[string]interface{}{
82+
"error": err.Error(),
83+
})
84+
return diag.FromErr(fmt.Errorf("error setting license labels: %w", err))
7285
}
86+
if err := d.Set("edition_key", info.EditionKey); err != nil {
87+
tflog.Error(ctx, "Failed to set edition_key attribute", map[string]interface{}{
88+
"error": err.Error(),
89+
})
90+
return diag.FromErr(fmt.Errorf("error setting license edition_key: %w", err))
91+
}
92+
if err := d.Set("name", info.Name); err != nil {
93+
tflog.Error(ctx, "Failed to set name attribute", map[string]interface{}{
94+
"error": err.Error(),
95+
})
96+
return diag.FromErr(fmt.Errorf("error setting license name: %w", err))
97+
}
98+
if err := d.Set("total", info.Total); err != nil {
99+
tflog.Error(ctx, "Failed to set total attribute", map[string]interface{}{
100+
"error": err.Error(),
101+
})
102+
return diag.FromErr(fmt.Errorf("error setting license total: %w", err))
103+
}
104+
if err := d.Set("used", info.Used); err != nil {
105+
tflog.Error(ctx, "Failed to set used attribute", map[string]interface{}{
106+
"error": err.Error(),
107+
})
108+
return diag.FromErr(fmt.Errorf("error setting license used: %w", err))
109+
}
110+
111+
// Use the license key as the ID for consistent reference
112+
d.SetId(licenseKey)
113+
tflog.Debug(ctx, "Successfully read vSphere license data source")
114+
115+
return diags
73116
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package license
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"strings"
11+
12+
"github.com/hashicorp/terraform-plugin-log/tflog"
13+
"github.com/vmware/govmomi/license"
14+
"github.com/vmware/govmomi/vim25/methods"
15+
"github.com/vmware/govmomi/vim25/types"
16+
)
17+
18+
// MaskLicenseKey returns a masked version of the license key for secure logging.
19+
// Format: First 4 chars + ****** + last 4 chars
20+
func MaskLicenseKey(key string) string {
21+
if len(key) < 9 {
22+
return "********"
23+
}
24+
return key[:4] + strings.Repeat("*", len(key)-8) + key[len(key)-4:]
25+
}
26+
27+
// MaskedLicenseKeyLogOperation logs license operations with masked key information
28+
func MaskedLicenseKeyLogOperation(ctx context.Context, operation string, key string, additional map[string]interface{}) {
29+
if additional == nil {
30+
additional = make(map[string]interface{})
31+
}
32+
33+
additional["masked_key"] = MaskLicenseKey(key)
34+
additional["key_length"] = len(key)
35+
36+
tflog.Debug(ctx, "License operation: "+operation, additional)
37+
}
38+
39+
// GetLicenseInfoFromKey retrieves license information based on the provided license key using the license manager.
40+
func GetLicenseInfoFromKey(ctx context.Context, key string, manager *license.Manager) *types.LicenseManagerLicenseInfo {
41+
tflog.Debug(ctx, "Attempting to get license info")
42+
43+
tflog.Debug(ctx, "Listing all licenses via license manager")
44+
infoList, err := manager.List(ctx)
45+
if err != nil {
46+
tflog.Error(ctx, "Failed to list licenses from vSphere", map[string]interface{}{
47+
"error": err.Error(),
48+
})
49+
return nil
50+
}
51+
52+
tflog.Debug(ctx, "Iterating through license list to find match", map[string]interface{}{
53+
"listSize": len(infoList),
54+
})
55+
for i := range infoList {
56+
info := infoList[i]
57+
if info.LicenseKey == key {
58+
tflog.Debug(ctx, "Found matching license key in list")
59+
return &info
60+
}
61+
}
62+
63+
tflog.Debug(ctx, "License key not found in the list")
64+
return nil
65+
}
66+
67+
// KeyExists checks if a given license key exists within the license manager.
68+
func KeyExists(ctx context.Context, key string, manager *license.Manager) bool {
69+
tflog.Debug(ctx, "Checking if license key exists")
70+
tflog.Debug(ctx, "Listing all licenses via license manager to check existence")
71+
infoList, err := manager.List(ctx)
72+
if err != nil {
73+
tflog.Error(ctx, "Failed to list licenses while checking key existence", map[string]interface{}{
74+
"error": err.Error(),
75+
})
76+
return false
77+
}
78+
79+
tflog.Debug(ctx, "Iterating through license list to find key", map[string]interface{}{
80+
"listSize": len(infoList),
81+
})
82+
for _, info := range infoList {
83+
if info.LicenseKey == key {
84+
tflog.Debug(ctx, "Found matching license key")
85+
return true
86+
}
87+
}
88+
89+
tflog.Debug(ctx, "License key not found in the list")
90+
return false
91+
}
92+
93+
// UpdateLabels updates labels for a specified license key using the provided label map.
94+
func UpdateLabels(ctx context.Context, manager *license.Manager, licenseKey string, labelMap map[string]interface{}) error {
95+
tflog.Debug(ctx, "Updating labels for a specific license resource", map[string]interface{}{
96+
"labelCount": len(labelMap),
97+
})
98+
99+
for key, value := range labelMap {
100+
stringValue, ok := value.(string)
101+
if !ok {
102+
err := fmt.Errorf("label value for key '%s' is not a string (type: %T)", key, value)
103+
tflog.Error(ctx, "Invalid label value type during update", map[string]interface{}{
104+
"labelKey": key,
105+
"valueType": fmt.Sprintf("%T", value),
106+
"error": err.Error(),
107+
})
108+
return err
109+
}
110+
111+
tflog.Debug(ctx, "Updating individual license label", map[string]interface{}{
112+
"labelKey": key,
113+
"labelValue": stringValue,
114+
})
115+
116+
err := UpdateLabel(ctx, manager, licenseKey, key, stringValue)
117+
if err != nil {
118+
tflog.Error(ctx, "Failed to update individual license label", map[string]interface{}{
119+
"labelKey": key,
120+
"labelValue": stringValue,
121+
"error": err.Error(),
122+
})
123+
return fmt.Errorf("failed to update label '%s' for the license resource: %w", key, err)
124+
}
125+
}
126+
127+
tflog.Debug(ctx, "Successfully updated all labels for the license resource")
128+
return nil
129+
}
130+
131+
// UpdateLabel assigns or updates the specified label key-value pair for a given license using the license manager.
132+
func UpdateLabel(ctx context.Context, m *license.Manager, licenseKey string, key string, val string) error {
133+
tflog.Debug(ctx, "Attempting to update a single license label", map[string]interface{}{
134+
"labelKey": key,
135+
"labelValue": val,
136+
})
137+
138+
req := types.UpdateLicenseLabel{
139+
This: m.Reference(),
140+
LicenseKey: licenseKey,
141+
LabelKey: key,
142+
LabelValue: val,
143+
}
144+
145+
_, err := methods.UpdateLicenseLabel(ctx, m.Client(), &req)
146+
if err != nil {
147+
tflog.Error(ctx, "Failed API call to update license label", map[string]interface{}{
148+
"labelKey": key,
149+
"labelValue": val,
150+
"error": err.Error(),
151+
})
152+
return fmt.Errorf("failed to update label '%s': %w", key, err)
153+
}
154+
155+
tflog.Debug(ctx, "Successfully updated single license label via API", map[string]interface{}{
156+
"labelKey": key,
157+
"labelValue": val,
158+
})
159+
return nil
160+
}
161+
162+
// DiagnosticError creates an error using the diagnostic property value.
163+
func DiagnosticError(ctx context.Context, info types.LicenseManagerLicenseInfo) error {
164+
tflog.Debug(ctx, "Searching for 'diagnostic' property in license info")
165+
for _, property := range info.Properties {
166+
tflog.Trace(ctx, "Checking license property", map[string]interface{}{"propertyKey": property.Key})
167+
if property.Key == "diagnostic" {
168+
diagnosticValue, ok := property.Value.(string)
169+
if !ok {
170+
err := fmt.Errorf("diagnostic property value is not a string (type: %T)", property.Value)
171+
tflog.Error(ctx, "Invalid type for diagnostic property value", map[string]interface{}{
172+
"valueType": fmt.Sprintf("%T", property.Value),
173+
"error": err.Error(),
174+
})
175+
return errors.New("failed to process diagnostic property due to unexpected type")
176+
}
177+
tflog.Debug(ctx, "Found 'diagnostic' property, creating error from its value", map[string]interface{}{"diagnosticValue": diagnosticValue})
178+
return errors.New(diagnosticValue)
179+
}
180+
}
181+
182+
tflog.Debug(ctx, "'diagnostic' property not found in license info")
183+
return nil
184+
}
185+
186+
// KeyValuesToMap converts a slice of KeyValue objects into a map with string keys and interface{} values.
187+
func KeyValuesToMap(ctx context.Context, keyValues []types.KeyValue) map[string]interface{} {
188+
mapLen := len(keyValues)
189+
tflog.Debug(ctx, "Converting KeyValue slice to map", map[string]interface{}{"sliceLength": mapLen})
190+
191+
resultMap := make(map[string]interface{}, mapLen)
192+
for _, kv := range keyValues {
193+
tflog.Trace(ctx, "Adding key-value pair to map", map[string]interface{}{"key": kv.Key})
194+
resultMap[kv.Key] = kv.Value
195+
}
196+
197+
tflog.Debug(ctx, "Successfully converted KeyValue slice to map", map[string]interface{}{"mapLength": len(resultMap)})
198+
return resultMap
199+
}

0 commit comments

Comments
 (0)