Skip to content

Commit

Permalink
Add clusters endpoint (#121)
Browse files Browse the repository at this point in the history
* Clusters: Add command to show cluster details

* Clusters: Add clusters flag to create and delete commands

* Clusters: Implement delete cluster

* Clusters: Implement create cluster

* Clusters: Add non-interactive cluster creation logic

* Clusters: Add non-interactive cluster creation logic

* Clusters: Vendor dependency survey

* Cluster delete: change the basic usage text

* Clusters endpoint: Add docs

* Modify according to the current API changes

* clusters: Multiple fixes

* Update according to new pricing plan
* Separate provider depenedent options into different sets
* Display error message in case of cluster failure

* Remove deprecated vm_size options

Removes `g1-small` and `n1-standard-1`

* Add plan VM mappings

* Update docs

* cluster_details: Annotate response message with error

* Remove depracted addons field
  • Loading branch information
palash25 authored and utsavoza committed Jan 22, 2019
1 parent b8a86f9 commit fc2c4fd
Show file tree
Hide file tree
Showing 806 changed files with 356,304 additions and 15 deletions.
11 changes: 6 additions & 5 deletions appbase/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
"github.com/appbaseio/abc/appbase/user"
"github.com/olekukonko/tablewriter"
"net/http"
"os"
"sort"
"strconv"
"time"

"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
"github.com/appbaseio/abc/appbase/user"
"github.com/olekukonko/tablewriter"
)

