Merge pull request #2076 from RainbowMango/pr_update_controller-runtime
Update controller-runtime to v0.12.2
This commit is contained in:
commit
b14c6f452e
4
go.mod
4
go.mod
|
@ -11,7 +11,7 @@ require (
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||||
github.com/olekukonko/tablewriter v0.0.4
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3
|
github.com/onsi/ginkgo/v2 v2.1.3
|
||||||
github.com/onsi/gomega v1.17.0
|
github.com/onsi/gomega v1.18.1
|
||||||
github.com/opensearch-project/opensearch-go v1.1.0
|
github.com/opensearch-project/opensearch-go v1.1.0
|
||||||
github.com/prometheus/client_golang v1.12.1
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/spf13/cobra v1.4.0
|
github.com/spf13/cobra v1.4.0
|
||||||
|
@ -38,7 +38,7 @@ require (
|
||||||
k8s.io/kubectl v0.24.2
|
k8s.io/kubectl v0.24.2
|
||||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||||
sigs.k8s.io/cluster-api v1.0.1
|
sigs.k8s.io/cluster-api v1.0.1
|
||||||
sigs.k8s.io/controller-runtime v0.11.1
|
sigs.k8s.io/controller-runtime v0.12.2
|
||||||
sigs.k8s.io/kind v0.12.0
|
sigs.k8s.io/kind v0.12.0
|
||||||
sigs.k8s.io/mcs-api v0.1.0
|
sigs.k8s.io/mcs-api v0.1.0
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -660,6 +660,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
||||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
|
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
@ -669,8 +670,9 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
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/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
@ -1584,8 +1586,8 @@ sigs.k8s.io/cluster-api v1.0.1 h1:0YXQoemI4WnZF8RzT9T2vCtnXAi22rD4Fx1Tj2hhCEM=
|
||||||
sigs.k8s.io/cluster-api v1.0.1/go.mod h1:/LkJXtsvhxTV4U0z1Y2Y1Gr2xebJ0/ce09Ab2M0XU/U=
|
sigs.k8s.io/cluster-api v1.0.1/go.mod h1:/LkJXtsvhxTV4U0z1Y2Y1Gr2xebJ0/ce09Ab2M0XU/U=
|
||||||
sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
|
sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
|
||||||
sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
|
sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
|
||||||
sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU=
|
sigs.k8s.io/controller-runtime v0.12.2 h1:nqV02cvhbAj7tbt21bpPpTByrXGn2INHRsi39lXy9sE=
|
||||||
sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
|
sigs.k8s.io/controller-runtime v0.12.2/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0=
|
||||||
sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
|
sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
|
||||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
|
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
|
||||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
language: go
|
|
||||||
arch:
|
|
||||||
- amd64
|
|
||||||
- ppc64le
|
|
||||||
|
|
||||||
go:
|
|
||||||
- gotip
|
|
||||||
- 1.16.x
|
|
||||||
- 1.15.x
|
|
||||||
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
|
|
||||||
install: skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go mod tidy && git diff --exit-code go.mod go.sum
|
|
||||||
- make test
|
|
|
@ -1,3 +1,25 @@
|
||||||
|
## 1.18.1
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Add pointer support to HaveField matcher (#495) [79e41a3]
|
||||||
|
|
||||||
|
## 1.18.0
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Docs now live on the master branch in the docs folder which will make for easier PRs. The docs also use Ginkgo 2.0's new docs html/css/js. [2570272]
|
||||||
|
- New HaveValue matcher can handle actuals that are either values (in which case they are passed on unscathed) or pointers (in which case they are indirected). [Docs here.](https://onsi.github.io/gomega/#working-with-values) (#485) [bdc087c]
|
||||||
|
- Gmeasure has been declared GA [360db9d]
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Gomega now uses ioutil for Go 1.15 and lower (#492) - official support is only for the most recent two major versions of Go but this will unblock users who need to stay on older unsupported versions of Go. [c29c1c0]
|
||||||
|
|
||||||
|
## Maintenace
|
||||||
|
- Remove Travis workflow (#491) [72e6040]
|
||||||
|
- Upgrade to Ginkgo 2.0.0 GA [f383637]
|
||||||
|
- chore: fix description of HaveField matcher (#487) [2b4b2c0]
|
||||||
|
- use tools.go to ensure Ginkgo cli dependencies are included [f58a52b]
|
||||||
|
- remove dockerfile and simplify github actions to match ginkgo's actions [3f8160d]
|
||||||
|
|
||||||
## 1.17.0
|
## 1.17.0
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
FROM golang:1.15
|
|
|
@ -1,33 +0,0 @@
|
||||||
###### Help ###################################################################
|
|
||||||
|
|
||||||
.DEFAULT_GOAL = help
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
|
|
||||||
help: ## list Makefile targets
|
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
|
||||||
|
|
||||||
###### Targets ################################################################
|
|
||||||
|
|
||||||
test: version download fmt vet ginkgo ## Runs all build, static analysis, and test steps
|
|
||||||
|
|
||||||
download: ## Download dependencies
|
|
||||||
go mod download
|
|
||||||
|
|
||||||
vet: ## Run static code analysis
|
|
||||||
go vet ./...
|
|
||||||
|
|
||||||
ginkgo: ## Run tests using Ginkgo
|
|
||||||
go run github.com/onsi/ginkgo/ginkgo -p -r --randomizeAllSpecs --failOnPending --randomizeSuites --race
|
|
||||||
|
|
||||||
fmt: ## Checks that the code is formatted correcty
|
|
||||||
@@if [ -n "$$(gofmt -s -e -l -d .)" ]; then \
|
|
||||||
echo "gofmt check failed: run 'gofmt -s -e -l -w .'"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker_test: ## Run tests in a container via docker-compose
|
|
||||||
docker-compose build test && docker-compose run --rm test make test
|
|
||||||
|
|
||||||
version: ## Display the version of Go
|
|
||||||
@@go version
|
|
|
@ -1,10 +0,0 @@
|
||||||
version: '3.0'
|
|
||||||
|
|
||||||
services:
|
|
||||||
test:
|
|
||||||
build:
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
context: .
|
|
||||||
working_dir: /app
|
|
||||||
volumes:
|
|
||||||
- ${PWD}:/app
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/onsi/gomega/types"
|
"github.com/onsi/gomega/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const GOMEGA_VERSION = "1.17.0"
|
const GOMEGA_VERSION = "1.18.1"
|
||||||
|
|
||||||
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
|
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
|
||||||
If you're using Ginkgo then you probably forgot to put your assertion in an It().
|
If you're using Ginkgo then you probably forgot to put your assertion in an It().
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//go:build go1.16
|
||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
// Package gutil is a replacement for ioutil, which should not be used in new
|
||||||
|
// code as of Go 1.16. With Go 1.16 and higher, this implementation
|
||||||
|
// uses the ioutil replacement functions in "io" and "os" with some
|
||||||
|
// Gomega specifics. This means that we should not get deprecation warnings
|
||||||
|
// for ioutil when they are added.
|
||||||
|
package gutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NopCloser(r io.Reader) io.ReadCloser {
|
||||||
|
return io.NopCloser(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAll(r io.Reader) ([]byte, error) {
|
||||||
|
return io.ReadAll(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDir(dirname string) ([]string, error) {
|
||||||
|
entries, err := os.ReadDir(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, entry := range entries {
|
||||||
|
names = append(names, entry.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFile(filename string) ([]byte, error) {
|
||||||
|
return os.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MkdirTemp(dir, pattern string) (string, error) {
|
||||||
|
return os.MkdirTemp(dir, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteFile(filename string, data []byte) error {
|
||||||
|
return os.WriteFile(filename, data, 0644)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
//go:build !go1.16
|
||||||
|
// +build !go1.16
|
||||||
|
|
||||||
|
// Package gutil is a replacement for ioutil, which should not be used in new
|
||||||
|
// code as of Go 1.16. With Go 1.15 and lower, this implementation
|
||||||
|
// uses the ioutil functions, meaning that although Gomega is not officially
|
||||||
|
// supported on these versions, it is still likely to work.
|
||||||
|
package gutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NopCloser(r io.Reader) io.ReadCloser {
|
||||||
|
return ioutil.NopCloser(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAll(r io.Reader) ([]byte, error) {
|
||||||
|
return ioutil.ReadAll(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDir(dirname string) ([]string, error) {
|
||||||
|
files, err := ioutil.ReadDir(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, file := range files {
|
||||||
|
names = append(names, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFile(filename string) ([]byte, error) {
|
||||||
|
return ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MkdirTemp(dir, pattern string) (string, error) {
|
||||||
|
return ioutil.TempDir(dir, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteFile(filename string, data []byte) error {
|
||||||
|
return ioutil.WriteFile(filename, data, 0644)
|
||||||
|
}
|
|
@ -357,12 +357,12 @@ func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher {
|
||||||
// type Person struct {
|
// type Person struct {
|
||||||
// FirstName string
|
// FirstName string
|
||||||
// LastName string
|
// LastName string
|
||||||
// DOB time.Time
|
// DOB time.Time
|
||||||
// }
|
// }
|
||||||
// Expect(book).To(HaveField("Title", "Les Miserables"))
|
// Expect(book).To(HaveField("Title", "Les Miserables"))
|
||||||
// Expect(book).To(HaveField("Title", ContainSubstring("Les"))
|
// Expect(book).To(HaveField("Title", ContainSubstring("Les"))
|
||||||
// Expect(book).To(HaveField("Person.FirstName", Equal("Victor"))
|
// Expect(book).To(HaveField("Author.FirstName", Equal("Victor"))
|
||||||
// Expect(book).To(HaveField("Person.DOB.Year()", BeNumerically("<", 1900))
|
// Expect(book).To(HaveField("Author.DOB.Year()", BeNumerically("<", 1900))
|
||||||
func HaveField(field string, expected interface{}) types.GomegaMatcher {
|
func HaveField(field string, expected interface{}) types.GomegaMatcher {
|
||||||
return &matchers.HaveFieldMatcher{
|
return &matchers.HaveFieldMatcher{
|
||||||
Field: field,
|
Field: field,
|
||||||
|
@ -370,6 +370,26 @@ func HaveField(field string, expected interface{}) types.GomegaMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HaveValue applies the given matcher to the value of actual, optionally and
|
||||||
|
// repeatedly dereferencing pointers or taking the concrete value of interfaces.
|
||||||
|
// Thus, the matcher will always be applied to non-pointer and non-interface
|
||||||
|
// values only. HaveValue will fail with an error if a pointer or interface is
|
||||||
|
// nil. It will also fail for more than 31 pointer or interface dereferences to
|
||||||
|
// guard against mistakenly applying it to arbitrarily deep linked pointers.
|
||||||
|
//
|
||||||
|
// HaveValue differs from gstruct.PointTo in that it does not expect actual to
|
||||||
|
// be a pointer (as gstruct.PointTo does) but instead also accepts non-pointer
|
||||||
|
// and even interface values.
|
||||||
|
//
|
||||||
|
// actual := 42
|
||||||
|
// Expect(actual).To(HaveValue(42))
|
||||||
|
// Expect(&actual).To(HaveValue(42))
|
||||||
|
func HaveValue(matcher types.GomegaMatcher) types.GomegaMatcher {
|
||||||
|
return &matchers.HaveValueMatcher{
|
||||||
|
Matcher: matcher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//BeNumerically performs numerical assertions in a type-agnostic way.
|
//BeNumerically performs numerical assertions in a type-agnostic way.
|
||||||
//Actual and expected should be numbers, though the specific type of
|
//Actual and expected should be numbers, though the specific type of
|
||||||
//number is irrelevant (float32, float64, uint8, etc...).
|
//number is irrelevant (float32, float64, uint8, etc...).
|
||||||
|
|
|
@ -12,6 +12,13 @@ func extractField(actual interface{}, field string) (interface{}, error) {
|
||||||
fields := strings.SplitN(field, ".", 2)
|
fields := strings.SplitN(field, ".", 2)
|
||||||
actualValue := reflect.ValueOf(actual)
|
actualValue := reflect.ValueOf(actual)
|
||||||
|
|
||||||
|
if actualValue.Kind() == reflect.Ptr {
|
||||||
|
actualValue = actualValue.Elem()
|
||||||
|
}
|
||||||
|
if actualValue == (reflect.Value{}) {
|
||||||
|
return nil, fmt.Errorf("HaveField encountered nil while dereferencing a pointer of type %T.", actual)
|
||||||
|
}
|
||||||
|
|
||||||
if actualValue.Kind() != reflect.Struct {
|
if actualValue.Kind() != reflect.Struct {
|
||||||
return nil, fmt.Errorf("HaveField encountered:\n%s\nWhich is not a struct.", format.Object(actual, 1))
|
return nil, fmt.Errorf("HaveField encountered:\n%s\nWhich is not a struct.", format.Object(actual, 1))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ package matchers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
"github.com/onsi/gomega/format"
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/onsi/gomega/internal/gutil"
|
||||||
"github.com/onsi/gomega/types"
|
"github.com/onsi/gomega/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) {
|
||||||
if a.Body != nil {
|
if a.Body != nil {
|
||||||
defer a.Body.Close()
|
defer a.Body.Close()
|
||||||
var err error
|
var err error
|
||||||
matcher.cachedBody, err = io.ReadAll(a.Body)
|
matcher.cachedBody, err = gutil.ReadAll(a.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ package matchers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/onsi/gomega/format"
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/onsi/gomega/internal/gutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HaveHTTPStatusMatcher struct {
|
type HaveHTTPStatusMatcher struct {
|
||||||
|
@ -78,7 +78,7 @@ func formatHttpResponse(input interface{}) string {
|
||||||
body := "<nil>"
|
body := "<nil>"
|
||||||
if resp.Body != nil {
|
if resp.Body != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := gutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data = []byte("<error reading body>")
|
data = []byte("<error reading body>")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package matchers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/onsi/gomega/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxIndirections = 31
|
||||||
|
|
||||||
|
type HaveValueMatcher struct {
|
||||||
|
Matcher types.GomegaMatcher // the matcher to apply to the "resolved" actual value.
|
||||||
|
resolvedActual interface{} // the ("resolved") value.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HaveValueMatcher) Match(actual interface{}) (bool, error) {
|
||||||
|
val := reflect.ValueOf(actual)
|
||||||
|
for allowedIndirs := maxIndirections; allowedIndirs > 0; allowedIndirs-- {
|
||||||
|
// return an error if value isn't valid. Please note that we cannot
|
||||||
|
// check for nil here, as we might not deal with a pointer or interface
|
||||||
|
// at this point.
|
||||||
|
if !val.IsValid() {
|
||||||
|
return false, errors.New(format.Message(
|
||||||
|
actual, "not to be <nil>"))
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
// resolve pointers and interfaces to their values, then rinse and
|
||||||
|
// repeat.
|
||||||
|
if val.IsNil() {
|
||||||
|
return false, errors.New(format.Message(
|
||||||
|
actual, "not to be <nil>"))
|
||||||
|
}
|
||||||
|
val = val.Elem()
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// forward the final value to the specified matcher.
|
||||||
|
m.resolvedActual = val.Interface()
|
||||||
|
return m.Matcher.Match(m.resolvedActual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// too many indirections: extreme star gazing, indeed...?
|
||||||
|
return false, errors.New(format.Message(actual, "too many indirections"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HaveValueMatcher) FailureMessage(_ interface{}) (message string) {
|
||||||
|
return m.Matcher.FailureMessage(m.resolvedActual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HaveValueMatcher) NegatedFailureMessage(_ interface{}) (message string) {
|
||||||
|
return m.Matcher.NegatedFailureMessage(m.resolvedActual)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build tools
|
||||||
|
// +build tools
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/onsi/ginkgo/v2/ginkgo"
|
||||||
|
)
|
|
@ -291,11 +291,12 @@ github.com/onsi/ginkgo/v2/internal/parallel_support
|
||||||
github.com/onsi/ginkgo/v2/internal/testingtproxy
|
github.com/onsi/ginkgo/v2/internal/testingtproxy
|
||||||
github.com/onsi/ginkgo/v2/reporters
|
github.com/onsi/ginkgo/v2/reporters
|
||||||
github.com/onsi/ginkgo/v2/types
|
github.com/onsi/ginkgo/v2/types
|
||||||
# github.com/onsi/gomega v1.17.0
|
# github.com/onsi/gomega v1.18.1
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/onsi/gomega
|
github.com/onsi/gomega
|
||||||
github.com/onsi/gomega/format
|
github.com/onsi/gomega/format
|
||||||
github.com/onsi/gomega/internal
|
github.com/onsi/gomega/internal
|
||||||
|
github.com/onsi/gomega/internal/gutil
|
||||||
github.com/onsi/gomega/matchers
|
github.com/onsi/gomega/matchers
|
||||||
github.com/onsi/gomega/matchers/support/goraph/bipartitegraph
|
github.com/onsi/gomega/matchers/support/goraph/bipartitegraph
|
||||||
github.com/onsi/gomega/matchers/support/goraph/edge
|
github.com/onsi/gomega/matchers/support/goraph/edge
|
||||||
|
@ -1459,13 +1460,14 @@ sigs.k8s.io/cluster-api/feature
|
||||||
sigs.k8s.io/cluster-api/util/certs
|
sigs.k8s.io/cluster-api/util/certs
|
||||||
sigs.k8s.io/cluster-api/util/secret
|
sigs.k8s.io/cluster-api/util/secret
|
||||||
sigs.k8s.io/cluster-api/util/version
|
sigs.k8s.io/cluster-api/util/version
|
||||||
# sigs.k8s.io/controller-runtime v0.11.1
|
# sigs.k8s.io/controller-runtime v0.12.2
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
sigs.k8s.io/controller-runtime
|
sigs.k8s.io/controller-runtime
|
||||||
sigs.k8s.io/controller-runtime/pkg/builder
|
sigs.k8s.io/controller-runtime/pkg/builder
|
||||||
sigs.k8s.io/controller-runtime/pkg/cache
|
sigs.k8s.io/controller-runtime/pkg/cache
|
||||||
sigs.k8s.io/controller-runtime/pkg/cache/internal
|
sigs.k8s.io/controller-runtime/pkg/cache/internal
|
||||||
sigs.k8s.io/controller-runtime/pkg/certwatcher
|
sigs.k8s.io/controller-runtime/pkg/certwatcher
|
||||||
|
sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics
|
||||||
sigs.k8s.io/controller-runtime/pkg/client
|
sigs.k8s.io/controller-runtime/pkg/client
|
||||||
sigs.k8s.io/controller-runtime/pkg/client/apiutil
|
sigs.k8s.io/controller-runtime/pkg/client/apiutil
|
||||||
sigs.k8s.io/controller-runtime/pkg/client/config
|
sigs.k8s.io/controller-runtime/pkg/client/config
|
||||||
|
|
|
@ -7,6 +7,7 @@ linters:
|
||||||
- depguard
|
- depguard
|
||||||
- dogsled
|
- dogsled
|
||||||
- errcheck
|
- errcheck
|
||||||
|
- errorlint
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- goconst
|
- goconst
|
||||||
- gocritic
|
- gocritic
|
||||||
|
@ -34,6 +35,7 @@ linters:
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
|
@ -59,9 +61,13 @@ linters-settings:
|
||||||
- pkg: sigs.k8s.io/controller-runtime
|
- pkg: sigs.k8s.io/controller-runtime
|
||||||
alias: ctrl
|
alias: ctrl
|
||||||
staticcheck:
|
staticcheck:
|
||||||
go: "1.17"
|
go: "1.18"
|
||||||
stylecheck:
|
stylecheck:
|
||||||
go: "1.17"
|
go: "1.18"
|
||||||
|
depguard:
|
||||||
|
include-go-root: true
|
||||||
|
packages:
|
||||||
|
- io/ioutil # https://go.dev/doc/go1.16#ioutil
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
@ -121,6 +127,11 @@ issues:
|
||||||
- linters:
|
- linters:
|
||||||
- gocritic
|
- gocritic
|
||||||
text: "singleCaseSwitch: should rewrite switch statement to if statement"
|
text: "singleCaseSwitch: should rewrite switch statement to if statement"
|
||||||
|
# It considers all file access to a filename that comes from a variable problematic,
|
||||||
|
# which is naiv at best.
|
||||||
|
- linters:
|
||||||
|
- gosec
|
||||||
|
text: "G304: Potential file inclusion via variable"
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
|
|
|
@ -30,13 +30,13 @@ on your situation.
|
||||||
take this approach: the StatefulSet controller appends a specific number
|
take this approach: the StatefulSet controller appends a specific number
|
||||||
to each pod that it creates, while the Deployment controller hashes the
|
to each pod that it creates, while the Deployment controller hashes the
|
||||||
pod template spec and appends that.
|
pod template spec and appends that.
|
||||||
|
|
||||||
- In the few cases when you cannot take advantage of deterministic names
|
- In the few cases when you cannot take advantage of deterministic names
|
||||||
(e.g. when using generateName), it may be useful in to track which
|
(e.g. when using generateName), it may be useful in to track which
|
||||||
actions you took, and assume that they need to be repeated if they don't
|
actions you took, and assume that they need to be repeated if they don't
|
||||||
occur after a given time (e.g. using a requeue result). This is what
|
occur after a given time (e.g. using a requeue result). This is what
|
||||||
the ReplicaSet controller does.
|
the ReplicaSet controller does.
|
||||||
|
|
||||||
In general, write your controller with the assumption that information
|
In general, write your controller with the assumption that information
|
||||||
will eventually be correct, but may be slightly out of date. Make sure
|
will eventually be correct, but may be slightly out of date. Make sure
|
||||||
that your reconcile function enforces the entire state of the world each
|
that your reconcile function enforces the entire state of the world each
|
||||||
|
@ -48,9 +48,9 @@ generally cover most circumstances.
|
||||||
### Q: Where's the fake client? How do I use it?
|
### Q: Where's the fake client? How do I use it?
|
||||||
|
|
||||||
**A**: The fake client
|
**A**: The fake client
|
||||||
[exists](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/client/fake),
|
[exists](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client/fake),
|
||||||
but we generally recommend using
|
but we generally recommend using
|
||||||
[envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
|
[envtest.Environment](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
|
||||||
to test against a real API server. In our experience, tests using fake
|
to test against a real API server. In our experience, tests using fake
|
||||||
clients gradually re-implement poorly-written impressions of a real API
|
clients gradually re-implement poorly-written impressions of a real API
|
||||||
server, which leads to hard-to-maintain, complex test code.
|
server, which leads to hard-to-maintain, complex test code.
|
||||||
|
@ -58,7 +58,7 @@ server, which leads to hard-to-maintain, complex test code.
|
||||||
### Q: How should I write tests? Any suggestions for getting started?
|
### Q: How should I write tests? Any suggestions for getting started?
|
||||||
|
|
||||||
- Use the aforementioned
|
- Use the aforementioned
|
||||||
[envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
|
[envtest.Environment](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
|
||||||
to spin up a real API server instead of trying to mock one out.
|
to spin up a real API server instead of trying to mock one out.
|
||||||
|
|
||||||
- Structure your tests to check that the state of the world is as you
|
- Structure your tests to check that the state of the world is as you
|
||||||
|
@ -77,5 +77,5 @@ mapping between Go types and group-version-kinds in Kubernetes. In
|
||||||
general, your application should have its own Scheme containing the types
|
general, your application should have its own Scheme containing the types
|
||||||
from the API groups that it needs (be they Kubernetes types or your own).
|
from the API groups that it needs (be they Kubernetes types or your own).
|
||||||
See the [scheme builder
|
See the [scheme builder
|
||||||
docs](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/scheme) for
|
docs](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/scheme) for
|
||||||
more information.
|
more information.
|
||||||
|
|
|
@ -27,6 +27,7 @@ aliases:
|
||||||
- vincepri
|
- vincepri
|
||||||
- alexeldeib
|
- alexeldeib
|
||||||
- varshaprasad96
|
- varshaprasad96
|
||||||
|
- fillzpp
|
||||||
|
|
||||||
# folks to can approve things in the directly-ported
|
# folks to can approve things in the directly-ported
|
||||||
# testing_frameworks portions of the codebase
|
# testing_frameworks portions of the codebase
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[](https://goreportcard.com/report/sigs.k8s.io/controller-runtime)
|
[](https://goreportcard.com/report/sigs.k8s.io/controller-runtime)
|
||||||
[](https://godoc.org/sigs.k8s.io/controller-runtime)
|
[](https://pkg.go.dev/sigs.k8s.io/controller-runtime)
|
||||||
|
|
||||||
# Kubernetes controller-runtime Project
|
# Kubernetes controller-runtime Project
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ limitations under the License.
|
||||||
//
|
//
|
||||||
// Logging (pkg/log) in controller-runtime is done via structured logs, using a
|
// Logging (pkg/log) in controller-runtime is done via structured logs, using a
|
||||||
// log set of interfaces called logr
|
// log set of interfaces called logr
|
||||||
// (https://godoc.org/github.com/go-logr/logr). While controller-runtime
|
// (https://pkg.go.dev/github.com/go-logr/logr). While controller-runtime
|
||||||
// provides easy setup for using Zap (https://go.uber.org/zap, pkg/log/zap),
|
// provides easy setup for using Zap (https://go.uber.org/zap, pkg/log/zap),
|
||||||
// you can provide any implementation of logr as the base logger for
|
// you can provide any implementation of logr as the base logger for
|
||||||
// controller-runtime.
|
// controller-runtime.
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||||
|
@ -148,9 +149,9 @@ func (blder *Builder) WithOptions(options controller.Options) *Builder {
|
||||||
return blder
|
return blder
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLogger overrides the controller options's logger used.
|
// WithLogConstructor overrides the controller options's LogConstructor.
|
||||||
func (blder *Builder) WithLogger(log logr.Logger) *Builder {
|
func (blder *Builder) WithLogConstructor(logConstructor func(*reconcile.Request) logr.Logger) *Builder {
|
||||||
blder.ctrlOptions.Log = log
|
blder.ctrlOptions.LogConstructor = logConstructor
|
||||||
return blder
|
return blder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,13 +305,31 @@ func (blder *Builder) doController(r reconcile.Reconciler) error {
|
||||||
ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout
|
ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controllerName := blder.getControllerName(gvk)
|
||||||
|
|
||||||
// Setup the logger.
|
// Setup the logger.
|
||||||
if ctrlOptions.Log.GetSink() == nil {
|
if ctrlOptions.LogConstructor == nil {
|
||||||
ctrlOptions.Log = blder.mgr.GetLogger()
|
log := blder.mgr.GetLogger().WithValues(
|
||||||
|
"controller", controllerName,
|
||||||
|
"controllerGroup", gvk.Group,
|
||||||
|
"controllerKind", gvk.Kind,
|
||||||
|
)
|
||||||
|
|
||||||
|
lowerCamelCaseKind := strings.ToLower(gvk.Kind[:1]) + gvk.Kind[1:]
|
||||||
|
|
||||||
|
ctrlOptions.LogConstructor = func(req *reconcile.Request) logr.Logger {
|
||||||
|
log := log
|
||||||
|
if req != nil {
|
||||||
|
log = log.WithValues(
|
||||||
|
lowerCamelCaseKind, klog.KRef(req.Namespace, req.Name),
|
||||||
|
"namespace", req.Namespace, "name", req.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return log
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctrlOptions.Log = ctrlOptions.Log.WithValues("reconciler group", gvk.Group, "reconciler kind", gvk.Kind)
|
|
||||||
|
|
||||||
// Build the controller and return.
|
// Build the controller and return.
|
||||||
blder.ctrl, err = newController(blder.getControllerName(gvk), blder.mgr, ctrlOptions)
|
blder.ctrl, err = newController(controllerName, blder.mgr, ctrlOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,29 @@ var (
|
||||||
// metav1.PartialObjectMetadata to the client when fetching objects in your
|
// metav1.PartialObjectMetadata to the client when fetching objects in your
|
||||||
// reconciler, otherwise you'll end up with a duplicate structured or
|
// reconciler, otherwise you'll end up with a duplicate structured or
|
||||||
// unstructured cache.
|
// unstructured cache.
|
||||||
|
//
|
||||||
|
// When watching a resource with OnlyMetadata, for example the v1.Pod, you
|
||||||
|
// should not Get and List using the v1.Pod type. Instead, you should use
|
||||||
|
// the special metav1.PartialObjectMetadata type.
|
||||||
|
//
|
||||||
|
// ❌ Incorrect:
|
||||||
|
//
|
||||||
|
// pod := &v1.Pod{}
|
||||||
|
// mgr.GetClient().Get(ctx, nsAndName, pod)
|
||||||
|
//
|
||||||
|
// ✅ Correct:
|
||||||
|
//
|
||||||
|
// pod := &metav1.PartialObjectMetadata{}
|
||||||
|
// pod.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
|
// Group: "",
|
||||||
|
// Version: "v1",
|
||||||
|
// Kind: "Pod",
|
||||||
|
// })
|
||||||
|
// mgr.GetClient().Get(ctx, nsAndName, pod)
|
||||||
|
//
|
||||||
|
// In the first case, controller-runtime will create another cache for the
|
||||||
|
// concrete type on top of the metadata cache; this increases memory
|
||||||
|
// consumption and leads to race conditions as caches are not in sync.
|
||||||
OnlyMetadata = projectAs(projectAsMetadata)
|
OnlyMetadata = projectAs(projectAsMetadata)
|
||||||
|
|
||||||
_ ForOption = OnlyMetadata
|
_ ForOption = OnlyMetadata
|
||||||
|
|
|
@ -128,6 +128,18 @@ type Options struct {
|
||||||
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
|
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
|
||||||
// otherwise you will mutate the object in the cache.
|
// otherwise you will mutate the object in the cache.
|
||||||
UnsafeDisableDeepCopyByObject DisableDeepCopyByObject
|
UnsafeDisableDeepCopyByObject DisableDeepCopyByObject
|
||||||
|
|
||||||
|
// TransformByObject is a map from GVKs to transformer functions which
|
||||||
|
// get applied when objects of the transformation are about to be committed
|
||||||
|
// to cache.
|
||||||
|
//
|
||||||
|
// This function is called both for new objects to enter the cache,
|
||||||
|
// and for updated objects.
|
||||||
|
TransformByObject TransformByObject
|
||||||
|
|
||||||
|
// DefaultTransform is the transform used for all GVKs which do
|
||||||
|
// not have an explicit transform func set in TransformByObject
|
||||||
|
DefaultTransform toolscache.TransformFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultResyncTime = 10 * time.Hour
|
var defaultResyncTime = 10 * time.Hour
|
||||||
|
@ -146,7 +158,12 @@ func New(config *rest.Config, opts Options) (Cache, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK)
|
transformByGVK, err := convertToTransformByKindAndGVK(opts.TransformByObject, opts.DefaultTransform, opts.Scheme)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK, transformByGVK)
|
||||||
return &informerCache{InformersMap: im}, nil
|
return &informerCache{InformersMap: im}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,3 +258,18 @@ func convertToDisableDeepCopyByGVK(disableDeepCopyByObject DisableDeepCopyByObje
|
||||||
}
|
}
|
||||||
return disableDeepCopyByGVK, nil
|
return disableDeepCopyByGVK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransformByObject associate a client.Object's GVK to a transformer function
|
||||||
|
// to be applied when storing the object into the cache.
|
||||||
|
type TransformByObject map[client.Object]toolscache.TransformFunc
|
||||||
|
|
||||||
|
func convertToTransformByKindAndGVK(t TransformByObject, defaultTransform toolscache.TransformFunc, scheme *runtime.Scheme) (internal.TransformFuncByObject, error) {
|
||||||
|
result := internal.NewTransformFuncByObject()
|
||||||
|
for obj, transformation := range t {
|
||||||
|
if err := result.Set(obj, scheme, transformation); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.SetDefault(defaultTransform)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -96,11 +96,11 @@ func (ip *informerCache) objectTypeForListObject(list client.ObjectList) (*schem
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(gvk.Kind, "List") {
|
|
||||||
return nil, nil, fmt.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
|
|
||||||
}
|
|
||||||
// we need the non-list GVK, so chop off the "List" from the end of the kind
|
// we need the non-list GVK, so chop off the "List" from the end of the kind
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
if strings.HasSuffix(gvk.Kind, "List") && apimeta.IsListType(list) {
|
||||||
|
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
||||||
|
}
|
||||||
|
|
||||||
_, isUnstructured := list.(*unstructured.UnstructuredList)
|
_, isUnstructured := list.(*unstructured.UnstructuredList)
|
||||||
var cacheTypeObj runtime.Object
|
var cacheTypeObj runtime.Object
|
||||||
if isUnstructured {
|
if isUnstructured {
|
||||||
|
@ -193,8 +193,8 @@ func indexByField(indexer Informer, field string, extractor client.IndexerFunc)
|
||||||
rawVals := extractor(obj)
|
rawVals := extractor(obj)
|
||||||
var vals []string
|
var vals []string
|
||||||
if ns == "" {
|
if ns == "" {
|
||||||
// if we're not doubling the keys for the namespaced case, just re-use what was returned to us
|
// if we're not doubling the keys for the namespaced case, just create a new slice with same length
|
||||||
vals = rawVals
|
vals = make([]string, len(rawVals))
|
||||||
} else {
|
} else {
|
||||||
// if we need to add non-namespaced versions too, double the length
|
// if we need to add non-namespaced versions too, double the length
|
||||||
vals = make([]string, len(rawVals)*2)
|
vals = make([]string, len(rawVals)*2)
|
||||||
|
|
|
@ -52,11 +52,12 @@ func NewInformersMap(config *rest.Config,
|
||||||
namespace string,
|
namespace string,
|
||||||
selectors SelectorsByGVK,
|
selectors SelectorsByGVK,
|
||||||
disableDeepCopy DisableDeepCopyByGVK,
|
disableDeepCopy DisableDeepCopyByGVK,
|
||||||
|
transformers TransformFuncByObject,
|
||||||
) *InformersMap {
|
) *InformersMap {
|
||||||
return &InformersMap{
|
return &InformersMap{
|
||||||
structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),
|
structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||||
unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),
|
unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||||
metadata: newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),
|
metadata: newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||||
|
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
}
|
}
|
||||||
|
@ -108,18 +109,18 @@ func (m *InformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, obj
|
||||||
|
|
||||||
// newStructuredInformersMap creates a new InformersMap for structured objects.
|
// newStructuredInformersMap creates a new InformersMap for structured objects.
|
||||||
func newStructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
func newStructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK) *specificInformersMap {
|
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createStructuredListWatch)
|
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createStructuredListWatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUnstructuredInformersMap creates a new InformersMap for unstructured objects.
|
// newUnstructuredInformersMap creates a new InformersMap for unstructured objects.
|
||||||
func newUnstructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
func newUnstructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK) *specificInformersMap {
|
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createUnstructuredListWatch)
|
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createUnstructuredListWatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newMetadataInformersMap creates a new InformersMap for metadata-only objects.
|
// newMetadataInformersMap creates a new InformersMap for metadata-only objects.
|
||||||
func newMetadataInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
func newMetadataInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK) *specificInformersMap {
|
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createMetadataListWatch)
|
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createMetadataListWatch)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,9 @@ func newSpecificInformersMap(config *rest.Config,
|
||||||
namespace string,
|
namespace string,
|
||||||
selectors SelectorsByGVK,
|
selectors SelectorsByGVK,
|
||||||
disableDeepCopy DisableDeepCopyByGVK,
|
disableDeepCopy DisableDeepCopyByGVK,
|
||||||
createListWatcher createListWatcherFunc) *specificInformersMap {
|
transformers TransformFuncByObject,
|
||||||
|
createListWatcher createListWatcherFunc,
|
||||||
|
) *specificInformersMap {
|
||||||
ip := &specificInformersMap{
|
ip := &specificInformersMap{
|
||||||
config: config,
|
config: config,
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
|
@ -68,6 +70,7 @@ func newSpecificInformersMap(config *rest.Config,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
selectors: selectors.forGVK,
|
selectors: selectors.forGVK,
|
||||||
disableDeepCopy: disableDeepCopy,
|
disableDeepCopy: disableDeepCopy,
|
||||||
|
transformers: transformers,
|
||||||
}
|
}
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
@ -135,6 +138,9 @@ type specificInformersMap struct {
|
||||||
|
|
||||||
// disableDeepCopy indicates not to deep copy objects during get or list objects.
|
// disableDeepCopy indicates not to deep copy objects during get or list objects.
|
||||||
disableDeepCopy DisableDeepCopyByGVK
|
disableDeepCopy DisableDeepCopyByGVK
|
||||||
|
|
||||||
|
// transform funcs are applied to objects before they are committed to the cache
|
||||||
|
transformers TransformFuncByObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
|
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
|
||||||
|
@ -227,6 +233,12 @@ func (ip *specificInformersMap) addInformerToMap(gvk schema.GroupVersionKind, ob
|
||||||
ni := cache.NewSharedIndexInformer(lw, obj, resyncPeriod(ip.resync)(), cache.Indexers{
|
ni := cache.NewSharedIndexInformer(lw, obj, resyncPeriod(ip.resync)(), cache.Indexers{
|
||||||
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
|
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check to see if there is a transformer for this gvk
|
||||||
|
if err := ni.SetTransform(ip.transformers.Get(gvk)); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
rm, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
rm, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
|
|
50
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/transformers.go
generated
vendored
Normal file
50
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/transformers.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransformFuncByObject provides access to the correct transform function for
|
||||||
|
// any given GVK.
|
||||||
|
type TransformFuncByObject interface {
|
||||||
|
Set(runtime.Object, *runtime.Scheme, cache.TransformFunc) error
|
||||||
|
Get(schema.GroupVersionKind) cache.TransformFunc
|
||||||
|
SetDefault(transformer cache.TransformFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformFuncByGVK struct {
|
||||||
|
defaultTransform cache.TransformFunc
|
||||||
|
transformers map[schema.GroupVersionKind]cache.TransformFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransformFuncByObject creates a new TransformFuncByObject instance.
|
||||||
|
func NewTransformFuncByObject() TransformFuncByObject {
|
||||||
|
return &transformFuncByGVK{
|
||||||
|
transformers: make(map[schema.GroupVersionKind]cache.TransformFunc),
|
||||||
|
defaultTransform: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transformFuncByGVK) SetDefault(transformer cache.TransformFunc) {
|
||||||
|
t.defaultTransform = transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transformFuncByGVK) Set(obj runtime.Object, scheme *runtime.Scheme, transformer cache.TransformFunc) error {
|
||||||
|
gvk, err := apiutil.GVKForObject(obj, scheme)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.transformers[gvk] = transformer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t transformFuncByGVK) Get(gvk schema.GroupVersionKind) cache.TransformFunc {
|
||||||
|
if val, ok := t.transformers[gvk]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return t.defaultTransform
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc {
|
||||||
// create a cache for cluster scoped resources
|
// create a cache for cluster scoped resources
|
||||||
gCache, err := New(config, opts)
|
gCache, err := New(config, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating global cache %v", err)
|
return nil, fmt.Errorf("error creating global cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ns := range namespaces {
|
for _, ns := range namespaces {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics"
|
||||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,8 +117,10 @@ func (cw *CertWatcher) Watch() {
|
||||||
// and updates the current certificate on the watcher. If a callback is set, it
|
// and updates the current certificate on the watcher. If a callback is set, it
|
||||||
// is invoked with the new certificate.
|
// is invoked with the new certificate.
|
||||||
func (cw *CertWatcher) ReadCertificate() error {
|
func (cw *CertWatcher) ReadCertificate() error {
|
||||||
|
metrics.ReadCertificateTotal.Inc()
|
||||||
cert, err := tls.LoadX509KeyPair(cw.certPath, cw.keyPath)
|
cert, err := tls.LoadX509KeyPair(cw.certPath, cw.keyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
metrics.ReadCertificateErrors.Inc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
vendor/sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics/metrics.go
generated
vendored
Normal file
45
vendor/sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics/metrics.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ReadCertificateTotal is a prometheus counter metrics which holds the total
|
||||||
|
// number of certificate reads.
|
||||||
|
ReadCertificateTotal = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "certwatcher_read_certificate_total",
|
||||||
|
Help: "Total number of certificate reads",
|
||||||
|
})
|
||||||
|
|
||||||
|
// ReadCertificateErrors is a prometheus counter metrics which holds the total
|
||||||
|
// number of errors from certificate read.
|
||||||
|
ReadCertificateErrors = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "certwatcher_read_certificate_errors_total",
|
||||||
|
Help: "Total number of certificate read errors",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
metrics.Registry.MustRegister(
|
||||||
|
ReadCertificateTotal,
|
||||||
|
ReadCertificateErrors,
|
||||||
|
)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package apiutil
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
@ -38,7 +39,8 @@ type dynamicRESTMapper struct {
|
||||||
|
|
||||||
lazy bool
|
lazy bool
|
||||||
// Used for lazy init.
|
// Used for lazy init.
|
||||||
initOnce sync.Once
|
inited uint32
|
||||||
|
initMtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// DynamicRESTMapperOption is a functional option on the dynamicRESTMapper.
|
// DynamicRESTMapperOption is a functional option on the dynamicRESTMapper.
|
||||||
|
@ -125,11 +127,18 @@ func (drm *dynamicRESTMapper) setStaticMapper() error {
|
||||||
|
|
||||||
// init initializes drm only once if drm is lazy.
|
// init initializes drm only once if drm is lazy.
|
||||||
func (drm *dynamicRESTMapper) init() (err error) {
|
func (drm *dynamicRESTMapper) init() (err error) {
|
||||||
drm.initOnce.Do(func() {
|
// skip init if drm is not lazy or has initialized
|
||||||
if drm.lazy {
|
if !drm.lazy || atomic.LoadUint32(&drm.inited) != 0 {
|
||||||
err = drm.setStaticMapper()
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
drm.initMtx.Lock()
|
||||||
|
defer drm.initMtx.Unlock()
|
||||||
|
if drm.inited == 0 {
|
||||||
|
if err = drm.setStaticMapper(); err == nil {
|
||||||
|
atomic.StoreUint32(&drm.inited, 1)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ func loadConfig(context string) (*rest.Config, error) {
|
||||||
if _, ok := os.LookupEnv("HOME"); !ok {
|
if _, ok := os.LookupEnv("HOME"); !ok {
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get current user: %v", err)
|
return nil, fmt.Errorf("could not get current user: %w", err)
|
||||||
}
|
}
|
||||||
loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName))
|
loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName))
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ type ClientBuilder struct {
|
||||||
initObject []client.Object
|
initObject []client.Object
|
||||||
initLists []client.ObjectList
|
initLists []client.ObjectList
|
||||||
initRuntimeObjects []runtime.Object
|
initRuntimeObjects []runtime.Object
|
||||||
|
objectTracker testing.ObjectTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithScheme sets this builder's internal scheme.
|
// WithScheme sets this builder's internal scheme.
|
||||||
|
@ -128,6 +129,12 @@ func (f *ClientBuilder) WithRuntimeObjects(initRuntimeObjs ...runtime.Object) *C
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithObjectTracker can be optionally used to initialize this fake client with testing.ObjectTracker.
|
||||||
|
func (f *ClientBuilder) WithObjectTracker(ot testing.ObjectTracker) *ClientBuilder {
|
||||||
|
f.objectTracker = ot
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// Build builds and returns a new fake client.
|
// Build builds and returns a new fake client.
|
||||||
func (f *ClientBuilder) Build() client.WithWatch {
|
func (f *ClientBuilder) Build() client.WithWatch {
|
||||||
if f.scheme == nil {
|
if f.scheme == nil {
|
||||||
|
@ -137,7 +144,14 @@ func (f *ClientBuilder) Build() client.WithWatch {
|
||||||
f.restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{})
|
f.restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{})
|
||||||
}
|
}
|
||||||
|
|
||||||
tracker := versionedTracker{ObjectTracker: testing.NewObjectTracker(f.scheme, scheme.Codecs.UniversalDecoder()), scheme: f.scheme}
|
var tracker versionedTracker
|
||||||
|
|
||||||
|
if f.objectTracker == nil {
|
||||||
|
tracker = versionedTracker{ObjectTracker: testing.NewObjectTracker(f.scheme, scheme.Codecs.UniversalDecoder()), scheme: f.scheme}
|
||||||
|
} else {
|
||||||
|
tracker = versionedTracker{ObjectTracker: f.objectTracker, scheme: f.scheme}
|
||||||
|
}
|
||||||
|
|
||||||
for _, obj := range f.initObject {
|
for _, obj := range f.initObject {
|
||||||
if err := tracker.Add(obj); err != nil {
|
if err := tracker.Add(obj); err != nil {
|
||||||
panic(fmt.Errorf("failed to add object %v to fake client: %w", obj, err))
|
panic(fmt.Errorf("failed to add object %v to fake client: %w", obj, err))
|
||||||
|
@ -201,7 +215,7 @@ func (t versionedTracker) Add(obj runtime.Object) error {
|
||||||
func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
|
func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
|
||||||
accessor, err := meta.Accessor(obj)
|
accessor, err := meta.Accessor(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get accessor for object: %v", err)
|
return fmt.Errorf("failed to get accessor for object: %w", err)
|
||||||
}
|
}
|
||||||
if accessor.GetName() == "" {
|
if accessor.GetName() == "" {
|
||||||
return apierrors.NewInvalid(
|
return apierrors.NewInvalid(
|
||||||
|
@ -227,7 +241,7 @@ func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Ob
|
||||||
|
|
||||||
// convertFromUnstructuredIfNecessary will convert *unstructured.Unstructured for a GVK that is recocnized
|
// convertFromUnstructuredIfNecessary will convert *unstructured.Unstructured for a GVK that is recocnized
|
||||||
// by the schema into the whatever the schema produces with New() for said GVK.
|
// by the schema into the whatever the schema produces with New() for said GVK.
|
||||||
// This is required because the tracker unconditionally saves on manipulations, but it's List() implementation
|
// This is required because the tracker unconditionally saves on manipulations, but its List() implementation
|
||||||
// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
|
// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
|
||||||
// we save as the very same type, otherwise subsequent List requests will fail.
|
// we save as the very same type, otherwise subsequent List requests will fail.
|
||||||
func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
|
func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
|
||||||
|
@ -255,7 +269,7 @@ func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (ru
|
||||||
func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
|
func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
|
||||||
accessor, err := meta.Accessor(obj)
|
accessor, err := meta.Accessor(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get accessor for object: %v", err)
|
return fmt.Errorf("failed to get accessor for object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if accessor.GetName() == "" {
|
if accessor.GetName() == "" {
|
||||||
|
@ -301,7 +315,7 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
|
||||||
}
|
}
|
||||||
intResourceVersion, err := strconv.ParseUint(oldAccessor.GetResourceVersion(), 10, 64)
|
intResourceVersion, err := strconv.ParseUint(oldAccessor.GetResourceVersion(), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can not convert resourceVersion %q to int: %v", oldAccessor.GetResourceVersion(), err)
|
return fmt.Errorf("can not convert resourceVersion %q to int: %w", oldAccessor.GetResourceVersion(), err)
|
||||||
}
|
}
|
||||||
intResourceVersion++
|
intResourceVersion++
|
||||||
accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
|
accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
|
||||||
|
@ -352,9 +366,7 @@ func (c *fakeClient) Watch(ctx context.Context, list client.ObjectList, opts ...
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
listOpts := client.ListOptions{}
|
listOpts := client.ListOptions{}
|
||||||
listOpts.ApplyOptions(opts)
|
listOpts.ApplyOptions(opts)
|
||||||
|
@ -371,9 +383,7 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
|
||||||
|
|
||||||
originalKind := gvk.Kind
|
originalKind := gvk.Kind
|
||||||
|
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, isUnstructuredList := obj.(*unstructured.UnstructuredList); isUnstructuredList && !c.scheme.Recognizes(gvk) {
|
if _, isUnstructuredList := obj.(*unstructured.UnstructuredList); isUnstructuredList && !c.scheme.Recognizes(gvk) {
|
||||||
// We need to register the ListKind with UnstructuredList:
|
// We need to register the ListKind with UnstructuredList:
|
||||||
|
@ -477,6 +487,12 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
|
||||||
delOptions := client.DeleteOptions{}
|
delOptions := client.DeleteOptions{}
|
||||||
delOptions.ApplyOptions(opts)
|
delOptions.ApplyOptions(opts)
|
||||||
|
|
||||||
|
for _, dryRunOpt := range delOptions.DryRun {
|
||||||
|
if dryRunOpt == metav1.DryRunAll {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check the ResourceVersion if that Precondition was specified.
|
// Check the ResourceVersion if that Precondition was specified.
|
||||||
if delOptions.Preconditions != nil && delOptions.Preconditions.ResourceVersion != nil {
|
if delOptions.Preconditions != nil && delOptions.Preconditions.ResourceVersion != nil {
|
||||||
name := accessor.GetName()
|
name := accessor.GetName()
|
||||||
|
@ -511,6 +527,12 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
|
||||||
dcOptions := client.DeleteAllOfOptions{}
|
dcOptions := client.DeleteAllOfOptions{}
|
||||||
dcOptions.ApplyOptions(opts)
|
dcOptions.ApplyOptions(opts)
|
||||||
|
|
||||||
|
for _, dryRunOpt := range dcOptions.DryRun {
|
||||||
|
if dryRunOpt == metav1.DryRunAll {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
|
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
|
||||||
o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
|
o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -146,9 +146,7 @@ func (mc *metadataClient) List(ctx context.Context, obj ObjectList, opts ...List
|
||||||
}
|
}
|
||||||
|
|
||||||
gvk := metadata.GroupVersionKind()
|
gvk := metadata.GroupVersionKind()
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
listOpts := ListOptions{}
|
listOpts := ListOptions{}
|
||||||
listOpts.ApplyOptions(opts)
|
listOpts.ApplyOptions(opts)
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (n *namespacedClient) RESTMapper() meta.RESTMapper {
|
||||||
func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
|
func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
@ -74,7 +74,7 @@ func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...Creat
|
||||||
func (n *namespacedClient) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
|
func (n *namespacedClient) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
@ -92,7 +92,7 @@ func (n *namespacedClient) Update(ctx context.Context, obj Object, opts ...Updat
|
||||||
func (n *namespacedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
|
func (n *namespacedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
@ -110,7 +110,7 @@ func (n *namespacedClient) Delete(ctx context.Context, obj Object, opts ...Delet
|
||||||
func (n *namespacedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
|
func (n *namespacedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNamespaceScoped {
|
if isNamespaceScoped {
|
||||||
|
@ -123,7 +123,7 @@ func (n *namespacedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...
|
||||||
func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
|
func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
@ -141,7 +141,7 @@ func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, o
|
||||||
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
|
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
if isNamespaceScoped {
|
if isNamespaceScoped {
|
||||||
if key.Namespace != "" && key.Namespace != n.namespace {
|
if key.Namespace != "" && key.Namespace != n.namespace {
|
||||||
|
@ -179,7 +179,7 @@ func (nsw *namespacedClientStatusWriter) Update(ctx context.Context, obj Object,
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
@ -198,7 +198,7 @@ func (nsw *namespacedClientStatusWriter) Patch(ctx context.Context, obj Object,
|
||||||
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
|
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding the scope of the object: %v", err)
|
return fmt.Errorf("error finding the scope of the object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectNamespace := obj.GetNamespace()
|
objectNamespace := obj.GetNamespace()
|
||||||
|
|
|
@ -318,7 +318,7 @@ func (p PropagationPolicy) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
|
||||||
// pre-parsed selectors (since generally, selectors will be executed
|
// pre-parsed selectors (since generally, selectors will be executed
|
||||||
// against the cache).
|
// against the cache).
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
// LabelSelector filters results by label. Use SetLabelSelector to
|
// LabelSelector filters results by label. Use labels.Parse() to
|
||||||
// set from raw string form.
|
// set from raw string form.
|
||||||
LabelSelector labels.Selector
|
LabelSelector labels.Selector
|
||||||
// FieldSelector filters results by a particular field. In order
|
// FieldSelector filters results by a particular field. In order
|
||||||
|
|
|
@ -95,8 +95,7 @@ func (uc *unstructuredClient) Update(ctx context.Context, obj Object, opts ...Up
|
||||||
|
|
||||||
// Delete implements client.Client.
|
// Delete implements client.Client.
|
||||||
func (uc *unstructuredClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
|
func (uc *unstructuredClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
|
||||||
_, ok := obj.(*unstructured.Unstructured)
|
if _, ok := obj.(*unstructured.Unstructured); !ok {
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,8 +117,7 @@ func (uc *unstructuredClient) Delete(ctx context.Context, obj Object, opts ...De
|
||||||
|
|
||||||
// DeleteAllOf implements client.Client.
|
// DeleteAllOf implements client.Client.
|
||||||
func (uc *unstructuredClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
|
func (uc *unstructuredClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
|
||||||
_, ok := obj.(*unstructured.Unstructured)
|
if _, ok := obj.(*unstructured.Unstructured); !ok {
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +139,7 @@ func (uc *unstructuredClient) DeleteAllOf(ctx context.Context, obj Object, opts
|
||||||
|
|
||||||
// Patch implements client.Client.
|
// Patch implements client.Client.
|
||||||
func (uc *unstructuredClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
|
func (uc *unstructuredClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
|
||||||
_, ok := obj.(*unstructured.Unstructured)
|
if _, ok := obj.(*unstructured.Unstructured); !ok {
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,9 +198,7 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ...
|
||||||
}
|
}
|
||||||
|
|
||||||
gvk := u.GroupVersionKind()
|
gvk := u.GroupVersionKind()
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
listOpts := ListOptions{}
|
listOpts := ListOptions{}
|
||||||
listOpts.ApplyOptions(opts)
|
listOpts.ApplyOptions(opts)
|
||||||
|
@ -222,8 +217,7 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ...
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *unstructuredClient) UpdateStatus(ctx context.Context, obj Object, opts ...UpdateOption) error {
|
func (uc *unstructuredClient) UpdateStatus(ctx context.Context, obj Object, opts ...UpdateOption) error {
|
||||||
_, ok := obj.(*unstructured.Unstructured)
|
if _, ok := obj.(*unstructured.Unstructured); !ok {
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,7 @@ func (w *watchingClient) listOpts(opts ...ListOption) ListOptions {
|
||||||
|
|
||||||
func (w *watchingClient) metadataWatch(ctx context.Context, obj *metav1.PartialObjectMetadataList, opts ...ListOption) (watch.Interface, error) {
|
func (w *watchingClient) metadataWatch(ctx context.Context, obj *metav1.PartialObjectMetadataList, opts ...ListOption) (watch.Interface, error) {
|
||||||
gvk := obj.GroupVersionKind()
|
gvk := obj.GroupVersionKind()
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
listOpts := w.listOpts(opts...)
|
listOpts := w.listOpts(opts...)
|
||||||
|
|
||||||
|
@ -85,9 +83,7 @@ func (w *watchingClient) metadataWatch(ctx context.Context, obj *metav1.PartialO
|
||||||
|
|
||||||
func (w *watchingClient) unstructuredWatch(ctx context.Context, obj *unstructured.UnstructuredList, opts ...ListOption) (watch.Interface, error) {
|
func (w *watchingClient) unstructuredWatch(ctx context.Context, obj *unstructured.UnstructuredList, opts ...ListOption) (watch.Interface, error) {
|
||||||
gvk := obj.GroupVersionKind()
|
gvk := obj.GroupVersionKind()
|
||||||
if strings.HasSuffix(gvk.Kind, "List") {
|
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := w.client.unstructuredClient.cache.getResource(obj)
|
r, err := w.client.unstructuredClient.cache.getResource(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -18,7 +18,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
ioutil "io/ioutil"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -96,7 +96,7 @@ func (d *DeferredFileLoader) loadFile() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(d.path)
|
content, err := os.ReadFile(d.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.err = fmt.Errorf("could not read file at %s", d.path)
|
d.err = fmt.Errorf("could not read file at %s", d.path)
|
||||||
return
|
return
|
||||||
|
|
|
@ -23,6 +23,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/internal/controller"
|
"sigs.k8s.io/controller-runtime/pkg/internal/controller"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
|
@ -45,9 +47,9 @@ type Options struct {
|
||||||
// The overall is a token bucket and the per-item is exponential.
|
// The overall is a token bucket and the per-item is exponential.
|
||||||
RateLimiter ratelimiter.RateLimiter
|
RateLimiter ratelimiter.RateLimiter
|
||||||
|
|
||||||
// Log is the logger used for this controller and passed to each reconciliation
|
// LogConstructor is used to construct a logger used for this controller and passed
|
||||||
// request via the context field.
|
// to each reconciliation via the context field.
|
||||||
Log logr.Logger
|
LogConstructor func(request *reconcile.Request) logr.Logger
|
||||||
|
|
||||||
// CacheSyncTimeout refers to the time limit set to wait for syncing caches.
|
// CacheSyncTimeout refers to the time limit set to wait for syncing caches.
|
||||||
// Defaults to 2 minutes if not set.
|
// Defaults to 2 minutes if not set.
|
||||||
|
@ -104,8 +106,20 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
|
||||||
return nil, fmt.Errorf("must specify Name for Controller")
|
return nil, fmt.Errorf("must specify Name for Controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Log.GetSink() == nil {
|
if options.LogConstructor == nil {
|
||||||
options.Log = mgr.GetLogger()
|
log := mgr.GetLogger().WithValues(
|
||||||
|
"controller", name,
|
||||||
|
)
|
||||||
|
options.LogConstructor = func(req *reconcile.Request) logr.Logger {
|
||||||
|
log := log
|
||||||
|
if req != nil {
|
||||||
|
log = log.WithValues(
|
||||||
|
"object", klog.KRef(req.Namespace, req.Name),
|
||||||
|
"namespace", req.Namespace, "name", req.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return log
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.MaxConcurrentReconciles <= 0 {
|
if options.MaxConcurrentReconciles <= 0 {
|
||||||
|
@ -135,7 +149,7 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
|
||||||
CacheSyncTimeout: options.CacheSyncTimeout,
|
CacheSyncTimeout: options.CacheSyncTimeout,
|
||||||
SetFields: mgr.SetFields,
|
SetFields: mgr.SetFields,
|
||||||
Name: name,
|
Name: name,
|
||||||
Log: options.Log.WithName("controller").WithName(name),
|
LogConstructor: options.LogConstructor,
|
||||||
RecoverPanic: options.RecoverPanic,
|
RecoverPanic: options.RecoverPanic,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
13
vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go
generated
vendored
13
vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go
generated
vendored
|
@ -345,30 +345,35 @@ func mutate(f MutateFn, key client.ObjectKey, obj client.Object) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MutateFn is a function which mutates the existing object into it's desired state.
|
// MutateFn is a function which mutates the existing object into its desired state.
|
||||||
type MutateFn func() error
|
type MutateFn func() error
|
||||||
|
|
||||||
// AddFinalizer accepts an Object and adds the provided finalizer if not present.
|
// AddFinalizer accepts an Object and adds the provided finalizer if not present.
|
||||||
func AddFinalizer(o client.Object, finalizer string) {
|
// It returns an indication of whether it updated the object's list of finalizers.
|
||||||
|
func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) {
|
||||||
f := o.GetFinalizers()
|
f := o.GetFinalizers()
|
||||||
for _, e := range f {
|
for _, e := range f {
|
||||||
if e == finalizer {
|
if e == finalizer {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.SetFinalizers(append(f, finalizer))
|
o.SetFinalizers(append(f, finalizer))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFinalizer accepts an Object and removes the provided finalizer if present.
|
// RemoveFinalizer accepts an Object and removes the provided finalizer if present.
|
||||||
func RemoveFinalizer(o client.Object, finalizer string) {
|
// It returns an indication of whether it updated the object's list of finalizers.
|
||||||
|
func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) {
|
||||||
f := o.GetFinalizers()
|
f := o.GetFinalizers()
|
||||||
for i := 0; i < len(f); i++ {
|
for i := 0; i < len(f); i++ {
|
||||||
if f[i] == finalizer {
|
if f[i] == finalizer {
|
||||||
f = append(f[:i], f[i+1:]...)
|
f = append(f[:i], f[i+1:]...)
|
||||||
i--
|
i--
|
||||||
|
finalizersUpdated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.SetFinalizers(f)
|
o.SetFinalizers(f)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainsFinalizer checks an Object that the provided finalizer is present.
|
// ContainsFinalizer checks an Object that the provided finalizer is present.
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||||
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
|
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
|
||||||
|
@ -83,8 +84,11 @@ type Controller struct {
|
||||||
// startWatches maintains a list of sources, handlers, and predicates to start when the controller is started.
|
// startWatches maintains a list of sources, handlers, and predicates to start when the controller is started.
|
||||||
startWatches []watchDescription
|
startWatches []watchDescription
|
||||||
|
|
||||||
// Log is used to log messages to users during reconciliation, or for example when a watch is started.
|
// LogConstructor is used to construct a logger to then log messages to users during reconciliation,
|
||||||
Log logr.Logger
|
// or for example when a watch is started.
|
||||||
|
// Note: LogConstructor has to be able to handle nil requests as we are also using it
|
||||||
|
// outside the context of a reconciliation.
|
||||||
|
LogConstructor func(request *reconcile.Request) logr.Logger
|
||||||
|
|
||||||
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
|
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
|
||||||
RecoverPanic bool
|
RecoverPanic bool
|
||||||
|
@ -99,18 +103,21 @@ type watchDescription struct {
|
||||||
|
|
||||||
// Reconcile implements reconcile.Reconciler.
|
// Reconcile implements reconcile.Reconciler.
|
||||||
func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {
|
func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {
|
||||||
if c.RecoverPanic {
|
defer func() {
|
||||||
defer func() {
|
if r := recover(); r != nil {
|
||||||
if r := recover(); r != nil {
|
if c.RecoverPanic {
|
||||||
for _, fn := range utilruntime.PanicHandlers {
|
for _, fn := range utilruntime.PanicHandlers {
|
||||||
fn(r)
|
fn(r)
|
||||||
}
|
}
|
||||||
err = fmt.Errorf("panic: %v [recovered]", r)
|
err = fmt.Errorf("panic: %v [recovered]", r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
log := logf.FromContext(ctx)
|
||||||
log := c.Log.WithValues("name", req.Name, "namespace", req.Namespace)
|
log.Info(fmt.Sprintf("Observed a panic in reconciler: %v", r))
|
||||||
ctx = logf.IntoContext(ctx, log)
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
return c.Do.Reconcile(ctx, req)
|
return c.Do.Reconcile(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +147,7 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Log.Info("Starting EventSource", "source", src)
|
c.LogConstructor(nil).Info("Starting EventSource", "source", src)
|
||||||
return src.Start(c.ctx, evthdler, c.Queue, prct...)
|
return src.Start(c.ctx, evthdler, c.Queue, prct...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +182,7 @@ func (c *Controller) Start(ctx context.Context) error {
|
||||||
// caches to sync so that they have a chance to register their intendeded
|
// caches to sync so that they have a chance to register their intendeded
|
||||||
// caches.
|
// caches.
|
||||||
for _, watch := range c.startWatches {
|
for _, watch := range c.startWatches {
|
||||||
c.Log.Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))
|
c.LogConstructor(nil).Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))
|
||||||
|
|
||||||
if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
|
if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -183,7 +190,7 @@ func (c *Controller) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
|
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
|
||||||
c.Log.Info("Starting Controller")
|
c.LogConstructor(nil).Info("Starting Controller")
|
||||||
|
|
||||||
for _, watch := range c.startWatches {
|
for _, watch := range c.startWatches {
|
||||||
syncingSource, ok := watch.src.(source.SyncingSource)
|
syncingSource, ok := watch.src.(source.SyncingSource)
|
||||||
|
@ -200,7 +207,7 @@ func (c *Controller) Start(ctx context.Context) error {
|
||||||
// is an error or a timeout
|
// is an error or a timeout
|
||||||
if err := syncingSource.WaitForSync(sourceStartCtx); err != nil {
|
if err := syncingSource.WaitForSync(sourceStartCtx); err != nil {
|
||||||
err := fmt.Errorf("failed to wait for %s caches to sync: %w", c.Name, err)
|
err := fmt.Errorf("failed to wait for %s caches to sync: %w", c.Name, err)
|
||||||
c.Log.Error(err, "Could not wait for Cache to sync")
|
c.LogConstructor(nil).Error(err, "Could not wait for Cache to sync")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +224,7 @@ func (c *Controller) Start(ctx context.Context) error {
|
||||||
c.startWatches = nil
|
c.startWatches = nil
|
||||||
|
|
||||||
// Launch workers to process resources
|
// Launch workers to process resources
|
||||||
c.Log.Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
|
c.LogConstructor(nil).Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
|
||||||
wg.Add(c.MaxConcurrentReconciles)
|
wg.Add(c.MaxConcurrentReconciles)
|
||||||
for i := 0; i < c.MaxConcurrentReconciles; i++ {
|
for i := 0; i < c.MaxConcurrentReconciles; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -237,9 +244,9 @@ func (c *Controller) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
c.Log.Info("Shutdown signal received, waiting for all workers to finish")
|
c.LogConstructor(nil).Info("Shutdown signal received, waiting for all workers to finish")
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
c.Log.Info("All workers finished")
|
c.LogConstructor(nil).Info("All workers finished")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,19 +298,21 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
|
||||||
c.updateMetrics(time.Since(reconcileStartTS))
|
c.updateMetrics(time.Since(reconcileStartTS))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Make sure that the the object is a valid request.
|
// Make sure that the object is a valid request.
|
||||||
req, ok := obj.(reconcile.Request)
|
req, ok := obj.(reconcile.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
// As the item in the workqueue is actually invalid, we call
|
// As the item in the workqueue is actually invalid, we call
|
||||||
// Forget here else we'd go into a loop of attempting to
|
// Forget here else we'd go into a loop of attempting to
|
||||||
// process a work item that is invalid.
|
// process a work item that is invalid.
|
||||||
c.Queue.Forget(obj)
|
c.Queue.Forget(obj)
|
||||||
c.Log.Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
|
c.LogConstructor(nil).Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
|
||||||
// Return true, don't take a break
|
// Return true, don't take a break
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log := c.Log.WithValues("name", req.Name, "namespace", req.Namespace)
|
log := c.LogConstructor(&req)
|
||||||
|
|
||||||
|
log = log.WithValues("reconcileID", uuid.NewUUID())
|
||||||
ctx = logf.IntoContext(ctx, log)
|
ctx = logf.IntoContext(ctx, log)
|
||||||
|
|
||||||
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
|
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
|
||||||
|
@ -336,7 +345,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
|
||||||
|
|
||||||
// GetLogger returns this controller's logger.
|
// GetLogger returns this controller's logger.
|
||||||
func (c *Controller) GetLogger() logr.Logger {
|
func (c *Controller) GetLogger() logr.Logger {
|
||||||
return c.Log
|
return c.LogConstructor(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InjectFunc implement SetFields.Injector.
|
// InjectFunc implement SetFields.Injector.
|
||||||
|
|
|
@ -19,7 +19,6 @@ package leaderelection
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
|
@ -27,6 +26,7 @@ import (
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||||
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/recorder"
|
"sigs.k8s.io/controller-runtime/pkg/recorder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ type Options struct {
|
||||||
LeaderElection bool
|
LeaderElection bool
|
||||||
|
|
||||||
// LeaderElectionResourceLock determines which resource lock to use for leader election,
|
// LeaderElectionResourceLock determines which resource lock to use for leader election,
|
||||||
// defaults to "configmapsleases".
|
// defaults to "leases".
|
||||||
LeaderElectionResourceLock string
|
LeaderElectionResourceLock string
|
||||||
|
|
||||||
// LeaderElectionNamespace determines the namespace in which the leader
|
// LeaderElectionNamespace determines the namespace in which the leader
|
||||||
|
@ -57,11 +57,12 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default resource lock to "configmapsleases". We must keep this default until we are sure all controller-runtime
|
// Default resource lock to "leases". The previous default (from v0.7.0 to v0.11.x) was configmapsleases, which was
|
||||||
// users have upgraded from the original default ConfigMap lock to a controller-runtime version that has this new
|
// used to migrate from configmaps to leases. Since the default was "configmapsleases" for over a year, spanning
|
||||||
// default. Many users of controller-runtime skip versions, so we should be extremely conservative here.
|
// five minor releases, any actively maintained operators are very likely to have a released version that uses
|
||||||
|
// "configmapsleases". Therefore defaulting to "leases" should be safe.
|
||||||
if options.LeaderElectionResourceLock == "" {
|
if options.LeaderElectionResourceLock == "" {
|
||||||
options.LeaderElectionResourceLock = resourcelock.ConfigMapsLeasesResourceLock
|
options.LeaderElectionResourceLock = resourcelock.LeasesResourceLock
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeaderElectionID must be provided to prevent clashes
|
// LeaderElectionID must be provided to prevent clashes
|
||||||
|
@ -118,7 +119,7 @@ func getInClusterNamespace() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the namespace file and return its content
|
// Load the namespace file and return its content
|
||||||
namespace, err := ioutil.ReadFile(inClusterNamespacePath)
|
namespace, err := os.ReadFile(inClusterNamespacePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error reading namespace file: %w", err)
|
return "", fmt.Errorf("error reading namespace file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,7 @@ func (l *DelegatingLogSink) Fulfill(actual logr.LogSink) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDelegatingLogSink constructs a new DelegatingLogSink which uses
|
// NewDelegatingLogSink constructs a new DelegatingLogSink which uses
|
||||||
// the given logger before it's promise is fulfilled.
|
// the given logger before its promise is fulfilled.
|
||||||
func NewDelegatingLogSink(initial logr.LogSink) *DelegatingLogSink {
|
func NewDelegatingLogSink(initial logr.LogSink) *DelegatingLogSink {
|
||||||
l := &DelegatingLogSink{
|
l := &DelegatingLogSink{
|
||||||
logger: initial,
|
logger: initial,
|
||||||
|
|
|
@ -29,7 +29,7 @@ limitations under the License.
|
||||||
//
|
//
|
||||||
// All logging in controller-runtime is structured, using a set of interfaces
|
// All logging in controller-runtime is structured, using a set of interfaces
|
||||||
// defined by a package called logr
|
// defined by a package called logr
|
||||||
// (https://godoc.org/github.com/go-logr/logr). The sub-package zap provides
|
// (https://pkg.go.dev/github.com/go-logr/logr). The sub-package zap provides
|
||||||
// helpers for setting up logr backed by Zap (go.uber.org/zap).
|
// helpers for setting up logr backed by Zap (go.uber.org/zap).
|
||||||
package log
|
package log
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ type KubeAPIWarningLogger struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleWarningHeader handles logging for responses from API server that are
|
// HandleWarningHeader handles logging for responses from API server that are
|
||||||
// warnings with code being 299 and uses a logr.Logger for it's logging purposes.
|
// warnings with code being 299 and uses a logr.Logger for its logging purposes.
|
||||||
func (l *KubeAPIWarningLogger) HandleWarningHeader(code int, agent string, message string) {
|
func (l *KubeAPIWarningLogger) HandleWarningHeader(code int, agent string, message string) {
|
||||||
if code != 299 || len(message) == 0 {
|
if code != 299 || len(message) == 0 {
|
||||||
return
|
return
|
||||||
|
|
|
@ -457,21 +457,21 @@ func (cm *controllerManager) Start(ctx context.Context) (err error) {
|
||||||
// between conversion webhooks and the cache sync (usually initial list) which causes the webhooks
|
// between conversion webhooks and the cache sync (usually initial list) which causes the webhooks
|
||||||
// to never start because no cache can be populated.
|
// to never start because no cache can be populated.
|
||||||
if err := cm.runnables.Webhooks.Start(cm.internalCtx); err != nil {
|
if err := cm.runnables.Webhooks.Start(cm.internalCtx); err != nil {
|
||||||
if err != wait.ErrWaitTimeout {
|
if !errors.Is(err, wait.ErrWaitTimeout) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start and wait for caches.
|
// Start and wait for caches.
|
||||||
if err := cm.runnables.Caches.Start(cm.internalCtx); err != nil {
|
if err := cm.runnables.Caches.Start(cm.internalCtx); err != nil {
|
||||||
if err != wait.ErrWaitTimeout {
|
if !errors.Is(err, wait.ErrWaitTimeout) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the non-leaderelection Runnables after the cache has synced.
|
// Start the non-leaderelection Runnables after the cache has synced.
|
||||||
if err := cm.runnables.Others.Start(cm.internalCtx); err != nil {
|
if err := cm.runnables.Others.Start(cm.internalCtx); err != nil {
|
||||||
if err != wait.ErrWaitTimeout {
|
if !errors.Is(err, wait.ErrWaitTimeout) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -587,7 +587,7 @@ func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) e
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-cm.shutdownCtx.Done()
|
<-cm.shutdownCtx.Done()
|
||||||
if err := cm.shutdownCtx.Err(); err != nil && err != context.Canceled {
|
if err := cm.shutdownCtx.Err(); err != nil && !errors.Is(err, context.Canceled) {
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
if cm.gracefulShutdownTimeout > 0 {
|
if cm.gracefulShutdownTimeout > 0 {
|
||||||
return fmt.Errorf("failed waiting for all runnables to end within grace period of %s: %w", cm.gracefulShutdownTimeout, err)
|
return fmt.Errorf("failed waiting for all runnables to end within grace period of %s: %w", cm.gracefulShutdownTimeout, err)
|
||||||
|
@ -597,6 +597,7 @@ func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) e
|
||||||
// For any other error, return the error.
|
// For any other error, return the error.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,9 +98,9 @@ type Manager interface {
|
||||||
|
|
||||||
// Options are the arguments for creating a new Manager.
|
// Options are the arguments for creating a new Manager.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Scheme is the scheme used to resolve runtime.Objects to GroupVersionKinds / Resources
|
// Scheme is the scheme used to resolve runtime.Objects to GroupVersionKinds / Resources.
|
||||||
// Defaults to the kubernetes/client-go scheme.Scheme, but it's almost always better
|
// Defaults to the kubernetes/client-go scheme.Scheme, but it's almost always better
|
||||||
// idea to pass your own scheme in. See the documentation in pkg/scheme for more information.
|
// to pass your own scheme in. See the documentation in pkg/scheme for more information.
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
|
|
||||||
// MapperProvider provides the rest mapper used to map go types to Kubernetes APIs
|
// MapperProvider provides the rest mapper used to map go types to Kubernetes APIs
|
||||||
|
@ -186,11 +186,11 @@ type Options struct {
|
||||||
// between tries of actions. Default is 2 seconds.
|
// between tries of actions. Default is 2 seconds.
|
||||||
RetryPeriod *time.Duration
|
RetryPeriod *time.Duration
|
||||||
|
|
||||||
// Namespace if specified restricts the manager's cache to watch objects in
|
// Namespace, if specified, restricts the manager's cache to watch objects in
|
||||||
// the desired namespace Defaults to all namespaces
|
// the desired namespace. Defaults to all namespaces.
|
||||||
//
|
//
|
||||||
// Note: If a namespace is specified, controllers can still Watch for a
|
// Note: If a namespace is specified, controllers can still Watch for a
|
||||||
// cluster-scoped resource (e.g Node). For namespaced resources the cache
|
// cluster-scoped resource (e.g Node). For namespaced resources, the cache
|
||||||
// will only hold objects from the desired namespace.
|
// will only hold objects from the desired namespace.
|
||||||
Namespace string
|
Namespace string
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ type Options struct {
|
||||||
// if this is set, the Manager will use this server instead.
|
// if this is set, the Manager will use this server instead.
|
||||||
WebhookServer *webhook.Server
|
WebhookServer *webhook.Server
|
||||||
|
|
||||||
// Functions to all for a user to customize the values that will be injected.
|
// Functions to allow for a user to customize values that will be injected.
|
||||||
|
|
||||||
// NewCache is the function that will create the cache to be used
|
// NewCache is the function that will create the cache to be used
|
||||||
// by the manager. If not set this will use the default new cache function.
|
// by the manager. If not set this will use the default new cache function.
|
||||||
|
@ -239,6 +239,11 @@ type Options struct {
|
||||||
// use the cache for reads and the client for writes.
|
// use the cache for reads and the client for writes.
|
||||||
NewClient cluster.NewClientFunc
|
NewClient cluster.NewClientFunc
|
||||||
|
|
||||||
|
// BaseContext is the function that provides Context values to Runnables
|
||||||
|
// managed by the Manager. If a BaseContext function isn't provided, Runnables
|
||||||
|
// will receive a new Background Context instead.
|
||||||
|
BaseContext BaseContextFunc
|
||||||
|
|
||||||
// ClientDisableCacheFor tells the client that, if any cache is used, to bypass it
|
// ClientDisableCacheFor tells the client that, if any cache is used, to bypass it
|
||||||
// for the given objects.
|
// for the given objects.
|
||||||
ClientDisableCacheFor []client.Object
|
ClientDisableCacheFor []client.Object
|
||||||
|
@ -278,6 +283,10 @@ type Options struct {
|
||||||
newHealthProbeListener func(addr string) (net.Listener, error)
|
newHealthProbeListener func(addr string) (net.Listener, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BaseContextFunc is a function used to provide a base Context to Runnables
|
||||||
|
// managed by a Manager.
|
||||||
|
type BaseContextFunc func() context.Context
|
||||||
|
|
||||||
// Runnable allows a component to be started.
|
// Runnable allows a component to be started.
|
||||||
// It's very important that Start blocks until
|
// It's very important that Start blocks until
|
||||||
// it's done running.
|
// it's done running.
|
||||||
|
@ -335,11 +344,21 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the resource lock to enable leader election)
|
// Create the resource lock to enable leader election)
|
||||||
leaderConfig := options.LeaderElectionConfig
|
var leaderConfig *rest.Config
|
||||||
if leaderConfig == nil {
|
var leaderRecorderProvider *intrec.Provider
|
||||||
|
|
||||||
|
if options.LeaderElectionConfig == nil {
|
||||||
leaderConfig = rest.CopyConfig(config)
|
leaderConfig = rest.CopyConfig(config)
|
||||||
|
leaderRecorderProvider = recorderProvider
|
||||||
|
} else {
|
||||||
|
leaderConfig = rest.CopyConfig(options.LeaderElectionConfig)
|
||||||
|
leaderRecorderProvider, err = options.newRecorderProvider(leaderConfig, cluster.GetScheme(), options.Logger.WithName("events"), options.makeBroadcaster)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
resourceLock, err := options.newResourceLock(leaderConfig, recorderProvider, leaderelection.Options{
|
|
||||||
|
resourceLock, err := options.newResourceLock(leaderConfig, leaderRecorderProvider, leaderelection.Options{
|
||||||
LeaderElection: options.LeaderElection,
|
LeaderElection: options.LeaderElection,
|
||||||
LeaderElectionResourceLock: options.LeaderElectionResourceLock,
|
LeaderElectionResourceLock: options.LeaderElectionResourceLock,
|
||||||
LeaderElectionID: options.LeaderElectionID,
|
LeaderElectionID: options.LeaderElectionID,
|
||||||
|
@ -367,7 +386,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
runnables := newRunnables(errChan)
|
runnables := newRunnables(options.BaseContext, errChan)
|
||||||
|
|
||||||
return &controllerManager{
|
return &controllerManager{
|
||||||
stopProcedureEngaged: pointer.Int64(0),
|
stopProcedureEngaged: pointer.Int64(0),
|
||||||
|
@ -475,6 +494,11 @@ func (o Options) AndFromOrDie(loader config.ControllerManagerConfiguration) Opti
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Options) setLeaderElectionConfig(obj v1alpha1.ControllerManagerConfigurationSpec) Options {
|
func (o Options) setLeaderElectionConfig(obj v1alpha1.ControllerManagerConfigurationSpec) Options {
|
||||||
|
if obj.LeaderElection == nil {
|
||||||
|
// The source does not have any configuration; noop
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
if !o.LeaderElection && obj.LeaderElection.LeaderElect != nil {
|
if !o.LeaderElection && obj.LeaderElection.LeaderElect != nil {
|
||||||
o.LeaderElection = *obj.LeaderElection.LeaderElect
|
o.LeaderElection = *obj.LeaderElection.LeaderElect
|
||||||
}
|
}
|
||||||
|
@ -514,11 +538,17 @@ func defaultHealthProbeListener(addr string) (net.Listener, error) {
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", addr)
|
ln, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listening on %s: %v", addr, err)
|
return nil, fmt.Errorf("error listening on %s: %w", addr, err)
|
||||||
}
|
}
|
||||||
return ln, nil
|
return ln, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultBaseContext is used as the BaseContext value in Options if one
|
||||||
|
// has not already been set.
|
||||||
|
func defaultBaseContext() context.Context {
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
// setOptionsDefaults set default values for Options fields.
|
// setOptionsDefaults set default values for Options fields.
|
||||||
func setOptionsDefaults(options Options) Options {
|
func setOptionsDefaults(options Options) Options {
|
||||||
// Allow newResourceLock to be mocked
|
// Allow newResourceLock to be mocked
|
||||||
|
@ -582,5 +612,9 @@ func setOptionsDefaults(options Options) Options {
|
||||||
options.Logger = log.Log
|
options.Logger = log.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.BaseContext == nil {
|
||||||
|
options.BaseContext = defaultBaseContext
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,12 +35,12 @@ type runnables struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRunnables creates a new runnables object.
|
// newRunnables creates a new runnables object.
|
||||||
func newRunnables(errChan chan error) *runnables {
|
func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
|
||||||
return &runnables{
|
return &runnables{
|
||||||
Webhooks: newRunnableGroup(errChan),
|
Webhooks: newRunnableGroup(baseContext, errChan),
|
||||||
Caches: newRunnableGroup(errChan),
|
Caches: newRunnableGroup(baseContext, errChan),
|
||||||
LeaderElection: newRunnableGroup(errChan),
|
LeaderElection: newRunnableGroup(baseContext, errChan),
|
||||||
Others: newRunnableGroup(errChan),
|
Others: newRunnableGroup(baseContext, errChan),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,14 +100,15 @@ type runnableGroup struct {
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRunnableGroup(errChan chan error) *runnableGroup {
|
func newRunnableGroup(baseContext BaseContextFunc, errChan chan error) *runnableGroup {
|
||||||
r := &runnableGroup{
|
r := &runnableGroup{
|
||||||
startReadyCh: make(chan *readyRunnable),
|
startReadyCh: make(chan *readyRunnable),
|
||||||
errChan: errChan,
|
errChan: errChan,
|
||||||
ch: make(chan *readyRunnable),
|
ch: make(chan *readyRunnable),
|
||||||
wg: new(sync.WaitGroup),
|
wg: new(sync.WaitGroup),
|
||||||
}
|
}
|
||||||
r.ctx, r.cancel = context.WithCancel(context.Background())
|
|
||||||
|
r.ctx, r.cancel = context.WithCancel(baseContext())
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logf.RuntimeLog.WithName("predicate").WithName("eventFilters")
|
var log = logf.RuntimeLog.WithName("predicate").WithName("eventFilters")
|
||||||
|
@ -239,6 +240,15 @@ type and struct {
|
||||||
predicates []Predicate
|
predicates []Predicate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a and) InjectFunc(f inject.Func) error {
|
||||||
|
for _, p := range a.predicates {
|
||||||
|
if err := f(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a and) Create(e event.CreateEvent) bool {
|
func (a and) Create(e event.CreateEvent) bool {
|
||||||
for _, p := range a.predicates {
|
for _, p := range a.predicates {
|
||||||
if !p.Create(e) {
|
if !p.Create(e) {
|
||||||
|
@ -284,6 +294,15 @@ type or struct {
|
||||||
predicates []Predicate
|
predicates []Predicate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o or) InjectFunc(f inject.Func) error {
|
||||||
|
for _, p := range o.predicates {
|
||||||
|
if err := f(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (o or) Create(e event.CreateEvent) bool {
|
func (o or) Create(e event.CreateEvent) bool {
|
||||||
for _, p := range o.predicates {
|
for _, p := range o.predicates {
|
||||||
if p.Create(e) {
|
if p.Create(e) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
@ -133,9 +134,14 @@ func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue w
|
||||||
i, lastErr = ks.cache.GetInformer(ctx, ks.Type)
|
i, lastErr = ks.cache.GetInformer(ctx, ks.Type)
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
kindMatchErr := &meta.NoKindMatchError{}
|
kindMatchErr := &meta.NoKindMatchError{}
|
||||||
if errors.As(lastErr, &kindMatchErr) {
|
switch {
|
||||||
|
case errors.As(lastErr, &kindMatchErr):
|
||||||
log.Error(lastErr, "if kind is a CRD, it should be installed before calling Start",
|
log.Error(lastErr, "if kind is a CRD, it should be installed before calling Start",
|
||||||
"kind", kindMatchErr.GroupKind)
|
"kind", kindMatchErr.GroupKind)
|
||||||
|
case runtime.IsNotRegisteredError(lastErr):
|
||||||
|
log.Error(lastErr, "kind must be registered to the Scheme")
|
||||||
|
default:
|
||||||
|
log.Error(lastErr, "failed to get informer from cache")
|
||||||
}
|
}
|
||||||
return false, nil // Retry.
|
return false, nil // Retry.
|
||||||
}
|
}
|
||||||
|
@ -175,6 +181,9 @@ func (ks *Kind) WaitForSync(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
ks.startCancel()
|
ks.startCancel()
|
||||||
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return errors.New("timed out waiting for cache to be synced")
|
return errors.New("timed out waiting for cache to be synced")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
v1 "k8s.io/api/admission/v1"
|
v1 "k8s.io/api/admission/v1"
|
||||||
|
@ -60,7 +59,7 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
if body, err = ioutil.ReadAll(r.Body); err != nil {
|
if body, err = io.ReadAll(r.Body); err != nil {
|
||||||
wh.log.Error(err, "unable to read the body from the incoming request")
|
wh.log.Error(err, "unable to read the body from the incoming request")
|
||||||
reviewResponse = Errored(http.StatusBadRequest, err)
|
reviewResponse = Errored(http.StatusBadRequest, err)
|
||||||
wh.writeResponse(w, reviewResponse)
|
wh.writeResponse(w, reviewResponse)
|
||||||
|
@ -125,8 +124,16 @@ func (wh *Webhook) writeResponseTyped(w io.Writer, response Response, admRevGVK
|
||||||
// writeAdmissionResponse writes ar to w.
|
// writeAdmissionResponse writes ar to w.
|
||||||
func (wh *Webhook) writeAdmissionResponse(w io.Writer, ar v1.AdmissionReview) {
|
func (wh *Webhook) writeAdmissionResponse(w io.Writer, ar v1.AdmissionReview) {
|
||||||
if err := json.NewEncoder(w).Encode(ar); err != nil {
|
if err := json.NewEncoder(w).Encode(ar); err != nil {
|
||||||
wh.log.Error(err, "unable to encode the response")
|
wh.log.Error(err, "unable to encode and write the response")
|
||||||
wh.writeResponse(w, Errored(http.StatusInternalServerError, err))
|
// Since the `ar v1.AdmissionReview` is a clear and legal object,
|
||||||
|
// it should not have problem to be marshalled into bytes.
|
||||||
|
// The error here is probably caused by the abnormal HTTP connection,
|
||||||
|
// e.g., broken pipe, so we can only write the error response once,
|
||||||
|
// to avoid endless circular calling.
|
||||||
|
serverError := Errored(http.StatusInternalServerError, err)
|
||||||
|
if err = json.NewEncoder(w).Encode(v1.AdmissionReview{Response: &serverError.AdmissionResponse}); err != nil {
|
||||||
|
wh.log.Error(err, "still unable to encode and write the InternalServerError response")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res := ar.Response
|
res := ar.Response
|
||||||
if log := wh.log; log.V(1).Enabled() {
|
if log := wh.log; log.V(1).Enabled() {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -77,6 +76,9 @@ type Server struct {
|
||||||
// "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility)
|
// "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility)
|
||||||
TLSMinVersion string
|
TLSMinVersion string
|
||||||
|
|
||||||
|
// TLSOpts is used to allow configuring the TLS config used for the server
|
||||||
|
TLSOpts []func(*tls.Config)
|
||||||
|
|
||||||
// WebhookMux is the multiplexer that handles different webhooks.
|
// WebhookMux is the multiplexer that handles different webhooks.
|
||||||
WebhookMux *http.ServeMux
|
WebhookMux *http.ServeMux
|
||||||
|
|
||||||
|
@ -241,9 +243,9 @@ func (s *Server) Start(ctx context.Context) error {
|
||||||
// load CA to verify client certificate
|
// load CA to verify client certificate
|
||||||
if s.ClientCAName != "" {
|
if s.ClientCAName != "" {
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
clientCABytes, err := ioutil.ReadFile(filepath.Join(s.CertDir, s.ClientCAName))
|
clientCABytes, err := os.ReadFile(filepath.Join(s.CertDir, s.ClientCAName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read client CA cert: %v", err)
|
return fmt.Errorf("failed to read client CA cert: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := certPool.AppendCertsFromPEM(clientCABytes)
|
ok := certPool.AppendCertsFromPEM(clientCABytes)
|
||||||
|
@ -255,6 +257,11 @@ func (s *Server) Start(ctx context.Context) error {
|
||||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fallback TLS config ready, will now mutate if passer wants full control over it
|
||||||
|
for _, op := range s.TLSOpts {
|
||||||
|
op(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := tls.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)), cfg)
|
listener, err := tls.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)), cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -305,11 +312,11 @@ func (s *Server) StartedChecker() healthz.Checker {
|
||||||
d := &net.Dialer{Timeout: 10 * time.Second}
|
d := &net.Dialer{Timeout: 10 * time.Second}
|
||||||
conn, err := tls.DialWithDialer(d, "tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)), config)
|
conn, err := tls.DialWithDialer(d, "tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("webhook server is not reachable: %v", err)
|
return fmt.Errorf("webhook server is not reachable: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
return fmt.Errorf("webhook server is not reachable: closing connection: %v", err)
|
return fmt.Errorf("webhook server is not reachable: closing connection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in New Issue