Skip to content

Commit

Permalink
implementing mysocketio json output
Browse files Browse the repository at this point in the history
  • Loading branch information
steiler committed May 12, 2022
1 parent c03692c commit 7fe1f8a
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 16 deletions.
4 changes: 4 additions & 0 deletions clab/file.go
Expand Up @@ -35,6 +35,10 @@ type TopoFile struct {
name string // file name without extension
}

func (tf *TopoFile) GetDir() string {
return tf.dir
}

// GetTopology parses the topology file into c.Conf structure
// as well as populates the TopoFile structure with the topology file related information
func (c *CLab) GetTopology(topo, varsFile string) error {
Expand Down
122 changes: 106 additions & 16 deletions cmd/inspect.go
Expand Up @@ -11,14 +11,17 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"

"github.com/olekukonko/tablewriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-labs/containerlab/clab"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
"github.com/srl-labs/containerlab/types"
"github.com/srl-labs/containerlab/utils"
"github.com/srl-labs/containerlab/utils/mysocketio"
)

var format string
Expand Down Expand Up @@ -76,10 +79,6 @@ var inspectCmd = &cobra.Command{
return fmt.Errorf("failed to list containers: %s", err)
}

if len(containers) == 0 {
log.Println("no containers found")
return nil
}
if details {
b, err := json.MarshalIndent(containers, "", " ")
if err != nil {
Expand Down Expand Up @@ -120,7 +119,6 @@ func printContainerInspect(c *clab.CLab, containers []types.GenericContainer, fo
contDetails := make([]types.ContainerDetails, 0, len(containers))
// do not print published ports unless mysocketio kind is found
printMysocket := false
var mysocketCID string

for i := range containers {
cont := &containers[i]
Expand All @@ -145,7 +143,6 @@ func printContainerInspect(c *clab.CLab, containers []types.GenericContainer, fo
cdet.Kind = kind
if kind == "mysocketio" {
printMysocket = true
mysocketCID = cont.ID
}
}
if group, ok := cont.Labels["clab-node-group"]; ok {
Expand All @@ -162,7 +159,22 @@ func printContainerInspect(c *clab.CLab, containers []types.GenericContainer, fo
})

if format == "json" {
b, err := json.MarshalIndent(contDetails, "", " ")
resultJson := &types.JsonInspect{ContainerData: contDetails, MySocketIoData: []*types.MySocketIoEntry{}}
if printMysocket {
// search for the mysocketio token file by deducing it from the topology config binds section
tokenFile, err := deduceMySocketIoTokenFileFromBindMounts(c.Nodes, c.TopoFile.GetDir())
if err != nil {
return err
}
// retrieve the MySocketIO Data
socketdata, err := getMySocketIoData(tokenFile)
if err != nil {
return fmt.Errorf("error when processing mysocketio data: %v", err)
}
resultJson.MySocketIoData = socketdata
}

b, err := json.MarshalIndent(resultJson, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal container details: %v", err)
}
Expand Down Expand Up @@ -197,19 +209,97 @@ func printContainerInspect(c *clab.CLab, containers []types.GenericContainer, fo
return nil
}

runtime := c.GlobalRuntime()

stdout, stderr, err := runtime.Exec(context.Background(), mysocketCID, []string{"mysocketctl", "socket", "ls"})
// search for the mysocketio token file by deducing it from the topology config binds section
tokenFile, err := deduceMySocketIoTokenFileFromBindMounts(c.Nodes, c.TopoFile.GetDir())
if err != nil {
return fmt.Errorf("failed to execute cmd: %v", err)

return err
}
if len(stderr) > 0 {
log.Infof("errors during listing mysocketio sockets: %s", string(stderr))
// retrieve the MySocketIO Data
socketdata, err := getMySocketIoData(tokenFile)
if err != nil {
return fmt.Errorf("error when processing mysocketio data: %v", err)
}

// prepare data for table
var tabDataMySocketIo [][]string
for _, entry := range socketdata {
var portstrarr []string
for _, port := range entry.Ports {
portstrarr = append(portstrarr, strconv.Itoa(port))
}
tabDataMySocketIo = append(tabDataMySocketIo, []string{entry.SocketId, entry.DnsName, strings.Join(portstrarr, ", "), entry.Type, strconv.FormatBool(entry.CloudAuth), entry.Name})
}
tableMySocketIo := tablewriter.NewWriter(os.Stdout)
headerMySocketIo := []string{
"Socket ID",
"DNS Name",
"Ports",
"Type",
"Cloud Auth",
"Name",
}
// configure table output
tableMySocketIo.SetHeader(headerMySocketIo)
tableMySocketIo.SetAutoFormatHeaders(false)
tableMySocketIo.SetAutoWrapText(false)
tableMySocketIo.AppendBulk(tabDataMySocketIo)
fmt.Println("Published ports:")
fmt.Println(string(stdout))
tableMySocketIo.Render()

return nil
}

// getMySocketioData uses the mysocketio.http client to retrieve the socket data
func getMySocketIoData(tokenfile string) ([]*types.MySocketIoEntry, error) {
result := []*types.MySocketIoEntry{}

client, err := mysocketio.NewClient(tokenfile)
if err != nil {
return nil, err
}

sockets := []mysocketio.Socket{}
err = client.Request("GET", "connect", &sockets, nil)
if err != nil {
return nil, err
}

for _, s := range sockets {
newentry := &types.MySocketIoEntry{
SocketId: s.SocketID,
DnsName: s.Dnsname,
Ports: s.SocketTcpPorts,
Type: s.SocketType,
CloudAuth: s.CloudAuthEnabled,
Name: s.Name,
}
result = append(result, newentry)
}
return result, nil
}

// deduceMySocketIoTokenFileFromBindMounts searches through the topology to find a node of kind mysocketio.
// if that is found, the bindmounts are searched for ".mysocketio_token" and the path is being converted into an
// absolute path and returned.
// If the
func deduceMySocketIoTokenFileFromBindMounts(nodes map[string]nodes.Node, configPath string) (string, error) {
for _, node := range nodes {
// search for mysocketio kind in topology config
if node.Config().Kind == "mysocketio" {
// iterate through bind mounts
for _, bind := range node.Config().Binds {
// watch out for ".mysocketio_token"
if strings.Contains(bind, ".mysocketio_token") {
// split the bindmount and resolve the path to an absolute path
deduced_absfilepath := utils.ResolvePath(strings.Split(bind, ":")[0], configPath)
// check file existence before returning
if !utils.FileExists(deduced_absfilepath) {
return "", fmt.Errorf(".mysocketio_token resolved to %s, but that file doesn't exist", deduced_absfilepath)
}
return deduced_absfilepath, nil
}
}
}
}
return "", fmt.Errorf("unable to find \".mysocketio_token\"")
}
14 changes: 14 additions & 0 deletions types/types.go
Expand Up @@ -276,3 +276,17 @@ type ContainerDetails struct {
IPv4Address string `json:"ipv4_address,omitempty"`
IPv6Address string `json:"ipv6_address,omitempty"`
}

type MySocketIoEntry struct {
SocketId string `json:"socket_id,omitempty"`
DnsName string `json:"dns_name,omitempty"`
Ports []int `json:"ports,omitempty"`
Type string `json:"type,omitempty"`
CloudAuth bool `json:"cloud_auth,omitempty"`
Name string `json:"name,omitempty"`
}

type JsonInspect struct {
ContainerData []ContainerDetails `json:"container_data"`
MySocketIoData []*MySocketIoEntry `json:"mysocketio_data"`
}
111 changes: 111 additions & 0 deletions utils/mysocketio/mysocketio.go
@@ -0,0 +1,111 @@
package mysocketio

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
h "net/http"
"os"
"strings"
)

type Client struct {
token string
}

func GetApiUrl() string {
if os.Getenv("MYSOCKET_API") != "" {
return os.Getenv("MYSOCKET_API")
} else {
return "https://api.mysocket.io"
}
}

func NewClient(tokenfile string) (*Client, error) {
token, err := GetToken(tokenfile)
if err != nil {
return nil, err
}

c := &Client{token: token}

return c, nil
}

func (c *Client) Request(method string, url string, target interface{}, data interface{}) error {
jv, _ := json.Marshal(data)
body := bytes.NewBuffer(jv)

req, _ := h.NewRequest(method, fmt.Sprintf("%s/%s", GetApiUrl(), url), body)
req.Header.Add("x-access-token", c.token)
req.Header.Set("Content-Type", "application/json")
client := &h.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}

defer resp.Body.Close()

if resp.StatusCode == 401 {
return errors.New("no valid token, Please login")
}

if resp.StatusCode < 200 || resp.StatusCode > 204 {
responseData, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("failed to create object (%d) %v", resp.StatusCode, string(responseData))
}

if resp.StatusCode == 204 {
return nil
}

err = json.NewDecoder(resp.Body).Decode(target)
if err != nil {
return errors.New("failed to decode data")
}

return nil
}

func GetToken(tokenfile string) (string, error) {
if _, err := os.Stat(tokenfile); os.IsNotExist(err) {
return "", errors.New("please login first (no token found)")
}
content, err := ioutil.ReadFile(tokenfile)
if err != nil {
return "", err
}

tokenString := strings.TrimRight(string(content), "\n")
return tokenString, nil
}

type Socket struct {
Tunnels []Tunnel `json:"tunnels,omitempty"`
Username string `json:"user_name,omitempty"`
SocketID string `json:"socket_id,omitempty"`
SocketTcpPorts []int `json:"socket_tcp_ports,omitempty"`
Dnsname string `json:"dnsname,omitempty"`
Name string `json:"name,omitempty"`
SocketType string `json:"socket_type,omitempty"`
ProtectedSocket bool `json:"protected_socket"`
ProtectedUsername string `json:"protected_username"`
ProtectedPassword string `json:"protected_password"`
CloudAuthEnabled bool `json:"cloud_authentication_enabled,omitempty"`
AllowedEmailAddresses []string `json:"cloud_authentication_email_allowed_addressses,omitempty"`
AllowedEmailDomains []string `json:"cloud_authentication_email_allowed_domains,omitempty"`
SSHCa string `json:"ssh_ca,omitempty"`
UpstreamUsername string `json:"upstream_username,omitempty"`
UpstreamPassword string `json:"upstream_password,omitempty"`
UpstreamHttpHostname string `json:"upstream_http_hostname,omitempty"`
UpstreamType string `json:"upstream_type,omitempty"`
}

type Tunnel struct {
TunnelID string `json:"tunnel_id,omitempty"`
LocalPort int `json:"local_port,omitempty"`
TunnelServer string `json:"tunnel_server,omitempty"`
}

0 comments on commit 7fe1f8a

Please sign in to comment.