github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/formatter_test.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package service
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/cli/cli/command/formatter"
    14  	"github.com/docker/docker/api/types/swarm"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/golden"
    18  )
    19  
    20  func TestServiceContextWrite(t *testing.T) {
    21  	var (
    22  		// we need a pair of variables for setting the job parameters, because
    23  		// those parameters take pointers to uint64, which we can't make as a
    24  		// literal
    25  		varThree uint64 = 3
    26  		varTen   uint64 = 10
    27  	)
    28  	cases := []struct {
    29  		context  formatter.Context
    30  		expected string
    31  	}{
    32  		// Errors
    33  		{
    34  			formatter.Context{Format: "{{InvalidFunction}}"},
    35  			`template parsing error: template: :1: function "InvalidFunction" not defined`,
    36  		},
    37  		{
    38  			formatter.Context{Format: "{{nil}}"},
    39  			`template parsing error: template: :1:2: executing "" at <nil>: nil is not a command`,
    40  		},
    41  		// Table format
    42  		{
    43  			formatter.Context{Format: NewListFormat("table", false)},
    44  			`ID         NAME      MODE             REPLICAS               IMAGE     PORTS
    45  02_bar     bar       replicated       2/4                              *:80->8090/udp
    46  01_baz     baz       global           1/3                              *:80->8080/tcp
    47  04_qux2    qux2      replicated       3/3 (max 2 per node)             
    48  03_qux10   qux10     replicated       2/3 (max 1 per node)             
    49  05_job1    zarp1     replicated job   2/3 (5/10 completed)             
    50  06_job2    zarp2     global job       1/1 (3/4 completed)              
    51  `,
    52  		},
    53  		{
    54  			formatter.Context{Format: NewListFormat("table", true)},
    55  			`02_bar
    56  01_baz
    57  04_qux2
    58  03_qux10
    59  05_job1
    60  06_job2
    61  `,
    62  		},
    63  		{
    64  			formatter.Context{Format: NewListFormat("table {{.Name}}\t{{.Mode}}", false)},
    65  			`NAME      MODE
    66  bar       replicated
    67  baz       global
    68  qux2      replicated
    69  qux10     replicated
    70  zarp1     replicated job
    71  zarp2     global job
    72  `,
    73  		},
    74  		{
    75  			formatter.Context{Format: NewListFormat("table {{.Name}}", true)},
    76  			`NAME
    77  bar
    78  baz
    79  qux2
    80  qux10
    81  zarp1
    82  zarp2
    83  `,
    84  		},
    85  		// Raw Format
    86  		{
    87  			formatter.Context{Format: NewListFormat("raw", false)},
    88  			string(golden.Get(t, "service-context-write-raw.golden")),
    89  		},
    90  		{
    91  			formatter.Context{Format: NewListFormat("raw", true)},
    92  			`id: 02_bar
    93  id: 01_baz
    94  id: 04_qux2
    95  id: 03_qux10
    96  id: 05_job1
    97  id: 06_job2
    98  `,
    99  		},
   100  		// Custom Format
   101  		{
   102  			formatter.Context{Format: NewListFormat("{{.Name}}", false)},
   103  			`bar
   104  baz
   105  qux2
   106  qux10
   107  zarp1
   108  zarp2
   109  `,
   110  		},
   111  	}
   112  
   113  	services := []swarm.Service{
   114  		{
   115  			ID: "01_baz",
   116  			Spec: swarm.ServiceSpec{
   117  				Annotations: swarm.Annotations{Name: "baz"},
   118  				Mode: swarm.ServiceMode{
   119  					Global: &swarm.GlobalService{},
   120  				},
   121  			},
   122  			Endpoint: swarm.Endpoint{
   123  				Ports: []swarm.PortConfig{
   124  					{
   125  						PublishMode:   "ingress",
   126  						PublishedPort: 80,
   127  						TargetPort:    8080,
   128  						Protocol:      "tcp",
   129  					},
   130  				},
   131  			},
   132  			ServiceStatus: &swarm.ServiceStatus{
   133  				RunningTasks: 1,
   134  				DesiredTasks: 3,
   135  			},
   136  		},
   137  		{
   138  			ID: "02_bar",
   139  			Spec: swarm.ServiceSpec{
   140  				Annotations: swarm.Annotations{Name: "bar"},
   141  				Mode: swarm.ServiceMode{
   142  					Replicated: &swarm.ReplicatedService{},
   143  				},
   144  			},
   145  			Endpoint: swarm.Endpoint{
   146  				Ports: []swarm.PortConfig{
   147  					{
   148  						PublishMode:   "ingress",
   149  						PublishedPort: 80,
   150  						TargetPort:    8090,
   151  						Protocol:      "udp",
   152  					},
   153  				},
   154  			},
   155  			ServiceStatus: &swarm.ServiceStatus{
   156  				RunningTasks: 2,
   157  				DesiredTasks: 4,
   158  			},
   159  		},
   160  		{
   161  			ID: "03_qux10",
   162  			Spec: swarm.ServiceSpec{
   163  				Annotations: swarm.Annotations{Name: "qux10"},
   164  				Mode: swarm.ServiceMode{
   165  					Replicated: &swarm.ReplicatedService{},
   166  				},
   167  				TaskTemplate: swarm.TaskSpec{
   168  					Placement: &swarm.Placement{MaxReplicas: 1},
   169  				},
   170  			},
   171  			ServiceStatus: &swarm.ServiceStatus{
   172  				RunningTasks: 2,
   173  				DesiredTasks: 3,
   174  			},
   175  		},
   176  		{
   177  			ID: "04_qux2",
   178  			Spec: swarm.ServiceSpec{
   179  				Annotations: swarm.Annotations{Name: "qux2"},
   180  				Mode: swarm.ServiceMode{
   181  					Replicated: &swarm.ReplicatedService{},
   182  				},
   183  				TaskTemplate: swarm.TaskSpec{
   184  					Placement: &swarm.Placement{MaxReplicas: 2},
   185  				},
   186  			},
   187  			ServiceStatus: &swarm.ServiceStatus{
   188  				RunningTasks: 3,
   189  				DesiredTasks: 3,
   190  			},
   191  		},
   192  		{
   193  			ID: "05_job1",
   194  			Spec: swarm.ServiceSpec{
   195  				Annotations: swarm.Annotations{Name: "zarp1"},
   196  				Mode: swarm.ServiceMode{
   197  					ReplicatedJob: &swarm.ReplicatedJob{
   198  						MaxConcurrent:    &varThree,
   199  						TotalCompletions: &varTen,
   200  					},
   201  				},
   202  			},
   203  			ServiceStatus: &swarm.ServiceStatus{
   204  				RunningTasks:   2,
   205  				DesiredTasks:   3,
   206  				CompletedTasks: 5,
   207  			},
   208  		},
   209  		{
   210  			ID: "06_job2",
   211  			Spec: swarm.ServiceSpec{
   212  				Annotations: swarm.Annotations{Name: "zarp2"},
   213  				Mode: swarm.ServiceMode{
   214  					GlobalJob: &swarm.GlobalJob{},
   215  				},
   216  			},
   217  			ServiceStatus: &swarm.ServiceStatus{
   218  				RunningTasks:   1,
   219  				DesiredTasks:   1,
   220  				CompletedTasks: 3,
   221  			},
   222  		},
   223  	}
   224  
   225  	for _, tc := range cases {
   226  		tc := tc
   227  		t.Run(string(tc.context.Format), func(t *testing.T) {
   228  			var out bytes.Buffer
   229  			tc.context.Output = &out
   230  
   231  			if err := ListFormatWrite(tc.context, services); err != nil {
   232  				assert.Error(t, err, tc.expected)
   233  			} else {
   234  				assert.Equal(t, out.String(), tc.expected)
   235  			}
   236  		})
   237  	}
   238  }
   239  
   240  func TestServiceContextWriteJSON(t *testing.T) {
   241  	services := []swarm.Service{
   242  		{
   243  			ID: "01_baz",
   244  			Spec: swarm.ServiceSpec{
   245  				Annotations: swarm.Annotations{Name: "baz"},
   246  				Mode: swarm.ServiceMode{
   247  					Global: &swarm.GlobalService{},
   248  				},
   249  			},
   250  			Endpoint: swarm.Endpoint{
   251  				Ports: []swarm.PortConfig{
   252  					{
   253  						PublishMode:   "ingress",
   254  						PublishedPort: 80,
   255  						TargetPort:    8080,
   256  						Protocol:      "tcp",
   257  					},
   258  				},
   259  			},
   260  			ServiceStatus: &swarm.ServiceStatus{
   261  				RunningTasks: 1,
   262  				DesiredTasks: 3,
   263  			},
   264  		},
   265  		{
   266  			ID: "02_bar",
   267  			Spec: swarm.ServiceSpec{
   268  				Annotations: swarm.Annotations{Name: "bar"},
   269  				Mode: swarm.ServiceMode{
   270  					Replicated: &swarm.ReplicatedService{},
   271  				},
   272  			},
   273  			Endpoint: swarm.Endpoint{
   274  				Ports: []swarm.PortConfig{
   275  					{
   276  						PublishMode:   "ingress",
   277  						PublishedPort: 80,
   278  						TargetPort:    8080,
   279  						Protocol:      "tcp",
   280  					},
   281  				},
   282  			},
   283  			ServiceStatus: &swarm.ServiceStatus{
   284  				RunningTasks: 2,
   285  				DesiredTasks: 4,
   286  			},
   287  		},
   288  	}
   289  	expectedJSONs := []map[string]any{
   290  		{"ID": "02_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": "", "Ports": "*:80->8080/tcp"},
   291  		{"ID": "01_baz", "Name": "baz", "Mode": "global", "Replicas": "1/3", "Image": "", "Ports": "*:80->8080/tcp"},
   292  	}
   293  
   294  	out := bytes.NewBufferString("")
   295  	err := ListFormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, services)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   300  		msg := fmt.Sprintf("Output: line %d: %s", i, line)
   301  		var m map[string]any
   302  		err := json.Unmarshal([]byte(line), &m)
   303  		assert.NilError(t, err, msg)
   304  		assert.Check(t, is.DeepEqual(expectedJSONs[i], m), msg)
   305  	}
   306  }
   307  
   308  func TestServiceContextWriteJSONField(t *testing.T) {
   309  	services := []swarm.Service{
   310  		{
   311  			ID: "01_baz",
   312  			Spec: swarm.ServiceSpec{
   313  				Annotations: swarm.Annotations{Name: "baz"},
   314  				Mode: swarm.ServiceMode{
   315  					Global: &swarm.GlobalService{},
   316  				},
   317  			},
   318  			ServiceStatus: &swarm.ServiceStatus{
   319  				RunningTasks: 2,
   320  				DesiredTasks: 4,
   321  			},
   322  		},
   323  		{
   324  			ID: "24_bar",
   325  			Spec: swarm.ServiceSpec{
   326  				Annotations: swarm.Annotations{Name: "bar"},
   327  				Mode: swarm.ServiceMode{
   328  					Replicated: &swarm.ReplicatedService{},
   329  				},
   330  			},
   331  			ServiceStatus: &swarm.ServiceStatus{
   332  				RunningTasks: 2,
   333  				DesiredTasks: 4,
   334  			},
   335  		},
   336  	}
   337  	out := bytes.NewBufferString("")
   338  	err := ListFormatWrite(formatter.Context{Format: "{{json .Name}}", Output: out}, services)
   339  	if err != nil {
   340  		t.Fatal(err)
   341  	}
   342  	for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
   343  		msg := fmt.Sprintf("Output: line %d: %s", i, line)
   344  		var s string
   345  		err := json.Unmarshal([]byte(line), &s)
   346  		assert.NilError(t, err, msg)
   347  		assert.Check(t, is.Equal(services[i].Spec.Name, s), msg)
   348  	}
   349  }
   350  
   351  func TestServiceContext_Ports(t *testing.T) {
   352  	c := serviceContext{
   353  		service: swarm.Service{
   354  			Endpoint: swarm.Endpoint{
   355  				Ports: []swarm.PortConfig{
   356  					{
   357  						Protocol:      "tcp",
   358  						TargetPort:    80,
   359  						PublishedPort: 81,
   360  						PublishMode:   "ingress",
   361  					},
   362  					{
   363  						Protocol:      "tcp",
   364  						TargetPort:    80,
   365  						PublishedPort: 80,
   366  						PublishMode:   "ingress",
   367  					},
   368  					{
   369  						Protocol:      "tcp",
   370  						TargetPort:    95,
   371  						PublishedPort: 95,
   372  						PublishMode:   "ingress",
   373  					},
   374  					{
   375  						Protocol:      "tcp",
   376  						TargetPort:    90,
   377  						PublishedPort: 90,
   378  						PublishMode:   "ingress",
   379  					},
   380  					{
   381  						Protocol:      "tcp",
   382  						TargetPort:    91,
   383  						PublishedPort: 91,
   384  						PublishMode:   "ingress",
   385  					},
   386  					{
   387  						Protocol:      "tcp",
   388  						TargetPort:    92,
   389  						PublishedPort: 92,
   390  						PublishMode:   "ingress",
   391  					},
   392  					{
   393  						Protocol:      "tcp",
   394  						TargetPort:    93,
   395  						PublishedPort: 93,
   396  						PublishMode:   "ingress",
   397  					},
   398  					{
   399  						Protocol:      "tcp",
   400  						TargetPort:    94,
   401  						PublishedPort: 94,
   402  						PublishMode:   "ingress",
   403  					},
   404  					{
   405  						Protocol:      "udp",
   406  						TargetPort:    95,
   407  						PublishedPort: 95,
   408  						PublishMode:   "ingress",
   409  					},
   410  					{
   411  						Protocol:      "udp",
   412  						TargetPort:    90,
   413  						PublishedPort: 90,
   414  						PublishMode:   "ingress",
   415  					},
   416  					{
   417  						Protocol:      "udp",
   418  						TargetPort:    96,
   419  						PublishedPort: 96,
   420  						PublishMode:   "ingress",
   421  					},
   422  					{
   423  						Protocol:      "udp",
   424  						TargetPort:    91,
   425  						PublishedPort: 91,
   426  						PublishMode:   "ingress",
   427  					},
   428  					{
   429  						Protocol:      "udp",
   430  						TargetPort:    92,
   431  						PublishedPort: 92,
   432  						PublishMode:   "ingress",
   433  					},
   434  					{
   435  						Protocol:      "udp",
   436  						TargetPort:    93,
   437  						PublishedPort: 93,
   438  						PublishMode:   "ingress",
   439  					},
   440  					{
   441  						Protocol:      "udp",
   442  						TargetPort:    94,
   443  						PublishedPort: 94,
   444  						PublishMode:   "ingress",
   445  					},
   446  					{
   447  						Protocol:      "tcp",
   448  						TargetPort:    60,
   449  						PublishedPort: 60,
   450  						PublishMode:   "ingress",
   451  					},
   452  					{
   453  						Protocol:      "tcp",
   454  						TargetPort:    61,
   455  						PublishedPort: 61,
   456  						PublishMode:   "ingress",
   457  					},
   458  					{
   459  						Protocol:      "tcp",
   460  						TargetPort:    61,
   461  						PublishedPort: 62,
   462  						PublishMode:   "ingress",
   463  					},
   464  					{
   465  						Protocol:      "sctp",
   466  						TargetPort:    97,
   467  						PublishedPort: 97,
   468  						PublishMode:   "ingress",
   469  					},
   470  					{
   471  						Protocol:      "sctp",
   472  						TargetPort:    98,
   473  						PublishedPort: 98,
   474  						PublishMode:   "ingress",
   475  					},
   476  				},
   477  			},
   478  		},
   479  	}
   480  
   481  	assert.Check(t, is.Equal("*:97-98->97-98/sctp, *:60-61->60-61/tcp, *:62->61/tcp, *:80-81->80/tcp, *:90-95->90-95/tcp, *:90-96->90-96/udp", c.Ports()))
   482  }