diff --git a/Makefile b/Makefile index 6447f3282..076d30b3b 100644 --- a/Makefile +++ b/Makefile @@ -227,10 +227,14 @@ manifests: $(CONTROLLER_GEN) ## Generate WebhookConfiguration, ClusterRole and C output:rbac:artifacts:config=$(PROJECT_ROOT)/config/rbac .PHONY: generate -generate: $(CONTROLLER_GEN) ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +generate: generate-fakes $(CONTROLLER_GEN) ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="$(PROJECT_ROOT)/hack/boilerplate.go.txt" paths="$(PROJECT_ROOT)/api/..." hack/update-clientgen.sh +.PHONY: generate-fakes +generate-fakes: ## Generate fake implementations for testing using counterfeiter. + go generate ./... + # Targets that need Go workspace mode (CI sets GOFLAGS=-mod=vendor which conflicts with go.work) fmt vet test test-e2e run update-vendor update-dep: GOFLAGS= diff --git a/api/operator/v1alpha1/features.go b/api/operator/v1alpha1/features.go index d28e35132..9404af507 100644 --- a/api/operator/v1alpha1/features.go +++ b/api/operator/v1alpha1/features.go @@ -13,8 +13,17 @@ var ( // For more details, // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/istio-csr-controller.md FeatureIstioCSR featuregate.Feature = "IstioCSR" + + // TrustManager enables the controller for trustmanagers.operator.openshift.io resource, + // which extends cert-manager-operator to deploy and manage the trust-manager operand. + // trust-manager provides a way to manage trust bundles in OpenShift clusters. + // + // For more details, + // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/trust-manager-controller.md + FeatureTrustManager featuregate.Feature = "TrustManager" ) var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureTrustManager: {Default: false, PreRelease: "TechPreview"}, } diff --git a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml index 15d1aae67..bb6579df9 100644 --- a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml @@ -629,6 +629,7 @@ spec: resources: - certmanagers/finalizers - istiocsrs/finalizers + - trustmanagers/finalizers verbs: - update - apiGroups: @@ -636,6 +637,7 @@ spec: resources: - certmanagers/status - istiocsrs/status + - trustmanagers/status verbs: - get - patch @@ -644,6 +646,7 @@ spec: - operator.openshift.io resources: - istiocsrs + - trustmanagers verbs: - get - list diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c387f3a40..aed4a6c3c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -226,6 +226,7 @@ rules: resources: - certmanagers/finalizers - istiocsrs/finalizers + - trustmanagers/finalizers verbs: - update - apiGroups: @@ -233,6 +234,7 @@ rules: resources: - certmanagers/status - istiocsrs/status + - trustmanagers/status verbs: - get - patch @@ -241,6 +243,7 @@ rules: - operator.openshift.io resources: - istiocsrs + - trustmanagers verbs: - get - list diff --git a/go.work.sum b/go.work.sum index 25b7a39d9..0904386ec 100644 --- a/go.work.sum +++ b/go.work.sum @@ -38,7 +38,6 @@ github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5 github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -63,7 +62,6 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -71,6 +69,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -78,13 +77,11 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.9.2 h1:92AGsQmNTRMzuzHEYfCdjQeUzTrgE1vfO5/7fEVoXdY= github.com/charmbracelet/x/ansi v0.9.2/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= @@ -98,7 +95,6 @@ github.com/containerd/typeurl/v2 v2.2.2/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsx github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= github.com/coredns/corefile-migration v1.0.26/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -115,6 +111,7 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= @@ -122,10 +119,10 @@ github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdR github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -184,9 +181,7 @@ github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -215,8 +210,6 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25L github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= -github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hashicorp/vault/sdk v0.20.0/go.mod h1:xEjAt/n/2lHBAkYiRPRmvf1d5B6HlisPh2pELlRCosk= @@ -249,14 +242,11 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= @@ -264,9 +254,7 @@ github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOzn github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/ipvs v1.1.0/go.mod h1:4VJMWuf098bsUMmZEiD4Tjk/O7mOn3l1PTD3s4OoYAs= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= @@ -279,7 +267,6 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/mozilla/tls-observatory v0.0.0-20250923143331-eef96233227e/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= @@ -295,19 +282,15 @@ github.com/operator-framework/api v0.15.0/go.mod h1:scnY9xqSeCsOdtJtNoHIXd7OtHZ1 github.com/operator-framework/api v0.31.0/go.mod h1:57oCiHNeWcxmzu1Se8qlnwEKr/GGXnuHvspIYFCcXmY= github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -326,10 +309,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -344,13 +325,11 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -367,10 +346,9 @@ github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0 github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= @@ -419,7 +397,6 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -445,17 +422,11 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -464,12 +435,7 @@ golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= -golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= @@ -481,12 +447,12 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0 golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genai v1.36.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= google.golang.org/genai v1.37.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 h1:x1vNwUhVOcsYoKyEGCZBH694SBmmBjA2EfauFVEI2+M= google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= @@ -528,7 +494,6 @@ google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -537,8 +502,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= k8s.io/cri-api v0.32.1/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d h1:qUrYOinhdAUL0xxhA4gPqogPBaS9nIq2l2kTb6pmeB0= -k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= diff --git a/pkg/controller/istiocsr/client.go b/pkg/controller/common/client.go similarity index 87% rename from pkg/controller/istiocsr/client.go rename to pkg/controller/common/client.go index 3195b0823..f7d025ee2 100644 --- a/pkg/controller/istiocsr/client.go +++ b/pkg/controller/common/client.go @@ -1,4 +1,4 @@ -package istiocsr +package common import ( "context" @@ -12,13 +12,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" ) +// ctrlClientImpl implements the CtrlClient interface using the manager's client. type ctrlClientImpl struct { client.Client } +// CtrlClient defines the interface for controller client operations. +// //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate -//counterfeiter:generate -o fakes . ctrlClient -type ctrlClient interface { +//counterfeiter:generate -o fakes . CtrlClient +type CtrlClient interface { Get(context.Context, client.ObjectKey, client.Object) error List(context.Context, client.ObjectList, ...client.ListOption) error StatusUpdate(context.Context, client.Object, ...client.SubResourceUpdateOption) error @@ -30,11 +33,12 @@ type ctrlClient interface { Exists(context.Context, client.ObjectKey, client.Object) (bool, error) } -func NewClient(m manager.Manager) (ctrlClient, error) { - // Use the manager's client directly instead of creating a custom client. - // The manager's client uses the manager's cache, which ensures the reconciler - // reads from the same cache that the controller's watches use, preventing - // cache mismatch issues. +// NewClient creates a new controller client from the manager. +// Use the manager's client directly instead of creating a custom client. +// The manager's client uses the manager's cache, which ensures the reconciler +// reads from the same cache that the controller's watches use, preventing +// cache mismatch issues. +func NewClient(m manager.Manager) (CtrlClient, error) { return &ctrlClientImpl{ Client: m.GetClient(), }, nil diff --git a/pkg/controller/common/constants.go b/pkg/controller/common/constants.go new file mode 100644 index 000000000..5b1405708 --- /dev/null +++ b/pkg/controller/common/constants.go @@ -0,0 +1,6 @@ +package common + +// ManagedResourceLabelKey is the common label key used by all operand controllers +// to identify resources they manage. Each controller uses a different value +// to distinguish its resources. +const ManagedResourceLabelKey = "app" diff --git a/pkg/controller/istiocsr/errors.go b/pkg/controller/common/errors.go similarity index 71% rename from pkg/controller/istiocsr/errors.go rename to pkg/controller/common/errors.go index 8e1091873..d8b565e09 100644 --- a/pkg/controller/istiocsr/errors.go +++ b/pkg/controller/common/errors.go @@ -1,4 +1,4 @@ -package istiocsr +package common import ( "errors" @@ -7,16 +7,21 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" ) +// ErrorReason represents the reason for a reconciliation error. type ErrorReason string const ( + // IrrecoverableError indicates an error that cannot be recovered by retrying. IrrecoverableError ErrorReason = "IrrecoverableError" + // RetryRequiredError indicates an error that may be recovered by retrying. RetryRequiredError ErrorReason = "RetryRequiredError" + // MultipleInstanceError indicates that multiple singleton instances exist. MultipleInstanceError ErrorReason = "MultipleInstanceError" ) +// ReconcileError represents an error that occurred during reconciliation. type ReconcileError struct { Reason ErrorReason `json:"reason,omitempty"` Message string `json:"message,omitempty"` @@ -25,6 +30,7 @@ type ReconcileError struct { var _ error = &ReconcileError{} +// NewIrrecoverableError creates a new irrecoverable error. func NewIrrecoverableError(err error, message string, args ...any) *ReconcileError { if err == nil { return nil @@ -36,6 +42,7 @@ func NewIrrecoverableError(err error, message string, args ...any) *ReconcileErr } } +// NewMultipleInstanceError creates a new multiple instance error. func NewMultipleInstanceError(err error) *ReconcileError { if err == nil { return nil @@ -47,6 +54,7 @@ func NewMultipleInstanceError(err error) *ReconcileError { } } +// NewRetryRequiredError creates a new error that requires retry. func NewRetryRequiredError(err error, message string, args ...any) *ReconcileError { if err == nil { return nil @@ -58,6 +66,7 @@ func NewRetryRequiredError(err error, message string, args ...any) *ReconcileErr } } +// FromClientError creates a ReconcileError from a Kubernetes client error. func FromClientError(err error, message string, args ...any) *ReconcileError { if err == nil { return nil @@ -70,6 +79,7 @@ func FromClientError(err error, message string, args ...any) *ReconcileError { return NewRetryRequiredError(err, message, args...) } +// FromError creates a ReconcileError from a generic error. func FromError(err error, message string, args ...any) *ReconcileError { if err == nil { return nil @@ -80,6 +90,7 @@ func FromError(err error, message string, args ...any) *ReconcileError { return NewRetryRequiredError(err, message, args...) } +// IsIrrecoverableError checks if the error is an irrecoverable error. func IsIrrecoverableError(err error) bool { rerr := &ReconcileError{} if errors.As(err, &rerr) { @@ -88,6 +99,7 @@ func IsIrrecoverableError(err error) bool { return false } +// IsRetryRequiredError checks if the error requires retry. func IsRetryRequiredError(err error) bool { rerr := &ReconcileError{} if errors.As(err, &rerr) { @@ -96,6 +108,7 @@ func IsRetryRequiredError(err error) bool { return false } +// IsMultipleInstanceError checks if the error is a multiple instance error. func IsMultipleInstanceError(err error) bool { rerr := &ReconcileError{} if errors.As(err, &rerr) { @@ -104,7 +117,7 @@ func IsMultipleInstanceError(err error) bool { return false } -// ReconcileError implements the ReconcileError interface. +// Error implements the error interface. func (e *ReconcileError) Error() string { return fmt.Sprintf("%s: %s", e.Message, e.Err) } diff --git a/pkg/controller/istiocsr/fakes/fake_ctrl_client.go b/pkg/controller/common/fakes/fake_ctrl_client.go similarity index 94% rename from pkg/controller/istiocsr/fakes/fake_ctrl_client.go rename to pkg/controller/common/fakes/fake_ctrl_client.go index f4b0eb591..86273f28f 100644 --- a/pkg/controller/istiocsr/fakes/fake_ctrl_client.go +++ b/pkg/controller/common/fakes/fake_ctrl_client.go @@ -5,7 +5,7 @@ import ( "context" "sync" - "k8s.io/apimachinery/pkg/types" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -36,11 +36,11 @@ type FakeCtrlClient struct { deleteReturnsOnCall map[int]struct { result1 error } - ExistsStub func(context.Context, types.NamespacedName, client.Object) (bool, error) + ExistsStub func(context.Context, client.ObjectKey, client.Object) (bool, error) existsMutex sync.RWMutex existsArgsForCall []struct { arg1 context.Context - arg2 types.NamespacedName + arg2 client.ObjectKey arg3 client.Object } existsReturns struct { @@ -51,11 +51,11 @@ type FakeCtrlClient struct { result1 bool result2 error } - GetStub func(context.Context, types.NamespacedName, client.Object) error + GetStub func(context.Context, client.ObjectKey, client.Object) error getMutex sync.RWMutex getArgsForCall []struct { arg1 context.Context - arg2 types.NamespacedName + arg2 client.ObjectKey arg3 client.Object } getReturns struct { @@ -260,12 +260,12 @@ func (fake *FakeCtrlClient) DeleteReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeCtrlClient) Exists(arg1 context.Context, arg2 types.NamespacedName, arg3 client.Object) (bool, error) { +func (fake *FakeCtrlClient) Exists(arg1 context.Context, arg2 client.ObjectKey, arg3 client.Object) (bool, error) { fake.existsMutex.Lock() ret, specificReturn := fake.existsReturnsOnCall[len(fake.existsArgsForCall)] fake.existsArgsForCall = append(fake.existsArgsForCall, struct { arg1 context.Context - arg2 types.NamespacedName + arg2 client.ObjectKey arg3 client.Object }{arg1, arg2, arg3}) stub := fake.ExistsStub @@ -287,13 +287,13 @@ func (fake *FakeCtrlClient) ExistsCallCount() int { return len(fake.existsArgsForCall) } -func (fake *FakeCtrlClient) ExistsCalls(stub func(context.Context, types.NamespacedName, client.Object) (bool, error)) { +func (fake *FakeCtrlClient) ExistsCalls(stub func(context.Context, client.ObjectKey, client.Object) (bool, error)) { fake.existsMutex.Lock() defer fake.existsMutex.Unlock() fake.ExistsStub = stub } -func (fake *FakeCtrlClient) ExistsArgsForCall(i int) (context.Context, types.NamespacedName, client.Object) { +func (fake *FakeCtrlClient) ExistsArgsForCall(i int) (context.Context, client.ObjectKey, client.Object) { fake.existsMutex.RLock() defer fake.existsMutex.RUnlock() argsForCall := fake.existsArgsForCall[i] @@ -326,12 +326,12 @@ func (fake *FakeCtrlClient) ExistsReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *FakeCtrlClient) Get(arg1 context.Context, arg2 types.NamespacedName, arg3 client.Object) error { +func (fake *FakeCtrlClient) Get(arg1 context.Context, arg2 client.ObjectKey, arg3 client.Object) error { fake.getMutex.Lock() ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)] fake.getArgsForCall = append(fake.getArgsForCall, struct { arg1 context.Context - arg2 types.NamespacedName + arg2 client.ObjectKey arg3 client.Object }{arg1, arg2, arg3}) stub := fake.GetStub @@ -353,13 +353,13 @@ func (fake *FakeCtrlClient) GetCallCount() int { return len(fake.getArgsForCall) } -func (fake *FakeCtrlClient) GetCalls(stub func(context.Context, types.NamespacedName, client.Object) error) { +func (fake *FakeCtrlClient) GetCalls(stub func(context.Context, client.ObjectKey, client.Object) error) { fake.getMutex.Lock() defer fake.getMutex.Unlock() fake.GetStub = stub } -func (fake *FakeCtrlClient) GetArgsForCall(i int) (context.Context, types.NamespacedName, client.Object) { +func (fake *FakeCtrlClient) GetArgsForCall(i int) (context.Context, client.ObjectKey, client.Object) { fake.getMutex.RLock() defer fake.getMutex.RUnlock() argsForCall := fake.getArgsForCall[i] @@ -708,24 +708,6 @@ func (fake *FakeCtrlClient) UpdateWithRetryReturnsOnCall(i int, result1 error) { func (fake *FakeCtrlClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.createMutex.RLock() - defer fake.createMutex.RUnlock() - fake.deleteMutex.RLock() - defer fake.deleteMutex.RUnlock() - fake.existsMutex.RLock() - defer fake.existsMutex.RUnlock() - fake.getMutex.RLock() - defer fake.getMutex.RUnlock() - fake.listMutex.RLock() - defer fake.listMutex.RUnlock() - fake.patchMutex.RLock() - defer fake.patchMutex.RUnlock() - fake.statusUpdateMutex.RLock() - defer fake.statusUpdateMutex.RUnlock() - fake.updateMutex.RLock() - defer fake.updateMutex.RUnlock() - fake.updateWithRetryMutex.RLock() - defer fake.updateWithRetryMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value @@ -744,3 +726,5 @@ func (fake *FakeCtrlClient) recordInvocation(key string, args []interface{}) { } fake.invocations[key] = append(fake.invocations[key], args) } + +var _ common.CtrlClient = new(FakeCtrlClient) diff --git a/pkg/controller/common/utils.go b/pkg/controller/common/utils.go new file mode 100644 index 000000000..ce7304c2b --- /dev/null +++ b/pkg/controller/common/utils.go @@ -0,0 +1,52 @@ +package common + +import ( + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// UpdateNamespace sets the namespace on the given object. +func UpdateNamespace(obj client.Object, newNamespace string) { + obj.SetNamespace(newNamespace) +} + +// UpdateResourceLabels sets the labels on the given object. +func UpdateResourceLabels(obj client.Object, labels map[string]string) { + obj.SetLabels(labels) +} + +// HasObjectChanged compares two objects of the same type and returns true if they differ. +// Returns false if the objects are not of the same type. +func HasObjectChanged(desired, fetched client.Object) bool { + if reflect.TypeOf(desired) != reflect.TypeOf(fetched) { + return false + } + return ObjectMetadataModified(desired, fetched) +} + +// ObjectMetadataModified compares the labels of two objects and returns true if they differ. +func ObjectMetadataModified(desired, fetched client.Object) bool { + return !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels()) +} + +// ContainsAnnotation checks if the given object has the specified annotation. +func ContainsAnnotation(obj client.Object, annotation string) bool { + _, exist := obj.GetAnnotations()[annotation] + return exist +} + +// AddAnnotation adds an annotation to the object if it doesn't already exist. +// Returns true if the annotation was added, false if it already existed. +func AddAnnotation(obj client.Object, annotation, value string) bool { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string, 1) + } + if _, exist := annotations[annotation]; !exist { + annotations[annotation] = value + obj.SetAnnotations(annotations) + return true + } + return false +} diff --git a/pkg/controller/istiocsr/certificates.go b/pkg/controller/istiocsr/certificates.go index 6fde42a70..2731ae9a5 100644 --- a/pkg/controller/istiocsr/certificates.go +++ b/pkg/controller/istiocsr/certificates.go @@ -12,6 +12,7 @@ import ( certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -26,7 +27,7 @@ func (r *Reconciler) createOrApplyCertificates(istiocsr *v1alpha1.IstioCSR, reso fetched := &certmanagerv1.Certificate{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s certificate resource already exists", certificateName) + return common.FromClientError(err, "failed to check %s certificate resource already exists", certificateName) } if exist && istioCSRCreateRecon { @@ -35,7 +36,7 @@ func (r *Reconciler) createOrApplyCertificates(istiocsr *v1alpha1.IstioCSR, reso if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("certificate has been modified, updating to desired state", "name", certificateName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s certificate resource", certificateName) + return common.FromClientError(err, "failed to update %s certificate resource", certificateName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "certificate resource %s reconciled back to desired state", certificateName) } else { @@ -43,7 +44,7 @@ func (r *Reconciler) createOrApplyCertificates(istiocsr *v1alpha1.IstioCSR, reso } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s certificate resource", certificateName) + return common.FromClientError(err, "failed to create %s certificate resource", certificateName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "certificate resource %s created", certificateName) } @@ -54,7 +55,7 @@ func (r *Reconciler) createOrApplyCertificates(istiocsr *v1alpha1.IstioCSR, reso func (r *Reconciler) getCertificateObject(istiocsr *v1alpha1.IstioCSR, resourceLabels map[string]string) (*certmanagerv1.Certificate, error) { certificate := decodeCertificateObjBytes(assets.MustAsset(certificateAssetName)) - updateNamespace(certificate, istiocsr.Spec.IstioCSRConfig.Istio.Namespace) + common.UpdateNamespace(certificate, istiocsr.Spec.IstioCSRConfig.Istio.Namespace) // add custom label for identification on the object created in different namespace. labels := make(map[string]string, len(resourceLabels)+1) maps.Copy(labels, resourceLabels) @@ -62,7 +63,7 @@ func (r *Reconciler) getCertificateObject(istiocsr *v1alpha1.IstioCSR, resourceL certificate.SetLabels(labels) if err := updateCertificateParams(istiocsr, certificate); err != nil { - return nil, NewIrrecoverableError(err, "failed to update certificate resource for %s/%s istiocsr deployment", istiocsr.GetNamespace(), istiocsr.GetName()) + return nil, common.NewIrrecoverableError(err, "failed to update certificate resource for %s/%s istiocsr deployment", istiocsr.GetNamespace(), istiocsr.GetName()) } return certificate, nil diff --git a/pkg/controller/istiocsr/certificates_test.go b/pkg/controller/istiocsr/certificates_test.go index 92b24e880..e9bb417eb 100644 --- a/pkg/controller/istiocsr/certificates_test.go +++ b/pkg/controller/istiocsr/certificates_test.go @@ -11,7 +11,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) func TestCreateOrApplyCertificates(t *testing.T) { @@ -215,7 +215,7 @@ func TestCreateOrApplyCertificates(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := &v1alpha1.IstioCSR{} if err := r.Get(context.Background(), types.NamespacedName{ Namespace: testIstioCSR().Namespace, diff --git a/pkg/controller/istiocsr/controller.go b/pkg/controller/istiocsr/controller.go index ce9f41294..36fe9629b 100644 --- a/pkg/controller/istiocsr/controller.go +++ b/pkg/controller/istiocsr/controller.go @@ -12,16 +12,12 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -32,21 +28,17 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" ) -var ( - // requestEnqueueLabelKey is the label key name used for filtering reconcile - // events to include only the resources created by the controller. - requestEnqueueLabelKey = "app" - - // requestEnqueueLabelValue is the label value used for filtering reconcile - // events to include only the resources created by the controller. - requestEnqueueLabelValue = "cert-manager-istio-csr" -) +// RequestEnqueueLabelValue is the label value used for filtering reconcile +// events to include only the resources created by the IstioCSR controller. +// The label key is common.ManagedResourceLabelKey. +const RequestEnqueueLabelValue = "cert-manager-istio-csr" // Reconciler reconciles a IstioCSR object. type Reconciler struct { - ctrlClient + common.CtrlClient ctx context.Context eventRecorder record.EventRecorder @@ -59,61 +51,14 @@ type Reconciler struct { // +kubebuilder:rbac:groups=operator.openshift.io,resources=istiocsrs/finalizers,verbs=update // +kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete -// NewCacheBuilder returns a cache builder function configured with label selectors -// for managed resources. This function is used by the manager to create its cache -// to ensure the reconciler reads from the same cache that the controller's watches use. -func NewCacheBuilder(config *rest.Config, opts cache.Options) (cache.Cache, error) { - managedResourceLabelReq, err := labels.NewRequirement(requestEnqueueLabelKey, selection.Equals, []string{requestEnqueueLabelValue}) - if err != nil { - return nil, fmt.Errorf("invalid cache label requirement for %q: %w", requestEnqueueLabelKey, err) - } - managedResourceLabelReqSelector := labels.NewSelector().Add(*managedResourceLabelReq) - - // Configure cache with label selectors for managed resources - opts.ByObject = map[client.Object]cache.ByObject{ - // Explicitly include IstioCSR to ensure the cache properly watches and syncs all IstioCSR objects - &v1alpha1.IstioCSR{}: {}, - // Resources managed by controller (with label selectors) - &certmanagerv1.Certificate{}: { - Label: managedResourceLabelReqSelector, - }, - &appsv1.Deployment{}: { - Label: managedResourceLabelReqSelector, - }, - &rbacv1.ClusterRole{}: { - Label: managedResourceLabelReqSelector, - }, - &rbacv1.ClusterRoleBinding{}: { - Label: managedResourceLabelReqSelector, - }, - &rbacv1.Role{}: { - Label: managedResourceLabelReqSelector, - }, - &rbacv1.RoleBinding{}: { - Label: managedResourceLabelReqSelector, - }, - &corev1.Service{}: { - Label: managedResourceLabelReqSelector, - }, - &corev1.ServiceAccount{}: { - Label: managedResourceLabelReqSelector, - }, - &networkingv1.NetworkPolicy{}: { - Label: managedResourceLabelReqSelector, - }, - } - - return cache.New(config, opts) -} - // New returns a new Reconciler instance. func New(mgr ctrl.Manager) (*Reconciler, error) { - c, err := NewClient(mgr) + c, err := common.NewClient(mgr) if err != nil { return nil, err } return &Reconciler{ - ctrlClient: c, + CtrlClient: c, ctx: context.Background(), eventRecorder: mgr.GetEventRecorderFor(ControllerName), log: ctrl.Log.WithName(ControllerName), @@ -137,7 +82,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { } labelOk := func() bool { - if objLabels[requestEnqueueLabelKey] == requestEnqueueLabelValue { + if objLabels[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue { return true } value := objLabels[IstiocsrResourceWatchLabelName] @@ -171,7 +116,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { // predicate function to ignore events for objects not managed by controller. controllerManagedResources := predicate.NewPredicateFuncs(func(object client.Object) bool { - return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue + return object.GetLabels() != nil && object.GetLabels()[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue }) // predicate function to filter events for objects which controller is interested in, but @@ -185,7 +130,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { return false } // Accept if it's a managed ConfigMap OR a watched ConfigMap - return object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue || + return object.GetLabels()[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue || object.GetLabels()[IstiocsrResourceWatchLabelName] != "" }) @@ -262,7 +207,7 @@ func (r *Reconciler) processReconcileRequest(istiocsr *v1alpha1.IstioCSR, req ty } if err := r.disallowMultipleIstioCSRInstances(istiocsr); err != nil { - if IsMultipleInstanceError(err) { + if common.IsMultipleInstanceError(err) { r.eventRecorder.Eventf(istiocsr, corev1.EventTypeWarning, "MultiIstioCSRInstance", "creation of multiple istiocsr instances is not supported, will not be processed") err = nil } @@ -272,7 +217,7 @@ func (r *Reconciler) processReconcileRequest(istiocsr *v1alpha1.IstioCSR, req ty var errUpdate error = nil if err := r.reconcileIstioCSRDeployment(istiocsr, istioCSRCreateRecon); err != nil { r.log.Error(err, "failed to reconcile IstioCSR deployment", "request", req) - if IsIrrecoverableError(err) { + if common.IsIrrecoverableError(err) { // Set both conditions atomically before updating status degradedChanged := istiocsr.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionTrue, v1alpha1.ReasonFailed, fmt.Sprintf("reconciliation failed with irrecoverable error not retrying: %v", err)) readyChanged := istiocsr.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonReady, "") diff --git a/pkg/controller/istiocsr/controller_test.go b/pkg/controller/istiocsr/controller_test.go index 310c18f60..9d29a2d86 100644 --- a/pkg/controller/istiocsr/controller_test.go +++ b/pkg/controller/istiocsr/controller_test.go @@ -19,7 +19,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) func TestReconcile(t *testing.T) { @@ -415,7 +415,7 @@ func TestReconcile(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := testIstioCSR() result, err := r.Reconcile(context.Background(), ctrl.Request{ @@ -757,7 +757,7 @@ func TestProcessReconcileRequest(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := tt.getIstioCSR() _, err := r.processReconcileRequest(istiocsr, types.NamespacedName{Name: istiocsr.GetName(), Namespace: istiocsr.GetNamespace()}) diff --git a/pkg/controller/istiocsr/deployments.go b/pkg/controller/istiocsr/deployments.go index de1935cbe..ad8326f4d 100644 --- a/pkg/controller/istiocsr/deployments.go +++ b/pkg/controller/istiocsr/deployments.go @@ -22,6 +22,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -42,7 +43,7 @@ func (r *Reconciler) createOrApplyDeployments(istiocsr *v1alpha1.IstioCSR, resou fetched := &appsv1.Deployment{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s deployment resource already exists", deploymentName) + return common.FromClientError(err, "failed to check %s deployment resource already exists", deploymentName) } if exist && istioCSRCreateRecon { @@ -51,7 +52,7 @@ func (r *Reconciler) createOrApplyDeployments(istiocsr *v1alpha1.IstioCSR, resou if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("deployment has been modified, updating to desired state", "name", deploymentName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s deployment resource", deploymentName) + return common.FromClientError(err, "failed to update %s deployment resource", deploymentName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "deployment resource %s reconciled back to desired state", deploymentName) } else { @@ -59,13 +60,13 @@ func (r *Reconciler) createOrApplyDeployments(istiocsr *v1alpha1.IstioCSR, resou } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s deployment resource", deploymentName) + return common.FromClientError(err, "failed to create %s deployment resource", deploymentName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "deployment resource %s created", deploymentName) } if err := r.updateImageInStatus(istiocsr, desired); err != nil { - return FromClientError(err, "failed to update %s/%s istiocsr status with image info", istiocsr.GetNamespace(), istiocsr.GetName()) + return common.FromClientError(err, "failed to update %s/%s istiocsr status with image info", istiocsr.GetNamespace(), istiocsr.GetName()) } return nil } @@ -77,8 +78,8 @@ func (r *Reconciler) getDeploymentObject(istiocsr *v1alpha1.IstioCSR, resourceLa deployment := decodeDeploymentObjBytes(assets.MustAsset(deploymentAssetName)) - updateNamespace(deployment, istiocsr.GetNamespace()) - updateResourceLabels(deployment, resourceLabels) + common.UpdateNamespace(deployment, istiocsr.GetNamespace()) + common.UpdateResourceLabels(deployment, resourceLabels) updatePodTemplateLabels(deployment, resourceLabels) updateArgList(deployment, istiocsr) @@ -96,7 +97,7 @@ func (r *Reconciler) getDeploymentObject(istiocsr *v1alpha1.IstioCSR, resourceLa return nil, fmt.Errorf("failed to update node selector: %w", err) } if err := r.updateImage(deployment); err != nil { - return nil, NewIrrecoverableError(err, "failed to update image %s/%s", istiocsr.GetNamespace(), istiocsr.GetName()) + return nil, common.NewIrrecoverableError(err, "failed to update image %s/%s", istiocsr.GetNamespace(), istiocsr.GetName()) } if err := r.updateVolumes(deployment, istiocsr, resourceLabels); err != nil { return nil, fmt.Errorf("failed to update volume %s/%s: %w", istiocsr.GetNamespace(), istiocsr.GetName(), err) @@ -236,17 +237,17 @@ func updateNodeSelector(deployment *appsv1.Deployment, istiocsr *v1alpha1.IstioC func (r *Reconciler) assertIssuerRefExists(istiocsr *v1alpha1.IstioCSR) error { issuerRefKind := strings.ToLower(istiocsr.Spec.IstioCSRConfig.CertManager.IssuerRef.Kind) if issuerRefKind != clusterIssuerKind && issuerRefKind != issuerKind { - return NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef.kind can be any of `%s` or `%s`, configured: %s", clusterIssuerKind, issuerKind, issuerKind) + return common.NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef.kind can be any of `%s` or `%s`, configured: %s", clusterIssuerKind, issuerKind, issuerRefKind) } issuerRefGroup := strings.ToLower(istiocsr.Spec.IstioCSRConfig.CertManager.IssuerRef.Group) if issuerRefGroup != issuerGroup { - return NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef.group can be only `%s`, configured: %s", issuerGroup, issuerRefGroup) + return common.NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef.group can be only `%s`, configured: %s", issuerGroup, issuerRefGroup) } obj, err := r.getIssuer(istiocsr) if err != nil { - return FromClientError(err, "failed to fetch issuer") + return common.FromClientError(err, "failed to fetch issuer") } var issuerConfig certmanagerv1.IssuerConfig @@ -254,18 +255,18 @@ func (r *Reconciler) assertIssuerRefExists(istiocsr *v1alpha1.IstioCSR) error { case clusterIssuerKind: clusterIssuer, ok := obj.(*certmanagerv1.ClusterIssuer) if !ok { - return NewIrrecoverableError(errInvalidIssuerRefConfig, "failed to convert to ClusterIssuer") + return common.NewIrrecoverableError(errInvalidIssuerRefConfig, "failed to convert to ClusterIssuer") } issuerConfig = clusterIssuer.Spec.IssuerConfig case issuerKind: issuer, ok := obj.(*certmanagerv1.Issuer) if !ok { - return NewIrrecoverableError(errInvalidIssuerRefConfig, "failed to convert to Issuer") + return common.NewIrrecoverableError(errInvalidIssuerRefConfig, "failed to convert to Issuer") } issuerConfig = issuer.Spec.IssuerConfig } if issuerConfig.ACME != nil { - return NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef uses unsupported ACME issuer") + return common.NewIrrecoverableError(errInvalidIssuerRefConfig, "spec.istioCSRConfig.certManager.issuerRef uses unsupported ACME issuer") } return nil @@ -275,7 +276,7 @@ func (r *Reconciler) updateVolumes(deployment *appsv1.Deployment, istiocsr *v1al // Use user-configured CA certificate if provided if istiocsr.Spec.IstioCSRConfig.CertManager.IstioCACertificate != nil { if err := r.handleUserProvidedCA(deployment, istiocsr, resourceLabels); err != nil { - return FromError(err, "failed to validate and mount CA certificate ConfigMap") + return common.FromError(err, "failed to validate and mount CA certificate ConfigMap") } return nil } @@ -319,25 +320,25 @@ func (r *Reconciler) handleUserProvidedCA(deployment *appsv1.Deployment, istiocs sourceConfigMap := &corev1.ConfigMap{} if err := r.Get(r.ctx, sourceConfigMapKey, sourceConfigMap); err != nil { - return NewIrrecoverableError(err, "failed to fetch CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) + return common.NewIrrecoverableError(err, "failed to fetch CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) } // Add watch label to the source ConfigMap to trigger reconciliation on changes. // This is done before validation so that if validation fails now, fixing the ConfigMap // will trigger reconciliation. if err := r.updateWatchLabel(sourceConfigMap, istiocsr); err != nil { - return FromClientError(err, "failed to update watch label on CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) + return common.FromClientError(err, "failed to update watch label on CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) } // Validate that the specified key exists in the ConfigMap if _, exists := sourceConfigMap.Data[caCertConfig.Key]; !exists { - return NewIrrecoverableError(fmt.Errorf("key %q not found in ConfigMap %s/%s", caCertConfig.Key, sourceConfigMapKey.Namespace, sourceConfigMapKey.Name), "invalid CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) + return common.NewIrrecoverableError(fmt.Errorf("key %q not found in ConfigMap %s/%s", caCertConfig.Key, sourceConfigMapKey.Namespace, sourceConfigMapKey.Name), "invalid CA certificate ConfigMap %s/%s", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name) } // Validate that the key contains PEM-formatted content pemData := sourceConfigMap.Data[caCertConfig.Key] if err := r.validatePEMData(pemData); err != nil { - return NewIrrecoverableError(err, "invalid PEM data in CA certificate ConfigMap %s/%s key %q", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name, caCertConfig.Key) + return common.NewIrrecoverableError(err, "invalid PEM data in CA certificate ConfigMap %s/%s key %q", sourceConfigMapKey.Namespace, sourceConfigMapKey.Name, caCertConfig.Key) } // Create a managed copy of the ConfigMap in the IstioCSR namespace. @@ -349,7 +350,7 @@ func (r *Reconciler) handleUserProvidedCA(deployment *appsv1.Deployment, istiocs // its managed copy. Additionally, if a user directly modifies the operator-managed copy, it will be // reconciled back to the desired state derived from the validated source ConfigMap. if err := r.createOrUpdateCAConfigMap(istiocsr, pemData, resourceLabels); err != nil { - return FromClientError(err, "failed to create CA certificate ConfigMap copy") + return common.FromClientError(err, "failed to create CA certificate ConfigMap copy") } // Mount the copied CA certificate ConfigMap (always uses the standard name) @@ -366,7 +367,7 @@ func (r *Reconciler) handleIssuerBasedCA(deployment *appsv1.Deployment, istiocsr obj, err := r.getIssuer(istiocsr) if err != nil { - return FromClientError(err, "failed to fetch issuer") + return common.FromClientError(err, "failed to fetch issuer") } issuerRefKind := strings.ToLower(istiocsr.Spec.IstioCSRConfig.CertManager.IssuerRef.Kind) @@ -374,13 +375,13 @@ func (r *Reconciler) handleIssuerBasedCA(deployment *appsv1.Deployment, istiocsr case clusterIssuerKind: clusterIssuer, ok := obj.(*certmanagerv1.ClusterIssuer) if !ok { - return FromClientError(fmt.Errorf("failed to convert to ClusterIssuer"), "failed to fetch issuer") + return common.FromClientError(fmt.Errorf("failed to convert to ClusterIssuer"), "failed to fetch issuer") } issuerConfig = clusterIssuer.Spec.IssuerConfig case issuerKind: issuer, ok := obj.(*certmanagerv1.Issuer) if !ok { - return FromClientError(fmt.Errorf("failed to convert to Issuer"), "failed to fetch issuer") + return common.FromClientError(fmt.Errorf("failed to convert to Issuer"), "failed to fetch issuer") } issuerConfig = issuer.Spec.IssuerConfig } @@ -389,14 +390,14 @@ func (r *Reconciler) handleIssuerBasedCA(deployment *appsv1.Deployment, istiocsr if issuerConfig.CA != nil && issuerConfig.CA.SecretName != "" { if err := r.createCAConfigMapFromIssuerSecret(istiocsr, issuerConfig, resourceLabels); err != nil { - return FromClientError(err, "failed to create CA ConfigMap") + return common.FromClientError(err, "failed to create CA ConfigMap") } shouldUpdateVolume = true } if issuerConfig.CA == nil { if err := r.createCAConfigMapFromIstiodCertificate(istiocsr, resourceLabels); err != nil { - return FromClientError(err, "failed to create CA ConfigMap") + return common.FromClientError(err, "failed to create CA ConfigMap") } shouldUpdateVolume = true } @@ -503,7 +504,7 @@ func (r *Reconciler) getIssuer(istiocsr *v1alpha1.IstioCSR) (client.Object, erro func (r *Reconciler) createCAConfigMapFromIstiodCertificate(istiocsr *v1alpha1.IstioCSR, resourceLabels map[string]string) error { istiodCertificate, err := r.getCertificateObject(istiocsr, resourceLabels) if err != nil { - return FromClientError(err, "failed to fetch istiod certificate") + return common.FromClientError(err, "failed to fetch istiod certificate") } secretKey := client.ObjectKey{ diff --git a/pkg/controller/istiocsr/deployments_test.go b/pkg/controller/istiocsr/deployments_test.go index dec2f92c3..298e925ac 100644 --- a/pkg/controller/istiocsr/deployments_test.go +++ b/pkg/controller/istiocsr/deployments_test.go @@ -20,7 +20,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) func TestCreateOrApplyDeployments(t *testing.T) { @@ -1071,7 +1071,7 @@ func TestCreateOrApplyDeployments(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := testIstioCSR() if tt.updateIstioCSR != nil { tt.updateIstioCSR(istiocsr) diff --git a/pkg/controller/istiocsr/install_instiocsr_test.go b/pkg/controller/istiocsr/install_instiocsr_test.go index 761193764..d128b1f00 100644 --- a/pkg/controller/istiocsr/install_instiocsr_test.go +++ b/pkg/controller/istiocsr/install_instiocsr_test.go @@ -16,7 +16,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) func TestReconcileIstioCSRDeployment(t *testing.T) { @@ -120,7 +120,7 @@ func TestReconcileIstioCSRDeployment(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock err := r.reconcileIstioCSRDeployment(istiocsr, true) if (tt.wantErr != "" || err != nil) && (err == nil || err.Error() != tt.wantErr) { t.Errorf("reconcileIstioCSRDeployment() err: %v, wantErr: %v", err, tt.wantErr) diff --git a/pkg/controller/istiocsr/install_istiocsr.go b/pkg/controller/istiocsr/install_istiocsr.go index cc7a82ec3..05f1d2457 100644 --- a/pkg/controller/istiocsr/install_istiocsr.go +++ b/pkg/controller/istiocsr/install_istiocsr.go @@ -5,11 +5,12 @@ import ( "maps" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" ) func (r *Reconciler) reconcileIstioCSRDeployment(istiocsr *v1alpha1.IstioCSR, istioCSRCreateRecon bool) error { if err := validateIstioCSRConfig(istiocsr); err != nil { - return NewIrrecoverableError(err, "%s/%s configuration validation failed", istiocsr.GetNamespace(), istiocsr.GetName()) + return common.NewIrrecoverableError(err, "%s/%s configuration validation failed", istiocsr.GetNamespace(), istiocsr.GetName()) } // if user has set custom labels to be added to all resources created by the controller diff --git a/pkg/controller/istiocsr/networkpolicies.go b/pkg/controller/istiocsr/networkpolicies.go index 9aea14401..461fa8b9f 100644 --- a/pkg/controller/istiocsr/networkpolicies.go +++ b/pkg/controller/istiocsr/networkpolicies.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -73,7 +74,7 @@ func (r *Reconciler) createOrUpdateNetworkPolicy(policy *networkingv1.NetworkPol } exist, err := r.Exists(r.ctx, key, fetched) if err != nil { - return FromClientError(err, "failed to check %s network policy resource already exists", policyName) + return common.FromClientError(err, "failed to check %s network policy resource already exists", policyName) } if exist && istioCSRCreateRecon { @@ -82,7 +83,7 @@ func (r *Reconciler) createOrUpdateNetworkPolicy(policy *networkingv1.NetworkPol if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("network policy has been modified, updating to desired state", "name", policyName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s network policy resource", policyName) + return common.FromClientError(err, "failed to update %s network policy resource", policyName) } r.eventRecorder.Eventf(policy, corev1.EventTypeNormal, "Reconciled", "network policy resource %s reconciled back to desired state", policyName) } else { @@ -90,7 +91,7 @@ func (r *Reconciler) createOrUpdateNetworkPolicy(policy *networkingv1.NetworkPol } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s network policy resource", policyName) + return common.FromClientError(err, "failed to create %s network policy resource", policyName) } r.eventRecorder.Eventf(policy, corev1.EventTypeNormal, "Reconciled", "network policy resource %s created", policyName) } diff --git a/pkg/controller/istiocsr/rbacs.go b/pkg/controller/istiocsr/rbacs.go index 9636d1288..e138b4bda 100644 --- a/pkg/controller/istiocsr/rbacs.go +++ b/pkg/controller/istiocsr/rbacs.go @@ -8,6 +8,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -72,7 +73,7 @@ func (r *Reconciler) createOrApplyClusterRoles(istiocsr *v1alpha1.IstioCSR, reso } exist, err = r.Exists(r.ctx, key, fetched) if err != nil { - return "", FromClientError(err, "failed to check %s clusterrole resource already exists", roleName) + return "", common.FromClientError(err, "failed to check %s clusterrole resource already exists", roleName) } } if istiocsr.Status.ClusterRole == "" { @@ -81,12 +82,12 @@ func (r *Reconciler) createOrApplyClusterRoles(istiocsr *v1alpha1.IstioCSR, reso // make sure required resource does not exist already. clusterRoleList := &rbacv1.ClusterRoleList{} if err := r.List(r.ctx, clusterRoleList, client.MatchingLabels(desired.GetLabels())); err != nil { - return "", FromClientError(err, "failed to list clusterrole resources, impacted namespace %s", istiocsr.GetNamespace()) + return "", common.FromClientError(err, "failed to list clusterrole resources, impacted namespace %s", istiocsr.GetNamespace()) } if len(clusterRoleList.Items) > 0 { if len(clusterRoleList.Items) != 1 { r.eventRecorder.Eventf(istiocsr, corev1.EventTypeWarning, "DuplicateResources", "more than 1 clusterrole resources exist with matching labels") - return "", NewIrrecoverableError(fmt.Errorf("more than 1 clusterrole resources exist with matching labels"), "matched clusterrole resources: %+v", clusterRoleList.Items) + return "", common.NewIrrecoverableError(fmt.Errorf("more than 1 clusterrole resources exist with matching labels"), "matched clusterrole resources: %+v", clusterRoleList.Items) } clusterRoleList.Items[0].DeepCopyInto(fetched) @@ -101,7 +102,7 @@ func (r *Reconciler) createOrApplyClusterRoles(istiocsr *v1alpha1.IstioCSR, reso if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("clusterrole has been modified, updating to desired state", "name", roleName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return "", FromClientError(err, "failed to update %s clusterrole resource", roleName) + return "", common.FromClientError(err, "failed to update %s clusterrole resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s reconciled back to desired state", roleName) } else { @@ -109,12 +110,12 @@ func (r *Reconciler) createOrApplyClusterRoles(istiocsr *v1alpha1.IstioCSR, reso } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return "", FromClientError(err, "failed to create %s clusterrole resource", roleName) + return "", common.FromClientError(err, "failed to create %s clusterrole resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s created", roleName) } if roleName, err = r.updateClusterRoleNameInStatus(istiocsr, desired, fetched); err != nil { - return "", FromClientError(err, "failed to update %s/%s istiocsr status with %s clusterrole resource name", istiocsr.GetNamespace(), istiocsr.GetName(), roleName) + return "", common.FromClientError(err, "failed to update %s/%s istiocsr status with %s clusterrole resource name", istiocsr.GetNamespace(), istiocsr.GetName(), roleName) } return roleName, nil @@ -165,7 +166,7 @@ func (r *Reconciler) createOrApplyClusterRoleBindings(istiocsr *v1alpha1.IstioCS } exist, err = r.Exists(r.ctx, key, fetched) if err != nil { - return FromClientError(err, "failed to check %s clusterrolebinding resource already exists", roleBindingName) + return common.FromClientError(err, "failed to check %s clusterrolebinding resource already exists", roleBindingName) } } if istiocsr.Status.ClusterRoleBinding == "" { @@ -174,12 +175,12 @@ func (r *Reconciler) createOrApplyClusterRoleBindings(istiocsr *v1alpha1.IstioCS // make sure required resource does not exist already. clusterRoleBindingsList := &rbacv1.ClusterRoleBindingList{} if err := r.List(r.ctx, clusterRoleBindingsList, client.MatchingLabels(desired.GetLabels())); err != nil { - return FromClientError(err, "failed to list clusterrolebinding resources, impacted namespace %s", istiocsr.GetNamespace()) + return common.FromClientError(err, "failed to list clusterrolebinding resources, impacted namespace %s", istiocsr.GetNamespace()) } if len(clusterRoleBindingsList.Items) > 0 { if len(clusterRoleBindingsList.Items) != 1 { r.eventRecorder.Eventf(istiocsr, corev1.EventTypeWarning, "DuplicateResources", "more than 1 clusterrolebinding resources exist with matching labels") - return NewIrrecoverableError(fmt.Errorf("more than 1 clusterrolebinding resources exist with matching labels"), "matched clusterrolebinding resources: %+v", clusterRoleBindingsList.Items) + return common.NewIrrecoverableError(fmt.Errorf("more than 1 clusterrolebinding resources exist with matching labels"), "matched clusterrolebinding resources: %+v", clusterRoleBindingsList.Items) } clusterRoleBindingsList.Items[0].DeepCopyInto(fetched) @@ -194,7 +195,7 @@ func (r *Reconciler) createOrApplyClusterRoleBindings(istiocsr *v1alpha1.IstioCS if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("clusterrolebinding has been modified, updating to desired state", "name", roleBindingName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s clusterrolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to update %s clusterrolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s reconciled back to desired state", roleBindingName) } else { @@ -202,12 +203,12 @@ func (r *Reconciler) createOrApplyClusterRoleBindings(istiocsr *v1alpha1.IstioCS } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s clusterrolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to create %s clusterrolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s created", roleBindingName) } if err := r.updateClusterRoleBindingNameInStatus(istiocsr, desired, fetched); err != nil { - return FromClientError(err, "failed to update %s/%s istiocsr status with %s clusterrolebinding resource name", istiocsr.GetNamespace(), istiocsr.GetName(), roleBindingName) + return common.FromClientError(err, "failed to update %s/%s istiocsr status with %s clusterrolebinding resource name", istiocsr.GetNamespace(), istiocsr.GetName(), roleBindingName) } return nil @@ -243,7 +244,7 @@ func (r *Reconciler) createOrApplyRoles(istiocsr *v1alpha1.IstioCSR, resourceLab fetched := &rbacv1.Role{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s role resource already exists", roleName) + return common.FromClientError(err, "failed to check %s role resource already exists", roleName) } if exist && istioCSRCreateRecon { @@ -252,7 +253,7 @@ func (r *Reconciler) createOrApplyRoles(istiocsr *v1alpha1.IstioCSR, resourceLab if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("role has been modified, updating to desired state", "name", roleName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s role resource", roleName) + return common.FromClientError(err, "failed to update %s role resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "role resource %s reconciled back to desired state", roleName) } else { @@ -260,7 +261,7 @@ func (r *Reconciler) createOrApplyRoles(istiocsr *v1alpha1.IstioCSR, resourceLab } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s role resource", roleName) + return common.FromClientError(err, "failed to create %s role resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "role resource %s created", roleName) } @@ -270,7 +271,7 @@ func (r *Reconciler) createOrApplyRoles(istiocsr *v1alpha1.IstioCSR, resourceLab func (r *Reconciler) getRoleObject(istiocsrNamespace, roleNamespace string, resourceLabels map[string]string) *rbacv1.Role { role := decodeRoleObjBytes(assets.MustAsset(roleAssetName)) - updateNamespace(role, roleNamespace) + common.UpdateNamespace(role, roleNamespace) updateResourceLabelsWithIstioMapperLabels(role, istiocsrNamespace, resourceLabels) return role } @@ -283,7 +284,7 @@ func (r *Reconciler) createOrApplyRoleBindings(istiocsr *v1alpha1.IstioCSR, serv fetched := &rbacv1.RoleBinding{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) + return common.FromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) } if exist && istioCSRCreateRecon { @@ -292,7 +293,7 @@ func (r *Reconciler) createOrApplyRoleBindings(istiocsr *v1alpha1.IstioCSR, serv if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("rolebinding has been modified, updating to desired state", "name", roleBindingName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s rolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to update %s rolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s reconciled back to desired state", roleBindingName) } else { @@ -300,7 +301,7 @@ func (r *Reconciler) createOrApplyRoleBindings(istiocsr *v1alpha1.IstioCSR, serv } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s rolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to create %s rolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s created", roleBindingName) } @@ -310,7 +311,7 @@ func (r *Reconciler) createOrApplyRoleBindings(istiocsr *v1alpha1.IstioCSR, serv func (r *Reconciler) getRoleBindingObject(serviceAccount, istiocsrNamespace, roleNamespace string, resourceLabels map[string]string) *rbacv1.RoleBinding { roleBinding := decodeRoleBindingObjBytes(assets.MustAsset(roleBindingAssetName)) - updateNamespace(roleBinding, roleNamespace) + common.UpdateNamespace(roleBinding, roleNamespace) updateResourceLabelsWithIstioMapperLabels(roleBinding, istiocsrNamespace, resourceLabels) updateServiceAccountNamespaceInRBACBindingObject[*rbacv1.RoleBinding](roleBinding, serviceAccount, istiocsrNamespace) return roleBinding @@ -324,7 +325,7 @@ func (r *Reconciler) createOrApplyRoleForLeases(istiocsr *v1alpha1.IstioCSR, res fetched := &rbacv1.Role{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s role resource already exists", roleName) + return common.FromClientError(err, "failed to check %s role resource already exists", roleName) } if exist && istioCSRCreateRecon { @@ -333,7 +334,7 @@ func (r *Reconciler) createOrApplyRoleForLeases(istiocsr *v1alpha1.IstioCSR, res if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("role has been modified, updating to desired state", "name", roleName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s role resource", roleName) + return common.FromClientError(err, "failed to update %s role resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "role resource %s reconciled back to desired state", roleName) } else { @@ -341,7 +342,7 @@ func (r *Reconciler) createOrApplyRoleForLeases(istiocsr *v1alpha1.IstioCSR, res } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s role resource", roleName) + return common.FromClientError(err, "failed to create %s role resource", roleName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "role resource %s created", roleName) } @@ -351,7 +352,7 @@ func (r *Reconciler) createOrApplyRoleForLeases(istiocsr *v1alpha1.IstioCSR, res func (r *Reconciler) getRoleForLeasesObject(istiocsrNamespace, roleNamespace string, resourceLabels map[string]string) *rbacv1.Role { role := decodeRoleObjBytes(assets.MustAsset(roleLeasesAssetName)) - updateNamespace(role, roleNamespace) + common.UpdateNamespace(role, roleNamespace) updateResourceLabelsWithIstioMapperLabels(role, istiocsrNamespace, resourceLabels) return role } @@ -364,7 +365,7 @@ func (r *Reconciler) createOrApplyRoleBindingForLeases(istiocsr *v1alpha1.IstioC fetched := &rbacv1.RoleBinding{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) + return common.FromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) } if exist && istioCSRCreateRecon { @@ -373,7 +374,7 @@ func (r *Reconciler) createOrApplyRoleBindingForLeases(istiocsr *v1alpha1.IstioC if exist && hasObjectChanged(desired, fetched) { r.log.V(1).Info("rolebinding has been modified, updating to desired state", "name", roleBindingName) if err := r.UpdateWithRetry(r.ctx, desired); err != nil { - return FromClientError(err, "failed to update %s rolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to update %s rolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s reconciled back to desired state", roleBindingName) } else { @@ -381,7 +382,7 @@ func (r *Reconciler) createOrApplyRoleBindingForLeases(istiocsr *v1alpha1.IstioC } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s rolebinding resource", roleBindingName) + return common.FromClientError(err, "failed to create %s rolebinding resource", roleBindingName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s created", roleBindingName) } @@ -391,7 +392,7 @@ func (r *Reconciler) createOrApplyRoleBindingForLeases(istiocsr *v1alpha1.IstioC func (r *Reconciler) getRoleBindingForLeasesObject(serviceAccount, istiocsrNamespace, roleNamespace string, resourceLabels map[string]string) *rbacv1.RoleBinding { roleBinding := decodeRoleBindingObjBytes(assets.MustAsset(roleBindingLeasesAssetName)) - updateNamespace(roleBinding, roleNamespace) + common.UpdateNamespace(roleBinding, roleNamespace) updateResourceLabelsWithIstioMapperLabels(roleBinding, istiocsrNamespace, resourceLabels) updateServiceAccountNamespaceInRBACBindingObject[*rbacv1.RoleBinding](roleBinding, serviceAccount, istiocsrNamespace) return roleBinding diff --git a/pkg/controller/istiocsr/rbacs_test.go b/pkg/controller/istiocsr/rbacs_test.go index a23b40c1e..168b98996 100644 --- a/pkg/controller/istiocsr/rbacs_test.go +++ b/pkg/controller/istiocsr/rbacs_test.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) func TestCreateOrApplyRBACResource(t *testing.T) { @@ -533,7 +533,7 @@ func TestCreateOrApplyRBACResource(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := testIstioCSR() if tt.updateIstioCSR != nil { tt.updateIstioCSR(istiocsr) diff --git a/pkg/controller/istiocsr/serviceaccounts.go b/pkg/controller/istiocsr/serviceaccounts.go index ed10ee237..3e7c04df5 100644 --- a/pkg/controller/istiocsr/serviceaccounts.go +++ b/pkg/controller/istiocsr/serviceaccounts.go @@ -7,6 +7,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -18,7 +19,7 @@ func (r *Reconciler) createOrApplyServiceAccounts(istiocsr *v1alpha1.IstioCSR, r fetched := &corev1.ServiceAccount{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) if err != nil { - return FromClientError(err, "failed to check %s serviceaccount resource already exists", serviceAccountName) + return common.FromClientError(err, "failed to check %s serviceaccount resource already exists", serviceAccountName) } if exist { @@ -29,21 +30,21 @@ func (r *Reconciler) createOrApplyServiceAccounts(istiocsr *v1alpha1.IstioCSR, r } if !exist { if err := r.Create(r.ctx, desired); err != nil { - return FromClientError(err, "failed to create %s serviceaccount resource", serviceAccountName) + return common.FromClientError(err, "failed to create %s serviceaccount resource", serviceAccountName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "serviceaccount resource %s created", serviceAccountName) } if err := r.updateServiceAccountNameInStatus(istiocsr, desired); err != nil { - return FromClientError(err, "failed to update %s/%s istiocsr status with %s serviceaccount resource name", istiocsr.GetNamespace(), istiocsr.GetName(), serviceAccountName) + return common.FromClientError(err, "failed to update %s/%s istiocsr status with %s serviceaccount resource name", istiocsr.GetNamespace(), istiocsr.GetName(), serviceAccountName) } return nil } func (r *Reconciler) getServiceAccountObject(istiocsr *v1alpha1.IstioCSR, resourceLabels map[string]string) *corev1.ServiceAccount { serviceAccount := decodeServiceAccountObjBytes(assets.MustAsset(serviceAccountAssetName)) - updateNamespace(serviceAccount, istiocsr.GetNamespace()) - updateResourceLabels(serviceAccount, resourceLabels) + common.UpdateNamespace(serviceAccount, istiocsr.GetNamespace()) + common.UpdateResourceLabels(serviceAccount, resourceLabels) return serviceAccount } diff --git a/pkg/controller/istiocsr/serviceaccounts_test.go b/pkg/controller/istiocsr/serviceaccounts_test.go index ead3a0ea5..ac7a65d89 100644 --- a/pkg/controller/istiocsr/serviceaccounts_test.go +++ b/pkg/controller/istiocsr/serviceaccounts_test.go @@ -10,7 +10,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) const ( @@ -71,7 +71,7 @@ func TestCreateOrApplyServiceAccounts(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := testIstioCSR() err := r.createOrApplyServiceAccounts(istiocsr, controllerDefaultResourceLabels, false) if (tt.wantErr != "" || err != nil) && (err == nil || err.Error() != tt.wantErr) { diff --git a/pkg/controller/istiocsr/services.go b/pkg/controller/istiocsr/services.go index 24e8c6d68..d2ae5712b 100644 --- a/pkg/controller/istiocsr/services.go +++ b/pkg/controller/istiocsr/services.go @@ -7,6 +7,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/operator/assets" ) @@ -21,7 +22,7 @@ func (r *Reconciler) createOrApplyServices(istiocsr *v1alpha1.IstioCSR, resource return err } if err := r.updateGRPCEndpointInStatus(istiocsr, service); err != nil { - return FromClientError(err, "failed to update %s/%s istiocsr status with %s service endpoint info", istiocsr.GetNamespace(), istiocsr.GetName(), service.GetName()) + return common.FromClientError(err, "failed to update %s/%s istiocsr status with %s service endpoint info", istiocsr.GetNamespace(), istiocsr.GetName(), service.GetName()) } metricsService := r.getMetricsServiceObject(istiocsr, resourceLabels) @@ -37,7 +38,7 @@ func (r *Reconciler) createOrApplyService(istiocsr *v1alpha1.IstioCSR, svc *core fetched := &corev1.Service{} exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(svc), fetched) if err != nil { - return FromClientError(err, "failed to check %s service resource already exists", serviceName) + return common.FromClientError(err, "failed to check %s service resource already exists", serviceName) } if exist && istioCSRCreateRecon { @@ -46,7 +47,7 @@ func (r *Reconciler) createOrApplyService(istiocsr *v1alpha1.IstioCSR, svc *core if exist && hasObjectChanged(svc, fetched) { r.log.V(1).Info("service has been modified, updating to desired state", "name", serviceName) if err := r.UpdateWithRetry(r.ctx, svc); err != nil { - return FromClientError(err, "failed to update %s service resource", serviceName) + return common.FromClientError(err, "failed to update %s service resource", serviceName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "service resource %s reconciled back to desired state", serviceName) } else { @@ -54,7 +55,7 @@ func (r *Reconciler) createOrApplyService(istiocsr *v1alpha1.IstioCSR, svc *core } if !exist { if err := r.Create(r.ctx, svc); err != nil { - return FromClientError(err, "failed to create %s service resource", serviceName) + return common.FromClientError(err, "failed to create %s service resource", serviceName) } r.eventRecorder.Eventf(istiocsr, corev1.EventTypeNormal, "Reconciled", "service resource %s created", serviceName) } @@ -63,8 +64,8 @@ func (r *Reconciler) createOrApplyService(istiocsr *v1alpha1.IstioCSR, svc *core func (r *Reconciler) getServiceObject(istiocsr *v1alpha1.IstioCSR, resourceLabels map[string]string) *corev1.Service { service := decodeServiceObjBytes(assets.MustAsset(serviceAssetName)) - updateNamespace(service, istiocsr.GetNamespace()) - updateResourceLabels(service, resourceLabels) + common.UpdateNamespace(service, istiocsr.GetNamespace()) + common.UpdateResourceLabels(service, resourceLabels) if istiocsr.Spec.IstioCSRConfig.Server != nil { updateServicePort(service, istiocsr.Spec.IstioCSRConfig.Server.Port) } @@ -73,8 +74,8 @@ func (r *Reconciler) getServiceObject(istiocsr *v1alpha1.IstioCSR, resourceLabel func (r *Reconciler) getMetricsServiceObject(istiocsr *v1alpha1.IstioCSR, resourceLabels map[string]string) *corev1.Service { service := decodeServiceObjBytes(assets.MustAsset(metricsServiceAssetName)) - updateNamespace(service, istiocsr.GetNamespace()) - updateResourceLabels(service, resourceLabels) + common.UpdateNamespace(service, istiocsr.GetNamespace()) + common.UpdateResourceLabels(service, resourceLabels) return service } diff --git a/pkg/controller/istiocsr/services_test.go b/pkg/controller/istiocsr/services_test.go index f7f6f2c4a..708a116a4 100644 --- a/pkg/controller/istiocsr/services_test.go +++ b/pkg/controller/istiocsr/services_test.go @@ -10,7 +10,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" - "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr/fakes" + "github.com/openshift/cert-manager-operator/pkg/controller/common/fakes" ) const ( @@ -106,7 +106,7 @@ func TestCreateOrApplyServices(t *testing.T) { if tt.preReq != nil { tt.preReq(r, mock) } - r.ctrlClient = mock + r.CtrlClient = mock istiocsr := testIstioCSR() if tt.updateIstioCSR != nil { tt.updateIstioCSR(istiocsr) diff --git a/pkg/controller/istiocsr/utils.go b/pkg/controller/istiocsr/utils.go index d1d5ded9e..8140e711a 100644 --- a/pkg/controller/istiocsr/utils.go +++ b/pkg/controller/istiocsr/utils.go @@ -21,6 +21,7 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" ) var ( @@ -110,47 +111,19 @@ func (r *Reconciler) removeFinalizer(ctx context.Context, istiocsr *v1alpha1.Ist } func containsProcessedAnnotation(istiocsr *v1alpha1.IstioCSR) bool { - _, exist := istiocsr.GetAnnotations()[controllerProcessedAnnotation] - return exist + return common.ContainsAnnotation(istiocsr, controllerProcessedAnnotation) } func containsProcessingRejectedAnnotation(istiocsr *v1alpha1.IstioCSR) bool { - _, exist := istiocsr.GetAnnotations()[controllerProcessingRejectedAnnotation] - return exist + return common.ContainsAnnotation(istiocsr, controllerProcessingRejectedAnnotation) } func addProcessedAnnotation(istiocsr *v1alpha1.IstioCSR) bool { - annotations := istiocsr.GetAnnotations() - if annotations == nil { - annotations = make(map[string]string, 1) - } - if _, exist := annotations[controllerProcessedAnnotation]; !exist { - annotations[controllerProcessedAnnotation] = "true" - istiocsr.SetAnnotations(annotations) - return true - } - return false + return common.AddAnnotation(istiocsr, controllerProcessedAnnotation, "true") } func addProcessingRejectedAnnotation(istiocsr *v1alpha1.IstioCSR) bool { - annotations := istiocsr.GetAnnotations() - if annotations == nil { - annotations = make(map[string]string, 1) - } - if _, exist := annotations[controllerProcessingRejectedAnnotation]; !exist { - annotations[controllerProcessingRejectedAnnotation] = "true" - istiocsr.SetAnnotations(annotations) - return true - } - return false -} - -func updateNamespace(obj client.Object, newNamespace string) { - obj.SetNamespace(newNamespace) -} - -func updateResourceLabels(obj client.Object, labels map[string]string) { - obj.SetLabels(labels) + return common.AddAnnotation(istiocsr, controllerProcessingRejectedAnnotation, "true") } func updateResourceLabelsWithIstioMapperLabels(obj client.Object, istiocsrNamespace string, labels map[string]string) { @@ -322,11 +295,7 @@ func hasObjectChanged(desired, fetched client.Object) bool { default: panic(fmt.Sprintf("unsupported object type: %T", desired)) } - return objectModified || objectMetadataModified(desired, fetched) -} - -func objectMetadataModified(desired, fetched client.Object) bool { - return !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels()) + return objectModified || common.ObjectMetadataModified(desired, fetched) } func certificateSpecModified(desired, fetched *certmanagerv1.Certificate) bool { @@ -527,7 +496,7 @@ func (r *Reconciler) disallowMultipleIstioCSRInstances(istiocsr *v1alpha1.IstioC if istiocsr.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, statusMessage) { updateErr = r.updateCondition(istiocsr, nil) } - return NewMultipleInstanceError(utilerrors.NewAggregate([]error{fmt.Errorf("%s", statusMessage), updateErr})) + return common.NewMultipleInstanceError(utilerrors.NewAggregate([]error{fmt.Errorf("%s", statusMessage), updateErr})) } istiocsrList := &v1alpha1.IstioCSRList{} @@ -573,5 +542,5 @@ func (r *Reconciler) disallowMultipleIstioCSRInstances(istiocsr *v1alpha1.IstioC return utilerrors.NewAggregate([]error{condUpdateErr, annUpdateErr}) } - return NewMultipleInstanceError(fmt.Errorf("%s", statusMessage)) + return common.NewMultipleInstanceError(fmt.Errorf("%s", statusMessage)) } diff --git a/pkg/controller/trustmanager/constants.go b/pkg/controller/trustmanager/constants.go new file mode 100644 index 000000000..a4d105da5 --- /dev/null +++ b/pkg/controller/trustmanager/constants.go @@ -0,0 +1,68 @@ +package trustmanager + +import ( + "os" + "time" +) + +const ( + // trustManagerCommonName is the name commonly used for naming resources. + trustManagerCommonName = "cert-manager-trust-manager" + + // ControllerName is the name of the controller used in logs and events. + ControllerName = trustManagerCommonName + "-controller" + + // controllerProcessedAnnotation is the annotation added to trustmanager resource once after + // successful reconciliation by the controller. + controllerProcessedAnnotation = "operator.openshift.io/trust-manager-processed" + + // controllerProcessingRejectedAnnotation is the annotation added to trustmanager resource when multiple + // instances of trustmanager resource is created. + controllerProcessingRejectedAnnotation = "operator.openshift.io/trust-manager-reject-multiple-instance" + + // finalizer name for trustmanager.openshift.operator.io resource. + finalizer = "trustmanager.openshift.operator.io/" + ControllerName + + // defaultRequeueTime is the default reconcile requeue time. + defaultRequeueTime = time.Second * 30 + + // trustManagerObjectName is the name of the trust-manager resource created by user. + // trust-manager CRD enforces name to be `cluster`. + trustManagerObjectName = "cluster" + + // trustManagerImageNameEnvVarName is the environment variable key name + // containing the image name of the trust-manager as value. + trustManagerImageNameEnvVarName = "RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER" + + // trustManagerImageVersionEnvVarName is the environment variable key name + // containing the image version of the trust-manager as value. + trustManagerImageVersionEnvVarName = "TRUSTMANAGER_OPERAND_IMAGE_VERSION" + + // defaultTrustNamespace is the default namespace where trust-manager looks for trust sources. + defaultTrustNamespace = "cert-manager" + + // operandNamespace is the namespace where trust-manager operand is deployed. + operandNamespace = "cert-manager" + + // fieldOwner is the field manager name used for Server-Side Apply operations. + // All resource reconcilers should use this to identify ownership of fields. + fieldOwner = "trust-manager-controller" +) + +var ( + controllerDefaultResourceLabels = map[string]string{ + "app": trustManagerCommonName, + "app.kubernetes.io/name": trustManagerCommonName, + "app.kubernetes.io/instance": trustManagerCommonName, + "app.kubernetes.io/version": os.Getenv(trustManagerImageVersionEnvVarName), + "app.kubernetes.io/managed-by": "cert-manager-operator", + "app.kubernetes.io/part-of": "cert-manager-operator", + } +) + +// asset names are the files present in the root bindata/ dir. Which are then loaded +// and made available by the pkg/operator/assets package. +// TODO: Add more asset names as resources are implemented +const ( + serviceAccountAssetName = "trust-manager/resources/serviceaccount_trust-manager.yml" +) diff --git a/pkg/controller/trustmanager/controller.go b/pkg/controller/trustmanager/controller.go new file mode 100644 index 000000000..c0ceae84a --- /dev/null +++ b/pkg/controller/trustmanager/controller.go @@ -0,0 +1,232 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +// RequestEnqueueLabelValue is the label value used for filtering reconcile +// events to include only the resources created by the TrustManager controller. +// The label key is common.ManagedResourceLabelKey. +const RequestEnqueueLabelValue = "cert-manager-trust-manager" + +// Reconciler reconciles a TrustManager object. +type Reconciler struct { + common.CtrlClient + + ctx context.Context + eventRecorder record.EventRecorder + log logr.Logger + scheme *runtime.Scheme +} + +// TODO: Add more RBAC rules as resources are implemented +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/finalizers,verbs=update +// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete + +// New returns a new Reconciler instance. +func New(mgr ctrl.Manager) (*Reconciler, error) { + c, err := common.NewClient(mgr) + if err != nil { + return nil, err + } + return &Reconciler{ + CtrlClient: c, + ctx: context.Background(), + eventRecorder: mgr.GetEventRecorderFor(ControllerName), + log: ctrl.Log.WithName(ControllerName), + scheme: mgr.GetScheme(), + }, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + mapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request { + r.log.V(4).Info("received reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + + objLabels := obj.GetLabels() + if objLabels != nil { + if objLabels[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: trustManagerObjectName, + }, + }, + } + } + } + + r.log.V(4).Info("object not of interest, ignoring reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + return []reconcile.Request{} + } + + // predicate function to ignore events for objects not managed by controller. + controllerManagedResources := predicate.NewPredicateFuncs(func(object client.Object) bool { + labels := object.GetLabels() + matches := labels != nil && labels[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue + r.log.V(4).Info("predicate evaluation", "object", fmt.Sprintf("%T", object), "name", object.GetName(), "namespace", object.GetNamespace(), "labels", labels, "matches", matches) + return matches + }) + + controllerManagedResourcePredicates := builder.WithPredicates(controllerManagedResources) + + // TODO: Add more watches as resources are implemented + return ctrl.NewControllerManagedBy(mgr). + // GenerationChangedPredicate ignores status-only updates + For(&v1alpha1.TrustManager{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Named(ControllerName). + Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Complete(r) +} + +// Reconcile function to compare the state specified by the TrustManager object against the actual cluster state, +// and to make the cluster state reflect the state specified by the user. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.log.V(1).Info("reconciling", "request", req) + + // Fetch the trustmanager.openshift.operator.io CR + trustManager := &v1alpha1.TrustManager{} + // Note: No namespace because TrustManager is cluster-scoped + if err := r.Get(ctx, types.NamespacedName{Name: req.Name}, trustManager); err != nil { + if errors.IsNotFound(err) { + // NotFound errors, since they can't be fixed by an immediate + // requeue (have to wait for a new notification), and can be processed + // on deleted requests. + r.log.V(1).Info("trustmanager.openshift.operator.io object not found, skipping reconciliation", "request", req) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("failed to fetch trustmanager.openshift.operator.io %q during reconciliation: %w", req.NamespacedName, err) + } + + if !trustManager.DeletionTimestamp.IsZero() { + r.log.V(1).Info("trustmanager.openshift.operator.io is marked for deletion", "name", req.NamespacedName) + + if requeue, err := r.cleanUp(trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("clean up failed for %q trustmanager.openshift.operator.io instance deletion: %w", req.NamespacedName, err) + } else if requeue { + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + + if err := r.removeFinalizer(ctx, trustManager, finalizer); err != nil { + return ctrl.Result{}, err + } + + r.log.V(1).Info("removed finalizer, cleanup complete", "request", req.NamespacedName) + return ctrl.Result{}, nil + } + + // Set finalizers on the trustmanager.openshift.operator.io resource + if err := r.addFinalizer(ctx, trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update %q trustmanager.openshift.operator.io with finalizers: %w", req.NamespacedName, err) + } + + return r.processReconcileRequest(trustManager, req.NamespacedName) +} + +func (r *Reconciler) processReconcileRequest(trustManager *v1alpha1.TrustManager, req types.NamespacedName) (ctrl.Result, error) { + trustManagerCreateRecon := false + if !containsProcessedAnnotation(trustManager) && reflect.DeepEqual(trustManager.Status, v1alpha1.TrustManagerStatus{}) { + r.log.V(1).Info("starting reconciliation of newly created trustmanager", "name", trustManager.GetName()) + trustManagerCreateRecon = true + } + + // TrustManager is a singleton - only "cluster" is valid + // This is enforced by CRD validation, but we double-check here + if err := r.disallowMultipleTrustManagerInstances(trustManager); err != nil { + if common.IsMultipleInstanceError(err) { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "MultiTrustManagerInstance", "creation of multiple trustmanager instances is not supported, will not be processed") + err = nil + } + return ctrl.Result{}, err + } + + var errUpdate error = nil + if err := r.reconcileTrustManagerDeployment(trustManager, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile TrustManager deployment", "request", req) + if common.IsIrrecoverableError(err) { + // Permanent failure - don't retry + // Set Degraded=True, Ready=False + // Set both conditions atomically before updating status + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionTrue, v1alpha1.ReasonFailed, fmt.Sprintf("reconciliation failed with irrecoverable error not retrying: %v", err)) + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on irrecoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate + } else { + // Temporary failure - retry after delay + // Set Degraded=False, Ready=False with "in progress" message + // Set both conditions atomically before updating status + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonInProgress, fmt.Sprintf("reconciliation failed, retrying: %v", err)) + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on recoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, err) + } + // For recoverable errors, either requeue manually or return error, not both + // If status update failed, return the update error; otherwise return the original error + if errUpdate != nil { + return ctrl.Result{}, errUpdate + } + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + } + + // Success - update status + // Set both conditions atomically before updating status on success + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionTrue, v1alpha1.ReasonReady, "reconciliation successful") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on successful reconciliation", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate +} + +// cleanUp handles deletion of trustmanager.openshift.operator.io gracefully. +func (r *Reconciler) cleanUp(trustManager *v1alpha1.TrustManager) (bool, error) { + // TODO: For GA, handle cleaning up of resources created for installing trust-manager operand. + // As per Non-Goals in the enhancement, removing the TrustManager CR will not remove the + // trust-manager deployment or its associated resources. + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "RemoveDeployment", "%s trustmanager marked for deletion, remove all resources created for trustmanager deployment manually", trustManager.GetName()) + return false, nil +} diff --git a/pkg/controller/trustmanager/install_trustmanager.go b/pkg/controller/trustmanager/install_trustmanager.go new file mode 100644 index 000000000..56bd845df --- /dev/null +++ b/pkg/controller/trustmanager/install_trustmanager.go @@ -0,0 +1,93 @@ +package trustmanager + +import ( + "fmt" + "os" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +func (r *Reconciler) reconcileTrustManagerDeployment(trustManager *v1alpha1.TrustManager, trustManagerCreateRecon bool) error { + if err := validateTrustManagerConfig(trustManager); err != nil { + return common.NewIrrecoverableError(err, "%s configuration validation failed", trustManager.GetName()) + } + + resourceLabels := getResourceLabels(trustManager) + + // Validate trust namespace exists + trustNamespace := getTrustNamespace(trustManager) + if err := r.validateTrustNamespace(trustNamespace); err != nil { + return common.NewIrrecoverableError(err, "trust namespace %q validation failed", trustNamespace) + } + + // TODO: Reconcile all trust-manager resources + // For now, just reconcile ServiceAccount to verify controller is working + if err := r.createOrApplyServiceAccounts(trustManager, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile serviceaccount resource") + return err + } + + // TODO: As implementation extends, move status field updates inline within each resource reconciler + if err := r.updateStatusObservedState(trustManager); err != nil { + return fmt.Errorf("failed to update status observed state: %w", err) + } + + if addProcessedAnnotation(trustManager) { + if err := r.UpdateWithRetry(r.ctx, trustManager); err != nil { + return fmt.Errorf("failed to update processed annotation to %s: %w", trustManager.GetName(), err) + } + } + + r.log.V(4).Info("finished reconciliation of trustmanager", "name", trustManager.GetName()) + return nil +} + +// validateTrustNamespace validates that the trust namespace exists. +func (r *Reconciler) validateTrustNamespace(namespace string) error { + exists, err := r.namespaceExists(namespace) + if err != nil { + return fmt.Errorf("failed to check if namespace %q exists: %w", namespace, err) + } + if !exists { + return fmt.Errorf("trust namespace %q does not exist, create the namespace before creating TrustManager CR", namespace) + } + return nil +} + +// updateStatusObservedState populates and persists the TrustManager status with the observed state. +// Returns nil if no changes were needed, otherwise returns an error if the update fails. +func (r *Reconciler) updateStatusObservedState(trustManager *v1alpha1.TrustManager) error { + changed := false + + if image := os.Getenv(trustManagerImageNameEnvVarName); trustManager.Status.TrustManagerImage != image { + trustManager.Status.TrustManagerImage = image + changed = true + } + + if ns := getTrustNamespace(trustManager); trustManager.Status.TrustNamespace != ns { + trustManager.Status.TrustNamespace = ns + changed = true + } + + if policy := trustManager.Spec.TrustManagerConfig.SecretTargets.Policy; trustManager.Status.SecretTargetsPolicy != policy { + trustManager.Status.SecretTargetsPolicy = policy + changed = true + } + + if policy := trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy; trustManager.Status.DefaultCAPackagePolicy != policy { + trustManager.Status.DefaultCAPackagePolicy = policy + changed = true + } + + if policy := trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates; trustManager.Status.FilterExpiredCertificatesPolicy != policy { + trustManager.Status.FilterExpiredCertificatesPolicy = policy + changed = true + } + + if !changed { + return nil + } + + return r.updateStatus(r.ctx, trustManager) +} diff --git a/pkg/controller/trustmanager/serviceaccounts.go b/pkg/controller/trustmanager/serviceaccounts.go new file mode 100644 index 000000000..5a5dc7ccc --- /dev/null +++ b/pkg/controller/trustmanager/serviceaccounts.go @@ -0,0 +1,35 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServiceAccounts(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) error { + desired := r.getServiceAccountObject(resourceLabels) + serviceAccountName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling serviceaccount resource", "name", serviceAccountName) + + // Server-Side Apply: patches only our fields + if err := r.Patch(r.ctx, desired, client.Apply, client.FieldOwner(fieldOwner), client.ForceOwnership); err != nil { + return common.FromClientError(err, "failed to apply serviceaccount %q", serviceAccountName) + } + + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "serviceaccount resource %s applied", serviceAccountName) + r.log.V(2).Info("applied serviceaccount", "name", serviceAccountName) + return nil +} + +func (r *Reconciler) getServiceAccountObject(resourceLabels map[string]string) *corev1.ServiceAccount { + serviceAccount := decodeServiceAccountObjBytes(assets.MustAsset(serviceAccountAssetName)) + common.UpdateNamespace(serviceAccount, operandNamespace) + common.UpdateResourceLabels(serviceAccount, resourceLabels) + + return serviceAccount +} diff --git a/pkg/controller/trustmanager/utils.go b/pkg/controller/trustmanager/utils.go new file mode 100644 index 000000000..d82393618 --- /dev/null +++ b/pkg/controller/trustmanager/utils.go @@ -0,0 +1,219 @@ +package trustmanager + +import ( + "context" + "fmt" + "maps" + "reflect" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +func init() { + if err := corev1.AddToScheme(scheme); err != nil { + panic(err) + } + // TODO: Add more groups to scheme as resources are implemented +} + +// updateStatus is for updating the status subresource of trustmanager.openshift.operator.io. +func (r *Reconciler) updateStatus(ctx context.Context, changed *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(changed) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + r.log.V(4).Info("updating trustmanager.openshift.operator.io status", "request", namespacedName) + current := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, current); err != nil { + return fmt.Errorf("failed to fetch trustmanager.openshift.operator.io %q for status update: %w", namespacedName, err) + } + changed.Status.DeepCopyInto(¤t.Status) + + if err := r.StatusUpdate(ctx, current); err != nil { + return fmt.Errorf("failed to update trustmanager.openshift.operator.io %q status: %w", namespacedName, err) + } + + return nil + }); err != nil { + return err + } + + return nil +} + +// addFinalizer adds finalizer to trustmanager.openshift.operator.io resource. +func (r *Reconciler) addFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if !controllerutil.ContainsFinalizer(trustManager, finalizer) { + if !controllerutil.AddFinalizer(trustManager, finalizer) { + return fmt.Errorf("failed to create %q trustmanager.openshift.operator.io object with finalizers added", namespacedName) + } + + // update trustmanager.openshift.operator.io on adding finalizer. + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to add finalizers on %q trustmanager.openshift.operator.io with %w", namespacedName, err) + } + + updated := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, updated); err != nil { + return fmt.Errorf("failed to fetch trustmanager.openshift.operator.io %q after updating finalizers: %w", namespacedName, err) + } + updated.DeepCopyInto(trustManager) + return nil + } + return nil +} + +// removeFinalizer removes finalizers added to trustmanager.openshift.operator.io resource. +func (r *Reconciler) removeFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager, finalizer string) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if controllerutil.ContainsFinalizer(trustManager, finalizer) { + if !controllerutil.RemoveFinalizer(trustManager, finalizer) { + return fmt.Errorf("failed to create %q trustmanager.openshift.operator.io object with finalizers removed", namespacedName) + } + + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to remove finalizers on %q trustmanager.openshift.operator.io with %w", namespacedName, err) + } + return nil + } + + return nil +} + +func containsProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + return common.ContainsAnnotation(trustManager, controllerProcessedAnnotation) +} + +func containsProcessingRejectedAnnotation(trustManager *v1alpha1.TrustManager) bool { + return common.ContainsAnnotation(trustManager, controllerProcessingRejectedAnnotation) +} + +func addProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + return common.AddAnnotation(trustManager, controllerProcessedAnnotation, "true") +} + +func addProcessingRejectedAnnotation(trustManager *v1alpha1.TrustManager) bool { + return common.AddAnnotation(trustManager, controllerProcessingRejectedAnnotation, "true") +} + +func decodeServiceAccountObjBytes(objBytes []byte) *corev1.ServiceAccount { + obj, err := runtime.Decode(codecs.UniversalDecoder(corev1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*corev1.ServiceAccount) +} + +func validateTrustManagerConfig(trustManager *v1alpha1.TrustManager) error { + if reflect.ValueOf(trustManager.Spec.TrustManagerConfig).IsZero() { + return fmt.Errorf("spec.trustManagerConfig config cannot be empty") + } + return nil +} + +func (r *Reconciler) updateCondition(trustManager *v1alpha1.TrustManager, prependErr error) error { + if err := r.updateStatus(r.ctx, trustManager); err != nil { + errUpdate := fmt.Errorf("failed to update %s status: %w", trustManager.GetName(), err) + if prependErr != nil { + return utilerrors.NewAggregate([]error{prependErr, errUpdate}) + } + return errUpdate + } + return prependErr +} + +func (r *Reconciler) disallowMultipleTrustManagerInstances(trustManager *v1alpha1.TrustManager) error { + statusMessage := fmt.Sprintf("multiple instances of trustmanager exists, %s will not be processed", trustManager.GetName()) + + if containsProcessingRejectedAnnotation(trustManager) { + r.log.V(4).Info("trustmanager resource contains processing rejected annotation", "name", trustManager.Name) + // ensure status is updated. + var updateErr error + if trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, statusMessage) { + updateErr = r.updateCondition(trustManager, nil) + } + return common.NewMultipleInstanceError(utilerrors.NewAggregate([]error{fmt.Errorf("%s", statusMessage), updateErr})) + } + + trustManagerList := &v1alpha1.TrustManagerList{} + if err := r.List(r.ctx, trustManagerList); err != nil { + return fmt.Errorf("failed to fetch list of trustmanager resources: %w", err) + } + + if len(trustManagerList.Items) <= 1 { + return nil + } + + ignoreProcessing := false + for _, item := range trustManagerList.Items { + if item.GetName() == trustManager.Name { + continue + } + if item.CreationTimestamp.Time.Before(trustManager.CreationTimestamp.Time) || + // Even when timestamps are equal will skip processing. And if this ends + // up in ignoring all trustmanager instances, which means user must have created + // all in parallel, onus is on user to delete all and recreate just one required + // instance of trustmanager. + item.CreationTimestamp.Time.Equal(trustManager.CreationTimestamp.Time) { + ignoreProcessing = true + } + } + + if ignoreProcessing { + var condUpdateErr, annUpdateErr error + if trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonFailed, statusMessage) { + condUpdateErr = r.updateCondition(trustManager, nil) + } + if addProcessingRejectedAnnotation(trustManager) { + if err := r.UpdateWithRetry(r.ctx, trustManager); err != nil { + annUpdateErr = fmt.Errorf("failed to update reject processing annotation to %s: %w", trustManager.GetName(), err) + } + } + if condUpdateErr != nil || annUpdateErr != nil { + return utilerrors.NewAggregate([]error{condUpdateErr, annUpdateErr}) + } + } + + return common.NewMultipleInstanceError(fmt.Errorf("%s", statusMessage)) +} + +// getTrustNamespace returns the trust namespace from the TrustManager config. +// If not specified, returns the default trust namespace. +func getTrustNamespace(trustManager *v1alpha1.TrustManager) string { + if trustManager.Spec.TrustManagerConfig.TrustNamespace != "" { + return trustManager.Spec.TrustManagerConfig.TrustNamespace + } + return defaultTrustNamespace +} + +// getResourceLabels returns the labels to apply to all resources created by the controller. +// It merges user-specified labels with the controller's default labels. +func getResourceLabels(trustManager *v1alpha1.TrustManager) map[string]string { + resourceLabels := make(map[string]string) + if len(trustManager.Spec.ControllerConfig.Labels) != 0 { + maps.Copy(resourceLabels, trustManager.Spec.ControllerConfig.Labels) + } + maps.Copy(resourceLabels, controllerDefaultResourceLabels) + return resourceLabels +} + +// namespaceExists checks if a namespace exists in the cluster. +func (r *Reconciler) namespaceExists(namespace string) (bool, error) { + ns := &corev1.Namespace{} + key := client.ObjectKey{Name: namespace} + return r.Exists(r.ctx, key, ns) +} diff --git a/pkg/features/features_test.go b/pkg/features/features_test.go index 357d7af43..b7a60be8e 100644 --- a/pkg/features/features_test.go +++ b/pkg/features/features_test.go @@ -2,6 +2,7 @@ package features import ( "fmt" + "slices" "strings" "testing" @@ -23,7 +24,9 @@ var expectedDefaultFeatureState = map[bool][]featuregate.Feature{ // features DISABLED by default, // list of features which are expected to be disabled at runtime. - false: {}, + false: { + featuregate.Feature("TrustManager"), + }, } func TestFeatureGates(t *testing.T) { @@ -32,6 +35,7 @@ func TestFeatureGates(t *testing.T) { for _, featureNames := range expectedDefaultFeatureState { testFeatureNames = append(testFeatureNames, featureNames...) } + slices.Sort(testFeatureNames) knownOperatorFeatures := make([]featuregate.Feature, 0) feats := mutableFeatureGate.GetAll() @@ -42,6 +46,7 @@ func TestFeatureGates(t *testing.T) { } knownOperatorFeatures = append(knownOperatorFeatures, feat) } + slices.Sort(knownOperatorFeatures) assert.Equal(t, knownOperatorFeatures, testFeatureNames, `the list of features known to the operator differ from what is being tested here, diff --git a/pkg/operator/setup_manager.go b/pkg/operator/setup_manager.go index e7c350b51..b134adb0c 100644 --- a/pkg/operator/setup_manager.go +++ b/pkg/operator/setup_manager.go @@ -8,19 +8,26 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr" + "github.com/openshift/cert-manager-operator/pkg/controller/trustmanager" "github.com/openshift/cert-manager-operator/pkg/version" ) @@ -29,6 +36,27 @@ var ( setupLog = ctrl.Log.WithName("setup-manager") ) +// istioCSRManagedResources defines the resources managed by the IstioCSR controller. +// These resources will be watched with a label selector filter. +var istioCSRManagedResources = []client.Object{ + &certmanagerv1.Certificate{}, + &appsv1.Deployment{}, + &rbacv1.ClusterRole{}, + &rbacv1.ClusterRoleBinding{}, + &rbacv1.Role{}, + &rbacv1.RoleBinding{}, + &corev1.Service{}, + &corev1.ServiceAccount{}, + &networkingv1.NetworkPolicy{}, +} + +// trustManagerManagedResources defines the resources managed by the TrustManager controller. +// These resources will be watched with a label selector filter. +// TODO: Add more resources as they are implemented +var trustManagerManagedResources = []client.Object{ + &corev1.ServiceAccount{}, +} + func init() { ctrllog.SetLogger(klog.NewKlogr()) @@ -42,42 +70,160 @@ func init() { // +kubebuilder:scaffold:scheme } -// Manager holds the manager resource for the istio-csr controller. +// Manager holds the manager resource for the controller-runtime based controllers. type Manager struct { manager manager.Manager } -// NewControllerManager creates a new manager. -func NewControllerManager() (*Manager, error) { - setupLog.Info("setting up operator manager", "controller", istiocsr.ControllerName) +// ControllerConfig specifies which controllers to enable in the unified manager. +type ControllerConfig struct { + EnableIstioCSR bool + EnableTrustManager bool +} + +// NewControllerManager creates a unified manager for all enabled operand controllers. +// It shares a single metrics server, cache, and client across all controllers. +func NewControllerManager(config ControllerConfig) (*Manager, error) { + setupLog.Info("setting up unified operator manager") setupLog.Info("controller", "version", version.Get()) + setupLog.Info("enabled controllers", "istioCSR", config.EnableIstioCSR, "trustManager", config.EnableTrustManager) + + cacheBuilder := newUnifiedCacheBuilder(config) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - // Use custom cache builder to configure label selectors for managed resources - NewCache: istiocsr.NewCacheBuilder, + Scheme: scheme, + NewCache: cacheBuilder, Logger: ctrl.Log.WithName("operator-manager"), }) if err != nil { return nil, fmt.Errorf("failed to create manager: %w", err) } - r, err := istiocsr.New(mgr) - if err != nil { - return nil, fmt.Errorf("failed to create %s reconciler object: %w", istiocsr.ControllerName, err) + // Setup enabled controllers + if config.EnableIstioCSR { + if err := setupIstioCSRController(mgr); err != nil { + return nil, err + } } - if err := r.SetupWithManager(mgr); err != nil { - return nil, fmt.Errorf("failed to create %s controller: %w", istiocsr.ControllerName, err) + + if config.EnableTrustManager { + if err := setupTrustManagerController(mgr); err != nil { + return nil, err + } } - // +kubebuilder:scaffold:builder return &Manager{ manager: mgr, }, nil } -// Start starts the operator synchronously until a message is received from ctx. +// setupIstioCSRController creates and registers the IstioCSR controller with the manager. +func setupIstioCSRController(mgr ctrl.Manager) error { + setupLog.Info("setting up controller", "name", istiocsr.ControllerName) + r, err := istiocsr.New(mgr) + if err != nil { + return fmt.Errorf("failed to create %s reconciler object: %w", istiocsr.ControllerName, err) + } + if err := r.SetupWithManager(mgr); err != nil { + return fmt.Errorf("failed to create %s controller: %w", istiocsr.ControllerName, err) + } + return nil +} + +// setupTrustManagerController creates and registers the TrustManager controller with the manager. +func setupTrustManagerController(mgr ctrl.Manager) error { + setupLog.Info("setting up controller", "name", trustmanager.ControllerName) + r, err := trustmanager.New(mgr) + if err != nil { + return fmt.Errorf("failed to create %s reconciler object: %w", trustmanager.ControllerName, err) + } + if err := r.SetupWithManager(mgr); err != nil { + return fmt.Errorf("failed to create %s controller: %w", trustmanager.ControllerName, err) + } + return nil +} + +// newUnifiedCacheBuilder creates a cache builder that combines cache configurations +// for all enabled controllers into a single unified cache. +func newUnifiedCacheBuilder(config ControllerConfig) cache.NewCacheFunc { + return func(restConfig *rest.Config, opts cache.Options) (cache.Cache, error) { + objectList, err := buildCacheObjectList(config) + if err != nil { + return nil, err + } + opts.ByObject = objectList + return cache.New(restConfig, opts) + } +} + +// buildCacheObjectList creates the cache configuration with label selectors +// for managed resources based on enabled controllers. +// All controllers use common.ManagedResourceLabelKey as the label key. +func buildCacheObjectList(config ControllerConfig) (map[client.Object]cache.ByObject, error) { + objectList := make(map[client.Object]cache.ByObject) + + if config.EnableIstioCSR { + if err := addControllerCacheConfig(objectList, istiocsr.RequestEnqueueLabelValue, istioCSRManagedResources); err != nil { + return nil, fmt.Errorf("failed to configure IstioCSR cache: %w", err) + } + // IstioCSR CR - no label filter needed + objectList[&v1alpha1.IstioCSR{}] = cache.ByObject{} + } + + if config.EnableTrustManager { + if err := addControllerCacheConfig(objectList, trustmanager.RequestEnqueueLabelValue, trustManagerManagedResources); err != nil { + return nil, fmt.Errorf("failed to configure TrustManager cache: %w", err) + } + // TrustManager CR - no label filter needed + objectList[&v1alpha1.TrustManager{}] = cache.ByObject{} + } + + return objectList, nil +} + +// addControllerCacheConfig adds cache configuration for a controller's managed resources. +// All controllers use common.ManagedResourceLabelKey as the label key. +// If a resource type already exists (from another controller), the label selector is updated +// to use the 'In' operator to match resources from either controller. +func addControllerCacheConfig(objectList map[client.Object]cache.ByObject, labelValue string, resources []client.Object) error { + labelKey := common.ManagedResourceLabelKey + + for _, res := range resources { + resType := fmt.Sprintf("%T", res) + + if existing, exists := objectList[res]; exists { + // Resource already configured by another controller + // Merge label values using 'In' operator: app in (value1, value2) + existingReqs, _ := existing.Label.Requirements() + var existingValues []string + for _, req := range existingReqs { + if req.Key() == labelKey { + existingValues = req.Values().List() + break + } + } + + // Create new requirement with both values + mergedValues := append(existingValues, labelValue) + mergedReq, err := labels.NewRequirement(labelKey, selection.In, mergedValues) + if err != nil { + return fmt.Errorf("failed to create merged label requirement for key %q with values %v: %w", labelKey, mergedValues, err) + } + objectList[res] = cache.ByObject{Label: labels.NewSelector().Add(*mergedReq)} + setupLog.V(4).Info("merged label selector for shared resource", "type", resType, "values", mergedValues) + } else { + // First controller to configure this resource + labelReq, err := labels.NewRequirement(labelKey, selection.Equals, []string{labelValue}) + if err != nil { + return fmt.Errorf("failed to create label requirement for key %q with value %q: %w", labelKey, labelValue, err) + } + objectList[res] = cache.ByObject{Label: labels.NewSelector().Add(*labelReq)} + } + } + return nil +} + +// Start starts the unified controller manager synchronously until ctx is cancelled. func (mgr *Manager) Start(ctx context.Context) error { - mgr.manager.GetEventRecorderFor("cert-manager-istio-csr-controller").Event(&v1alpha1.IstioCSR{}, corev1.EventTypeNormal, "ControllerStarted", "controller is starting") return mgr.manager.Start(ctx) } diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index bc00e61ff..77659ca47 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -139,16 +139,25 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error return fmt.Errorf("failed to parse addon features: %w", err) } - // enable controller-runtime and istio-csr controller - // only when "IstioCSR" feature is turned on from --addon-features - if features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) { - manager, err := NewControllerManager() + // Check if any operand controllers are enabled + istioCSREnabled := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) + trustManagerEnabled := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureTrustManager) + + if istioCSREnabled || trustManagerEnabled { + // Create unified manager for all enabled operand controllers + manager, err := NewControllerManager(ControllerConfig{ + EnableIstioCSR: istioCSREnabled, + EnableTrustManager: trustManagerEnabled, + }) if err != nil { - return fmt.Errorf("failed to create controller manager: %w", err) - } - if err := manager.Start(ctrl.SetupSignalHandler()); err != nil { //nolint:contextcheck // SetupSignalHandler creates a new context for signal handling, which is intentional - return fmt.Errorf("failed to start istiocsr controller: %w", err) + return fmt.Errorf("failed to create unified controller manager: %w", err) } + + go func() { + if err := manager.Start(ctrl.SetupSignalHandler()); err != nil { + ctrl.Log.Error(err, "failed to start unified controller manager") + } + }() } <-ctx.Done() diff --git a/test/e2e/trustmanager_test.go b/test/e2e/trustmanager_test.go new file mode 100644 index 000000000..98e85cab6 --- /dev/null +++ b/test/e2e/trustmanager_test.go @@ -0,0 +1,227 @@ +//go:build e2e +// +build e2e + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + operatorclientv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/typed/operator/v1alpha1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" +) + +const ( + trustManagerNamespace = "cert-manager" + trustManagerServiceAccountName = "trust-manager" + trustManagerCommonName = "cert-manager-trust-manager" +) + +var _ = Describe("TrustManager", Ordered, Label("Feature:TrustManager"), func() { + ctx := context.TODO() + var clientset *kubernetes.Clientset + + trustManagerClient := func() operatorclientv1alpha1.TrustManagerInterface { + return certmanageroperatorclient.OperatorV1alpha1().TrustManagers() + } + + waitForTrustManagerReady := func() v1alpha1.TrustManagerStatus { + By("waiting for TrustManager CR to be ready") + status, err := pollTillTrustManagerAvailable(ctx, trustManagerClient(), "cluster") + Expect(err).Should(BeNil()) + + return status + } + + BeforeAll(func() { + var err error + clientset, err = kubernetes.NewForConfig(cfg) + Expect(err).Should(BeNil()) + + By("enabling TrustManager feature gate via subscription") + err = patchSubscriptionWithEnvVars(ctx, loader, map[string]string{ + "UNSUPPORTED_ADDON_FEATURES": "TrustManager=true", + }) + Expect(err).NotTo(HaveOccurred()) + + By("waiting for operator deployment to rollout with TrustManager feature enabled") + err = waitForDeploymentEnvVarAndRollout(ctx, operatorNamespace, operatorDeploymentName, "UNSUPPORTED_ADDON_FEATURES", "TrustManager=true", lowTimeout) + Expect(err).NotTo(HaveOccurred()) + }) + + BeforeEach(func() { + By("waiting for operator status to become available") + err := VerifyHealthyOperatorConditions(certmanageroperatorclient.OperatorV1alpha1()) + Expect(err).NotTo(HaveOccurred(), "Operator is expected to be available") + }) + + AfterEach(func() { + By("cleaning up TrustManager CR if it exists") + _ = trustManagerClient().Delete(ctx, "cluster", metav1.DeleteOptions{}) + + By("waiting for TrustManager CR to be deleted") + Eventually(func() bool { + _, err := trustManagerClient().Get(ctx, "cluster", metav1.GetOptions{}) + return apierrors.IsNotFound(err) + }, lowTimeout, fastPollInterval).Should(BeTrue()) + }) + + // Note: Currently, the TrustManager controller only reconciles ServiceAccounts. + // Additional tests for Deployments, RBAC, ConfigMaps, etc. should be added + // as those resources are implemented. + + Context("basic reconciliation", func() { + It("should create TrustManager CR and reconcile ServiceAccount", func() { + By("creating TrustManager CR with default settings") + _, err := trustManagerClient().Create(ctx, &v1alpha1.TrustManager{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Spec: v1alpha1.TrustManagerSpec{ + TrustManagerConfig: v1alpha1.TrustManagerConfig{}, + }, + }, metav1.CreateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + status := waitForTrustManagerReady() + Expect(status.TrustNamespace).Should(Equal("cert-manager")) + + By("verifying ServiceAccount is created with correct labels") + sa, err := clientset.CoreV1().ServiceAccounts(trustManagerNamespace).Get(ctx, trustManagerServiceAccountName, metav1.GetOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + verifyTrustManagerManagedLabels(sa.Labels) + + By("modifying ServiceAccount labels externally") + sa.Labels["app.kubernetes.io/instance"] = "modified-value" + _, err = clientset.CoreV1().ServiceAccounts(trustManagerNamespace).Update(ctx, sa, metav1.UpdateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + By("verifying controller reconciles and restores correct labels") + Eventually(func(g Gomega) { + sa, err := clientset.CoreV1().ServiceAccounts(trustManagerNamespace).Get(ctx, trustManagerServiceAccountName, metav1.GetOptions{}) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(sa.Labels).Should(HaveKeyWithValue("app.kubernetes.io/instance", trustManagerCommonName)) + }, lowTimeout, fastPollInterval).Should(Succeed()) + + By("deleting ServiceAccount externally") + err = clientset.CoreV1().ServiceAccounts(trustManagerNamespace).Delete(ctx, trustManagerServiceAccountName, metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + By("verifying controller reconciles and recreates ServiceAccount") + Eventually(func(g Gomega) { + sa, err := clientset.CoreV1().ServiceAccounts(trustManagerNamespace).Get(ctx, trustManagerServiceAccountName, metav1.GetOptions{}) + g.Expect(err).ShouldNot(HaveOccurred()) + verifyTrustManagerManagedLabels(sa.Labels) + }, lowTimeout, fastPollInterval).Should(Succeed()) + }) + + It("should reflect spec values in status", func() { + customNamespace := "custom-trust-ns" + + By("creating custom trust namespace") + _, err := clientset.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: customNamespace}, + }, metav1.CreateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + defer func() { + By("cleaning up custom trust namespace") + _ = clientset.CoreV1().Namespaces().Delete(ctx, customNamespace, metav1.DeleteOptions{}) + }() + + By("creating TrustManager CR with custom configuration") + _, err = trustManagerClient().Create(ctx, &v1alpha1.TrustManager{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Spec: v1alpha1.TrustManagerSpec{ + TrustManagerConfig: v1alpha1.TrustManagerConfig{ + TrustNamespace: customNamespace, + SecretTargets: v1alpha1.SecretTargetsConfig{ + Policy: v1alpha1.SecretTargetsPolicyCustom, + AuthorizedSecrets: []string{"secret1", "secret2"}, + }, + DefaultCAPackage: v1alpha1.DefaultCAPackageConfig{ + Policy: v1alpha1.DefaultCAPackagePolicyEnabled, + }, + FilterExpiredCertificates: v1alpha1.FilterExpiredCertificatesPolicyEnabled, + }, + }, + }, metav1.CreateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + status := waitForTrustManagerReady() + + By("verifying spec values are reflected in status") + // Expect(status.TrustManagerImage).ShouldNot(BeEmpty()) // TODO: uncomment this when we will implement deployment resource + Expect(status.TrustNamespace).Should(Equal(customNamespace)) + Expect(status.SecretTargetsPolicy).Should(Equal(v1alpha1.SecretTargetsPolicyCustom)) + Expect(status.DefaultCAPackagePolicy).Should(Equal(v1alpha1.DefaultCAPackagePolicyEnabled)) + Expect(status.FilterExpiredCertificatesPolicy).Should(Equal(v1alpha1.FilterExpiredCertificatesPolicyEnabled)) + }) + }) + + Context("singleton validation", func() { + It("should reject TrustManager with name other than 'cluster'", func() { + By("attempting to create TrustManager with invalid name") + _, err := trustManagerClient().Create(ctx, &v1alpha1.TrustManager{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid-name"}, + Spec: v1alpha1.TrustManagerSpec{ + TrustManagerConfig: v1alpha1.TrustManagerConfig{}, + }, + }, metav1.CreateOptions{}) + Expect(err).Should(HaveOccurred()) + // CEL validation error: TrustManager is a singleton, .metadata.name must be 'cluster' + Expect(err.Error()).Should(ContainSubstring("TrustManager is a singleton")) + Expect(err.Error()).Should(ContainSubstring(".metadata.name must be 'cluster'")) + }) + }) +}) + +// pollTillTrustManagerAvailable polls the TrustManager object and returns its status +// once the TrustManager is available, otherwise returns a time-out error +func pollTillTrustManagerAvailable(ctx context.Context, client operatorclientv1alpha1.TrustManagerInterface, trustManagerName string) (v1alpha1.TrustManagerStatus, error) { + var trustManagerStatus v1alpha1.TrustManagerStatus + + err := wait.PollUntilContextTimeout(ctx, slowPollInterval, highTimeout, true, func(context.Context) (bool, error) { + trustManager, err := client.Get(ctx, trustManagerName, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + trustManagerStatus = trustManager.Status + + // Check ready condition + readyCondition := meta.FindStatusCondition(trustManagerStatus.Conditions, v1alpha1.Ready) + if readyCondition == nil { + return false, nil + } + + // Check for degraded condition + degradedCondition := meta.FindStatusCondition(trustManagerStatus.Conditions, v1alpha1.Degraded) + if degradedCondition != nil && degradedCondition.Status == metav1.ConditionTrue { + return false, nil // Return false to keep polling, not an error + } + + return readyCondition.Status == metav1.ConditionTrue, nil + }) + + return trustManagerStatus, err +} + +// verifyTrustManagerManagedLabels verifies that the resource has all the expected +// labels for resources managed by the TrustManager controller. +func verifyTrustManagerManagedLabels(labels map[string]string) { + Expect(labels).Should(HaveKeyWithValue("app", trustManagerCommonName)) + Expect(labels).Should(HaveKeyWithValue("app.kubernetes.io/name", trustManagerCommonName)) + Expect(labels).Should(HaveKeyWithValue("app.kubernetes.io/instance", trustManagerCommonName)) + Expect(labels).Should(HaveKeyWithValue("app.kubernetes.io/managed-by", "cert-manager-operator")) + Expect(labels).Should(HaveKeyWithValue("app.kubernetes.io/part-of", "cert-manager-operator")) + Expect(labels).Should(HaveKey("app.kubernetes.io/version")) // Version comes from env var +} diff --git a/tools/go.mod b/tools/go.mod index 482c49907..6585cc03d 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,7 +6,7 @@ require ( github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/golangci/golangci-lint/v2 v2.7.2 github.com/google/go-jsonnet v0.21.0 - github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 + github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 github.com/openshift/api v0.0.0-20260105191300-d1c4dc4fd37b github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af golang.org/x/tools v0.41.0 diff --git a/tools/go.sum b/tools/go.sum index c2cedeecf..a9245922e 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -342,8 +342,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 h1:NicmruxkeqHjDv03SfSxqmaLuisddudfP3h5wdXFbhM= -github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I= +github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 h1:D4O2wLxB384TS3ohBJMfolnxb4qGmoZ1PnWNtit8LYo= +github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1/go.mod h1:RuJdxo0oI6dClIaMzdl3hewq3a065RH65dofJP03h8I= github.com/mgechev/revive v1.13.0 h1:yFbEVliCVKRXY8UgwEO7EOYNopvjb1BFbmYqm9hZjBM= github.com/mgechev/revive v1.13.0/go.mod h1:efJfeBVCX2JUumNQ7dtOLDja+QKj9mYGgEZA7rt5u+0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/.gitignore b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/.gitignore index 1597f12b7..070b2a1fc 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/.gitignore +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/.gitignore @@ -30,3 +30,4 @@ integration/testdata/output *.profile *.bench /.vscode +.DS_Store diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/README.md b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/README.md index 5d471d643..dd3689898 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/README.md +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/README.md @@ -20,25 +20,14 @@ If you are having problems with `counterfeiter` and are not using a supported ve Typically, `counterfeiter` is used in `go generate` directives. It can be frustrating when you change your interface declaration and suddenly all of your generated code is suddenly out-of-date. The best practice here is to use the [`go generate` command](https://blog.golang.org/generate) to make it easier to keep your test doubles up to date. -#### Step 1 - Create `tools.go` +⚠️ If you are working with go 1.23 or earlier, please refer to an [older version of this README](https://github.com/maxbrunsfeld/counterfeiter/blob/e39cbe6aaa94a0b6718cf3d413cd5319c3a1f6fa/README.md#using-counterfeiter), as the instructions below assume go 1.24 (which added `go tool` support) and later. -You can take a dependency on tools by creating a `tools.go` file, as described in [How can I track tool dependencies for a module?](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module). This ensures that everyone working with your module is using the same version of each tool you use. +#### Step 1 - Add `counterfeiter` as a tool dependency -```shell -$ cat tools/tools.go -``` - -```go -//go:build tools +Establish a tool dependency on counterfeiter by running the following command: -package tools - -import ( - _ "github.com/maxbrunsfeld/counterfeiter/v6" -) - -// This file imports packages that are used when running go generate, or used -// during the development process but not otherwise depended on by built code. +```shell +go get -tool github.com/maxbrunsfeld/counterfeiter/v6 ``` #### Step 2a - Add `go:generate` Directives @@ -52,7 +41,7 @@ $ cat myinterface.go ```go package foo -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . MySpecialInterface +//go:generate go tool counterfeiter . MySpecialInterface type MySpecialInterface interface { DoThings(string, uint64) (int, error) @@ -67,8 +56,8 @@ Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... D #### Step 2b - Add `counterfeiter:generate` Directives If you plan to have many directives in a single package, consider using this -option. You can add directives right next to your interface definitions -(or not), in any `.go` file in your module. +option, as it will speed things up considerably. You can add directives right +next to your interface definitions (or not), in any `.go` file in your module. ```shell $ cat myinterface.go @@ -78,7 +67,7 @@ $ cat myinterface.go package foo // You only need **one** of these per package! -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate +//go:generate go tool counterfeiter -generate // You will add lots of directives like these in the same package... //counterfeiter:generate . MySpecialInterface @@ -112,7 +101,7 @@ $ go generate ./... You can use the following command to invoke `counterfeiter` from within a go module: ```shell -$ go run github.com/maxbrunsfeld/counterfeiter/v6 +$ go tool counterfeiter USAGE counterfeiter @@ -153,13 +142,13 @@ type MySpecialInterface interface { ``` ```shell -$ go run github.com/maxbrunsfeld/counterfeiter/v6 path/to/foo MySpecialInterface +$ go tool counterfeiter path/to/foo MySpecialInterface Wrote `FakeMySpecialInterface` to `path/to/foo/foofakes/fake_my_special_interface.go` ``` ### Using Test Doubles In Your Tests -Instantiate fakes`: +Instantiate fakes: ```go import "my-repo/path/to/foo/foofakes" @@ -196,7 +185,7 @@ For more examples of using the `counterfeiter` API, look at [some of the provide For third party interfaces, you can specify the interface using the alternative syntax `.`, for example: ```shell -$ go run github.com/maxbrunsfeld/counterfeiter/v6 github.com/go-redis/redis.Pipeliner +$ go tool counterfeiter github.com/go-redis/redis.Pipeliner ``` ### Running The Tests For `counterfeiter` diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/arguments/parser.go b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/arguments/parser.go index 557d4959f..7b1ec82a3 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/arguments/parser.go +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/arguments/parser.go @@ -91,7 +91,7 @@ func New(args []string, workingDir string, evaler Evaler, stater Stater) (*Parse } func (a *ParsedArguments) PrettyPrint() { - b, _ := json.Marshal(a) + b, _ := json.MarshalIndent(a, "", " ") fmt.Println(string(b)) } diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/fake.go b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/fake.go index bf88f18d7..fbdad7b35 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/fake.go +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/fake.go @@ -25,19 +25,22 @@ const ( // Fake is used to generate a Fake implementation of an interface. type Fake struct { - Packages []*packages.Package - Package *packages.Package - Target *types.TypeName - Mode FakeMode - DestinationPackage string - Name string - TargetAlias string - TargetName string - TargetPackage string - Imports Imports - Methods []Method - Function Method - Header string + Packages []*packages.Package + Package *packages.Package + Target *types.TypeName + Mode FakeMode + DestinationPackage string + Name string + GenericTypeParametersAndConstraints string + GenericTypeParameters string + GenericTypeConstraints string + TargetAlias string + TargetName string + TargetPackage string + Imports Imports + Methods []Method + Function Method + Header string } // Method is a method of the interface. @@ -101,6 +104,72 @@ func (f *Fake) IsFunction() bool { return ok } +// IsConstraintInterface indicates whether the interface is a constraint interface +// (contains type constraints like ~string) which cannot be implemented by concrete types. +func (f *Fake) IsConstraintInterface() bool { + if !f.IsInterface() { + return false + } + + iface, ok := f.Target.Type().Underlying().(*types.Interface) + if !ok { + return false + } + + // check if the interface has any type constraints + for i := 0; i < iface.NumEmbeddeds(); i++ { + if _, ok := iface.EmbeddedType(i).(*types.Union); ok { + return true + } + } + + // check for approximation constraints by examining the string representation + // a bit of a hack, but the Go types API doesn't expose type constraints cleanly + return strings.Contains(iface.String(), "~") +} + +// HasConstraintInterface indicates whether any of the generic type constraints +// are constraint interfaces that cannot be used in type assertions. +func (f *Fake) HasConstraintInterface() bool { + if f.Target == nil || f.Target.Type() == nil { + return false + } + + named, ok := f.Target.Type().(*types.Named) + if !ok { + return false + } + + typeParams := named.TypeParams() + if typeParams.Len() == 0 { + return false + } + + for i := 0; i < typeParams.Len(); i++ { + param := typeParams.At(i) + constraint := param.Constraint() + + // check if the constraint is a constraint interface + if iface, ok := constraint.Underlying().(*types.Interface); ok { + // check if this interface contains type constraints + for j := 0; j < iface.NumEmbeddeds(); j++ { + if _, ok := iface.EmbeddedType(j).(*types.Union); ok { + return true + } + } + + // check for approximation constraints by examining the string representation + // a bit of a hack, but the Go types API doesn't expose type constraints cleanly + constraintStr := constraint.String() + if strings.Contains(constraintStr, "~") { + return true + } + } + } + + return false +} + func unexport(s string) string { s = strings.TrimSpace(s) if s == "" { diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/interface_template.go b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/interface_template.go index 1a8fde9b4..1a946f807 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/interface_template.go +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/interface_template.go @@ -10,12 +10,15 @@ import ( var title = cases.Title(language.Und, cases.NoLower) +var hasConstraintInterface = func(f *Fake) bool { return f.HasConstraintInterface() } + var interfaceFuncs = template.FuncMap{ - "ToLower": strings.ToLower, - "UnExport": unexport, - "Replace": strings.Replace, - "IsExported": isExported, - "Title": title.String, + "ToLower": strings.ToLower, + "UnExport": unexport, + "Replace": strings.Replace, + "IsExported": isExported, + "Title": title.String, + "HasConstraintInterface": hasConstraintInterface, } const interfaceTemplate string = `{{.Header}}// Code generated by counterfeiter. DO NOT EDIT. @@ -27,7 +30,7 @@ import ( {{- end}} ) -type {{.Name}} struct { +type {{.Name}}{{.GenericTypeParametersAndConstraints}} struct { {{- range .Methods}} {{.Name}}Stub func({{.Params.AsArgs}}) {{.Returns.AsReturnSignature}} {{UnExport .Name}}Mutex sync.RWMutex @@ -54,7 +57,7 @@ type {{.Name}} struct { } {{range .Methods -}} -func (fake *{{$.Name}}) {{.Name}}({{.Params.AsNamedArgsWithTypes}}) {{.Returns.AsReturnSignature}} { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{.Name}}({{.Params.AsNamedArgsWithTypes}}) {{.Returns.AsReturnSignature}} { {{- range .Params.Slices}} var {{UnExport .Name}}Copy {{.Type}} if {{UnExport .Name}} != nil { @@ -90,20 +93,20 @@ func (fake *{{$.Name}}) {{.Name}}({{.Params.AsNamedArgsWithTypes}}) {{.Returns.A {{- end}} } -func (fake *{{$.Name}}) {{Title .Name}}CallCount() int { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}CallCount() int { fake.{{UnExport .Name}}Mutex.RLock() defer fake.{{UnExport .Name}}Mutex.RUnlock() return len(fake.{{UnExport .Name}}ArgsForCall) } -func (fake *{{$.Name}}) {{Title .Name}}Calls(stub func({{.Params.AsArgs}}) {{.Returns.AsReturnSignature}}) { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}Calls(stub func({{.Params.AsArgs}}) {{.Returns.AsReturnSignature}}) { fake.{{UnExport .Name}}Mutex.Lock() defer fake.{{UnExport .Name}}Mutex.Unlock() fake.{{.Name}}Stub = stub } {{if .Params.HasLength -}} -func (fake *{{$.Name}}) {{Title .Name}}ArgsForCall(i int) {{.Params.AsReturnSignature}} { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}ArgsForCall(i int) {{.Params.AsReturnSignature}} { fake.{{UnExport .Name}}Mutex.RLock() defer fake.{{UnExport .Name}}Mutex.RUnlock() argsForCall := fake.{{UnExport .Name}}ArgsForCall[i] @@ -112,7 +115,7 @@ func (fake *{{$.Name}}) {{Title .Name}}ArgsForCall(i int) {{.Params.AsReturnSign {{- end}} {{if .Returns.HasLength -}} -func (fake *{{$.Name}}) {{Title .Name}}Returns({{.Returns.AsNamedArgsWithTypes}}) { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}Returns({{.Returns.AsNamedArgsWithTypes}}) { fake.{{UnExport .Name}}Mutex.Lock() defer fake.{{UnExport .Name}}Mutex.Unlock() fake.{{.Name}}Stub = nil @@ -123,7 +126,7 @@ func (fake *{{$.Name}}) {{Title .Name}}Returns({{.Returns.AsNamedArgsWithTypes}} }{ {{- .Returns.AsNamedArgs -}} } } -func (fake *{{$.Name}}) {{Title .Name}}ReturnsOnCall(i int, {{.Returns.AsNamedArgsWithTypes}}) { +func (fake *{{$.Name}}{{$.GenericTypeParameters}}) {{Title .Name}}ReturnsOnCall(i int, {{.Returns.AsNamedArgsWithTypes}}) { fake.{{UnExport .Name}}Mutex.Lock() defer fake.{{UnExport .Name}}Mutex.Unlock() fake.{{.Name}}Stub = nil @@ -144,13 +147,9 @@ func (fake *{{$.Name}}) {{Title .Name}}ReturnsOnCall(i int, {{.Returns.AsNamedAr {{end -}} {{end}} -func (fake *{{.Name}}) Invocations() map[string][][]interface{} { +func (fake *{{.Name}}{{$.GenericTypeParameters}}) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - {{- range .Methods}} - fake.{{UnExport .Name}}Mutex.RLock() - defer fake.{{UnExport .Name}}Mutex.RUnlock() - {{- end}} copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value @@ -158,7 +157,7 @@ func (fake *{{.Name}}) Invocations() map[string][][]interface{} { return copiedInvocations } -func (fake *{{.Name}}) recordInvocation(key string, args []interface{}) { +func (fake *{{.Name}}{{$.GenericTypeParameters}}) recordInvocation(key string, args []interface{}) { fake.invocationsMutex.Lock() defer fake.invocationsMutex.Unlock() if fake.invocations == nil { @@ -171,6 +170,8 @@ func (fake *{{.Name}}) recordInvocation(key string, args []interface{}) { } {{if IsExported .TargetName -}} -var _ {{.TargetAlias}}.{{.TargetName}} = new({{.Name}}) +{{if not (HasConstraintInterface .) -}} +var _ {{.TargetAlias}}.{{.TargetName}}{{.GenericTypeConstraints}} = new({{.Name}}{{.GenericTypeConstraints}}) +{{- end}} {{- end}} ` diff --git a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/loader.go b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/loader.go index 4a8695b1e..08233b0d6 100644 --- a/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/loader.go +++ b/vendor/github.com/maxbrunsfeld/counterfeiter/v6/generator/loader.go @@ -57,9 +57,34 @@ func (f *Fake) loadPackages(c Cacher, workingDir string) error { return nil } +func (f *Fake) getGenericTypeData(typeName *types.TypeName) (paramNames []string, constraintNames []string, paramAndConstraintNames []string, found bool) { + if named, ok := typeName.Type().(*types.Named); ok { + if _, ok := named.Underlying().(*types.Interface); ok { + typeParams := named.TypeParams() + if typeParams.Len() > 0 { + for i := 0; i < typeParams.Len(); i++ { + param := typeParams.At(i) + paramName := param.Obj().Name() + constraint := param.Constraint() + constraintSections := strings.Split(constraint.String(), "/") + constraintName := constraintSections[len(constraintSections)-1] + paramNames = append(paramNames, paramName) + constraintNames = append(constraintNames, constraintName) + paramAndConstraintNames = append(paramAndConstraintNames, fmt.Sprintf("%s %s", paramName, constraintName)) + found = true + } + } + } + } + return +} + func (f *Fake) findPackage() error { var target *types.TypeName var pkg *packages.Package + genericTypeParametersAndConstraints := []string{} + genericTypeConstraints := []string{} + genericTypeParameters := []string{} for i := range f.Packages { if f.Packages[i].Types == nil || f.Packages[i].Types.Scope() == nil { continue @@ -72,6 +97,15 @@ func (f *Fake) findPackage() error { raw := pkg.Types.Scope().Lookup(f.TargetName) if raw != nil { if typeName, ok := raw.(*types.TypeName); ok { + if paramNames, constraintNames, paramAndConstraintNames, found := f.getGenericTypeData(typeName); found { + genericTypeParameters = append(genericTypeParameters, paramNames...) + genericTypeConstraints = append(genericTypeConstraints, constraintNames...) + genericTypeParametersAndConstraints = append( + genericTypeParametersAndConstraints, + paramAndConstraintNames..., + ) + } + target = typeName break } @@ -89,6 +123,11 @@ func (f *Fake) findPackage() error { f.Target = target f.Package = pkg f.TargetPackage = imports.VendorlessPath(pkg.PkgPath) + if len(genericTypeParameters) > 0 { + f.GenericTypeParametersAndConstraints = fmt.Sprintf("[%s]", strings.Join(genericTypeParametersAndConstraints, ", ")) + f.GenericTypeParameters = fmt.Sprintf("[%s]", strings.Join(genericTypeParameters, ", ")) + f.GenericTypeConstraints = fmt.Sprintf("[%s]", strings.Join(genericTypeConstraints, ", ")) + } t := f.Imports.Add(pkg.Name, f.TargetPackage) f.TargetAlias = t.Alias if f.Mode != Package { @@ -97,7 +136,11 @@ func (f *Fake) findPackage() error { if f.Mode == InterfaceOrFunction { if !f.IsInterface() && !f.IsFunction() { - return fmt.Errorf("cannot generate an fake for %s because it is not an interface or function", f.TargetName) + return fmt.Errorf("cannot generate a fake for %s because it is not an interface or function", f.TargetName) + } + + if f.IsConstraintInterface() { + return fmt.Errorf("cannot generate a fake for %s because it is a constraint interface (contains type constraints like ~string) which cannot be implemented by concrete types", f.TargetName) } } @@ -130,14 +173,10 @@ func (f *Fake) addImportsFor(typ types.Type) { f.addImportsFor(t.Elem()) case *types.Chan: f.addImportsFor(t.Elem()) + case *types.Alias: + f.addImportsForNamedType(t) case *types.Named: - if t.Obj() != nil && t.Obj().Pkg() != nil { - typeArgs := t.TypeArgs() - for i := 0; i < typeArgs.Len(); i++ { - f.addImportsFor(typeArgs.At(i)) - } - f.Imports.Add(t.Obj().Pkg().Name(), t.Obj().Pkg().Path()) - } + f.addImportsForNamedType(t) case *types.Slice: f.addImportsFor(t.Elem()) case *types.Array: @@ -154,3 +193,16 @@ func (f *Fake) addImportsFor(typ types.Type) { log.Printf("!!! WARNING: Missing case for type %s\n", reflect.TypeOf(typ).String()) } } + +func (f *Fake) addImportsForNamedType(t interface { + Obj() *types.TypeName + TypeArgs() *types.TypeList +}) { + if t.Obj() != nil && t.Obj().Pkg() != nil { + typeArgs := t.TypeArgs() + for i := 0; i < typeArgs.Len(); i++ { + f.addImportsFor(typeArgs.At(i)) + } + f.Imports.Add(t.Obj().Pkg().Name(), t.Obj().Pkg().Path()) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f5b37e298..d205fa6fb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1003,8 +1003,8 @@ github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.16 ## explicit; go 1.9 github.com/mattn/go-runewidth -# github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 -## explicit; go 1.20 +# github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 +## explicit; go 1.24.0 github.com/maxbrunsfeld/counterfeiter/v6 github.com/maxbrunsfeld/counterfeiter/v6/arguments github.com/maxbrunsfeld/counterfeiter/v6/command