From 4038ad21d8d6b2536351c3f4ebf0149f816c917b Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 2 Jan 2024 13:50:00 +0100 Subject: [PATCH] add junos_chassis_inventory data-source Fix #587 --- .changes/issue-587.md | 4 + docs/data-sources/chassis_inventory.md | 59 +++ internal/junos/netconf_rpc.go | 25 ++ .../data_source_chassis_inventory.go | 344 ++++++++++++++++++ .../data_source_chassis_inventory_test.go | 33 ++ internal/providerfwk/provider.go | 1 + .../1/main.tf | 1 + 7 files changed, 467 insertions(+) create mode 100644 .changes/issue-587.md create mode 100644 docs/data-sources/chassis_inventory.md create mode 100644 internal/providerfwk/data_source_chassis_inventory.go create mode 100644 internal/providerfwk/data_source_chassis_inventory_test.go create mode 100644 internal/providerfwk/testdata/TestAccDataSourceChassisInventory_basic/1/main.tf diff --git a/.changes/issue-587.md b/.changes/issue-587.md new file mode 100644 index 00000000..723dc8a8 --- /dev/null +++ b/.changes/issue-587.md @@ -0,0 +1,4 @@ + +FEATURES: + +* add **junos_chassis_inventory** data-source (Fix [#587](https://github.com/jeremmfr/terraform-provider-junos/issues/587)) diff --git a/docs/data-sources/chassis_inventory.md b/docs/data-sources/chassis_inventory.md new file mode 100644 index 00000000..9b5e0b34 --- /dev/null +++ b/docs/data-sources/chassis_inventory.md @@ -0,0 +1,59 @@ +--- +page_title: "Junos: junos_chassis_inventory" +--- + +# junos_chassis_inventory + +Get chassis inventory (like the `show chassis hardware` command). + +## Example Usage + +```hcl +# Read chassis inventory and display serial-number +data "junos_chassis_inventory" "demo" {} +output "SN" { + value = data.junos_chassis_inventory.demo.chassis.0.serial_number +} +``` + +## Attributes Reference + +The following attributes are exported: + +- **id** (String) + An identifier for the data source with value `chassis_inventory`. +- **chassis** (Block List) + Chassis inventory for each routing engine. + - Attributes defined by [component information schema](#component-information-schema) + - **re_name** (String) + Name of the routing engine (only if there are multiple routing engines). + - **module** (Block List) + For each module, component information. + See [below for nested schema](#module-attributes). + +### module attributes + +- Attributes defined by [component information schema](#component-information-schema) +- **sub_module** (Block List) + For each sub-module, component information. + - Attributes defined by [component information schema](#component-information-schema) + - **sub_sub_module** (Block List) + For each sub-sub-module, component information. + Attributes defined by [component information schema](#component-information-schema) + +### component information schema + +- **clei_code** (String) + Common Language Equipment Identifier code of the component. +- **description** (String) + Description of the component. +- **model_number** (String) + Model number of the component. +- **name** (String) + Name of the component. +- **part_number** (String) + Part number of the component. +- **serial_number** (String) + Serial number of the component. +- **version** (String) + Version of the component. diff --git a/internal/junos/netconf_rpc.go b/internal/junos/netconf_rpc.go index bc0a2a16..6942805f 100644 --- a/internal/junos/netconf_rpc.go +++ b/internal/junos/netconf_rpc.go @@ -33,6 +33,7 @@ const ( rpcCloseSession = "" rpcGetSystemInformation = "" + RPCGetChassisInventory = `` RPCGetInterfaceInformationInterfaceName = "%s" //nolint:lll RPCGetInterfacesInformationTerse = `` RPCGetInterfaceInformationTerse = `%s` @@ -114,3 +115,27 @@ type RPCGetRouteInformationReply struct { } `xml:"rt"` } `xml:"route-table"` } + +type RPCGetChassisInventoryReply struct { + XMLName xml.Name `xml:"chassis-inventory"` + Chassis struct { + RPCGetChassisInventoryReplyComponent + Module []struct { + RPCGetChassisInventoryReplyComponent + SubModule []struct { + RPCGetChassisInventoryReplyComponent + SubSubModule []RPCGetChassisInventoryReplyComponent `xml:"chassis-sub-sub-module"` + } `xml:"chassis-sub-module"` + } `xml:"chassis-module"` + } `xml:"chassis"` +} + +type RPCGetChassisInventoryReplyComponent struct { + Name *string `xml:"name"` + Version *string `xml:"version"` + PartNumber *string `xml:"part-number"` + SerialNumber *string `xml:"serial-number"` + ModelNumber *string `xml:"model-number"` + CleiCode *string `xml:"clei-code"` + Description *string `xml:"description"` +} diff --git a/internal/providerfwk/data_source_chassis_inventory.go b/internal/providerfwk/data_source_chassis_inventory.go new file mode 100644 index 00000000..83947a7d --- /dev/null +++ b/internal/providerfwk/data_source_chassis_inventory.go @@ -0,0 +1,344 @@ +package providerfwk + +import ( + "context" + "encoding/xml" + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + + "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/types" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &chassisInventoryDataSource{} + _ datasource.DataSourceWithConfigure = &chassisInventoryDataSource{} +) + +type chassisInventoryDataSource struct { + client *junos.Client +} + +func (dsc *chassisInventoryDataSource) typeName() string { + return providerName + "_chassis_inventory" +} + +func (dsc *chassisInventoryDataSource) junosName() string { + return "chassis hardware" +} + +func newChassisInventoryDataSource() datasource.DataSource { + return &chassisInventoryDataSource{} +} + +func (dsc *chassisInventoryDataSource) Metadata( + _ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse, +) { + resp.TypeName = dsc.typeName() +} + +func (dsc *chassisInventoryDataSource) Configure( + ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedDataSourceConfigureType(ctx, req, resp) + + return + } + dsc.client = client +} + +func (dsc *chassisInventoryDataSource) Schema( + _ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Get chassis inventory (" + dsc.junosName() + ")", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the data source with value `chassis_inventory`.", + }, + "chassis": schema.ListAttribute{ + Computed: true, + Description: "Chassis inventory for each routing engine.", + ElementType: types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "re_name": types.StringType, + "clei_code": types.StringType, + "description": types.StringType, + "model_number": types.StringType, + "name": types.StringType, + "part_number": types.StringType, + "serial_number": types.StringType, + "version": types.StringType, + "module": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "clei_code": types.StringType, + "description": types.StringType, + "model_number": types.StringType, + "name": types.StringType, + "part_number": types.StringType, + "serial_number": types.StringType, + "version": types.StringType, + "sub_module": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "clei_code": types.StringType, + "description": types.StringType, + "model_number": types.StringType, + "name": types.StringType, + "part_number": types.StringType, + "serial_number": types.StringType, + "version": types.StringType, + "sub_sub_module": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "clei_code": types.StringType, + "description": types.StringType, + "model_number": types.StringType, + "name": types.StringType, + "part_number": types.StringType, + "serial_number": types.StringType, + "version": types.StringType, + })), + })), + })), + }), + }, + }, + } +} + +type chassisInventoryDataSourceData struct { + ID types.String `tfsdk:"id"` + Chassis []chassisInventoryDataSourceBlockChassis `tfsdk:"chassis"` +} + +type chassisInventoryDataSourceBlockChassis struct { + ReName types.String `tfsdk:"re_name"` + CleiCode types.String `tfsdk:"clei_code"` + Description types.String `tfsdk:"description"` + ModelNumber types.String `tfsdk:"model_number"` + Name types.String `tfsdk:"name"` + PartNumber types.String `tfsdk:"part_number"` + SerialNumber types.String `tfsdk:"serial_number"` + Version types.String `tfsdk:"version"` + Module []chassisInventoryDataSourceBlockChassisBlockModule `tfsdk:"module"` +} + +type chassisInventoryDataSourceBlockChassisBlockModule struct { + CleiCode types.String `tfsdk:"clei_code"` + Description types.String `tfsdk:"description"` + ModelNumber types.String `tfsdk:"model_number"` + Name types.String `tfsdk:"name"` + PartNumber types.String `tfsdk:"part_number"` + SerialNumber types.String `tfsdk:"serial_number"` + Version types.String `tfsdk:"version"` + SubModule []chassisInventoryDataSourceBlockChassisBlockModuleBlockSubModule `tfsdk:"sub_module"` +} + +//nolint:lll +type chassisInventoryDataSourceBlockChassisBlockModuleBlockSubModule struct { + CleiCode types.String `tfsdk:"clei_code"` + Description types.String `tfsdk:"description"` + ModelNumber types.String `tfsdk:"model_number"` + Name types.String `tfsdk:"name"` + PartNumber types.String `tfsdk:"part_number"` + SerialNumber types.String `tfsdk:"serial_number"` + Version types.String `tfsdk:"version"` + SubSubModule []chassisInventoryDataSourceBlockChassisBlockModuleBlockSubModuleBlockSubSubModule `tfsdk:"sub_sub_module"` +} + +type chassisInventoryDataSourceBlockChassisBlockModuleBlockSubModuleBlockSubSubModule struct { + CleiCode types.String `tfsdk:"clei_code"` + Description types.String `tfsdk:"description"` + ModelNumber types.String `tfsdk:"model_number"` + Name types.String `tfsdk:"name"` + PartNumber types.String `tfsdk:"part_number"` + SerialNumber types.String `tfsdk:"serial_number"` + Version types.String `tfsdk:"version"` +} + +func (dsc *chassisInventoryDataSource) Read( + ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse, +) { + junSess, err := dsc.client.StartNewSession(ctx) + if err != nil { + resp.Diagnostics.AddError(tfdiag.StartSessErrSummary, err.Error()) + + return + } + defer junSess.Close() + + var data chassisInventoryDataSourceData + junos.MutexLock() + err = data.read(ctx, junSess) + junos.MutexUnlock() + if err != nil { + resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) + + return + } + + data.fillID() + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (dscData *chassisInventoryDataSourceData) fillID() { + dscData.ID = types.StringValue("chassis_inventory") +} + +func (dscData *chassisInventoryDataSourceData) read( + _ context.Context, junSess *junos.Session, +) error { + replyData, err := junSess.CommandXML(junos.RPCGetChassisInventory) + if err != nil { + return err + } + if strings.Contains(replyData, "