// Port of LLDPConfig.html's "Global Mode" table only. The page also has a // per-port "Interface Mode" table (Transmit/Receive Enable etc, one row per // port with per-port field names like "1.0.24.v_2_1_2") -- that's a // separate, more involved write target (needs the same per-port-field-name // handling as resource_port.go) and isn't built yet; only requested was // "LLDP Configuration" which maps to the global settings. package provider import ( "context" "fmt" "strconv" "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 ( lldpConfigPath = "/LLDPConfig.html" lldpGlobalModeCaption = "Global Mode" lldpTransmitIntervalLabel = "Transmit Interval" lldpTransmitHoldLabel = "Transmit Hold" lldpReinitDelayLabel = "Re-Initialization Delay" lldpNotificationIntervalLabel = "Notification Interval" ) var ( _ resource.Resource = &lldpConfigurationResource{} _ resource.ResourceWithConfigure = &lldpConfigurationResource{} ) func NewLLDPConfigurationResource() resource.Resource { return &lldpConfigurationResource{} } type lldpConfigurationResource struct { client *client.Client } type lldpConfigurationResourceModel struct { ID types.String `tfsdk:"id"` TransmitInterval types.Int64 `tfsdk:"transmit_interval"` TransmitHold types.Int64 `tfsdk:"transmit_hold"` ReinitDelay types.Int64 `tfsdk:"reinit_delay"` NotificationInterval types.Int64 `tfsdk:"notification_interval"` } func (r *lldpConfigurationResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_lldp_configuration" } func (r *lldpConfigurationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "Controls the switch's LLDP Global Mode settings (LLDPConfig.html). Singleton -- there is only ever one of these per switch. Does NOT cover the page's per-port Interface Mode table -- see the package doc comment. Delete resets all four fields to their documented factory defaults.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, Description: "Always \"lldp_configuration\" -- this is a singleton resource.", }, "transmit_interval": schema.Int64Attribute{ Required: true, Description: "Seconds between LLDP transmissions. Range 5-32768 (switch default: 30).", }, "transmit_hold": schema.Int64Attribute{ Required: true, Description: "Multiplier for how long a receiver holds LLDP info before aging it out. Range 2-10 (switch default: 4).", }, "reinit_delay": schema.Int64Attribute{ Required: true, Description: "Seconds to wait before re-initializing LLDP on a port after it's disabled. Range 1-10 (switch default: 2).", }, "notification_interval": schema.Int64Attribute{ Required: true, Description: "Minimum seconds between LLDP change notifications. Range 5-3600 (switch default: 5).", }, }, } } func (r *lldpConfigurationResource) 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 lldpConfigurationState struct { TransmitInterval int64 TransmitHold int64 ReinitDelay int64 NotificationInterval int64 } func (r *lldpConfigurationResource) readState() (lldpConfigurationState, error) { tables, err := r.client.FetchTables(lldpConfigPath) if err != nil { return lldpConfigurationState{}, err } table, ok := tables[lldpGlobalModeCaption] if !ok { return lldpConfigurationState{}, fmt.Errorf("table captioned %q not found on %s", lldpGlobalModeCaption, lldpConfigPath) } parse := func(label string) (int64, error) { v, err := strconv.ParseInt(table.Record[label], 10, 64) if err != nil { return 0, fmt.Errorf("parsing %q (%q) as int: %w", label, table.Record[label], err) } return v, nil } transmitInterval, err := parse(lldpTransmitIntervalLabel) if err != nil { return lldpConfigurationState{}, err } transmitHold, err := parse(lldpTransmitHoldLabel) if err != nil { return lldpConfigurationState{}, err } reinitDelay, err := parse(lldpReinitDelayLabel) if err != nil { return lldpConfigurationState{}, err } notificationInterval, err := parse(lldpNotificationIntervalLabel) if err != nil { return lldpConfigurationState{}, err } return lldpConfigurationState{ TransmitInterval: transmitInterval, TransmitHold: transmitHold, ReinitDelay: reinitDelay, NotificationInterval: notificationInterval, }, nil } func (r *lldpConfigurationResource) setState(desired lldpConfigurationState) error { tables, err := r.client.FetchTables(lldpConfigPath) if err != nil { return err } table, ok := tables[lldpGlobalModeCaption] if !ok { return fmt.Errorf("table captioned %q not found on %s", lldpGlobalModeCaption, lldpConfigPath) } field := func(label string) (string, error) { f, ok := table.FieldNames[label] if !ok { return "", fmt.Errorf("field %q not found in table %q", label, lldpGlobalModeCaption) } return f, nil } transmitIntervalField, err := field(lldpTransmitIntervalLabel) if err != nil { return err } transmitHoldField, err := field(lldpTransmitHoldLabel) if err != nil { return err } reinitDelayField, err := field(lldpReinitDelayLabel) if err != nil { return err } notificationIntervalField, err := field(lldpNotificationIntervalLabel) if err != nil { return err } _, err = r.client.SubmitForm(lldpConfigPath, map[string]string{ transmitIntervalField: strconv.FormatInt(desired.TransmitInterval, 10), transmitHoldField: strconv.FormatInt(desired.TransmitHold, 10), reinitDelayField: strconv.FormatInt(desired.ReinitDelay, 10), notificationIntervalField: strconv.FormatInt(desired.NotificationInterval, 10), }) return err } func lldpConfigStateToModel(s lldpConfigurationState) lldpConfigurationResourceModel { return lldpConfigurationResourceModel{ ID: types.StringValue("lldp_configuration"), TransmitInterval: types.Int64Value(s.TransmitInterval), TransmitHold: types.Int64Value(s.TransmitHold), ReinitDelay: types.Int64Value(s.ReinitDelay), NotificationInterval: types.Int64Value(s.NotificationInterval), } } func (r *lldpConfigurationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan lldpConfigurationResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } desired := lldpConfigurationState{ TransmitInterval: plan.TransmitInterval.ValueInt64(), TransmitHold: plan.TransmitHold.ValueInt64(), ReinitDelay: plan.ReinitDelay.ValueInt64(), NotificationInterval: plan.NotificationInterval.ValueInt64(), } if err := r.setState(desired); err != nil { resp.Diagnostics.AddError("Unable to set LLDP Configuration state", err.Error()) return } actual, err := r.readState() if err != nil { resp.Diagnostics.AddError("Unable to read back LLDP Configuration state", err.Error()) return } state := lldpConfigStateToModel(actual) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *lldpConfigurationResource) Read(ctx context.Context, _ resource.ReadRequest, resp *resource.ReadResponse) { actual, err := r.readState() if err != nil { resp.Diagnostics.AddError("Unable to read LLDP Configuration state", err.Error()) return } state := lldpConfigStateToModel(actual) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *lldpConfigurationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan lldpConfigurationResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } desired := lldpConfigurationState{ TransmitInterval: plan.TransmitInterval.ValueInt64(), TransmitHold: plan.TransmitHold.ValueInt64(), ReinitDelay: plan.ReinitDelay.ValueInt64(), NotificationInterval: plan.NotificationInterval.ValueInt64(), } if err := r.setState(desired); err != nil { resp.Diagnostics.AddError("Unable to set LLDP Configuration state", err.Error()) return } actual, err := r.readState() if err != nil { resp.Diagnostics.AddError("Unable to read back LLDP Configuration state", err.Error()) return } state := lldpConfigStateToModel(actual) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } // Delete resets all four fields to their documented factory defaults -- // safe, no traffic impact. func (r *lldpConfigurationResource) Delete(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) { defaults := lldpConfigurationState{TransmitInterval: 30, TransmitHold: 4, ReinitDelay: 2, NotificationInterval: 5} if err := r.setState(defaults); err != nil { resp.Diagnostics.AddError("Unable to reset LLDP Configuration state on delete", err.Error()) } }