Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

[WIP] Display information about the cluster or all running footloose containers #103

Merged
merged 28 commits into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a096521
Working on listing all machines based on label.
Skarlso Mar 22, 2019
d054122
Slow progress.
Skarlso Mar 22, 2019
84ca325
added todo
Skarlso Mar 22, 2019
07ea620
Added normal and json formatter.
Skarlso Mar 23, 2019
869d6f8
Removed todo
Skarlso Mar 23, 2019
8c2a35d
Searching for all labelled containers.
Skarlso Mar 23, 2019
39f90a8
Added readonly check.
Skarlso Mar 23, 2019
9440515
Using the api to handle cluster specific containers.
Skarlso Mar 23, 2019
e45d5bb
Added port mapping.
Skarlso Mar 23, 2019
3fbfb60
Removed log output when --all is switched on.
Skarlso Mar 23, 2019
08a3e2d
added hostname
Skarlso Mar 23, 2019
acfdc08
Added displaying cluster data in case no containers are running.
Skarlso Mar 24, 2019
295ff7b
Falling back to display portMappings in case there are no ports when …
Skarlso Mar 24, 2019
25c7654
Added error handling for list.
Skarlso Mar 24, 2019
531ae18
Added two trivial tests.
Skarlso Mar 24, 2019
a24fdcf
Using the Hostname function instead of the property and a tiny typo fix.
Skarlso Mar 28, 2019
39e3245
Addressing comments...
Skarlso Mar 30, 2019
9190cfa
Fixed test flag name.
Skarlso Mar 30, 2019
7fb56a1
Changed output.
Skarlso Mar 31, 2019
3b486fd
Changed output but it is not working yet. I will soon fix it.
Skarlso Mar 31, 2019
36f161b
Fixed output.
Skarlso Mar 31, 2019
3fde86f
Fixed filter label name.
Skarlso Mar 31, 2019
26da54e
Formatter... work in progress for single machine.
Skarlso Mar 31, 2019
09b40d6
Working single formatting.
Skarlso Mar 31, 2019
d183e61
Ups.. left image out.
Skarlso Mar 31, 2019
9ca9c4b
Adjusted the test and fixed port mapping.
Skarlso Mar 31, 2019
e0be0ad
Formatting and making table view use json formatter.
Skarlso Mar 31, 2019
dd971fe
Merge branch 'master' into issues_7_list_status
Skarlso Apr 1, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
module github.com/weaveworks/footloose

require (
github.com/apcera/termtables v0.0.0-20170405184538-bcbc5dc54055
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.3.3 // indirect
github.com/ghodss/yaml v1.0.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.3.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
sigs.k8s.io/kind v0.0.0-20190204012257-d1773a79317d
)
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
github.com/apcera/termtables v0.0.0-20170405184538-bcbc5dc54055 h1:IkPAzP+QjchKXXFX6LCcpDKa89b/e/0gPCUbQGWtUUY=
github.com/apcera/termtables v0.0.0-20170405184538-bcbc5dc54055/go.mod h1:8mHYHlOef9UC51cK1/WRvE/iQVM8O8QlYFa8eh8r5I8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/platform9/azure-provider v0.1.0 h1:i4pcZJEAL8r6HHNmN0puVQDHF1fbA2SHdzklssoaeIc=
Expand All @@ -22,8 +36,15 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 h1:78jEq2G3J16aXneH23HSnTQQTCwMHoyO8VEiUH+bpPM=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
36 changes: 36 additions & 0 deletions list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"github.com/spf13/cobra"
"github.com/weaveworks/footloose/pkg/cluster"
)

var listCmd = &cobra.Command{
Use: "list",
Short: "List all running machines",
RunE: list,
}

var listOptions struct {
format string
config string
all bool
}

func init() {
listCmd.Flags().StringVarP(&listOptions.config, "config", "c", Footloose, "Cluster configuration file")
listCmd.Flags().StringVarP(&listOptions.format, "format", "f", "default", "Formatting options")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use --output, -o here so we can reserve --format for go templates.

listCmd.Flags().BoolVar(&listOptions.all, "all", false, "List all footloose created machines in every cluster.")
footloose.AddCommand(listCmd)
}

