Skip to content

Commit

Permalink
release
Browse files Browse the repository at this point in the history
  • Loading branch information
caguiclajmg committed Jun 25, 2022
1 parent 0ca076f commit 5116327
Show file tree
Hide file tree
Showing 11 changed files with 1,072 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [ "credentials", "--apiKey", "rec8DNOFisd19B944", "--apiToken", "edBfHBhyDsUzeTnJLjAHhXvwqUAFjU" ]
}
]
}
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# tensordock-cli

A CLI client for https://tensordock.com

## Installation

Grab a build from [Releases](releases)

## Building

```
go build
```

## Usage

### Configuration


```
$ tensordock-cli config --apiKey <YOUR_API_KEY> --apiToken <YOUR_API_TOKEN> [--serviceUrl <SERVICE_URL>]
```

Note: Go to https://console.tensordock.com/api to get your API key and token

### List Servers

```sh
$ tensordock-cli servers list
```

### Get server info

```sh
$ tensordock-cli servers info --server <serverId>
```

### Start/Stop Server

```sh
$ tensordock-cli servers <start|stop> --server <serverId>
```

### Get billing info

```sh
$ tensordock-cli billing
```
143 changes: 143 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package api

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)

type Response struct {
Success bool `json:"success"`
}

type ServersGetSingleResponse struct {
Response
Server Server `json:"server"`
}

type ServersListResponse struct {
Response
Servers map[string]Server `json:"servers"`
}

type BillingDetailsResponse struct {
Response
BillingDetails
}

type Client struct {
BaseUrl string
ApiKey string
ApiToken string
}

func (client *Client) get(path string, params map[string]string) (*json.RawMessage, error) {
query := url.Values{}
query.Add("api_key", client.ApiKey)
query.Add("api_token", client.ApiToken)
for key, elem := range params {
query.Add(key, elem)
}

url := fmt.Sprintf("%v/%v?%v", client.BaseUrl, path, query.Encode())
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}

res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()

bytes, _ := ioutil.ReadAll(res.Body)
if res.StatusCode == 200 && strings.HasPrefix(res.Header.Get("Content-Type"), "text/html") {
bytes, err := json.Marshal(Response{Success: false})
if err != nil {
return nil, err
}
msg := json.RawMessage(bytes)
return &msg, nil
}

msg := json.RawMessage(bytes)
return &msg, nil
}

func (client *Client) ListServers() (*ServersListResponse, error) {
raw, err := client.get("list", nil)
if err != nil {
return nil, err
}

var res ServersListResponse
if err := json.Unmarshal(*raw, &res); err != nil {
return nil, err
}

return &res, nil
}

func (client *Client) StopServer(server string) (*Response, error) {
raw, err := client.get("stop/single", map[string]string{"server": server})
if err != nil {
return nil, err
}

var res Response
if err := json.Unmarshal(*raw, &res); err != nil {
return nil, err
}

return &res, nil
}

func (client *Client) StartServer(server string) (*Response, error) {
raw, err := client.get("start/single", map[string]string{"server": server})
if err != nil {
return nil, err
}

var res Response
if err := json.Unmarshal(*raw, &res); err != nil {
return nil, err
}

return &res, nil
}

func (client *Client) GetServer(server string) (*ServersGetSingleResponse, error) {
raw, err := client.get("get/single", map[string]string{"server": server})
if err != nil {
return nil, err
}

var res ServersGetSingleResponse
if err := json.Unmarshal(*raw, &res); err != nil {
return nil, err
}

return &res, nil
}

func (client *Client) GetBillingDetails() (*BillingDetailsResponse, error) {
raw, err := client.get("billing", nil)
if err != nil {
return nil, err
}

var res BillingDetailsResponse
if err := json.Unmarshal(*raw, &res); err != nil {
return nil, err
}

return &res, nil
}

func NewClient(baseUrl string, apiKey string, apiToken string) *Client {
return &Client{baseUrl, apiKey, apiToken}
}
32 changes: 32 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package api