type appBody struct {
Expand Down
7 changes: 4 additions & 3 deletions appbase/app/app_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package app
import (
"encoding/json"
"fmt"
"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
"io/ioutil"
"net/http"
"strconv"
"strings"

"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
)

type createdAppDetails struct {
Expand Down
143 changes: 143 additions & 0 deletions appbase/cluster/cluster_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package cluster

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"

"github.com/AlecAivazis/survey"
"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
)

type status struct {
Message string `json:"message"`
Code int `json:"code"`
}

type cluster struct {
Name string `json:"name"`
ID string `json:"id"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
Message string `json:"message"`
Provider string `json:"provider"`
}

type createClusterRespBody struct {
Status status `json:"status"`
Cluster cluster `json:"cluster"`
}

func decodeResp(resp *http.Response, res *createClusterRespBody) error {
dec := json.NewDecoder(resp.Body)
err := dec.Decode(&res)
if err != nil {
return err
}
return nil
}

// BuildRequestBody creates a request body to for cluster deployment based on input from flags
func BuildRequestBody(name string, location string, vmSize string, plan string, ssh string, provider string, nodes int, esVersion string, volumeSize int) string {
esBody := "\"elasticsearch\": {\n \"nodes\": " + strconv.Itoa(nodes) + ",\n \"version\": \"" + esVersion + "\",\n \"volume_size\": " + strconv.Itoa(volumeSize) + "\n }"
clusterBody := "\"cluster\": {\n \"name\": \"" + name + "\",\n \"location\": \"" + location + "\",\n \"vm_size\": \"" + vmSize + "\",\n \"ssh_public_key\": \"" + ssh + "\",\n \"pricing_plan\": \"" + plan + "\",\n \"provider\": \"" + provider + "\"\n }"
return "{\n " + esBody + ",\n " + clusterBody + "\n}"
}

var additionalChoices = []*survey.Question{
{
Name: "logstash",
Prompt: &survey.Confirm{Message: "Would you like to provide Logstash options to your cluster deployment?"},
Validate: survey.Required,
},
{
Name: "kibana",
Prompt: &survey.Confirm{Message: "Would you like to provide Kibana options to your cluster deployment?"},
Validate: survey.Required,
},
{
Name: "addons",
Prompt: &survey.Select{
Message: "How many add-ons would you like to add to your cluster deployment?",
Help: "The following adddons are supported currently: Mirage, DejaVu, Elasticsearch-HQ",
Options: []string{"0", "1", "2", "3"},
},
Validate: survey.Required,
},
}

// BuildRequestBodyInteractive asks the user questions based on which it constructs the
// request body string to deploy the cluster.
func BuildRequestBodyInteractive() string {
answers := make(map[string]interface{})

err := survey.Ask(additionalChoices, &answers)
if err != nil {
fmt.Println(err.Error())
}

clustersString, plan := buildClusterObjectString()
respBodyString := "{\n " + buildESObjectString(plan) + " " + clustersString

if answers["logstash"] == true {
respBodyString = respBodyString + buildLogstashObjectString()
}
if answers["kibana"] == true {
respBodyString = respBodyString + buildKibanaObjectString()
}
if answers["addons"] != "0" {
num, _ := strconv.Atoi(answers["addons"].(string))
respBodyString = respBodyString + buildAddonsObjectString(num)
}

idx := strings.LastIndex(respBodyString, ",")
return respBodyString[:idx] + respBodyString[idx+1:] + "}"
}

// DeployCluster creates a cluster in interactive mode by asking the user
// for the deployment details.
func DeployCluster(body string) error {
payload := strings.NewReader(body)
spinner.Start()
defer spinner.Stop()

req, err := http.NewRequest("POST", common.AccAPIURL+"/v1/_deploy", payload)
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")

resp, err := session.SendRequest(req)
if err != nil {
return err
}
spinner.Stop()

if resp.StatusCode != 202 {
defer resp.Body.Close()
var res createClusterRespBody
_ = decodeResp(resp, &res)
return fmt.Errorf("There was an error %s", res.Status.Message)
}

var res createClusterRespBody
err = decodeResp(resp, &res)
if err != nil {
return err
}

// output
fmt.Printf("ID: %s\n", res.Cluster.ID)
fmt.Printf("Name: %s\n", res.Cluster.Name)
fmt.Printf("Status: %s\n", res.Cluster.Status)
fmt.Printf("Provider: %s\n", res.Cluster.Provider)
fmt.Printf("Created at: %s\n", res.Cluster.CreatedAt)
fmt.Printf("Message: %s\n", res.Cluster.Message)

return nil
}
49 changes: 49 additions & 0 deletions appbase/cluster/cluster_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cluster

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
)

type deleteRespBody struct {
Status statusDetailsBody `json:"status"`
Deployment string `json:"deployment"`
}

// RunClusterDelete deletes a cluster using the cluster ID
func RunClusterDelete(clusterID string) error {
spinner.Start()
defer spinner.Stop()

req, err := http.NewRequest("DELETE", common.AccAPIURL+"/v1/_delete/"+clusterID, nil)
if err != nil {
return err
}
resp, err := session.SendRequest(req)
if err != nil {
return err
}
spinner.Stop()
// status code not 200
if resp.StatusCode != 200 {
defer resp.Body.Close()
bodyBytes, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("There was an error %s", string(bodyBytes))
}
// decode
var res deleteRespBody
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&res)
if err != nil {
return err
}
// output
fmt.Println(res.Status.Message)
return nil
}
123 changes: 123 additions & 0 deletions appbase/cluster/cluster_details.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package cluster

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"

"github.com/appbaseio/abc/appbase/common"
"github.com/appbaseio/abc/appbase/session"
"github.com/appbaseio/abc/appbase/spinner"
)

type clusterDetailsBody struct {
Name string `json:"name"`
ID string `json:"id"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
PricingPlan string `json:"pricing_plan"`
Region string `json:"region"`
EsVersion string `json:"es_version"`
TotalNodes int `json:"total_nodes"`
DashboardURL string `json:"dashboard_url"`
DashboardUsername string `json:"dashboard_username"`
DashboardPassword string `json:"dashboard_password"`
DashboardHTTPS bool `json:"dashboard_https"`
}

type kibanaDetailsBody struct{}

type logstashDetailsBody struct{}

type esDetailsBody struct {
Name string `json:"name"`
RequiredNodes int `json:"required_nodes"`
ReadyNodes int `json:"ready_nodes"`
Status string `json:"status"`
Username string `json:"username"`
Password string `json:"password"`
URL string `json:"url"`
HTTPS bool `json:"https"`
}

type deploymentDetailsBody struct {
Elasticsearch esDetailsBody `json:"elasticsearch"`
Logstash logstashDetailsBody `json:"logstash"`
Kibana kibanaDetailsBody `json:"kibana"`
}

type statusDetailsBody struct {
Message string `json:"message"`
Code int `json:"code"`
}

type clusterRespBody struct {
Status statusDetailsBody `json:"status"`
Deployment deploymentDetailsBody `json:"deployment"`
Cluster clusterDetailsBody `json:"cluster"`
}

// ShowClusterDetails shows the cluster details
func ShowClusterDetails(cluster string) error {
spinner.StartText("Loading cluster details")
defer spinner.Stop()

// get cluster details
req, err := http.NewRequest("GET", common.AccAPIURL+"/v1/_status/"+cluster, nil)
if err != nil {
return err
}
resp, err := session.SendRequest(req)
if err != nil {
return err
}
spinner.Stop()
// decode response
var res clusterRespBody
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&res)
if err != nil {
return err
}

if res.Status.Code != 200 {
errMessage := fmt.Sprintf("Could not fetch cluster details: [error: %s %d]", res.Status.Message, res.Status.Code)
return errors.New(errMessage)
}

fmt.Println("Cluster Details")
fmt.Println("--------------------------------------------------------")
fmt.Printf("Name: %s\n", res.Cluster.Name)
fmt.Printf("ID: %s\n", res.Cluster.ID)
fmt.Printf("Status: %s\n", res.Cluster.Status)
fmt.Printf("Created at: %s\n", res.Cluster.CreatedAt)
fmt.Printf("Pricing Plan: %s\n", res.Cluster.PricingPlan)
fmt.Printf("Region: %s\n", res.Cluster.Region)
fmt.Printf("ES Version: %s\n", res.Cluster.EsVersion)
fmt.Printf("Total Nodes: %d\n", res.Cluster.TotalNodes)
fmt.Printf("Dashboard URL: %s\n", res.Cluster.DashboardURL)
fmt.Printf("Dashboard Username: %s\n", res.Cluster.DashboardUsername)
fmt.Printf("Dashboard Password: %s\n", res.Cluster.DashboardPassword)
fmt.Printf("Dashboard HTTPS: %s\n", strconv.FormatBool(res.Cluster.DashboardHTTPS))

fmt.Println()

fmt.Println("Deployment Details")
fmt.Println("--------------------------------------------------------")
fmt.Println("ElasticSearch:")

fmt.Printf("ID: %s\n", cluster)
fmt.Printf("Name: %s\n", res.Deployment.Elasticsearch.Name)
fmt.Printf("Required Nodes: %d\n", res.Deployment.Elasticsearch.RequiredNodes)
fmt.Printf("Ready Nodes: %d\n", res.Deployment.Elasticsearch.ReadyNodes)
fmt.Printf("Status: %s\n", res.Deployment.Elasticsearch.Status)
fmt.Printf("Username: %s\n", res.Deployment.Elasticsearch.Username)
fmt.Printf("Password: %s\n", res.Deployment.Elasticsearch.Password)
fmt.Printf("URL: %s\n", res.Deployment.Elasticsearch.URL)
fmt.Printf("HTTPS: %s\n", strconv.FormatBool(res.Deployment.Elasticsearch.HTTPS))

return nil
}
Loading

0 comments on commit fc2c4fd

Please sign in to comment.