// 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 zsp import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strconv" "time" "github.com/sirupsen/logrus" "github.rackspace.com/DCM/gomigrate/core" "github.rackspace.com/DCM/gomigrate/shared" "github.rackspace.com/gorax/clients/goctk" "github.rackspace.com/gorax/clients/mongodbAPI" "github.rackspace.com/gorax/gokeys" ) type ( // Client contains all the data types needed to // initialize and use this package. Client struct { Token gokeys.Credential ctk *goctk.Client mgo *mongodbAPI.Client } // RequestBody is the data structure required by the ZSP API // to POST a new ZSP unlock. RequestBody struct { User string `json:"user"` Account int `json:"account"` Ticket string `json:"ticket"` } // ResponseBody is the type returned from the ZSP API POST request. ResponseBody struct { ID string `json:"ID"` Name string `json:"Name"` } ) var ( argM *[]string argA *[]string ) // 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("zsp", "ZSP Unlock for account(s) or MG(s).") argM = shared.ArgMoveGroups(cmd) argA = shared.ArgAccounts(cmd) return core.Plugin{CMD: cmd, Func: pluginFunc} } // pluginFunc function is executed from the gomigrate caller func pluginFunc(cfg gokeys.Settings) { // Pull an identity token from the users gokeys keychain iKey, err := cfg.Identity(false) if err != nil { logrus.Fatal(err) } cKey, err := cfg.Core(false) if err != nil { logrus.Fatal(err) } ctkC, err := goctk.NewCTK(goctk.Token(cKey.Password)) if err != nil { logrus.Fatal(err) } mgoC := mongodbAPI.New(mongodbAPI.WithToken(cKey.Password), "migration", false) c := Client{ Token: iKey, mgo: mgoC, ctk: ctkC, } moveGroupList := *argM accountList := *argA if len(moveGroupList) == 0 && len(accountList) == 0 { logrus.Fatal("At least one move-group or account number is required.") } if len(moveGroupList) != 0 { for _, moveGroup := range moveGroupList { accountUnlocked := make(map[int]bool) tickets, err := c.findTickets(moveGroup) if err != nil { logrus.Errorf("Unable to find tickets for %s - %s", moveGroup, err) } logrus.Infof("Running ZSP Unlock for %s. Found %d tickets...", moveGroup, len(tickets)) for _, t := range tickets { if _, ok := accountUnlocked[t.AccountNum]; ok { continue } time.Sleep(time.Second * 1) resp, err := c.ZSPUnlock(RequestBody{User: cKey.Username, Account: t.AccountNum, Ticket: t.Ticket}) if err != nil { logrus.Errorf("%s:%d:%s::Unlock Failed. Pausing for 5 seconds - %s", moveGroup, t.AccountNum, t.Ticket, err) time.Sleep(time.Second * 5) continue } accountUnlocked[t.AccountNum] = true logrus.Infof("%s:%s Unlock Successful - ID:%s", moveGroup, t.Ticket, resp.ID) } } } if len(accountList) != 0 { for _, account := range accountList { accountID, err := strconv.Atoi(account) if err != nil { logrus.Errorf("Atoi convert error account:%s - %s", account, err) continue } tickets, err := c.ticketSearch([]int{accountID}) if err != nil { logrus.Errorf("CORE Error finding for ticket for %s - %s", account, err) continue } resp, err := c.ZSPUnlock(RequestBody{User: cKey.Username, Account: accountID, Ticket: tickets[0].Number}) if err != nil { logrus.Errorf("%d:%s::Unlock Failed. Pausing for 5 seconds - %s", accountID, tickets[0].Number, err) time.Sleep(time.Second * 5) continue } logrus.Infof("%s:Unlock Successful - ID:%s", tickets[0].Number, resp.ID) } } } // ZSPUnlock function will interface with the ZSP service API to POST data. func (c Client) ZSPUnlock(body RequestBody) (*ResponseBody, error) { var headers = map[string]string{ "Accept": "application/json", "Content-Type": "application/json", "X-Auth-Token": c.Token.Password, } btyPayload, err := json.Marshal(body) if err != nil { logrus.Errorf("zsp:post:Marshal:%s", err) return nil, err } ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() // Build the request req, err := http.NewRequestWithContext(ctx, http.MethodPost, shared.ZSPAPI, bytes.NewReader(btyPayload)) if err != nil { logrus.Errorf("zsp:post:NewRequest:%s", err) return nil, err } for key, value := range headers { req.Header.Set(key, value) } // Send the request via a client // Do sends an HTTP request and // returns an HTTP response r, err := http.DefaultClient.Do(req) if err != nil { logrus.Errorf("zsp:post:Do:%s", err) return nil, err } if r != nil { defer func() { if defErr := r.Body.Close(); defErr != nil { err = fmt.Errorf("%s/%s", err, defErr) } }() } if r.StatusCode != http.StatusOK { return nil, fmt.Errorf("zsp:post:resp_err:%s", r.Status) } // Decode the json response back payload := new(ResponseBody) if err = json.NewDecoder(r.Body).Decode(&payload); err != nil { return payload, err } return payload, nil }