// list will list all machines in a given cluster.
// if --all option is provided it will list every machine created by
// footloose no matter what cluster they are in.
func list(cmd *cobra.Command, args []string) error {
cluster, err := cluster.NewFromFile(listOptions.config)
if err != nil {
return err
}
return cluster.List(listOptions.all, listOptions.format)
}
94 changes: 94 additions & 0 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package cluster

import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/weaveworks/footloose/pkg/config"
Expand Down Expand Up @@ -162,6 +166,8 @@ func (c *Cluster) createMachine(machine *Machine, i int) error {
func (c *Cluster) createMachineRunArgs(machine *Machine, name string, i int) []string {
runArgs := []string{
"-it", "-d", "--rm",
"--label", "org.weaveworks.owner=footloose",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works.weave.* should be the prefix :)

"--label", "org.weaveworks.cluster=" + c.spec.Cluster.Name,
"--name", name,
"--hostname", machine.Hostname(),
"--tmpfs", "/run",
Expand Down Expand Up @@ -232,6 +238,94 @@ func (c *Cluster) Delete() error {
return c.forEachMachine(c.deleteMachine)
}

// List will generate an output for each machine.
func (c *Cluster) List(all bool, format string) error {
machines, err := c.gatherMachinesWithFallback(all)
if err != nil {
return err
}
formatter, err := getFormatter(format)
if err != nil {
return err
}
return formatter.Format(machines)
}

func (c *Cluster) gatherMachinesWithFallback(all bool) (machines []*Machine, err error) {
machines, err = c.gatherMachinesByAPI(all)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if we should have the footloose.yaml file that created the container as a label on the container. This way, we could inspect running containers and detect if there are nodes that should be up in other clusters and are actually down. Meh, that's something we can refined after this PR has landed.

if err != nil {
return []*Machine{}, err
}
// Footloose has no machines running. Falling back to display
// cluster related data.
if len(machines) < 1 {
machines = c.gatherMachinesByCluster()
}
return
}

func (c *Cluster) gatherMachinesByAPI(all bool) (machines []*Machine, err error) {
cli, err := client.NewEnvClient()
if err != nil {
return []*Machine{}, err
}

args := filters.NewArgs()
args.Add("label", "org.weaveworks.owner=footloose")
if !all {
args.Add("label", "org.weaveworks.cluster="+c.spec.Cluster.Name)
}
ctx := context.Background()
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
Filters: args,
})
if err != nil {
return []*Machine{}, err
}

for _, container := range containers {
m := Machine{}
spec := config.Machine{}
m.name = container.Names[0]
inspect, err := cli.ContainerInspect(ctx, container.ID)
if err != nil {
return []*Machine{}, err
}
m.hostname = inspect.Config.Hostname
ports := make(map[int]int)
for _, p := range container.Ports {
ports[int(p.PrivatePort)] = int(p.PublicPort)
}
m.ports = ports
spec.Cmd = container.Command
spec.Image = container.Image
var volumes []config.Volume
for _, mount := range container.Mounts {
v := config.Volume{
Type: string(mount.Type),
Source: mount.Source,
Destination: mount.Destination,
ReadOnly: mount.RW,
}
volumes = append(volumes, v)
}
spec.Volumes = volumes
m.spec = &spec
machines = append(machines, &m)
}
return
}

func (c *Cluster) gatherMachinesByCluster() (machines []*Machine) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that you already have forEachMachine but since I wanted each machine I extracted this. I could use the forEachMachine function but then I would have to resort to a package wide variable to track each machines inspect data that came back. That was my other idea in case I would loose the --all option.

for _, template := range c.spec.Machines {
for i := 0; i < template.Count; i++ {
machine := c.machine(&template.Spec, i)
machines = append(machines, machine)
}
}
return
}

