Hello community,

here is the log from the commit of package kucero for openSUSE:Factory checked 
in at 2020-11-11 20:46:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kucero (Old)
 and      /work/SRC/openSUSE:Factory/.kucero.new.26437 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kucero"

Wed Nov 11 20:46:54 2020 rev:2 rq:847638 version:1.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/kucero/kucero.changes    2020-10-13 
15:46:04.389460778 +0200
+++ /work/SRC/openSUSE:Factory/.kucero.new.26437/kucero.changes 2020-11-11 
20:46:56.431677897 +0100
@@ -1,0 +2,6 @@
+Tue Nov 10 01:51:50 UTC 2020 - jenting hsiao <[email protected]>
+
+- Bump verison v1.4.0
+  * Removes kubectl command binary dependency.
+
+-------------------------------------------------------------------

Old:
----
  kucero-1.3.0.tar.gz

New:
----
  kucero-1.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kucero.spec ++++++
--- /var/tmp/diff_new_pack.sVXmVI/_old  2020-11-11 20:46:57.695679083 +0100
+++ /var/tmp/diff_new_pack.sVXmVI/_new  2020-11-11 20:46:57.699679087 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package kucero
 #
-# Copyright (c) 2020 SUSE LLC, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -15,16 +15,17 @@
 # Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
+
 %define goipath github.com/jenting/kucero
 
 Name:           kucero
-Version:        1.3.0
+Version:        1.4.0
 Release:        0
 Summary:        Kubernetes control plane certificate auto rotation
 License:        Apache-2.0
 Group:          System/Management
-URL:            https://github.com/jenting/kucero
-Source0:        %{name}-%{version}.tar.gz
+URL:            https://github.com/SUSE/kucero
+Source0:        
https://github.com/SUSE/kucero/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
 Source1:        vendor.tar.gz
 BuildRequires:  golang-packaging
 BuildRequires:  golang(API) = 1.14

++++++ kucero-1.3.0.tar.gz -> kucero-1.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/Dockerfile new/kucero-1.4.0/Dockerfile
--- old/kucero-1.3.0/Dockerfile 2020-09-29 04:57:56.000000000 +0200
+++ new/kucero-1.4.0/Dockerfile 2020-11-05 03:28:51.000000000 +0100
@@ -1,4 +1,4 @@
-FROM golang:1.14-stretch as build
+FROM golang:1.15-buster as build
 WORKDIR /src
 
 ARG VERSION=latest
@@ -8,7 +8,6 @@
     CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w -X 
main.version=${VERSION}" -o kucero cmd/kucero/*.go
 
 FROM opensuse/leap:15.2
-RUN zypper --non-interactive install kubernetes-client
 WORKDIR /usr/bin
 COPY --from=build /src/kucero .
 ENTRYPOINT ["/usr/bin/kucero"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/README.md new/kucero-1.4.0/README.md
--- old/kucero-1.3.0/README.md  2020-09-29 04:57:56.000000000 +0200
+++ new/kucero-1.4.0/README.md  2020-11-05 03:28:51.000000000 +0100
@@ -28,7 +28,6 @@
 
 ## Container Requirement Package
 
-- /usr/bin/kubectl
 - /usr/bin/nsenter
 
 ## Kubeadm Compatibility
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/cmd/kucero/main.go 
new/kucero-1.4.0/cmd/kucero/main.go
--- old/kucero-1.3.0/cmd/kucero/main.go 2020-09-29 04:57:56.000000000 +0200
+++ new/kucero-1.4.0/cmd/kucero/main.go 2020-11-05 03:28:51.000000000 +0100
@@ -162,7 +162,7 @@
                logrus.Infof("Kubelet CSR controller CA key: %s", caKeyPath)
        }
 
-       rotateCertificateWhenNeeded(nodeName, isControlPlaneNode, client)
+       rotateCertificateWhenNeeded(corev1Node, isControlPlaneNode, client)
 }
 
 // nodeMeta is used to remember information across nodes
@@ -171,7 +171,8 @@
        Unschedulable bool `json:"unschedulable"`
 }
 
