Skip to content

Commit

Permalink
restructured files and handled index out of bounds errors in flag han…
Browse files Browse the repository at this point in the history
…dling
  • Loading branch information
jlstack committed Mar 15, 2016
1 parent 3035393 commit f1a12a5
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 178 deletions.
127 changes: 0 additions & 127 deletions CloudantAccountModel/CloudantAccountModel.go
Original file line number Diff line number Diff line change
@@ -1,136 +1,9 @@
package cam

import (
"errors"
"fmt"
"github.com/cloudfoundry/cli/cf/terminal"
"github.com/cloudfoundry/cli/plugin"
"github.com/ibmjstart/bluemix-cloudant-replicator/utils"
"net/http"
"regexp"
"strings"
"time"
)

type CloudantAccount struct {
Endpoint string
Username string
Password string
Url string
Cookie string
}

type CreateAccountResponse struct {
account CloudantAccount
err error
}

func init() {
terminal.InitColorSupport()
}

func createAccount(cliConnection plugin.CliConnection, httpClient *http.Client, env []string, endpoint string) CreateAccountResponse {
account, err := parseCreds(env)
if err != nil {
err = errors.New("Problem finding Cloudant credentials for app at '" + terminal.ColorizeBold(endpoint, 36) +
"'.\nMake sure that there is a valid 'cloudantNoSQLDB' service bound to your app.\nContinuing on with other regions.\n")
return CreateAccountResponse{account: account, err: err}
}
account.Endpoint = endpoint
account.Cookie = getCookie(account, httpClient)
return CreateAccountResponse{account: account, err: nil}
}

/*
* Cycles through all endpoints and retrieves the Cloudant
* credentials for the specified app in each region.
*/
func GetCloudantAccounts(cliConnection plugin.CliConnection, httpClient *http.Client, ENDPOINTS []string, appname string, password string) ([]CloudantAccount, error) {
var cloudantAccounts []CloudantAccount
_, username, org, space := bcr_utils.GetCurrentTarget(cliConnection)
ch := make(chan CreateAccountResponse)
for i := 0; i < len(ENDPOINTS); i++ {
env, err := getAppEnv(cliConnection, username, password, org, ENDPOINTS[i], appname, space)
go func(cliConnection plugin.CliConnection, httpClient *http.Client, env []string, endpoint string, envErr error) {
if envErr == nil {
ch <- createAccount(cliConnection, httpClient, env, endpoint)
} else {
ch <- CreateAccountResponse{account: CloudantAccount{}, err: err}
}
}(cliConnection, httpClient, env, ENDPOINTS[i], err)
}
responses := 0
for {
select {
case r := <-ch:
responses += 1
bcr_utils.CheckErrorNonFatal(r.err)
if r.err == nil {
cloudantAccounts = append(cloudantAccounts, r.account)
}
case <-time.After(50 * time.Millisecond):
continue
}
if responses == len(ENDPOINTS) {
break
}
}
close(ch)
return cloudantAccounts, nil
}

func parseCreds(env []string) (CloudantAccount, error) {
var account CloudantAccount
for i := 0; i < len(env); i++ {
if strings.Index(env[i], "cloudantNoSQLDB") != -1 {
user_reg, _ := regexp.Compile("\"username\": \"([\x00-\x7F]+)\"")
pass_reg, _ := regexp.Compile("\"password\": \"([\x00-\x7F]+)\"")
url_reg, _ := regexp.Compile("\"url\": \"([\x00-\x7F]+)\"")
account.Username = strings.Split(user_reg.FindString(env[i]), "\"")[3]
account.Password = strings.Split(pass_reg.FindString(env[i]), "\"")[3]
account.Url = strings.Split(url_reg.FindString(env[i]), "\"")[3]
break
}
}
if account.Username == "" || account.Password == "" || account.Url == "" {
return account, errors.New("Cloudant credentials incomplete\n")
}
return account, nil
}

