diff --git a/Makefile b/Makefile index 8ceac5adb3..0c5c3742c0 100644 --- a/Makefile +++ b/Makefile @@ -101,8 +101,8 @@ endif REPO?=tigera/operator PACKAGE_NAME?=github.com/tigera/operator LOCAL_USER_ID?=$(shell id -u $$USER) -GO_BUILD_VER?=1.25.7-llvm18.1.8-k8s1.34.3-1 -CALICO_BASE_VER ?= ubi9-1770969585 +GO_BUILD_VER?=1.25.7-llvm18.1.8-k8s1.34.4 +CALICO_BASE_VER ?= ubi9-1771532994 CALICO_BUILD?=calico/go-build:$(GO_BUILD_VER)-$(BUILDARCH) CALICO_BASE ?= calico/base:$(CALICO_BASE_VER) SRC_FILES=$(shell find ./pkg -name '*.go') diff --git a/api/go.mod b/api/go.mod index 7a14c25a2a..16df0fe948 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,26 +1,26 @@ module github.com/tigera/operator/api -go 1.25.5 +go 1.25.7 require ( github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 - github.com/tigera/api v0.0.0-20251017180206-9d7c2da4f711 - k8s.io/api v0.34.3 - k8s.io/apimachinery v0.34.3 + github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34 + k8s.io/api v0.34.4 + k8s.io/apimachinery v0.34.4 sigs.k8s.io/controller-runtime v0.20.2 ) require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect diff --git a/api/go.sum b/api/go.sum index 0f0cb42b02..76127da3e8 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,13 +1,13 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -15,8 +15,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -31,14 +31,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE= @@ -51,43 +47,49 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tigera/api v0.0.0-20251017180206-9d7c2da4f711 h1:A75XdvxO3SlR5qydLSf+CovlwkRCONGcMhZD0l58kNM= -github.com/tigera/api v0.0.0-20251017180206-9d7c2da4f711/go.mod h1:5vkALOm1TWUzg3ElTWnTE3O6wkNB3F8cTkZtio7eFGw= +github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34 h1:bFz27L2XauCys8mBwGQUXzYL8fS4oC5/ModvfujYKaQ= +github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34/go.mod h1:CX/80DoAQD88kMvkvujV4h3XPmIBo0yeSf9OZmvZY0c= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -97,14 +99,12 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/api v0.34.4 h1:Z5hsoQcZ2yBjelb9j5JKzCVo9qv9XLkVm5llnqS4h+0= +k8s.io/api v0.34.4/go.mod h1:6SaGYuGPkMqqCgg8rPG/OQoCrhgSEV+wWn9v21fDP3o= +k8s.io/apimachinery v0.34.4 h1:C5SiSzLEMyWIk53sSbnk0WlOOyqv/MFnWvuc/d6M+xc= +k8s.io/apimachinery v0.34.4/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= diff --git a/go.mod b/go.mod index 38bc3dd40f..33d5c7dc4a 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sirupsen/logrus v1.9.4 github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d github.com/stretchr/testify v1.11.1 - github.com/tigera/api v0.0.0-20260131232301-9cd5ef33622f + github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34 github.com/tigera/operator/api v0.0.0-20260120220012-4a3f8a7d8399 github.com/urfave/cli/v3 v3.6.2 go.uber.org/zap v1.27.1 @@ -39,12 +39,12 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.19.5 - k8s.io/api v0.34.3 - k8s.io/apiextensions-apiserver v0.34.3 - k8s.io/apimachinery v0.34.3 - k8s.io/apiserver v0.34.3 - k8s.io/client-go v0.34.3 - k8s.io/kube-aggregator v0.34.3 + k8s.io/api v0.34.4 + k8s.io/apiextensions-apiserver v0.34.4 + k8s.io/apimachinery v0.34.4 + k8s.io/apiserver v0.34.4 + k8s.io/client-go v0.34.4 + k8s.io/kube-aggregator v0.34.4 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/controller-runtime v0.22.5 sigs.k8s.io/gateway-api v1.3.1-0.20250527223622-54df0a899c1c @@ -183,11 +183,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect howett.net/plist v1.0.1 // indirect - k8s.io/cli-runtime v0.34.3 // indirect - k8s.io/component-base v0.34.3 // indirect + k8s.io/cli-runtime v0.34.4 // indirect + k8s.io/component-base v0.34.4 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect - k8s.io/kubectl v0.34.3 // indirect + k8s.io/kubectl v0.34.4 // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect diff --git a/go.sum b/go.sum index 7394d746db..29fb59f491 100644 --- a/go.sum +++ b/go.sum @@ -313,12 +313,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= @@ -410,8 +406,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tigera/api v0.0.0-20260131232301-9cd5ef33622f h1:kvsv9htGZBL8L4j+SRjy4iwCM4iVcEEo9RuMQ+o1CgA= -github.com/tigera/api v0.0.0-20260131232301-9cd5ef33622f/go.mod h1:bxcXRBfeRHbx80vBXAg8ivbN/y6seqMm2MPhgbWoShc= +github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34 h1:bFz27L2XauCys8mBwGQUXzYL8fS4oC5/ModvfujYKaQ= +github.com/tigera/api v0.0.0-20260227222130-df0b9e289a34/go.mod h1:CX/80DoAQD88kMvkvujV4h3XPmIBo0yeSf9OZmvZY0c= github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8= github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= @@ -577,8 +573,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -593,28 +587,28 @@ helm.sh/helm/v3 v3.19.5 h1:l8zDGBhPaF2z5pTR5ASku/yZwi0qZrWthWMzvf1ZruE= helm.sh/helm/v3 v3.19.5/go.mod h1:PC1rk7PqacpkV4acUFMLStOOis7QM9Jq3DveHBInu4s= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= -k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= -k8s.io/cli-runtime v0.34.3 h1:YRyMhiwX0dT9lmG0AtZDaeG33Nkxgt9OlCTZhRXj9SI= -k8s.io/cli-runtime v0.34.3/go.mod h1:GVwL1L5uaGEgM7eGeKjaTG2j3u134JgG4dAI6jQKhMc= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= -k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= -k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= +k8s.io/api v0.34.4 h1:Z5hsoQcZ2yBjelb9j5JKzCVo9qv9XLkVm5llnqS4h+0= +k8s.io/api v0.34.4/go.mod h1:6SaGYuGPkMqqCgg8rPG/OQoCrhgSEV+wWn9v21fDP3o= +k8s.io/apiextensions-apiserver v0.34.4 h1:TAh2mEduc27sR7lfEthOL2oNeQuux9pQCEJCVC9Gxrs= +k8s.io/apiextensions-apiserver v0.34.4/go.mod h1:13rZ7iu/F4APVV0I0StgBmhvWBGgjGTDaqi21G0113E= +k8s.io/apimachinery v0.34.4 h1:C5SiSzLEMyWIk53sSbnk0WlOOyqv/MFnWvuc/d6M+xc= +k8s.io/apimachinery v0.34.4/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.4 h1:QmMakuCjlFBJpsXKIUom8OUE7+PhZk7hyNiLqlyDH58= +k8s.io/apiserver v0.34.4/go.mod h1:4dM2Pfd+VQQA/4pLVPorZJbIadaTLcvgQn2GYYcA6Ic= +k8s.io/cli-runtime v0.34.4 h1:QdGWDtJENTskib2Ab304Xwklv+lk4mxz+fd2ng36lZY= +k8s.io/cli-runtime v0.34.4/go.mod h1:PED/aZzYDUv6nPRGYXCFUnNOVBWlUDlVITu0Q3djDus= +k8s.io/client-go v0.34.4 h1:IXhvzFdm0e897kXtLbeyMpAGzontcShJ/gi/XCCsOLc= +k8s.io/client-go v0.34.4/go.mod h1:tXIVJTQabT5QRGlFdxZQFxrIhcGUPpKL5DAc4gSWTE8= +k8s.io/component-base v0.34.4 h1:jP4XqR48YelfXIlRpOHQgms5GebU23zSE6xcvTwpXDE= +k8s.io/component-base v0.34.4/go.mod h1:uujRfLNOwNiFWz47eBjNZEj/Swn2cdhqI7lW2MeFdrU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-aggregator v0.34.3 h1:rKsZWTD2As4dKuv+zzdJU0uo5H7bFlAEoSucai4mW6M= -k8s.io/kube-aggregator v0.34.3/go.mod h1:d4D8PV2FK4Qlq6u442FSum1tHPhK9tKdKBfH/A3R0I0= +k8s.io/kube-aggregator v0.34.4 h1:b2Y07+HfImko/ru5BSMsWOSndGJ7+gc5AyZe/rgc+wI= +k8s.io/kube-aggregator v0.34.4/go.mod h1:L2u+9yLYVB5v9+np9EInd63aq5uwN+pnHrlVNaEi8Jc= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.34.3 h1:vpM6//153gh5gvsYHXWHVJ4l4xmN5QFwTSmlfd8icm8= -k8s.io/kubectl v0.34.3/go.mod h1:zZQHtIZoUqTP1bAnPzq/3W1jfc0NeOeunFgcswrfg1c= +k8s.io/kubectl v0.34.4 h1:60NkmD2prPpAJIl81CO6QkQXJ2UlhH5LGIpFxlqK9D8= +k8s.io/kubectl v0.34.4/go.mod h1:Yqa6hDnryvuHFWA/NwJExnSATXMdPeMtOZstdTXeeIM= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= diff --git a/pkg/controller/logcollector/logcollector_controller.go b/pkg/controller/logcollector/logcollector_controller.go index bc89d5a97b..31534b2f14 100644 --- a/pkg/controller/logcollector/logcollector_controller.go +++ b/pkg/controller/logcollector/logcollector_controller.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/tigera/operator/pkg/dns" corev1 "k8s.io/api/core/v1" @@ -438,6 +439,18 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile certificateManager.AddToStatusManager(r.status, render.LogCollectorNamespace) + gracePeriod := utils.ParseGracePeriod(license.Status.GracePeriod) + licenseStatus := utils.GetLicenseStatus(license, gracePeriod) + licenseExpired := licenseStatus == utils.LicenseStatusExpired + + // When in the grace period, schedule a requeue so the controller automatically + // transitions to expired state when the grace period elapses. + var graceRequeueAfter time.Duration + if licenseStatus == utils.LicenseStatusInGracePeriod { + reqLogger.Info("License has expired and is within the grace period. Please renew your license to avoid service disruption.") + graceRequeueAfter = time.Until(license.Status.Expiry.Add(gracePeriod)) + } + exportLogs := utils.IsFeatureActive(license, common.ExportLogsFeature) if !exportLogs && instance.Spec.AdditionalStores != nil { r.status.SetDegraded(operatorv1.ResourceValidationError, "Feature is not active - License does not support feature: export-logs", nil, reqLogger) @@ -595,6 +608,7 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile EKSLogForwarderKeyPair: eksLogForwarderKeyPair, PacketCapture: packetcaptureapi, NonClusterHost: nonclusterhost, + LicenseExpired: licenseExpired, } // Render the fluentd component for Linux comp := render.Fluentd(fluentdCfg) @@ -666,6 +680,7 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile UseSyslogCertificate: useSyslogCertificate, FluentdKeyPair: fluentdKeyPair, EKSLogForwarderKeyPair: eksLogForwarderKeyPair, + LicenseExpired: licenseExpired, } comp = render.Fluentd(fluentdCfg) @@ -683,6 +698,12 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile } } + if licenseExpired { + r.status.SetDegraded(operatorv1.ResourceValidationError, + "License is expired - Log forwarding is stopped. Contact Tigera support or email licensing@tigera.io", nil, reqLogger) + return reconcile.Result{}, nil + } + // Clear the degraded bit if we've reached this far. r.status.ClearDegraded() @@ -697,7 +718,7 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile if err = r.client.Status().Update(ctx, instance); err != nil { return reconcile.Result{}, err } - return reconcile.Result{}, nil + return reconcile.Result{RequeueAfter: graceRequeueAfter}, nil } func getS3Credential(client client.Client) (*render.S3Credential, error) { diff --git a/pkg/controller/logcollector/logcollector_controller_test.go b/pkg/controller/logcollector/logcollector_controller_test.go index 048089b602..47371ca82b 100644 --- a/pkg/controller/logcollector/logcollector_controller_test.go +++ b/pkg/controller/logcollector/logcollector_controller_test.go @@ -76,6 +76,7 @@ var _ = Describe("LogCollector controller tests", func() { mockStatus.On("AddStatefulSets", mock.Anything).Return() mockStatus.On("AddCronJobs", mock.Anything) mockStatus.On("RemoveCertificateSigningRequests", mock.Anything).Return() + mockStatus.On("RemoveDaemonsets", mock.Anything).Return() mockStatus.On("AddCertificateSigningRequests", mock.Anything).Return() mockStatus.On("IsAvailable").Return(true) mockStatus.On("OnCRFound").Return() @@ -844,4 +845,82 @@ var _ = Describe("LogCollector controller tests", func() { Expect(pullSecret.Kind).To(Equal("LogCollector")) }) }) + + Context("License expiry", func() { + It("should set degraded status and delete fluentd DaemonSet when license is expired", func() { + // First reconcile to create fluentd resources. + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Verify the DaemonSet exists. + ds := appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{Kind: "DaemonSet", APIVersion: "apps/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "fluentd-node", + Namespace: render.LogCollectorNamespace, + }, + } + Expect(test.GetResource(c, &ds)).To(BeNil()) + + // Replace the valid license with an expired one. + Expect(c.Delete(ctx, &v3.LicenseKey{ObjectMeta: metav1.ObjectMeta{Name: "default"}})).NotTo(HaveOccurred()) + Expect(c.Create(ctx, &v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default", CreationTimestamp: metav1.Now()}, + Status: v3.LicenseKeyStatus{ + Expiry: metav1.Time{Time: time.Now().Add(-24 * time.Hour)}, + }, + })).NotTo(HaveOccurred()) + + mockStatus.On("SetDegraded", operatorv1.ResourceValidationError, + "License is expired - Log forwarding is stopped. Contact Tigera support or email licensing@tigera.io", mock.Anything, mock.Anything).Return() + + // Reconcile again with expired license. + _, err = r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Verify the DaemonSet has been deleted. + ds = appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{Kind: "DaemonSet", APIVersion: "apps/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "fluentd-node", + Namespace: render.LogCollectorNamespace, + }, + } + Expect(test.GetResource(c, &ds)).NotTo(BeNil()) + }) + + It("should requeue when license is in the grace period", func() { + // First reconcile to create fluentd resources. + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Replace the valid license with one that expired 1 day ago but has a 90-day grace period. + Expect(c.Delete(ctx, &v3.LicenseKey{ObjectMeta: metav1.ObjectMeta{Name: "default"}})).NotTo(HaveOccurred()) + Expect(c.Create(ctx, &v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default", CreationTimestamp: metav1.Now()}, + Status: v3.LicenseKeyStatus{ + Expiry: metav1.Time{Time: time.Now().Add(-24 * time.Hour)}, + GracePeriod: "90d", + Features: []string{"export-logs"}, + }, + })).NotTo(HaveOccurred()) + + result, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Should requeue to re-reconcile when the grace period expires. + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + Expect(result.RequeueAfter).To(BeNumerically("~", 89*24*time.Hour, 1*time.Hour)) + + // DaemonSet should still exist during the grace period. + ds := appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{Kind: "DaemonSet", APIVersion: "apps/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "fluentd-node", + Namespace: render.LogCollectorNamespace, + }, + } + Expect(test.GetResource(c, &ds)).To(BeNil()) + }) + }) }) diff --git a/pkg/controller/monitor/monitor_controller.go b/pkg/controller/monitor/monitor_controller.go index daa58d84ab..93dbc3ea56 100644 --- a/pkg/controller/monitor/monitor_controller.go +++ b/pkg/controller/monitor/monitor_controller.go @@ -19,6 +19,7 @@ import ( _ "embed" "fmt" "reflect" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -65,9 +66,10 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { prometheusReady := &utils.ReadyFlag{} tierWatchReady := &utils.ReadyFlag{} + licenseAPIReady := &utils.ReadyFlag{} // Create the reconciler - reconciler := newReconciler(mgr, opts, prometheusReady, tierWatchReady) + reconciler := newReconciler(mgr, opts, prometheusReady, tierWatchReady, licenseAPIReady) // Create a new controller c, err := ctrlruntime.NewController("monitor-controller", mgr, controller.Options{Reconciler: reconciler}) @@ -91,10 +93,12 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { go waitToAddPrometheusWatch(c, opts.K8sClientset, log, prometheusReady) + go utils.WaitToAddLicenseKeyWatch(c, opts.K8sClientset, log, licenseAPIReady) + return add(mgr, c) } -func newReconciler(mgr manager.Manager, opts options.ControllerOptions, prometheusReady *utils.ReadyFlag, tierWatchReady *utils.ReadyFlag) reconcile.Reconciler { +func newReconciler(mgr manager.Manager, opts options.ControllerOptions, prometheusReady *utils.ReadyFlag, tierWatchReady *utils.ReadyFlag, licenseAPIReady *utils.ReadyFlag) reconcile.Reconciler { r := &ReconcileMonitor{ client: mgr.GetClient(), scheme: mgr.GetScheme(), @@ -102,6 +106,7 @@ func newReconciler(mgr manager.Manager, opts options.ControllerOptions, promethe status: status.New(mgr.GetClient(), "monitor", opts.KubernetesVersion), prometheusReady: prometheusReady, tierWatchReady: tierWatchReady, + licenseAPIReady: licenseAPIReady, clusterDomain: opts.ClusterDomain, multiTenant: opts.MultiTenant, } @@ -184,6 +189,7 @@ type ReconcileMonitor struct { status status.StatusManager prometheusReady *utils.ReadyFlag tierWatchReady *utils.ReadyFlag + licenseAPIReady *utils.ReadyFlag clusterDomain string multiTenant bool } @@ -244,6 +250,33 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ } } + if !r.licenseAPIReady.IsReady() { + r.status.SetDegraded(operatorv1.ResourceNotReady, "Waiting for LicenseKeyAPI to be ready", nil, reqLogger) + return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil + } + + license, err := utils.FetchLicenseKey(ctx, r.client) + if err != nil { + if errors.IsNotFound(err) { + r.status.SetDegraded(operatorv1.ResourceNotFound, "License not found", err, reqLogger) + return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil + } + r.status.SetDegraded(operatorv1.ResourceReadError, "Error querying license", err, reqLogger) + return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil + } + + gracePeriod := utils.ParseGracePeriod(license.Status.GracePeriod) + licenseStatus := utils.GetLicenseStatus(license, gracePeriod) + licenseExpired := licenseStatus == utils.LicenseStatusExpired + + // When in the grace period, schedule a requeue so the controller automatically + // transitions to expired state when the grace period elapses. + var graceRequeueAfter time.Duration + if licenseStatus == utils.LicenseStatusInGracePeriod { + reqLogger.Info("License has expired and is within the grace period. Please renew your license to avoid service disruption.") + graceRequeueAfter = time.Until(license.Status.Expiry.Add(gracePeriod)) + } + variant, install, err := utils.GetInstallation(context.Background(), r.client) if err != nil { if errors.IsNotFound(err) { @@ -388,6 +421,7 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ OpenShift: r.provider.IsOpenShift(), KubeControllerPort: kubeControllersMetricsPort, FelixPrometheusMetricsEnabled: utils.IsFelixPrometheusMetricsEnabled(felixConfiguration), + LicenseExpired: licenseExpired, } // Render prometheus component @@ -431,6 +465,12 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ // Tell the status manager that we're ready to monitor the resources we've told it about and receive statuses. r.status.ReadyToMonitor() + if licenseExpired { + r.status.SetDegraded(operatorv1.ResourceValidationError, + "License is expired - Contact Tigera support or email licensing@tigera.io", nil, reqLogger) + return reconcile.Result{}, nil + } + r.status.ClearDegraded() if !r.status.IsAvailable() { @@ -444,7 +484,7 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ return reconcile.Result{}, err } - return reconcile.Result{}, nil + return reconcile.Result{RequeueAfter: graceRequeueAfter}, nil } func fillDefaults(instance *operatorv1.Monitor) { diff --git a/pkg/controller/monitor/monitor_controller_test.go b/pkg/controller/monitor/monitor_controller_test.go index 2d2992badd..9c8df546c3 100644 --- a/pkg/controller/monitor/monitor_controller_test.go +++ b/pkg/controller/monitor/monitor_controller_test.go @@ -17,6 +17,7 @@ package monitor import ( "bytes" "context" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -96,6 +97,7 @@ var _ = Describe("Monitor controller tests", func() { status: mockStatus, prometheusReady: &utils.ReadyFlag{}, tierWatchReady: &utils.ReadyFlag{}, + licenseAPIReady: &utils.ReadyFlag{}, } // We start off with a 'standard' installation, with nothing special @@ -131,9 +133,18 @@ var _ = Describe("Monitor controller tests", func() { Expect(err).NotTo(HaveOccurred()) Expect(cli.Create(ctx, certificateManager.KeyPair().Secret(common.OperatorNamespace()))).NotTo(HaveOccurred()) + // Create a valid (non-expired) license. + Expect(cli.Create(ctx, &v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default", CreationTimestamp: metav1.Now()}, + Status: v3.LicenseKeyStatus{ + Expiry: metav1.Time{Time: time.Now().Add(24 * time.Hour)}, + }, + })).NotTo(HaveOccurred()) + // Mark that watches were successful. r.prometheusReady.MarkAsReady() r.tierWatchReady.MarkAsReady() + r.licenseAPIReady.MarkAsReady() }) Context("controller reconciliation", func() { @@ -633,4 +644,71 @@ var _ = Describe("Monitor controller tests", func() { Expect(instance.Status.Conditions[2].ObservedGeneration).To(Equal(generation)) }) }) + + Context("License expiry", func() { + It("should set degraded status when license is expired", func() { + // Replace the valid license with an expired one. + Expect(cli.Delete(ctx, &v3.LicenseKey{ObjectMeta: metav1.ObjectMeta{Name: "default"}})).NotTo(HaveOccurred()) + Expect(cli.Create(ctx, &v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default", CreationTimestamp: metav1.Now()}, + Status: v3.LicenseKeyStatus{ + Expiry: metav1.Time{Time: time.Now().Add(-24 * time.Hour)}, + }, + })).NotTo(HaveOccurred()) + + mockStatus.On("SetDegraded", operatorv1.ResourceValidationError, + "License is expired - Contact Tigera support or email licensing@tigera.io", mock.Anything, mock.Anything).Return() + + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Verify that ServiceMonitors are not created when license is expired. + sm := &monitoringv1.ServiceMonitor{} + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.CalicoNodeMonitor, Namespace: common.TigeraPrometheusNamespace}, sm)).To(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.ElasticsearchMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).To(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.FluentdMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).To(HaveOccurred()) + + // Verify that other Prometheus resources are still created. + am := &monitoringv1.Alertmanager{} + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.CalicoNodeAlertmanager, Namespace: common.TigeraPrometheusNamespace}, am)).NotTo(HaveOccurred()) + p := &monitoringv1.Prometheus{} + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.CalicoNodePrometheus, Namespace: common.TigeraPrometheusNamespace}, p)).NotTo(HaveOccurred()) + }) + + It("should not set degraded status when license is valid", func() { + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // ServiceMonitors should be created with a valid license. + sm := &monitoringv1.ServiceMonitor{} + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.CalicoNodeMonitor, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.ElasticsearchMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.FluentdMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + }) + + It("should requeue when license is in the grace period", func() { + // Replace the valid license with one that expired 1 day ago but has a 90-day grace period. + Expect(cli.Delete(ctx, &v3.LicenseKey{ObjectMeta: metav1.ObjectMeta{Name: "default"}})).NotTo(HaveOccurred()) + Expect(cli.Create(ctx, &v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default", CreationTimestamp: metav1.Now()}, + Status: v3.LicenseKeyStatus{ + Expiry: metav1.Time{Time: time.Now().Add(-24 * time.Hour)}, + GracePeriod: "90d", + }, + })).NotTo(HaveOccurred()) + + result, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + // Should requeue to re-reconcile when the grace period expires. + Expect(result.RequeueAfter).To(BeNumerically(">", 0)) + Expect(result.RequeueAfter).To(BeNumerically("~", 89*24*time.Hour, 1*time.Hour)) + + // ServiceMonitors should still be created during the grace period. + sm := &monitoringv1.ServiceMonitor{} + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.CalicoNodeMonitor, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.ElasticsearchMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + Expect(cli.Get(ctx, client.ObjectKey{Name: monitor.FluentdMetrics, Namespace: common.TigeraPrometheusNamespace}, sm)).NotTo(HaveOccurred()) + }) + }) }) diff --git a/pkg/controller/status/status.go b/pkg/controller/status/status.go index 060398b6c1..60a26a57c0 100644 --- a/pkg/controller/status/status.go +++ b/pkg/controller/status/status.go @@ -361,7 +361,11 @@ func (m *statusManager) SetDegraded(reason operator.TigeraStatusReason, msg stri defer m.lock.Unlock() m.degraded = true m.explicitDegradedReason = reason - m.explicitDegradedMsg = fmt.Sprintf("%s: %s", msg, errormsg) + if errormsg != "" { + m.explicitDegradedMsg = fmt.Sprintf("%s: %s", msg, errormsg) + } else { + m.explicitDegradedMsg = msg + } } // ClearDegraded clears degraded state. diff --git a/pkg/controller/status/status_test.go b/pkg/controller/status/status_test.go index f64fb655c1..c503e10508 100644 --- a/pkg/controller/status/status_test.go +++ b/pkg/controller/status/status_test.go @@ -455,7 +455,7 @@ var _ = Describe("Status reporting tests", func() { sm.failing = []string{"This pod has died"} Expect(sm.degradedMessage()).To(Equal("This pod has died")) sm.SetDegraded(operator.ResourceNotFound, "Controller set us degraded", nil, log) - Expect(sm.degradedMessage()).To(Equal("Controller set us degraded: \nThis pod has died")) + Expect(sm.degradedMessage()).To(Equal("Controller set us degraded\nThis pod has died")) }) It("should contain all the NamespacesNames for all the resources added by multiple calls to Set", func() { diff --git a/pkg/controller/utils/license.go b/pkg/controller/utils/license.go new file mode 100644 index 0000000000..6ee9fcec9d --- /dev/null +++ b/pkg/controller/utils/license.go @@ -0,0 +1,107 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "context" + "strconv" + "strings" + "time" + + "github.com/go-logr/logr" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "sigs.k8s.io/controller-runtime/pkg/client" + + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" + + "github.com/tigera/operator/pkg/ctrlruntime" +) + +// LicenseStatus represents the current state of the license with respect to expiry and grace period. +type LicenseStatus int + +const ( + // LicenseStatusValid means the license has not expired. + LicenseStatusValid LicenseStatus = iota + // LicenseStatusInGracePeriod means the license expiry has passed but the grace period has not elapsed. + LicenseStatusInGracePeriod + // LicenseStatusExpired means the license expiry plus the grace period has passed. + LicenseStatusExpired +) + +// WaitToAddLicenseKeyWatch starts a goroutine that waits for the LicenseKey CRD to be available +// and then adds a watch for it on the given controller. +func WaitToAddLicenseKeyWatch(controller ctrlruntime.Controller, c kubernetes.Interface, log logr.Logger, flag *ReadyFlag) { + WaitToAddResourceWatch(controller, c, log, flag, []client.Object{&v3.LicenseKey{TypeMeta: metav1.TypeMeta{Kind: v3.KindLicenseKey}}}) +} + +// FetchLicenseKey returns the license if it has been installed. It's useful +// to prevent rollout of TSEE components that might require it. +// It will return an error if the license is not installed/cannot be read +func FetchLicenseKey(ctx context.Context, cli client.Client) (v3.LicenseKey, error) { + instance := &v3.LicenseKey{} + err := cli.Get(ctx, DefaultInstanceKey, instance) + return *instance, err +} + +// IsFeatureActive return true if the feature is listed in LicenseStatusKey +func IsFeatureActive(license v3.LicenseKey, featureName string) bool { + for _, v := range license.Status.Features { + if v == featureName || v == "all" { + return true + } + } + + return false +} + +// ParseGracePeriod parses a grace period string in the format "Nd" (e.g. "90d") +// where N is a non-negative number of days. This is the format produced by the +// license controller. Returns 0 if the string is empty, cannot be parsed, or +// represents a negative duration. +func ParseGracePeriod(gracePeriod string) time.Duration { + if gracePeriod == "" { + return 0 + } + s, ok := strings.CutSuffix(gracePeriod, "d") + if !ok { + return 0 + } + days, err := strconv.Atoi(s) + if err != nil || days < 0 { + return 0 + } + return time.Duration(days) * 24 * time.Hour +} + +// GetLicenseStatus returns the current license status using a single point-in-time check. +// It uses a single time.Now() call to avoid inconsistencies at state boundaries. +func GetLicenseStatus(license v3.LicenseKey, gracePeriod time.Duration) LicenseStatus { + if license.Status.Expiry.IsZero() { + return LicenseStatusValid + } + now := time.Now() + expiry := license.Status.Expiry.Time + if now.After(expiry.Add(gracePeriod)) { + return LicenseStatusExpired + } + if now.After(expiry) { + return LicenseStatusInGracePeriod + } + return LicenseStatusValid +} diff --git a/pkg/controller/utils/license_test.go b/pkg/controller/utils/license_test.go new file mode 100644 index 0000000000..ae5b4cb3ce --- /dev/null +++ b/pkg/controller/utils/license_test.go @@ -0,0 +1,81 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" +) + +var _ = Describe("License helpers", func() { + var license v3.LicenseKey + + BeforeEach(func() { + license = v3.LicenseKey{ + ObjectMeta: metav1.ObjectMeta{Name: "default"}, + } + }) + + Context("ParseGracePeriod", func() { + DescribeTable("should parse grace period strings", + func(input string, expected time.Duration) { + Expect(ParseGracePeriod(input)).To(Equal(expected)) + }, + Entry("90 days", "90d", 90*24*time.Hour), + Entry("30 days", "30d", 30*24*time.Hour), + Entry("0 days", "0d", time.Duration(0)), + Entry("empty string", "", time.Duration(0)), + Entry("invalid string", "abc", time.Duration(0)), + Entry("negative days", "-5d", time.Duration(0)), + Entry("bare number without unit", "90", time.Duration(0)), + ) + }) + + Context("GetLicenseStatus", func() { + DescribeTable("should return the correct license status", + func(expiryOffset time.Duration, gracePeriod time.Duration, expected LicenseStatus) { + license.Status.Expiry = metav1.Time{Time: time.Now().Add(expiryOffset)} + Expect(GetLicenseStatus(license, gracePeriod)).To(Equal(expected)) + }, + Entry("valid: expiry in the future, no grace period", + 24*time.Hour, time.Duration(0), LicenseStatusValid), + Entry("valid: expiry in the future, with grace period", + 24*time.Hour, 90*24*time.Hour, LicenseStatusValid), + Entry("expired: expiry in the past, no grace period", + -24*time.Hour, time.Duration(0), LicenseStatusExpired), + Entry("in grace period: expiry in past but within grace period", + -24*time.Hour, 90*24*time.Hour, LicenseStatusInGracePeriod), + Entry("expired: expiry in past and beyond grace period", + -100*24*time.Hour, 90*24*time.Hour, LicenseStatusExpired), + ) + + It("should return valid when expiry is zero", func() { + license.Status.Expiry = metav1.Time{} + Expect(GetLicenseStatus(license, 90*24*time.Hour)).To(Equal(LicenseStatusValid)) + }) + + It("should return expired when expiry is exactly at grace period boundary", func() { + // Set expiry far enough in the past that even with clock skew it's clearly past grace. + license.Status.Expiry = metav1.Time{Time: time.Now().Add(-91 * 24 * time.Hour)} + Expect(GetLicenseStatus(license, 90*24*time.Hour)).To(Equal(LicenseStatusExpired)) + }) + }) +}) diff --git a/pkg/controller/utils/utils.go b/pkg/controller/utils/utils.go index 057d4fbfe4..cc5b83de94 100644 --- a/pkg/controller/utils/utils.go +++ b/pkg/controller/utils/utils.go @@ -257,10 +257,6 @@ func createPeriodicReconcileChannel(period time.Duration) chan event.GenericEven return periodicReconcileEvents } -func WaitToAddLicenseKeyWatch(controller ctrlruntime.Controller, c kubernetes.Interface, log logr.Logger, flag *ReadyFlag) { - WaitToAddResourceWatch(controller, c, log, flag, []client.Object{&v3.LicenseKey{TypeMeta: metav1.TypeMeta{Kind: v3.KindLicenseKey}}}) -} - func WaitToAddClusterInformationWatch(controller ctrlruntime.Controller, c kubernetes.Interface, log logr.Logger, flag *ReadyFlag) { WaitToAddResourceWatch(controller, c, log, flag, []client.Object{&v3.ClusterInformation{TypeMeta: metav1.TypeMeta{Kind: v3.KindClusterInformation}}}) } @@ -358,15 +354,6 @@ func GetLogCollector(ctx context.Context, cli client.Client) (*operatorv1.LogCol return logCollector, nil } -// FetchLicenseKey returns the license if it has been installed. It's useful -// to prevent rollout of TSEE components that might require it. -// It will return an error if the license is not installed/cannot be read -func FetchLicenseKey(ctx context.Context, cli client.Client) (v3.LicenseKey, error) { - instance := &v3.LicenseKey{} - err := cli.Get(ctx, DefaultInstanceKey, instance) - return *instance, err -} - // FetchClusterInformation fetches and returns the clusterinformation. func FetchClusterInformation(ctx context.Context, cli client.Client) (v3.ClusterInformation, error) { instance := &v3.ClusterInformation{} @@ -374,17 +361,6 @@ func FetchClusterInformation(ctx context.Context, cli client.Client) (v3.Cluster return *instance, err } -// IsFeatureActive return true if the feature is listed in LicenseStatusKey -func IsFeatureActive(license v3.LicenseKey, featureName string) bool { - for _, v := range license.Status.Features { - if v == featureName || v == "all" { - return true - } - } - - return false -} - // ValidateCertPair checks if the given secret exists in the given // namespace and if so that it contains key and cert fields. If an // empty string is passed for the keyName argument it is skipped. diff --git a/pkg/render/fluentd.go b/pkg/render/fluentd.go index ee176529a4..4352b88303 100644 --- a/pkg/render/fluentd.go +++ b/pkg/render/fluentd.go @@ -185,6 +185,9 @@ type FluentdConfiguration struct { PacketCapture *operatorv1.PacketCaptureAPI NonClusterHost *operatorv1.NonClusterHost + + // LicenseExpired indicates the license has expired and fluentd DaemonSet should be removed. + LicenseExpired bool } type fluentdComponent struct { @@ -332,7 +335,11 @@ func (c *fluentdComponent) Objects() ([]client.Object, []client.Object) { objs = append(objs, c.packetCaptureApiRole(), c.packetCaptureApiRoleBinding()) } - objs = append(objs, c.daemonset()) + if c.cfg.LicenseExpired { + toDelete = append(toDelete, c.daemonset()) + } else { + objs = append(objs, c.daemonset()) + } if c.cfg.NonClusterHost != nil && c.cfg.OSType == rmeta.OSTypeLinux { objs = append(objs, c.nonClusterHostInputService()) diff --git a/pkg/render/fluentd_test.go b/pkg/render/fluentd_test.go index 6a6ce1e52e..a4627156fd 100644 --- a/pkg/render/fluentd_test.go +++ b/pkg/render/fluentd_test.go @@ -1316,6 +1316,46 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() { })) }) }) + + It("should move DaemonSet to toDelete when LicenseExpired is true", func() { + cfg.LicenseExpired = true + component := render.Fluentd(cfg) + Expect(component.ResolveImages(nil)).To(BeNil()) + toCreate, toDelete := component.Objects() + + // DaemonSet should not be in toCreate. + for _, obj := range toCreate { + if ds, ok := obj.(*appsv1.DaemonSet); ok { + Fail("DaemonSet should not be in toCreate when license is expired, but found: " + ds.Name) + } + } + + // DaemonSet should be in toDelete. + found := false + for _, obj := range toDelete { + if ds, ok := obj.(*appsv1.DaemonSet); ok && ds.Name == "fluentd-node" { + found = true + break + } + } + Expect(found).To(BeTrue(), "Expected fluentd-node DaemonSet to be in toDelete") + }) + + It("should include DaemonSet in toCreate when LicenseExpired is false", func() { + cfg.LicenseExpired = false + component := render.Fluentd(cfg) + Expect(component.ResolveImages(nil)).To(BeNil()) + toCreate, _ := component.Objects() + + found := false + for _, obj := range toCreate { + if ds, ok := obj.(*appsv1.DaemonSet); ok && ds.Name == "fluentd-node" { + found = true + break + } + } + Expect(found).To(BeTrue(), "Expected fluentd-node DaemonSet to be in toCreate") + }) }) func setupEKSCloudwatchLogConfig() *render.EksCloudwatchLogConfig { diff --git a/pkg/render/monitor/monitor.go b/pkg/render/monitor/monitor.go index 44836e07d7..b01d02a510 100644 --- a/pkg/render/monitor/monitor.go +++ b/pkg/render/monitor/monitor.go @@ -145,6 +145,7 @@ type Config struct { OpenShift bool KubeControllerPort int FelixPrometheusMetricsEnabled bool + LicenseExpired bool } type monitorComponent struct { @@ -230,12 +231,22 @@ func (mc *monitorComponent) Objects() ([]client.Object, []client.Object) { mc.prometheusServiceClusterRole(), mc.prometheusServiceClusterRoleBinding(), mc.prometheusRule(), + ) + + var toDelete []client.Object + + serviceMonitors := []client.Object{ mc.serviceMonitorCalicoNode(), mc.serviceMonitorElasticsearch(), mc.serviceMonitorFluentd(), mc.serviceMonitorQueryServer(), mc.serviceMonitorCalicoKubeControllers(), - ) + } + if mc.cfg.LicenseExpired { + toDelete = append(toDelete, serviceMonitors...) + } else { + toCreate = append(toCreate, serviceMonitors...) + } if mc.cfg.KeyValidatorConfig != nil { toCreate = append(toCreate, secret.ToRuntimeObjects(mc.cfg.KeyValidatorConfig.RequiredSecrets(common.TigeraPrometheusNamespace)...)...) @@ -254,7 +265,6 @@ func (mc *monitorComponent) Objects() ([]client.Object, []client.Object) { } } - var toDelete []client.Object if mc.cfg.Installation.TyphaMetricsPort != nil { toCreate = append(toCreate, mc.typhaServiceMonitor()) } else { diff --git a/pkg/render/monitor/monitor_test.go b/pkg/render/monitor/monitor_test.go index b09c68aa2a..69f76bf78f 100644 --- a/pkg/render/monitor/monitor_test.go +++ b/pkg/render/monitor/monitor_test.go @@ -981,6 +981,64 @@ var _ = Describe("monitor rendering tests", func() { Expect(servicemonitorObj.Spec.Endpoints[2].ScrapeTimeout).To(BeEquivalentTo("5s")) Expect(*servicemonitorObj.Spec.Endpoints[2].RelabelConfigs[0].Replacement).To(Equal("http")) }) + + It("Should move ServiceMonitors to toDelete when LicenseExpired is true", func() { + cfg.LicenseExpired = true + component := monitor.Monitor(cfg) + Expect(component.ResolveImages(nil)).NotTo(HaveOccurred()) + toCreate, toDelete := component.Objects() + + // ServiceMonitors should not be in toCreate. + for _, obj := range toCreate { + if _, ok := obj.(*monitoringv1.ServiceMonitor); ok { + Fail("ServiceMonitor should not be in toCreate when license is expired, but found: " + obj.GetName()) + } + } + + // ServiceMonitors should be in toDelete. + serviceMonitorNames := []string{ + monitor.CalicoNodeMonitor, + monitor.ElasticsearchMetrics, + monitor.FluentdMetrics, + "calico-api", + "calico-kube-controllers-metrics", + } + for _, name := range serviceMonitorNames { + found := false + for _, obj := range toDelete { + if sm, ok := obj.(*monitoringv1.ServiceMonitor); ok && sm.Name == name { + found = true + break + } + } + Expect(found).To(BeTrue(), "Expected ServiceMonitor %s to be in toDelete", name) + } + }) + + It("Should include ServiceMonitors in toCreate when LicenseExpired is false", func() { + cfg.LicenseExpired = false + component := monitor.Monitor(cfg) + Expect(component.ResolveImages(nil)).NotTo(HaveOccurred()) + toCreate, _ := component.Objects() + + serviceMonitorNames := []string{ + monitor.CalicoNodeMonitor, + monitor.ElasticsearchMetrics, + monitor.FluentdMetrics, + "calico-api", + "calico-kube-controllers-metrics", + } + for _, name := range serviceMonitorNames { + found := false + for _, obj := range toCreate { + if sm, ok := obj.(*monitoringv1.ServiceMonitor); ok && sm.Name == name { + found = true + break + } + } + Expect(found).To(BeTrue(), "Expected ServiceMonitor %s to be in toCreate", name) + } + }) }) // expectedBaseResources These are the expected resources in the most basic setup.