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 }