/*
* Returns the result of "cf env APP"
*/
func getAppEnv(cliConnection plugin.CliConnection, username string, password string, org string, endpoint string, appname string, space string) ([]string, error) {
fmt.Println("Retrieving CloudantNoSQLDB credentials for '" + terminal.ColorizeBold(appname, 36) + "' in '" + terminal.ColorizeBold(endpoint, 36) + "'\n")
startingEndpoint, _ := cliConnection.ApiEndpoint()
if startingEndpoint != endpoint {
_, err := cliConnection.CliCommandWithoutTerminalOutput("login", "-u", username, "-p", password, "-o", org, "-a", endpoint, "-s", space)
if err != nil {
fmt.Println("Unable to log in to org '" + terminal.ColorizeBold(org, 36) + "' and/or space '" + terminal.ColorizeBold(space, 36) + "'\n")
_, err = cliConnection.CliCommand("login", "-u", username, "-p", password, "-a", endpoint)
bcr_utils.CheckErrorFatal(err)
}
}
output, err := cliConnection.CliCommandWithoutTerminalOutput("env", appname)
if err != nil {
return output, errors.New("No '" + terminal.ColorizeBold(appname, 36) + "' in '" + terminal.ColorizeBold(endpoint, 36) +
"'.\nContinuing on with other regions.\n")
}
return output, err
}