type Cost struct {
Charged float32 `json:"charged"`
HourOff float32 `json:"hour_off"`
HourOn float32 `json:"hour_on"`
MinutesOff float32 `json:"minutes_off"`
MinutesOn float32 `json:"minutes_on"`
}

type Server struct {
Cost Cost `json:"cost"`
CPUModel string `json:"cpu_model"`
GPUCount int `json:"gpu_count"`
GPUModel string `json:"gpu_model"`
Id string `json:"id"`
Ip string `json:"ip"`
Links map[string]map[string]string `json:"links"`
Location string `json:"location"`
Name string `json:"name"`
Ram int `json:"ram"`
Status string `json:"status"`
Storage int `json:"storage"`
StorageClass string `json:"storage_class"`
Type string `json:"type"`
VCPUs int `json:"vcpus"`
}

type BillingDetails struct {
Balance float32 `json:"balance"`
HourlySpendingRate float32 `json:"hourly_spending_rate"`
}
36 changes: 36 additions & 0 deletions commands/billing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package commands

import (
"fmt"
"log"

"github.com/spf13/cobra"
)

var (
billingCmd = &cobra.Command{
Use: "billing",
Short: "Manage billing",
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.GetBillingDetails()
if err != nil {
return err
}

if !res.Success {
log.Printf("api call failed")
return nil
}

fmt.Printf(`Balance: %v
Hourly Spending Rate: %v`,
res.Balance,
res.HourlySpendingRate)
return nil
},
}
)

func init() {
rootCmd.AddCommand(billingCmd)
}
44 changes: 44 additions & 0 deletions commands/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package commands

import (
"log"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
configCmd = &cobra.Command{
Use: "config",
Short: "Set API token/key",
RunE: func(cmd *cobra.Command, args []string) error {
apiKey, err := cmd.Flags().GetString("apiKey")
if err != nil {
return err
}

apiToken, err := cmd.Flags().GetString("apiToken")
if err != nil {
return err
}

viper.Set("apiKey", apiKey)
viper.Set("apiToken", apiToken)
viper.WriteConfig()

return nil
},
PostRun: func(cmd *cobra.Command, args []string) {
log.Print("config updated")
},
}
)

func init() {
configCmd.Flags().String("apiKey", "", "API Key")
configCmd.MarkFlagRequired("apiKey")
configCmd.Flags().String("apiToken", "", "API Token")
configCmd.MarkFlagRequired("apiToken")
configCmd.Flags().String("serviceUrl", "https://console.tensordock.com/api", "Service URL")
rootCmd.AddCommand(configCmd)
}
67 changes: 67 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package commands

import (
"log"
"os"
"path/filepath"

"github.com/caguiclajmg/tensordock-cli/api"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
cfgFile string
client *api.Client

rootCmd = &cobra.Command{
Use: "tensordock-cli",
Short: "A brief description of your application",
SilenceUsage: true,
}
)

func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
cobra.OnInitialize(initConfig)

pflags := rootCmd.PersistentFlags()
pflags.StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tensordock.yml)")
pflags.String("apiKey", "", "API key")
pflags.String("apiToken", "", "API token")

viper.BindPFlag("apiKey", pflags.Lookup("apiKey"))
viper.BindPFlag("apiToken", pflags.Lookup("apiToken"))
}

func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)

viper.SetConfigFile(filepath.Join(home, ".tensordock.yml"))
}

viper.SetDefault("serviceUrl", "https://console.tensordock.com/api")

err := viper.ReadInConfig()
if err != nil {
log.Fatalf("error: %v", err)
}

viper.AutomaticEnv()

serviceUrl := viper.GetString("serviceUrl")
apiKey := viper.GetString("apiKey")
apiToken := viper.GetString("apiToken")

client = api.NewClient(serviceUrl, apiKey, apiToken)
}
Loading

0 comments on commit 5116327

Please sign in to comment.