github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/template/context_test.go (about)

     1  package template
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/docker/swarmkit/api"
     8  	"github.com/stretchr/testify/assert"
     9  )
    10  
    11  func TestTemplateContext(t *testing.T) {
    12  	for _, testcase := range []struct {
    13  		Test            string
    14  		Task            *api.Task
    15  		Context         Context
    16  		Expected        *api.ContainerSpec
    17  		Err             error
    18  		NodeDescription *api.NodeDescription
    19  	}{
    20  		{
    21  			Test: "Identity",
    22  			Task: modifyTask(func(t *api.Task) {
    23  				t.Spec = api.TaskSpec{
    24  					Runtime: &api.TaskSpec_Container{
    25  						Container: &api.ContainerSpec{
    26  							Env: []string{
    27  								"NOTOUCH=dont",
    28  							},
    29  							Mounts: []api.Mount{
    30  								{
    31  									Target: "foo",
    32  									Source: "bar",
    33  								},
    34  							},
    35  						},
    36  					},
    37  				}
    38  			}),
    39  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
    40  			}),
    41  			Expected: &api.ContainerSpec{
    42  				Env: []string{
    43  					"NOTOUCH=dont",
    44  				},
    45  				Mounts: []api.Mount{
    46  					{
    47  						Target: "foo",
    48  						Source: "bar",
    49  					},
    50  				},
    51  			},
    52  		},
    53  		{
    54  			Test: "Env",
    55  			Task: modifyTask(func(t *api.Task) {
    56  				t.Spec = api.TaskSpec{
    57  					Runtime: &api.TaskSpec_Container{
    58  						Container: &api.ContainerSpec{
    59  							Labels: map[string]string{
    60  								"ContainerLabel": "should-NOT-end-up-as-task",
    61  							},
    62  							Env: []string{
    63  								"MYENV=notemplate",
    64  								"{{.NotExpanded}}=foo",
    65  								"SERVICE_ID={{.Service.ID}}",
    66  								"SERVICE_NAME={{.Service.Name}}",
    67  								"TASK_ID={{.Task.ID}}",
    68  								"TASK_NAME={{.Task.Name}}",
    69  								"NODE_ID={{.Node.ID}}",
    70  								"SERVICE_LABELS={{range $k, $v := .Service.Labels}}{{$k}}={{$v}},{{end}}",
    71  							},
    72  						},
    73  					},
    74  				}
    75  			}),
    76  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
    77  			}),
    78  			Expected: &api.ContainerSpec{
    79  				Labels: map[string]string{
    80  					"ContainerLabel": "should-NOT-end-up-as-task",
    81  				},
    82  				Env: []string{
    83  					"MYENV=notemplate",
    84  					"{{.NotExpanded}}=foo",
    85  					"SERVICE_ID=serviceID",
    86  					"SERVICE_NAME=serviceName",
    87  					"TASK_ID=taskID",
    88  					"TASK_NAME=serviceName.10.taskID",
    89  					"NODE_ID=nodeID",
    90  					"SERVICE_LABELS=ServiceLabelOneKey=service-label-one-value,ServiceLabelTwoKey=service-label-two-value,com.example.ServiceLabelThreeKey=service-label-three-value,",
    91  				},
    92  			},
    93  		},
    94  		{
    95  			Test: "Mount",
    96  			Task: modifyTask(func(t *api.Task) {
    97  				t.Spec = api.TaskSpec{
    98  					Runtime: &api.TaskSpec_Container{
    99  						Container: &api.ContainerSpec{
   100  							Mounts: []api.Mount{
   101  								{
   102  									Source: "bar-{{.Node.ID}}-{{.Task.Name}}",
   103  									Target: "foo-{{.Service.ID}}-{{.Service.Name}}",
   104  								},
   105  								{
   106  									Source: "bar-{{.Node.ID}}-{{.Service.Name}}",
   107  									Target: "foo-{{.Task.Slot}}-{{.Task.ID}}",
   108  								},
   109  							},
   110  						},
   111  					},
   112  				}
   113  			}),
   114  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
   115  			}),
   116  			Expected: &api.ContainerSpec{
   117  				Mounts: []api.Mount{
   118  					{
   119  						Source: "bar-nodeID-serviceName.10.taskID",
   120  						Target: "foo-serviceID-serviceName",
   121  					},
   122  					{
   123  						Source: "bar-nodeID-serviceName",
   124  						Target: "foo-10-taskID",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			Test: "Hostname",
   131  			Task: modifyTask(func(t *api.Task) {
   132  				t.Spec = api.TaskSpec{
   133  					Runtime: &api.TaskSpec_Container{
   134  						Container: &api.ContainerSpec{
   135  							Hostname: "myhost-{{.Task.Slot}}",
   136  						},
   137  					},
   138  				}
   139  			}),
   140  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
   141  			}),
   142  			Expected: &api.ContainerSpec{
   143  				Hostname: "myhost-10",
   144  			},
   145  		},
   146  		{
   147  			Test: "Node hostname",
   148  			Task: modifyTask(func(t *api.Task) {
   149  				t.Spec = api.TaskSpec{
   150  					Runtime: &api.TaskSpec_Container{
   151  						Container: &api.ContainerSpec{
   152  							Hostname: "myservice-{{.Node.Hostname}}",
   153  						},
   154  					},
   155  				}
   156  			}),
   157  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
   158  				n.Hostname = "mynode"
   159  			}),
   160  			Expected: &api.ContainerSpec{
   161  				Hostname: "myservice-mynode",
   162  			},
   163  		},
   164  		{
   165  			Test: "Node architecture",
   166  			Task: modifyTask(func(t *api.Task) {
   167  				t.Spec = api.TaskSpec{
   168  					Runtime: &api.TaskSpec_Container{
   169  						Container: &api.ContainerSpec{
   170  							Hostname: "{{.Node.Hostname}}-{{.Node.Platform.OS}}-{{.Node.Platform.Architecture}}",
   171  						},
   172  					},
   173  				}
   174  			}),
   175  			NodeDescription: modifyNode(func(n *api.NodeDescription) {
   176  				n.Hostname = "mynode"
   177  				n.Platform.Architecture = "myarchitecture"
   178  				n.Platform.OS = "myos"
   179  			}),
   180  			Expected: &api.ContainerSpec{
   181  				Hostname: "mynode-myos-myarchitecture",
   182  			},
   183  		},
   184  	} {
   185  		t.Run(testcase.Test, func(t *testing.T) {
   186  			spec, err := ExpandContainerSpec(testcase.NodeDescription, testcase.Task)
   187  			if err != nil {
   188  				if testcase.Err == nil {
   189  					t.Fatalf("unexpected error: %v", err)
   190  				} else {
   191  					if err != testcase.Err {
   192  						t.Fatalf("unexpected error: %v != %v", err, testcase.Err)
   193  					}
   194  				}
   195  			}
   196  
   197  			assert.Equal(t, testcase.Expected, spec)
   198  
   199  			for k, v := range testcase.Task.Annotations.Labels {
   200  				// make sure that that task.annotations.labels didn't make an appearance.
   201  				visitAllTemplatedFields(spec, func(s string) {
   202  					if strings.Contains(s, k) || strings.Contains(s, v) {
   203  						t.Fatalf("string value from task labels found in expanded spec: %q or %q found in %q, on %#v", k, v, s, spec)
   204  					}
   205  				})
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  // modifyTask generates a task with interesting values then calls the function
   212  // with it. The caller can then modify the task and return the result.
   213  func modifyTask(fn func(t *api.Task)) *api.Task {
   214  	t := &api.Task{
   215  		ID:        "taskID",
   216  		ServiceID: "serviceID",
   217  		NodeID:    "nodeID",
   218  		Slot:      10,
   219  		Annotations: api.Annotations{
   220  			Labels: map[string]string{
   221  				// SUBTLE(stevvooe): Task labels ARE NOT templated. These are
   222  				// reserved for the system and templated is not really needed.
   223  				// Non of these values show show up in templates.
   224  				"TaskLabelOneKey":               "task-label-one-value",
   225  				"TaskLabelTwoKey":               "task-label-two-value",
   226  				"com.example.TaskLabelThreeKey": "task-label-three-value",
   227  			},
   228  		},
   229  		ServiceAnnotations: api.Annotations{
   230  			Name: "serviceName",
   231  			Labels: map[string]string{
   232  				"ServiceLabelOneKey":               "service-label-one-value",
   233  				"ServiceLabelTwoKey":               "service-label-two-value",
   234  				"com.example.ServiceLabelThreeKey": "service-label-three-value",
   235  			},
   236  		},
   237  	}
   238  
   239  	fn(t)
   240  
   241  	return t
   242  }
   243  
   244  // modifyNode generates a node with interesting values then calls the function
   245  // with it. The caller can then modify the node and return the result.
   246  func modifyNode(fn func(n *api.NodeDescription)) *api.NodeDescription {
   247  	n := &api.NodeDescription{
   248  		Hostname: "nodeHostname",
   249  		Platform: &api.Platform{
   250  			Architecture: "x86_64",
   251  			OS:           "linux",
   252  		},
   253  	}
   254  
   255  	fn(n)
   256  
   257  	return n
   258  }
   259  
   260  // visitAllTemplatedFields does just that.
   261  // TODO(stevvooe): Might be best to make this the actual implementation.
   262  func visitAllTemplatedFields(spec *api.ContainerSpec, fn func(value string)) {
   263  	for _, v := range spec.Env {
   264  		fn(v)
   265  	}
   266  
   267  	for _, mount := range spec.Mounts {
   268  		fn(mount.Target)
   269  		fn(mount.Source)
   270  
   271  		if mount.VolumeOptions != nil {
   272  			for _, v := range mount.VolumeOptions.Labels {
   273  				fn(v)
   274  			}
   275  
   276  			if mount.VolumeOptions.DriverConfig != nil {
   277  				for _, v := range mount.VolumeOptions.DriverConfig.Options {
   278  					fn(v)
   279  				}
   280  			}
   281  		}
   282  	}
   283  }