github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/executors/kubernetes/executor_kubernetes_test.go (about) 1 package kubernetes 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/Sirupsen/logrus" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 21 "k8s.io/kubernetes/pkg/api" 22 "k8s.io/kubernetes/pkg/api/resource" 23 "k8s.io/kubernetes/pkg/api/testapi" 24 "k8s.io/kubernetes/pkg/api/unversioned" 25 "k8s.io/kubernetes/pkg/client/restclient" 26 client "k8s.io/kubernetes/pkg/client/unversioned" 27 "k8s.io/kubernetes/pkg/client/unversioned/fake" 28 29 "gitlab.com/gitlab-org/gitlab-runner/common" 30 "gitlab.com/gitlab-org/gitlab-runner/executors" 31 "gitlab.com/gitlab-org/gitlab-runner/helpers" 32 ) 33 34 var ( 35 TRUE = true 36 ) 37 38 func TestLimits(t *testing.T) { 39 tests := []struct { 40 CPU, Memory string 41 Expected api.ResourceList 42 }{ 43 { 44 CPU: "100m", 45 Memory: "100Mi", 46 Expected: api.ResourceList{ 47 api.ResourceCPU: resource.MustParse("100m"), 48 api.ResourceMemory: resource.MustParse("100Mi"), 49 }, 50 }, 51 { 52 CPU: "100m", 53 Expected: api.ResourceList{ 54 api.ResourceCPU: resource.MustParse("100m"), 55 }, 56 }, 57 { 58 Memory: "100Mi", 59 Expected: api.ResourceList{ 60 api.ResourceMemory: resource.MustParse("100Mi"), 61 }, 62 }, 63 { 64 CPU: "100j", 65 Expected: api.ResourceList{}, 66 }, 67 { 68 Memory: "100j", 69 Expected: api.ResourceList{}, 70 }, 71 { 72 Expected: api.ResourceList{}, 73 }, 74 } 75 76 for _, test := range tests { 77 res, _ := limits(test.CPU, test.Memory) 78 assert.Equal(t, test.Expected, res) 79 } 80 } 81 82 func TestVolumeMounts(t *testing.T) { 83 tests := []struct { 84 GlobalConfig *common.Config 85 RunnerConfig common.RunnerConfig 86 Build *common.Build 87 88 Expected []api.VolumeMount 89 }{ 90 { 91 GlobalConfig: &common.Config{}, 92 RunnerConfig: common.RunnerConfig{ 93 RunnerSettings: common.RunnerSettings{ 94 Kubernetes: &common.KubernetesConfig{}, 95 }, 96 }, 97 Build: &common.Build{ 98 Runner: &common.RunnerConfig{}, 99 }, 100 Expected: []api.VolumeMount{ 101 {Name: "repo"}, 102 }, 103 }, 104 { 105 GlobalConfig: &common.Config{}, 106 RunnerConfig: common.RunnerConfig{ 107 RunnerSettings: common.RunnerSettings{ 108 Kubernetes: &common.KubernetesConfig{ 109 Volumes: common.KubernetesVolumes{ 110 HostPaths: []common.KubernetesHostPath{ 111 {Name: "docker", MountPath: "/var/run/docker.sock", HostPath: "/var/run/docker.sock"}, 112 }, 113 PVCs: []common.KubernetesPVC{ 114 {Name: "PVC", MountPath: "/path/to/whatever"}, 115 }, 116 EmptyDirs: []common.KubernetesEmptyDir{ 117 {Name: "emptyDir", MountPath: "/path/to/empty/dir"}, 118 }, 119 }, 120 }, 121 }, 122 }, 123 Build: &common.Build{ 124 Runner: &common.RunnerConfig{}, 125 }, 126 Expected: []api.VolumeMount{ 127 {Name: "repo"}, 128 {Name: "docker", MountPath: "/var/run/docker.sock"}, 129 {Name: "PVC", MountPath: "/path/to/whatever"}, 130 {Name: "emptyDir", MountPath: "/path/to/empty/dir"}, 131 }, 132 }, 133 { 134 GlobalConfig: &common.Config{}, 135 RunnerConfig: common.RunnerConfig{ 136 RunnerSettings: common.RunnerSettings{ 137 Kubernetes: &common.KubernetesConfig{ 138 Volumes: common.KubernetesVolumes{ 139 HostPaths: []common.KubernetesHostPath{ 140 {Name: "test", MountPath: "/opt/test/readonly", ReadOnly: true, HostPath: "/opt/test/rw"}, 141 {Name: "docker", MountPath: "/var/run/docker.sock"}, 142 }, 143 ConfigMaps: []common.KubernetesConfigMap{ 144 {Name: "configMap", MountPath: "/path/to/configmap", ReadOnly: true}, 145 }, 146 Secrets: []common.KubernetesSecret{ 147 {Name: "secret", MountPath: "/path/to/secret", ReadOnly: true}, 148 }, 149 }, 150 }, 151 }, 152 }, 153 Build: &common.Build{ 154 Runner: &common.RunnerConfig{}, 155 }, 156 Expected: []api.VolumeMount{ 157 {Name: "repo"}, 158 {Name: "test", MountPath: "/opt/test/readonly", ReadOnly: true}, 159 {Name: "docker", MountPath: "/var/run/docker.sock"}, 160 {Name: "secret", MountPath: "/path/to/secret", ReadOnly: true}, 161 {Name: "configMap", MountPath: "/path/to/configmap", ReadOnly: true}, 162 }, 163 }, 164 } 165 166 for _, test := range tests { 167 e := &executor{ 168 AbstractExecutor: executors.AbstractExecutor{ 169 ExecutorOptions: executorOptions, 170 Build: test.Build, 171 Config: test.RunnerConfig, 172 }, 173 } 174 175 mounts := e.getVolumeMounts() 176 for _, expected := range test.Expected { 177 assert.Contains(t, mounts, expected, "Expected volumeMount definition for %s was not found", expected.Name) 178 } 179 } 180 } 181 182 func TestVolumes(t *testing.T) { 183 tests := []struct { 184 GlobalConfig *common.Config 185 RunnerConfig common.RunnerConfig 186 Build *common.Build 187 188 Expected []api.Volume 189 }{ 190 { 191 GlobalConfig: &common.Config{}, 192 RunnerConfig: common.RunnerConfig{ 193 RunnerSettings: common.RunnerSettings{ 194 Kubernetes: &common.KubernetesConfig{}, 195 }, 196 }, 197 Build: &common.Build{ 198 Runner: &common.RunnerConfig{}, 199 }, 200 Expected: []api.Volume{ 201 {Name: "repo", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, 202 }, 203 }, 204 { 205 GlobalConfig: &common.Config{}, 206 RunnerConfig: common.RunnerConfig{ 207 RunnerSettings: common.RunnerSettings{ 208 Kubernetes: &common.KubernetesConfig{ 209 Volumes: common.KubernetesVolumes{ 210 HostPaths: []common.KubernetesHostPath{ 211 {Name: "docker", MountPath: "/var/run/docker.sock"}, 212 {Name: "host-path", MountPath: "/path/two", HostPath: "/path/one"}, 213 }, 214 PVCs: []common.KubernetesPVC{ 215 {Name: "PVC", MountPath: "/path/to/whatever"}, 216 }, 217 ConfigMaps: []common.KubernetesConfigMap{ 218 {Name: "ConfigMap", MountPath: "/path/to/config", Items: map[string]string{"key_1": "/path/to/key_1"}}, 219 }, 220 Secrets: []common.KubernetesSecret{ 221 {Name: "secret", MountPath: "/path/to/secret", ReadOnly: true, Items: map[string]string{"secret_1": "/path/to/secret_1"}}, 222 }, 223 EmptyDirs: []common.KubernetesEmptyDir{ 224 {Name: "emptyDir", MountPath: "/path/to/empty/dir", Medium: "Memory"}, 225 }, 226 }, 227 }, 228 }, 229 }, 230 Build: &common.Build{ 231 Runner: &common.RunnerConfig{}, 232 }, 233 Expected: []api.Volume{ 234 {Name: "repo", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, 235 {Name: "docker", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/var/run/docker.sock"}}}, 236 {Name: "host-path", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/path/one"}}}, 237 {Name: "PVC", VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "PVC"}}}, 238 {Name: "emptyDir", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: "Memory"}}}, 239 { 240 Name: "ConfigMap", 241 VolumeSource: api.VolumeSource{ 242 ConfigMap: &api.ConfigMapVolumeSource{ 243 LocalObjectReference: api.LocalObjectReference{Name: "ConfigMap"}, 244 Items: []api.KeyToPath{{Key: "key_1", Path: "/path/to/key_1"}}, 245 }, 246 }, 247 }, 248 { 249 Name: "secret", 250 VolumeSource: api.VolumeSource{ 251 Secret: &api.SecretVolumeSource{ 252 SecretName: "secret", 253 Items: []api.KeyToPath{{Key: "secret_1", Path: "/path/to/secret_1"}}, 254 }, 255 }, 256 }, 257 }, 258 }, 259 } 260 261 for _, test := range tests { 262 e := &executor{ 263 AbstractExecutor: executors.AbstractExecutor{ 264 ExecutorOptions: executorOptions, 265 Build: test.Build, 266 Config: test.RunnerConfig, 267 }, 268 } 269 270 volumes := e.getVolumes() 271 for _, expected := range test.Expected { 272 assert.Contains(t, volumes, expected, "Expected volume definition for %s was not found", expected.Name) 273 } 274 } 275 } 276 277 func fakeKubeDeleteResponse(status int) *http.Response { 278 codec := testapi.Default.Codec() 279 280 body := objBody(codec, &unversioned.Status{Code: int32(status)}) 281 return &http.Response{StatusCode: status, Body: body, Header: map[string][]string{ 282 "Content-Type": []string{"application/json"}, 283 }} 284 } 285 286 func TestCleanup(t *testing.T) { 287 version := testapi.Default.GroupVersion().Version 288 codec := testapi.Default.Codec() 289 290 objectMeta := api.ObjectMeta{Name: "test-resource", Namespace: "test-ns"} 291 292 tests := []struct { 293 Name string 294 Pod *api.Pod 295 Credentials *api.Secret 296 ClientFunc func(*http.Request) (*http.Response, error) 297 Error bool 298 }{ 299 { 300 Name: "Proper Cleanup", 301 Pod: &api.Pod{ObjectMeta: objectMeta}, 302 ClientFunc: func(req *http.Request) (*http.Response, error) { 303 switch p, m := req.URL.Path, req.Method; { 304 case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource": 305 return fakeKubeDeleteResponse(http.StatusOK), nil 306 default: 307 return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p) 308 } 309 }, 310 }, 311 { 312 Name: "Delete failure", 313 Pod: &api.Pod{ObjectMeta: objectMeta}, 314 ClientFunc: func(req *http.Request) (*http.Response, error) { 315 return nil, fmt.Errorf("delete failed") 316 }, 317 Error: true, 318 }, 319 { 320 Name: "POD already deleted", 321 Pod: &api.Pod{ObjectMeta: objectMeta}, 322 ClientFunc: func(req *http.Request) (*http.Response, error) { 323 switch p, m := req.URL.Path, req.Method; { 324 case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource": 325 return fakeKubeDeleteResponse(http.StatusNotFound), nil 326 default: 327 return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p) 328 } 329 }, 330 Error: true, 331 }, 332 { 333 Name: "POD creation failed, Secretes provided", 334 Pod: nil, // a failed POD create request will cause a nil Pod 335 Credentials: &api.Secret{ObjectMeta: objectMeta}, 336 ClientFunc: func(req *http.Request) (*http.Response, error) { 337 switch p, m := req.URL.Path, req.Method; { 338 case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/secrets/test-resource": 339 return fakeKubeDeleteResponse(http.StatusNotFound), nil 340 default: 341 return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p) 342 } 343 }, 344 Error: true, 345 }, 346 } 347 348 for _, test := range tests { 349 t.Run(test.Name, func(t *testing.T) { 350 c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}}) 351 fakeClient := fake.RESTClient{ 352 Codec: codec, 353 Client: fake.CreateHTTPClient(test.ClientFunc), 354 } 355 c.Client = fakeClient.Client 356 357 ex := executor{ 358 kubeClient: c, 359 pod: test.Pod, 360 credentials: test.Credentials, 361 } 362 ex.configurationOverwrites = &overwrites{namespace: "test-ns"} 363 errored := false 364 buildTrace := FakeBuildTrace{ 365 testWriter{ 366 call: func(b []byte) (int, error) { 367 if !errored { 368 if s := string(b); strings.Contains(s, "Error cleaning up") { 369 errored = true 370 } else if test.Error { 371 t.Errorf("expected failure. got: '%s'", string(b)) 372 } 373 } 374 return len(b), nil 375 }, 376 }, 377 } 378 ex.AbstractExecutor.Trace = buildTrace 379 ex.AbstractExecutor.BuildLogger = common.NewBuildLogger(buildTrace, logrus.WithFields(logrus.Fields{})) 380 381 ex.Cleanup() 382 383 if test.Error && !errored { 384 t.Errorf("expected cleanup to fail but it didn't") 385 } else if !test.Error && errored { 386 t.Errorf("expected cleanup not to fail but it did") 387 } 388 }) 389 } 390 } 391 392 func TestPrepare(t *testing.T) { 393 tests := []struct { 394 GlobalConfig *common.Config 395 RunnerConfig *common.RunnerConfig 396 Build *common.Build 397 398 Expected *executor 399 Error bool 400 }{ 401 { 402 GlobalConfig: &common.Config{}, 403 RunnerConfig: &common.RunnerConfig{ 404 RunnerSettings: common.RunnerSettings{ 405 Kubernetes: &common.KubernetesConfig{ 406 Host: "test-server", 407 ServiceCPULimit: "100m", 408 ServiceMemoryLimit: "200Mi", 409 CPULimit: "1.5", 410 MemoryLimit: "4Gi", 411 HelperCPULimit: "50m", 412 HelperMemoryLimit: "100Mi", 413 Privileged: true, 414 PullPolicy: "if-not-present", 415 }, 416 }, 417 }, 418 Build: &common.Build{ 419 JobResponse: common.JobResponse{ 420 GitInfo: common.GitInfo{ 421 Sha: "1234567890", 422 }, 423 Image: common.Image{ 424 Name: "test-image", 425 }, 426 Variables: []common.JobVariable{ 427 {Key: "privileged", Value: "true"}, 428 }, 429 }, 430 Runner: &common.RunnerConfig{}, 431 }, 432 Expected: &executor{ 433 options: &kubernetesOptions{ 434 Image: common.Image{ 435 Name: "test-image", 436 }, 437 }, 438 configurationOverwrites: &overwrites{namespace: "default"}, 439 serviceLimits: api.ResourceList{ 440 api.ResourceCPU: resource.MustParse("100m"), 441 api.ResourceMemory: resource.MustParse("200Mi"), 442 }, 443 buildLimits: api.ResourceList{ 444 api.ResourceCPU: resource.MustParse("1.5"), 445 api.ResourceMemory: resource.MustParse("4Gi"), 446 }, 447 helperLimits: api.ResourceList{ 448 api.ResourceCPU: resource.MustParse("50m"), 449 api.ResourceMemory: resource.MustParse("100Mi"), 450 }, 451 serviceRequests: api.ResourceList{}, 452 buildRequests: api.ResourceList{}, 453 helperRequests: api.ResourceList{}, 454 pullPolicy: "IfNotPresent", 455 }, 456 }, 457 { 458 GlobalConfig: &common.Config{}, 459 RunnerConfig: &common.RunnerConfig{ 460 RunnerSettings: common.RunnerSettings{ 461 Kubernetes: &common.KubernetesConfig{ 462 Host: "test-server", 463 ServiceAccount: "default", 464 ServiceAccountOverwriteAllowed: ".*", 465 BearerTokenOverwriteAllowed: true, 466 ServiceCPULimit: "100m", 467 ServiceMemoryLimit: "200Mi", 468 CPULimit: "1.5", 469 MemoryLimit: "4Gi", 470 HelperCPULimit: "50m", 471 HelperMemoryLimit: "100Mi", 472 ServiceCPURequest: "99m", 473 ServiceMemoryRequest: "5Mi", 474 CPURequest: "1", 475 MemoryRequest: "1.5Gi", 476 HelperCPURequest: "0.5m", 477 HelperMemoryRequest: "42Mi", 478 Privileged: false, 479 }, 480 }, 481 }, 482 Build: &common.Build{ 483 JobResponse: common.JobResponse{ 484 GitInfo: common.GitInfo{ 485 Sha: "1234567890", 486 }, 487 Image: common.Image{ 488 Name: "test-image", 489 }, 490 Variables: []common.JobVariable{ 491 {Key: ServiceAccountOverwriteVariableName, Value: "not-default"}, 492 }, 493 }, 494 Runner: &common.RunnerConfig{}, 495 }, 496 Expected: &executor{ 497 options: &kubernetesOptions{ 498 Image: common.Image{ 499 Name: "test-image", 500 }, 501 }, 502 configurationOverwrites: &overwrites{namespace: "default", serviceAccount: "not-default"}, 503 serviceLimits: api.ResourceList{ 504 api.ResourceCPU: resource.MustParse("100m"), 505 api.ResourceMemory: resource.MustParse("200Mi"), 506 }, 507 buildLimits: api.ResourceList{ 508 api.ResourceCPU: resource.MustParse("1.5"), 509 api.ResourceMemory: resource.MustParse("4Gi"), 510 }, 511 helperLimits: api.ResourceList{ 512 api.ResourceCPU: resource.MustParse("50m"), 513 api.ResourceMemory: resource.MustParse("100Mi"), 514 }, 515 serviceRequests: api.ResourceList{ 516 api.ResourceCPU: resource.MustParse("99m"), 517 api.ResourceMemory: resource.MustParse("5Mi"), 518 }, 519 buildRequests: api.ResourceList{ 520 api.ResourceCPU: resource.MustParse("1"), 521 api.ResourceMemory: resource.MustParse("1.5Gi"), 522 }, 523 helperRequests: api.ResourceList{ 524 api.ResourceCPU: resource.MustParse("0.5m"), 525 api.ResourceMemory: resource.MustParse("42Mi"), 526 }, 527 }, 528 Error: false, 529 }, 530 531 { 532 GlobalConfig: &common.Config{}, 533 RunnerConfig: &common.RunnerConfig{ 534 RunnerSettings: common.RunnerSettings{ 535 Kubernetes: &common.KubernetesConfig{ 536 Host: "test-server", 537 ServiceAccount: "default", 538 ServiceAccountOverwriteAllowed: "allowed-.*", 539 ServiceCPULimit: "100m", 540 ServiceMemoryLimit: "200Mi", 541 CPULimit: "1.5", 542 MemoryLimit: "4Gi", 543 HelperCPULimit: "50m", 544 HelperMemoryLimit: "100Mi", 545 ServiceCPURequest: "99m", 546 ServiceMemoryRequest: "5Mi", 547 CPURequest: "1", 548 MemoryRequest: "1.5Gi", 549 HelperCPURequest: "0.5m", 550 HelperMemoryRequest: "42Mi", 551 Privileged: false, 552 }, 553 }, 554 }, 555 Build: &common.Build{ 556 JobResponse: common.JobResponse{ 557 GitInfo: common.GitInfo{ 558 Sha: "1234567890", 559 }, 560 Image: common.Image{ 561 Name: "test-image", 562 }, 563 Variables: []common.JobVariable{ 564 {Key: ServiceAccountOverwriteVariableName, Value: "not-default"}, 565 }, 566 }, 567 Runner: &common.RunnerConfig{}, 568 }, 569 Expected: &executor{ 570 options: &kubernetesOptions{ 571 Image: common.Image{ 572 Name: "test-image", 573 }, 574 }, 575 configurationOverwrites: &overwrites{namespace: "namespacee"}, 576 serviceLimits: api.ResourceList{ 577 api.ResourceCPU: resource.MustParse("100m"), 578 api.ResourceMemory: resource.MustParse("200Mi"), 579 }, 580 buildLimits: api.ResourceList{ 581 api.ResourceCPU: resource.MustParse("1.5"), 582 api.ResourceMemory: resource.MustParse("4Gi"), 583 }, 584 helperLimits: api.ResourceList{ 585 api.ResourceCPU: resource.MustParse("50m"), 586 api.ResourceMemory: resource.MustParse("100Mi"), 587 }, 588 serviceRequests: api.ResourceList{ 589 api.ResourceCPU: resource.MustParse("99m"), 590 api.ResourceMemory: resource.MustParse("5Mi"), 591 }, 592 buildRequests: api.ResourceList{ 593 api.ResourceCPU: resource.MustParse("1"), 594 api.ResourceMemory: resource.MustParse("1.5Gi"), 595 }, 596 helperRequests: api.ResourceList{ 597 api.ResourceCPU: resource.MustParse("0.5m"), 598 api.ResourceMemory: resource.MustParse("42Mi"), 599 }, 600 }, 601 Error: true, 602 }, 603 { 604 GlobalConfig: &common.Config{}, 605 RunnerConfig: &common.RunnerConfig{ 606 RunnerSettings: common.RunnerSettings{ 607 Kubernetes: &common.KubernetesConfig{ 608 Host: "test-server", 609 Namespace: "namespace", 610 ServiceAccount: "a_service_account", 611 ServiceAccountOverwriteAllowed: ".*", 612 NamespaceOverwriteAllowed: "^n.*?e$", 613 ServiceCPULimit: "100m", 614 ServiceMemoryLimit: "200Mi", 615 CPULimit: "1.5", 616 MemoryLimit: "4Gi", 617 HelperCPULimit: "50m", 618 HelperMemoryLimit: "100Mi", 619 ServiceCPURequest: "99m", 620 ServiceMemoryRequest: "5Mi", 621 CPURequest: "1", 622 MemoryRequest: "1.5Gi", 623 HelperCPURequest: "0.5m", 624 HelperMemoryRequest: "42Mi", 625 Privileged: false, 626 }, 627 }, 628 }, 629 Build: &common.Build{ 630 JobResponse: common.JobResponse{ 631 GitInfo: common.GitInfo{ 632 Sha: "1234567890", 633 }, 634 Image: common.Image{ 635 Name: "test-image", 636 }, 637 Variables: []common.JobVariable{ 638 {Key: NamespaceOverwriteVariableName, Value: "namespacee"}, 639 }, 640 }, 641 Runner: &common.RunnerConfig{}, 642 }, 643 Expected: &executor{ 644 options: &kubernetesOptions{ 645 Image: common.Image{ 646 Name: "test-image", 647 }, 648 }, 649 configurationOverwrites: &overwrites{namespace: "namespacee", serviceAccount: "a_service_account"}, 650 serviceLimits: api.ResourceList{ 651 api.ResourceCPU: resource.MustParse("100m"), 652 api.ResourceMemory: resource.MustParse("200Mi"), 653 }, 654 buildLimits: api.ResourceList{ 655 api.ResourceCPU: resource.MustParse("1.5"), 656 api.ResourceMemory: resource.MustParse("4Gi"), 657 }, 658 helperLimits: api.ResourceList{ 659 api.ResourceCPU: resource.MustParse("50m"), 660 api.ResourceMemory: resource.MustParse("100Mi"), 661 }, 662 serviceRequests: api.ResourceList{ 663 api.ResourceCPU: resource.MustParse("99m"), 664 api.ResourceMemory: resource.MustParse("5Mi"), 665 }, 666 buildRequests: api.ResourceList{ 667 api.ResourceCPU: resource.MustParse("1"), 668 api.ResourceMemory: resource.MustParse("1.5Gi"), 669 }, 670 helperRequests: api.ResourceList{ 671 api.ResourceCPU: resource.MustParse("0.5m"), 672 api.ResourceMemory: resource.MustParse("42Mi"), 673 }, 674 }, 675 Error: true, 676 }, 677 { 678 GlobalConfig: &common.Config{}, 679 RunnerConfig: &common.RunnerConfig{ 680 RunnerSettings: common.RunnerSettings{ 681 Kubernetes: &common.KubernetesConfig{ 682 Namespace: "namespace", 683 Host: "test-server", 684 }, 685 }, 686 }, 687 Build: &common.Build{ 688 JobResponse: common.JobResponse{ 689 GitInfo: common.GitInfo{ 690 Sha: "1234567890", 691 }, 692 Image: common.Image{ 693 Name: "test-image", 694 }, 695 Variables: []common.JobVariable{ 696 {Key: NamespaceOverwriteVariableName, Value: "namespace"}, 697 }, 698 }, 699 Runner: &common.RunnerConfig{}, 700 }, 701 Expected: &executor{ 702 options: &kubernetesOptions{ 703 Image: common.Image{ 704 Name: "test-image", 705 }, 706 }, 707 configurationOverwrites: &overwrites{namespace: "namespace"}, 708 serviceLimits: api.ResourceList{}, 709 buildLimits: api.ResourceList{}, 710 helperLimits: api.ResourceList{}, 711 serviceRequests: api.ResourceList{}, 712 buildRequests: api.ResourceList{}, 713 helperRequests: api.ResourceList{}, 714 }, 715 }, 716 { 717 GlobalConfig: &common.Config{}, 718 RunnerConfig: &common.RunnerConfig{ 719 RunnerSettings: common.RunnerSettings{ 720 Kubernetes: &common.KubernetesConfig{ 721 Image: "test-image", 722 Host: "test-server", 723 }, 724 }, 725 }, 726 Build: &common.Build{ 727 JobResponse: common.JobResponse{ 728 GitInfo: common.GitInfo{ 729 Sha: "1234567890", 730 }, 731 }, 732 Runner: &common.RunnerConfig{}, 733 }, 734 Expected: &executor{ 735 options: &kubernetesOptions{ 736 Image: common.Image{ 737 Name: "test-image", 738 }, 739 }, 740 configurationOverwrites: &overwrites{namespace: "default"}, 741 serviceLimits: api.ResourceList{}, 742 buildLimits: api.ResourceList{}, 743 helperLimits: api.ResourceList{}, 744 serviceRequests: api.ResourceList{}, 745 buildRequests: api.ResourceList{}, 746 helperRequests: api.ResourceList{}, 747 }, 748 }, 749 { 750 GlobalConfig: &common.Config{}, 751 RunnerConfig: &common.RunnerConfig{ 752 RunnerSettings: common.RunnerSettings{ 753 Kubernetes: &common.KubernetesConfig{ 754 Host: "test-server", 755 }, 756 }, 757 }, 758 Build: &common.Build{ 759 JobResponse: common.JobResponse{ 760 GitInfo: common.GitInfo{ 761 Sha: "1234567890", 762 }, 763 Image: common.Image{ 764 Name: "test-image", 765 Entrypoint: []string{"/init", "run"}, 766 }, 767 Services: common.Services{ 768 { 769 Name: "test-service", 770 Entrypoint: []string{"/init", "run"}, 771 Command: []string{"application", "--debug"}, 772 }, 773 }, 774 }, 775 Runner: &common.RunnerConfig{}, 776 }, 777 Expected: &executor{ 778 options: &kubernetesOptions{ 779 Image: common.Image{ 780 Name: "test-image", 781 Entrypoint: []string{"/init", "run"}, 782 }, 783 Services: common.Services{ 784 { 785 Name: "test-service", 786 Entrypoint: []string{"/init", "run"}, 787 Command: []string{"application", "--debug"}, 788 }, 789 }, 790 }, 791 configurationOverwrites: &overwrites{namespace: "default"}, 792 serviceLimits: api.ResourceList{}, 793 buildLimits: api.ResourceList{}, 794 helperLimits: api.ResourceList{}, 795 serviceRequests: api.ResourceList{}, 796 buildRequests: api.ResourceList{}, 797 helperRequests: api.ResourceList{}, 798 }, 799 }, 800 } 801 802 for index, test := range tests { 803 t.Run(strconv.Itoa(index), func(t *testing.T) { 804 e := &executor{ 805 AbstractExecutor: executors.AbstractExecutor{ 806 ExecutorOptions: executorOptions, 807 }, 808 } 809 810 prepareOptions := common.ExecutorPrepareOptions{ 811 Config: test.RunnerConfig, 812 Build: test.Build, 813 Context: context.TODO(), 814 } 815 816 err := e.Prepare(prepareOptions) 817 818 if err != nil { 819 assert.False(t, test.Build.IsSharedEnv()) 820 if test.Error { 821 assert.Error(t, err) 822 } else { 823 assert.NoError(t, err) 824 } 825 if !test.Error { 826 t.Errorf("Got error. Expected: %v", test.Expected) 827 } 828 return 829 } 830 831 // Set this to nil so we aren't testing the functionality of the 832 // base AbstractExecutor's Prepare method 833 e.AbstractExecutor = executors.AbstractExecutor{} 834 835 // TODO: Improve this so we don't have to nil-ify the kubeClient. 836 // It currently contains some moving parts that are failing, meaning 837 // we'll need to mock _something_ 838 e.kubeClient = nil 839 assert.Equal(t, test.Expected, e) 840 }) 841 } 842 } 843 844 // This test reproduces the bug reported in https://gitlab.com/gitlab-org/gitlab-runner/issues/2583 845 func TestPrepareIssue2583(t *testing.T) { 846 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 847 return 848 } 849 850 namespace := "my_namespace" 851 serviceAccount := "my_account" 852 853 runnerConfig := &common.RunnerConfig{ 854 RunnerSettings: common.RunnerSettings{ 855 Executor: "kubernetes", 856 Kubernetes: &common.KubernetesConfig{ 857 Image: "an/image:latest", 858 Namespace: namespace, 859 NamespaceOverwriteAllowed: ".*", 860 ServiceAccount: serviceAccount, 861 ServiceAccountOverwriteAllowed: ".*", 862 }, 863 }, 864 } 865 866 build := &common.Build{ 867 JobResponse: common.JobResponse{ 868 Variables: []common.JobVariable{ 869 {Key: NamespaceOverwriteVariableName, Value: "namespace"}, 870 {Key: ServiceAccountOverwriteVariableName, Value: "sa"}, 871 }, 872 }, 873 Runner: &common.RunnerConfig{}, 874 } 875 876 e := &executor{ 877 AbstractExecutor: executors.AbstractExecutor{ 878 ExecutorOptions: executorOptions, 879 }, 880 } 881 882 prepareOptions := common.ExecutorPrepareOptions{ 883 Config: runnerConfig, 884 Build: build, 885 Context: context.TODO(), 886 } 887 888 err := e.Prepare(prepareOptions) 889 assert.NoError(t, err) 890 assert.Equal(t, namespace, runnerConfig.Kubernetes.Namespace) 891 assert.Equal(t, serviceAccount, runnerConfig.Kubernetes.ServiceAccount) 892 } 893 894 func TestSetupCredentials(t *testing.T) { 895 version := testapi.Default.GroupVersion().Version 896 codec := testapi.Default.Codec() 897 898 type testDef struct { 899 Credentials []common.Credentials 900 VerifyFn func(*testing.T, testDef, *api.Secret) 901 } 902 tests := []testDef{ 903 { 904 // don't execute VerifyFn 905 VerifyFn: nil, 906 }, 907 { 908 Credentials: []common.Credentials{ 909 { 910 Type: "registry", 911 URL: "http://example.com", 912 Username: "user", 913 Password: "password", 914 }, 915 }, 916 VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) { 917 assert.Equal(t, api.SecretTypeDockercfg, secret.Type) 918 assert.NotEmpty(t, secret.Data[api.DockerConfigKey]) 919 }, 920 }, 921 { 922 Credentials: []common.Credentials{ 923 { 924 Type: "other", 925 URL: "http://example.com", 926 Username: "user", 927 Password: "password", 928 }, 929 }, 930 // don't execute VerifyFn 931 VerifyFn: nil, 932 }, 933 } 934 935 executed := false 936 fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) { 937 return func(req *http.Request) (resp *http.Response, err error) { 938 podBytes, err := ioutil.ReadAll(req.Body) 939 executed = true 940 941 if err != nil { 942 t.Errorf("failed to read request body: %s", err.Error()) 943 return 944 } 945 946 p := new(api.Secret) 947 948 err = json.Unmarshal(podBytes, p) 949 950 if err != nil { 951 t.Errorf("error decoding pod: %s", err.Error()) 952 return 953 } 954 955 if test.VerifyFn != nil { 956 test.VerifyFn(t, test, p) 957 } 958 959 resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{ 960 Reader: bytes.NewBuffer(podBytes), 961 }} 962 resp.Header = make(http.Header) 963 resp.Header.Add("Content-Type", "application/json") 964 965 return 966 } 967 } 968 969 for _, test := range tests { 970 c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}}) 971 fakeClient := fake.RESTClient{ 972 Codec: codec, 973 Client: fake.CreateHTTPClient(fakeClientRoundTripper(test)), 974 } 975 c.Client = fakeClient.Client 976 977 ex := executor{ 978 kubeClient: c, 979 options: &kubernetesOptions{}, 980 AbstractExecutor: executors.AbstractExecutor{ 981 Config: common.RunnerConfig{ 982 RunnerSettings: common.RunnerSettings{ 983 Kubernetes: &common.KubernetesConfig{ 984 Namespace: "default", 985 }, 986 }, 987 }, 988 BuildShell: &common.ShellConfiguration{}, 989 Build: &common.Build{ 990 JobResponse: common.JobResponse{ 991 Variables: []common.JobVariable{}, 992 Credentials: test.Credentials, 993 }, 994 Runner: &common.RunnerConfig{}, 995 }, 996 }, 997 } 998 999 executed = false 1000 err := ex.prepareOverwrites(make(common.JobVariables, 0)) 1001 assert.NoError(t, err) 1002 err = ex.setupCredentials() 1003 assert.NoError(t, err) 1004 if test.VerifyFn != nil { 1005 assert.True(t, executed) 1006 } else { 1007 assert.False(t, executed) 1008 } 1009 } 1010 } 1011 1012 func TestSetupBuildPod(t *testing.T) { 1013 version := testapi.Default.GroupVersion().Version 1014 codec := testapi.Default.Codec() 1015 1016 type testDef struct { 1017 RunnerConfig common.RunnerConfig 1018 Options *kubernetesOptions 1019 PrepareFn func(*testing.T, testDef, *executor) 1020 VerifyFn func(*testing.T, testDef, *api.Pod) 1021 Variables []common.JobVariable 1022 } 1023 tests := []testDef{ 1024 { 1025 RunnerConfig: common.RunnerConfig{ 1026 RunnerSettings: common.RunnerSettings{ 1027 Kubernetes: &common.KubernetesConfig{ 1028 Namespace: "default", 1029 NodeSelector: map[string]string{ 1030 "a-selector": "first", 1031 "another-selector": "second", 1032 }, 1033 }, 1034 }, 1035 }, 1036 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1037 assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.NodeSelector, pod.Spec.NodeSelector) 1038 }, 1039 }, 1040 { 1041 RunnerConfig: common.RunnerConfig{ 1042 RunnerSettings: common.RunnerSettings{ 1043 Kubernetes: &common.KubernetesConfig{ 1044 Namespace: "default", 1045 }, 1046 }, 1047 }, 1048 PrepareFn: func(t *testing.T, test testDef, e *executor) { 1049 e.credentials = &api.Secret{ 1050 ObjectMeta: api.ObjectMeta{ 1051 Name: "job-credentials", 1052 }, 1053 } 1054 }, 1055 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1056 secrets := []api.LocalObjectReference{{Name: "job-credentials"}} 1057 assert.Equal(t, secrets, pod.Spec.ImagePullSecrets) 1058 }, 1059 }, 1060 { 1061 RunnerConfig: common.RunnerConfig{ 1062 RunnerSettings: common.RunnerSettings{ 1063 Kubernetes: &common.KubernetesConfig{ 1064 Namespace: "default", 1065 ImagePullSecrets: []string{ 1066 "docker-registry-credentials", 1067 }, 1068 }, 1069 }, 1070 }, 1071 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1072 secrets := []api.LocalObjectReference{{Name: "docker-registry-credentials"}} 1073 assert.Equal(t, secrets, pod.Spec.ImagePullSecrets) 1074 }, 1075 }, 1076 { 1077 RunnerConfig: common.RunnerConfig{ 1078 RunnerSettings: common.RunnerSettings{ 1079 Kubernetes: &common.KubernetesConfig{ 1080 Namespace: "default", 1081 }, 1082 }, 1083 }, 1084 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1085 hasHelper := false 1086 for _, c := range pod.Spec.Containers { 1087 if c.Name == "helper" { 1088 hasHelper = true 1089 } 1090 } 1091 assert.True(t, hasHelper) 1092 }, 1093 }, 1094 { 1095 RunnerConfig: common.RunnerConfig{ 1096 RunnerSettings: common.RunnerSettings{ 1097 Kubernetes: &common.KubernetesConfig{ 1098 Namespace: "default", 1099 HelperImage: "custom/helper-image", 1100 }, 1101 }, 1102 }, 1103 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1104 for _, c := range pod.Spec.Containers { 1105 if c.Name == "helper" { 1106 assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.HelperImage, c.Image) 1107 } 1108 } 1109 }, 1110 }, 1111 { 1112 RunnerConfig: common.RunnerConfig{ 1113 RunnerSettings: common.RunnerSettings{ 1114 Kubernetes: &common.KubernetesConfig{ 1115 Namespace: "default", 1116 PodLabels: map[string]string{ 1117 "test": "label", 1118 "another": "label", 1119 "var": "$test", 1120 }, 1121 }, 1122 }, 1123 }, 1124 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1125 assert.Equal(t, map[string]string{ 1126 "test": "label", 1127 "another": "label", 1128 "var": "sometestvar", 1129 }, pod.ObjectMeta.Labels) 1130 }, 1131 Variables: []common.JobVariable{ 1132 {Key: "test", Value: "sometestvar"}, 1133 }, 1134 }, 1135 { 1136 RunnerConfig: common.RunnerConfig{ 1137 RunnerSettings: common.RunnerSettings{ 1138 Kubernetes: &common.KubernetesConfig{ 1139 Namespace: "default", 1140 PodAnnotations: map[string]string{ 1141 "test": "annotation", 1142 "another": "annotation", 1143 "var": "$test", 1144 }, 1145 }, 1146 }, 1147 }, 1148 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1149 assert.Equal(t, map[string]string{ 1150 "test": "annotation", 1151 "another": "annotation", 1152 "var": "sometestvar", 1153 }, pod.ObjectMeta.Annotations) 1154 }, 1155 Variables: []common.JobVariable{ 1156 {Key: "test", Value: "sometestvar"}, 1157 }, 1158 }, 1159 { 1160 RunnerConfig: common.RunnerConfig{ 1161 RunnerSettings: common.RunnerSettings{ 1162 Kubernetes: &common.KubernetesConfig{ 1163 Namespace: "default", 1164 HelperImage: "custom/helper-image", 1165 }, 1166 }, 1167 }, 1168 Options: &kubernetesOptions{ 1169 Image: common.Image{ 1170 Name: "test-image", 1171 Entrypoint: []string{"/init", "run"}, 1172 }, 1173 Services: common.Services{ 1174 { 1175 Name: "test-service", 1176 Entrypoint: []string{"/init", "run"}, 1177 Command: []string{"application", "--debug"}, 1178 }, 1179 }, 1180 }, 1181 VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { 1182 require.Len(t, pod.Spec.Containers, 3) 1183 1184 assert.Equal(t, pod.Spec.Containers[0].Name, "build") 1185 assert.Equal(t, pod.Spec.Containers[0].Image, "test-image") 1186 assert.Equal(t, pod.Spec.Containers[0].Command, []string{"/init", "run"}) 1187 assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty") 1188 1189 assert.Equal(t, pod.Spec.Containers[1].Name, "helper") 1190 assert.Equal(t, pod.Spec.Containers[1].Image, "custom/helper-image") 1191 assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty") 1192 assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty") 1193 1194 assert.Equal(t, pod.Spec.Containers[2].Name, "svc-0") 1195 assert.Equal(t, pod.Spec.Containers[2].Image, "test-service") 1196 assert.Equal(t, pod.Spec.Containers[2].Command, []string{"/init", "run"}) 1197 assert.Equal(t, pod.Spec.Containers[2].Args, []string{"application", "--debug"}) 1198 }, 1199 }, 1200 } 1201 1202 executed := false 1203 fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) { 1204 return func(req *http.Request) (resp *http.Response, err error) { 1205 executed = true 1206 podBytes, err := ioutil.ReadAll(req.Body) 1207 1208 if err != nil { 1209 t.Errorf("failed to read request body: %s", err.Error()) 1210 return 1211 } 1212 1213 p := new(api.Pod) 1214 1215 err = json.Unmarshal(podBytes, p) 1216 1217 if err != nil { 1218 t.Errorf("error decoding pod: %s", err.Error()) 1219 return 1220 } 1221 1222 test.VerifyFn(t, test, p) 1223 1224 resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{ 1225 Reader: bytes.NewBuffer(podBytes), 1226 }} 1227 resp.Header = make(http.Header) 1228 resp.Header.Add("Content-Type", "application/json") 1229 1230 return 1231 } 1232 } 1233 1234 for _, test := range tests { 1235 c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}}) 1236 fakeClient := fake.RESTClient{ 1237 Codec: codec, 1238 Client: fake.CreateHTTPClient(fakeClientRoundTripper(test)), 1239 } 1240 c.Client = fakeClient.Client 1241 1242 vars := test.Variables 1243 if vars == nil { 1244 vars = []common.JobVariable{} 1245 } 1246 1247 options := test.Options 1248 if options == nil { 1249 options = &kubernetesOptions{} 1250 } 1251 ex := executor{ 1252 kubeClient: c, 1253 options: options, 1254 AbstractExecutor: executors.AbstractExecutor{ 1255 Config: test.RunnerConfig, 1256 BuildShell: &common.ShellConfiguration{}, 1257 Build: &common.Build{ 1258 JobResponse: common.JobResponse{ 1259 Variables: vars, 1260 }, 1261 Runner: &test.RunnerConfig, 1262 }, 1263 }, 1264 } 1265 1266 if test.PrepareFn != nil { 1267 test.PrepareFn(t, test, &ex) 1268 } 1269 1270 executed = false 1271 err := ex.prepareOverwrites(make(common.JobVariables, 0)) 1272 assert.NoError(t, err, "error preparing overwrites: %s") 1273 err = ex.setupBuildPod() 1274 assert.NoError(t, err, "error setting up build pod: %s") 1275 assert.True(t, executed) 1276 } 1277 } 1278 1279 func TestKubernetesSuccessRun(t *testing.T) { 1280 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1281 return 1282 } 1283 1284 successfulBuild, err := common.GetRemoteSuccessfulBuild() 1285 assert.NoError(t, err) 1286 successfulBuild.Image.Name = "docker:git" 1287 build := &common.Build{ 1288 JobResponse: successfulBuild, 1289 Runner: &common.RunnerConfig{ 1290 RunnerSettings: common.RunnerSettings{ 1291 Executor: "kubernetes", 1292 Kubernetes: &common.KubernetesConfig{}, 1293 }, 1294 }, 1295 } 1296 1297 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1298 assert.NoError(t, err) 1299 } 1300 1301 func TestKubernetesNoRootImage(t *testing.T) { 1302 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1303 return 1304 } 1305 1306 successfulBuild, err := common.GetRemoteSuccessfulBuildWithDumpedVariables() 1307 1308 assert.NoError(t, err) 1309 successfulBuild.Image.Name = "registry.gitlab.com/gitlab-org/gitlab-runner/alpine-no-root" 1310 build := &common.Build{ 1311 JobResponse: successfulBuild, 1312 Runner: &common.RunnerConfig{ 1313 RunnerSettings: common.RunnerSettings{ 1314 Executor: "kubernetes", 1315 Kubernetes: &common.KubernetesConfig{}, 1316 }, 1317 }, 1318 } 1319 1320 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1321 assert.NoError(t, err) 1322 } 1323 1324 func TestKubernetesBuildFail(t *testing.T) { 1325 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1326 return 1327 } 1328 1329 failedBuild, err := common.GetRemoteFailedBuild() 1330 assert.NoError(t, err) 1331 build := &common.Build{ 1332 JobResponse: failedBuild, 1333 Runner: &common.RunnerConfig{ 1334 RunnerSettings: common.RunnerSettings{ 1335 Executor: "kubernetes", 1336 Kubernetes: &common.KubernetesConfig{}, 1337 }, 1338 }, 1339 } 1340 build.Image.Name = "docker:git" 1341 1342 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1343 require.Error(t, err, "error") 1344 assert.IsType(t, err, &common.BuildError{}) 1345 assert.Contains(t, err.Error(), "Error executing in Docker Container: 1") 1346 } 1347 1348 func TestKubernetesMissingImage(t *testing.T) { 1349 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1350 return 1351 } 1352 1353 failedBuild, err := common.GetRemoteFailedBuild() 1354 assert.NoError(t, err) 1355 build := &common.Build{ 1356 JobResponse: failedBuild, 1357 Runner: &common.RunnerConfig{ 1358 RunnerSettings: common.RunnerSettings{ 1359 Executor: "kubernetes", 1360 Kubernetes: &common.KubernetesConfig{}, 1361 }, 1362 }, 1363 } 1364 build.Image.Name = "some/non-existing/image" 1365 1366 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1367 require.Error(t, err) 1368 assert.IsType(t, err, &common.BuildError{}) 1369 assert.Contains(t, err.Error(), "image pull failed") 1370 } 1371 1372 func TestKubernetesMissingTag(t *testing.T) { 1373 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1374 return 1375 } 1376 1377 failedBuild, err := common.GetRemoteFailedBuild() 1378 assert.NoError(t, err) 1379 build := &common.Build{ 1380 JobResponse: failedBuild, 1381 Runner: &common.RunnerConfig{ 1382 RunnerSettings: common.RunnerSettings{ 1383 Executor: "kubernetes", 1384 Kubernetes: &common.KubernetesConfig{}, 1385 }, 1386 }, 1387 } 1388 build.Image.Name = "docker:missing-tag" 1389 1390 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1391 require.Error(t, err) 1392 assert.IsType(t, err, &common.BuildError{}) 1393 assert.Contains(t, err.Error(), "image pull failed") 1394 } 1395 1396 func TestKubernetesBuildAbort(t *testing.T) { 1397 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1398 return 1399 } 1400 1401 failedBuild, err := common.GetRemoteFailedBuild() 1402 assert.NoError(t, err) 1403 build := &common.Build{ 1404 JobResponse: failedBuild, 1405 Runner: &common.RunnerConfig{ 1406 RunnerSettings: common.RunnerSettings{ 1407 Executor: "kubernetes", 1408 Kubernetes: &common.KubernetesConfig{}, 1409 }, 1410 }, 1411 SystemInterrupt: make(chan os.Signal, 1), 1412 } 1413 build.Image.Name = "docker:git" 1414 1415 abortTimer := time.AfterFunc(time.Second, func() { 1416 t.Log("Interrupt") 1417 build.SystemInterrupt <- os.Interrupt 1418 }) 1419 defer abortTimer.Stop() 1420 1421 timeoutTimer := time.AfterFunc(time.Minute, func() { 1422 t.Log("Timedout") 1423 t.FailNow() 1424 }) 1425 defer timeoutTimer.Stop() 1426 1427 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1428 assert.EqualError(t, err, "aborted: interrupt") 1429 } 1430 1431 func TestKubernetesBuildCancel(t *testing.T) { 1432 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1433 return 1434 } 1435 1436 failedBuild, err := common.GetRemoteFailedBuild() 1437 assert.NoError(t, err) 1438 build := &common.Build{ 1439 JobResponse: failedBuild, 1440 Runner: &common.RunnerConfig{ 1441 RunnerSettings: common.RunnerSettings{ 1442 Executor: "kubernetes", 1443 Kubernetes: &common.KubernetesConfig{}, 1444 }, 1445 }, 1446 SystemInterrupt: make(chan os.Signal, 1), 1447 } 1448 build.Image.Name = "docker:git" 1449 1450 trace := &common.Trace{Writer: os.Stdout} 1451 1452 abortTimer := time.AfterFunc(time.Second, func() { 1453 t.Log("Interrupt") 1454 trace.CancelFunc() 1455 }) 1456 defer abortTimer.Stop() 1457 1458 timeoutTimer := time.AfterFunc(time.Minute, func() { 1459 t.Log("Timedout") 1460 t.FailNow() 1461 }) 1462 defer timeoutTimer.Stop() 1463 1464 err = build.Run(&common.Config{}, trace) 1465 assert.IsType(t, err, &common.BuildError{}) 1466 assert.EqualError(t, err, "canceled") 1467 } 1468 1469 func TestOverwriteNamespaceNotMatch(t *testing.T) { 1470 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1471 return 1472 } 1473 1474 build := &common.Build{ 1475 JobResponse: common.JobResponse{ 1476 GitInfo: common.GitInfo{ 1477 Sha: "1234567890", 1478 }, 1479 Image: common.Image{ 1480 Name: "test-image", 1481 }, 1482 Variables: []common.JobVariable{ 1483 {Key: NamespaceOverwriteVariableName, Value: "namespace"}, 1484 }, 1485 }, 1486 Runner: &common.RunnerConfig{ 1487 RunnerSettings: common.RunnerSettings{ 1488 Executor: "kubernetes", 1489 Kubernetes: &common.KubernetesConfig{ 1490 NamespaceOverwriteAllowed: "^not_a_match$", 1491 }, 1492 }, 1493 }, 1494 SystemInterrupt: make(chan os.Signal, 1), 1495 } 1496 build.Image.Name = "docker:git" 1497 1498 err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1499 require.Error(t, err) 1500 assert.Contains(t, err.Error(), "does not match") 1501 } 1502 1503 func TestOverwriteServiceAccountNotMatch(t *testing.T) { 1504 if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") { 1505 return 1506 } 1507 1508 build := &common.Build{ 1509 JobResponse: common.JobResponse{ 1510 GitInfo: common.GitInfo{ 1511 Sha: "1234567890", 1512 }, 1513 Image: common.Image{ 1514 Name: "test-image", 1515 }, 1516 Variables: []common.JobVariable{ 1517 {Key: ServiceAccountOverwriteVariableName, Value: "service-account"}, 1518 }, 1519 }, 1520 Runner: &common.RunnerConfig{ 1521 RunnerSettings: common.RunnerSettings{ 1522 Executor: "kubernetes", 1523 Kubernetes: &common.KubernetesConfig{ 1524 ServiceAccountOverwriteAllowed: "^not_a_match$", 1525 }, 1526 }, 1527 }, 1528 SystemInterrupt: make(chan os.Signal, 1), 1529 } 1530 build.Image.Name = "docker:git" 1531 1532 err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1533 require.Error(t, err) 1534 assert.Contains(t, err.Error(), "does not match") 1535 } 1536 1537 type FakeReadCloser struct { 1538 io.Reader 1539 } 1540 1541 func (f FakeReadCloser) Close() error { 1542 return nil 1543 } 1544 1545 type FakeBuildTrace struct { 1546 testWriter 1547 } 1548 1549 func (f FakeBuildTrace) Success() {} 1550 func (f FakeBuildTrace) Fail(err error, failureReason common.JobFailureReason) {} 1551 func (f FakeBuildTrace) Notify(func()) {} 1552 func (f FakeBuildTrace) SetCancelFunc(cancelFunc context.CancelFunc) {} 1553 func (f FakeBuildTrace) SetFailuresCollector(fc common.FailuresCollector) {} 1554 func (f FakeBuildTrace) IsStdout() bool { 1555 return false 1556 }