// 2019 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 autodiscover is the translations script used to run environment true-up on a // customer environment. package autodiscover import ( "fmt" "log" "os" "strings" "time" "github.com/jedib0t/go-pretty/v6/table" "github.com/sirupsen/logrus" input "github.com/tcnksm/go-input" "github.rackspace.com/DCM/gomigrate/core" "github.rackspace.com/DCM/gomigrate/shared" migrationMongo "github.rackspace.com/DCM/migration/pkg/collection/mongo" "github.rackspace.com/gorax/clients/dcxAutohost" "github.rackspace.com/gorax/clients/migrationAPI" "github.rackspace.com/gorax/gokeys" ) var ( argEnvironments *[]string flagMakeChanges *bool flagProcessMigPorts *bool ) // Plugin function will return a argparse.Command type back to the parent parser // nolint:typecheck func Plugin(p *core.Parser) core.Plugin { // Create a argument for the DCX Translations logic cmd := p.NewCommand("auto_discover", "Auto True-Up an Environment in EM") argEnvironments = shared.ArgEnvironments(cmd) flagMakeChanges = shared.ArgFlag(cmd, "make-changes", "make data changes in EM") flagProcessMigPorts = shared.ArgFlag(cmd, "process-migration-ports", "process migration ports") return core.Plugin{CMD: cmd, Func: pluginFunc} } // pluginFunc function will prompt for a list of move-groups, then query the migration database // for a listing of network devices to run the failover_device call on. func pluginFunc(cfg gokeys.Settings) { // Pull a Identity token from the users gokeys keychain iKey, err := cfg.Identity(false) if err != nil { log.Fatal(err) } // Pull a core token from the gokeys keychain coreKey, err := cfg.Core(false) if err != nil { log.Fatal(err) } sso, err := cfg.SSO() if err != nil { log.Fatal(err) } // Create a UI input and set the reader/writer of the interface ui := &input.UI{Writer: os.Stdout, Reader: os.Stdin} optsNoReq := &input.Options{Required: false, HideOrder: true} envList := *argEnvironments if len(envList) == 0 { log.Fatalf("no environments found in %v", envList) } dcx, err := dcxAutohost.New(dcxAutohost.WithToken(iKey.Password), false) if err != nil { log.Fatal(err) } // invert value for client method flagDryRun := true if flagMakeChanges != nil { flagDryRun = !*flagMakeChanges } results := make([]migrationMongo.StatusEnvTrueUp, 0) for _, e := range envList { ignoreList := make([]string, 0) // Prompt the user for a list of devices to ignore, should be in format 123456,921291 ignore, err := ui.Ask("Enter a comma-separated list of devices to ignore. [Enter for none]", optsNoReq) if err != nil { log.Fatal(err) } if strings.Replace(ignore, " ", "", -1) != "" { ignoreList = strings.Split(strings.Replace(ignore, " ", "", -1), ",") } envResp, err := dcx.EnvironmentAutoDiscoverExnet(e, ignoreList, &flagDryRun, flagProcessMigPorts) if envResp == nil { envResp = &dcxAutohost.EnvironmentAutoDiscover{} } r := migrationMongo.StatusEnvTrueUp{ Number: e, Username: sso.Username, Timestamp: time.Now().Unix(), Result: *envResp, } if err != nil { switch errType := err.(type) { case dcxAutohost.DCXErr: errPrint(e, errType.Errors) r.Error = strings.Join(errType.Errors, "\n") default: errPrint(e, []string{err.Error()}) r.Error = err.Error() } if !flagDryRun { results = append(results, r) } continue } prettyPrint(e, flagDryRun, envResp) if !flagDryRun { results = append(results, r) } } // post results to status endpoint if len(results) > 0 { logrus.Debug("Recording results to DB ...") statusUpdate(coreKey.Password, iKey.Password, results) } } // errPrint func will take a structured type of response data and render a table // output to stdout. func errPrint(env string, data []string) { t := table.NewWriter() t.SetOutputMirror(os.Stdout) t.AppendHeader(table.Row{fmt.Sprintf("%s ERRORS", env)}) for i, d := range data { t.AppendRow(table.Row{fmt.Sprintf("%d.) %s", i+1, d)}) } t.SetStyle(table.StyleDefault) t.Render() } // prettyPrint func will take a structured type of response data and render a table // output to stdout. func prettyPrint(env string, dryRun bool, data *dcxAutohost.EnvironmentAutoDiscover) { runType := "Dry Run" if !dryRun { runType = "Prod Run" } t := table.NewWriter() t.SetOutputMirror(os.Stdout) t.AppendHeader(table.Row{env, "Device", "Actions/Errors"}) for _, d := range data.Devices { t.AppendRow(table.Row{ runType, d.DeviceNumber, func() string { if len(d.Errors) > 0 { return strings.Join(d.Errors, "\n") } return strings.Join(d.Actions, "\n") }(), }) t.AppendSeparator() } t.SortBy([]table.SortBy{{Name: "Device", Mode: table.Asc}}) t.SetStyle(table.StyleDefault) t.Render() } func statusUpdate(coreToken, identityToken string, results []migrationMongo.StatusEnvTrueUp) { collection, movegroup := shared.GetEnvMetadata(results[0].Number, coreToken) m := migrationAPI.New(identityToken, false) err := m.StatusUpdateEnvTrueUp(collection, movegroup, results) if err != nil { logrus.Errorf("error updating status results: %s", err.Error()) } }