github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/node/formatter_test.go (about)

     1  package node
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/cli/cli/command/formatter"
    11  	"github.com/docker/cli/internal/test"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/swarm"
    14  	"github.com/docker/docker/pkg/stringid"
    15  	"gotest.tools/assert"
    16  	is "gotest.tools/assert/cmp"
    17  )
    18  
    19  func TestNodeContext(t *testing.T) {
    20  	nodeID := stringid.GenerateRandomID()
    21  
    22  	var ctx nodeContext
    23  	cases := []struct {
    24  		nodeCtx  nodeContext
    25  		expValue string
    26  		call     func() string
    27  	}{
    28  		{nodeContext{
    29  			n: swarm.Node{ID: nodeID},
    30  		}, nodeID, ctx.ID},
    31  		{nodeContext{
    32  			n: swarm.Node{Description: swarm.NodeDescription{Hostname: "node_hostname"}},
    33  		}, "node_hostname", ctx.Hostname},
    34  		{nodeContext{
    35  			n: swarm.Node{Status: swarm.NodeStatus{State: swarm.NodeState("foo")}},
    36  		}, "Foo", ctx.Status},
    37  		{nodeContext{
    38  			n: swarm.Node{Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")}},
    39  		}, "Drain", ctx.Availability},
    40  		{nodeContext{
    41  			n: swarm.Node{ManagerStatus: &swarm.ManagerStatus{Leader: true}},
    42  		}, "Leader", ctx.ManagerStatus},
    43  	}
    44  
    45  	for _, c := range cases {
    46  		ctx = c.nodeCtx
    47  		v := c.call()
    48  		if strings.Contains(v, ",") {
    49  			test.CompareMultipleValues(t, v, c.expValue)
    50  		} else if v != c.expValue {
    51  			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
    52  		}
    53  	}
    54  }
    55  
    56  func TestNodeContextWrite(t *testing.T) {
    57  	cases := []struct {
    58  		context     formatter.Context
    59  		expected    string
    60  		clusterInfo swarm.ClusterInfo
    61  	}{
    62  
    63  		// Errors
    64  		{
    65  			context: formatter.Context{Format: "{{InvalidFunction}}"},
    66  			expected: `Template parsing error: template: :1: function "InvalidFunction" not defined
    67  `,
    68  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
    69  		},
    70  		{
    71  			context: formatter.Context{Format: "{{nil}}"},
    72  			expected: `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
    73  `,
    74  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
    75  		},
    76  		// Table format
    77  		{
    78  			context: formatter.Context{Format: NewFormat("table", false)},
    79  			expected: `ID                  HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    80  nodeID1             foobar_baz          Foo                 Drain               Leader              18.03.0-ce
    81  nodeID2             foobar_bar          Bar                 Active              Reachable           1.2.3
    82  nodeID3             foobar_boo          Boo                 Active                                  ` + "\n", // (to preserve whitespace)
    83  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
    84  		},
    85  		{
    86  			context: formatter.Context{Format: NewFormat("table", true)},
    87  			expected: `nodeID1
    88  nodeID2
    89  nodeID3
    90  `,
    91  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
    92  		},
    93  		{
    94  			context: formatter.Context{Format: NewFormat("table {{.Hostname}}", false)},
    95  			expected: `HOSTNAME
    96  foobar_baz
    97  foobar_bar
    98  foobar_boo
    99  `,
   100  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   101  		},
   102  		{
   103  			context: formatter.Context{Format: NewFormat("table {{.Hostname}}", true)},
   104  			expected: `HOSTNAME
   105  foobar_baz
   106  foobar_bar
   107  foobar_boo
   108  `,
   109  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   110  		},
   111  		{
   112  			context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)},
   113  			expected: `ID                  HOSTNAME            TLS STATUS
   114  nodeID1             foobar_baz          Needs Rotation
   115  nodeID2             foobar_bar          Ready
   116  nodeID3             foobar_boo          Unknown
   117  `,
   118  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   119  		},
   120  		{ // no cluster TLS status info, TLS status for all nodes is unknown
   121  			context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)},
   122  			expected: `ID                  HOSTNAME            TLS STATUS
   123  nodeID1             foobar_baz          Unknown
   124  nodeID2             foobar_bar          Unknown
   125  nodeID3             foobar_boo          Unknown
   126  `,
   127  			clusterInfo: swarm.ClusterInfo{},
   128  		},
   129  		// Raw Format
   130  		{
   131  			context: formatter.Context{Format: NewFormat("raw", false)},
   132  			expected: `node_id: nodeID1
   133  hostname: foobar_baz
   134  status: Foo
   135  availability: Drain
   136  manager_status: Leader
   137  
   138  node_id: nodeID2
   139  hostname: foobar_bar
   140  status: Bar
   141  availability: Active
   142  manager_status: Reachable
   143  
   144  node_id: nodeID3
   145  hostname: foobar_boo
   146  status: Boo
   147  availability: Active
   148  manager_status: ` + "\n\n", // to preserve whitespace
   149  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   150  		},
   151  		{
   152  			context: formatter.Context{Format: NewFormat("raw", true)},
   153  			expected: `node_id: nodeID1
   154  node_id: nodeID2
   155  node_id: nodeID3
   156  `,
   157  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   158  		},
   159  		// Custom Format
   160  		{
   161  			context: formatter.Context{Format: NewFormat("{{.Hostname}}  {{.TLSStatus}}", false)},
   162  			expected: `foobar_baz  Needs Rotation
   163  foobar_bar  Ready
   164  foobar_boo  Unknown
   165  `,
   166  			clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
   167  		},
   168  	}
   169  
   170  	for _, testcase := range cases {
   171  		nodes := []swarm.Node{
   172  			{
   173  				ID: "nodeID1",
   174  				Description: swarm.NodeDescription{
   175  					Hostname: "foobar_baz",
   176  					TLSInfo:  swarm.TLSInfo{TrustRoot: "no"},
   177  					Engine:   swarm.EngineDescription{EngineVersion: "18.03.0-ce"},
   178  				},
   179  				Status:        swarm.NodeStatus{State: swarm.NodeState("foo")},
   180  				Spec:          swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")},
   181  				ManagerStatus: &swarm.ManagerStatus{Leader: true},
   182  			},
   183  			{
   184  				ID: "nodeID2",
   185  				Description: swarm.NodeDescription{
   186  					Hostname: "foobar_bar",
   187  					TLSInfo:  swarm.TLSInfo{TrustRoot: "hi"},
   188  					Engine:   swarm.EngineDescription{EngineVersion: "1.2.3"},
   189  				},
   190  				Status: swarm.NodeStatus{State: swarm.NodeState("bar")},
   191  				Spec:   swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
   192  				ManagerStatus: &swarm.ManagerStatus{
   193  					Leader:       false,
   194  					Reachability: swarm.Reachability("Reachable"),
   195  				},
   196  			},
   197  			{
   198  				ID:          "nodeID3",
   199  				Description: swarm.NodeDescription{Hostname: "foobar_boo"},
   200  				Status:      swarm.NodeStatus{State: swarm.NodeState("boo")},
   201  				Spec:        swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
   202  			},
   203  		}
   204  		out := bytes.NewBufferString("")
   205  		testcase.context.Output = out
   206  		err := FormatWrite(testcase.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &testcase.clusterInfo}})
   207  		if err != nil {
   208  			assert.Error(t, err, testcase.expected)
   209  		} else {
   210  			assert.Check(t, is.Equal(testcase.expected, out.String()))
   211  		}
   212  	}
   213  }
   214  
   215  func TestNodeContextWriteJSON(t *testing.T) {
   216  	cases := []struct {
   217  		expected []map[string]interface{}
   218  		info     types.Info
   219  	}{
   220  		{
   221  			expected: []map[string]interface{}{
   222  				{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "1.2.3"},
   223  				{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": ""},
   224  				{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
   225  			},
   226  			info: types.Info{},
   227  		},
   228  		{
   229  			expected: []map[string]interface{}{
   230  				{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready", "EngineVersion": "1.2.3"},
   231  				{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation", "EngineVersion": ""},
   232  				{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
   233  			},
   234  			info: types.Info{
   235  				Swarm: swarm.Info{
   236  					Cluster: &swarm.ClusterInfo{
   237  						TLSInfo:                swarm.TLSInfo{TrustRoot: "hi"},
   238  						RootRotationInProgress: true,
   239  					},
   240  				},
   241  			},
   242  		},
   243  	}
   244  
   245  	for _, testcase := range cases {
   246  		nodes := []swarm.Node{
   247  			{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, Engine: swarm.EngineDescription{EngineVersion: "1.2.3"}}},
   248  			{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}},
   249  			{ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}},
   250  		}
   251  		out := bytes.NewBufferString("")
   252  		err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info)
   253  		if err != nil {
   254  			t.Fatal(err)
   255  		}
   256  		for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   257  			msg := fmt.Sprintf("Output: line %d: %s", i, line)
   258  			var m map[string]interface{}
   259  			err := json.Unmarshal([]byte(line), &m)
   260  			assert.NilError(t, err, msg)
   261  			assert.Check(t, is.DeepEqual(testcase.expected[i], m), msg)
   262  		}
   263  	}
   264  }
   265  
   266  func TestNodeContextWriteJSONField(t *testing.T) {
   267  	nodes := []swarm.Node{
   268  		{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz"}},
   269  		{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}},
   270  	}
   271  	out := bytes.NewBufferString("")
   272  	err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, types.Info{})
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   277  		msg := fmt.Sprintf("Output: line %d: %s", i, line)
   278  		var s string
   279  		err := json.Unmarshal([]byte(line), &s)
   280  		assert.NilError(t, err, msg)
   281  		assert.Check(t, is.Equal(nodes[i].ID, s), msg)
   282  	}
   283  }
   284  
   285  func TestNodeInspectWriteContext(t *testing.T) {
   286  	node := swarm.Node{
   287  		ID: "nodeID1",
   288  		Description: swarm.NodeDescription{
   289  			Hostname: "foobar_baz",
   290  			TLSInfo: swarm.TLSInfo{
   291  				TrustRoot:           "-----BEGIN CERTIFICATE-----\ndata\n-----END CERTIFICATE-----\n",
   292  				CertIssuerPublicKey: []byte("pubKey"),
   293  				CertIssuerSubject:   []byte("subject"),
   294  			},
   295  			Platform: swarm.Platform{
   296  				OS:           "linux",
   297  				Architecture: "amd64",
   298  			},
   299  			Resources: swarm.Resources{
   300  				MemoryBytes: 1,
   301  			},
   302  			Engine: swarm.EngineDescription{
   303  				EngineVersion: "0.1.1",
   304  			},
   305  		},
   306  		Status: swarm.NodeStatus{
   307  			State: swarm.NodeState("ready"),
   308  			Addr:  "1.1.1.1",
   309  		},
   310  		Spec: swarm.NodeSpec{
   311  			Availability: swarm.NodeAvailability("drain"),
   312  			Role:         swarm.NodeRole("manager"),
   313  		},
   314  	}
   315  	out := bytes.NewBufferString("")
   316  	context := formatter.Context{
   317  		Format: NewFormat("pretty", false),
   318  		Output: out,
   319  	}
   320  	err := InspectFormatWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) {
   321  		return node, nil, nil
   322  	})
   323  	if err != nil {
   324  		t.Fatal(err)
   325  	}
   326  	expected := `ID:			nodeID1
   327  Hostname:              	foobar_baz
   328  Joined at:             	0001-01-01 00:00:00 +0000 utc
   329  Status:
   330   State:			Ready
   331   Availability:         	Drain
   332   Address:		1.1.1.1
   333  Platform:
   334   Operating System:	linux
   335   Architecture:		amd64
   336  Resources:
   337   CPUs:			0
   338   Memory:		1B
   339  Engine Version:		0.1.1
   340  TLS Info:
   341   TrustRoot:
   342  -----BEGIN CERTIFICATE-----
   343  data
   344  -----END CERTIFICATE-----
   345  
   346   Issuer Subject:	c3ViamVjdA==
   347   Issuer Public Key:	cHViS2V5
   348  `
   349  	assert.Check(t, is.Equal(expected, out.String()))
   350  }