package coreAPI import ( "encoding/json" "errors" "fmt" "net" "net/http" "net/url" "strconv" "strings" "time" "github.com/sirupsen/logrus" ) type ( // DeviceInfo : Defines the data on a CORE device DeviceInfo struct { ID string `json:"_id"` AccountInfo AccountInfo `json:"account"` ChassisSerial string `json:"chassis_serial"` Context bool `json:"context"` DataCenter string `json:"datacenter"` DracIP string `json:"drac_ip"` DracIPs []string `json:"drac_ips"` FailoverDevice *FailoverDevice `json:"failover_device"` LastCoreImport string `json:"last_core_import"` Name string `json:"name"` PlatformModel string `json:"platform_model"` PlatformName string `json:"platform_name"` PrimaryIP string `json:"primary_ip"` Serial string `json:"serial"` Skus []string `json:"skus"` Status string `json:"status"` AuthorizeUsers []string `json:"authorized_users"` DeviceAuth *DeviceAuth `json:"device_credentials"` Uptime interface{} `json:"uptime"` Version string `json:"version"` } // DeviceAuth : Defines credentials used for accessing a device DeviceAuth struct { TacacsPassword string `json:"tacacs_password"` TacacsEnable string `json:"tacacs_enable"` TacacsUser string `json:"tacacs_user"` } // AccountInfo : Defines the data on a CORE account AccountInfo struct { Name string `json:"name"` Number string `json:"number"` SegmentName string `json:"segment_name"` ServiceLevel string `json:"service_level"` Status string `json:"status"` } // FailoverDevice : Defines the data on a Failover Device in an HA pair FailoverDevice struct { FailoverID string `json:"failover_id"` PrimaryIP string `json:"primary_ip"` } ) // CoreDevice : Structure for device information from coreAPI:core/device type CoreDevice struct { ID int `json:"_id"` AccountExec string `json:"account.account_exec"` AccountIsInternal bool `json:"account.is_internal"` AccountName string `json:"account.name"` AccountNetsecTeam string `json:"account.netsec_team"` AccountNumber string `json:"account.number"` AccountPrimaryLeadTech string `json:"account.primary_lead_tech"` AccountSegmentName string `json:"account.segment_name"` AccountServiceLevel string `json:"account.service_level"` AccountSkus []int `json:"account.skus"` AccountStatus string `json:"account.status"` BackupnetIP string `json:"backupnet_ip"` DataCenter string `json:"datacenter"` DracGateway string `json:"drac_gateway"` DracIP string `json:"drac_ip"` DracIPs []string `json:"drac_ips"` DracNetwork string `json:"drac_network"` FailoverDeviceID *int `json:"failover_device.failover_id"` FailoverPrimaryIP string `json:"failover_device.primary_ip"` Gateway string `json:"gateway"` IsUkAccount bool `json:"is_uk_account"` Location string `json:"location"` Name string `json:"name"` PlatformModel string `json:"platform_model"` PlatformName string `json:"platform_name"` PrimaryIP string `json:"primary_ip"` ServicesNetIps []string `json:"services_net_ips"` Skus []int `json:"skus"` Status string `json:"status"` SupportTeam string `json:"account.support_team"` SwitchportAssignments []string `json:"switchport_assignments"` } // Device : Return information on a specific device from coreAPI func (c *Client) Device(d interface{}) (CoreDevice, error) { var ( data = new(struct { Result CoreDevice `json:"result"` }) device string ) switch i := d.(type) { case string: device = i case int: device = strconv.Itoa(i) default: return data.Result, errors.New("device number must be an integer or string") } req, err := http.NewRequest(http.MethodGet, URLBase+"/core/device/"+device, nil) if err != nil { return data.Result, err } c.reqHeaders(req) res, err := http.DefaultClient.Do(req) if res != nil { defer func() { if defErr := res.Body.Close(); defErr != nil { err = fmt.Errorf("%s:%s", err, defErr) } }() } if err != nil { return data.Result, err } // Device data if err := json.NewDecoder(res.Body).Decode(data); err != nil { return data.Result, err } return data.Result, nil } // DeviceInfo : Takes a platform and device number, returns the device info if found. If no match is found it returns an error. // WARNING: This actually pulls from ndapi, not coreapi func (c *Client) DeviceInfo(platform string, device int, refresh bool) (*DeviceInfo, error) { if c.Token == nil { return nil, errors.New("core Token Missing") } req, err := http.NewRequest(http.MethodGet, URLBase+"/ndapi/getDevice/"+platform+"/"+strconv.Itoa(device), nil) if err != nil { return nil, err } if refresh { req.URL.RawQuery = url.Values{"refresh": []string{"true"}}.Encode() } c.reqHeaders(req) res, err := http.DefaultClient.Do(req) if res != nil { defer func() { if defErr := res.Body.Close(); defErr != nil { err = fmt.Errorf("%s:%s", err, defErr) } }() } if err != nil { return nil, err } if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("coreAPI:DeviceInfo:error retrieving information %d - %s", res.StatusCode, res.Status) } var data struct { Result *DeviceInfo `json:"result"` } // Device data if err := json.NewDecoder(res.Body).Decode(&data); err != nil { return data.Result, err } if data.Result == nil { return nil, fmt.Errorf("coreAPI:DeviceInfo:no data found for %s:%d", platform, device) } return data.Result, nil } // FindDevice : Runs through a list of platforms, returns the device info and platform of the first match. If no matches are found an error is returned. func (c *Client) FindDevice(device int, platforms []string, refresh bool) (*DeviceInfo, string, error) { for _, platform := range platforms { if d, err := c.DeviceInfo(platform, device, refresh); err == nil { return d, platform, nil } } return nil, "", fmt.Errorf("coreAPI:FindDevice:unable to find %d in %s", device, strings.Join(platforms, ",")) } // IsPix : Returns true if the device is a Cisco PIX func (d DeviceInfo) IsPix() bool { return strings.Contains(strings.ToLower(d.PlatformModel), "pix") } // IsASA : Returns true if the device is a Cisco ASA func (d DeviceInfo) IsASA() bool { return strings.Contains(strings.ToLower(d.PlatformModel), "asa") } // IsF5 : Returns true if the device is a F5 BigIP func (d DeviceInfo) IsF5() bool { return strings.Contains(strings.ToLower(d.PlatformModel), "f5") } // IsADX : Returns true if the device is a Brocade ADX func (d DeviceInfo) IsADX() bool { return strings.Contains(strings.ToLower(d.PlatformModel), "adx") } // Decommissioned : Returns true if the device is currently in Decommission or Computer No Longer Active status func (d DeviceInfo) Decommissioned() bool { return d.Status == "Decommission" || d.Status == "Computer No Longer Active" } // ActiveMgmt: Ranges through a list of management IPs to determine if we can open // a socket to them, and returns the first one with an open SSH port. func (d DeviceInfo) ActiveMgmt() (ipAddr string) { // Cover any scenarios where the drac_ips list is not available. if len(d.DracIPs) < 2 { return d.DracIP } for _, ip := range d.DracIPs { conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, "22"), time.Second) if err != nil { logrus.Infof("ConnErr:%s (%s):%s", d.ID, ip, err) } if conn != nil { if err := conn.Close(); err != nil { logrus.Errorf("ConnCloseErr:%s (%s):%s", d.ID, ip, err) return } logrus.Infof("%s:Active:%s:Was:%s", d.ID, ip, d.DracIP) ipAddr = ip break } } return }