github.com/docker/compose-on-kubernetes@v0.5.0/internal/e2e/cluster/types.go (about) 1 package cluster 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "strings" 8 9 composev1alpha3 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1alpha3" 10 composev1beta1 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1" 11 composev1beta2 "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta2" 12 "github.com/docker/compose-on-kubernetes/api/compose/v1alpha3" 13 "github.com/docker/compose-on-kubernetes/api/compose/v1beta1" 14 "github.com/docker/compose-on-kubernetes/api/compose/v1beta2" 15 "github.com/docker/compose-on-kubernetes/internal/conversions" 16 "github.com/docker/compose-on-kubernetes/internal/parsing" 17 "github.com/docker/compose-on-kubernetes/internal/patch" 18 "github.com/pkg/errors" 19 appsv1 "k8s.io/api/apps/v1" 20 apiv1 "k8s.io/api/core/v1" 21 storagetypes "k8s.io/api/storage/v1" 22 kerrors "k8s.io/apimachinery/pkg/api/errors" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 apitypes "k8s.io/apimachinery/pkg/types" 25 "k8s.io/apimachinery/pkg/util/wait" 26 typesappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" 27 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 28 storagev1 "k8s.io/client-go/kubernetes/typed/storage/v1" 29 "k8s.io/client-go/rest" 30 ) 31 32 // Namespace is a test dedicated namespace 33 type Namespace struct { 34 name string 35 stackRESTv1beta2 rest.Interface 36 stackRESTv1alpha3 rest.Interface 37 stacks composev1beta2.StackInterface 38 stacksv1alpha3 composev1alpha3.StackInterface 39 stacks1 composev1beta1.StackInterface 40 pods corev1.PodInterface 41 deployments typesappsv1.DeploymentInterface 42 services corev1.ServiceInterface 43 nodes corev1.NodeInterface 44 servicesSupplier func() *rest.Request 45 storageClasses storagev1.StorageClassInterface 46 configMaps corev1.ConfigMapInterface 47 secrets corev1.SecretInterface 48 config *rest.Config 49 } 50 51 // StackOperationStrategy is the strategy for a stack create/update 52 type StackOperationStrategy int 53 54 const ( 55 //StackOperationV1beta1 will use v1beta1 API 56 StackOperationV1beta1 StackOperationStrategy = iota 57 //StackOperationV1beta2Compose will use v1beta2 composefile subresource 58 StackOperationV1beta2Compose 59 //StackOperationV1beta2Stack will use v1beta2 structured stack 60 StackOperationV1beta2Stack 61 //StackOperationV1alpha3 will use a v1alpha3 structured stack 62 StackOperationV1alpha3 63 ) 64 65 // PodPredicate returns true when a predicate is verified on a pod and an optional message indicating why the predicate is false 66 type PodPredicate func(pod apiv1.Pod) (bool, string) 67 68 func newNamespace(config *rest.Config, namespace string) (*Namespace, error) { 69 composeClientSet, err := composev1beta2.NewForConfig(config) 70 if err != nil { 71 return nil, err 72 } 73 composeClientSet1, err := composev1beta1.NewForConfig(config) 74 if err != nil { 75 return nil, err 76 } 77 composeClientSetv1alpha3, err := composev1alpha3.NewForConfig(config) 78 if err != nil { 79 return nil, err 80 } 81 82 coreClientSet, err := corev1.NewForConfig(config) 83 if err != nil { 84 return nil, err 85 } 86 87 storageClientSet, err := storagev1.NewForConfig(config) 88 if err != nil { 89 return nil, err 90 } 91 appsClientSet, err := typesappsv1.NewForConfig(config) 92 if err != nil { 93 return nil, err 94 } 95 96 return &Namespace{ 97 name: namespace, 98 stackRESTv1beta2: composeClientSet.RESTClient(), 99 stackRESTv1alpha3: composeClientSetv1alpha3.RESTClient(), 100 stacks: composeClientSet.Stacks(namespace), 101 stacks1: composeClientSet1.Stacks(namespace), 102 stacksv1alpha3: composeClientSetv1alpha3.Stacks(namespace), 103 pods: coreClientSet.Pods(namespace), 104 deployments: appsClientSet.Deployments(namespace), 105 services: coreClientSet.Services(namespace), 106 nodes: coreClientSet.Nodes(), 107 storageClasses: storageClientSet.StorageClasses(), 108 servicesSupplier: func() *rest.Request { 109 return coreClientSet.RESTClient().Get().Resource("services").Namespace(namespace) 110 }, 111 secrets: coreClientSet.Secrets(namespace), 112 configMaps: coreClientSet.ConfigMaps(namespace), 113 config: config, 114 }, nil 115 } 116 117 // HasStorageClass returns true if cluster has at least one StorageClass defined 118 func (ns *Namespace) HasStorageClass() (bool, error) { 119 storageClasses, err := ns.storageClasses.List(metav1.ListOptions{}) 120 if err != nil { 121 return false, err 122 } 123 sc := defaultStorageClass(storageClasses.Items) 124 if sc == nil || sc.Provisioner == "kubernetes.io/host-path" { 125 return false, nil 126 } 127 return true, nil 128 } 129 130 func defaultStorageClass(classes []storagetypes.StorageClass) *storagetypes.StorageClass { 131 for _, c := range classes { 132 if c.Annotations != nil && c.Annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true" { 133 return &c 134 } 135 } 136 return nil 137 } 138 139 // RESTClientV1beta2 returns a RESTClient for the stacks 140 func (ns *Namespace) RESTClientV1beta2() rest.Interface { 141 return ns.stackRESTv1beta2 142 } 143 144 // RESTClientV1alpha3 returns a RESTClient for the stacks 145 func (ns *Namespace) RESTClientV1alpha3() rest.Interface { 146 return ns.stackRESTv1alpha3 147 } 148 149 // Name returns the name of the namespace. 150 func (ns *Namespace) Name() string { 151 return ns.name 152 } 153 154 // Deployments returns a DeploymentInterface 155 func (ns *Namespace) Deployments() typesappsv1.DeploymentInterface { 156 return ns.deployments 157 } 158 159 // Pods returns a PodInterface 160 func (ns *Namespace) Pods() corev1.PodInterface { 161 return ns.pods 162 } 163 164 // StacksV1beta1 returns a v1beta1 client 165 func (ns *Namespace) StacksV1beta1() composev1beta1.StackInterface { 166 return ns.stacks1 167 } 168 169 // StacksV1alpha3 returns a v1alpha3 client 170 func (ns *Namespace) StacksV1alpha3() composev1alpha3.StackInterface { 171 return ns.stacksv1alpha3 172 } 173 174 // CreatePullSecret creates a pull secret 175 func (ns *Namespace) CreatePullSecret(name, server, username, password string) error { 176 data, err := generatePullSecretData(server, username, password) 177 if err != nil { 178 return err 179 } 180 s := &apiv1.Secret{ 181 ObjectMeta: metav1.ObjectMeta{ 182 Namespace: ns.name, 183 Name: name, 184 }, 185 Type: apiv1.SecretTypeDockerConfigJson, 186 Data: map[string][]byte{ 187 apiv1.DockerConfigJsonKey: data, 188 }, 189 } 190 _, err = ns.Secrets().Create(s) 191 return err 192 } 193 194 func generatePullSecretData(server, username, password string) ([]byte, error) { 195 e := dockerConfigEntry{ 196 Username: username, 197 Password: password, 198 Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))), 199 } 200 cfg := dockerConfigJSON{ 201 Auths: map[string]dockerConfigEntry{ 202 server: e, 203 }, 204 } 205 return json.Marshal(&cfg) 206 } 207 208 type dockerConfigJSON struct { 209 Auths map[string]dockerConfigEntry `json:"auths"` 210 } 211 type dockerConfigEntry struct { 212 Username string `json:"username,omitempty"` 213 Password string `json:"password,omitempty"` 214 Email string `json:"email,omitempty"` 215 Auth string `json:"auth,omitempty"` 216 } 217 218 // CreateStack creates a stack. 219 func (ns *Namespace) CreateStack(strategy StackOperationStrategy, name, composeFile string) (*v1alpha3.Stack, error) { 220 switch strategy { 221 case StackOperationV1beta2Compose: 222 compose := &v1beta2.ComposeFile{ 223 ObjectMeta: metav1.ObjectMeta{ 224 Name: name, 225 }, 226 ComposeFile: composeFile, 227 } 228 return nil, ns.stackRESTv1beta2.Post().Namespace(ns.name).Name(name).Resource("stacks").SubResource("composefile").Body(compose).Do().Error() 229 case StackOperationV1beta2Stack: 230 var stack *v1beta2.Stack 231 var err error 232 config, err := parsing.LoadStackData([]byte(composeFile), map[string]string{}) 233 if err != nil { 234 return nil, err 235 } 236 stack = &v1beta2.Stack{ 237 ObjectMeta: metav1.ObjectMeta{ 238 Name: name, 239 }, 240 Spec: &v1beta2.StackSpec{}, 241 } 242 spec := conversions.FromComposeConfig(config) 243 if err := v1alpha3.Convert_v1alpha3_StackSpec_To_v1beta2_StackSpec(spec, stack.Spec, nil); err != nil { 244 return nil, err 245 } 246 res, err := ns.stacks.Create(stack) 247 if err != nil { 248 return nil, err 249 } 250 var aslatest v1alpha3.Stack 251 err = v1alpha3.Convert_v1beta2_Stack_To_v1alpha3_Stack(res, &aslatest, nil) 252 return &aslatest, err 253 case StackOperationV1alpha3: 254 var stack *v1alpha3.Stack 255 var err error 256 config, err := parsing.LoadStackData([]byte(composeFile), map[string]string{}) 257 if err != nil { 258 return nil, err 259 } 260 stack = &v1alpha3.Stack{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: name, 263 }, 264 Spec: conversions.FromComposeConfig(config), 265 } 266 return ns.stacksv1alpha3.Create(stack) 267 case StackOperationV1beta1: 268 stack := &v1beta1.Stack{ 269 ObjectMeta: metav1.ObjectMeta{ 270 Name: name, 271 }, 272 Spec: v1beta1.StackSpec{ 273 ComposeFile: composeFile, 274 }, 275 } 276 _, err := ns.stacks1.Create(stack) 277 return nil, err 278 } 279 return nil, nil 280 } 281 282 // DeleteStacks deletes all stacks. 283 func (ns *Namespace) DeleteStacks() error { 284 return ns.stacks.DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{}) 285 } 286 287 // DeleteStack deletes a stack. 288 func (ns *Namespace) DeleteStack(name string) error { 289 return ns.stacks.Delete(name, &metav1.DeleteOptions{}) 290 } 291 292 // DeleteStackWithPropagation deletes a stack using the specified propagation. 293 func (ns *Namespace) DeleteStackWithPropagation(name string, propagation metav1.DeletionPropagation) error { 294 return ns.stacks.Delete(name, &metav1.DeleteOptions{PropagationPolicy: &propagation}) 295 } 296 297 // DeleteStacksv1 deletes all stacks. 298 func (ns *Namespace) DeleteStacksv1() error { 299 return ns.stacks1.DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{}) 300 } 301 302 // DeleteStackv1 deletes a stack. 303 func (ns *Namespace) DeleteStackv1(name string) error { 304 return ns.stacks1.Delete(name, &metav1.DeleteOptions{}) 305 } 306 307 // UpdateStack updates a stack. 308 func (ns *Namespace) UpdateStack(strategy StackOperationStrategy, name, composeFile string) (*v1alpha3.Stack, error) { 309 switch strategy { 310 case StackOperationV1beta2Compose: 311 compose := &v1beta2.ComposeFile{ 312 ObjectMeta: metav1.ObjectMeta{ 313 Name: name, 314 }, 315 ComposeFile: composeFile, 316 } 317 return nil, ns.stackRESTv1beta2.Put().Namespace(ns.name).Name(name).Resource("stacks").SubResource("composefile").Body(compose).Do().Error() 318 case StackOperationV1alpha3: 319 p := patch.New() 320 config, err := parsing.LoadStackData([]byte(composeFile), map[string]string{}) 321 if err != nil { 322 return nil, err 323 } 324 newStack := &v1alpha3.Stack{ 325 ObjectMeta: metav1.ObjectMeta{ 326 Name: name, 327 }, 328 Spec: conversions.FromComposeConfig(config), 329 } 330 if err != nil { 331 return nil, err 332 } 333 p = p.Replace("/spec", newStack.Spec) 334 335 buf, err := p.ToJSON() 336 if err != nil { 337 return nil, err 338 } 339 return ns.stacksv1alpha3.Patch(name, apitypes.JSONPatchType, buf) 340 case StackOperationV1beta2Stack: 341 p := patch.New() 342 config, err := parsing.LoadStackData([]byte(composeFile), map[string]string{}) 343 if err != nil { 344 return nil, err 345 } 346 newStack := &v1beta2.Stack{ 347 ObjectMeta: metav1.ObjectMeta{ 348 Name: name, 349 }, 350 Spec: &v1beta2.StackSpec{}, 351 } 352 spec := conversions.FromComposeConfig(config) 353 if err := v1alpha3.Convert_v1alpha3_StackSpec_To_v1beta2_StackSpec(spec, newStack.Spec, nil); err != nil { 354 return nil, err 355 } 356 if err != nil { 357 return nil, err 358 } 359 p = p.Replace("/spec", newStack.Spec) 360 361 buf, err := p.ToJSON() 362 if err != nil { 363 return nil, err 364 } 365 res, err := ns.stacks.Patch(name, apitypes.JSONPatchType, buf) 366 if err != nil { 367 return nil, err 368 } 369 var aslatest v1alpha3.Stack 370 err = v1alpha3.Convert_v1beta2_Stack_To_v1alpha3_Stack(res, &aslatest, nil) 371 return &aslatest, err 372 case StackOperationV1beta1: 373 p := patch.New() 374 p = p.Replace("/spec/composeFile", composeFile) 375 buf, err := p.ToJSON() 376 if err != nil { 377 return nil, err 378 } 379 _, err = ns.stacks1.Patch(name, apitypes.JSONPatchType, buf) 380 return nil, err 381 } 382 return nil, nil 383 } 384 385 // UpdateStackFromSpec updates a stack from a Spec. 386 func (ns *Namespace) UpdateStackFromSpec(name string, newStack *v1alpha3.Stack) (*v1alpha3.Stack, error) { 387 filtered := &v1alpha3.Stack{ 388 Spec: newStack.Spec, 389 } 390 buf, err := json.Marshal(filtered) 391 if err != nil { 392 return nil, errors.Wrap(err, "stack marshaling error") 393 } 394 return ns.stacksv1alpha3.Patch(name, apitypes.MergePatchType, buf) 395 } 396 397 // GetStack gets a stack. 398 func (ns *Namespace) GetStack(name string) (*v1alpha3.Stack, error) { 399 return ns.stacksv1alpha3.Get(name, metav1.GetOptions{}) 400 } 401 402 // ListStacks lists the stacks. 403 func (ns *Namespace) ListStacks() ([]v1alpha3.Stack, error) { 404 stacks, err := ns.stacksv1alpha3.List(metav1.ListOptions{}) 405 if err != nil { 406 return nil, err 407 } 408 409 return stacks.Items, nil 410 } 411 412 // ContainsZeroStack is a poller that checks that no stack is created. 413 func (ns *Namespace) ContainsZeroStack() wait.ConditionFunc { 414 return ns.ContainsNStacks(0) 415 } 416 417 // ContainsNStacks is a poller that checks how many stacks are created. 418 func (ns *Namespace) ContainsNStacks(count int) wait.ConditionFunc { 419 return func() (bool, error) { 420 stacks, err := ns.ListStacks() 421 if err != nil { 422 return false, err 423 } 424 425 if len(stacks) != count { 426 return false, nil 427 } 428 429 return true, nil 430 } 431 } 432 433 // ContainsZeroPod is a poller that checks that no pod is created. 434 func (ns *Namespace) ContainsZeroPod() wait.ConditionFunc { 435 return ns.ContainsNPods(0) 436 } 437 438 // ContainsNPods is a poller that checks how many pods are created. 439 func (ns *Namespace) ContainsNPods(count int) wait.ConditionFunc { 440 return ns.ContainsNPodsMatchingSelector(count, "") 441 } 442 443 // PodIsActuallyRemoved is a poller that checks that a pod has been terminated 444 func (ns *Namespace) PodIsActuallyRemoved(name string) wait.ConditionFunc { 445 return func() (bool, error) { 446 _, err := ns.pods.Get(name, metav1.GetOptions{}) 447 if kerrors.IsNotFound(err) { 448 return true, nil 449 } 450 return false, err 451 } 452 } 453 454 // ContainsNPodsMatchingSelector is a poller that checks how many pods are created for given label selector. 455 func (ns *Namespace) ContainsNPodsMatchingSelector(count int, labelSelector string) wait.ConditionFunc { 456 return func() (bool, error) { 457 pods, err := ns.ListPods(labelSelector) 458 if err != nil { 459 return false, err 460 } 461 462 if len(pods) != count { 463 return false, nil 464 } 465 466 return true, nil 467 } 468 } 469 470 // ContainsNPodsWithPredicate is a poller that checks how many pods matching the predicate are created. 471 func (ns *Namespace) ContainsNPodsWithPredicate(count int, labelSelector string, predicate PodPredicate) wait.ConditionFunc { 472 return func() (bool, error) { 473 pods, err := ns.ListPods(labelSelector) 474 if err != nil { 475 return false, err 476 } 477 478 if len(pods) != count { 479 return false, nil 480 } 481 482 for _, pod := range pods { 483 if ok, _ := predicate(pod); !ok { 484 return false, nil 485 } 486 } 487 488 return true, nil 489 } 490 } 491 492 // IsStackAvailable is a poller that checks is a given stack is available. 493 func (ns *Namespace) IsStackAvailable(name string) wait.ConditionFunc { 494 return func() (bool, error) { 495 stack, err := ns.GetStack(name) 496 if err != nil { 497 return false, err 498 } 499 500 if stack.Status == nil || stack.Status.Phase != v1alpha3.StackAvailable { 501 return false, nil 502 } 503 504 return true, nil 505 } 506 } 507 508 // IsStackFailed is a poller that checks if a given stack has failed with the correct error. 509 func (ns *Namespace) IsStackFailed(name string, errorSubstr string) wait.ConditionFunc { 510 return func() (bool, error) { 511 stack, err := ns.GetStack(name) 512 if err != nil { 513 return false, err 514 } 515 516 if stack.Status == nil || stack.Status.Phase != v1alpha3.StackFailure { 517 return false, nil 518 } 519 520 if !strings.Contains(stack.Status.Message, errorSubstr) { 521 return false, fmt.Errorf("status message is %q. expected to contain %q", stack.Status.Message, errorSubstr) 522 } 523 524 return true, nil 525 } 526 } 527 528 // IsServicePresent is a poller that checks if a service is present. 529 func (ns *Namespace) IsServicePresent(labelSelector string) wait.ConditionFunc { 530 return func() (bool, error) { 531 services, err := ns.services.List(metav1.ListOptions{ 532 LabelSelector: labelSelector, 533 }) 534 if err != nil { 535 return false, err 536 } 537 538 if len(services.Items) == 0 { 539 return false, nil 540 } 541 542 return true, nil 543 } 544 } 545 546 // ServiceCount is a poller that checks a number of services to be present. 547 func (ns *Namespace) ServiceCount(labelSelector string, count int) wait.ConditionFunc { 548 return func() (bool, error) { 549 services, err := ns.services.List(metav1.ListOptions{ 550 LabelSelector: labelSelector, 551 }) 552 if err != nil { 553 return false, err 554 } 555 556 if len(services.Items) != count { 557 return false, nil 558 } 559 560 return true, nil 561 } 562 } 563 564 // IsServiceNotPresent is a poller that checks if a service is not present. 565 func (ns *Namespace) IsServiceNotPresent(labelSelector string) wait.ConditionFunc { 566 return func() (bool, error) { 567 services, err := ns.services.List(metav1.ListOptions{ 568 LabelSelector: labelSelector, 569 }) 570 if err != nil { 571 return false, err 572 } 573 574 if len(services.Items) > 0 { 575 return false, nil 576 } 577 578 return true, nil 579 } 580 } 581 582 // IsServiceResponding is a poller that checks is responding with the expected 583 // content text. 584 func (ns *Namespace) IsServiceResponding(service string, url string, expectedText string) wait.ConditionFunc { 585 return func() (bool, error) { 586 resp, err := ns.servicesSupplier(). 587 Name(service). 588 SubResource(strings.Split(url, "/")...). 589 DoRaw() 590 if err != nil { 591 return false, nil 592 } 593 594 if !strings.Contains(string(resp), expectedText) { 595 return false, nil 596 } 597 598 return true, nil 599 } 600 } 601 602 // ListPods lists the pods that match a given selector. 603 func (ns *Namespace) ListPods(labelSelector string) ([]apiv1.Pod, error) { 604 pods, err := ns.pods.List(metav1.ListOptions{ 605 LabelSelector: labelSelector, 606 }) 607 if err != nil { 608 return nil, err 609 } 610 611 return pods.Items, nil 612 } 613 614 // ListAllPods lists all pods in the namespace. 615 func (ns *Namespace) ListAllPods() ([]apiv1.Pod, error) { 616 return ns.ListPods("") 617 } 618 619 // ListDeployments lists the deployments that match a given selector. 620 func (ns *Namespace) ListDeployments(labelSelector string) ([]appsv1.Deployment, error) { 621 deployments, err := ns.deployments.List(metav1.ListOptions{ 622 LabelSelector: labelSelector, 623 }) 624 if err != nil { 625 return nil, err 626 } 627 628 return deployments.Items, nil 629 } 630 631 // ListServices lists the services that match a given selector. 632 func (ns *Namespace) ListServices(labelSelector string) ([]apiv1.Service, error) { 633 services, err := ns.services.List(metav1.ListOptions{ 634 LabelSelector: labelSelector, 635 }) 636 if err != nil { 637 return nil, err 638 } 639 640 return services.Items, nil 641 } 642 643 // ListNodes lists the nodes available in the cluster. 644 func (ns *Namespace) ListNodes() ([]apiv1.Node, error) { 645 nodes, err := ns.nodes.List(metav1.ListOptions{}) 646 if err != nil { 647 return nil, err 648 } 649 return nodes.Items, nil 650 } 651 652 // ConfigMaps returns a ConfigMaps client for the namespace 653 func (ns *Namespace) ConfigMaps() corev1.ConfigMapInterface { 654 return ns.configMaps 655 } 656 657 // Secrets returns a Secrets client for the namespace 658 func (ns *Namespace) Secrets() corev1.SecretInterface { 659 return ns.secrets 660 } 661 662 // Services returns a Services client for the namespace 663 func (ns *Namespace) Services() corev1.ServiceInterface { 664 return ns.services 665 } 666 667 // As returns the same namespace with an impersonated config 668 func (ns *Namespace) As(user rest.ImpersonationConfig) (*Namespace, error) { 669 cfg := *ns.config 670 cfg.Impersonate = user 671 return newNamespace(&cfg, ns.name) 672 }