195 lines
6.2 KiB
Go
195 lines
6.2 KiB
Go
// Port of Time_Zone_Configuration.html. Simple scalar page, both fields
|
|
// (Time Zone combo, Acronym textbox) are read-write, no duplicate labels.
|
|
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
|
|
"terraform-provider-hpe1810/internal/provider/client"
|
|
)
|
|
|
|
const (
|
|
timeZonePath = "/Time_Zone_Configuration.html"
|
|
timeZoneCaption = "Time Zone Configuration"
|
|
timeZoneNameLabel = "Time Zone"
|
|
timeZoneAcronymLabel = "Acronym"
|
|
)
|
|
|
|
var (
|
|
_ resource.Resource = &timeZoneResource{}
|
|
_ resource.ResourceWithConfigure = &timeZoneResource{}
|
|
)
|
|
|
|
func NewTimeZoneResource() resource.Resource {
|
|
return &timeZoneResource{}
|
|
}
|
|
|
|
type timeZoneResource struct {
|
|
client *client.Client
|
|
}
|
|
|
|
type timeZoneResourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Zone types.String `tfsdk:"zone"`
|
|
Acronym types.String `tfsdk:"acronym"`
|
|
}
|
|
|
|
func (r *timeZoneResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_time_zone"
|
|
}
|
|
|
|
func (r *timeZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Controls the switch's Time Zone Configuration page. Singleton -- there is only ever one of these per switch. Delete resets to \"None\" (no offset) with an empty acronym.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{
|
|
Computed: true,
|
|
Description: "Always \"time_zone\" -- this is a singleton resource.",
|
|
},
|
|
"zone": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "e.g. \"(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna\", or \"None\". Not enum-validated client-side -- the exact display strings the switch accepts are numerous (see _xe_Time_Zone_Configuration.js on the switch); invalid values are caught by reading back rather than guessed at up front.",
|
|
},
|
|
"acronym": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "0-4 characters, e.g. \"CET\". Can be an empty string.",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *timeZoneResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
c, ok := req.ProviderData.(*client.Client)
|
|
if !ok {
|
|
resp.Diagnostics.AddError("Unexpected resource configure type", fmt.Sprintf("expected *client.Client, got %T", req.ProviderData))
|
|
return
|
|
}
|
|
r.client = c
|
|
}
|
|
|
|
type timeZoneState struct {
|
|
Zone string
|
|
Acronym string
|
|
}
|
|
|
|
func (r *timeZoneResource) readState() (timeZoneState, error) {
|
|
tables, err := r.client.FetchTables(timeZonePath)
|
|
if err != nil {
|
|
return timeZoneState{}, err
|
|
}
|
|
table, ok := tables[timeZoneCaption]
|
|
if !ok {
|
|
return timeZoneState{}, fmt.Errorf("table captioned %q not found on %s", timeZoneCaption, timeZonePath)
|
|
}
|
|
return timeZoneState{
|
|
Zone: table.Record[timeZoneNameLabel],
|
|
Acronym: table.Record[timeZoneAcronymLabel],
|
|
}, nil
|
|
}
|
|
|
|
func (r *timeZoneResource) setState(desired timeZoneState) error {
|
|
tables, err := r.client.FetchTables(timeZonePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
table, ok := tables[timeZoneCaption]
|
|
if !ok {
|
|
return fmt.Errorf("table captioned %q not found on %s", timeZoneCaption, timeZonePath)
|
|
}
|
|
zoneField, ok := table.FieldNames[timeZoneNameLabel]
|
|
if !ok {
|
|
return fmt.Errorf("field %q not found in table %q", timeZoneNameLabel, timeZoneCaption)
|
|
}
|
|
acronymField, ok := table.FieldNames[timeZoneAcronymLabel]
|
|
if !ok {
|
|
return fmt.Errorf("field %q not found in table %q", timeZoneAcronymLabel, timeZoneCaption)
|
|
}
|
|
|
|
_, err = r.client.SubmitForm(timeZonePath, map[string]string{
|
|
zoneField: desired.Zone,
|
|
acronymField: desired.Acronym,
|
|
})
|
|
return err
|
|
}
|
|
|
|
func timeZoneStateToModel(s timeZoneState) timeZoneResourceModel {
|
|
return timeZoneResourceModel{
|
|
ID: types.StringValue("time_zone"),
|
|
Zone: types.StringValue(s.Zone),
|
|
Acronym: types.StringValue(s.Acronym),
|
|
}
|
|
}
|
|
|
|
func (r *timeZoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var plan timeZoneResourceModel
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
desired := timeZoneState{Zone: plan.Zone.ValueString(), Acronym: plan.Acronym.ValueString()}
|
|
if err := r.setState(desired); err != nil {
|
|
resp.Diagnostics.AddError("Unable to set Time Zone state", err.Error())
|
|
return
|
|
}
|
|
|
|
actual, err := r.readState()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to read back Time Zone state", err.Error())
|
|
return
|
|
}
|
|
|
|
state := timeZoneStateToModel(actual)
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
|
}
|
|
|
|
func (r *timeZoneResource) Read(ctx context.Context, _ resource.ReadRequest, resp *resource.ReadResponse) {
|
|
actual, err := r.readState()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to read Time Zone state", err.Error())
|
|
return
|
|
}
|
|
|
|
state := timeZoneStateToModel(actual)
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
|
}
|
|
|
|
func (r *timeZoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var plan timeZoneResourceModel
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
desired := timeZoneState{Zone: plan.Zone.ValueString(), Acronym: plan.Acronym.ValueString()}
|
|
if err := r.setState(desired); err != nil {
|
|
resp.Diagnostics.AddError("Unable to set Time Zone state", err.Error())
|
|
return
|
|
}
|
|
|
|
actual, err := r.readState()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to read back Time Zone state", err.Error())
|
|
return
|
|
}
|
|
|
|
state := timeZoneStateToModel(actual)
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
|
}
|
|
|
|
// Delete resets to "None" (no timezone offset) with an empty acronym --
|
|
// harmless, matches the switch's out-of-box default.
|
|
func (r *timeZoneResource) Delete(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
if err := r.setState(timeZoneState{Zone: "None", Acronym: ""}); err != nil {
|
|
resp.Diagnostics.AddError("Unable to reset Time Zone state on delete", err.Error())
|
|
}
|
|
}
|