/*
* Gets cookie for a specified CloudantAccount. This cookie is
* used to authenticate all necessary api calls.
*/
func getCookie(account CloudantAccount, httpClient *http.Client) string {
url := "https://" + account.Username + ".cloudant.com/_session"
body := "name=" + account.Username + "&password=" + account.Password
headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
resp, err := bcr_utils.MakeRequest(httpClient, "POST", url, body, headers)
bcr_utils.CheckErrorFatal(err)
cookie := resp.Header.Get("Set-Cookie")
resp.Body.Close()
return cookie
}
38 changes: 13 additions & 25 deletions bc-replicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/cloudfoundry/cli/cf/terminal"
"github.com/cloudfoundry/cli/plugin"
"github.com/ibmjstart/bluemix-cloudant-replicator/CloudantAccountModel"
"github.com/ibmjstart/bluemix-cloudant-replicator/cloudantAccounts"
"github.com/ibmjstart/bluemix-cloudant-replicator/prompts"
"github.com/ibmjstart/bluemix-cloudant-replicator/utils"
"io/ioutil"
Expand Down Expand Up @@ -43,27 +44,13 @@ type BCReplicatorPlugin struct{}
func (c *BCReplicatorPlugin) Run(cliConnection plugin.CliConnection, args []string) {
if args[0] == "cloudant-replicate" {
terminal.InitColorSupport()
var appname, password string
var dbs []string
var err error
all_dbs := false
loggedIn, _ := cliConnection.IsLoggedIn()
if !loggedIn || err != nil {
fmt.Println("Please log in first\n")
cliConnection.CliCommand("login")
}
for i := 1; i < len(args); i++ {
switch args[i] {
case "-a":
appname = args[i+1]
case "-d":
dbs = strings.Split(args[i+1], ",")
case "-p":
password = args[i+1]
case "--all-dbs":
all_dbs = true
}
}
appname, dbs, password, all_dbs := bcr_utils.HandleFlags(args)
if appname == "" {
appname, err = bcr_prompts.GetAppName(cliConnection)
bcr_utils.CheckErrorNonFatal(err)
Expand All @@ -84,17 +71,17 @@ func (c *BCReplicatorPlugin) Run(cliConnection plugin.CliConnection, args []stri
startingEndpoint, username, startingOrg, startingSpace := bcr_utils.GetCurrentTarget(cliConnection)
defer finalLogin(cliConnection, startingEndpoint, username, password, startingOrg, startingSpace)
var httpClient = &http.Client{}
cloudantAccounts, err := cam.GetCloudantAccounts(cliConnection, httpClient, ENDPOINTS, appname, password)
cloudantAccounts, err := ca.GetCloudantAccounts(cliConnection, httpClient, ENDPOINTS, appname, password)
bcr_utils.CheckErrorFatal(err)
if len(dbs) == 0 && !all_dbs {
if all_dbs {
dbs = bcr_utils.GetAllDatabases(httpClient, cloudantAccounts[0])
} else if len(dbs) == 0 {
dbs, err = bcr_prompts.GetDatabases(httpClient, cloudantAccounts[0])
bcr_utils.CheckErrorFatal(err)
} else if all_dbs {
dbs = bcr_prompts.GetAllDatabases(httpClient, cloudantAccounts[0])
} else {
all_dbs := bcr_prompts.GetAllDatabases(httpClient, cloudantAccounts[0])
dbs_list := bcr_utils.GetAllDatabases(httpClient, cloudantAccounts[0])
for i := 0; i < len(dbs); i++ {
if !bcr_utils.IsValid(dbs[i], all_dbs) {
if !bcr_utils.IsValid(dbs[i], dbs_list) {
bcr_utils.CheckErrorFatal(errors.New(dbs[i] + " is not a valid database in '" +
terminal.ColorizeBold(cloudantAccounts[0].Endpoint, 36) + "'"))
}
Expand Down Expand Up @@ -364,11 +351,12 @@ func (c *BCReplicatorPlugin) GetMetadata() plugin.PluginMetadata {
// UsageDetails is optional
// It is used to show help of usage of each command
UsageDetails: plugin.Usage{
Usage: "cf cloudant-replicate [-a APP] [-d DATABASE] [-p PASSWORD]\n",
Usage: "cf cloudant-replicate [-a APP] [-d DATABASE] [--all-dbs] [-p PASSWORD]\n",
Options: map[string]string{
"-a": "App",
"-d": "Database (* selects all databases)",
"-p": "Password"},
"-a": "App",
"-d": "Database",
"--all-dbs": "Select all databases",
"-p": "Password"},
},
},
},
Expand Down
129 changes: 129 additions & 0 deletions cloudantAccounts/cloudantAccounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package ca

import (
"errors"
"fmt"
"github.com/cloudfoundry/cli/cf/terminal"
"github.com/cloudfoundry/cli/plugin"
"github.com/ibmjstart/bluemix-cloudant-replicator/CloudantAccountModel"
"github.com/ibmjstart/bluemix-cloudant-replicator/utils"
"net/http"
"regexp"
"strings"
"time"
)

type CreateAccountResponse struct {
account cam.CloudantAccount
err error
}

func init() {
terminal.InitColorSupport()
}

func createAccount(cliConnection plugin.CliConnection, httpClient *http.Client, env []string, endpoint string) CreateAccountResponse {
account, err := parseCreds(env)
if err != nil {
err = errors.New("Problem finding Cloudant credentials for app at '" + terminal.ColorizeBold(endpoint, 36) +
"'.\nMake sure that there is a valid 'cloudantNoSQLDB' service bound to your app.\nContinuing on with other regions.\n")
return CreateAccountResponse{account: account, err: err}
}
account.Endpoint = endpoint
account.Cookie = getCookie(account, httpClient)
return CreateAccountResponse{account: account, err: nil}
}

/*
* Cycles through all endpoints and retrieves the Cloudant
* credentials for the specified app in each region.
*/
func GetCloudantAccounts(cliConnection plugin.CliConnection, httpClient *http.Client, ENDPOINTS []string, appname string, password string) ([]cam.CloudantAccount, error) {
var cloudantAccounts []cam.CloudantAccount
_, username, org, space := bcr_utils.GetCurrentTarget(cliConnection)
ch := make(chan CreateAccountResponse)
for i := 0; i < len(ENDPOINTS); i++ {
env, err := getAppEnv(cliConnection, username, password, org, ENDPOINTS[i], appname, space)
go func(cliConnection plugin.CliConnection, httpClient *http.Client, env []string, endpoint string, envErr error) {
if envErr == nil {
ch <- createAccount(cliConnection, httpClient, env, endpoint)
} else {
ch <- CreateAccountResponse{account: cam.CloudantAccount{}, err: err}
}
}(cliConnection, httpClient, env, ENDPOINTS[i], err)
}
responses := 0
for {
select {
case r := <-ch:
responses += 1
bcr_utils.CheckErrorNonFatal(r.err)
if r.err == nil {
cloudantAccounts = append(cloudantAccounts, r.account)
}
case <-time.After(50 * time.Millisecond):
continue
}
if responses == len(ENDPOINTS) {
break
}
}
close(ch)
return cloudantAccounts, nil
}

func parseCreds(env []string) (cam.CloudantAccount, error) {
var account cam.CloudantAccount
for i := 0; i < len(env); i++ {
if strings.Index(env[i], "cloudantNoSQLDB") != -1 {
user_reg, _ := regexp.Compile("\"username\": \"([\x00-\x7F]+)\"")
pass_reg, _ := regexp.Compile("\"password\": \"([\x00-\x7F]+)\"")
url_reg, _ := regexp.Compile("\"url\": \"([\x00-\x7F]+)\"")
account.Username = strings.Split(user_reg.FindString(env[i]), "\"")[3]
account.Password = strings.Split(pass_reg.FindString(env[i]), "\"")[3]
account.Url = strings.Split(url_reg.FindString(env[i]), "\"")[3]
break
}
}
if account.Username == "" || account.Password == "" || account.Url == "" {
return account, errors.New("Cloudant credentials incomplete\n")
}
return account, nil
}

/*
* Returns the result of "cf env APP"
*/
func getAppEnv(cliConnection plugin.CliConnection, username string, password string, org string, endpoint string, appname string, space string) ([]string, error) {
fmt.Println("Retrieving CloudantNoSQLDB credentials for '" + terminal.ColorizeBold(appname, 36) + "' in '" + terminal.ColorizeBold(endpoint, 36) + "'\n")
startingEndpoint, _ := cliConnection.ApiEndpoint()
if startingEndpoint != endpoint {
_, err := cliConnection.CliCommandWithoutTerminalOutput("login", "-u", username, "-p", password, "-o", org, "-a", endpoint, "-s", space)
if err != nil {
fmt.Println("Unable to log in to org '" + terminal.ColorizeBold(org, 36) + "' and/or space '" + terminal.ColorizeBold(space, 36) + "'\n")
_, err = cliConnection.CliCommand("login", "-u", username, "-p", password, "-a", endpoint)
bcr_utils.CheckErrorFatal(err)
}
}
output, err := cliConnection.CliCommandWithoutTerminalOutput("env", appname)
if err != nil {
return output, errors.New("No '" + terminal.ColorizeBold(appname, 36) + "' in '" + terminal.ColorizeBold(endpoint, 36) +
"'.\nContinuing on with other regions.\n")
}
return output, err
}

/*
* Gets cookie for a specified CloudantAccount. This cookie is
* used to authenticate all necessary api calls.
*/
func getCookie(account cam.CloudantAccount, httpClient *http.Client) string {
url := "https://" + account.Username + ".cloudant.com/_session"
body := "name=" + account.Username + "&password=" + account.Password
headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
resp, err := bcr_utils.MakeRequest(httpClient, "POST", url, body, headers)
bcr_utils.CheckErrorFatal(err)
cookie := resp.Header.Get("Set-Cookie")
resp.Body.Close()
return cookie
}
26 changes: 1 addition & 25 deletions prompts/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package bcr_prompts

import (
"bufio"
"encoding/json"
"errors"
"fmt"
"github.com/cloudfoundry/cli/cf/terminal"
"github.com/cloudfoundry/cli/plugin"
"github.com/ibmjstart/bluemix-cloudant-replicator/CloudantAccountModel"
"github.com/ibmjstart/bluemix-cloudant-replicator/utils"
"io/ioutil"
"net/http"
"os"
"strconv"
Expand All @@ -32,35 +30,13 @@ func GetPassword() string {
return string(pw)
}

/*
* Requests all databases for a given Cloudant account
* and returns them as a string array
*/
func GetAllDatabases(httpClient *http.Client, account cam.CloudantAccount) []string {
url := "https://" + account.Username + ".cloudant.com/_all_dbs"
headers := map[string]string{"Cookie": account.Cookie}
resp, _ := bcr_utils.MakeRequest(httpClient, "GET", url, "", headers)
respBody, _ := ioutil.ReadAll(resp.Body)
dbsStr := string(respBody)
var dbs []string
json.Unmarshal([]byte(dbsStr), &dbs)
resp.Body.Close()
var noRepDbs []string
for i := 0; i < len(dbs); i++ {
if dbs[i] != "_replicator" {
noRepDbs = append(noRepDbs, dbs[i])
}
}
return noRepDbs
}

/*
* Lists all databases for a specified CloudantAccount and
* prompts the user to select one
*/
func GetDatabases(httpClient *http.Client, account cam.CloudantAccount) ([]string, error) {
reader := bufio.NewReader(os.Stdin)
all_dbs := GetAllDatabases(httpClient, account)
all_dbs := bcr_utils.GetAllDatabases(httpClient, account)
if len(all_dbs) == 0 {
return all_dbs, errors.New("No databases found for CloudantNoSQLDB service in '" +
terminal.ColorizeBold(account.Endpoint, 36) + "'")
Expand Down
Loading

0 comments on commit f1a12a5

Please sign in to comment.