diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 0716e69c1a..7108947845 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -4,9 +4,8 @@ aliases: # active folks who can be contacted to perform admin-related # tasks on the repo, or otherwise approve any PRS. controller-runtime-admins: - - droot - - mengqiy - - pwittrock + - vincepri + - joelanford # non-admin folks who have write-access and can approve any PRs in the repo controller-runtime-maintainers: @@ -15,17 +14,15 @@ aliases: # non-admin folks who can approve any PRs in the repo controller-runtime-approvers: - - gerred - - shawn-hurley - alvaroaleman + - fillzpp + - sbueringer # folks who can review and LGTM any PRs in the repo (doesn't # include approvers & admins -- those count too via the OWNERS # file) controller-runtime-reviewers: - - alenkacz - vincepri - - alexeldeib - varshaprasad96 - fillzpp - sbueringer @@ -40,3 +37,7 @@ aliases: # but are no longer directly involved controller-runtime-emeritus-maintainers: - directxman12 + controller-runtime-emeritus-admins: + - droot + - mengqiy + - pwittrock diff --git a/go.mod b/go.mod index 930cf96133..62a9e4b5cb 100644 --- a/go.mod +++ b/go.mod @@ -8,22 +8,22 @@ require ( github.com/go-logr/logr v1.2.3 github.com/go-logr/zapr v1.2.3 github.com/google/go-cmp v0.5.9 - github.com/onsi/ginkgo/v2 v2.5.1 + github.com/onsi/ginkgo/v2 v2.6.0 github.com/onsi/gomega v1.24.1 github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_model v0.3.0 go.uber.org/goleak v1.2.0 go.uber.org/zap v1.24.0 golang.org/x/sys v0.3.0 - golang.org/x/time v0.0.0-20220609170525-579cf78fd858 + golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.2.0 - k8s.io/api v0.26.0-alpha.3 - k8s.io/apiextensions-apiserver v0.26.0-alpha.3 - k8s.io/apimachinery v0.26.0-alpha.3 - k8s.io/client-go v0.26.0-alpha.3 - k8s.io/component-base v0.26.0-alpha.3 + k8s.io/api v0.26.0 + k8s.io/apiextensions-apiserver v0.26.0 + k8s.io/apimachinery v0.26.0 + k8s.io/client-go v0.26.0 + k8s.io/component-base v0.26.0 k8s.io/klog/v2 v2.80.1 - k8s.io/utils v0.0.0-20220922133306-665eaaec4324 + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 sigs.k8s.io/yaml v1.3.0 ) @@ -42,7 +42,7 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect @@ -56,11 +56,10 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/net v0.2.0 // indirect + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/term v0.2.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index ccf5e288d3..2908895ff0 100644 --- a/go.sum +++ b/go.sum @@ -165,8 +165,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -211,8 +211,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= -github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= +github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -309,9 +309,8 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -352,8 +351,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -416,8 +415,8 @@ golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -425,13 +424,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -591,22 +590,22 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.0-alpha.3 h1:BDEgBeZMrDND/zz9qZ3lCt7Q+dl5F9RkZSG9zgZgVJE= -k8s.io/api v0.26.0-alpha.3/go.mod h1:aZAAOxt17tk0NlKqpw0CUdwZXV8QJ4PSOxh7ZkPq7+0= -k8s.io/apiextensions-apiserver v0.26.0-alpha.3 h1:cXcCIkR6z/FzRUbvdaLo5mHAZfe7fhjGHrVOVSM4wk8= -k8s.io/apiextensions-apiserver v0.26.0-alpha.3/go.mod h1:xrcDutAClgZrMVKTwdAIbNQyrIM+3PehfgzwUh3sX3w= -k8s.io/apimachinery v0.26.0-alpha.3 h1:Tfo5DZs+FJEO5+VRDO2t5/GFPfDC96exqvux7QH5aNs= -k8s.io/apimachinery v0.26.0-alpha.3/go.mod h1:zSkBXgO5G/dSQOe256tx5Yo2OJytojpY3bsXu/4/ZJE= -k8s.io/client-go v0.26.0-alpha.3 h1:3XWtCf3gWOnrTyuEZ32QSI8vZVqZuQI9CLtkYjmTZpY= -k8s.io/client-go v0.26.0-alpha.3/go.mod h1:zDKMziul3e15XShoRBGcoF9jp/V/QaauvDUhv8/0o7E= -k8s.io/component-base v0.26.0-alpha.3 h1:zbYawCLVDrMQhMcxKZ0Z7h4OYRRY5QCxP/S3KR5oBn4= -k8s.io/component-base v0.26.0-alpha.3/go.mod h1:PjyV13TXUPcTZEqK63PUe5GLZsm8MsSelkRPJ7vBcqA= +k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= +k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= +k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= +k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= +k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/builder/controller.go b/pkg/builder/controller.go index efaf069205..9991343be7 100644 --- a/pkg/builder/controller.go +++ b/pkg/builder/controller.go @@ -17,6 +17,7 @@ limitations under the License. package builder import ( + "errors" "fmt" "strings" @@ -182,10 +183,6 @@ func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, erro if blder.forInput.err != nil { return nil, blder.forInput.err } - // Checking the reconcile type exist or not - if blder.forInput.object == nil { - return nil, fmt.Errorf("must provide an object for reconciliation") - } // Set the ControllerManagedBy if err := blder.doController(r); err != nil { @@ -231,6 +228,9 @@ func (blder *Builder) doWatch() error { } // Watches the managed types + if len(blder.ownsInput) > 0 && blder.forInput.object == nil { + return errors.New("Owns() can only be used together with For()") + } for _, own := range blder.ownsInput { typeForSrc, err := blder.project(own.object, own.objectProjection) if err != nil { @@ -249,6 +249,9 @@ func (blder *Builder) doWatch() error { } // Do the watch requests + if len(blder.watchesInput) == 0 && blder.forInput.object == nil { + return errors.New("there are no watches configured, controller will never get triggered. Use For(), Owns() or Watches() to set them up") + } for _, w := range blder.watchesInput { allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...) allPredicates = append(allPredicates, w.predicates...) @@ -269,11 +272,14 @@ func (blder *Builder) doWatch() error { return nil } -func (blder *Builder) getControllerName(gvk schema.GroupVersionKind) string { +func (blder *Builder) getControllerName(gvk schema.GroupVersionKind, hasGVK bool) (string, error) { if blder.name != "" { - return blder.name + return blder.name, nil + } + if !hasGVK { + return "", errors.New("one of For() or Named() must be called") } - return strings.ToLower(gvk.Kind) + return strings.ToLower(gvk.Kind), nil } func (blder *Builder) doController(r reconcile.Reconciler) error { @@ -286,13 +292,18 @@ func (blder *Builder) doController(r reconcile.Reconciler) error { // Retrieve the GVK from the object we're reconciling // to prepopulate logger information, and to optionally generate a default name. - gvk, err := getGvk(blder.forInput.object, blder.mgr.GetScheme()) - if err != nil { - return err + var gvk schema.GroupVersionKind + hasGVK := blder.forInput.object != nil + if hasGVK { + var err error + gvk, err = getGvk(blder.forInput.object, blder.mgr.GetScheme()) + if err != nil { + return err + } } // Setup concurrency. - if ctrlOptions.MaxConcurrentReconciles == 0 { + if ctrlOptions.MaxConcurrentReconciles == 0 && hasGVK { groupKind := gvk.GroupKind().String() if concurrency, ok := globalOpts.GroupKindConcurrency[groupKind]; ok && concurrency > 0 { @@ -305,21 +316,30 @@ func (blder *Builder) doController(r reconcile.Reconciler) error { ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout } - controllerName := blder.getControllerName(gvk) + controllerName, err := blder.getControllerName(gvk, hasGVK) + if err != nil { + return err + } // Setup the logger. if ctrlOptions.LogConstructor == nil { log := blder.mgr.GetLogger().WithValues( "controller", controllerName, - "controllerGroup", gvk.Group, - "controllerKind", gvk.Kind, ) + if hasGVK { + log = log.WithValues( + "controllerGroup", gvk.Group, + "controllerKind", gvk.Kind, + ) + } ctrlOptions.LogConstructor = func(req *reconcile.Request) logr.Logger { log := log if req != nil { + if hasGVK { + log = log.WithValues(gvk.Kind, klog.KRef(req.Namespace, req.Name)) + } log = log.WithValues( - gvk.Kind, klog.KRef(req.Namespace, req.Name), "namespace", req.Namespace, "name", req.Name, ) } diff --git a/pkg/builder/controller_test.go b/pkg/builder/controller_test.go index 4b694dcf45..fec0634ea6 100644 --- a/pkg/builder/controller_test.go +++ b/pkg/builder/controller_test.go @@ -112,16 +112,55 @@ var _ = Describe("application", func() { Expect(instance).To(BeNil()) }) - It("should return an error if For function is not called", func() { + It("should return an error if For and Named function are not called", func() { By("creating a controller manager") m, err := manager.New(cfg, manager.Options{}) Expect(err).NotTo(HaveOccurred()) instance, err := ControllerManagedBy(m). + Watches(&source.Kind{Type: &appsv1.ReplicaSet{}}, &handler.EnqueueRequestForObject{}). + Build(noop) + Expect(err).To(MatchError(ContainSubstring("one of For() or Named() must be called"))) + Expect(instance).To(BeNil()) + }) + + It("should return an error when using Owns without For", func() { + By("creating a controller manager") + m, err := manager.New(cfg, manager.Options{}) + Expect(err).NotTo(HaveOccurred()) + + instance, err := ControllerManagedBy(m). + Named("my_controller"). Owns(&appsv1.ReplicaSet{}). Build(noop) - Expect(err).To(MatchError(ContainSubstring("must provide an object for reconciliation"))) + Expect(err).To(MatchError(ContainSubstring("Owns() can only be used together with For()"))) Expect(instance).To(BeNil()) + + }) + + It("should return an error when there are no watches", func() { + By("creating a controller manager") + m, err := manager.New(cfg, manager.Options{}) + Expect(err).NotTo(HaveOccurred()) + + instance, err := ControllerManagedBy(m). + Named("my_controller"). + Build(noop) + Expect(err).To(MatchError(ContainSubstring("there are no watches configured, controller will never get triggered. Use For(), Owns() or Watches() to set them up"))) + Expect(instance).To(BeNil()) + }) + + It("should allow creating a controllerw without calling For", func() { + By("creating a controller manager") + m, err := manager.New(cfg, manager.Options{}) + Expect(err).NotTo(HaveOccurred()) + + instance, err := ControllerManagedBy(m). + Named("my_controller"). + Watches(&source.Kind{Type: &appsv1.ReplicaSet{}}, &handler.EnqueueRequestForObject{}). + Build(noop) + Expect(err).NotTo(HaveOccurred()) + Expect(instance).NotTo(BeNil()) }) It("should return an error if there is no GVK for an object, and thus we can't default the controller name", func() { diff --git a/pkg/client/client.go b/pkg/client/client.go index 0b0b018a44..7d1ed5c968 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -18,6 +18,7 @@ package client import ( "context" + "errors" "fmt" "strings" @@ -292,18 +293,48 @@ func (c *client) Status() SubResourceWriter { return c.SubResource("status") } -func (c *client) SubResource(subResource string) SubResourceWriter { - return &subResourceWriter{client: c, subResource: subResource} +func (c *client) SubResource(subResource string) SubResourceClient { + return &subResourceClient{client: c, subResource: subResource} } -// subResourceWriter is client.SubResourceWriter that writes to subresources. -type subResourceWriter struct { +// subResourceClient is client.SubResourceWriter that writes to subresources. +type subResourceClient struct { client *client subResource string } -// ensure subResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &subResourceWriter{} +// ensure subResourceClient implements client.SubResourceClient. +var _ SubResourceClient = &subResourceClient{} + +// SubResourceGetOptions holds all the possible configuration +// for a subresource Get request. +type SubResourceGetOptions struct { + Raw *metav1.GetOptions +} + +// ApplyToSubResourceGet updates the configuaration to the given get options. +func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) { + if getOpt.Raw != nil { + o.Raw = getOpt.Raw + } +} + +// ApplyOptions applues the given options. +func (getOpt *SubResourceGetOptions) ApplyOptions(opts []SubResourceGetOption) *SubResourceGetOptions { + for _, o := range opts { + o.ApplyToSubResourceGet(getOpt) + } + + return getOpt +} + +// AsGetOptions returns the configured options as *metav1.GetOptions. +func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions { + if getOpt.Raw == nil { + return &metav1.GetOptions{} + } + return getOpt.Raw +} // SubResourceUpdateOptions holds all the possible configuration // for a subresource update request. @@ -398,42 +429,54 @@ func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOp } } -func (sw *subResourceWriter) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) - defer sw.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind()) +func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error { + switch obj.(type) { + case *unstructured.Unstructured: + return sc.client.unstructuredClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...) + case *metav1.PartialObjectMetadata: + return errors.New("can not get subresource using only metadata") + default: + return sc.client.typedClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...) + } +} + +// Create implements client.SubResourceClient +func (sc *subResourceClient) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) + defer sc.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...) + return sc.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...) case *metav1.PartialObjectMetadata: return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?") default: - return sw.client.typedClient.CreateSubResource(ctx, obj, subResource, sw.subResource, opts...) + return sc.client.typedClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...) } } -// Update implements client.SubResourceWriter. -func (sw *subResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) +// Update implements client.SubResourceClient +func (sc *subResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.UpdateSubResource(ctx, obj, sw.subResource, opts...) + return sc.client.unstructuredClient.UpdateSubResource(ctx, obj, sc.subResource, opts...) case *metav1.PartialObjectMetadata: return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?") default: - return sw.client.typedClient.UpdateSubResource(ctx, obj, sw.subResource, opts...) + return sc.client.typedClient.UpdateSubResource(ctx, obj, sc.subResource, opts...) } } // Patch implements client.SubResourceWriter. -func (sw *subResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { - defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) +func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) switch obj.(type) { case *unstructured.Unstructured: - return sw.client.unstructuredClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.unstructuredClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) case *metav1.PartialObjectMetadata: - return sw.client.metadataClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.metadataClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) default: - return sw.client.typedClient.PatchSubResource(ctx, obj, sw.subResource, patch, opts...) + return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) } } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 4ef391625e..856aaa067e 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "reflect" "sync/atomic" "time" @@ -740,8 +741,23 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) }) - Describe("SubResourceWriter", func() { + Describe("SubResourceClient", func() { Context("with structured objects", func() { + It("should be able to read the Scale subresource", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + Expect(cl).NotTo(BeNil()) + + By("Creating a deployment") + dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("reading the scale subresource") + scale := &autoscalingv1.Scale{} + err = cl.SubResource("scale").Get(ctx, dep, scale) + Expect(err).NotTo(HaveOccurred()) + Expect(scale.Spec.Replicas).To(Equal(*dep.Spec.Replicas)) + }) It("should be able to create ServiceAccount tokens", func() { cl, err := client.New(cfg, client.Options{}) Expect(err).NotTo(HaveOccurred()) @@ -901,6 +917,31 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC }) Context("with unstructured objects", func() { + It("should be able to read the Scale subresource", func() { + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + Expect(cl).NotTo(BeNil()) + + By("Creating a deployment") + dep, err := clientset.AppsV1().Deployments(dep.Namespace).Create(ctx, dep, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + dep.APIVersion = appsv1.SchemeGroupVersion.String() + dep.Kind = reflect.TypeOf(dep).Elem().Name() + depUnstructured, err := toUnstructured(dep) + Expect(err).NotTo(HaveOccurred()) + + By("reading the scale subresource") + scale := &unstructured.Unstructured{} + scale.SetAPIVersion("autoscaling/v1") + scale.SetKind("Scale") + err = cl.SubResource("scale").Get(ctx, depUnstructured, scale) + Expect(err).NotTo(HaveOccurred()) + + val, found, err := unstructured.NestedInt64(scale.UnstructuredContent(), "spec", "replicas") + Expect(err).NotTo(HaveOccurred()) + Expect(found).To(BeTrue()) + Expect(int32(val)).To(Equal(*dep.Spec.Replicas)) + }) It("should be able to create ServiceAccount tokens", func() { cl, err := client.New(cfg, client.Options{}) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/client/dryrun.go b/pkg/client/dryrun.go index d4a5865822..73b56429e7 100644 --- a/pkg/client/dryrun.go +++ b/pkg/client/dryrun.go @@ -87,29 +87,33 @@ func (c *dryRunClient) Status() SubResourceWriter { } // SubResource implements client.SubResourceClient. -func (c *dryRunClient) SubResource(subResource string) SubResourceWriter { - return &dryRunSubResourceWriter{client: c.client.SubResource(subResource)} +func (c *dryRunClient) SubResource(subResource string) SubResourceClient { + return &dryRunSubResourceClient{client: c.client.SubResource(subResource)} } // ensure dryRunSubResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &dryRunSubResourceWriter{} +var _ SubResourceWriter = &dryRunSubResourceClient{} -// dryRunSubResourceWriter is client.SubResourceWriter that writes status subresource with dryRun mode +// dryRunSubResourceClient is client.SubResourceWriter that writes status subresource with dryRun mode // enforced. -type dryRunSubResourceWriter struct { - client SubResourceWriter +type dryRunSubResourceClient struct { + client SubResourceClient } -func (sw *dryRunSubResourceWriter) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { +func (sw *dryRunSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error { + return sw.client.Get(ctx, obj, subResource, opts...) +} + +func (sw *dryRunSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { return sw.client.Create(ctx, obj, subResource, append(opts, DryRunAll)...) } // Update implements client.SubResourceWriter. -func (sw *dryRunSubResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { +func (sw *dryRunSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { return sw.client.Update(ctx, obj, append(opts, DryRunAll)...) } // Patch implements client.SubResourceWriter. -func (sw *dryRunSubResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { +func (sw *dryRunSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { return sw.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...) } diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index f05bc095df..4da642319a 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -756,8 +756,8 @@ func (c *fakeClient) Status() client.SubResourceWriter { return c.SubResource("status") } -func (c *fakeClient) SubResource(subResource string) client.SubResourceWriter { - return &fakeSubResourceWriter{client: c} +func (c *fakeClient) SubResource(subResource string) client.SubResourceClient { + return &fakeSubResourceClient{client: c} } func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error { @@ -786,15 +786,19 @@ func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupV return gvr, nil } -type fakeSubResourceWriter struct { +type fakeSubResourceClient struct { client *fakeClient } -func (sw *fakeSubResourceWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { +func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error { + panic("fakeSubResourceClient does not support get") +} + +func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { panic("fakeSubResourceWriter does not support create") } -func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { +func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { // TODO(droot): This results in full update of the obj (spec + subresources). Need // a way to update subresource only. updateOptions := client.SubResourceUpdateOptions{} @@ -807,7 +811,7 @@ func (sw *fakeSubResourceWriter) Update(ctx context.Context, obj client.Object, return sw.client.Update(ctx, body, &updateOptions.UpdateOptions) } -func (sw *fakeSubResourceWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { +func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { // TODO(droot): This results in full update of the obj (spec + subresources). Need // a way to update subresource only. diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index f49176c669..b642f7f88f 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -85,21 +85,21 @@ type StatusClient interface { Status() SubResourceWriter } -// SubResourceClient knows how to create a client which can update subresource +// SubResourceClientConstructor knows how to create a client which can update subresource // for kubernetes objects. -type SubResourceClient interface { - // SubResource returns a subresource client for the named subResource. Known - // upstream subResources are: - // - ServiceAccount tokens: +type SubResourceClientConstructor interface { + // SubResourceClientConstructor returns a subresource client for the named subResource. Known + // upstream subResources usages are: + // - ServiceAccount token creation: // sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // token := &authenticationv1.TokenRequest{} // c.SubResourceClient("token").Create(ctx, sa, token) // - // - Pod evictions: + // - Pod eviction creation: // pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // c.SubResourceClient("eviction").Create(ctx, pod, &policyv1.Eviction{}) // - // - Pod bindings: + // - Pod binding creation: // pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // binding := &corev1.Binding{Target: corev1.ObjectReference{Name: "my-node"}} // c.SubResourceClient("binding").Create(ctx, pod, binding) @@ -116,16 +116,26 @@ type SubResourceClient interface { // } // c.SubResourceClient("approval").Update(ctx, csr) // + // - Scale retrieval: + // dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} + // scale := &autoscalingv1.Scale{} + // c.SubResourceClient("scale").Get(ctx, dep, scale) + // // - Scale update: // dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}} // scale := &autoscalingv1.Scale{Spec: autoscalingv1.ScaleSpec{Replicas: 2}} // c.SubResourceClient("scale").Update(ctx, dep, client.WithSubResourceBody(scale)) - SubResource(subResource string) SubResourceWriter + SubResource(subResource string) SubResourceClient } // StatusWriter is kept for backward compatibility. type StatusWriter = SubResourceWriter +// SubResourceReader knows how to read SubResources +type SubResourceReader interface { + Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error +} + // SubResourceWriter knows how to update subresource of a Kubernetes object. type SubResourceWriter interface { // Create saves the subResource object in the Kubernetes cluster. obj must be a @@ -142,12 +152,18 @@ type SubResourceWriter interface { Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error } +// SubResourceClient knows how to perform CRU operations on Kubernetes objects. +type SubResourceClient interface { + SubResourceReader + SubResourceWriter +} + // Client knows how to perform CRUD operations on Kubernetes objects. type Client interface { Reader Writer StatusClient - SubResourceClient + SubResourceClientConstructor // Scheme returns the scheme this client is using. Scheme() *runtime.Scheme diff --git a/pkg/client/namespaced_client.go b/pkg/client/namespaced_client.go index a8a73e145e..00bc2175ce 100644 --- a/pkg/client/namespaced_client.go +++ b/pkg/client/namespaced_client.go @@ -166,20 +166,20 @@ func (n *namespacedClient) Status() SubResourceWriter { } // SubResource implements client.SubResourceClient. -func (n *namespacedClient) SubResource(subResource string) SubResourceWriter { - return &namespacedClientSubResourceWriter{StatusClient: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n} +func (n *namespacedClient) SubResource(subResource string) SubResourceClient { + return &namespacedClientSubResourceClient{client: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n} } -// ensure namespacedClientSubResourceWriter implements client.SubResourceWriter. -var _ SubResourceWriter = &namespacedClientSubResourceWriter{} +// ensure namespacedClientSubResourceClient implements client.SubResourceClient. +var _ SubResourceClient = &namespacedClientSubResourceClient{} -type namespacedClientSubResourceWriter struct { - StatusClient SubResourceWriter +type namespacedClientSubResourceClient struct { + client SubResourceClient namespace string namespacedclient Client } -func (nsw *namespacedClientSubResourceWriter) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { +func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { return fmt.Errorf("error finding the scope of the object: %w", err) @@ -194,11 +194,29 @@ func (nsw *namespacedClientSubResourceWriter) Create(ctx context.Context, obj, s obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Create(ctx, obj, subResource, opts...) + return nsw.client.Get(ctx, obj, subResource, opts...) +} + +func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error { + isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) + if err != nil { + return fmt.Errorf("error finding the scope of the object: %w", err) + } + + objectNamespace := obj.GetNamespace() + if objectNamespace != nsw.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + } + + if isNamespaceScoped && objectNamespace == "" { + obj.SetNamespace(nsw.namespace) + } + + return nsw.client.Create(ctx, obj, subResource, opts...) } // Update implements client.SubResourceWriter. -func (nsw *namespacedClientSubResourceWriter) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { +func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { return fmt.Errorf("error finding the scope of the object: %w", err) @@ -212,11 +230,11 @@ func (nsw *namespacedClientSubResourceWriter) Update(ctx context.Context, obj Ob if isNamespaceScoped && objectNamespace == "" { obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Update(ctx, obj, opts...) + return nsw.client.Update(ctx, obj, opts...) } // Patch implements client.SubResourceWriter. -func (nsw *namespacedClientSubResourceWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { +func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper()) if err != nil { @@ -231,5 +249,5 @@ func (nsw *namespacedClientSubResourceWriter) Patch(ctx context.Context, obj Obj if isNamespaceScoped && objectNamespace == "" { obj.SetNamespace(nsw.namespace) } - return nsw.StatusClient.Patch(ctx, obj, patch, opts...) + return nsw.client.Patch(ctx, obj, patch, opts...) } diff --git a/pkg/client/options.go b/pkg/client/options.go index caf14dee67..c8fa0dc797 100644 --- a/pkg/client/options.go +++ b/pkg/client/options.go @@ -67,6 +67,11 @@ type DeleteAllOfOption interface { ApplyToDeleteAllOf(*DeleteAllOfOptions) } +// SubResourceGetOption modifies options for a SubResource Get request. +type SubResourceGetOption interface { + ApplyToSubResourceGet(*SubResourceGetOptions) +} + // SubResourceUpdateOption is some configuration that modifies options for a update request. type SubResourceUpdateOption interface { // ApplyToSubResourceUpdate applies this configuration to the given update options. diff --git a/pkg/client/options_test.go b/pkg/client/options_test.go index 1bf90dc89c..ca7ee0fb08 100644 --- a/pkg/client/options_test.go +++ b/pkg/client/options_test.go @@ -112,7 +112,7 @@ var _ = Describe("CreateOptions", func() { var _ = Describe("DeleteOptions", func() { It("Should set GracePeriodSeconds", func() { - o := &client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64Ptr(42)} + o := &client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64(42)} newDeleteOpts := &client.DeleteOptions{} o.ApplyToDelete(newDeleteOpts) Expect(newDeleteOpts).To(Equal(o)) @@ -185,7 +185,7 @@ var _ = Describe("PatchOptions", func() { Expect(newPatchOpts).To(Equal(o)) }) It("Should set Force", func() { - o := &client.PatchOptions{Force: utilpointer.BoolPtr(true)} + o := &client.PatchOptions{Force: utilpointer.Bool(true)} newPatchOpts := &client.PatchOptions{} o.ApplyToPatch(newPatchOpts) Expect(newPatchOpts).To(Equal(o)) @@ -218,7 +218,7 @@ var _ = Describe("DeleteAllOfOptions", func() { Expect(newDeleteAllOfOpts).To(Equal(o)) }) It("Should set DeleleteOptions", func() { - o := &client.DeleteAllOfOptions{DeleteOptions: client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64Ptr(44)}} + o := &client.DeleteAllOfOptions{DeleteOptions: client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64(44)}} newDeleteAllOfOpts := &client.DeleteAllOfOptions{} o.ApplyToDeleteAllOf(newDeleteAllOfOpts) Expect(newDeleteAllOfOpts).To(Equal(o)) diff --git a/pkg/client/split.go b/pkg/client/split.go index 42b61152bd..19d1ab4db7 100644 --- a/pkg/client/split.go +++ b/pkg/client/split.go @@ -61,9 +61,9 @@ func NewDelegatingClient(in NewDelegatingClientInput) (Client, error) { uncachedGVKs: uncachedGVKs, cacheUnstructured: in.CacheUnstructured, }, - Writer: in.Client, - StatusClient: in.Client, - SubResourceClient: in.Client, + Writer: in.Client, + StatusClient: in.Client, + SubResourceClientConstructor: in.Client, }, nil } @@ -71,7 +71,7 @@ type delegatingClient struct { Reader Writer StatusClient - SubResourceClient + SubResourceClientConstructor scheme *runtime.Scheme mapper meta.RESTMapper diff --git a/pkg/client/typed_client.go b/pkg/client/typed_client.go index 09cad7b928..ade251572b 100644 --- a/pkg/client/typed_client.go +++ b/pkg/client/typed_client.go @@ -167,6 +167,29 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti Into(obj) } +func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + + if subResourceObj.GetName() == "" { + subResourceObj.SetName(obj.GetName()) + } + + getOpts := &SubResourceGetOptions{} + getOpts.ApplyOptions(opts) + + return o.Get(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource(subResource). + VersionedParams(getOpts.AsGetOptions(), c.paramCodec). + Do(ctx). + Into(subResourceObj) +} + func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { o, err := c.cache.getObjMeta(obj) if err != nil { diff --git a/pkg/client/unstructured_client.go b/pkg/client/unstructured_client.go index da742632c8..7f25c7be90 100644 --- a/pkg/client/unstructured_client.go +++ b/pkg/client/unstructured_client.go @@ -225,7 +225,7 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ... Into(obj) } -func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { +func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { if _, ok := obj.(*unstructured.Unstructured); !ok { return fmt.Errorf("unstructured client did not understand object: %T", subResource) } @@ -243,6 +243,37 @@ func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subRes return err } + getOpts := &SubResourceGetOptions{} + getOpts.ApplyOptions(opts) + + return o.Get(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource(subResource). + VersionedParams(getOpts.AsGetOptions(), uc.paramCodec). + Do(ctx). + Into(subResourceObj) +} + +func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { + if _, ok := obj.(*unstructured.Unstructured); !ok { + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) + } + + if _, ok := subResourceObj.(*unstructured.Unstructured); !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + + if subResourceObj.GetName() == "" { + subResourceObj.SetName(obj.GetName()) + } + + o, err := uc.cache.getObjMeta(obj) + if err != nil { + return err + } + createOpts := &SubResourceCreateOptions{} createOpts.ApplyOptions(opts) diff --git a/pkg/config/v1alpha1/types.go b/pkg/config/v1alpha1/types.go index 1af58a0348..f2226278c6 100644 --- a/pkg/config/v1alpha1/types.go +++ b/pkg/config/v1alpha1/types.go @@ -94,6 +94,10 @@ type ControllerConfigurationSpec struct { // Defaults to 2 minutes if not set. // +optional CacheSyncTimeout *time.Duration `json:"cacheSyncTimeout,omitempty"` + + // RecoverPanic indicates if panics should be recovered. + // +optional + RecoverPanic *bool `json:"recoverPanic,omitempty"` } // ControllerMetrics defines the metrics configs. diff --git a/pkg/config/v1alpha1/zz_generated.deepcopy.go b/pkg/config/v1alpha1/zz_generated.deepcopy.go index 5329bef667..cdc7c334be 100644 --- a/pkg/config/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/config/v1alpha1/zz_generated.deepcopy.go @@ -27,6 +27,11 @@ func (in *ControllerConfigurationSpec) DeepCopyInto(out *ControllerConfiguration *out = new(timex.Duration) **out = **in } + if in.RecoverPanic != nil { + in, out := &in.RecoverPanic, &out.RecoverPanic + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerConfigurationSpec. diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 4286c135dd..fe7f94fdc1 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -56,7 +56,8 @@ type Options struct { CacheSyncTimeout time.Duration // RecoverPanic indicates whether the panic caused by reconcile should be recovered. - RecoverPanic bool + // Defaults to the Controller.RecoverPanic setting from the Manager if unset. + RecoverPanic *bool } // Controller implements a Kubernetes API. A Controller manages a work queue fed reconcile.Requests @@ -139,6 +140,10 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller return nil, err } + if options.RecoverPanic == nil { + options.RecoverPanic = mgr.GetControllerOptions().RecoverPanic + } + // Create controller with dependencies set return &controller.Controller{ Do: options.Reconciler, diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 8932cb35ca..b5d816bc28 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -25,11 +25,14 @@ import ( . "github.com/onsi/gomega" "go.uber.org/goleak" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/config/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" + internalcontroller "sigs.k8s.io/controller-runtime/pkg/internal/controller" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/runtime/inject" @@ -142,6 +145,39 @@ var _ = Describe("controller.Controller", func() { clientTransport.CloseIdleConnections() Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed()) }) + + It("should default RecoverPanic from the manager", func() { + m, err := manager.New(cfg, manager.Options{Controller: v1alpha1.ControllerConfigurationSpec{RecoverPanic: pointer.Bool(true)}}) + Expect(err).NotTo(HaveOccurred()) + + c, err := controller.New("new-controller", m, controller.Options{ + Reconciler: reconcile.Func(nil), + }) + Expect(err).NotTo(HaveOccurred()) + + ctrl, ok := c.(*internalcontroller.Controller) + Expect(ok).To(BeTrue()) + + Expect(ctrl.RecoverPanic).NotTo(BeNil()) + Expect(*ctrl.RecoverPanic).To(BeTrue()) + }) + + It("should not override RecoverPanic on the controller", func() { + m, err := manager.New(cfg, manager.Options{Controller: v1alpha1.ControllerConfigurationSpec{RecoverPanic: pointer.Bool(true)}}) + Expect(err).NotTo(HaveOccurred()) + + c, err := controller.New("new-controller", m, controller.Options{ + Reconciler: reconcile.Func(nil), + RecoverPanic: pointer.Bool(false), + }) + Expect(err).NotTo(HaveOccurred()) + + ctrl, ok := c.(*internalcontroller.Controller) + Expect(ok).To(BeTrue()) + + Expect(ctrl.RecoverPanic).NotTo(BeNil()) + Expect(*ctrl.RecoverPanic).To(BeFalse()) + }) }) }) diff --git a/pkg/controller/controllerutil/controllerutil.go b/pkg/controller/controllerutil/controllerutil.go index 99974eb959..344abcd288 100644 --- a/pkg/controller/controllerutil/controllerutil.go +++ b/pkg/controller/controllerutil/controllerutil.go @@ -76,8 +76,8 @@ func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Sch Kind: gvk.Kind, Name: owner.GetName(), UID: owner.GetUID(), - BlockOwnerDeletion: pointer.BoolPtr(true), - Controller: pointer.BoolPtr(true), + BlockOwnerDeletion: pointer.Bool(true), + Controller: pointer.Bool(true), } // Return early with an error if the object is already controlled. diff --git a/pkg/controller/controllerutil/controllerutil_test.go b/pkg/controller/controllerutil/controllerutil_test.go index cbcc33f532..d176c4fb6a 100644 --- a/pkg/controller/controllerutil/controllerutil_test.go +++ b/pkg/controller/controllerutil/controllerutil_test.go @@ -101,7 +101,6 @@ var _ = Describe("Controllerutil", func() { APIVersion: "extensions/v1beta1", UID: "foo-uid-2", })) - }) }) @@ -596,7 +595,7 @@ var _ = Describe("Controllerutil", func() { assertLocalDeployWasUpdated(nil) op, err = controllerutil.CreateOrPatch(context.TODO(), c, deploy, func() error { - deploy.Spec.Replicas = pointer.Int32Ptr(5) + deploy.Spec.Replicas = pointer.Int32(5) deploy.Status.Conditions = []appsv1.DeploymentCondition{{ Type: appsv1.DeploymentProgressing, Status: corev1.ConditionTrue, @@ -770,11 +769,15 @@ var _ = Describe("Controllerutil", func() { }) }) -const testFinalizer = "foo.bar.baz" -const testFinalizer1 = testFinalizer + "1" +const ( + testFinalizer = "foo.bar.baz" + testFinalizer1 = testFinalizer + "1" +) -var _ runtime.Object = &errRuntimeObj{} -var _ metav1.Object = &errMetaObj{} +var ( + _ runtime.Object = &errRuntimeObj{} + _ metav1.Object = &errMetaObj{} +) type errRuntimeObj struct { runtime.TypeMeta diff --git a/pkg/envtest/crd.go b/pkg/envtest/crd.go index 0aa5255c64..dc38b793b4 100644 --- a/pkg/envtest/crd.go +++ b/pkg/envtest/crd.go @@ -84,8 +84,10 @@ type CRDInstallOptions struct { WebhookOptions WebhookInstallOptions } -const defaultPollInterval = 100 * time.Millisecond -const defaultMaxWait = 10 * time.Second +const ( + defaultPollInterval = 100 * time.Millisecond + defaultMaxWait = 10 * time.Second +) // InstallCRDs installs a collection of CRDs into a cluster by reading the crd yaml files from a directory. func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]*apiextensionsv1.CustomResourceDefinition, error) { @@ -362,7 +364,7 @@ func modifyConversionWebhooks(crds []*apiextensionsv1.CustomResourceDefinition, if err != nil { return err } - url := pointer.StringPtr(fmt.Sprintf("https://%s/convert", hostPort)) + url := pointer.String(fmt.Sprintf("https://%s/convert", hostPort)) for i := range crds { // Continue if we're preserving unknown fields. diff --git a/pkg/internal/controller/controller.go b/pkg/internal/controller/controller.go index b67657bf0c..f7734695ce 100644 --- a/pkg/internal/controller/controller.go +++ b/pkg/internal/controller/controller.go @@ -92,7 +92,7 @@ type Controller struct { LogConstructor func(request *reconcile.Request) logr.Logger // RecoverPanic indicates whether the panic caused by reconcile should be recovered. - RecoverPanic bool + RecoverPanic *bool } // watchDescription contains all the information necessary to start a watch. @@ -106,7 +106,7 @@ type watchDescription struct { func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) { defer func() { if r := recover(); r != nil { - if c.RecoverPanic { + if c.RecoverPanic != nil && *c.RecoverPanic { for _, fn := range utilruntime.PanicHandlers { fn(r) } diff --git a/pkg/internal/controller/controller_test.go b/pkg/internal/controller/controller_test.go index 2c7726e2d5..5d3b1c9bea 100644 --- a/pkg/internal/controller/controller_test.go +++ b/pkg/internal/controller/controller_test.go @@ -33,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/cache/informertest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -114,7 +115,7 @@ var _ = Describe("controller", func() { defer func() { Expect(recover()).To(BeNil()) }() - ctrl.RecoverPanic = true + ctrl.RecoverPanic = pointer.Bool(true) ctrl.Do = reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) { var res *reconcile.Result return *res, nil diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index 073d252718..3e79f50bbd 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -409,6 +409,8 @@ func (cm *controllerManager) Start(ctx context.Context) (err error) { cm.Unlock() return errors.New("manager already started") } + cm.started = true + var ready bool defer func() { // Only unlock the manager if we haven't reached diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index 6b01d48293..f3b8443a95 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -1492,6 +1492,29 @@ var _ = Describe("manger.Manager", func() { Expect(err).NotTo(HaveOccurred()) Expect(m.Add(&failRec{})).To(HaveOccurred()) }) + + It("should fail if attempted to start a second time", func() { + m, err := New(cfg, Options{}) + Expect(err).NotTo(HaveOccurred()) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + defer GinkgoRecover() + Expect(m.Start(ctx)).NotTo(HaveOccurred()) + }() + // Wait for the Manager to start + Eventually(func() bool { + mgr, ok := m.(*controllerManager) + Expect(ok).To(BeTrue()) + return mgr.runnables.Caches.Started() + }).Should(BeTrue()) + + err = m.Start(ctx) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(Equal("manager already started")) + + }) }) Describe("SetFields", func() { It("should inject field values", func() { diff --git a/tools/setup-envtest/go.mod b/tools/setup-envtest/go.mod index a9d77722ee..29592281a0 100644 --- a/tools/setup-envtest/go.mod +++ b/tools/setup-envtest/go.mod @@ -13,15 +13,12 @@ require ( ) require ( - github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/nxadm/tail v1.4.8 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.26.0 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/tools/setup-envtest/go.sum b/tools/setup-envtest/go.sum index 43bbb76861..42347d8c1d 100644 --- a/tools/setup-envtest/go.sum +++ b/tools/setup-envtest/go.sum @@ -7,7 +7,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -39,7 +38,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -158,7 +156,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=