github.com/xeptore/docker-cli@v20.10.14+incompatible/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/v3/assert"
    16  	is "gotest.tools/v3/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  	nodes := []swarm.Node{
   171  		{
   172  			ID: "nodeID1",
   173  			Description: swarm.NodeDescription{
   174  				Hostname: "foobar_baz",
   175  				TLSInfo:  swarm.TLSInfo{TrustRoot: "no"},
   176  				Engine:   swarm.EngineDescription{EngineVersion: "18.03.0-ce"},
   177  			},
   178  			Status:        swarm.NodeStatus{State: swarm.NodeState("foo")},
   179  			Spec:          swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")},
   180  			ManagerStatus: &swarm.ManagerStatus{Leader: true},
   181  		},
   182  		{
   183  			ID: "nodeID2",
   184  			Description: swarm.NodeDescription{
   185  				Hostname: "foobar_bar",
   186  				TLSInfo:  swarm.TLSInfo{TrustRoot: "hi"},
   187  				Engine:   swarm.EngineDescription{EngineVersion: "1.2.3"},
   188  			},
   189  			Status: swarm.NodeStatus{State: swarm.NodeState("bar")},
   190  			Spec:   swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
   191  			ManagerStatus: &swarm.ManagerStatus{
   192  				Leader:       false,
   193  				Reachability: swarm.Reachability("Reachable"),
   194  			},
   195  		},
   196  		{
   197  			ID:          "nodeID3",
   198  			Description: swarm.NodeDescription{Hostname: "foobar_boo"},
   199  			Status:      swarm.NodeStatus{State: swarm.NodeState("boo")},
   200  			Spec:        swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
   201  		},
   202  	}
   203  
   204  	for _, tc := range cases {
   205  		tc := tc
   206  		t.Run(string(tc.context.Format), func(t *testing.T) {
   207  			var out bytes.Buffer
   208  			tc.context.Output = &out
   209  
   210  			err := FormatWrite(tc.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &tc.clusterInfo}})
   211  			if err != nil {
   212  				assert.Error(t, err, tc.expected)
   213  			} else {
   214  				assert.Equal(t, out.String(), tc.expected)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func TestNodeContextWriteJSON(t *testing.T) {
   221  	cases := []struct {
   222  		expected []map[string]interface{}
   223  		info     types.Info
   224  	}{
   225  		{
   226  			expected: []map[string]interface{}{
   227  				{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "1.2.3"},
   228  				{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": ""},
   229  				{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
   230  			},
   231  			info: types.Info{},
   232  		},
   233  		{
   234  			expected: []map[string]interface{}{
   235  				{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready", "EngineVersion": "1.2.3"},
   236  				{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation", "EngineVersion": ""},
   237  				{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
   238  			},
   239  			info: types.Info{
   240  				Swarm: swarm.Info{
   241  					Cluster: &swarm.ClusterInfo{
   242  						TLSInfo:                swarm.TLSInfo{TrustRoot: "hi"},
   243  						RootRotationInProgress: true,
   244  					},
   245  				},
   246  			},
   247  		},
   248  	}
   249  
   250  	for _, testcase := range cases {
   251  		nodes := []swarm.Node{
   252  			{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, Engine: swarm.EngineDescription{EngineVersion: "1.2.3"}}},
   253  			{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}},
   254  			{ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}},
   255  		}
   256  		out := bytes.NewBufferString("")
   257  		err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info)
   258  		if err != nil {
   259  			t.Fatal(err)
   260  		}
   261  		for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   262  			msg := fmt.Sprintf("Output: line %d: %s", i, line)
   263  			var m map[string]interface{}
   264  			err := json.Unmarshal([]byte(line), &m)
   265  			assert.NilError(t, err, msg)
   266  			assert.Check(t, is.DeepEqual(testcase.expected[i], m), msg)
   267  		}
   268  	}
   269  }
   270  
   271  func TestNodeContextWriteJSONField(t *testing.T) {
   272  	nodes := []swarm.Node{
   273  		{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz"}},
   274  		{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}},
   275  	}
   276  	out := bytes.NewBufferString("")
   277  	err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, types.Info{})
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   282  		msg := fmt.Sprintf("Output: line %d: %s", i, line)
   283  		var s string
   284  		err := json.Unmarshal([]byte(line), &s)
   285  		assert.NilError(t, err, msg)
   286  		assert.Check(t, is.Equal(nodes[i].ID, s), msg)
   287  	}
   288  }
   289  
   290  func TestNodeInspectWriteContext(t *testing.T) {
   291  	node := swarm.Node{
   292  		ID: "nodeID1",
   293  		Description: swarm.NodeDescription{
   294  			Hostname: "foobar_baz",
   295  			TLSInfo: swarm.TLSInfo{
   296  				TrustRoot:           "-----BEGIN CERTIFICATE-----\ndata\n-----END CERTIFICATE-----\n",
   297  				CertIssuerPublicKey: []byte("pubKey"),
   298  				CertIssuerSubject:   []byte("subject"),
   299  			},
   300  			Platform: swarm.Platform{
   301  				OS:           "linux",
   302  				Architecture: "amd64",
   303  			},
   304  			Resources: swarm.Resources{
   305  				MemoryBytes: 1,
   306  			},
   307  			Engine: swarm.EngineDescription{
   308  				EngineVersion: "0.1.1",
   309  			},
   310  		},
   311  		Status: swarm.NodeStatus{
   312  			State: swarm.NodeState("ready"),
   313  			Addr:  "1.1.1.1",
   314  		},
   315  		Spec: swarm.NodeSpec{
   316  			Availability: swarm.NodeAvailability("drain"),
   317  			Role:         swarm.NodeRole("manager"),
   318  		},
   319  	}
   320  	out := bytes.NewBufferString("")
   321  	context := formatter.Context{
   322  		Format: NewFormat("pretty", false),
   323  		Output: out,
   324  	}
   325  	err := InspectFormatWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) {
   326  		return node, nil, nil
   327  	})
   328  	if err != nil {
   329  		t.Fatal(err)
   330  	}
   331  	expected := `ID:			nodeID1
   332  Hostname:              	foobar_baz
   333  Joined at:             	0001-01-01 00:00:00 +0000 utc
   334  Status:
   335   State:			Ready
   336   Availability:         	Drain
   337   Address:		1.1.1.1
   338  Platform:
   339   Operating System:	linux
   340   Architecture:		amd64
   341  Resources:
   342   CPUs:			0
   343   Memory:		1B
   344  Engine Version:		0.1.1
   345  TLS Info:
   346   TrustRoot:
   347  -----BEGIN CERTIFICATE-----
   348  data
   349  -----END CERTIFICATE-----
   350  
   351   Issuer Subject:	c3ViamVjdA==
   352   Issuer Public Key:	cHViS2V5
   353  `
   354  	assert.Check(t, is.Equal(expected, out.String()))
   355  }