diff --git a/controllers/external/testing.go b/controllers/external/testing.go index 762588ce2064..bed8d4b497ba 100644 --- a/controllers/external/testing.go +++ b/controllers/external/testing.go @@ -58,6 +58,10 @@ var ( Type: "object", XPreserveUnknownFields: pointer.BoolPtr(true), }, + "status": { + Type: "object", + XPreserveUnknownFields: pointer.BoolPtr(true), + }, }, }, }, diff --git a/go.mod b/go.mod index f2ab225e1498..a38b73a1fdda 100644 --- a/go.mod +++ b/go.mod @@ -11,23 +11,27 @@ require ( github.com/evanphx/json-patch v4.5.0+incompatible github.com/go-logr/logr v0.1.0 github.com/gogo/protobuf v1.3.1 - github.com/google/go-cmp v0.4.0 + github.com/golang/protobuf v1.3.5 // indirect + github.com/google/go-cmp v0.4.1 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect github.com/google/gofuzz v1.1.0 + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/imdario/mergo v0.3.9 // indirect github.com/onsi/ginkgo v1.12.0 github.com/onsi/gomega v1.9.0 - github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.5.0 + github.com/prometheus/client_golang v1.5.1 github.com/prometheus/client_model v0.2.0 + github.com/prometheus/procfs v0.0.11 // indirect github.com/spf13/cobra v0.0.6 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.6.2 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - google.golang.org/appengine v1.6.1 // indirect + google.golang.org/appengine v1.6.6 // indirect google.golang.org/grpc v1.26.0 k8s.io/api v0.17.2 k8s.io/apiextensions-apiserver v0.17.2 @@ -37,9 +41,8 @@ require ( k8s.io/cluster-bootstrap v0.17.2 k8s.io/component-base v0.17.2 k8s.io/klog v1.0.0 - k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c // indirect k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab - sigs.k8s.io/controller-runtime v0.5.3 + sigs.k8s.io/controller-runtime v0.5.5 sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index a843eb344877..59a7de4f9e33 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y 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/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -202,6 +204,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -241,6 +245,8 @@ github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -248,6 +254,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= @@ -336,8 +344,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 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/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= @@ -355,8 +363,8 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= -github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -372,6 +380,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -458,7 +468,6 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -523,11 +532,11 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -553,7 +562,6 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= @@ -571,8 +579,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -643,8 +651,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab h1:I3f2hcBrepGRXI1z4sukzAb8w1R4eqbsHrAsx06LGYM= k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= @@ -653,13 +659,12 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -sigs.k8s.io/controller-runtime v0.5.3 h1:HGL1kpWxe6WiiN4yCmYA5SFtvUMQAkOwV+Ylh7M87E4= -sigs.k8s.io/controller-runtime v0.5.3/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-runtime v0.5.5 h1:rDFXh0T6J6gmBmiDOJwNXef3os9fubmyn6aKAx2NI3s= +sigs.k8s.io/controller-runtime v0.5.5/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802 h1:L6/8hETA7jvdx3xBcbDifrIN2xaYHE7tA58n+Kdp2Zw= sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802/go.mod h1:HIZ3PWUezpklcjkqpFbnYOqaqsAE1JeCTEwkgvPLXjk= 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/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= 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= diff --git a/test/infrastructure/docker/go.mod b/test/infrastructure/docker/go.mod index f67925657147..83ea02c76648 100644 --- a/test/infrastructure/docker/go.mod +++ b/test/infrastructure/docker/go.mod @@ -11,7 +11,7 @@ require ( k8s.io/client-go v0.17.2 k8s.io/klog v1.0.0 sigs.k8s.io/cluster-api v0.3.3 - sigs.k8s.io/controller-runtime v0.5.3 + sigs.k8s.io/controller-runtime v0.5.5 sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802 sigs.k8s.io/yaml v1.2.0 ) diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum index cd857bea278f..b243e722f12b 100644 --- a/test/infrastructure/docker/go.sum +++ b/test/infrastructure/docker/go.sum @@ -168,6 +168,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y 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/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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= @@ -175,6 +177,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -205,12 +209,16 @@ github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -290,8 +298,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 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/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= @@ -308,8 +316,8 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= -github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -325,6 +333,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -393,7 +403,6 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -457,11 +466,11 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -487,7 +496,6 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -504,8 +512,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -570,8 +578,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab h1:I3f2hcBrepGRXI1z4sukzAb8w1R4eqbsHrAsx06LGYM= k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= @@ -580,13 +586,12 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -sigs.k8s.io/controller-runtime v0.5.3 h1:HGL1kpWxe6WiiN4yCmYA5SFtvUMQAkOwV+Ylh7M87E4= -sigs.k8s.io/controller-runtime v0.5.3/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-runtime v0.5.5 h1:rDFXh0T6J6gmBmiDOJwNXef3os9fubmyn6aKAx2NI3s= +sigs.k8s.io/controller-runtime v0.5.5/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802 h1:L6/8hETA7jvdx3xBcbDifrIN2xaYHE7tA58n+Kdp2Zw= sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802/go.mod h1:HIZ3PWUezpklcjkqpFbnYOqaqsAE1JeCTEwkgvPLXjk= 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/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= 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= diff --git a/util/conditions/patch.go b/util/conditions/patch.go index 8e2f4707d6f7..5181a940d4f8 100644 --- a/util/conditions/patch.go +++ b/util/conditions/patch.go @@ -89,8 +89,7 @@ func (p Patch) Apply(source Setter) error { for _, conditionPatch := range p { switch conditionPatch.Op { case AddConditionPatch: - // If the condition is already on source, check if source and target agree on the change; - // if not, this is a conflict. + // If the condition is already on source, check if source and target agree on the change; if not, this is a conflict. if sourceCondition := Get(source, conditionPatch.Target.Type); sourceCondition != nil { // If source and target agree on the change, then it is a conflict. if !hasSameState(sourceCondition, conditionPatch.Target) { @@ -139,3 +138,8 @@ func (p Patch) Apply(source Setter) error { } return nil } + +// IsZero returns true if the patch has no changes. +func (p Patch) IsZero() bool { + return len(p) == 0 +} diff --git a/util/conditions/setter.go b/util/conditions/setter.go index 0341a0e18104..2ed67abe4d8b 100644 --- a/util/conditions/setter.go +++ b/util/conditions/setter.go @@ -19,6 +19,7 @@ package conditions import ( "fmt" "sort" + "time" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -44,7 +45,7 @@ func Set(to Setter, condition *clusterv1.Condition) { conditions := to.GetConditions() newConditions := make(clusterv1.Conditions, 0, len(conditions)) exists := false - condition.LastTransitionTime = metav1.Now() + condition.LastTransitionTime = metav1.NewTime(time.Now().Truncate(time.Second)) for i := range conditions { existingCondition := conditions[i] if existingCondition.Type == condition.Type { diff --git a/util/patch/options.go b/util/patch/options.go new file mode 100644 index 000000000000..4b63264a38d7 --- /dev/null +++ b/util/patch/options.go @@ -0,0 +1,39 @@ +/* +Copyright 2020 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 patch + +// Option is some configuration that modifies options for a patch request. +type Option interface { + // ApplyToHelper applies this configuration to the given Helper options. + ApplyToHelper(*HelperOptions) +} + +// HelperOptions contains options for patch options. +type HelperOptions struct { + // IncludeStatusObservedGeneration sets the status.observedGeneration field + // on the incoming object to match metadata.generation, only if there is a change. + IncludeStatusObservedGeneration bool +} + +// WithStatusObservedGeneration sets the status.observedGeneration field +// on the incoming object to match metadata.generation, only if there is a change. +type WithStatusObservedGeneration struct{} + +// ApplyToHelper applies this configuration to the given an List options. +func (w WithStatusObservedGeneration) ApplyToHelper(in *HelperOptions) { + in.IncludeStatusObservedGeneration = true +} diff --git a/util/patch/patch.go b/util/patch/patch.go index 91550c39ea6d..b52a0d6dd1b9 100644 --- a/util/patch/patch.go +++ b/util/patch/patch.go @@ -18,117 +18,289 @@ package patch import ( "context" + "encoding/json" "reflect" + "time" "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/controller-runtime/pkg/client" ) -// Helper is a utility for ensuring the proper Patching of resources -// and their status +// Helper is a utility for ensuring the proper patching of objects. type Helper struct { - client client.Client - before map[string]interface{} - hasStatus bool - beforeStatus interface{} - resourcePatch client.Patch - statusPatch client.Patch + opts *HelperOptions + + client client.Client + beforeObject runtime.Object + before *unstructured.Unstructured + after *unstructured.Unstructured + changes map[string]bool + + isConditionsSetter bool } // NewHelper returns an initialized Helper -func NewHelper(resource runtime.Object, crClient client.Client) (*Helper, error) { - if resource == nil { - return nil, errors.Errorf("expected non-nil resource") +func NewHelper(obj runtime.Object, crClient client.Client, opts ...Option) (*Helper, error) { + // Return early if the object is nil. + // If you're wondering why we need reflection to do this check, see https://golang.org/doc/faq#nil_error. + // TODO(vincepri): Remove this check and let it panic if used improperly in a future minor release. + if obj == nil || (reflect.ValueOf(obj).IsValid() && reflect.ValueOf(obj).IsNil()) { + return nil, errors.Errorf("expected non-nil object") } - // If the object is already unstructured, we need to perform a deepcopy first - // because the `DefaultUnstructuredConverter.ToUnstructured` function returns - // the underlying unstructured object map without making a copy. - if _, ok := resource.(runtime.Unstructured); ok { - resource = resource.DeepCopyObject() + // Calculate the options to pass to the helper. + options := &HelperOptions{} + for _, opt := range opts { + opt.ApplyToHelper(options) } - // Convert the resource to unstructured for easier comparison later. - before, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource) + // Convert the object to unstructured to compare against our before copy. + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject()) if err != nil { return nil, err } + unstructuredObj := &unstructured.Unstructured{Object: raw} + + // Check if the object satisfies the Cluster API conditions contract. + _, canInterfaceConditions := obj.(conditions.Setter) + + return &Helper{ + opts: options, + client: crClient, + before: unstructuredObj, + beforeObject: obj.DeepCopyObject(), + isConditionsSetter: canInterfaceConditions, + }, nil +} + +// Patch will attempt to patch the given object, including its status. +func (h *Helper) Patch(ctx context.Context, obj runtime.Object) error { + if obj == nil { + return errors.Errorf("expected non-nil object") + } - hasStatus := false - // attempt to extract the status from the resource for easier comparison later - beforeStatus, ok, err := unstructured.NestedFieldCopy(before, "status") + // Convert the object to unstructured to compare against our before copy. + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject()) if err != nil { - return nil, err + return err + } + h.after = &unstructured.Unstructured{Object: raw} + + // Determine if the object has status. + if unstructuredHasStatus(h.after) { + if h.opts.IncludeStatusObservedGeneration { + // Set status.observedGeneration if we're asked to do so. + if err := unstructured.SetNestedField(h.after.Object, h.after.GetGeneration(), "status", "observedGeneration"); err != nil { + return err + } + + // Restore the changes back to the original object. + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(h.after.Object, obj); err != nil { + return err + } + } } - if ok { - hasStatus = true - // if the resource contains a status remove it from our unstructured copy - // to avoid unnecessary patching later - unstructured.RemoveNestedField(before, "status") + + // Calculate and store the top-level field changes (e.g. "metadata", "spec", "status") we have before/after. + h.changes, err = h.calculateChanges(obj) + if err != nil { + return err } - return &Helper{ - client: crClient, - before: before, - beforeStatus: beforeStatus, - hasStatus: hasStatus, - resourcePatch: client.MergeFrom(resource.DeepCopyObject()), - statusPatch: client.MergeFrom(resource.DeepCopyObject()), - }, nil + // Issue patches and return errors in an aggregate. + return kerrors.NewAggregate([]error{ + h.patch(ctx, obj), + h.patchStatus(ctx, obj), + h.patchStatusConditions(ctx, obj), + }) } -// Patch will attempt to patch the given resource and its status -func (h *Helper) Patch(ctx context.Context, resource runtime.Object) error { - if resource == nil { - return errors.Errorf("expected non-nil resource") +// patch issues a patch for metadata and spec. +func (h *Helper) patch(ctx context.Context, obj runtime.Object) error { + if !h.shouldPatch("metadata") && !h.shouldPatch("spec") { + return nil + } + beforeObject, afterObject, err := h.calculatePatch(obj, specPatch) + if err != nil { + return err } + return h.client.Patch(ctx, afterObject, client.MergeFrom(beforeObject)) +} - // If the object is already unstructured, we need to perform a deepcopy first - // because the `DefaultUnstructuredConverter.ToUnstructured` function returns - // the underlying unstructured object map without making a copy. - if _, ok := resource.(runtime.Unstructured); ok { - resource = resource.DeepCopyObject() +// patchStatus issues a patch if the status has changed. +func (h *Helper) patchStatus(ctx context.Context, obj runtime.Object) error { + if !h.shouldPatch("status") { + return nil } + beforeObject, afterObject, err := h.calculatePatch(obj, statusPatch) + if err != nil { + return err + } + return h.client.Status().Patch(ctx, afterObject, client.MergeFrom(beforeObject)) +} - // Convert the resource to unstructured to compare against our before copy. - after, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource) +// patchStatusConditions issues a patch if there are any changes to the conditions slice under +// the status subresource. This is a special case and it's handled separately given that +// we allow different controllers to act on conditions of the same object. +// +// This method has an internal backoff loop. When a conflict is detected, the method +// asks the Client for the a new version of the object we're trying to patch. +// +// Condition changes are then applied to the latest version of the object, and if there are +// no unresolvable conflicts, the patch is sent again. +func (h *Helper) patchStatusConditions(ctx context.Context, obj runtime.Object) error { + // Nothing to do if the object isn't a condition patcher. + if !h.isConditionsSetter { + return nil + } + + // Create the condition patch that's used later in client.Status.Patch(). + beforeObject, afterObject, err := h.calculatePatch(obj, statusConditionsPatch) if err != nil { return err } + conditionsPatch := client.MergeFromWithOptions(beforeObject, client.MergeFromWithOptimisticLock{}) + + // Make sure our before/after objects satisfy the proper interface before continuing. + // + // NOTE: The checks and error below are done so that we don't panic if any of the objects don't satisfy the + // interface any longer, although this shouldn't happen because we already check when creating the patcher. + before, ok := beforeObject.(conditions.Setter) + if !ok { + return errors.Errorf("object %s doesn't satisfy conditions.Setter, cannot patch", before.GetObjectKind()) + } + after, ok := afterObject.(conditions.Setter) + if !ok { + return errors.Errorf("object %s doesn't satisfy conditions.Setter, cannot patch", after.GetObjectKind()) + } + + // Store the diff from the before/after object, and return early if there are no changes. + diff := conditions.NewPatch( + before, + after, + ) + if diff.IsZero() { + return nil + } - hasStatus := false - // attempt to extract the status from the resource to compare against our - // beforeStatus copy - afterStatus, ok, err := unstructured.NestedFieldCopy(after, "status") + // Make a copy of the object and store the key used if we have conflicts. + key, err := client.ObjectKeyFromObject(after) if err != nil { return err } - if ok { - hasStatus = true - // if the resource contains a status remove it from our unstructured copy - // to avoid uneccsary patching. - unstructured.RemoveNestedField(after, "status") + + // Define and start a backoff loop to handle conflicts + // between controllers working on the same object. + // + // This has been copied from https://github.com/kubernetes/kubernetes/blob/release-1.16/pkg/controller/controller_utils.go#L86-L88. + backoff := wait.Backoff{ + Steps: 5, + Duration: 100 * time.Millisecond, + Jitter: 1.0, } - var errs []error + // Start the backoff loop and return errors if any. + return wait.ExponentialBackoff(backoff, func() (bool, error) { + // Issue a patch. + err := h.client.Status().Patch(ctx, after, conditionsPatch) + switch { + // Handle conflicts. + case apierrors.IsConflict(err): + // Get a new copy of the object. + if err := h.client.Get(ctx, key, after); err != nil { + return false, err + } + + // Reset the condition patch before merging conditions. + conditionsPatch = client.MergeFromWithOptions(after.DeepCopyObject(), client.MergeFromWithOptimisticLock{}) - if !reflect.DeepEqual(h.before, after) { - // only issue a Patch if the before and after resources (minus status) differ - if err := h.client.Patch(ctx, resource.DeepCopyObject(), h.resourcePatch); err != nil { - errs = append(errs, err) + // Set the condition patch previously created on the new object. + if err := diff.Apply(after); err != nil { + return false, err + } + + // Requeue. + return false, nil + case err != nil: + return false, err } - } - if (h.hasStatus || hasStatus) && !reflect.DeepEqual(h.beforeStatus, afterStatus) { - // only issue a Status Patch if the resource has a status and the beforeStatus - // and afterStatus copies differ - if err := h.client.Status().Patch(ctx, resource.DeepCopyObject(), h.statusPatch); err != nil { - errs = append(errs, err) + return true, nil + }) +} + +// calculatePatch returns the before/after objects to be given in a controller-runtime patch, scoped down to the absolute necessary. +func (h *Helper) calculatePatch(afterObj runtime.Object, focus patchType) (runtime.Object, runtime.Object, error) { + // Make a copy of the unstructured objects first. + before := h.before.DeepCopy() + after := h.after.DeepCopy() + + // Let's loop on the copies of our before/after and remove all the keys we don't need. + for _, v := range []*unstructured.Unstructured{before, after} { + // Ranges over the keys of the unstructured object, think of this as the very top level of an object + // when submitting a yaml to kubectl or a client. + // + // These would be keys like `apiVersion`, `kind`, `metadata`, `spec`, `status`, etc. + for key := range v.Object { + // If the current key isn't something we absolutetly need (see the map for reference), + // and it's not our current focus, then we should remove it. + if key != focus.Key() && !preserveUnstructuredKeys[key] { + unstructured.RemoveNestedField(v.Object, key) + continue + } + // If we've determined that we're able to interface with conditions.Setter interface, + // when dealing with the status patch, we need to remove it from the list of changes, + // so the other method can take care of it. + if h.isConditionsSetter && focus == statusPatch { + unstructured.RemoveNestedField(v.Object, "status", "conditions") + } } } - return kerrors.NewAggregate(errs) + // We've now applied all modifications to local unstructured objects, + // make copies of the original objects and convert them back. + beforeObj := h.beforeObject.DeepCopyObject() + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(before.Object, beforeObj); err != nil { + return nil, nil, err + } + afterObj = afterObj.DeepCopyObject() + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(after.Object, afterObj); err != nil { + return nil, nil, err + } + + return beforeObj, afterObj, nil +} + +func (h *Helper) shouldPatch(in string) bool { + return h.changes[in] +} + +// calculate changes tries to build a patch from the before/after objects we have +// and store in a map which top-level fields (e.g. `metadata`, `spec`, `status`, etc.) have changed. +func (h *Helper) calculateChanges(after runtime.Object) (map[string]bool, error) { + // Calculate patch data. + patch := client.MergeFrom(h.beforeObject) + diff, err := patch.Data(after) + if err != nil { + return nil, errors.Wrapf(err, "failed to calculate patch data") + } + + // Unmarshal patch data into a local map. + patchDiff := map[string]interface{}{} + if err := json.Unmarshal(diff, &patchDiff); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal patch data into a map") + } + + // Return the map. + res := make(map[string]bool, len(patchDiff)) + for key := range patchDiff { + res[key] = true + } + return res, nil } diff --git a/util/patch/patch_test.go b/util/patch/patch_test.go index 814ce15f54c8..0bd07ba04c5c 100644 --- a/util/patch/patch_test.go +++ b/util/patch/patch_test.go @@ -17,232 +17,608 @@ limitations under the License. package patch import ( - "context" + "reflect" "testing" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" "sigs.k8s.io/cluster-api/controllers/external" + "sigs.k8s.io/cluster-api/util/conditions" + "sigs.k8s.io/controller-runtime/pkg/client" ) -func TestHelperUnstructuredPatch(t *testing.T) { - g := NewWithT(t) - ctx := context.TODO() - - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "kind": "BootstrapConfig", - "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", - "metadata": map[string]interface{}{ - "name": "test-bootstrap", - "namespace": "default", - }, - "status": map[string]interface{}{ - "ready": true, - }, - }, - } - fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme) - g.Expect(fakeClient.Create(ctx, obj)).To(Succeed()) - - h, err := NewHelper(obj, fakeClient) - g.Expect(err).NotTo(HaveOccurred()) - - refs := []metav1.OwnerReference{ - { - APIVersion: "cluster.x-k8s.io/v1alpha3", - Kind: "Cluster", - Name: "test", - }, - } - obj.SetOwnerReferences(refs) - - g.Expect(h.Patch(ctx, obj)).To(Succeed()) - - // Make sure that the status has been preserved. - ready, err := external.IsReady(obj) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(ready).To(BeTrue()) - - // Make sure that the object has been patched properly. - afterObj := obj.DeepCopy() - g.Expect(fakeClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "test-bootstrap"}, afterObj)).To(Succeed()) - g.Expect(afterObj.GetOwnerReferences()).To(Equal(refs)) -} +var _ = Describe("Patch Helper", func() { -func TestHelperPatch(t *testing.T) { - - tests := []struct { - name string - before runtime.Object - after runtime.Object - wantErr bool - }{ - { - name: "Only remove finalizer update", - before: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - Finalizers: []string{ - clusterv1.ClusterFinalizer, - }, + It("Should patch an unstructured object", func() { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "BootstrapMachine", + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "metadata": map[string]interface{}{ + "generateName": "test-bootstrap-", + "namespace": "default", }, }, - after: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - }, - }, - { - name: "Only status update", - before: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - }, - after: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - Status: clusterv1.ClusterStatus{ - InfrastructureReady: true, - }, - }, - }, - { - name: "Only spec update", - before: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - }, - after: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - Spec: clusterv1.ClusterSpec{ - InfrastructureRef: &corev1.ObjectReference{ - Kind: "test-kind", - Name: "test-ref", - Namespace: "test-namespace", - }, - }, - }, - }, - { - name: "Both spec and status update", - before: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", + } + + Context("adding an owner reference, preserving its status", func() { + obj := obj.DeepCopy() + + By("Creating the unstructured object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.GetName(), Namespace: obj.GetNamespace()} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + obj.Object["status"] = map[string]interface{}{ + "ready": true, + } + Expect(testEnv.Status().Update(ctx, obj)).To(Succeed()) + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Modifying the OwnerReferences") + refs := []metav1.OwnerReference{ + { + APIVersion: "cluster.x-k8s.io/v1alpha3", + Kind: "Cluster", + Name: "test", + UID: types.UID("fake-uid"), }, - }, - after: &clusterv1.Cluster{ + } + obj.SetOwnerReferences(refs) + + By("Patching the unstructured object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating that the status has been preserved") + ready, err := external.IsReady(obj) + Expect(err).ToNot(HaveOccurred()) + Expect(ready).To(BeTrue()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return reflect.DeepEqual(obj.GetOwnerReferences(), objAfter.GetOwnerReferences()) + }, timeout).Should(BeTrue()) + }) + }) + + Describe("Should patch conditions", func() { + Specify("on a corev1.Node object", func() { + conditionTime := metav1.Date(2015, 1, 1, 12, 0, 0, 0, metav1.Now().Location()) + + obj := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - Spec: clusterv1.ClusterSpec{ - InfrastructureRef: &corev1.ObjectReference{ - Kind: "test-kind", - Name: "test-ref", - Namespace: "test-namespace", + GenerateName: "node-patch-test-", + Annotations: map[string]string{ + "test": "1", }, }, - Status: clusterv1.ClusterStatus{ - InfrastructureReady: true, - }, - }, - }, - { - name: "Only add finalizer update", - before: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - }, - }, - after: &clusterv1.Cluster{ + } + + By("Creating a Node object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.GetName()} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Appending a new condition") + condition := corev1.NodeCondition{ + Type: "CustomCondition", + Status: corev1.ConditionTrue, + LastHeartbeatTime: conditionTime, + LastTransitionTime: conditionTime, + Reason: "reason", + Message: "message", + } + obj.Status.Conditions = append(obj.Status.Conditions, condition) + + By("Patching the Node") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + Expect(testEnv.Get(ctx, key, objAfter)).To(Succeed()) + + ok, _ := ContainElement(condition).Match(objAfter.Status.Conditions) + return ok + }, timeout).Should(BeTrue()) + }) + + Describe("on a clusterv1.Cluster object", func() { + obj := &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-cluster", - Namespace: "test-namespace", - Finalizers: []string{ - clusterv1.ClusterFinalizer, - }, + GenerateName: "test-", + Namespace: "default", }, + } + + Specify("should mark it ready", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Marking Ready=True") + conditions.MarkTrue(obj, clusterv1.ReadyCondition) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + return reflect.DeepEqual(obj.Status.Conditions, objAfter.Status.Conditions) + }, timeout).Should(BeTrue()) + }) + + Specify("should recover if there is a resolvable conflict", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + objCopy := obj.DeepCopy() + + By("Marking a custom condition to be false") + conditions.MarkFalse(objCopy, clusterv1.ConditionType("TestCondition"), "reason", clusterv1.ConditionSeverityInfo, "message") + Expect(testEnv.Status().Update(ctx, objCopy)).To(Succeed()) + + By("Validating that the local object's resource version is behind") + Expect(obj.ResourceVersion).ToNot(Equal(objCopy.ResourceVersion)) + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Marking Ready=True") + conditions.MarkTrue(obj, clusterv1.ReadyCondition) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + ok, _ := ContainElements(obj.Status.Conditions[0], objCopy.Status.Conditions[0]).Match(objAfter.Status.Conditions) + return ok + }, timeout).Should(BeTrue()) + }) + + Specify("should recover if there is a resolvable conflict, incl. patch spec and status", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + objCopy := obj.DeepCopy() + + By("Marking a custom condition to be false") + conditions.MarkFalse(objCopy, clusterv1.ConditionType("TestCondition"), "reason", clusterv1.ConditionSeverityInfo, "message") + Expect(testEnv.Status().Update(ctx, objCopy)).To(Succeed()) + + By("Validating that the local object's resource version is behind") + Expect(obj.ResourceVersion).ToNot(Equal(objCopy.ResourceVersion)) + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Changing the object spec, status, and adding Ready=True condition") + obj.Spec.Paused = true + obj.Spec.ControlPlaneEndpoint.Host = "test://endpoint" + obj.Spec.ControlPlaneEndpoint.Port = 8443 + obj.Status.Phase = "custom-phase" + conditions.MarkTrue(obj, clusterv1.ReadyCondition) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + ok, _ := ContainElements(obj.Status.Conditions[0], objCopy.Status.Conditions[0]).Match(objAfter.Status.Conditions) + return ok && + obj.Spec.Paused == objAfter.Spec.Paused && + obj.Spec.ControlPlaneEndpoint == objAfter.Spec.ControlPlaneEndpoint && + obj.Status.Phase == objAfter.Status.Phase + }, timeout).Should(BeTrue()) + }) + + Specify("should return an error if there is an unresolvable conflict", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + objCopy := obj.DeepCopy() + + By("Marking a custom condition to be false") + conditions.MarkFalse(objCopy, clusterv1.ReadyCondition, "reason", clusterv1.ConditionSeverityInfo, "message") + Expect(testEnv.Status().Update(ctx, objCopy)).To(Succeed()) + + By("Validating that the local object's resource version is behind") + Expect(obj.ResourceVersion).ToNot(Equal(objCopy.ResourceVersion)) + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Marking Ready=True") + conditions.MarkTrue(obj, clusterv1.ReadyCondition) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).ToNot(Succeed()) + + By("Validating the object has not been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + ok, _ := ContainElement(objCopy.Status.Conditions[0]).Match(objAfter.Status.Conditions) + return ok + }, timeout).Should(BeTrue()) + }) + + }) + }) + + Describe("Should patch a clusterv1.Cluster", func() { + obj := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-", + Namespace: "default", }, - }, - { - name: "Only add ownerref update to unstructured object", - before: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "kind": "BootstrapConfig", - "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", - "metadata": map[string]interface{}{ - "name": "test-bootstrap", - "namespace": "default", - }, - }, + } + + Specify("add a finalizers", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Adding a finalizer") + obj.Finalizers = append(obj.Finalizers, clusterv1.ClusterFinalizer) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return reflect.DeepEqual(obj.Finalizers, objAfter.Finalizers) + }, timeout).Should(BeTrue()) + }) + + Specify("removing finalizers", func() { + obj := obj.DeepCopy() + obj.Finalizers = append(obj.Finalizers, clusterv1.ClusterFinalizer) + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Removing the finalizers") + obj.SetFinalizers(nil) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return len(objAfter.Finalizers) == 0 + }, timeout).Should(BeTrue()) + }) + + Specify("updating spec", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the object spec") + obj.Spec.Paused = true + obj.Spec.InfrastructureRef = &corev1.ObjectReference{ + Kind: "test-kind", + Name: "test-ref", + Namespace: "test-namespace", + } + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return objAfter.Spec.Paused == true && + reflect.DeepEqual(obj.Spec.InfrastructureRef, objAfter.Spec.InfrastructureRef) + }, timeout).Should(BeTrue()) + }) + + Specify("updating status", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the object status") + obj.Status.InfrastructureReady = true + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + return reflect.DeepEqual(objAfter.Status, obj.Status) + }, timeout).Should(BeTrue()) + }) + + Specify("updating both spec and status", func() { + obj := obj.DeepCopy() + + By("Creating the object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the object spec") + obj.Spec.Paused = true + obj.Spec.InfrastructureRef = &corev1.ObjectReference{ + Kind: "test-kind", + Name: "test-ref", + Namespace: "test-namespace", + } + + By("Updating the object status") + obj.Status.InfrastructureReady = true + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return reflect.DeepEqual(obj.Status, objAfter.Status) && + reflect.DeepEqual(obj.Spec, objAfter.Spec) + }, timeout).Should(BeTrue()) + }) + }) + + It("Should update Status.ObservedGeneration when using WithStatusObservedGeneration option", func() { + obj := &clusterv1.MachineSet{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-ms", + Namespace: "test-namespace", }, - after: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "kind": "BootstrapConfig", - "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", - "metadata": map[string]interface{}{ - "name": "test-bootstrap", - "namespace": "default", - "ownerReferences": []interface{}{ - map[string]interface{}{ - "kind": "TestOwner", - "apiVersion": "test.cluster.x-k8s.io/v1alpha3", - "name": "test", - }, - }, + Spec: clusterv1.MachineSetSpec{ + ClusterName: "test1", + Template: clusterv1.MachineTemplateSpec{ + Spec: clusterv1.MachineSpec{ + ClusterName: "test1", }, }, }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) + } - g.Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) + Context("when updating spec", func() { + obj := obj.DeepCopy() - ctx := context.Background() - fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme) + By("Creating the MachineSet object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() - beforeCopy := tt.before.DeepCopyObject() - g.Expect(fakeClient.Create(ctx, beforeCopy)).To(Succeed()) + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv, WithStatusObservedGeneration{}) + Expect(err).NotTo(HaveOccurred()) - h, err := NewHelper(beforeCopy, fakeClient) - g.Expect(err).NotTo(HaveOccurred()) + By("Updating the object spec") + obj.Spec.Replicas = pointer.Int32Ptr(10) - afterCopy := tt.after.DeepCopyObject() - err = h.Patch(ctx, afterCopy) - if tt.wantErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).NotTo(HaveOccurred()) + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return reflect.DeepEqual(obj.Spec, objAfter.Spec) && + obj.GetGeneration() == objAfter.Status.ObservedGeneration + }, timeout).Should(BeTrue()) + }) + + Context("when updating spec, status, and metadata", func() { + obj := obj.DeepCopy() + + By("Creating the MachineSet object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv, WithStatusObservedGeneration{}) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the object spec") + obj.Spec.Replicas = pointer.Int32Ptr(10) + + By("Updating the object status") + obj.Status.AvailableReplicas = 6 + obj.Status.ReadyReplicas = 6 + + By("Updating the object metadata") + obj.ObjectMeta.Annotations = map[string]string{ + "test1": "annotation", } - g.Expect(afterCopy).To(Equal(tt.after)) + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return reflect.DeepEqual(obj.Spec, objAfter.Spec) && + reflect.DeepEqual(obj.Status, objAfter.Status) && + obj.GetGeneration() == objAfter.Status.ObservedGeneration + }, timeout).Should(BeTrue()) + }) + + Context("without any changes", func() { + obj := obj.DeepCopy() + + By("Creating the MachineSet object") + Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred()) + key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace} + defer func() { + Expect(testEnv.Delete(ctx, obj)).To(Succeed()) + }() + obj.Status.ObservedGeneration = obj.GetGeneration() + lastGeneration := obj.GetGeneration() + Expect(testEnv.Status().Update(ctx, obj)) + + By("Creating a new patch helper") + patcher, err := NewHelper(obj, testEnv, WithStatusObservedGeneration{}) + Expect(err).NotTo(HaveOccurred()) + + By("Patching the object") + Expect(patcher.Patch(ctx, obj)).To(Succeed()) + + By("Validating the object has been updated") + Eventually(func() bool { + objAfter := obj.DeepCopy() + if err := testEnv.Get(ctx, key, objAfter); err != nil { + return false + } + + return lastGeneration == objAfter.Status.ObservedGeneration + }, timeout).Should(BeTrue()) }) - } + }) +}) + +func TestNewHelperNil(t *testing.T) { + var x *appsv1.Deployment + g := NewWithT(t) + _, err := NewHelper(x, nil) + g.Expect(err).ToNot(BeNil()) + _, err = NewHelper(nil, nil) + g.Expect(err).ToNot(BeNil()) } diff --git a/util/patch/suite_test.go b/util/patch/suite_test.go new file mode 100644 index 000000000000..22991fc1bd31 --- /dev/null +++ b/util/patch/suite_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2020 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 patch + +import ( + "context" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "k8s.io/klog" + "k8s.io/klog/klogr" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + + "sigs.k8s.io/cluster-api/cmd/clusterctl/log" + "sigs.k8s.io/cluster-api/test/helpers" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +func init() { + klog.InitFlags(nil) + log.SetLogger(klogr.New()) +} + +const ( + timeout = time.Second * 3 +) + +var ( + testEnv *helpers.TestEnvironment + ctx = context.Background() +) + +func TestPatch(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + By("bootstrapping test environment") + var err error + testEnv, err = helpers.NewTestEnvironment() + Expect(err).NotTo(HaveOccurred()) + + By("starting the manager") + go func() { + Expect(testEnv.StartManager()).To(Succeed()) + }() + + close(done) +}, 60) + +var _ = AfterSuite(func() { + if testEnv != nil { + By("tearing down the test environment") + Expect(testEnv.Stop()).To(Succeed()) + } +}) diff --git a/util/patch/utils.go b/util/patch/utils.go new file mode 100644 index 000000000000..492d29475e50 --- /dev/null +++ b/util/patch/utils.go @@ -0,0 +1,48 @@ +/* +Copyright 2020 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 patch + +import ( + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type patchType string + +func (p patchType) Key() string { + return strings.Split(string(p), ".")[0] +} + +const ( + specPatch patchType = "spec" + statusPatch patchType = "status" + statusConditionsPatch patchType = "status.conditions" +) + +var ( + preserveUnstructuredKeys = map[string]bool{ + "kind": true, + "apiVersion": true, + "metadata": true, + } +) + +func unstructuredHasStatus(u *unstructured.Unstructured) bool { + _, ok := u.Object["status"] + return ok +}