func (i *Ilo) GenerateCSR(cert *cfgresources.HTTPSCertAttributes) ([]byte, error) {
	csrConfig := &csr{
		Country:          cert.CountryCode,
		State:            cert.StateName,
		Locality:         cert.Locality,
		CommonName:       cert.CommonName,
		OrganizationName: cert.OrganizationName,
		OrganizationUnit: cert.OrganizationUnit,
		IncludeIP:        1,  
		Method:           "create_csr",
		SessionKey:       i.sessionKey,
	}
	payload, err := json.Marshal(csrConfig)
	if err != nil {
		return []byte{}, err
	}
	endpoint := "json/csr"
	statusCode, response, err := i.post(endpoint, payload)
	if statusCode == 500 {
		return []byte{}, fmt.Errorf("CSR being generated, retry later")
	}
	if statusCode != 200 {
		return []byte{}, fmt.Errorf("Unexpected return code: %d", statusCode)
	}
	if err != nil {
		return []byte{}, err
	}
	var r = new(csrResponse)
	err = json.Unmarshal(response, r)
	if err != nil {
		return []byte{}, err
	}
	return []byte(r.CsrPEM), nil
}
GenerateCSR generates a CSR request on the BMC.
If its the first CSR attempt - the BMC is going to take a while to generate the CSR,
the response will be a 500 with the body	{"message":"JS_CERT_NOT_AVAILABLE","details":null}
If the configuration for the Subject has not changed and the CSR is ready a CSR is returned.
package ilo
import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/bmc-toolbox/bmclib/cfgresources"
	"github.com/bmc-toolbox/bmclib/devices"
	"github.com/bmc-toolbox/bmclib/internal"
	"github.com/bmc-toolbox/bmclib/internal/helper"
)
var _ devices.Configure = (*Ilo)(nil)
func (i *Ilo) Resources() []string {
	return []string{
		"user",
		"syslog",
		"license",
		"ntp",
		"ldap_group",
		"ldap",
		"network",
		"power",
		"https_cert",
	}
}
func (i *Ilo) ApplyCfg(config *cfgresources.ResourcesConfig) (err error) {
	if i.sessionKey == "" {
		msg := "Expected sessionKey not found, unable to configure BMC."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", "Login()",
		)
		return errors.New(msg)
	}
	return nil
}
func (i *Ilo) isRoleValid(role string) bool {
	validRoles := []string{"admin", "user"}
	for _, v := range validRoles {
		if role == v {
			return true
		}
	}
	return false
}
func userExists(user string, usersInfo []UserInfo) (userInfo UserInfo, exists bool) {
	for _, userInfo := range usersInfo {
		if userInfo.UserName == user || userInfo.LoginName == user {
			return userInfo, true
		}
	}
	return userInfo, false
}
func ldapGroupExists(group string, directoryGroups []DirectoryGroups) (directoryGroup DirectoryGroups, exists bool) {
	for _, directoryGroup := range directoryGroups {
		if directoryGroup.Dn == group {
			return directoryGroup, true
		}
	}
	return directoryGroup, false
}
func (i *Ilo) User(users []*cfgresources.User) (err error) {
	existingUsers, err := i.queryUsers()
	if err != nil {
		msg := "Unable to query existing users"
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", "applyUserParams",
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	for _, user := range users {
		var postPayload bool
		if user.Name == "" {
			msg := "User resource expects parameter: Name."
			i.log.V(1).Info(msg, "step", "applyUserParams")
			return errors.New(msg)
		}
		if user.Password == "" {
			msg := "User resource expects parameter: Password."
			i.log.V(1).Info(msg, "step", "applyUserParams", "Username", user.Name)
			return errors.New(msg)
		}
		if !i.isRoleValid(user.Role) {
			msg := "User resource Role must be declared and a must be a valid role: 'admin' OR 'user'."
			i.log.V(1).Info(msg, "step", "applyUserParams", "Username", user.Name)
			return errors.New(msg)
		}
		userinfo, uexists := userExists(user.Name, existingUsers)
		userinfo.SessionKey = i.sessionKey
		if user.Enable {
			userinfo.RemoteConsPriv = 1
			userinfo.VirtualMediaPriv = 1
			userinfo.ResetPriv = 1
			userinfo.UserPriv = 1
			userinfo.Password = user.Password
			if user.Role == "admin" {
				userinfo.ConfigPriv = 1
				userinfo.LoginPriv = 1
			} else if user.Role == "user" {
				userinfo.ConfigPriv = 0
				userinfo.LoginPriv = 0
			}
			if uexists {
				userinfo.Method = "mod_user"
				userinfo.UserID = userinfo.ID
				userinfo.UserName = user.Name
				userinfo.LoginName = user.Name
				userinfo.Password = user.Password
			} else {
				userinfo.Method = "add_user"
				userinfo.UserName = user.Name
				userinfo.LoginName = user.Name
				userinfo.Password = user.Password
			}
			postPayload = true
		}
		if !user.Enable && uexists {
			userinfo.Method = "del_user"
			userinfo.UserID = userinfo.ID
			msg := "User disabled in config, will be removed."
			i.log.V(1).Info(msg,
				"IP", i.ip,
				"Model", i.HardwareType(),
				"User", user.Name,
			)
			postPayload = true
		}
		if postPayload {
			payload, err := json.Marshal(userinfo)
			if err != nil {
				msg := "Unable to marshal userInfo payload to set User config."
				i.log.V(1).Info(msg,
					"IP", i.ip,
					"Model", i.HardwareType(),
					"step", helper.WhosCalling(),
					"User", user.Name,
					"Error", internal.ErrStringOrEmpty(err),
				)
				continue
			}
			endpoint := "json/user_info"
			statusCode, response, err := i.post(endpoint, payload)
			if err != nil || statusCode != 200 {
				msg := "POST request to set User config returned error."
				i.log.V(1).Info(msg,
					"IP", i.ip,
					"Model", i.HardwareType(),
					"endpoint", endpoint,
					"step", helper.WhosCalling(),
					"User", user.Name,
					"StatusCode", statusCode,
					"response", string(response),
					"Error", internal.ErrStringOrEmpty(err),
				)
				continue
			}
			i.log.V(1).Info("User parameters applied.",
				"IP", i.ip,
				"Model", i.HardwareType(),
				"User", user.Name,
			)
		}
	}
	return err
}
func (i *Ilo) Syslog(cfg *cfgresources.Syslog) (err error) {
	var port int
	enable := 1
	if cfg.Server == "" {
		msg := "Syslog resource expects parameter: Server."
		i.log.V(1).Info(msg, "step", helper.WhosCalling())
		return errors.New(msg)
	}
	if cfg.Port == 0 {
		i.log.V(1).Info("Syslog resource port set to default: 514.", "step", helper.WhosCalling())
		port = 514
	} else {
		port = cfg.Port
	}
	if !cfg.Enable {
		enable = 0
		i.log.V(1).Info("Syslog resource declared with disable.", "step", helper.WhosCalling())
	}
	remoteSyslog := RemoteSyslog{
		SyslogEnable: enable,
		SyslogPort:   port,
		Method:       "syslog_save",
		SyslogServer: cfg.Server,
		SessionKey:   i.sessionKey,
	}
	payload, err := json.Marshal(remoteSyslog)
	if err != nil {
		msg := "Unable to marshal RemoteSyslog payload to set Syslog config."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	endpoint := "json/remote_syslog"
	statusCode, response, err := i.post(endpoint, payload)
	if err != nil || statusCode != 200 {
		msg := "POST request to set User config returned error."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"endpoint", endpoint,
			"step", helper.WhosCalling(),
			"StatusCode", statusCode,
			"response", string(response),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	i.log.V(1).Info("Syslog parameters applied.", "IP", i.ip, "Model", i.HardwareType())
	return err
}
func (i *Ilo) SetLicense(cfg *cfgresources.License) (err error) {
	if cfg.Key == "" {
		msg := "License resource expects parameter: Key."
		i.log.V(1).Info(msg, "step", helper.WhosCalling())
		return errors.New(msg)
	}
	license := LicenseInfo{
		Key:        cfg.Key,
		Method:     "activate",
		SessionKey: i.sessionKey,
	}
	payload, err := json.Marshal(license)
	if err != nil {
		msg := "Unable to marshal License payload to activate License."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	endpoint := "json/license_info"
	statusCode, response, err := i.post(endpoint, payload)
	if err != nil || statusCode != 200 {
		msg := "POST request to set User config returned error."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"endpoint", endpoint,
			"step", helper.WhosCalling(),
			"StatusCode", statusCode,
			"response", string(response),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	i.log.V(1).Info("License activated.", "IP", i.ip, "Model", i.HardwareType())
	return err
}
func (i *Ilo) Ntp(cfg *cfgresources.Ntp) (err error) {
	enable := 1
	if cfg.Server1 == "" {
		msg := "NTP resource expects parameter: server1."
		i.log.V(1).Info(msg, "step", helper.WhosCalling())
		return errors.New(msg)
	}
	if cfg.Timezone == "" {
		msg := "NTP resource expects parameter: timezone."
		i.log.V(1).Info(msg, "step", helper.WhosCalling())
		return errors.New(msg)
	}
	var timezones map[string]int
	if i.HardwareType() == "ilo5" {
		timezones = TimezonesIlo5
	} else {
		timezones = TimezonesIlo4
	}
	_, validTimezone := timezones[cfg.Timezone]
	if !validTimezone {
		msg := "NTP resource a valid timezone parameter, for valid timezones see hp/ilo/model.go"
		i.log.V(1).Info(msg, "step", helper.WhosCalling(), "UnknownTimezone", cfg.Timezone)
		return errors.New(msg)
	}
	if !cfg.Enable {
		enable = 0
		i.log.V(1).Info("NTP resource declared with disable.", "step", helper.WhosCalling())
	}
	existingConfig, err := i.queryNetworkSntp()
	if err != nil {
		msg := "Unable to query existing config"
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	networkSntp := NetworkSntp{
		Interface:                   existingConfig.Interface,
		PendingChange:               existingConfig.PendingChange,
		NicWcount:                   existingConfig.NicWcount,
		TzWcount:                    existingConfig.TzWcount,
		Ipv4Disabled:                0,
		Ipv6Disabled:                0,
		DhcpEnabled:                 enable,
		Dhcp6Enabled:                enable,
		UseDhcpSuppliedTimeServers:  0,  
		UseDhcp6SuppliedTimeServers: 0,
		Sdn1WCount:                  existingConfig.Sdn1WCount,
		Sdn2WCount:                  existingConfig.Sdn2WCount,
		TimePropagate:               existingConfig.TimePropagate,
		SntpServer1:                 cfg.Server1,
		SntpServer2:                 cfg.Server2,
		OurZone:                     timezones[cfg.Timezone],
		Method:                      "set_sntp",
		SessionKey:                  i.sessionKey,
	}
	payload, err := json.Marshal(networkSntp)
	if err != nil {
		msg := "Unable to marshal NetworkSntp payload to set NTP config."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	endpoint := "json/network_sntp"
	statusCode, response, err := i.post(endpoint, payload)
	if err != nil || statusCode != 200 {
		msg := "POST request to set NTP config returned error."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"endpoint", endpoint,
			"step", helper.WhosCalling(),
			"StatusCode", statusCode,
			"response", string(response),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	i.log.V(1).Info("NTP parameters applied.", "IP", i.ip, "Model", i.HardwareType())
	return err
}
func (i *Ilo) LdapGroups(cfgGroups []*cfgresources.LdapGroup, cfgLdap *cfgresources.Ldap) (err error) {
	directoryGroups, err := i.queryDirectoryGroups()
	if err != nil {
		msg := "Unable to query existing Ldap groups"
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return errors.New(msg)
	}
	for _, group := range cfgGroups {
		var postPayload bool
		if group.Group == "" {
			msg := "Ldap resource parameter Group required but not declared."
			i.log.V(1).Info(msg,
				"Model", i.HardwareType(),
				"step", helper.WhosCalling(),
				"Ldap role", group.Role,
			)
			return errors.New(msg)
		}
		if !i.isRoleValid(group.Role) {
			msg := "Ldap resource Role must be a valid role: admin OR user."
			i.log.V(1).Info(msg,
				"Model", i.HardwareType(),
				"step", helper.WhosCalling(),
				"Ldap role", group.Role,
			)
			return errors.New(msg)
		}
		groupDn := group.Group
		directoryGroup, gexists := ldapGroupExists(groupDn, directoryGroups)
		directoryGroup.Dn = groupDn
		directoryGroup.SessionKey = i.sessionKey
		if group.Enable {
			directoryGroup.LoginPriv = 1
			directoryGroup.RemoteConsPriv = 1
			directoryGroup.VirtualMediaPriv = 1
			directoryGroup.ResetPriv = 1
			if group.Role == "admin" {
				directoryGroup.ConfigPriv = 1
				directoryGroup.UserPriv = 1
			} else if group.Role == "user" {
				directoryGroup.ConfigPriv = 0
				directoryGroup.UserPriv = 0
			}
			if gexists {
				directoryGroup.Method = "mod_group"
			} else {
				directoryGroup.Method = "add_group"
			}
			postPayload = true
		}
		if !group.Enable && gexists {
			directoryGroup.Method = "del_group"
			i.log.V(1).Info("Ldap role group disabled in config, will be removed.",
				"IP", i.ip,
				"Model", i.HardwareType(),
				"User", group.Group,
			)
			postPayload = true
		}
		if postPayload {
			payload, err := json.Marshal(directoryGroup)
			if err != nil {
				i.log.V(1).Info("Unable to marshal directoryGroup payload to set LdapGroup config.",
					"IP", i.ip,
					"Model", i.HardwareType(),
					"step", helper.WhosCalling(),
					"Group", group.Group,
					"Error", internal.ErrStringOrEmpty(err),
				)
				continue
			}
			endpoint := "json/directory_groups"
			statusCode, response, err := i.post(endpoint, payload)
			if err != nil || statusCode != 200 {
				i.log.V(1).Info("POST request to set User config returned error.",
					"IP", i.ip,
					"Model", i.HardwareType(),
					"endpoint", endpoint,
					"step", helper.WhosCalling(),
					"Group", group.Group,
					"StatusCode", statusCode,
					"response", string(response),
					"Error", internal.ErrStringOrEmpty(err),
				)
				continue
			}
			i.log.V(1).Info("LdapGroup parameters applied.",
				"IP", i.ip,
				"Model", i.HardwareType(),
				"User", group.Group,
			)
		}
	}
	return err
}
func (i *Ilo) Ldap(cfg *cfgresources.Ldap) (err error) {
	if cfg.Server == "" {
		msg := "Ldap resource parameter Server required but not declared."
		i.log.V(1).Info(msg,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
		)
		return errors.New(msg)
	}
	if cfg.Port == 0 {
		msg := "Ldap resource parameter Port required but not declared."
		i.log.V(1).Info(msg,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
		)
		return errors.New(msg)
	}
	if cfg.BaseDn == "" {
		msg := "Ldap resource parameter BaseDn required but not declared."
		i.log.V(1).Info(msg,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
		)
		return errors.New(msg)
	}
	var enable int
	if !cfg.Enable {
		enable = 0
	} else {
		enable = 1
	}
	directory := Directory{
		ServerAddress:         cfg.Server,
		ServerPort:            cfg.Port,
		UserContexts:          []string{cfg.BaseDn},
		AuthenticationEnabled: enable,
		LocalUserAcct:         1,
		EnableGroupAccount:    1,
		EnableKerberos:        0,
		EnableGenericLdap:     enable,
		Method:                "mod_dir_config",
		SessionKey:            i.sessionKey,
	}
	payload, err := json.Marshal(directory)
	if err != nil {
		i.log.V(1).Info("Unable to marshal directory payload to set Ldap config.",
			"IP", i.ip,
			"Model", i.HardwareType(),
			"step", helper.WhosCalling(),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return err
	}
	endpoint := "json/directory"
	statusCode, response, err := i.post(endpoint, payload)
	if err != nil || statusCode != 200 {
		msg := "POST request to set Ldap config returned error."
		i.log.V(1).Info(msg,
			"IP", i.ip,
			"Model", i.HardwareType(),
			"endpoint", endpoint,
			"step", helper.WhosCalling(),
			"StatusCode", statusCode,
			"response", string(response),
			"Error", internal.ErrStringOrEmpty(err),
		)
		return err
	}
	i.log.V(1).Info("Ldap parameters applied.", "IP", i.ip, "Model", i.HardwareType())
	return err
}