// 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 link import ( "context" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "github.rackspace.com/gorax/clients/goctk" "github.com/sirupsen/logrus" "github.rackspace.com/DCM/gomigrate/core" "github.rackspace.com/DCM/gomigrate/shared" "github.rackspace.com/gorax/clients/mydp" "github.rackspace.com/gorax/gokeys" ) var ( argD *[]int 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 command and argument for the ip audit cmd := p.NewCommand("link", "Link devices between CORE and MyAdmin.") argD = shared.ArgDevices(cmd, "Two devices to link. Example, ") argA = shared.ArgAccounts(cmd, "Two accounts to link. Example, ") return core.Plugin{CMD: cmd, Func: pluginFunc} } type ( client struct { mydp *mydp.Client dpDevice int ctkDevice int dpAccount int ctkAccount int ctk *goctk.Client ctkToken string } ) // pluginFunc function is executed from the gomigrate caller func pluginFunc(cfg gokeys.Settings) { c, err := initClient(cfg) if err != nil { logrus.Fatal(err) } if c.ctkDevice != 0 && c.dpDevice != 0 { logrus.Info("Fetching device info from CORE and admin.mydatapipe.com...") // Get the device details from MyAdmin dpd, err := c.mydp.DeviceByDeviceID(c.dpDevice) if err != nil { logrus.Fatalf("mydp.DeviceByDeviceID(%d):%s", c.dpDevice, err) } // Get the device details from CORE cdr, err := c.ctk.ComputerQueryLight([]int{c.ctkDevice}) if err != nil { logrus.Fatalf("ctk.ComputerQueryLight(%d):%s", c.ctkDevice, err) } if len(cdr.Result) == 0 { logrus.Fatalf("ctk.ComputerQueryLight(%d):Device not found", c.ctkDevice) } cd := cdr.Result[0] if err := c.linkDPtoCOREDevice(dpd); err != nil { logrus.Fatal(err) } logrus.Infof("CORE link successful: %d -> %s [ACCT:%s] (%s)", c.ctkDevice, dpd.DeviceID, dpd.CustomerID, dpd.SpecificDeviceType) if err := c.linkCOREtoDPDevice(cd); err != nil { logrus.Fatal(err) } logrus.Infof("MyDP link successful: %s -> %d [ACCT:%s] (%s)", dpd.DeviceID, c.ctkDevice, cd.AccountNumber, cd.PlatformModel) } if c.ctkAccount != 0 && c.dpAccount != 0 { resp, err := c.mydp.PutRSCustomerID(c.dpAccount, c.ctkAccount) if err != nil { logrus.Fatalf("mydp.PutRSCustomerID(%d, %d):Error(%s)", c.ctkAccount, c.dpAccount, err) } if err := c.linkCOREtoDPAccount(); err != nil { logrus.Fatalf("linkCOREtoDPAccount(%d, %d):Error(%s)", c.ctkAccount, c.dpAccount, err) } logrus.Infof("MyDP link successful: %s:%s -> RS Account:%s", resp.CustomerID, resp.CustomerName, resp.RSCoreCustomerID) } } type ( methodAddReq struct { Class string `json:"class"` LoadArg string `json:"load_arg"` Method string `json:"method"` Args []string `json:"args"` KeywordArgs struct { } `json:"keyword_args"` } methodAddResp struct { Count int `json:"count"` LoadArg string `json:"load_arg"` Success bool `json:"success"` Method string `json:"method"` Limit int `json:"limit"` Result []json.RawMessage `json:"result"` Offset int `json:"offset"` Class string `json:"class"` } methodAddRes struct { ClassType string `json:"class_type"` ClassName string `json:"class_name"` LoadValue int `json:"load_value"` LoadKey string `json:"load_key"` Attributes string `json:"attributes"` Methods string `json:"methods"` } ) func (c *client) linkCOREtoDPDevice(cd *goctk.ComputerLight) error { _, err := c.mydp.ModifyServer(mydp.ServerEdit{ DeviceID: c.dpDevice, RSPlatform: cd.PlatformModel, RSCoreDeviceID: strconv.Itoa(c.ctkDevice), }) return err } func (c *client) linkDPtoCOREDevice(dpd *mydp.Device) error { // ADD DATAPIPE FLAGGED INTEGRATION SKU addFlaggedIntReq := methodAddReq{ Class: "Computer.Computer", LoadArg: strconv.Itoa(c.ctkDevice), Method: "addReplacePart", Args: []string{"110339", "2533"}, KeywordArgs: struct{}{}, } addFlaggedIntByt, err := json.Marshal(addFlaggedIntReq) if err != nil { return fmt.Errorf("json.Marshal(addFlaggedIntReq):%s", err) } addFlaggedIntResp, err := c.ctk.Query(addFlaggedIntByt) if err != nil { return fmt.Errorf("ctk.Query(addFlaggedIntByt):%s", err) } var addFlaggedIntResults methodAddResp if err := json.Unmarshal(addFlaggedIntResp, &[]interface{}{&addFlaggedIntResults}); err != nil { return fmt.Errorf("json.Unmarshal(addFlaggedIntByt):%s", err) } var addFlaggedIntResult methodAddRes for _, elem := range addFlaggedIntResults.Result { if err := json.Unmarshal(elem, &addFlaggedIntResult); err == nil { break } } if addFlaggedIntResult.LoadValue == 0 { return fmt.Errorf("unable to get service_part_id from flagged integration sku assignment for %d", c.ctkDevice) } // ADD DATAPIPE DEPLOYMENT SKU addDeploymentSKUReq := methodAddReq{ Class: "Computer.Computer", LoadArg: strconv.Itoa(c.ctkDevice), Method: "addReplacePart", Args: []string{"111507", "1442"}, KeywordArgs: struct{}{}, } addDeploymentSKUByt, err := json.Marshal(addDeploymentSKUReq) if err != nil { return fmt.Errorf("json.Marshal(addDeploymentSKUReq):%s", err) } if _, err := c.ctk.Query(addDeploymentSKUByt); err != nil { logrus.Errorf("Unable to add Deployment Type SKU in CORE") } // ADD DETAILS TO DATAPIPE FLAGGED INTEGRATION SKU addFlaggedIntDetailsReq := []methodAddReq{ { Class: "Computer.Part", LoadArg: strconv.Itoa(addFlaggedIntResult.LoadValue), Method: "addAttribute", Args: []string{"1466", dpd.CustomerID}, KeywordArgs: struct{}{}, }, { Class: "Computer.Part", LoadArg: strconv.Itoa(addFlaggedIntResult.LoadValue), Method: "addAttribute", Args: []string{"1465", dpd.DeviceID}, KeywordArgs: struct{}{}, }, { Class: "Computer.Part", LoadArg: strconv.Itoa(addFlaggedIntResult.LoadValue), Method: "addAttribute", Args: []string{"96", "Valid"}, KeywordArgs: struct{}{}, }, } addFlaggedIntDetailsByt, err := json.Marshal(addFlaggedIntDetailsReq) if err != nil { return fmt.Errorf("json.Marshal(addFlaggedIntDetailsReq):%s", err) } if _, err := c.ctk.Query(addFlaggedIntDetailsByt); err != nil { return fmt.Errorf("ctk.Query(addFlaggedIntDetailsByt):%s", err) } return nil } func (c *client) linkCOREtoDPAccount() error { // BEWARE: Gross code lies before you!! formData := fmt.Sprintf(`part_data=[ { "data_type":"skunit_part_data", "skunit_name":"datapipe_integration_devices", "part_label":"", "sku_id":"110339", "instance_attrs":[ { "data_type":"attr_part_data", "skunit_name":"datapipe_integration_devices", "part_label":"", "attr_id":96,"value":"Valid" }, { "data_type":"attr_part_data", "skunit_name":"datapipe_integration_devices", "part_label":"", "attr_id":1465, "value":"" }, { "data_type":"attr_part_data", "skunit_name":"datapipe_integration_devices", "part_label":"", "attr_id":1466, "value":"%d" } ] } ]`, c.dpAccount) ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://core.rackspace.com/skus/sku_parts/saveConfig/account/%d", c.ctkAccount), strings.NewReader(formData), ) if err != nil { return err } // Setting the request header for CTK token auth req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("cookie", fmt.Sprintf("rackspace_admin_session=%s", c.ctkToken)) res, err := http.DefaultClient.Do(req) if res != nil { // Note: We use a func to error check defer as opposed to using // defer res.Body.Close(), which will never return an error. defer func() { if defErr := res.Body.Close(); defErr != nil { err = fmt.Errorf("%s:%s", err, defErr) } }() } if err != nil { return err } if res.StatusCode > http.StatusNoContent { return fmt.Errorf("ctk.linkCOREtoDPAccount:%s", res.Status) } return nil } func initClient(cfg gokeys.Settings) (*client, error) { devices := *argD accounts := *argA c := new(client) if len(devices) > 0 && len(accounts) > 0 { return nil, fmt.Errorf("devices and accounts are mutually exclusive, got %v/%v", devices, accounts) } if len(devices) != 2 && len(accounts) != 2 { return nil, fmt.Errorf("requires one pair of `devices` or `accounts` got %v/%v", devices, accounts) } if len(devices) > 0 { if len(devices) != 2 { return nil, fmt.Errorf("devices should be in format, got %v", *argD) } c.ctkDevice = devices[0] c.dpDevice = devices[1] } if len(accounts) > 0 { if len(accounts) != 2 { return nil, fmt.Errorf("accounts should be in format, got %v", *argA) } ca, err := strconv.Atoi(accounts[0]) if err != nil { return nil, fmt.Errorf("core account not a valid integer, got %v", accounts[0]) } da, err := strconv.Atoi(accounts[1]) if err != nil { return nil, fmt.Errorf("datapipe account not a valid integer, got %v", accounts[1]) } c.ctkAccount = ca c.dpAccount = da } // Pull an identity token from the users gokeys keychain raxID, err := cfg.Identity(false) if err != nil { return c, fmt.Errorf("cfg.Identity:%s", err) } sso, err := cfg.SSO() if err != nil { return c, fmt.Errorf("cfg.SSO:%s", err) } ctkToken, err := cfg.Core(false) if err != nil { return c, fmt.Errorf("cfg.Core:%s", err) } c.ctkToken = ctkToken.Password // Grab the client for the DCX API if c.ctk, err = goctk.NewCTK(goctk.Token(raxID.Password)); err != nil { return c, fmt.Errorf("goctk.NewCTK:%s", err) } if c.mydp, err = shared.MyDPClient(sso.Username, sso.Password); err != nil { return c, fmt.Errorf("mydpclient:%s", err) } return c, nil }