diff --git a/config/asrockrack.go b/config/asrockrack.go new file mode 100644 index 0000000..51c995b --- /dev/null +++ b/config/asrockrack.go @@ -0,0 +1,116 @@ +package config + +import ( + "encoding/xml" + "strings" +) + +type asrockrackVendorConfig struct { + ConfigFormat string + ConfigData *asrockrackConfig +} + +type asrockrackConfig struct { + BiosCfg *asrockrackBiosCfg `xml:"BiosCfg"` +} + +type asrockrackBiosCfg struct { + XMLName xml.Name `xml:"BiosCfg"` + Menus []*asrockrackBiosCfgMenu `xml:"Menu"` +} + +type asrockrackBiosCfgMenu struct { + XMLName xml.Name `xml:"Menu"` + Name string `xml:"name,attr"` + Settings []*asrockrackBiosCfgSetting `xml:"Setting"` + Menus []*asrockrackBiosCfgMenu `xml:"Menu"` +} + +type asrockrackBiosCfgSetting struct { + XMLName xml.Name `xml:"Setting"` + Name string `xml:"Name,attr"` + Order string `xml:"order,attr"` + SelectedOption string `xml:"selectedOption,attr"` + Type string `xml:"type,attr"` +} + +func NewAsrockrackVendorConfigManager(configFormat string, vendorOptions map[string]string) (VendorConfigManager, error) { + asrr := &asrockrackVendorConfig{} + + switch strings.ToLower(configFormat) { + case "json": + asrr.ConfigFormat = strings.ToLower(configFormat) + default: + return nil, UnknownConfigFormatError(strings.ToLower(configFormat)) + } + + asrr.ConfigData = &asrockrackConfig{ + BiosCfg: &asrockrackBiosCfg{}, + } + + return asrr, nil +} + +// FindMenu locates an existing asrockrackBiosCfgMenu if one exists in the ConfigData, if not +// it creates one and returns a pointer to that. +func (cm *asrockrackVendorConfig) FindMenu(menuName string) (m *asrockrackBiosCfgMenu) { + if cm.ConfigData.BiosCfg.Menus == nil { + return + } + + for _, m = range cm.ConfigData.BiosCfg.Menus { + if m.Name == menuName { + return + } + } + + m.Name = menuName + + cm.ConfigData.BiosCfg.Menus = append(cm.ConfigData.BiosCfg.Menus, m) + + return +} + +// FindMenuSetting locates an existing asrockrackBiosCfgSetting if one exists in the +// ConfigData, if not it creates one and returns a pointer to that. +func (cm *asrockrackVendorConfig) FindMenuSetting(m *asrockrackBiosCfgMenu, name string) (s *asrockrackBiosCfgSetting) { + for _, s = range m.Settings { + if s.Name == name { + return + } + } + + s.Name = name + + m.Settings = append(m.Settings, s) + + return +} + +// TODO(jwb) How do we handle the random nature of sub menus here.. we could make the user pass the explicit pointer to a menu struct, or.. +func (cm *asrockrackVendorConfig) Raw(name, value string, menuPath []string) { +} + +func (cm *asrockrackVendorConfig) Marshal() (string, error) { + switch strings.ToLower(cm.ConfigFormat) { + case "xml": + x, err := xml.Marshal(cm.ConfigData) + if err != nil { + return "", err + } + + return string(x), nil + default: + return "", UnknownConfigFormatError(strings.ToLower(cm.ConfigFormat)) + } +} + +// Generic config options + +func (cm *asrockrackVendorConfig) EnableTPM() { + // Unimplemented +} + +func (cm *asrockrackVendorConfig) EnableSRIOV() { + // Unimplemented +} diff --git a/config/dell.go b/config/dell.go new file mode 100644 index 0000000..4bfc882 --- /dev/null +++ b/config/dell.go @@ -0,0 +1,141 @@ +package config + +import ( + "encoding/json" + "encoding/xml" + "strings" +) + +type dellVendorConfig struct { + ConfigFormat string + ConfigData *dellConfig +} + +type dellConfig struct { + SystemConfiguration *dellSystemConfiguration `xml:"SystemConfiguration" json:"SystemConfiguration"` +} + +type dellSystemConfiguration struct { + XMLName xml.Name `xml:"SystemConfiguration"` + Model string `xml:"Model,attr" json:"Model"` + Comments []string `xml:"Comments>Comment,omitempty" json:"Comments,omitempty"` + ServiceTag string `xml:"ServiceTag,attr" json:"ServiceTag"` + TimeStamp string `xml:"TimeStamp,attr" json:"TimeStamp"` + Components []*dellComponent `xml:"Component" json:"Components"` +} + +type dellComponent struct { + XMLName xml.Name `xml:"Component"` + FQDD string `xml:"FQDD,attr" json:"FQDD"` + Attributes []*dellComponentAttribute `xml:"Attribute" json:"Attributes"` +} + +type dellComponentAttribute struct { + XMLName xml.Name `xml:"Attribute"` + Name string `xml:"Name,attr" json:"Name"` + SetOnImport bool `xml:"SetOnImport,omitempty" json:"SetOnImport,omitempty"` + Comment string `xml:"Comment,omitempty" json:"Comment,omitempty"` + Value string `xml:",chardata" json:"Value"` +} + +func NewDellVendorConfigManager(configFormat string, vendorOptions map[string]string) (VendorConfigManager, error) { + dell := &dellVendorConfig{} + + switch strings.ToLower(configFormat) { + case "xml", "json": + dell.ConfigFormat = strings.ToLower(configFormat) + default: + return nil, UnknownConfigFormatError(strings.ToLower(configFormat)) + } + + dell.ConfigData = &dellConfig{ + SystemConfiguration: &dellSystemConfiguration{}, + } + + dell.setSystemConfiguration(vendorOptions["model"], vendorOptions["servicetag"]) + + return dell, nil +} + +func (cm *dellVendorConfig) setSystemConfiguration(model, servicetag string) { + cm.ConfigData.SystemConfiguration.Model = model + cm.ConfigData.SystemConfiguration.ServiceTag = servicetag + // TODO(jwb) Make this 'now' + cm.ConfigData.SystemConfiguration.TimeStamp = "Tue Nov 2 21:19:16 2021" +} + +// FindComponent locates an existing DellComponent if one exists in the ConfigData, if not +// it creates one and returns a pointer to that. +func (cm *dellVendorConfig) FindComponent(fqdd string) (c *dellComponent) { + for _, c = range cm.ConfigData.SystemConfiguration.Components { + if c.FQDD == fqdd { + return + } + } + + c = &dellComponent{ + XMLName: xml.Name{}, + FQDD: fqdd, + Attributes: []*dellComponentAttribute{}, + } + + cm.ConfigData.SystemConfiguration.Components = append(cm.ConfigData.SystemConfiguration.Components, c) + + return +} + +// FindComponentAttribute locates an existing DellComponentAttribute if one exists in the +// ConfigData, if not it creates one and returns a pointer to that. +func (cm *dellVendorConfig) FindComponentAttribute(c *dellComponent, name string) (a *dellComponentAttribute) { + for _, a = range c.Attributes { + if a.Name == name { + return + } + } + + a = &dellComponentAttribute{ + Name: name, + } + + c.Attributes = append(c.Attributes, a) + + return +} + +func (cm *dellVendorConfig) Raw(name, value string, menuPath []string) { + c := cm.FindComponent(menuPath[0]) + attr := cm.FindComponentAttribute(c, name) + attr.Value = value +} + +func (cm *dellVendorConfig) Marshal() (string, error) { + switch strings.ToLower(cm.ConfigFormat) { + case "xml": + x, err := xml.Marshal(cm.ConfigData.SystemConfiguration) + if err != nil { + return "", err + } + + return string(x), nil + case "json": + x, err := json.Marshal(cm.ConfigData.SystemConfiguration) + if err != nil { + return "", err + } + + return string(x), nil + default: + return "", UnknownConfigFormatError(strings.ToLower(cm.ConfigFormat)) + } +} + +// Generic config options + +func (cm *dellVendorConfig) EnableTPM() { + cm.Raw("EnableTPM", "Enabled", []string{"BIOS.Setup.1-1"}) +} + +func (cm *dellVendorConfig) EnableSRIOV() { + // TODO(jwb) How do we want to handle enabling this for different NICs + cm.Raw("VirtualizationMode", "SRIOV", []string{"NIC.Slot.3-1-1"}) +} diff --git a/config/errors.go b/config/errors.go new file mode 100644 index 0000000..60dd15f --- /dev/null +++ b/config/errors.go @@ -0,0 +1,17 @@ +package config + +import ( + "errors" + "fmt" +) + +var errUnknownConfigFormat = errors.New("unknown config format") +var errUnknownVendor = errors.New("unknown/unsupported vendor") + +func UnknownConfigFormatError(format string) error { + return fmt.Errorf("unknown config format %w : %s", errUnknownConfigFormat, format) +} + +func UnknownVendorError(vendorName string) error { + return fmt.Errorf("unknown/unsupported vendor %w : %s", errUnknownVendor, vendorName) +} diff --git a/config/interface.go b/config/interface.go new file mode 100644 index 0000000..9684cce --- /dev/null +++ b/config/interface.go @@ -0,0 +1,28 @@ +package config + +import ( + "strings" + + "github.com/bmc-toolbox/common" +) + +type VendorConfigManager interface { + EnableTPM() + EnableSRIOV() + + Raw(name, value string, menuPath []string) + Marshal() (string, error) +} + +func NewVendorConfigManager(configFormat, vendorName string, vendorOptions map[string]string) (VendorConfigManager, error) { + switch strings.ToLower(vendorName) { + case common.VendorDell: + return NewDellVendorConfigManager(configFormat, vendorOptions) + case common.VendorSupermicro: + return NewSupermicroVendorConfigManager(configFormat, vendorOptions) + case common.VendorAsrockrack: + return NewAsrockrackVendorConfigManager(configFormat, vendorOptions) + default: + return nil, UnknownVendorError(strings.ToLower(vendorName)) + } +} diff --git a/config/supermicro.go b/config/supermicro.go new file mode 100644 index 0000000..25fd02e --- /dev/null +++ b/config/supermicro.go @@ -0,0 +1,126 @@ +package config + +import ( + "encoding/xml" + "strings" +) + +type supermicroVendorConfig struct { + ConfigFormat string + ConfigData *supermicroConfig +} + +type supermicroConfig struct { + BiosCfg *supermicroBiosCfg `xml:"BiosCfg"` +} + +type supermicroBiosCfg struct { + XMLName xml.Name `xml:"BiosCfg"` + Menus []*supermicroBiosCfgMenu `xml:"Menu"` +} + +type supermicroBiosCfgMenu struct { + XMLName xml.Name `xml:"Menu"` + Name string `xml:"name,attr"` + Settings []*supermicroBiosCfgSetting `xml:"Setting"` + Menus []*supermicroBiosCfgMenu `xml:"Menu"` +} + +type supermicroBiosCfgSetting struct { + XMLName xml.Name `xml:"Setting"` + Name string `xml:"Name,attr"` + Order string `xml:"order,attr"` + SelectedOption string `xml:"selectedOption,attr"` + Type string `xml:"type,attr"` +} + +func NewSupermicroVendorConfigManager(configFormat string, vendorOptions map[string]string) (VendorConfigManager, error) { + supermicro := &supermicroVendorConfig{} + + switch strings.ToLower(configFormat) { + case "xml": + supermicro.ConfigFormat = strings.ToLower(configFormat) + default: + return nil, UnknownConfigFormatError(strings.ToLower(configFormat)) + } + + supermicro.ConfigData = &supermicroConfig{ + BiosCfg: &supermicroBiosCfg{}, + } + + return supermicro, nil +} + +// FindMenu locates an existing SupermicroBiosCfgMenu if one exists in the ConfigData, if not +// it creates one and returns a pointer to that. +func (cm *supermicroVendorConfig) FindMenu(menuName string, menuRoot *supermicroBiosCfgMenu) (m *supermicroBiosCfgMenu) { + // root is cm.ConfigData.BiosCfg.Menus + for _, m = range menuRoot.Menus { + if m.Name == menuName { + return + } + } + + m.Name = menuName + + menuRoot.Menus = append(menuRoot.Menus, m) + + return +} + +// FindMenuSetting locates an existing SupermicroBiosCfgSetting if one exists in the +// ConfigData, if not it creates one and returns a pointer to that. +func (cm *supermicroVendorConfig) FindMenuSetting(m *supermicroBiosCfgMenu, name string) (s *supermicroBiosCfgSetting) { + for _, s = range m.Settings { + if s.Name == name { + return + } + } + + s.Name = name + + m.Settings = append(m.Settings, s) + + return +} + +func (cm *supermicroVendorConfig) Raw(name, value string, menuPath []string) { + menus := make([]*supermicroBiosCfgMenu, 0, len(menuPath)) + + for i, name := range menuPath { + var m *supermicroBiosCfgMenu + + if i == 0 { + m = cm.FindMenu(name, cm.ConfigData.BiosCfg.Menus[0]) + } else { + m = cm.FindMenu(name, menus[i-1]) + } + + menus = append(menus, m) + } +} + +func (cm *supermicroVendorConfig) Marshal() (string, error) { + switch strings.ToLower(cm.ConfigFormat) { + case "xml": + x, err := xml.Marshal(cm.ConfigData) + if err != nil { + return "", err + } + + return string(x), nil + default: + return "", UnknownConfigFormatError(strings.ToLower(cm.ConfigFormat)) + } +} + +// Generic config options + +func (cm *supermicroVendorConfig) EnableTPM() { + cm.Raw(" Security Device Support", "Enable", []string{"Trusted Computing"}) + cm.Raw(" SHA-1 PCR Bank", "Enabled", []string{"Trusted Computing"}) +} + +func (cm *supermicroVendorConfig) EnableSRIOV() { + cm.Raw("SR-IOV Support", "Enabled", []string{"Advanced", "PCIe/PCI/PnP Configuration"}) +}