// io.Writer filter that writes that it receives to writer. Keeps track if it
// has seen a write matching regexp.
type matchFilter struct {
Expand Down
121 changes: 121 additions & 0 deletions pkg/cluster/formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cluster

import (
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/apcera/termtables"
"github.com/weaveworks/footloose/pkg/config"
)

// Formatter formats a slice of machines and outputs the result
// in a given format.
type Formatter interface {
Format([]*Machine) error
}

// JSONFormatter formats a slice of machines into a JSON and
// outputs it to stdout.
type JSONFormatter struct{}

// NormalFormatter formats a slice of machines into a colored
// table like output and prints that to stdout.
type NormalFormatter struct{}

type port struct {
Guest int `json:"guest"`
Host int `json:"host"`
}

type status struct {
Name string `json:"name"`
Spec config.Machine `json:"spec"`
Status string `json:"status"`
Ports []port `json:"ports"`
Hostname string `json:"hostname"`
}

// Format will output to stdout in JSON format.
func (JSONFormatter) Format(machines []*Machine) error {
statuses := make([]status, 0)
for _, m := range machines {
s := status{}
s.Hostname = m.Hostname()
s.Name = m.ContainerName()
s.Spec = *m.spec
state := "Stopped"
if m.IsRunning() {
state = "Running"
}
s.Status = state
var ports []port
for k, v := range m.ports {
p := port{
Host: v,
Guest: k,
}
ports = append(ports, p)
}
s.Ports = ports
statuses = append(statuses, s)
}
m := struct {
Machines []status `json:"machines"`
}{
Machines: statuses,
}
ms, err := json.Marshal(m)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be nice to directly provide an indented output I think (MashalIndent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I just thought that usually when you want JSON you want it because you would like to parse it. In which case indentation might cause a problem in certain cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json parsers should really be able to handle the whitespace introduced by the indentation :)

if err != nil {
return err
}
fmt.Printf("%s", ms)
return nil
}

// Format will output to stdout in table format.
func (NormalFormatter) Format(machines []*Machine) error {
table := termtables.CreateTable()
table.AddHeaders("Name", "Hostname", "Ports", "Image", "Cmd", "Volumes", "State")
for _, m := range machines {
state := "Stopped"
if m.IsRunning() {
state = "Running"
}
var ports []string
for k, v := range m.ports {
p := fmt.Sprintf("%d->%d", k, v)
ports = append(ports, p)
}
if len(ports) < 1 {
for _, p := range m.spec.PortMappings {
port := fmt.Sprintf("%d->%d", p.ContainerPort, 0)
ports = append(ports, port)
}
}
ps := strings.Join(ports, ",")
var volumes []string
for _, v := range m.spec.Volumes {
vf := fmt.Sprintf("%s->%s", v.Source, v.Destination)
volumes = append(volumes, vf)
}
vs := strings.Join(volumes, ",")
table.AddRow(m.ContainerName(), m.hostname, ps, m.spec.Image, m.spec.Cmd, vs, state)
}
fmt.Println(table.Render())
return nil
}

func getFormatter(format string) (Formatter, error) {
var formatter Formatter
switch format {
case "json":
formatter = new(JSONFormatter)
case "default":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a better name, like table :) and TableFormatter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. :)

formatter = new(NormalFormatter)
default:
return nil, errors.New("unrecognised formatting method")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice to add information about what is wrong, say:

unknown format "foofooo"

so the user knows at a glance which part of the command line is wrong.

}
return formatter, nil
}
5 changes: 5 additions & 0 deletions tests/test-list-ubuntu18.04.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
footloose config create --config %testName.footloose --name %testName --key %testName-key --image quay.io/footloose/ubuntu18.04
footloose create --config %testName.footloose
footloose delete --config %testName.footloose
%out footloose list --config %testName.footloose
%out footloose list -f json --config %testName.footloose
7 changes: 7 additions & 0 deletions tests/test-list-ubuntu18.04.golden.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
+-----------------------------+----------+-------+-------------------------------+-----+---------+---------+
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, considering how the golden output works, I can only test for non running containers, since the running ones have dynamic data. Like the host port number which would be different on each test run. This is good enough for now...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm envisioning being able to query parts of the json like docker inspect:

footloose show node0 -f '{{.Status}}'

we could use to test show when we have that.

| Name | Hostname | Ports | Image | Cmd | Volumes | State |
+-----------------------------+----------+-------+-------------------------------+-----+---------+---------+
| test-list-ubuntu18.04-node0 | node0 | 22->0 | quay.io/footloose/ubuntu18.04 | | | Stopped |
+-----------------------------+----------+-------+-------------------------------+-----+---------+---------+

{"machines":[{"name":"test-list-ubuntu18.04-node0","spec":{"name":"node%d","image":"quay.io/footloose/ubuntu18.04","portMappings":[{"containerPort":22}]},"status":"Stopped","ports":null,"hostname":"node0"}]}