-func rotateCertificateWhenNeeded(nodeName string, isControlPlaneNode bool, 
client *kubernetes.Clientset) {
+func rotateCertificateWhenNeeded(corev1Node *corev1.Node, isControlPlaneNode 
bool, client *kubernetes.Clientset) {
+       nodeName := corev1Node.GetName()
        certNode := node.New(isControlPlaneNode, nodeName, expiryTimeToRotate, 
enableKubeletClientCertRotation, enableKubeletServerCertRotation)
 
        lock := daemonsetlock.New(client, nodeName, dsNamespace, dsName, 
lockAnnotation)
@@ -247,8 +248,8 @@
                        // and try to acquire the lock again.
                        if (len(configsToBeUpdate) > 0 || len(expiryCerts) > 0) 
&& acquire(lock, &nodeMeta) {
                                if !nodeMeta.Unschedulable {
-                                       _ = host.Cordon(nodeName)
-                                       _ = host.Drain(nodeName)
+                                       _ = host.Cordon(client, corev1Node)
+                                       _ = host.Drain(client, corev1Node)
                                }
 
                                if len(configsToBeUpdate) > 0 {
@@ -272,7 +273,7 @@
                                }
 
                                if !nodeMeta.Unschedulable {
-                                       _ = host.Uncordon(nodeName)
+                                       _ = host.Uncordon(client, corev1Node)
                                }
 
                                release(lock)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/go.mod new/kucero-1.4.0/go.mod
--- old/kucero-1.3.0/go.mod     2020-09-29 04:57:56.000000000 +0200
+++ new/kucero-1.4.0/go.mod     2020-11-05 03:28:51.000000000 +0100
@@ -17,6 +17,7 @@
        k8s.io/apiserver v0.17.4
        k8s.io/client-go v0.17.4
        k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 // indirect
+       k8s.io/kubectl v0.17.4
        k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 // indirect
        sigs.k8s.io/controller-runtime v0.5.0
        sigs.k8s.io/yaml v1.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/go.sum new/kucero-1.4.0/go.sum
--- old/kucero-1.3.0/go.sum     2020-09-29 04:57:56.000000000 +0200
+++ new/kucero-1.4.0/go.sum     2020-11-05 03:28:51.000000000 +0100
@@ -11,6 +11,7 @@
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod 
h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/BurntSushi/toml v0.3.1/go.mod 
h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod 
h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod 
h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod 
h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod 
h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.0.0/go.mod 
h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -31,6 +32,7 @@
 github.com/bgentry/speakeasy v0.1.0/go.mod 
h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/blang/semver v3.5.0+incompatible/go.mod 
h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/cespare/xxhash v1.1.0/go.mod 
h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod 
h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
 github.com/client9/misspell v0.3.4/go.mod 
h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod 
h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/coreos/bbolt v1.3.2/go.mod 
h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -51,8 +53,10 @@
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod 
h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod 
h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod 
h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docker/distribution v2.7.1+incompatible/go.mod 
h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod 
h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-units v0.3.3/go.mod 
h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/go-units v0.4.0/go.mod 
h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -66,6 +70,8 @@
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod 
h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible 
h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod 
h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod 
h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
+github.com/fatih/camelcase v1.0.0/go.mod 
h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
 github.com/fatih/color v1.7.0/go.mod 
h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 
h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod 
h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -141,6 +147,9 @@
 github.com/golang/protobuf v1.3.1/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 
h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod 
h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
+github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod 
h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
+github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod 
h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod 
h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod 
h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod 
h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -203,6 +212,8 @@
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod 
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod 
h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
+github.com/lithammer/dedent v1.1.0/go.mod 
h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
 github.com/magiconair/properties v1.8.0/go.mod 
h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod 
h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod 
h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -216,6 +227,7 @@
 github.com/matttproud/golang_protobuf_extensions v1.0.1 
h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod 
h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod 
h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod 
h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/mapstructure v1.1.2/go.mod 
h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod 
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd 
h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -239,6 +251,7 @@
 github.com/onsi/gomega v1.7.0/go.mod 
h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
 github.com/onsi/gomega v1.8.1/go.mod 
h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod 
h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/pborman/uuid v1.2.0/go.mod 
h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod 
h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod 
h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -318,6 +331,7 @@
 github.com/weaveworks/kured v0.0.0-20200430160730-f2a0f8e20dbe 
h1:8GRZzx24E15rau+JK7fKJyKWAb/cUvsB05/l4mEVssk=
 github.com/weaveworks/kured v0.0.0-20200430160730-f2a0f8e20dbe/go.mod 
h1:s3eO4V98n+9KFlslYJIVTteTkyeFsSS/5+YRq0sEg8k=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod 
h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod 
h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod 
h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -485,12 +499,14 @@
 k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
 k8s.io/apiserver v0.17.4 h1:bYc9LvDPEF9xAL3fhbDzqNOQOAnNF2ZYCrMW8v52/mE=
 k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
+k8s.io/cli-runtime v0.17.4/go.mod 
h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=
 k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
 k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc=
 k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
 k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
 k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
 k8s.io/code-generator v0.17.2/go.mod 
h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
+k8s.io/code-generator v0.17.4/go.mod 
h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
 k8s.io/component-base v0.17.2/go.mod 
h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
 k8s.io/component-base v0.17.4/go.mod 
h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod 
h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
@@ -504,6 +520,9 @@
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod 
h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 
h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod 
h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
+k8s.io/kubectl v0.17.4 h1:Ts0CvqvIVceS4RTVXgWMH+YqtieLAzyS2T9eoz8uDQ0=
+k8s.io/kubectl v0.17.4/go.mod h1:im5QWmh6fvtmJkkNm4HToLe8z9aM3jihYK5X/wOybcY=
+k8s.io/metrics v0.17.4/go.mod h1:6rylW2iD3M9VppnEAAtJASY1XS8Pt9tcYh+tHxBeV3I=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod 
h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 
h1:Ly1Oxdu5p5ZFmiVT71LFgeZETvMfZ1iBIGeOenT2JeM=
 k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod 
h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@@ -514,6 +533,7 @@
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
 sigs.k8s.io/controller-runtime v0.5.0 
h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo=
 sigs.k8s.io/controller-runtime v0.5.0/go.mod 
h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8=
+sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod 
h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod 
h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 
h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod 
h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
@@ -521,3 +541,4 @@
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod 
h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kucero-1.3.0/pkg/host/kubectl.go 
new/kucero-1.4.0/pkg/host/kubectl.go
--- old/kucero-1.3.0/pkg/host/kubectl.go        2020-09-29 04:57:56.000000000 
+0200
+++ new/kucero-1.4.0/pkg/host/kubectl.go        2020-11-05 03:28:51.000000000 
+0100
@@ -17,47 +17,69 @@
 package host
 
 import (
+       "os"
+
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/client-go/kubernetes"
+       kubectldrain "k8s.io/kubectl/pkg/drain"
+
        "github.com/sirupsen/logrus"
 )
 
 // Uncordon executes `kubectl uncordon <node-name>`
 // on the host system
-func Uncordon(nodeName string) error {
+func Uncordon(client *kubernetes.Clientset, corev1Node *corev1.Node) error {
+       nodeName := corev1Node.GetName()
        logrus.Infof("Uncordoning %s node", nodeName)
 
-       cmd := NewCommand("/usr/bin/kubectl", "uncordon", nodeName)
-       err := cmd.Run()
-       if err != nil {
-               logrus.Errorf("Error invoking %s: %v", cmd.Args, err)
+       drainer := &kubectldrain.Helper{
+               Client: client,
+               Out:    os.Stdout,
+               ErrOut: os.Stderr,
        }
-
-       return err
+       // RunCordonOrUncordon runs either Cordon or Uncordon.
+       // The desired value "false" is passed to "Unschedulable" to indicate 
that the node is schedulable.
+       if err := kubectldrain.RunCordonOrUncordon(drainer, corev1Node, false); 
err != nil {
+               logrus.Errorf("Error uncordonning %s: %v", nodeName, err)
+       }
+       return nil
 }
 
 // Cordon executes `kubectl cordon <node-name>`
 // on the host system
-func Cordon(nodeName string) error {
+func Cordon(client *kubernetes.Clientset, corev1Node *corev1.Node) error {
+       nodeName := corev1Node.GetName()
        logrus.Infof("Cordoning %s node", nodeName)
 
-       cmd := NewCommand("/usr/bin/kubectl", "cordon", nodeName)
-       err := cmd.Run()
-       if err != nil {
-               logrus.Errorf("Error invoking %s: %v", cmd.Args, err)
+       drainer := &kubectldrain.Helper{
+               Client: client,
+               Out:    os.Stdout,
+               ErrOut: os.Stderr,
        }
-
-       return err
+       // RunCordonOrUncordon runs either Cordon or Uncordon.
+       // The desired value "true" is passed to "Unschedulable" to indicate 
that the node is unschedulable.
+       if err := kubectldrain.RunCordonOrUncordon(drainer, corev1Node, true); 
err != nil {
+               logrus.Errorf("Error cordonning %s: %v", nodeName, err)
+       }
+       return nil
 }
 
 // Drain executes `kubectl drain --ignore-daemonsets --delete-local-data 
--force <node-name>`
 // on the host system
-func Drain(nodeName string) error {
+func Drain(client *kubernetes.Clientset, corev1Node *corev1.Node) error {
+       nodeName := corev1Node.GetName()
        logrus.Infof("Draining %s node", nodeName)
 
-       cmd := NewCommand("/usr/bin/kubectl", "drain", "--ignore-daemonsets", 
"--delete-local-data", "--force", nodeName)
-       err := cmd.Run()
-       if err != nil {
-               logrus.Errorf("Error invoking %s: %v", cmd.Args, err)
+       drainer := &kubectldrain.Helper{
+               Client:              client,
+               Force:               true,
+               DeleteLocalData:     true,
+               IgnoreAllDaemonSets: true,
+               Out:                 os.Stdout,
+               ErrOut:              os.Stderr,
        }
-
-       return err
+       if err := kubectldrain.RunNodeDrain(drainer, nodeName); err != nil {
+               logrus.Errorf("Error draining %s: %v", nodeName, err)
+       }
+       return nil
 }

++++++ vendor.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/k8s.io/kubectl/LICENSE 
new/vendor/k8s.io/kubectl/LICENSE
--- old/vendor/k8s.io/kubectl/LICENSE   1970-01-01 01:00:00.000000000 +0100
+++ new/vendor/k8s.io/kubectl/LICENSE   2020-11-10 02:49:40.000000000 +0100
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/k8s.io/kubectl/pkg/drain/cordon.go 
new/vendor/k8s.io/kubectl/pkg/drain/cordon.go
--- old/vendor/k8s.io/kubectl/pkg/drain/cordon.go       1970-01-01 
01:00:00.000000000 +0100
+++ new/vendor/k8s.io/kubectl/pkg/drain/cordon.go       2020-11-10 
02:49:40.000000000 +0100
@@ -0,0 +1,95 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package drain
+
+import (
+       "fmt"
+
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+
+       "k8s.io/apimachinery/pkg/types"
+       "k8s.io/apimachinery/pkg/util/json"
+       "k8s.io/apimachinery/pkg/util/strategicpatch"
+       "k8s.io/client-go/kubernetes"
+)
+
+// CordonHelper wraps functionality to cordon/uncordon nodes
+type CordonHelper struct {
+       node    *corev1.Node
+       desired bool
+}
+
+// NewCordonHelper returns a new CordonHelper
+func NewCordonHelper(node *corev1.Node) *CordonHelper {
+       return &CordonHelper{
+               node: node,
+       }
+}
+
+// NewCordonHelperFromRuntimeObject returns a new CordonHelper, or an error if 
given object is not a
+// node or cannot be encoded as JSON
+func NewCordonHelperFromRuntimeObject(nodeObject runtime.Object, scheme 
*runtime.Scheme, gvk schema.GroupVersionKind) (*CordonHelper, error) {
+       nodeObject, err := scheme.ConvertToVersion(nodeObject, 
gvk.GroupVersion())
+       if err != nil {
+               return nil, err
+       }
+
+       node, ok := nodeObject.(*corev1.Node)
+       if !ok {
+               return nil, fmt.Errorf("unexpected type %T", nodeObject)
+       }
+
+       return NewCordonHelper(node), nil
+}
+
+// UpdateIfRequired returns true if c.node.Spec.Unschedulable isn't already 
set,
+// or false when no change is needed
+func (c *CordonHelper) UpdateIfRequired(desired bool) bool {
+       c.desired = desired
+
+       return c.node.Spec.Unschedulable != c.desired
+}
+
+// PatchOrReplace uses given clientset to update the node status, either by 
patching or
+// updating the given node object; it may return error if the object cannot be 
encoded as
+// JSON, or if either patch or update calls fail; it will also return a second 
error
+// whenever creating a patch has failed
+func (c *CordonHelper) PatchOrReplace(clientset kubernetes.Interface) (error, 
error) {
+       client := clientset.CoreV1().Nodes()
+
+       oldData, err := json.Marshal(c.node)
+       if err != nil {
+               return err, nil
+       }
+
+       c.node.Spec.Unschedulable = c.desired
+
+       newData, err := json.Marshal(c.node)
+       if err != nil {
+               return err, nil
+       }
+
+       patchBytes, patchErr := strategicpatch.CreateTwoWayMergePatch(oldData, 
newData, c.node)
+       if patchErr == nil {
+               _, err = client.Patch(c.node.Name, 
types.StrategicMergePatchType, patchBytes)
+       } else {
+               _, err = client.Update(c.node)
+       }
+       return err, patchErr
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/k8s.io/kubectl/pkg/drain/default.go 
new/vendor/k8s.io/kubectl/pkg/drain/default.go
--- old/vendor/k8s.io/kubectl/pkg/drain/default.go      1970-01-01 
01:00:00.000000000 +0100
+++ new/vendor/k8s.io/kubectl/pkg/drain/default.go      2020-11-10 
02:49:40.000000000 +0100
@@ -0,0 +1,69 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package drain
+
+import (
+       "fmt"
+
+       corev1 "k8s.io/api/core/v1"
+       utilerrors "k8s.io/apimachinery/pkg/util/errors"
+)
+
+// This file contains default implementations of how to
+// drain/cordon/uncordon nodes.  These functions may be called
+// directly, or their functionality copied into your own code, for
+// example if you want different output behaviour.
+
+// RunNodeDrain shows the canonical way to drain a node.
+// You should first cordon the node, e.g. using RunCordonOrUncordon
+func RunNodeDrain(drainer *Helper, nodeName string) error {
+       // TODO(justinsb): Ensure we have adequate e2e coverage of this 
function in library consumers
+       list, errs := drainer.GetPodsForDeletion(nodeName)
+       if errs != nil {
+               return utilerrors.NewAggregate(errs)
+       }
+       if warnings := list.Warnings(); warnings != "" {
+               fmt.Fprintf(drainer.ErrOut, "WARNING: %s\n", warnings)
+       }
+
+       if err := drainer.DeleteOrEvictPods(list.Pods()); err != nil {
+               // Maybe warn about non-deleted pods here
+               return err
+       }
+       return nil
+}
+
+// RunCordonOrUncordon demonstrates the canonical way to cordon or uncordon a 
Node
+func RunCordonOrUncordon(drainer *Helper, node *corev1.Node, desired bool) 
error {
+       // TODO(justinsb): Ensure we have adequate e2e coverage of this 
function in library consumers
+       c := NewCordonHelper(node)
+
+       if updateRequired := c.UpdateIfRequired(desired); !updateRequired {
+               // Already done
+               return nil
+       }
+
+       err, patchErr := c.PatchOrReplace(drainer.Client)
+       if patchErr != nil {
+               return patchErr
+       }
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/k8s.io/kubectl/pkg/drain/drain.go 
new/vendor/k8s.io/kubectl/pkg/drain/drain.go
--- old/vendor/k8s.io/kubectl/pkg/drain/drain.go        1970-01-01 
01:00:00.000000000 +0100
+++ new/vendor/k8s.io/kubectl/pkg/drain/drain.go        2020-11-10 
02:49:40.000000000 +0100
@@ -0,0 +1,308 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package drain
+
+import (
+       "context"
+       "fmt"
+       "io"
+       "math"
+       "time"
+
+       corev1 "k8s.io/api/core/v1"
+       policyv1beta1 "k8s.io/api/policy/v1beta1"
+       apierrors "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/fields"
+       "k8s.io/apimachinery/pkg/labels"
+       utilerrors "k8s.io/apimachinery/pkg/util/errors"
+       "k8s.io/apimachinery/pkg/util/wait"
+       "k8s.io/client-go/kubernetes"
+)
+
+const (
+       // EvictionKind represents the kind of evictions object
+       EvictionKind = "Eviction"
+       // EvictionSubresource represents the kind of evictions object as pod's 
subresource
+       EvictionSubresource = "pods/eviction"
+)
+
+// Helper contains the parameters to control the behaviour of drainer
+type Helper struct {
+       Client              kubernetes.Interface
+       Force               bool
+       GracePeriodSeconds  int
+       IgnoreAllDaemonSets bool
+       Timeout             time.Duration
+       DeleteLocalData     bool
+       Selector            string
+       PodSelector         string
+       Out                 io.Writer
+       ErrOut              io.Writer
+
+       // TODO(justinsb): unnecessary?
+       DryRun bool
+
+       // OnPodDeletedOrEvicted is called when a pod is evicted/deleted; for 
printing progress output
+       OnPodDeletedOrEvicted func(pod *corev1.Pod, usingEviction bool)
+}
+
+// CheckEvictionSupport uses Discovery API to find out if the server support
+// eviction subresource If support, it will return its groupVersion; Otherwise,
+// it will return an empty string
+func CheckEvictionSupport(clientset kubernetes.Interface) (string, error) {
+       discoveryClient := clientset.Discovery()
+       groupList, err := discoveryClient.ServerGroups()
+       if err != nil {
+               return "", err
+       }
+       foundPolicyGroup := false
+       var policyGroupVersion string
+       for _, group := range groupList.Groups {
+               if group.Name == "policy" {
+                       foundPolicyGroup = true
+                       policyGroupVersion = group.PreferredVersion.GroupVersion
+                       break
+               }
+       }
+       if !foundPolicyGroup {
+               return "", nil
+       }
+       resourceList, err := 
discoveryClient.ServerResourcesForGroupVersion("v1")
+       if err != nil {
+               return "", err
+       }
+       for _, resource := range resourceList.APIResources {
+               if resource.Name == EvictionSubresource && resource.Kind == 
EvictionKind {
+                       return policyGroupVersion, nil
+               }
+       }
+       return "", nil
+}
+
+func (d *Helper) makeDeleteOptions() *metav1.DeleteOptions {
+       deleteOptions := &metav1.DeleteOptions{}
+       if d.GracePeriodSeconds >= 0 {
+               gracePeriodSeconds := int64(d.GracePeriodSeconds)
+               deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
+       }
+       return deleteOptions
+}
+
+// DeletePod will delete the given pod, or return an error if it couldn't
+func (d *Helper) DeletePod(pod corev1.Pod) error {
+       return d.Client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, 
d.makeDeleteOptions())
+}
+
+// EvictPod will evict the give pod, or return an error if it couldn't
+func (d *Helper) EvictPod(pod corev1.Pod, policyGroupVersion string) error {
+       eviction := &policyv1beta1.Eviction{
+               TypeMeta: metav1.TypeMeta{
+                       APIVersion: policyGroupVersion,
+                       Kind:       EvictionKind,
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      pod.Name,
+                       Namespace: pod.Namespace,
+               },
+               DeleteOptions: d.makeDeleteOptions(),
+       }
+       // Remember to change change the URL manipulation func when Eviction's 
version change
+       return 
d.Client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(eviction)
+}
+
+// GetPodsForDeletion receives resource info for a node, and returns those 
pods as PodDeleteList,
+// or error if it cannot list pods. All pods that are ready to be deleted can 
be obtained with .Pods(),
+// and string with all warning can be obtained with .Warnings(), and .Errors() 
for all errors that
+// occurred during deletion.
+func (d *Helper) GetPodsForDeletion(nodeName string) (*podDeleteList, []error) 
{
+       labelSelector, err := labels.Parse(d.PodSelector)
+       if err != nil {
+               return nil, []error{err}
+       }
+
+       podList, err := 
d.Client.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{
+               LabelSelector: labelSelector.String(),
+               FieldSelector: 
fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}).String()})
+       if err != nil {
+               return nil, []error{err}
+       }
+
+       pods := []podDelete{}
+
+       for _, pod := range podList.Items {
+               var status podDeleteStatus
+               for _, filter := range d.makeFilters() {
+                       status = filter(pod)
+                       if !status.delete {
+                               // short-circuit as soon as pod is filtered out
+                               // at that point, there is no reason to run pod
+                               // through any additional filters
+                               break
+                       }
+               }
+               if status.delete {
+                       pods = append(pods, podDelete{
+                               pod:    pod,
+                               status: status,
+                       })
+               }
+       }
+
+       list := &podDeleteList{items: pods}
+
+       if errs := list.errors(); len(errs) > 0 {
+               return list, errs
+       }
+
+       return list, nil
+}
+
+// DeleteOrEvictPods deletes or evicts the pods on the api server
+func (d *Helper) DeleteOrEvictPods(pods []corev1.Pod) error {
+       if len(pods) == 0 {
+               return nil
+       }
+
+       policyGroupVersion, err := CheckEvictionSupport(d.Client)
+       if err != nil {
+               return err
+       }
+
+       // TODO(justinsb): unnecessary?
+       getPodFn := func(namespace, name string) (*corev1.Pod, error) {
+               return d.Client.CoreV1().Pods(namespace).Get(name, 
metav1.GetOptions{})
+       }
+       if len(policyGroupVersion) > 0 {
+               return d.evictPods(pods, policyGroupVersion, getPodFn)
+       }
+
+       return d.deletePods(pods, getPodFn)
+}
+
+func (d *Helper) evictPods(pods []corev1.Pod, policyGroupVersion string, 
getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
+       returnCh := make(chan error, 1)
+       // 0 timeout means infinite, we use MaxInt64 to represent it.
+       var globalTimeout time.Duration
+       if d.Timeout == 0 {
+               globalTimeout = time.Duration(math.MaxInt64)
+       } else {
+               globalTimeout = d.Timeout
+       }
+       ctx, cancel := context.WithTimeout(context.TODO(), globalTimeout)
+       defer cancel()
+       for _, pod := range pods {
+               go func(pod corev1.Pod, returnCh chan error) {
+                       for {
+                               fmt.Fprintf(d.Out, "evicting pod %q\n", 
pod.Name)
+                               select {
+                               case <-ctx.Done():
+                                       // return here or we'll leak a 
goroutine.
+                                       returnCh <- fmt.Errorf("error when 
evicting pod %q: global timeout reached: %v", pod.Name, globalTimeout)
+                                       return
+                               default:
+                               }
+                               err := d.EvictPod(pod, policyGroupVersion)
+                               if err == nil {
+                                       break
+                               } else if apierrors.IsNotFound(err) {
+                                       returnCh <- nil
+                                       return
+                               } else if apierrors.IsTooManyRequests(err) {
+                                       fmt.Fprintf(d.ErrOut, "error when 
evicting pod %q (will retry after 5s): %v\n", pod.Name, err)
+                                       time.Sleep(5 * time.Second)
+                               } else {
+                                       returnCh <- fmt.Errorf("error when 
evicting pod %q: %v", pod.Name, err)
+                                       return
+                               }
+                       }
+                       _, err := waitForDelete(ctx, []corev1.Pod{pod}, 
1*time.Second, time.Duration(math.MaxInt64), true, getPodFn, 
d.OnPodDeletedOrEvicted, globalTimeout)
+                       if err == nil {
+                               returnCh <- nil
+                       } else {
+                               returnCh <- fmt.Errorf("error when waiting for 
pod %q terminating: %v", pod.Name, err)
+                       }
+               }(pod, returnCh)
+       }
+
+       doneCount := 0
+       var errors []error
+
+       numPods := len(pods)
+       for doneCount < numPods {
+               select {
+               case err := <-returnCh:
+                       doneCount++
+                       if err != nil {
+                               errors = append(errors, err)
+                       }
+               default:
+               }
+       }
+
+       return utilerrors.NewAggregate(errors)
+}
+
+func (d *Helper) deletePods(pods []corev1.Pod, getPodFn func(namespace, name 
string) (*corev1.Pod, error)) error {
+       // 0 timeout means infinite, we use MaxInt64 to represent it.
+       var globalTimeout time.Duration
+       if d.Timeout == 0 {
+               globalTimeout = time.Duration(math.MaxInt64)
+       } else {
+               globalTimeout = d.Timeout
+       }
+       for _, pod := range pods {
+               err := d.DeletePod(pod)
+               if err != nil && !apierrors.IsNotFound(err) {
+                       return err
+               }
+       }
+       ctx := context.TODO()
+       _, err := waitForDelete(ctx, pods, 1*time.Second, globalTimeout, false, 
getPodFn, d.OnPodDeletedOrEvicted, globalTimeout)
+       return err
+}
+
+func waitForDelete(ctx context.Context, pods []corev1.Pod, interval, timeout 
time.Duration, usingEviction bool, getPodFn func(string, string) (*corev1.Pod, 
error), onDoneFn func(pod *corev1.Pod, usingEviction bool), globalTimeout 
time.Duration) ([]corev1.Pod, error) {
+       err := wait.PollImmediate(interval, timeout, func() (bool, error) {
+               pendingPods := []corev1.Pod{}
+               for i, pod := range pods {
+                       p, err := getPodFn(pod.Namespace, pod.Name)
+                       if apierrors.IsNotFound(err) || (p != nil && 
p.ObjectMeta.UID != pod.ObjectMeta.UID) {
+                               if onDoneFn != nil {
+                                       onDoneFn(&pod, usingEviction)
+                               }
+                               continue
+                       } else if err != nil {
+                               return false, err
+                       } else {
+                               pendingPods = append(pendingPods, pods[i])
+                       }
+               }
+               pods = pendingPods
+               if len(pendingPods) > 0 {
+                       select {
+                       case <-ctx.Done():
+                               return false, fmt.Errorf("global timeout 
reached: %v", globalTimeout)
+                       default:
+                               return false, nil
+                       }
+                       return false, nil
+               }
+               return true, nil
+       })
+       return pods, err
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/k8s.io/kubectl/pkg/drain/filters.go 
new/vendor/k8s.io/kubectl/pkg/drain/filters.go
--- old/vendor/k8s.io/kubectl/pkg/drain/filters.go      1970-01-01 
01:00:00.000000000 +0100
+++ new/vendor/k8s.io/kubectl/pkg/drain/filters.go      2020-11-10 
02:49:40.000000000 +0100
@@ -0,0 +1,223 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package drain
+
+import (
+       "fmt"
+       "strings"
+
+       appsv1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
+       apierrors "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+       daemonSetFatal      = "DaemonSet-managed Pods (use --ignore-daemonsets 
to ignore)"
+       daemonSetWarning    = "ignoring DaemonSet-managed Pods"
+       localStorageFatal   = "Pods with local storage (use --delete-local-data 
to override)"
+       localStorageWarning = "deleting Pods with local storage"
+       unmanagedFatal      = "Pods not managed by ReplicationController, 
ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override)"
+       unmanagedWarning    = "deleting Pods not managed by 
ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet"
+)
+
+type podDelete struct {
+       pod    corev1.Pod
+       status podDeleteStatus
+}
+
+type podDeleteList struct {
+       items []podDelete
+}
+
+func (l *podDeleteList) Pods() []corev1.Pod {
+       pods := []corev1.Pod{}
+       for _, i := range l.items {
+               if i.status.delete {
+                       pods = append(pods, i.pod)
+               }
+       }
+       return pods
+}
+
+func (l *podDeleteList) Warnings() string {
+       ps := make(map[string][]string)
+       for _, i := range l.items {
+               if i.status.reason == podDeleteStatusTypeWarning {
+                       ps[i.status.message] = append(ps[i.status.message], 
fmt.Sprintf("%s/%s", i.pod.Namespace, i.pod.Name))
+               }
+       }
+
+       msgs := []string{}
+       for key, pods := range ps {
+               msgs = append(msgs, fmt.Sprintf("%s: %s", key, 
strings.Join(pods, ", ")))
+       }
+       return strings.Join(msgs, "; ")
+}
+
+func (l *podDeleteList) errors() []error {
+       failedPods := make(map[string][]string)
+       for _, i := range l.items {
+               if i.status.reason == podDeleteStatusTypeError {
+                       msg := i.status.message
+                       if msg == "" {
+                               msg = "unexpected error"
+                       }
+                       failedPods[msg] = append(failedPods[msg], 
fmt.Sprintf("%s/%s", i.pod.Namespace, i.pod.Name))
+               }
+       }
+       errs := make([]error, 0)
+       for msg, pods := range failedPods {
+               errs = append(errs, fmt.Errorf("cannot delete %s: %s", msg, 
strings.Join(pods, ", ")))
+       }
+       return errs
+}
+
+type podDeleteStatus struct {
+       delete  bool
+       reason  string
+       message string
+}
+
+// Takes a pod and returns a PodDeleteStatus
+type podFilter func(corev1.Pod) podDeleteStatus
+
+const (
+       podDeleteStatusTypeOkay    = "Okay"
+       podDeleteStatusTypeSkip    = "Skip"
+       podDeleteStatusTypeWarning = "Warning"
+       podDeleteStatusTypeError   = "Error"
+)
+
+func makePodDeleteStatusOkay() podDeleteStatus {
+       return podDeleteStatus{
+               delete: true,
+               reason: podDeleteStatusTypeOkay,
+       }
+}
+
+func makePodDeleteStatusSkip() podDeleteStatus {
+       return podDeleteStatus{
+               delete: false,
+               reason: podDeleteStatusTypeSkip,
+       }
+}
+
+func makePodDeleteStatusWithWarning(delete bool, message string) 
podDeleteStatus {
+       return podDeleteStatus{
+               delete:  delete,
+               reason:  podDeleteStatusTypeWarning,
+               message: message,
+       }
+}
+
+func makePodDeleteStatusWithError(message string) podDeleteStatus {
+       return podDeleteStatus{
+               delete:  false,
+               reason:  podDeleteStatusTypeError,
+               message: message,
+       }
+}
+
+func (d *Helper) makeFilters() []podFilter {
+       return []podFilter{
+               d.daemonSetFilter,
+               d.mirrorPodFilter,
+               d.localStorageFilter,
+               d.unreplicatedFilter,
+       }
+}
+
+func hasLocalStorage(pod corev1.Pod) bool {
+       for _, volume := range pod.Spec.Volumes {
+               if volume.EmptyDir != nil {
+                       return true
+               }
+       }
+
+       return false
+}
+
+func (d *Helper) daemonSetFilter(pod corev1.Pod) podDeleteStatus {
+       // Note that we return false in cases where the pod is DaemonSet 
managed,
+       // regardless of flags.
+       //
+       // The exception is for pods that are orphaned (the referencing
+       // management resource - including DaemonSet - is not found).
+       // Such pods will be deleted if --force is used.
+       controllerRef := metav1.GetControllerOf(&pod)
+       if controllerRef == nil || controllerRef.Kind != 
appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
+               return makePodDeleteStatusOkay()
+       }
+       // Any finished pod can be removed.
+       if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == 
corev1.PodFailed {
+               return makePodDeleteStatusOkay()
+       }
+
+       if _, err := 
d.Client.AppsV1().DaemonSets(pod.Namespace).Get(controllerRef.Name, 
metav1.GetOptions{}); err != nil {
+               // remove orphaned pods with a warning if --force is used
+               if apierrors.IsNotFound(err) && d.Force {
+                       return makePodDeleteStatusWithWarning(true, err.Error())
+               }
+
+               return makePodDeleteStatusWithError(err.Error())
+       }
+
+       if !d.IgnoreAllDaemonSets {
+               return makePodDeleteStatusWithError(daemonSetFatal)
+       }
+
+       return makePodDeleteStatusWithWarning(false, daemonSetWarning)
+}
+
+func (d *Helper) mirrorPodFilter(pod corev1.Pod) podDeleteStatus {
+       if _, found := 
pod.ObjectMeta.Annotations[corev1.MirrorPodAnnotationKey]; found {
+               return makePodDeleteStatusSkip()
+       }
+       return makePodDeleteStatusOkay()
+}
+
+func (d *Helper) localStorageFilter(pod corev1.Pod) podDeleteStatus {
+       if !hasLocalStorage(pod) {
+               return makePodDeleteStatusOkay()
+       }
+       // Any finished pod can be removed.
+       if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == 
corev1.PodFailed {
+               return makePodDeleteStatusOkay()
+       }
+       if !d.DeleteLocalData {
+               return makePodDeleteStatusWithError(localStorageFatal)
+       }
+
+       return makePodDeleteStatusWithWarning(true, localStorageWarning)
+}
+
+func (d *Helper) unreplicatedFilter(pod corev1.Pod) podDeleteStatus {
+       // any finished pod can be removed
+       if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == 
corev1.PodFailed {
+               return makePodDeleteStatusOkay()
+       }
+
+       controllerRef := metav1.GetControllerOf(&pod)
+       if controllerRef != nil {
+               return makePodDeleteStatusOkay()
+       }
+       if d.Force {
+               return makePodDeleteStatusWithWarning(true, unmanagedWarning)
+       }
+       return makePodDeleteStatusWithError(unmanagedFatal)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt
--- old/vendor/modules.txt      2020-09-29 05:04:01.000000000 +0200
+++ new/vendor/modules.txt      2020-11-10 02:49:40.000000000 +0100
@@ -285,6 +285,8 @@
 k8s.io/klog/v2
 # k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6
 k8s.io/kube-openapi/pkg/util/proto
+# k8s.io/kubectl v0.17.4
+k8s.io/kubectl/pkg/drain
 # k8s.io/utils v0.0.0-20200414100711-2df71ebbae66
 k8s.io/utils/buffer
 k8s.io/utils/integer
_______________________________________________
openSUSE Commits mailing list -- [email protected]
To unsubscribe, email [email protected]
List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette
List Archives: 
https://lists.opensuse.org/archives/list/[email protected]

Reply via email to