From c2b1c3daf9e7c7130821da46a3953dd0b60d71f7 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 17:19:02 +0200 Subject: [PATCH 01/16] Capability to create Kubernetes client with k8s.io/client-go --- internal/k8s/client.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 internal/k8s/client.go diff --git a/internal/k8s/client.go b/internal/k8s/client.go new file mode 100644 index 00000000..ba9a3956 --- /dev/null +++ b/internal/k8s/client.go @@ -0,0 +1,27 @@ +package k8s + +import ( + "fmt" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +func NewClient(kubeContext string) (kubernetes.Interface, error) { + clientConfigLoader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &clientcmd.ConfigOverrides{CurrentContext: kubeContext}, + ) + + restConfig, err := clientConfigLoader.ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to build kubernetes client config: %w", err) + } + + client, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, fmt.Errorf("failed to create kubernetes client: %w", err) + } + + return client, nil +} From b60fc9f227e0257f6c769a6bfd0de2e6bd96d52d Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 15:46:27 +0200 Subject: [PATCH 02/16] go.mod & go.sum --- go.mod | 40 +++++++++++++++++++------- go.sum | 88 ++++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 101 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 03e0c77a..59cbd5c1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stackrox/roxie -go 1.25.6 +go 1.26.0 require ( dario.cat/mergo v1.0.2 @@ -9,16 +9,36 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - golang.org/x/term v0.38.0 + golang.org/x/term v0.39.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.35.3 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 +) + +require ( + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + k8s.io/api v0.36.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/cli v29.4.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -33,20 +53,20 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/vbatts/tar-split v0.12.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.35.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gotest.tools/v3 v3.5.2 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) diff --git a/go.sum b/go.sum index 9c9eb73a..3f3dd31b 100644 --- a/go.sum +++ b/go.sum @@ -5,30 +5,57 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM= github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 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/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM= github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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= @@ -42,12 +69,17 @@ 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= @@ -57,7 +89,14 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= @@ -66,40 +105,55 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +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/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +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/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= -k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From a1a291c2a381801e20645ffcf40f4e9f6a20ac57 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 17:46:51 +0200 Subject: [PATCH 03/16] Implementation of pull secret injection --- internal/deployer/openshift.go | 121 +++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 internal/deployer/openshift.go diff --git a/internal/deployer/openshift.go b/internal/deployer/openshift.go new file mode 100644 index 00000000..fb1a7d2a --- /dev/null +++ b/internal/deployer/openshift.go @@ -0,0 +1,121 @@ +package deployer + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + "github.com/stackrox/roxie/internal/dockerauth" + v1 "k8s.io/api/core/v1" + k8sapierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/retry" +) + +const ( + openshiftConfigNamespace = "openshift-config" + openshiftGlobalPullSecretName = "pull-secret" + dockerConfigJsonKey = ".dockerconfigjson" + registryForDownstreamImages = "quay.io/rhacs-eng" +) + +var ( + namespacedGlobalPullSecret = openshiftConfigNamespace + "/" + openshiftGlobalPullSecretName +) + +// dockerConfigJSON represents the structure of a .dockerconfigjson secret value. +type dockerConfigJSON struct { + Auths map[string]dockerauth.AuthEntry `json:"auths"` +} + +// InjectGlobalOpenShiftPullSecret adds registry credentials to the OpenShift global pull secret. +func (d *Deployer) InjectGlobalOpenShiftPullSecret(ctx context.Context) error { + return retry.OnError(retry.DefaultRetry, func(err error) bool { + return k8sapierrors.IsConflict(err) || k8sapierrors.IsAlreadyExists(err) || k8sapierrors.IsNotFound(err) + }, func() error { + return d.injectGlobalOpenShiftPullSecretOnce(ctx) + }) +} + +func (d *Deployer) injectGlobalOpenShiftPullSecretOnce(ctx context.Context) error { + if d.dockerCreds == nil { + return errors.New("no pull secrets found") + } + credentials := *d.dockerCreds + + if d.k8sClient == nil { + return errors.New("k8s client not initialized") + } + + secrets := d.k8sClient.CoreV1().Secrets(openshiftConfigNamespace) + secret, err := secrets.Get(ctx, openshiftGlobalPullSecretName, metav1.GetOptions{}) + if err != nil { + if !k8sapierrors.IsNotFound(err) { + return fmt.Errorf("retrieving secret %s: %w", namespacedGlobalPullSecret, err) + } + secret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: openshiftGlobalPullSecretName, + Namespace: openshiftConfigNamespace, + }, + Type: v1.SecretTypeDockerConfigJson, + } + } + + modified, err := injectRegistryCredentialsIntoSecret(credentials, secret) + if err != nil { + return fmt.Errorf("injecting registry credentials into Kubernetes secret: %w", err) + } + if !modified { + d.logger.Dimf("Global pull secret %s already contains entry for %s, skipping", namespacedGlobalPullSecret, registryForDownstreamImages) + return nil + } + + if secret.ResourceVersion == "" { + if _, err := secrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("creating secret %s: %w", namespacedGlobalPullSecret, err) + } + } else { + if _, err := secrets.Update(ctx, secret, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("updating secret %s: %w", namespacedGlobalPullSecret, err) + } + } + + d.logger.Successf("Injected pull secret for %s into %s", registryForDownstreamImages, namespacedGlobalPullSecret) + return nil +} + +// injectRegistryCredentialsIntoSecret mutates the secret in place, returning true if it was modified. +func injectRegistryCredentialsIntoSecret(credentials dockerauth.Credentials, secret *v1.Secret) (bool, error) { + var cfg dockerConfigJSON + if data, ok := secret.Data[dockerConfigJsonKey]; ok { + if err := json.Unmarshal(data, &cfg); err != nil { + return false, fmt.Errorf("unmarshaling %q in %s: %w", dockerConfigJsonKey, namespacedGlobalPullSecret, err) + } + } + if cfg.Auths == nil { + cfg.Auths = make(map[string]dockerauth.AuthEntry) + } + + if _, ok := cfg.Auths[registryForDownstreamImages]; ok { + return false, nil + } + + cfg.Auths[registryForDownstreamImages] = dockerauth.AuthEntry{ + Auth: base64.StdEncoding.EncodeToString([]byte(credentials.Username + ":" + credentials.Password)), + } + + updated, err := json.Marshal(cfg) + if err != nil { + return false, fmt.Errorf("marshaling updated docker config: %w", err) + } + + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + secret.Data[dockerConfigJsonKey] = updated + + return true, nil +} From d7986d884087045c3e0317d6c6d4b4989e2575a0 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 17:23:30 +0200 Subject: [PATCH 04/16] Invoke pull secret patching before deploying --- internal/deployer/deployer.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 2c602e21..de631ba4 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -336,6 +336,17 @@ func (d *Deployer) Deploy(ctx context.Context, components component.Component) e d.logger.Infof("Initiating deployment of %s", components) + if d.config.Roxie.KonfluxImages && env.GetCurrentClusterType() == types.ClusterTypeOpenShift4 { + // For deploying Konflux-built images, we need to configure image-rewriting on the cluster at the CRI-O level. + // But due to https://access.redhat.com/solutions/6540591 the standard pull-secret mechanism won't for the + // target image references. A workaround is to inject the pull secrets we need into OpenShift's global + // pull secrets. + // Infra OpenShift4 clusters already come equipped with this global pull secret. + if err := d.InjectGlobalOpenShiftPullSecret(ctx); err != nil { + return fmt.Errorf("injecting global OpenShift pull-secret for Konflux images: %w", err) + } + } + // If only deploying operator, use the operator-only flow. if components.IncludesOperatorExplicitly() { return d.deployOperatorOnly(ctx) From c671e5536866e61fd77bf7d769c07be775645ac6 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 17:25:26 +0200 Subject: [PATCH 05/16] Remove unused code --- internal/deployer/deployer.go | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index de631ba4..cfef6894 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -45,8 +45,7 @@ type Deployer struct { dockerCreds *dockerauth.Credentials envrcFile string - kubeContext string - clusterResourceKinds map[string]struct{} + kubeContext string config Config @@ -265,11 +264,6 @@ func New(log *logger.Logger) (*Deployer, error) { d.kubeContext = env.GetCurrentContext() if d.kubeContext != "" { - clusterResourceKinds, err := d.getClusterResourceKinds() - if err != nil { - return nil, fmt.Errorf("failed to get cluster resource kinds: %w", err) - } - d.clusterResourceKinds = clusterResourceKinds } log.Success("🚀 ACS Deployer initialized") @@ -277,27 +271,6 @@ func New(log *logger.Logger) (*Deployer, error) { return d, nil } -func (d *Deployer) getClusterResourceKinds() (map[string]struct{}, error) { - result, err := d.runKubectl(context.Background(), k8s.KubectlOptions{ - Args: []string{"api-resources", "-o", "name"}, - }) - if err != nil { - return nil, fmt.Errorf("failed to get cluster resource kinds: %w", err) - } - kinds := make(map[string]struct{}) - lines := strings.Split(strings.TrimSpace(result.Stdout), "\n") - for _, line := range lines { - fields := strings.SplitN(line, ".", 2) - if len(fields) == 0 || fields[0] == "" { - continue - } - kind := fields[0] - kinds[kind] = struct{}{} - } - - return kinds, nil -} - // Cleanup cleans up any temporary resources created by the deployer, such as temporary files. func (d *Deployer) Cleanup() { if d.tempDir != "" && d.envrcFile == "" { From 1d1fd04406817dbc08262512f452b659733f38a1 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 17:26:09 +0200 Subject: [PATCH 06/16] Create kubernetes client as part of the Deployer --- internal/deployer/deployer.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index cfef6894..f541b20d 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -13,6 +13,7 @@ import ( "time" "github.com/fatih/color" + "k8s.io/client-go/kubernetes" "github.com/stackrox/roxie/internal/component" "github.com/stackrox/roxie/internal/dockerauth" @@ -46,6 +47,7 @@ type Deployer struct { envrcFile string kubeContext string + k8sClient kubernetes.Interface config Config @@ -264,6 +266,12 @@ func New(log *logger.Logger) (*Deployer, error) { d.kubeContext = env.GetCurrentContext() if d.kubeContext != "" { + client, err := k8s.NewClient(d.kubeContext) + if err != nil { + return nil, fmt.Errorf("creating new Kubernetes client: %w", err) + } + d.k8sClient = client + } log.Success("🚀 ACS Deployer initialized") From 295f0178d3a72421a2bf7f210d10be6062895512 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 18:15:54 +0200 Subject: [PATCH 07/16] Tests --- internal/deployer/openshift_test.go | 118 ++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 internal/deployer/openshift_test.go diff --git a/internal/deployer/openshift_test.go b/internal/deployer/openshift_test.go new file mode 100644 index 00000000..f557ed8c --- /dev/null +++ b/internal/deployer/openshift_test.go @@ -0,0 +1,118 @@ +package deployer + +import ( + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stackrox/roxie/internal/dockerauth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" +) + +func TestInjectRegistryCredentialsIntoSecret(t *testing.T) { + const ( + registryUsername = "user" + registryPassword = "pass" + ) + + makeSecret := func(credentials map[string]map[string]string) *v1.Secret { + data, err := json.Marshal(map[string]any{ + "auths": credentials, + }) + require.NoError(t, err) + return &v1.Secret{Data: map[string][]byte{dockerConfigJsonKey: data}} + } + + encodeCredentials := func(username, password string) string { + return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + } + + tests := []struct { + name string + secret *v1.Secret + expectModified bool + expectError bool + expectCredentials map[string]map[string]string + }{ + { + name: "injects into empty auths", + secret: makeSecret(nil), + expectModified: true, + expectCredentials: map[string]map[string]string{ + registryForDownstreamImages: { + "auth": encodeCredentials(registryUsername, registryPassword), + }, + }, + }, + { + name: "preserves existing entries", + secret: makeSecret(map[string]map[string]string{ + "registry.example.com": { + "auth": encodeCredentials("other", "secret"), + }, + }), + expectModified: true, + expectCredentials: map[string]map[string]string{ + "registry.example.com": { + "auth": encodeCredentials("other", "secret"), + }, + registryForDownstreamImages: { + "auth": encodeCredentials(registryUsername, registryPassword), + }, + }, + }, + { + name: "skips if already present", + secret: makeSecret(map[string]map[string]string{ + registryForDownstreamImages: { + "auth": encodeCredentials("existing", "existing"), + }, + }), + expectModified: false, + expectCredentials: map[string]map[string]string{ + registryForDownstreamImages: { + "auth": encodeCredentials("existing", "existing"), + }, + }, + }, + { + name: "handles nil secret data", + secret: &v1.Secret{}, + expectModified: true, + expectCredentials: map[string]map[string]string{ + registryForDownstreamImages: { + "auth": encodeCredentials(registryUsername, registryPassword), + }, + }, + }, + { + name: "returns error on invalid JSON", + secret: &v1.Secret{Data: map[string][]byte{dockerConfigJsonKey: []byte("not json")}}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creds := dockerauth.Credentials{Username: registryUsername, Password: registryPassword} + modified, err := injectRegistryCredentialsIntoSecret(creds, tt.secret) + if tt.expectError { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expectModified, modified) + + var cfg dockerConfigJSON + require.NoError(t, json.Unmarshal(tt.secret.Data[dockerConfigJsonKey], &cfg)) + + assert.Equal(t, len(tt.expectCredentials), len(cfg.Auths), "credential length mismatch") + + for regName, regCredentials := range tt.expectCredentials { + assert.Equal(t, regCredentials["auth"], cfg.Auths[regName].Auth, "credentials mismatch for registry %d", regName) + } + }) + } +} From 0dd7e4d0f48cb3dc1287c7f8dc7450ae9be8b6bd Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 21:06:25 +0200 Subject: [PATCH 08/16] Make cluster type overwritable, e.g. for testing purposes --- cmd/deploy.go | 17 +++++--- internal/clusterdefaults/clusterdefaults.go | 6 +-- .../clusterdefaults/clusterdefaults_test.go | 3 +- internal/deployer/config.go | 7 +-- internal/deployer/deploy_via_operator.go | 4 +- internal/deployer/deployer.go | 8 ++-- internal/deployer/operator.go | 7 ++- internal/types/cluster_type.go | 43 +++++++++++++++++++ 8 files changed, 72 insertions(+), 23 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index 87d7caaf..dc5c7c20 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -347,11 +347,15 @@ func runDeploy(cmd *cobra.Command, args []string) error { } func configureConfig(log *logger.Logger, components component.Component, deploySettings *deployer.Config) error { - clusterType := env.GetCurrentClusterType() - log.Dimf("Detected cluster type: %v", clusterType) - defaults, err := clusterdefaults.ApplyClusterDefaults(clusterType, deploySettings) + if deploySettings.Roxie.ClusterType == types.ClusterTypeUnknown { + clusterType := env.GetCurrentClusterType() + log.Dimf("Detected cluster type: %v", clusterType) + deploySettings.Roxie.ClusterType = clusterType + } + clusterType := deploySettings.Roxie.ClusterType + defaults, err := clusterdefaults.ApplyClusterDefaults(deploySettings) if err != nil { - return fmt.Errorf("applying defaults for cluster type %v: %w", clusterType, err) + return err } if verbose { log.Dimf("Applying the following defaults based on detected cluster type %v:", clusterType) @@ -409,6 +413,8 @@ func deployValidate(components component.Component, deploySettings *deployer.Con return errors.New("running without a controlling terminal requires --envrc to be set") } + clusterType := deploySettings.Roxie.ClusterType + if env.RunningInRoxieContainer { // For running containerized we have specific requirements. if deploySettings.Central.PortForwardingEnabled() { @@ -419,7 +425,7 @@ func deployValidate(components component.Component, deploySettings *deployer.Con } // On infra OpenShift we already get image pull secrets for Quay automatically. - if clusterType := env.GetCurrentClusterType(); clusterType != types.ClusterTypeInfraOpenShift4 { + if clusterType != types.ClusterTypeInfraOpenShift4 { if os.Getenv("REGISTRY_USERNAME") == "" || os.Getenv("REGISTRY_PASSWORD") == "" { return fmt.Errorf("containerized mode requires REGISTRY_USERNAME and REGISTRY_PASSWORD environment variables for clusters of type %s", clusterType) } @@ -437,7 +443,6 @@ func deployValidate(components component.Component, deploySettings *deployer.Con if deploySettings.Operator.DeployViaOlm { return errors.New("using Konflux images while deploying operator via OLM is not supported") } - clusterType := env.GetCurrentClusterType() if !clusterType.IsOpenShift() { return fmt.Errorf("--konflux flag is only supported on OpenShift 4 clusters (current cluster type: %s)", clusterType.String()) } diff --git a/internal/clusterdefaults/clusterdefaults.go b/internal/clusterdefaults/clusterdefaults.go index 588c8e9d..50ad00a6 100644 --- a/internal/clusterdefaults/clusterdefaults.go +++ b/internal/clusterdefaults/clusterdefaults.go @@ -13,12 +13,12 @@ import ( // provided deployer.Config. // Returns *just* the assembled defaults for the given cluster type for logging purposes. func ApplyClusterDefaults( - clusterType types.ClusterType, config *deployer.Config, ) (*deployer.Config, error) { if config == nil { panic("applying cluster defaults to nil config") } + clusterType := config.Roxie.ClusterType defaults := getDefaultsForClusterType(clusterType) if defaults == nil { return nil, nil @@ -27,11 +27,11 @@ func ApplyClusterDefaults( // Make a copy. defaultsCopy, err := defaults.DeepCopy() if err != nil { - return nil, fmt.Errorf("deep-copying cluster defaults: %w", err) + return nil, fmt.Errorf("deep-copying cluster defaults or cluster type %s: %w", clusterType, err) } if err := mergo.Merge(config, defaultsCopy, mergo.WithoutDereference); err != nil { - return nil, fmt.Errorf("merging-in cluster defaults: %w", err) + return nil, fmt.Errorf("merging-in cluster defaults for cluster type %s: %w", clusterType, err) } return defaultsCopy, nil diff --git a/internal/clusterdefaults/clusterdefaults_test.go b/internal/clusterdefaults/clusterdefaults_test.go index f0dab56c..78b59e13 100644 --- a/internal/clusterdefaults/clusterdefaults_test.go +++ b/internal/clusterdefaults/clusterdefaults_test.go @@ -113,7 +113,8 @@ func TestClusterDefaults(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := tt.config - _, err := ApplyClusterDefaults(tt.clusterType, &config) + config.Roxie.ClusterType = tt.clusterType + _, err := ApplyClusterDefaults(&config) require.NoError(t, err) if tt.wantConfig.Central.Exposure == nil { diff --git a/internal/deployer/config.go b/internal/deployer/config.go index 907fc4b4..b4fbeabf 100644 --- a/internal/deployer/config.go +++ b/internal/deployer/config.go @@ -44,9 +44,10 @@ func (c *Config) DeepCopy() (*Config, error) { // RoxieConfig holds roxie-level settings such as version and feature flags. type RoxieConfig struct { - Version string `yaml:"version,omitempty"` - KonfluxImages bool `yaml:"konfluxImages,omitempty"` - FeatureFlags map[string]bool `yaml:"featureFlags,omitempty"` + Version string `yaml:"version,omitempty"` + KonfluxImages bool `yaml:"konfluxImages,omitempty"` + FeatureFlags map[string]bool `yaml:"featureFlags,omitempty"` + ClusterType types.ClusterType `yaml:"clusterType,omitempty"` } // NewRoxieConfig returns a RoxieConfig with initialized defaults. diff --git a/internal/deployer/deploy_via_operator.go b/internal/deployer/deploy_via_operator.go index f97a9233..ed5bae24 100644 --- a/internal/deployer/deploy_via_operator.go +++ b/internal/deployer/deploy_via_operator.go @@ -119,7 +119,7 @@ func (d *Deployer) ensureOperatorDeployed(ctx context.Context) error { func (d *Deployer) deployCentralOperator(ctx context.Context) error { d.logger.Info("🚀 Deploying Central via Operator...") - needPullSecrets := env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + needPullSecrets := d.config.Roxie.ClusterType != types.ClusterTypeInfraOpenShift4 if err := d.prepareNamespace(ctx, d.config.Central.Namespace, needPullSecrets); err != nil { return fmt.Errorf("failed to prepare namespace: %w", err) } @@ -655,7 +655,7 @@ func (d *Deployer) configureCentralEndpoint(ctx context.Context) error { func (d *Deployer) deploySecuredClusterOperator(ctx context.Context) error { d.logger.Info("🚀 Deploying SecuredCluster via Operator...") - needPullSecrets := env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + needPullSecrets := d.config.Roxie.ClusterType != types.ClusterTypeInfraOpenShift4 if err := d.prepareNamespace(ctx, d.config.SecuredCluster.Namespace, needPullSecrets); err != nil { return fmt.Errorf("failed to prepare namespace: %w", err) } diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index f541b20d..83090e73 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -309,7 +309,7 @@ func (d *Deployer) stopDetachedPortForward() { // Deploy deploys the specified components to the cluster. func (d *Deployer) Deploy(ctx context.Context, components component.Component) error { // Prepare and verify credentials early to fail fast. - if env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 { + if d.config.Roxie.ClusterType != types.ClusterTypeInfraOpenShift4 { if err := d.prepareCredentials(); err != nil { return fmt.Errorf("failed to prepare credentials: %w", err) } @@ -317,7 +317,7 @@ func (d *Deployer) Deploy(ctx context.Context, components component.Component) e d.logger.Infof("Initiating deployment of %s", components) - if d.config.Roxie.KonfluxImages && env.GetCurrentClusterType() == types.ClusterTypeOpenShift4 { + if d.config.Roxie.KonfluxImages && d.config.Roxie.ClusterType == types.ClusterTypeOpenShift4 { // For deploying Konflux-built images, we need to configure image-rewriting on the cluster at the CRI-O level. // But due to https://access.redhat.com/solutions/6540591 the standard pull-secret mechanism won't for the // target image references. A workaround is to inject the pull secrets we need into OpenShift's global @@ -800,7 +800,7 @@ func (d *Deployer) PrintCentralDeploymentSummary() { // Deployment details log.Info(cyan.Sprint("│") + createRow("Component", component)) - log.Info(cyan.Sprint("│") + createRow("Cluster Type", env.GetCurrentClusterType().String())) + log.Info(cyan.Sprint("│") + createRow("Cluster Type", d.config.Roxie.ClusterType.String())) log.Info(cyan.Sprint("│") + createRow("Main Tag", mainImageTag)) log.Info(cyan.Sprint("│") + createRow("Kubernetes Context", kubeContext)) @@ -979,7 +979,7 @@ func (d *Deployer) PrintSecuredClusterDeploymentSummary() { // Deployment details log.Info(cyan.Sprint("│") + createRow("Component", component)) - log.Info(cyan.Sprint("│") + createRow("Cluster Type", env.GetCurrentClusterType().String())) + log.Info(cyan.Sprint("│") + createRow("Cluster Type", d.config.Roxie.ClusterType.String())) log.Info(cyan.Sprint("│") + createRow("Main Tag", mainImageTag)) log.Info(cyan.Sprint("│") + createRow("Kubernetes Context", kubeContext)) diff --git a/internal/deployer/operator.go b/internal/deployer/operator.go index 8f451c4a..106476b0 100644 --- a/internal/deployer/operator.go +++ b/internal/deployer/operator.go @@ -14,7 +14,6 @@ import ( "gopkg.in/yaml.v3" - "github.com/stackrox/roxie/internal/env" "github.com/stackrox/roxie/internal/k8s" "github.com/stackrox/roxie/internal/ocihelper" "github.com/stackrox/roxie/internal/types" @@ -202,7 +201,7 @@ func (d *Deployer) getOperatorBundleImage() string { // ensureKonfluxImageRewriting configures image rewriting for Konflux images func (d *Deployer) ensureKonfluxImageRewriting(ctx context.Context) error { - if !env.GetCurrentClusterType().IsOpenShift() { + if !d.config.Roxie.ClusterType.IsOpenShift() { return errors.New("image rewriting for Konflux is only supported on OpenShift4 clusters") } @@ -290,7 +289,7 @@ func (d *Deployer) applyImageContentSourcePolicy(ctx context.Context) error { // removeKonfluxImageRewriting removes the ImageContentSourcePolicy for Konflux images if it exists func (d *Deployer) removeKonfluxImageRewriting(ctx context.Context) error { - if !env.GetCurrentClusterType().IsOpenShift() { + if !d.config.Roxie.ClusterType.IsOpenShift() { return nil } @@ -320,7 +319,7 @@ func (d *Deployer) deployOperatorFromCSV(ctx context.Context, bundleDir string) } serviceAccountName := deploymentSpec["service_account"].(string) - d.useOperatorPullSecrets = d.config.Roxie.KonfluxImages && env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + d.useOperatorPullSecrets = d.config.Roxie.KonfluxImages && d.config.Roxie.ClusterType != types.ClusterTypeInfraOpenShift4 d.logger.Info("📋 Operator deployment plan:") d.logger.Dim(fmt.Sprintf(" • Namespace: %s", operatorNamespace)) diff --git a/internal/types/cluster_type.go b/internal/types/cluster_type.go index 47146a20..2eb63447 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -1,5 +1,7 @@ package types +import "fmt" + // ClusterType represents different types of Kubernetes clusters type ClusterType int @@ -22,6 +24,27 @@ const ( ClusterTypeCRC ) +var ( + clusterTypeToIdentifier = map[ClusterType]string{ + ClusterTypeUnknown: "Unknown", + ClusterTypeInfraGKE: "InfraGKE", + ClusterTypeInfraOpenShift4: "InfraOpenShift4", + ClusterTypeOpenShift4: "OpenShift4", + ClusterTypeKind: "Kind", + ClusterTypeMinikube: "Minikube", + ClusterTypeK3s: "K3s", + ClusterTypeCRC: "CRC", + } + + identifierToClusterType = func() map[string]ClusterType { + m := make(map[string]ClusterType, len(clusterTypeToIdentifier)) + for k, v := range clusterTypeToIdentifier { + m[v] = k + } + return m + }() +) + func (ct ClusterType) IsOpenShift() bool { return ct == ClusterTypeInfraOpenShift4 || ct == ClusterTypeOpenShift4 } @@ -59,3 +82,23 @@ func AllClusterTypes() []ClusterType { ClusterTypeOpenShift4, } } + +func (ct ClusterType) MarshalYAML() (any, error) { + if id, ok := clusterTypeToIdentifier[ct]; ok { + return id, nil + } + return nil, fmt.Errorf("unknown cluster type: %d", ct) +} + +func (ct *ClusterType) UnmarshalYAML(unmarshal func(any) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + parsed, ok := identifierToClusterType[s] + if !ok { + return fmt.Errorf("unknown cluster type identifier: %q", s) + } + *ct = parsed + return nil +} From c8b8e98c35200a06d70565e0b22b189c3199efa2 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:07:05 +0200 Subject: [PATCH 09/16] ClusterType Tests --- internal/types/cluster_type_test.go | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 internal/types/cluster_type_test.go diff --git a/internal/types/cluster_type_test.go b/internal/types/cluster_type_test.go new file mode 100644 index 00000000..7758c1ec --- /dev/null +++ b/internal/types/cluster_type_test.go @@ -0,0 +1,75 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestClusterTypeMarshalYAML(t *testing.T) { + tests := []struct { + clusterType ClusterType + expected string + }{ + {ClusterTypeUnknown, "Unknown"}, + {ClusterTypeInfraGKE, "InfraGKE"}, + {ClusterTypeInfraOpenShift4, "InfraOpenShift4"}, + {ClusterTypeOpenShift4, "OpenShift4"}, + {ClusterTypeKind, "Kind"}, + {ClusterTypeMinikube, "Minikube"}, + {ClusterTypeK3s, "K3s"}, + {ClusterTypeCRC, "CRC"}, + } + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + out, err := yaml.Marshal(tt.clusterType) + require.NoError(t, err) + assert.Equal(t, tt.expected+"\n", string(out)) + }) + } +} + +func TestClusterTypeUnmarshalYAML(t *testing.T) { + tests := []struct { + input string + expected ClusterType + }{ + {"InfraGKE", ClusterTypeInfraGKE}, + {"InfraOpenShift4", ClusterTypeInfraOpenShift4}, + {"OpenShift4", ClusterTypeOpenShift4}, + {"Kind", ClusterTypeKind}, + {"Minikube", ClusterTypeMinikube}, + {"K3s", ClusterTypeK3s}, + {"CRC", ClusterTypeCRC}, + {"Unknown", ClusterTypeUnknown}, + } + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + var ct ClusterType + err := yaml.Unmarshal([]byte(tt.input), &ct) + require.NoError(t, err) + assert.Equal(t, tt.expected, ct) + }) + } +} + +func TestClusterTypeUnmarshalYAML_Invalid(t *testing.T) { + var ct ClusterType + err := yaml.Unmarshal([]byte("bogus"), &ct) + assert.ErrorContains(t, err, "unknown cluster type identifier") +} + +func TestClusterTypeRoundTrip(t *testing.T) { + for _, ct := range AllClusterTypes() { + t.Run(ct.String(), func(t *testing.T) { + out, err := yaml.Marshal(ct) + require.NoError(t, err) + + var parsed ClusterType + require.NoError(t, yaml.Unmarshal(out, &parsed)) + assert.Equal(t, ct, parsed) + }) + } +} From 1e2e40f89f6d279d6ea56183aa640559e7292b5a Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:19:05 +0200 Subject: [PATCH 10/16] go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 59cbd5c1..63116be2 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/stretchr/testify v1.11.1 golang.org/x/term v0.39.0 gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.36.1 k8s.io/apimachinery v0.36.1 k8s.io/client-go v0.36.1 k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 @@ -31,7 +32,6 @@ require ( golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect - k8s.io/api v0.36.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) From d19c9a4eabb06ec0151e5a42d78da428b9302693 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:28:24 +0200 Subject: [PATCH 11/16] Bump golangci-lint for Go 1.26 --- .github/workflows/code-quality.yml | 7 ++++--- .golangci.yml | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index ed8bc099..be1427db 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -21,9 +21,10 @@ jobs: go-version-file: 'go.mod' cache: true - - name: Install golangci-lint - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.12.2 - name: Check formatting run: | diff --git a/.golangci.yml b/.golangci.yml index dbab8885..8330b89c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ # golangci-lint configuration for roxie -version: 2 +version: "2" run: timeout: 5m @@ -15,6 +15,10 @@ linters: - ineffassign # Detect ineffectual assignments - misspell # Find commonly misspelled words + settings: + staticcheck: + checks: ["all", "-SA4009", "-ST1000", "-ST1003", "-ST1020"] + # Disable noisy linters disable: - errcheck # Too many false positives for cleanup code @@ -22,16 +26,12 @@ linters: - revive # Style linter is too opinionated - unparam # Often flags valid parameter flexibility -linters-settings: - staticcheck: - checks: ["all", "-SA4009"] # Disable "argument is overwritten" check - issues: # Don't limit the number of issues max-issues-per-linter: 0 max-same-issues: 0 output: - print-issued-lines: true - print-linter-name: true - sort-results: true + formats: + text: + path: stdout From 1244135363d3e15dd1d85ede20aeba68cb8951c1 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:45:29 +0200 Subject: [PATCH 12/16] Bump setup-go --- .github/workflows/code-quality.yml | 2 +- internal/deployer/operator.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index be1427db..07f3d8ba 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: 'go.mod' cache: true diff --git a/internal/deployer/operator.go b/internal/deployer/operator.go index 106476b0..67d5604d 100644 --- a/internal/deployer/operator.go +++ b/internal/deployer/operator.go @@ -295,7 +295,8 @@ func (d *Deployer) removeKonfluxImageRewriting(ctx context.Context) error { d.logger.Dim("Removing Konflux ImageContentSourcePolicy if present...") _, err := d.runKubectl(ctx, k8s.KubectlOptions{ - Args: []string{"delete", "imagecontentsourcepolicy", "acs-konflux-builds", "--ignore-not-found=true"}, + Args: []string{"delete", "imagecontentsourcepolicy", "acs-konflux-builds", "--ignore-not-found=true"}, + IgnoreErrors: true, }) if err != nil { return fmt.Errorf("failed to delete ImageContentSourcePolicy: %w", err) From e9de6aff5c32324899f3658a009b7052b5be7e88 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:47:39 +0200 Subject: [PATCH 13/16] (CI trigger) From 40d32082f26d35b1368873ab8c902947ed380262 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:52:48 +0200 Subject: [PATCH 14/16] Bump go-toolset --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2c40af0e..712b43b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # Supports multi-architecture builds (amd64, arm64) # Stage 1: Build roxie binary -FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:1.25@sha256:2830e4bd1c394ed506c00a9abbb4d00445e2e72e8ef4e3cd51e0da0db66dee12 AS builder +FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:9.7-1778675823 AS builder # Build arguments for cross-compilation # These are automatically provided by Docker buildx From 8b7f57d071e9bd0514d4ef16072532291c332a9d Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:57:14 +0200 Subject: [PATCH 15/16] GOTOOLCHAIN=auto --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 712b43b9..b89844c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ ARG TARGETARCH WORKDIR /build USER root +ENV GOTOOLCHAIN=auto # Copy go mod files first for better layer caching COPY go.mod go.sum ./ From 54d0d13abef032cdc910db18d95fdca194aa2207 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Wed, 13 May 2026 22:57:24 +0200 Subject: [PATCH 16/16] Revert "Bump go-toolset" This reverts commit 40d32082f26d35b1368873ab8c902947ed380262. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b89844c3..a26c41c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # Supports multi-architecture builds (amd64, arm64) # Stage 1: Build roxie binary -FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:9.7-1778675823 AS builder +FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:1.25@sha256:2830e4bd1c394ed506c00a9abbb4d00445e2e72e8ef4e3cd51e0da0db66dee12 AS builder # Build arguments for cross-compilation # These are automatically provided by Docker buildx