// 2020 Rackspace, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // Package portaudit will range through a list of legacy Datapipe devices and generate an // output of the port configurations so we can make decisions on the network config package portaudit import ( "fmt" "strconv" "strings" "time" "github.com/360EntSecGroup-Skylar/excelize" "github.com/sirupsen/logrus" "github.rackspace.com/DCM/discover/cmdb" "github.rackspace.com/DCM/discover/network" "github.rackspace.com/DCM/netparse" "github.rackspace.com/DCM/tools" "github.rackspace.com/gorax/clients/dcxAutohost" "github.rackspace.com/gorax/clients/dcxSwitchapi" "github.rackspace.com/gorax/clients/goctk" "github.rackspace.com/gorax/clients/mydp" "github.rackspace.com/gorax/clients/passwordsafe" "github.rackspace.com/gorax/clients/raxSQL" "github.rackspace.com/gorax/clients/unipede" "github.rackspace.com/gorax/gokeys" ) type ( // Client contains all the data types needed to // initialize and use this package. Client struct { cmdb cmdb.Getter network *network.Client sql *raxSQL.Client } resultType struct { Item cmdb.Model Ports []port } port struct { netparse.IfcConfig Port tools.PortParsed DataType string } ) // pluginFunc function is executed from the gomigrate caller func pluginFunc(cfg gokeys.Settings) { client := initClient(cfg) dwDev := client.getDevices() logrus.Info("Gathered port and device info from admin.mydatapipe") result := client.cmdbDevices(dwDev) // 2.) For each record, gather network configuration for i := range result { if len(result[i].Item.Environment.Current.PortsParsed) == 0 { result[i].Ports = append(result[i].Ports, port{ DataType: "No Port Assignments", IfcConfig: netparse.IfcConfig{RawCommands: []string{"No Port Assignments"}}, }) } for _, p := range result[i].Item.Environment.Current.PortsParsed { ifcConfig, err := client.network.Config(p) if err != nil { result[i].Ports = append(result[i].Ports, port{ DataType: typeFromError(err), Port: p, IfcConfig: netparse.IfcConfig{RawCommands: []string{err.Error()}}, }) logrus.Errorf("network.ConfigsForPortsParsed:%s", err) continue } result[i].Ports = append(result[i].Ports, port{ IfcConfig: ifcConfig, Port: p, DataType: "Gathered Port Data"}) } } if err := xlsx(result, dwDev, "DP_Port_Audit"); err != nil { logrus.Error(err) } } // xlsx is a function used to convert the resultType structs into a new excel document // and store the document in the current directory. func xlsx(results []resultType, devices []DWDevice, fileName string) error { var ( file = excelize.NewFile() sheet1 = "Port_Audit" sheet2 = "Device_List" sheet1Idx = file.NewSheet(sheet1) _ = file.NewSheet(sheet2) row = 2 ) file.DeleteSheet("Sheet1") file.SetCellValue(sheet1, "A1", "CORE ID") file.SetCellValue(sheet1, "B1", "DP ID") file.SetCellValue(sheet1, "C1", "Device Name") file.SetCellValue(sheet1, "D1", "Switch Name") file.SetCellValue(sheet1, "E1", "Switch Port") file.SetCellValue(sheet1, "F1", "Switch Port Config") file.SetCellValue(sheet1, "G1", "Data Type") file.SetCellValue(sheet1, "H1", "In Port-Channel") file.SetCellValue(sheet1, "I1", "Has IP Address") for _, r := range results { for _, v := range r.Ports { file.SetCellValue(sheet1, fmt.Sprintf("A%d", row), r.Item.ID) file.SetCellValue(sheet1, fmt.Sprintf("B%d", row), r.Item.Linked.ID) file.SetCellValue(sheet1, fmt.Sprintf("C%d", row), r.Item.Device.Device.HostName) file.SetCellValue(sheet1, fmt.Sprintf("D%d", row), v.Port.SwitchName) file.SetCellValue(sheet1, fmt.Sprintf("E%d", row), v.Port.PortName) file.SetCellValue(sheet1, fmt.Sprintf("F%d", row), strings.Join(v.RawCommands, "\n ")) file.SetCellValue(sheet1, fmt.Sprintf("G%d", row), v.DataType) file.SetCellValue(sheet1, fmt.Sprintf("H%d", row), func() bool { for _, cmd := range v.RawCommands { if strings.Contains(strings.ToLower(cmd), "channel-group") { return true } } return false }()) file.SetCellValue(sheet1, fmt.Sprintf("I%d", row), func() bool { for _, cmd := range v.RawCommands { if strings.Contains(strings.ToLower(cmd), "ip address ") { return true } } return false }()) row = row + 1 } } file.SetCellValue(sheet2, "A1", "Device ID") file.SetCellValue(sheet2, "B1", "Account Number") file.SetCellValue(sheet2, "C1", "Account Name") file.SetCellValue(sheet2, "D1", "Location ID") file.SetCellValue(sheet2, "E1", "Specific Device Type") file.SetCellValue(sheet2, "F1", "Specific Device ID") file.SetCellValue(sheet2, "G1", "Rackspace DC") file.SetCellValue(sheet2, "H1", "Datapipe DC") file.SetCellValue(sheet2, "I1", "Rack ID") file.SetCellValue(sheet2, "J1", "Rack Name") file.SetCellValue(sheet2, "K1", "Disabled") row = 2 for _, r := range devices { file.SetCellValue(sheet2, fmt.Sprintf("A%d", row), r.ID) file.SetCellValue(sheet2, fmt.Sprintf("B%d", row), r.AccountNumber) file.SetCellValue(sheet2, fmt.Sprintf("C%d", row), r.AccountName) file.SetCellValue(sheet2, fmt.Sprintf("D%d", row), r.LocationID) file.SetCellValue(sheet2, fmt.Sprintf("E%d", row), r.SpecificDeviceType) file.SetCellValue(sheet2, fmt.Sprintf("F%d", row), r.SpecificDeviceID) file.SetCellValue(sheet2, fmt.Sprintf("G%d", row), r.RSShortName) file.SetCellValue(sheet2, fmt.Sprintf("H%d", row), r.DatacenterName) file.SetCellValue(sheet2, fmt.Sprintf("I%d", row), r.RackID) file.SetCellValue(sheet2, fmt.Sprintf("J%d", row), r.RackName) file.SetCellValue(sheet2, fmt.Sprintf("K%d", row), r.Disabled) row = row + 1 } file.SetActiveSheet(sheet1Idx) if err := file.SaveAs(fmt.Sprintf("%s.xlsx", fileName)); err != nil { return err } logrus.Infof("csv file written to %s", fmt.Sprintf("%s.xlsx", fileName)) return nil } func (c *Client) cmdbDevices(devices []DWDevice) []resultType { // Creating a buffered channel that will block when we have reached the // passed in limit for concurrency ticker := time.NewTicker(time.Millisecond * 500) // Create a non-blocking channel that will receive all the responses // back from validate. resChan := make(chan *resultType) // Close the channels when this function has exited. defer func() { close(resChan) }() for _, device := range devices { go func(d DWDevice) { <-ticker.C m, err := c.cmdb.Get(strconv.Itoa(d.ID)) if err != nil { logrus.Debugf("cmdb.Get:%s", err) } // now we can send the result struct through the resultsChan resChan <- &resultType{Item: m, Ports: make([]port, 0)} }(device) } // Make a list of resultTypes to store the results we are about to receive. var results []resultType // Receive the results from the resChan as the come in. for i := 0; i < len(devices); i++ { result := <-resChan results = append(results, *result) fmt.Printf("\033[2K\rGathering from MyAdmin. %d/%d complete.", len(results), len(devices)) } return results } func (c *Client) getDevices() []DWDevice { p := QueryParams{} if argCage != nil { p.CageID = *argCage } if argRack != nil { p.RackID = *argRack } if argDC != nil { p.DC = *argDC } devices, err := c.DatapipeDevices(p) if err != nil { logrus.Fatalf("client.DatapipeDevices:%s", err) } return devices } func typeFromError(err error) string { switch { case strings.Contains(strings.ToLower(err.Error()), "404 not found"): return "Switch Not Found" case strings.Contains(strings.ToLower(err.Error()), "unable to find interface"): return "Interface Not Found" default: return "Unknown Error Gathering Config" } } func initClient(cfg gokeys.Settings) *Client { sKey, err := cfg.SSO() if err != nil { logrus.Fatalf("cfg.SSO:%s", err) } iKey, err := cfg.Identity(false) if err != nil { logrus.Fatalf("cfg.Identity:%s", err) } logrus.Info("Gathering the admin.mydatapipe Service Account from PasswordSafe") pwSafe, err := passwordsafe.NewTokenClient(iKey.Password) if err != nil { logrus.Fatalf("passwordsafe.NewClient:%s", err) } mydpUser, mydpPass, err := pwSafe.Credentials(20834, "MyDP Service Account") if err != nil { logrus.Fatalf("pwSafe.Credentials:%s", err) } myDP, err := mydp.New(mydpUser, mydpPass, false) if err != nil { logrus.Fatalf("mydp.New:%s", err) } uni, err := unipede.New(sKey.Username, sKey.Password, "") if err != nil { logrus.Fatalf("unipede.New:%s", err) } fe, err := dcxSwitchapi.NewClient(sKey.Username, sKey.Password) if err != nil { logrus.Fatalf("dcxSwitchapi.NewClient:%s", err) } dcx, err := dcxAutohost.New(dcxAutohost.WithToken(iKey.Password), false) if err != nil { logrus.Fatalf("dcxAutohost.New:%s", err) } ctk, err := goctk.NewCTK(goctk.Token(iKey.Password)) if err != nil { logrus.Fatalf("goctk.NewCTK:%s", err) } netw, err := network.New(network.WithClients(fe, uni)) if err != nil { logrus.Fatalf("network.New:%s", err) } sql, err := raxSQL.NewMSSQL(raxSQL.HostODSCore, raxSQL.AuthWindows{Username: sKey.Username, Password: sKey.Password, Domain: "RACKSPACE"}, raxSQL.WithTimeout(10800), ) if err != nil { logrus.Fatalf("raxSQL.NewMSSQL:%s", err) } return &Client{ cmdb: &cmdb.DP{ MyDP: myDP, CTK: ctk, DCX: dcx, DeviceMap: make(map[string]cmdb.DeviceInfo)}, network: netw, sql: sql, } }