github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/pkg/kubernetes/client.go (about) 1 package kubernetes 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "reflect" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 17 "k8s.io/apimachinery/pkg/api/meta" 18 19 extensions "k8s.io/api/extensions/v1beta1" 20 utilyaml "k8s.io/apimachinery/pkg/util/yaml" 21 k8syaml "sigs.k8s.io/yaml" 22 23 "github.com/caos/orbos/pkg/labels" 24 25 "github.com/caos/orbos/internal/helpers" 26 "github.com/caos/orbos/mntr" 27 apps "k8s.io/api/apps/v1" 28 batch "k8s.io/api/batch/v1" 29 "k8s.io/api/batch/v1beta1" 30 batchv1beta1 "k8s.io/api/batch/v1beta1" 31 core "k8s.io/api/core/v1" 32 policy "k8s.io/api/policy/v1beta1" 33 rbac "k8s.io/api/rbac/v1" 34 apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 35 apixv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" 36 macherrs "k8s.io/apimachinery/pkg/api/errors" 37 mach "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/types" 41 "k8s.io/apimachinery/pkg/watch" 42 "k8s.io/client-go/discovery" 43 "k8s.io/client-go/discovery/cached/memory" 44 "k8s.io/client-go/dynamic" 45 "k8s.io/client-go/kubernetes" 46 "k8s.io/client-go/kubernetes/scheme" 47 clgocore "k8s.io/client-go/kubernetes/typed/core/v1" 48 "k8s.io/client-go/rest" 49 "k8s.io/client-go/restmapper" 50 "k8s.io/client-go/tools/clientcmd" 51 "k8s.io/client-go/tools/remotecommand" 52 ) 53 54 const TaintKeyPrefix = "node.orbos.ch/" 55 56 type NodeWithKubeadm interface { 57 Execute(stdin io.Reader, cmd string) ([]byte, error) 58 } 59 60 type IDFunc func() string 61 62 func (i IDFunc) ID() string { 63 return i() 64 } 65 66 type ClientInt interface { 67 ApplyService(rsc *core.Service) error 68 DeleteService(namespace, name string) error 69 70 GetJob(namespace, name string) (*batch.Job, error) 71 ApplyJob(rsc *batch.Job) error 72 ApplyJobDryRun(rsc *batch.Job) error 73 DeleteJob(namespace string, name string) error 74 WaitUntilJobCompleted(namespace string, name string, timeout time.Duration) error 75 76 ApplyServiceAccount(rsc *core.ServiceAccount) error 77 DeleteServiceAccount(namespace, name string) error 78 79 ApplyStatefulSet(rsc *apps.StatefulSet, force bool) error 80 DeleteStatefulset(namespace, name string) error 81 ScaleStatefulset(namespace, name string, replicaCount int) error 82 WaitUntilStatefulsetIsReady(namespace string, name string, containerCheck, readyCheck bool, timeout time.Duration) error 83 84 ExecInPodWithOutput(namespace, name, container, command string) (string, error) 85 ExecInPod(namespace, name, container, command string) error 86 87 GetDeployment(namespace, name string) (*apps.Deployment, error) 88 ApplyDeployment(rsc *apps.Deployment, force bool) error 89 DeleteDeployment(namespace, name string) error 90 PatchDeployment(namespace, name string, data string) error 91 WaitUntilDeploymentReady(namespace string, name string, containerCheck, readyCheck bool, timeout time.Duration) error 92 ScaleDeployment(namespace, name string, replicaCount int) error 93 ExecInPodOfDeployment(namespace, name, container, command string) error 94 95 CheckCRD(name string) (*apixv1beta1.CustomResourceDefinition, bool, error) 96 GetNamespacedCRDResource(group, version, kind, namespace, name string) (*unstructured.Unstructured, error) 97 ApplyNamespacedCRDResource(group, version, kind, namespace, name string, crd *unstructured.Unstructured) error 98 DeleteNamespacedCRDResource(group, version, kind, namespace, name string) error 99 ApplyCRDResource(crd *unstructured.Unstructured) error 100 DeleteCRDResource(group, version, kind, name string) error 101 102 ApplyCronJob(rsc *v1beta1.CronJob) error 103 DeleteCronJob(namespace string, name string) error 104 ListCronJobs(namespace string, labels map[string]string) (*batchv1beta1.CronJobList, error) 105 106 ListSecrets(namespace string, labels map[string]string) (*core.SecretList, error) 107 GetSecret(namespace string, name string) (*core.Secret, error) 108 ApplySecret(rsc *core.Secret) error 109 DeleteSecret(namespace, name string) error 110 WaitForSecret(namespace string, name string, timeout time.Duration) error 111 112 GetConfigMap(namespace, name string) (*core.ConfigMap, error) 113 ApplyConfigmap(rsc *core.ConfigMap) error 114 DeleteConfigmap(namespace, name string) error 115 WaitForConfigMap(namespace string, name string, timeout time.Duration) error 116 117 ApplyRole(rsc *rbac.Role) error 118 DeleteRole(namespace, name string) error 119 120 ApplyClusterRole(rsc *rbac.ClusterRole) error 121 DeleteClusterRole(name string) error 122 123 ApplyIngress(rsc *extensions.Ingress) error 124 DeleteIngress(namespace, name string) error 125 126 ApplyRoleBinding(rsc *rbac.RoleBinding) error 127 DeleteRoleBinding(namespace, name string) error 128 129 ApplyClusterRoleBinding(rsc *rbac.ClusterRoleBinding) error 130 DeleteClusterRoleBinding(name string) error 131 132 ApplyPodDisruptionBudget(rsc *policy.PodDisruptionBudget) error 133 DeletePodDisruptionBudget(namespace string, name string) error 134 135 ApplyNamespace(rsc *core.Namespace) error 136 DeleteNamespace(name string) error 137 138 ListPersistentVolumes() (*core.PersistentVolumeList, error) 139 140 ApplyPlainYAML(mntr.Monitor, []byte) error 141 142 ListPersistentVolumeClaims(namespace string) (*core.PersistentVolumeClaimList, error) 143 DeletePersistentVolumeClaim(namespace, name string, timeout time.Duration) error 144 } 145 146 var _ ClientInt = (*Client)(nil) 147 148 type Client struct { 149 monitor mntr.Monitor 150 set *kubernetes.Clientset 151 dynamic dynamic.Interface 152 apixv1beta1client *apixv1beta1client.ApiextensionsV1beta1Client 153 mapper *restmapper.DeferredDiscoveryRESTMapper 154 restConfig *rest.Config 155 available bool 156 } 157 158 func NewK8sClientPathBeforeInCluster(monitor mntr.Monitor, kubeconfigPath string) (*Client, error) { 159 kubeconfigStr := "" 160 if kubeconfigPath != "" { 161 value, err := ioutil.ReadFile(helpers.PruneHome(kubeconfigPath)) 162 if err == nil { 163 kubeconfigStr = string(value) 164 } 165 } 166 167 return NewK8sClient(monitor, &kubeconfigStr, kubeconfigPath) 168 } 169 170 func newClient(monitor mntr.Monitor) *Client { 171 return &Client{monitor: monitor} 172 } 173 174 func NewK8sClient(monitor mntr.Monitor, kubeconfig *string, kubeconfigPath string) (*Client, error) { 175 kc := newClient(monitor) 176 if err := kc.init(kubeconfig, kubeconfigPath); err != nil { 177 return nil, err 178 } 179 return kc, nil 180 } 181 182 func NewK8sClientWithConfig(monitor mntr.Monitor, conf *rest.Config) (*Client, error) { 183 kc := newClient(monitor) 184 if err := kc.initConfig(conf); err != nil { 185 return nil, err 186 } 187 return kc, nil 188 } 189 190 func (c *Client) checkConnectivity() error { 191 192 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 193 defer cancel() 194 _, err := c.set.CoreV1().Nodes().Get(ctx, "<<<-impo$$ible->>>", mach.GetOptions{}) 195 if err == nil || macherrs.IsNotFound(err) { 196 return nil 197 } 198 return fmt.Errorf("connectivity check get node failed: %w", err) 199 } 200 201 func (c *Client) nodeApi() clgocore.NodeInterface { 202 return c.set.CoreV1().Nodes() 203 } 204 205 type File struct { 206 Name string 207 Content []byte 208 } 209 210 func (c *Client) ApplyNamespace(rsc *core.Namespace) error { 211 resources := c.set.CoreV1().Namespaces() 212 return c.applyResource("namespace", rsc.GetName(), func() error { 213 214 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 215 return err 216 }, func() error { 217 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 218 return err 219 }) 220 } 221 func (c *Client) DeleteNamespace(name string) error { 222 return notFoundIsSuccess(c.set.CoreV1().Namespaces().Delete(context.Background(), name, mach.DeleteOptions{})) 223 } 224 225 func (c *Client) ListNamespaces() (*core.NamespaceList, error) { 226 return c.set.CoreV1().Namespaces().List(context.Background(), mach.ListOptions{}) 227 } 228 229 func (c *Client) ListSecrets(namespace string, labels map[string]string) (*core.SecretList, error) { 230 labelSelector := "" 231 for k, v := range labels { 232 if labelSelector == "" { 233 labelSelector = fmt.Sprintf("%s=%s", k, v) 234 } else { 235 labelSelector = fmt.Sprintf("%s, %s=%s", labelSelector, k, v) 236 } 237 } 238 239 return c.set.CoreV1().Secrets(namespace).List(context.Background(), mach.ListOptions{LabelSelector: labelSelector}) 240 } 241 242 func (c *Client) ListPersistentVolumes() (*core.PersistentVolumeList, error) { 243 return c.set.CoreV1().PersistentVolumes().List(context.Background(), mach.ListOptions{}) 244 } 245 246 func (c *Client) ListPersistentVolumeClaims(namespace string) (*core.PersistentVolumeClaimList, error) { 247 return c.set.CoreV1().PersistentVolumeClaims(namespace).List(context.Background(), mach.ListOptions{}) 248 } 249 250 func (c *Client) DeletePersistentVolumeClaim(namespace, name string, timeout time.Duration) error { 251 ctx := context.Background() 252 253 returnChannel := make(chan error, 1) 254 interval := time.Second * 1 255 timesS := (timeout / interval) * time.Second 256 257 go func() { 258 ctx := context.Background() 259 for i := 0; i < int(timesS.Seconds()); i++ { 260 _, err := c.set.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, mach.GetOptions{}) 261 if err != nil && !macherrs.IsNotFound(err) { 262 returnChannel <- err 263 return 264 } 265 266 if macherrs.IsNotFound(err) { 267 returnChannel <- nil 268 return 269 } 270 time.Sleep(interval) 271 } 272 returnChannel <- errors.New("delete pvc timeout") 273 return 274 }() 275 276 if err := notFoundIsSuccess(c.set.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, name, mach.DeleteOptions{})); err != nil { 277 return err 278 } 279 280 select { 281 case res := <-returnChannel: 282 return res 283 case <-time.After(timeout): 284 return errors.New("timeout while waiting for job to complete") 285 } 286 } 287 288 func (c *Client) ScaleDeployment(namespace, name string, replicaCount int) error { 289 patch := []byte(`{"spec":{"replicas":` + strconv.Itoa(replicaCount) + `}}`) 290 _, err := c.set.AppsV1().Deployments(namespace).Patch(context.Background(), name, types.StrategicMergePatchType, patch, mach.PatchOptions{}) 291 return err 292 } 293 294 func (c *Client) GetDeployment(namespace, name string) (*apps.Deployment, error) { 295 return c.set.AppsV1().Deployments(namespace).Get(context.Background(), name, mach.GetOptions{}) 296 } 297 298 func (c *Client) ApplyDeployment(rsc *apps.Deployment, force bool) error { 299 resources := c.set.AppsV1().Deployments(rsc.GetNamespace()) 300 rscLabels, err := labels.NameFrom(rsc.Labels) 301 if err != nil { 302 return err 303 } 304 305 rscSelector, err := labels.SelectorFrom(rsc.Spec.Selector.MatchLabels) 306 if err != nil { 307 return err 308 } 309 310 return c.applyController( 311 "deployment", 312 force, 313 rscLabels, 314 rscSelector, 315 func() (*labels.Selector, error) { 316 sts, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 317 if err != nil { 318 return nil, err 319 } 320 321 return labels.SelectorFrom(sts.Spec.Selector.MatchLabels) 322 }, 323 func() error { 324 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 325 return err 326 }, 327 func() error { 328 _, err = resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 329 return err 330 }, 331 func() error { 332 return c.DeleteDeployment(rsc.GetNamespace(), rsc.GetName()) 333 }, 334 ) 335 } 336 337 func (c *Client) DeleteDeployment(namespace, name string) error { 338 return notFoundIsSuccess(c.set.AppsV1().Deployments(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 339 } 340 func (c *Client) PatchDeployment(namespace, name string, data string) error { 341 _, err := c.set.AppsV1().Deployments(namespace).Patch(context.Background(), name, types.StrategicMergePatchType, []byte(data), mach.PatchOptions{}) 342 return err 343 } 344 345 func (c *Client) WaitUntilDeploymentReady(namespace string, name string, containerCheck, readyCheck bool, timeout time.Duration) error { 346 returnChannel := make(chan error, 1) 347 go func() { 348 ctx := context.Background() 349 deploy, err := c.set.AppsV1().Deployments(namespace).Get(ctx, name, mach.GetOptions{}) 350 if err != nil { 351 returnChannel <- err 352 return 353 } 354 355 labelSelector := getLabelSelector(deploy.Spec.Selector.MatchLabels) 356 357 watch, err := c.set.CoreV1().Pods(namespace).Watch(ctx, mach.ListOptions{ 358 LabelSelector: labelSelector, 359 }) 360 defer watch.Stop() 361 if err != nil { 362 returnChannel <- err 363 return 364 } 365 replicas := deploy.Spec.Replicas 366 367 returnChannel <- waitForPodsPhase(watch, int(*replicas), core.PodRunning, containerCheck, readyCheck) 368 }() 369 370 select { 371 case res := <-returnChannel: 372 return res 373 case <-time.After(timeout): 374 return errors.New("timeout while waiting for deployment to be ready") 375 } 376 } 377 378 func (c *Client) ApplyService(rsc *core.Service) error { 379 resources := c.set.CoreV1().Services(rsc.GetNamespace()) 380 return c.applyResource("service", rsc.GetName(), func() error { 381 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 382 return err 383 }, func() error { 384 svc, err := resources.Get(context.Background(), rsc.Name, mach.GetOptions{}) 385 if err != nil { 386 return err 387 } 388 rsc.Spec.ClusterIP = svc.Spec.ClusterIP 389 rsc.ObjectMeta.ResourceVersion = svc.ObjectMeta.ResourceVersion 390 _, err = resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 391 return err 392 }) 393 } 394 395 func (c *Client) DeleteService(namespace, name string) error { 396 return notFoundIsSuccess(c.set.CoreV1().Services(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 397 } 398 399 func (c *Client) GetJob(namespace, name string) (*batch.Job, error) { 400 return c.set.BatchV1().Jobs(namespace).Get(context.Background(), name, mach.GetOptions{}) 401 } 402 403 func (c *Client) ApplyJobDryRun(rsc *batch.Job) error { 404 resources := c.set.BatchV1().Jobs(rsc.Namespace) 405 return c.applyResource("job", rsc.GetName(), func() error { 406 res, err := resources.Create(context.Background(), rsc, mach.CreateOptions{DryRun: []string{mach.DryRunAll}}) 407 if err != nil { 408 return err 409 } 410 *rsc = *res 411 return nil 412 }, func() error { 413 j, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 414 if err != nil { 415 return err 416 } 417 if j.GetName() == rsc.GetName() && j.GetNamespace() == rsc.GetNamespace() { 418 res, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{DryRun: []string{mach.DryRunAll}}) 419 if err != nil { 420 return err 421 } 422 *rsc = *res 423 return nil 424 } 425 return nil 426 }) 427 } 428 429 func (c *Client) ApplyJob(rsc *batch.Job) error { 430 resources := c.set.BatchV1().Jobs(rsc.Namespace) 431 return c.applyResource("job", rsc.GetName(), func() error { 432 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 433 return err 434 }, func() error { 435 j, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 436 if err != nil { 437 return err 438 } 439 if j.GetName() == rsc.GetName() && j.GetNamespace() == rsc.GetNamespace() { 440 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 441 return err 442 } 443 return nil 444 }) 445 } 446 447 func (c *Client) WaitUntilJobCompleted(namespace string, name string, timeout time.Duration) error { 448 returnChannel := make(chan error, 1) 449 go func() { 450 ctx := context.Background() 451 job, err := c.set.BatchV1().Jobs(namespace).Get(ctx, name, mach.GetOptions{}) 452 if err != nil { 453 returnChannel <- err 454 return 455 } 456 457 if job.Status.Succeeded > 0 { 458 returnChannel <- nil 459 return 460 } 461 462 labelSelector := getLabelSelector(job.Spec.Selector.MatchLabels) 463 464 watch, err := c.set.CoreV1().Pods(namespace).Watch(ctx, mach.ListOptions{ 465 LabelSelector: labelSelector, 466 }) 467 defer watch.Stop() 468 if err != nil { 469 returnChannel <- err 470 return 471 } 472 473 returnChannel <- waitForPodsPhase(watch, 1, core.PodSucceeded, false, false) 474 }() 475 476 select { 477 case res := <-returnChannel: 478 return res 479 case <-time.After(timeout): 480 return fmt.Errorf("timeout after %s while waiting for job to complete", timeout) 481 } 482 } 483 484 func (c *Client) DeleteJob(namespace string, name string) error { 485 job, err := c.GetJob(namespace, name) 486 if notFoundIsSuccess(err) != nil { 487 return err 488 } 489 490 if err := notFoundIsSuccess(c.set.BatchV1().Jobs(namespace).Delete(context.Background(), name, mach.DeleteOptions{})); err != nil { 491 return err 492 } 493 494 if job != nil && err == nil && job.Spec.Selector != nil && job.Spec.Selector.MatchLabels != nil { 495 //Pod cleanup if necessary 496 return notFoundIsSuccess(c.DeletePodsByLabels(namespace, job.Spec.Selector.MatchLabels)) 497 } 498 return nil 499 } 500 501 func (c *Client) ListCronJobs(namespace string, labels map[string]string) (*batchv1beta1.CronJobList, error) { 502 return c.set.BatchV1beta1().CronJobs(namespace).List(context.Background(), mach.ListOptions{LabelSelector: getLabelSelector(labels)}) 503 } 504 505 func (c *Client) ApplyCronJob(rsc *v1beta1.CronJob) error { 506 resources := c.set.BatchV1beta1().CronJobs(rsc.Namespace) 507 return c.applyResource("cronjob", rsc.GetName(), func() error { 508 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 509 return err 510 }, func() error { 511 j, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 512 if err != nil { 513 return err 514 } 515 if j.GetName() == rsc.GetName() && j.GetNamespace() == rsc.GetNamespace() { 516 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 517 return err 518 } 519 return nil 520 }) 521 } 522 523 func (c *Client) DeleteCronJob(namespace string, name string) error { 524 return notFoundIsSuccess(c.set.BatchV1beta1().CronJobs(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 525 } 526 527 func (c *Client) ListPods(namespace string, labels map[string]string) (*core.PodList, error) { 528 return c.set.CoreV1().Pods(namespace).List(context.Background(), mach.ListOptions{LabelSelector: getLabelSelector(labels)}) 529 } 530 531 func (c *Client) ApplyPodDisruptionBudget(rsc *policy.PodDisruptionBudget) error { 532 resources := c.set.PolicyV1beta1().PodDisruptionBudgets(rsc.Namespace) 533 return c.applyResource("poddisruptionbudget", rsc.GetName(), func() error { 534 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 535 return err 536 }, func() error { 537 pdb, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 538 if err != nil { 539 return err 540 } 541 if pdb.GetName() == rsc.GetName() && pdb.GetNamespace() == rsc.GetNamespace() { 542 rsc.ResourceVersion = pdb.ResourceVersion 543 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 544 return err 545 } 546 return nil 547 }) 548 } 549 func (c *Client) DeletePodDisruptionBudget(namespace string, name string) error { 550 return notFoundIsSuccess(c.set.PolicyV1beta1().PodDisruptionBudgets(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 551 } 552 553 func (c *Client) ApplyStatefulSet(rsc *apps.StatefulSet, force bool) error { 554 resources := c.set.AppsV1().StatefulSets(rsc.Namespace) 555 rscLabels, err := labels.NameFrom(rsc.Labels) 556 if err != nil { 557 return err 558 } 559 560 rscSelector, err := labels.SelectorFrom(rsc.Spec.Selector.MatchLabels) 561 if err != nil { 562 return err 563 } 564 565 return c.applyController( 566 "statefulset", 567 force, 568 rscLabels, 569 rscSelector, 570 func() (*labels.Selector, error) { 571 sts, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 572 if err != nil { 573 return nil, err 574 } 575 576 return labels.SelectorFrom(sts.Spec.Selector.MatchLabels) 577 }, 578 func() error { 579 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 580 return err 581 }, 582 func() error { 583 _, err = resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 584 return err 585 }, 586 func() error { 587 return c.DeleteStatefulset(rsc.GetNamespace(), rsc.GetName()) 588 }, 589 ) 590 } 591 592 func (c *Client) DeleteStatefulset(namespace, name string) error { 593 return notFoundIsSuccess(c.set.AppsV1().StatefulSets(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 594 } 595 596 func (c *Client) ScaleStatefulset(namespace, name string, replicaCount int) error { 597 patch := []byte(`{"spec":{"replicas":` + strconv.Itoa(replicaCount) + `}}`) 598 _, err := c.set.AppsV1().StatefulSets(namespace).Patch(context.Background(), name, types.StrategicMergePatchType, patch, mach.PatchOptions{}) 599 return err 600 } 601 602 func (c *Client) WaitUntilStatefulsetIsReady(namespace string, name string, containerCheck, readyCheck bool, timeout time.Duration) error { 603 returnChannel := make(chan error, 1) 604 go func() { 605 ctx := context.Background() 606 sfs, err := c.set.AppsV1().StatefulSets(namespace).Get(ctx, name, mach.GetOptions{}) 607 if err != nil { 608 returnChannel <- err 609 return 610 } 611 612 labelSelector := getLabelSelector(sfs.Spec.Selector.MatchLabels) 613 614 watch, err := c.set.CoreV1().Pods(namespace).Watch(ctx, mach.ListOptions{ 615 LabelSelector: labelSelector, 616 }) 617 defer watch.Stop() 618 if err != nil { 619 returnChannel <- err 620 return 621 } 622 replicas := sfs.Spec.Replicas 623 624 returnChannel <- waitForPodsPhase(watch, int(*replicas), core.PodRunning, containerCheck, readyCheck) 625 }() 626 627 select { 628 case res := <-returnChannel: 629 return res 630 case <-time.After(timeout): 631 return errors.New("timeout while waiting for job to complete") 632 } 633 } 634 635 func (c *Client) ApplySecret(rsc *core.Secret) error { 636 resources := c.set.CoreV1().Secrets(rsc.GetNamespace()) 637 return c.applyResource("secret", rsc.GetName(), func() error { 638 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 639 return err 640 }, func() error { 641 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 642 return err 643 }) 644 } 645 func (c *Client) GetSecret(namespace string, name string) (*core.Secret, error) { 646 return c.set.CoreV1().Secrets(namespace).Get(context.Background(), name, mach.GetOptions{}) 647 } 648 649 func (c *Client) WaitForSecret(namespace string, name string, timeout time.Duration) error { 650 ctx := context.Background() 651 return await( 652 timeout, 653 func() (interface{}, error) { 654 return c.set.CoreV1().Secrets(namespace).Get(ctx, name, mach.GetOptions{}) 655 }, 656 ) 657 } 658 659 func await(timeout time.Duration, getResource func() (interface{}, error)) error { 660 ticker := time.NewTicker(time.Second) 661 defer ticker.Stop() 662 timer := time.NewTimer(timeout) 663 defer timer.Stop() 664 for { 665 select { 666 case <-ticker.C: 667 resource, err := getResource() 668 if err != nil && !macherrs.IsNotFound(err) { 669 return err 670 } else if resource != nil { 671 return nil 672 } 673 674 case <-timer.C: 675 return errors.New("timeout while waiting for secret to be created") 676 } 677 } 678 } 679 680 func (c *Client) DeleteSecret(namespace, name string) error { 681 return notFoundIsSuccess(c.set.CoreV1().Secrets(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 682 } 683 684 func (c *Client) GetConfigMap(namespace, name string) (*core.ConfigMap, error) { 685 return c.set.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, mach.GetOptions{}) 686 } 687 688 func (c *Client) ApplyConfigmap(rsc *core.ConfigMap) error { 689 resources := c.set.CoreV1().ConfigMaps(rsc.GetNamespace()) 690 return c.applyResource("secret", rsc.GetName(), func() error { 691 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 692 return err 693 }, func() error { 694 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 695 return err 696 }) 697 } 698 699 func (c *Client) DeleteConfigmap(namespace, name string) error { 700 return notFoundIsSuccess(c.set.CoreV1().ConfigMaps(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 701 } 702 703 func (c *Client) ApplyServiceAccount(rsc *core.ServiceAccount) error { 704 resources := c.set.CoreV1().ServiceAccounts(rsc.Namespace) 705 return c.applyResource("serviceaccount", rsc.GetName(), func() error { 706 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 707 return err 708 }, func() error { 709 sa, err := resources.Get(context.Background(), rsc.GetName(), mach.GetOptions{}) 710 if err != nil { 711 return err 712 } 713 714 different := false 715 //workaround as 1 token will always be created by kubeapi 716 if (!(sa.Secrets != nil && len(sa.Secrets) == 1 && (rsc.Secrets == nil || len(rsc.Secrets) == 0)) && (!reflect.DeepEqual(sa.Secrets, rsc.Secrets))) || 717 (sa.ImagePullSecrets != nil && rsc.ImagePullSecrets != nil && !reflect.DeepEqual(sa.ImagePullSecrets, rsc.ImagePullSecrets)) || 718 (sa.AutomountServiceAccountToken != nil && rsc.AutomountServiceAccountToken != nil && *sa.AutomountServiceAccountToken != *rsc.AutomountServiceAccountToken) { 719 different = true 720 } 721 722 if different && 723 sa.GetName() == rsc.GetName() && 724 sa.GetNamespace() == rsc.GetNamespace() { 725 726 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 727 return err 728 } 729 return nil 730 }) 731 } 732 733 func (c *Client) WaitForConfigMap(namespace string, name string, timeout time.Duration) error { 734 ctx := context.Background() 735 return await( 736 timeout, 737 func() (interface{}, error) { 738 return c.set.CoreV1().ConfigMaps(namespace).Get(ctx, name, mach.GetOptions{}) 739 }, 740 ) 741 } 742 743 func (c *Client) DeleteServiceAccount(namespace, name string) error { 744 return notFoundIsSuccess(c.set.CoreV1().ServiceAccounts(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 745 } 746 747 func (c *Client) ApplyIngress(rsc *extensions.Ingress) error { 748 resources := c.set.ExtensionsV1beta1().Ingresses(rsc.GetNamespace()) 749 return c.applyResource("ingress", rsc.GetName(), func() error { 750 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 751 return err 752 }, func() error { 753 svc, err := resources.Get(context.Background(), rsc.Name, mach.GetOptions{}) 754 if err != nil { 755 return err 756 } 757 rsc.ObjectMeta.ResourceVersion = svc.ObjectMeta.ResourceVersion 758 _, err = resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 759 return err 760 }) 761 } 762 763 func (c *Client) DeleteIngress(namespace, name string) error { 764 return notFoundIsSuccess(c.set.ExtensionsV1beta1().Ingresses(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 765 } 766 767 func (c *Client) ApplyRole(rsc *rbac.Role) error { 768 resources := c.set.RbacV1().Roles(rsc.Namespace) 769 return c.applyResource("role", rsc.GetName(), func() error { 770 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 771 return err 772 }, func() error { 773 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 774 return err 775 }) 776 } 777 func (c *Client) DeleteRole(namespace, name string) error { 778 return notFoundIsSuccess(c.set.RbacV1().Roles(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 779 } 780 781 func (c *Client) ApplyClusterRole(rsc *rbac.ClusterRole) error { 782 resources := c.set.RbacV1().ClusterRoles() 783 return c.applyResource("clusterrole", rsc.GetName(), func() error { 784 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 785 return err 786 }, func() error { 787 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 788 return err 789 }) 790 } 791 792 func (c *Client) DeleteClusterRole(name string) error { 793 return notFoundIsSuccess(c.set.RbacV1().ClusterRoles().Delete(context.Background(), name, mach.DeleteOptions{})) 794 } 795 796 func (c *Client) ApplyRoleBinding(rsc *rbac.RoleBinding) error { 797 resources := c.set.RbacV1().RoleBindings(rsc.Namespace) 798 return c.applyResource("rolebinding", rsc.GetName(), func() error { 799 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 800 return err 801 }, func() error { 802 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 803 return err 804 }) 805 } 806 807 func (c *Client) DeleteRoleBinding(namespace, name string) error { 808 return notFoundIsSuccess(c.set.RbacV1().RoleBindings(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 809 } 810 811 func (c *Client) ApplyClusterRoleBinding(rsc *rbac.ClusterRoleBinding) error { 812 resources := c.set.RbacV1().ClusterRoleBindings() 813 return c.applyResource("clusterrolebinding", rsc.GetName(), func() error { 814 _, err := resources.Create(context.Background(), rsc, mach.CreateOptions{}) 815 return err 816 }, func() error { 817 _, err := resources.Update(context.Background(), rsc, mach.UpdateOptions{}) 818 return err 819 }) 820 } 821 822 func (c *Client) DeleteClusterRoleBinding(name string) error { 823 return notFoundIsSuccess(c.set.RbacV1().ClusterRoleBindings().Delete(context.Background(), name, mach.DeleteOptions{})) 824 } 825 826 type recreateErr struct{} 827 828 func (r *recreateErr) Error() string { return "recreate" } 829 830 func (c *Client) applyResource(object, name string, create func() error, update func() error) (err error) { 831 832 defer func() { 833 if err != nil { 834 err = fmt.Errorf("applying %s %s failed: %w", object, name, err) 835 } 836 }() 837 838 err = update() 839 _, recreate := err.(*recreateErr) 840 if err == nil || (!macherrs.IsNotFound(err) && !recreate) { 841 return err 842 } 843 return create() 844 } 845 846 func (c *Client) applyController( 847 controllerType string, 848 force bool, 849 newNameLabels *labels.Name, 850 newSelector *labels.Selector, 851 getCurrentSelectorFunc func() (*labels.Selector, error), 852 createResource func() error, 853 updateResource func() error, 854 deleteResource func() error) error { 855 856 return c.applyResource( 857 controllerType, 858 newNameLabels.Name(), 859 createResource, 860 func() error { 861 862 currentSelector, err := getCurrentSelectorFunc() 863 if err != nil { 864 return err 865 } 866 867 if newSelector.Equal(currentSelector) { 868 return updateResource() 869 } 870 871 if !force { 872 return fmt.Errorf("only recreating %s when force is true", controllerType) 873 } 874 875 if err := deleteResource(); err != nil { 876 return err 877 } 878 return &recreateErr{} 879 }, 880 ) 881 } 882 883 func restCfgFromContent(bytes []byte) (cfg *rest.Config, err error) { 884 defer func() { 885 if err != nil { 886 err = fmt.Errorf("creating a kubernetes client from bytes failed: %w", err) 887 } 888 }() 889 890 clientCfg, err := clientcmd.NewClientConfigFromBytes(bytes) 891 if err != nil { 892 return nil, err 893 } 894 895 return clientCfg.ClientConfig() 896 } 897 898 func (c *Client) init(kubeconfig *string, kubeconfigPath string) (err error) { 899 defer func() { 900 if err != nil { 901 err = fmt.Errorf("refreshing Kubernetes client failed: %w", err) 902 } 903 }() 904 905 restCfg := new(rest.Config) 906 if kubeconfig != nil && *kubeconfig != "" { 907 c.monitor.WithField("content", *kubeconfig).Debug("trying kubeconfig from in-memory content") 908 restCfg, err = restCfgFromContent([]byte(*kubeconfig)) 909 if err != nil { 910 return err 911 } 912 } else { 913 914 var inClusterErr error 915 restCfg, inClusterErr = rest.InClusterConfig() 916 if inClusterErr == nil { 917 return c.refreshAllClients(restCfg) 918 } 919 920 localKubeconfigContent, localKubeconfigErr := ioutil.ReadFile(kubeconfigPath) 921 if localKubeconfigErr != nil { 922 return fmt.Errorf("can't create kubernetes client: in-cluster err: %w: local kubeconfig err: %s", inClusterErr, localKubeconfigErr) 923 } 924 925 restCfg, localKubeconfigErr = restCfgFromContent(localKubeconfigContent) 926 if localKubeconfigErr != nil { 927 return fmt.Errorf("can't create kubernetes client: in-cluster err: %w: local kubeconfig err: %s", inClusterErr, localKubeconfigErr) 928 } 929 } 930 931 return c.refreshAllClients(restCfg) 932 } 933 934 func (c *Client) initConfig(config *rest.Config) (err error) { 935 return c.refreshAllClients(config) 936 } 937 938 func (c *Client) refreshAllClients(config *rest.Config) (err error) { 939 defer func() { 940 if err != nil { 941 err = fmt.Errorf("refreshing all kubernetes clients failed: %w", err) 942 } 943 }() 944 945 c.restConfig = config 946 947 set, err := kubernetes.NewForConfig(config) 948 if err != nil { 949 return fmt.Errorf("creating Clientset failed: %w", err) 950 } 951 c.set = set 952 953 apixv1beta1clientC, err := apixv1beta1client.NewForConfig(config) 954 if err != nil { 955 return fmt.Errorf("creating ApiextensionsV1beta1Client failed: %w", err) 956 } 957 c.apixv1beta1client = apixv1beta1clientC 958 959 dynamicC, err := dynamic.NewForConfig(config) 960 if err != nil { 961 return fmt.Errorf("creating dynamic client failed: %w", err) 962 } 963 c.dynamic = dynamicC 964 965 dc, err := discovery.NewDiscoveryClientForConfig(config) 966 if err != nil { 967 return fmt.Errorf("creating DiscoveryClient failed: %w", err) 968 } 969 c.mapper = restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc)) 970 971 return c.checkConnectivity() 972 } 973 974 func (c *Client) GetNode(id string) (node *core.Node, err error) { 975 return c.nodeApi().Get(context.Background(), id, mach.GetOptions{}) 976 } 977 978 func (c *Client) ListNodes(filterID ...string) (nodes []core.Node, err error) { 979 defer func() { 980 if err != nil { 981 err = fmt.Errorf("listing nodes %s failed: %w", strings.Join(filterID, ", "), err) 982 } 983 }() 984 985 labelSelector := "" 986 for _, id := range filterID { 987 labelSelector = fmt.Sprintf("%s,name=%s", labelSelector, id) 988 } 989 if len(labelSelector) > 0 { 990 labelSelector = labelSelector[1:] 991 } 992 993 nodeList, err := c.nodeApi().List(context.Background(), mach.ListOptions{ 994 LabelSelector: labelSelector, 995 }) 996 997 if err != nil { 998 return nil, err 999 } 1000 return nodeList.Items, nil 1001 } 1002 1003 func (c *Client) UpdateNode(node *core.Node) (err error) { 1004 1005 defer func() { 1006 if err != nil { 1007 err = fmt.Errorf("updating node %s failed: %w", node.GetName(), err) 1008 } 1009 }() 1010 1011 node.ResourceVersion = "" 1012 _, err = c.nodeApi().Update(context.Background(), node, mach.UpdateOptions{}) 1013 return err 1014 } 1015 1016 type DrainReason int 1017 1018 const ( 1019 Updating DrainReason = iota 1020 Rebooting 1021 Deleting 1022 ) 1023 1024 func (c *Client) cordon(node *core.Node, reason DrainReason) (err error) { 1025 defer func() { 1026 if err != nil { 1027 err = fmt.Errorf("cordoning node %s failed: %w", node.GetName(), err) 1028 } 1029 }() 1030 1031 monitor := c.monitor.WithFields(map[string]interface{}{ 1032 "machine": node.GetName(), 1033 }) 1034 monitor.Info("Cordoning node") 1035 1036 if c.Tainted(node, reason) { 1037 return nil 1038 } 1039 node.Spec.Taints = append(node.Spec.Taints, core.Taint{ 1040 Key: TaintKeyPrefix + reason.String(), 1041 Effect: "NoSchedule", 1042 }) 1043 1044 if err := c.UpdateNode(node); err != nil { 1045 return err 1046 } 1047 monitor.Info("Node cordoned") 1048 return nil 1049 } 1050 1051 type Machine interface { 1052 GetUpdating() bool 1053 SetUpdating(bool) 1054 GetJoined() bool 1055 SetJoined(bool) 1056 } 1057 1058 func (c *Client) Tainted(node *core.Node, reason DrainReason) bool { 1059 return c.tainted(node.Spec.Taints, reason) != -1 1060 } 1061 1062 func (c *Client) tainted(taints []core.Taint, reason DrainReason) int { 1063 1064 for idx, taint := range taints { 1065 if taint.Key == TaintKeyPrefix+reason.String() { 1066 return idx 1067 } 1068 } 1069 return -1 1070 } 1071 1072 func (c *Client) RemoveFromTaints(taints []core.Taint, reason DrainReason) (result []core.Taint) { 1073 idx := c.tainted(taints, reason) 1074 if idx < 0 { 1075 return taints 1076 } 1077 return append(taints[0:idx], taints[idx+1:]...) 1078 } 1079 1080 func (c *Client) Drain(machine Machine, node *core.Node, reason DrainReason, self bool) (err error) { 1081 defer func() { 1082 if err != nil { 1083 err = fmt.Errorf("draining node %s failed: %w", node.GetName(), err) 1084 } 1085 }() 1086 1087 monitor := c.monitor.WithFields(map[string]interface{}{ 1088 "machine": node.GetName(), 1089 }) 1090 monitor.Info("Draining node") 1091 1092 if err = c.cordon(node, reason); err != nil { 1093 return err 1094 } 1095 1096 if err := c.evictPods(node, self); err != nil { 1097 return err 1098 } 1099 if !machine.GetUpdating() { 1100 machine.SetUpdating(true) 1101 monitor.Changed("Node drained") 1102 } 1103 return nil 1104 } 1105 1106 func (c *Client) DeleteNode(name string) error { 1107 return notFoundIsSuccess(c.set.CoreV1().Nodes().Delete(context.Background(), name, mach.DeleteOptions{})) 1108 } 1109 1110 func (c *Client) evictPods(node *core.Node, self bool) (err error) { 1111 1112 defer func() { 1113 if err != nil { 1114 err = fmt.Errorf("evicting pods from node %s failed: %w", node.GetName(), err) 1115 } 1116 }() 1117 1118 monitor := c.monitor.WithFields(map[string]interface{}{ 1119 "machine": node.GetName(), 1120 }) 1121 1122 monitor.Info("Evicting pods") 1123 1124 fieldSelector := fmt.Sprintf("spec.nodeName=%s,status.phase=Running", node.Name) // possibly selects self too 1125 var labelSelector string 1126 if !self { 1127 labelSelector = "app.kubernetes.io/name!=orbiter" // exclude self 1128 } 1129 podItems, err := c.set.CoreV1().Pods("").List(context.Background(), mach.ListOptions{ 1130 FieldSelector: fieldSelector, 1131 LabelSelector: labelSelector, 1132 }) 1133 if err != nil { 1134 return fmt.Errorf("listing pods with field-selector %s and label-selector %s failed: %w", fieldSelector, labelSelector, err) 1135 } 1136 1137 // --ignore-daemonsets 1138 var pods []core.Pod 1139 for i := range podItems.Items { 1140 pod := podItems.Items[i] 1141 controllerRef := mach.GetControllerOf(&pod) 1142 if controllerRef == nil || controllerRef.Kind != apps.SchemeGroupVersion.WithKind("DaemonSet").Kind { 1143 pods = append(pods, pod) 1144 } 1145 } 1146 1147 var wg sync.WaitGroup 1148 synchronizer := helpers.NewSynchronizer(&wg) 1149 1150 for _, p := range pods { 1151 wg.Add(1) 1152 go func(pod core.Pod) { 1153 1154 var gracePeriodSeconds int64 = 60 1155 monitor := c.monitor.WithFields(map[string]interface{}{ 1156 "pod": pod.GetName(), 1157 "namespace": pod.GetNamespace(), 1158 }) 1159 monitor.Debug("Evicting pod") 1160 1161 watcher, goErr := c.set.CoreV1().Pods(pod.Namespace).Watch(context.Background(), mach.ListOptions{ 1162 FieldSelector: fmt.Sprintf("metadata.name=%s", pod.Name), 1163 }) 1164 if goErr != nil { 1165 synchronizer.Done(nil) 1166 return 1167 } 1168 defer watcher.Stop() 1169 1170 if goErr := c.set.PolicyV1beta1().Evictions(pod.Namespace).Evict(context.Background(), &policy.Eviction{ 1171 TypeMeta: mach.TypeMeta{ 1172 Kind: "EvictionKind", 1173 APIVersion: c.set.PolicyV1beta1().RESTClient().APIVersion().String(), 1174 }, 1175 ObjectMeta: mach.ObjectMeta{ 1176 Name: pod.Name, 1177 Namespace: pod.Namespace, 1178 }, 1179 DeleteOptions: &mach.DeleteOptions{ 1180 GracePeriodSeconds: &gracePeriodSeconds, 1181 }, 1182 }); goErr != nil { 1183 synchronizer.Done(fmt.Errorf("evicting pod %s failed: %w", pod.Name, goErr)) 1184 return 1185 } 1186 monitor.Debug("Watching pod") 1187 1188 timeout := time.After(time.Duration(safeUint64(pod.Spec.TerminationGracePeriodSeconds)) + 30) 1189 for { 1190 select { 1191 case event := <-watcher.ResultChan(): 1192 wPod, ok := event.Object.(*core.Pod) 1193 if !ok { 1194 continue 1195 } 1196 monitor = monitor.WithFields(map[string]interface{}{ 1197 "event": event.Type, 1198 }) 1199 monitor.Debug("Pod event happened") 1200 if event.Type == watch.Deleted { 1201 monitor.WithFields(map[string]interface{}{ 1202 "new_node": wPod.Spec.NodeName, 1203 }).Debug("Pod evicted") 1204 synchronizer.Done(nil) 1205 return 1206 } 1207 case <-timeout: 1208 1209 delErr := c.set.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, mach.DeleteOptions{}) 1210 if delErr != nil { 1211 delErr = fmt.Errorf("deleting pod %s after timout exceeded failed: %w", pod.Name, delErr) 1212 } 1213 synchronizer.Done(notFoundIsSuccess(delErr)) 1214 return 1215 } 1216 } 1217 }(p) 1218 } 1219 wg.Wait() 1220 1221 if synchronizer.IsError() { 1222 return fmt.Errorf("concurrently evicting pods from node %s failed: %w", node.Name, synchronizer) 1223 } 1224 1225 monitor.Info("Pods evicted") 1226 return nil 1227 } 1228 1229 func safeUint64(ptr *int64) int64 { 1230 if ptr == nil { 1231 return 0 1232 } 1233 return *ptr 1234 } 1235 func (c *Client) DeletePodsByLabels(namespace string, labels map[string]string) error { 1236 return notFoundIsSuccess(c.set.CoreV1().Pods(namespace).DeleteCollection(context.Background(), mach.DeleteOptions{}, mach.ListOptions{ 1237 LabelSelector: getLabelSelector(labels), 1238 })) 1239 } 1240 1241 func (c *Client) DeletePod(namespace, name string) error { 1242 return notFoundIsSuccess(c.set.CoreV1().Pods(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 1243 } 1244 1245 func getLabelSelector(labels map[string]string) string { 1246 labelSelector := "" 1247 for k, v := range labels { 1248 if labelSelector == "" { 1249 labelSelector = k + "=" + v 1250 } else { 1251 labelSelector = labelSelector + ", " + k + "=" + v 1252 1253 } 1254 } 1255 return labelSelector 1256 } 1257 1258 type podStatus struct { 1259 name string 1260 phase core.PodPhase 1261 containerStatuses []containerStatus 1262 } 1263 type containerStatus struct { 1264 containerState core.ContainerStatus 1265 } 1266 1267 func waitForPodsPhase(watch watch.Interface, replicaCount int, phase core.PodPhase, containerCheck bool, readyCheck bool) error { 1268 statusChan := make(chan podStatus) 1269 pods := make(map[string]core.PodPhase, 0) 1270 1271 if watch == nil { 1272 return errors.New("no pods watched") 1273 } 1274 1275 go func() { 1276 for event := range watch.ResultChan() { 1277 p, ok := event.Object.(*core.Pod) 1278 if !ok { 1279 continue 1280 } 1281 containerStatuses := make([]containerStatus, 0) 1282 for _, podContainerStatus := range p.Status.ContainerStatuses { 1283 containerStatuses = append(containerStatuses, containerStatus{ 1284 containerState: podContainerStatus, 1285 }) 1286 } 1287 1288 statusChan <- podStatus{p.Name, p.Status.Phase, containerStatuses} 1289 1290 } 1291 }() 1292 1293 runningPods := 0 1294 for runningPods < replicaCount { 1295 podStat := <-statusChan 1296 if podStat.phase == phase { 1297 running := true 1298 ready := true 1299 if containerCheck { 1300 running = checkContainer(podStat.containerStatuses) 1301 } 1302 if readyCheck { 1303 ready = checkReady(podStat.containerStatuses) 1304 } 1305 1306 if running && ready { 1307 pods[podStat.name] = podStat.phase 1308 } else { 1309 delete(pods, podStat.name) 1310 } 1311 } else { 1312 delete(pods, podStat.name) 1313 } 1314 runningPods = len(pods) 1315 } 1316 return nil 1317 } 1318 1319 func checkContainer(containers []containerStatus) bool { 1320 running := true 1321 for _, stat := range containers { 1322 if stat.containerState.State.Running == nil { 1323 running = false 1324 } 1325 } 1326 return running 1327 } 1328 1329 func checkReady(containers []containerStatus) bool { 1330 ready := true 1331 for _, stat := range containers { 1332 if !stat.containerState.Ready { 1333 ready = false 1334 } 1335 } 1336 return ready 1337 } 1338 1339 func (c *Client) CheckCRD(name string) (*apixv1beta1.CustomResourceDefinition, bool, error) { 1340 crds := c.apixv1beta1client.CustomResourceDefinitions() 1341 crd, err := crds.Get(context.Background(), name, mach.GetOptions{}) 1342 if macherrs.IsNotFound(err) { 1343 return nil, false, nil 1344 } 1345 return crd, true, err 1346 } 1347 1348 func (c *Client) GetNamespacedCRDResource(group, version, kind, namespace, name string) (*unstructured.Unstructured, error) { 1349 mapping, err := c.mapper.RESTMapping(schema.GroupKind{ 1350 Group: group, 1351 Kind: kind, 1352 }, version) 1353 if err != nil { 1354 return nil, err 1355 } 1356 resource := c.dynamic.Resource(mapping.Resource).Namespace(namespace) 1357 1358 return resource.Get(context.Background(), name, mach.GetOptions{}) 1359 } 1360 1361 func (c *Client) ApplyNamespacedCRDResource(group, version, kind, namespace, name string, crd *unstructured.Unstructured) error { 1362 mapping, err := c.mapper.RESTMapping(schema.GroupKind{ 1363 Group: group, 1364 Kind: kind, 1365 }, version) 1366 if err != nil { 1367 return err 1368 } 1369 1370 resources := c.dynamic.Resource(mapping.Resource).Namespace(namespace) 1371 existing, err := resources.Get(context.Background(), name, mach.GetOptions{}) 1372 if err != nil && !macherrs.IsNotFound(err) { 1373 return fmt.Errorf("getting existing crd %s of kind %s failed: %w", name, kind, err) 1374 } 1375 update := func() error { 1376 return err 1377 } 1378 if err == nil { 1379 crd.SetResourceVersion(existing.GetResourceVersion()) 1380 update = func() error { 1381 _, err := resources.Update(context.Background(), crd, mach.UpdateOptions{}) 1382 return err 1383 } 1384 } 1385 1386 return c.applyResource("crd", name, func() error { 1387 _, err := resources.Create(context.Background(), crd, mach.CreateOptions{}) 1388 return err 1389 }, update) 1390 } 1391 1392 func (c *Client) DeleteNamespacedCRDResource(group, version, kind, namespace, name string) error { 1393 mapping, err := c.mapper.RESTMapping(schema.GroupKind{ 1394 Group: group, 1395 Kind: kind, 1396 }, version) 1397 if err != nil { 1398 if _, ok := err.(*meta.NoKindMatchError); ok { 1399 return nil 1400 } 1401 return err 1402 } 1403 1404 return notFoundIsSuccess(c.dynamic.Resource(mapping.Resource).Namespace(namespace).Delete(context.Background(), name, mach.DeleteOptions{})) 1405 } 1406 1407 func (c *Client) ApplyCRDResource(crd *unstructured.Unstructured) error { 1408 kind := crd.Object["kind"].(string) 1409 apiVersion := strings.Split(crd.Object["apiVersion"].(string), "/") 1410 metadata := crd.Object["metadata"].(map[string]interface{}) 1411 name := metadata["name"].(string) 1412 1413 apiVersionGroup := "" 1414 apiVersionVersion := "" 1415 switch len(apiVersion) { 1416 case 2: 1417 apiVersionGroup = apiVersion[0] 1418 apiVersionVersion = apiVersion[1] 1419 case 1: 1420 apiVersionVersion = apiVersion[0] 1421 default: 1422 return fmt.Errorf("") 1423 } 1424 1425 mapping, err := c.mapper.RESTMapping(schema.GroupKind{ 1426 Group: apiVersionGroup, 1427 Kind: kind, 1428 }, apiVersionVersion) 1429 if err != nil { 1430 return err 1431 } 1432 1433 var resources dynamic.ResourceInterface 1434 clusterResources := c.dynamic.Resource(mapping.Resource) 1435 resources = clusterResources 1436 namespace, ok := metadata["namespace"] 1437 if ok && namespace.(string) != "" { 1438 resources = clusterResources.Namespace(namespace.(string)) 1439 } 1440 1441 existing, err := resources.Get(context.Background(), name, mach.GetOptions{}) 1442 if err != nil && !macherrs.IsNotFound(err) { 1443 return fmt.Errorf("getting existing crd %s of kind %s failed: %w", name, kind, err) 1444 } 1445 if err == nil { 1446 crd.SetResourceVersion(existing.GetResourceVersion()) 1447 } 1448 err = nil 1449 1450 return c.applyResource("crd", name, func() error { 1451 _, err := resources.Create(context.Background(), crd, mach.CreateOptions{}) 1452 return err 1453 }, func() error { 1454 _, err := resources.Update(context.Background(), crd, mach.UpdateOptions{}) 1455 return err 1456 }) 1457 } 1458 1459 func (c *Client) DeleteCRDResource(group, version, kind, name string) error { 1460 mapping, err := c.mapper.RESTMapping(schema.GroupKind{ 1461 Group: group, 1462 Kind: kind, 1463 }, version) 1464 if err != nil { 1465 return err 1466 } 1467 1468 return notFoundIsSuccess(c.dynamic.Resource(mapping.Resource).Delete(context.Background(), name, mach.DeleteOptions{})) 1469 } 1470 1471 func (c *Client) ExecInPodOfDeployment(namespace, name, container, command string) error { 1472 cmd := []string{ 1473 "sh", 1474 "-c", 1475 command, 1476 } 1477 deploy, err := c.GetDeployment(namespace, name) 1478 if err != nil { 1479 return err 1480 } 1481 1482 labelSelector := "" 1483 for k, v := range deploy.Spec.Selector.MatchLabels { 1484 if labelSelector == "" { 1485 labelSelector = fmt.Sprintf("%s=%s", k, v) 1486 } else { 1487 labelSelector = fmt.Sprintf("%s, %s=%s", labelSelector, k, v) 1488 } 1489 } 1490 list, err := c.set.CoreV1().Pods(namespace).List(context.Background(), mach.ListOptions{ 1491 LabelSelector: labelSelector, 1492 }) 1493 if err != nil { 1494 return err 1495 } 1496 1497 firstPod := list.Items[0] 1498 req := c.set.CoreV1().RESTClient().Post().Resource("pods").Namespace(namespace).Name(firstPod.Name).SubResource("exec") 1499 return c.execInPod(cmd, container, req) 1500 } 1501 func (c *Client) ExecInPod(namespace, name, container, command string) error { 1502 cmd := []string{ 1503 "sh", 1504 "-c", 1505 command, 1506 } 1507 1508 req := c.set.CoreV1().RESTClient().Post().Resource("pods").Namespace(namespace).Name(name).SubResource("exec") 1509 return c.execInPod(cmd, container, req) 1510 } 1511 1512 func (c *Client) ExecInPodWithOutput(namespace, name, container, command string) (string, error) { 1513 cmd := []string{ 1514 "sh", 1515 "-c", 1516 command, 1517 } 1518 1519 req := c.set.CoreV1().RESTClient().Post().Resource("pods").Namespace(namespace).Name(name).SubResource("exec") 1520 return c.execInPodWithOutput(cmd, container, req) 1521 } 1522 1523 func (c *Client) execInPod(cmd []string, container string, req *rest.Request) error { 1524 option := &core.PodExecOptions{ 1525 Command: cmd, 1526 Stdin: false, 1527 Stdout: true, 1528 Stderr: true, 1529 TTY: false, 1530 Container: container, 1531 } 1532 req.VersionedParams( 1533 option, 1534 scheme.ParameterCodec, 1535 ) 1536 1537 exec, err := remotecommand.NewSPDYExecutor(c.restConfig, "POST", req.URL()) 1538 if err != nil { 1539 return err 1540 } 1541 1542 var stdout, stderr bytes.Buffer 1543 err = exec.Stream(remotecommand.StreamOptions{ 1544 Stdin: nil, 1545 Stdout: &stdout, 1546 Stderr: &stderr, 1547 Tty: true, 1548 }) 1549 if err != nil { 1550 return err 1551 } 1552 1553 errStr := stderr.Bytes() 1554 if len(errStr) > 0 { 1555 return errors.New(string(errStr)) 1556 } 1557 1558 return nil 1559 } 1560 1561 func (c *Client) execInPodWithOutput(cmd []string, container string, req *rest.Request) (string, error) { 1562 option := &core.PodExecOptions{ 1563 Command: cmd, 1564 Stdin: false, 1565 Stdout: true, 1566 Stderr: true, 1567 TTY: false, 1568 Container: container, 1569 } 1570 req.VersionedParams( 1571 option, 1572 scheme.ParameterCodec, 1573 ) 1574 1575 exec, err := remotecommand.NewSPDYExecutor(c.restConfig, "POST", req.URL()) 1576 if err != nil { 1577 return "", err 1578 } 1579 1580 var stdout, stderr bytes.Buffer 1581 err = exec.Stream(remotecommand.StreamOptions{ 1582 Stdin: nil, 1583 Stdout: &stdout, 1584 Stderr: &stderr, 1585 Tty: true, 1586 }) 1587 if err != nil { 1588 return "", err 1589 } 1590 1591 errStr := stderr.Bytes() 1592 if len(errStr) > 0 { 1593 return "", errors.New(string(errStr)) 1594 } 1595 1596 outData := stdout.Bytes() 1597 return string(outData), nil 1598 } 1599 1600 func notFoundIsSuccess(err error) error { 1601 if macherrs.IsNotFound(err) { 1602 return nil 1603 } 1604 return err 1605 } 1606 1607 func (c *Client) ApplyPlainYAML(monitor mntr.Monitor, data []byte) error { 1608 utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(data))) 1609 1610 return forEachObjectInYAML(monitor, data, c.ApplyCRDResource) 1611 } 1612 1613 // ForEachObjectInYAMLActionFunc is a function that is executed against each 1614 // object found in a YAML document. 1615 // When a non-empty namespace is provided then the object is assigned the 1616 // namespace prior to any other actions being performed with or to the object. 1617 // this is heavily ispired by https://github.com/kubernetes/client-go/issues/216#issuecomment-718813670 1618 type forEachObjectInYAMLActionFunc func(*unstructured.Unstructured) error 1619 1620 // ForEachObjectInYAML excutes actionFn for each object in the provided YAML. 1621 // If an error is returned then no further objects are processed. 1622 // The data may be a single YAML document or multidoc YAML. 1623 // When a non-empty namespace is provided then all objects are assigned the 1624 // the namespace prior to any other actions being performed with or to the 1625 // object. 1626 // this is heavily ispired by https://github.com/kubernetes/client-go/issues/216#issuecomment-718813670 1627 func forEachObjectInYAML( 1628 monitor mntr.Monitor, 1629 data []byte, 1630 actionFn forEachObjectInYAMLActionFunc) error { 1631 1632 chanObj, chanErr := decodeYAML(data) 1633 for { 1634 select { 1635 case obj := <-chanObj: 1636 if obj == nil { 1637 return nil 1638 } 1639 1640 if err := actionFn(obj); err != nil { 1641 return fmt.Errorf("running passed forEachObjectInYAMLActionFunc failed: %w", err) 1642 } 1643 monitor.WithFields(map[string]interface{}{ 1644 "kind": obj.GetKind(), 1645 "name": obj.GetName(), 1646 }).Debug("forEachObjectInYAMLActionFunc succeeded") 1647 case err := <-chanErr: 1648 if err == nil { 1649 return nil 1650 } 1651 return err 1652 } 1653 } 1654 } 1655 1656 // DecodeYAML unmarshals a YAML document or multidoc YAML as unstructured 1657 // objects, placing each decoded object into a channel. 1658 // this is heavily ispired by https://github.com/kubernetes/client-go/issues/216#issuecomment-718813670 1659 func decodeYAML(data []byte) (<-chan *unstructured.Unstructured, <-chan error) { 1660 1661 var ( 1662 chanErr = make(chan error) 1663 chanObj = make(chan *unstructured.Unstructured) 1664 multidocReader = utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(data))) 1665 ) 1666 1667 go func() { 1668 defer close(chanErr) 1669 defer close(chanObj) 1670 1671 // Iterate over the data until Read returns io.EOF. Every successful 1672 // read returns a complete YAML document. 1673 for { 1674 buf, err := multidocReader.Read() 1675 if err != nil { 1676 if err == io.EOF { 1677 return 1678 } 1679 chanErr <- fmt.Errorf("failed to read yaml data: %w", err) 1680 return 1681 } 1682 1683 // Define the unstructured object into which the YAML document will be 1684 // unmarshaled. 1685 obj := &unstructured.Unstructured{ 1686 Object: map[string]interface{}{}, 1687 } 1688 1689 // Unmarshal the YAML document into the unstructured object. 1690 if err := k8syaml.Unmarshal(buf, &obj.Object); err != nil { 1691 chanErr <- fmt.Errorf("failed to unmarshal yaml data: %w", err) 1692 return 1693 } 1694 1695 // filter non-kind resources 1696 if kind, ok := obj.Object["kind"]; !ok || kind == "" { 1697 continue 1698 } 1699 1700 // Place the unstructured object into the channel. 1701 chanObj <- obj 1702 } 1703 }() 1704 1705 return chanObj, chanErr 1706 }