diff --git a/cmd/export.go b/cmd/export.go index fcc30fab..38b75744 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -36,12 +36,16 @@ import ( ) var ( - csvHeaders string + csvHeaders string + csvComponentTypes string ) func init() { ExportCmd.PersistentFlags().StringVar( - &csvHeaders, "headers", "ID,Name,Type,Vlan,Role,SubRole,Nid,Alias", "Comma separated list of fields to get") + &csvHeaders, "headers", "Type,Vlan,Role,SubRole,Nid,Alias,Name,ID,Location", "Comma separated list of fields to get") + ExportCmd.PersistentFlags().StringVarP( + &csvComponentTypes, "type", "t", "Node,Cabinet", "Comma separated list of the types of components to output") + ExportCmd.PersistentFlags().BoolP("all", "a", false, "List all components. This overrides the --type option") } // ExportCmd represents the export command @@ -61,15 +65,31 @@ func export(cmd *cobra.Command, args []string) error { return err } + all, err := cmd.Flags().GetBool("all") + if err != nil { + return err + } + headers := strings.Split(csvHeaders, ",") for i, header := range headers { headers[i] = strings.TrimSpace(header) } - log.Debug().Msgf("headers: %v", headers) + var types []string + if all { + // empty list means all types + log.Debug().Msgf("types: all") + } else { + types = strings.Split(csvComponentTypes, ",") + for i, t := range types { + types[i] = strings.TrimSpace(t) + } + log.Debug().Msgf("types: %v", types) + } + w := csv.NewWriter(os.Stdout) - err = d.ExportCsv(cmd.Context(), w, headers) + err = d.ExportCsv(cmd.Context(), w, headers, types) if err != nil { log.Error().Msgf("export failed: %s", err) } diff --git a/internal/domain/csv.go b/internal/domain/csv.go index ffdb1e1a..384d053e 100644 --- a/internal/domain/csv.go +++ b/internal/domain/csv.go @@ -42,6 +42,7 @@ import ( var ( csvAllowedHeaders = map[string]string{ "id": "ID", + "location": "Location", "name": "Name", "type": "Type", "devicetypeslug": "DeviceTypeSlug", @@ -53,7 +54,7 @@ var ( "nid": "Nid"} ) -func (d *Domain) ExportCsv(ctx context.Context, writer *csv.Writer, headers []string) error { +func (d *Domain) ExportCsv(ctx context.Context, writer *csv.Writer, headers []string, types []string) error { // Get the entire inventory inv, err := d.datastore.List() if err != nil { @@ -65,9 +66,26 @@ func (d *Domain) ExportCsv(ctx context.Context, writer *csv.Writer, headers []st keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { - ki := fmt.Sprintf("%v", keys[i]) - kj := fmt.Sprintf("%v", keys[j]) - return ki < kj + hwi := inv.Hardware[keys[i]] + hwj := inv.Hardware[keys[j]] + if hwi.Type == hwj.Type { + lenj := len(hwj.LocationPath) + for i, loci := range hwi.LocationPath { + if i >= lenj { + return false + } + + locj := hwj.LocationPath[i] + if loci.Ordinal == locj.Ordinal { + continue // go to the next location + } + return loci.Ordinal < locj.Ordinal + } + // This case should not be hit + // It means that the hardware type and location path are the same. + return false + } + return hwi.Type < hwj.Type }) normalizedHeaders, err := toNormalizedHeaders(headers) @@ -75,11 +93,20 @@ func (d *Domain) ExportCsv(ctx context.Context, writer *csv.Writer, headers []st return errors.Join(fmt.Errorf("invalid headers %v, allowed headers: %v", headers, csvAllowedHeaders), err) } + typeSet := make(map[string]struct{}) + for _, t := range types { + typeSet[strings.ToLower(t)] = struct{}{} + } + allTypes := len(types) == 0 + // Write the first csv row (i.e. the headers) writer.Write(normalizedHeaders) for _, uuid := range keys { hw := inv.Hardware[uuid] + if _, ok := typeSet[strings.ToLower(string(hw.Type))]; !allTypes && !ok { + continue + } row, err := d.externalInventoryProvider.GetFields(&hw, normalizedHeaders) if err != nil { return errors.Join(fmt.Errorf("unexpected error getting fields, %v, from hardware %v", normalizedHeaders, hw.ID), err) diff --git a/internal/provider/csm/csv.go b/internal/provider/csm/csv.go index c043f217..47f72ae1 100644 --- a/internal/provider/csm/csv.go +++ b/internal/provider/csm/csv.go @@ -35,20 +35,6 @@ import ( "github.com/rs/zerolog/log" ) -var ( - csvAllowedHeaders = map[string]string{ - "id": "ID", - "name": "Name", - "type": "Type", - "devicetypeslug": "DeviceTypeSlug", - "status": "Status", - "vlan": "Vlan", - "role": "Role", - "subrole": "SubRole", - "alias": "Alias", - "nid": "Nid"} -) - func (csm *CSM) GetFields(hw *inventory.Hardware, fieldNames []string) (values []string, err error) { values = make([]string, len(fieldNames)) @@ -62,6 +48,8 @@ func (csm *CSM) GetFields(hw *inventory.Hardware, fieldNames []string) (values [ switch name { case "ID": values[i] = fmt.Sprintf("%v", hw.ID) + case "Location": + values[i] = fmt.Sprintf("%v", hw.LocationPath) case "Name": values[i] = fmt.Sprintf("%v", hw.Name) case "Type":