From 1c2e2d9870c6936c2b1e48423aaebd7a9b67c781 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sat, 12 Apr 2025 03:20:54 +0200 Subject: [PATCH 1/5] feat: new cleanup script --- cleanup-renku-ci-deployments/go.mod | 51 ++++++++ cleanup-renku-ci-deployments/go.sum | 162 ++++++++++++++++++++++++++ cleanup-renku-ci-deployments/main.go | 126 ++++++++++++++++++++ cleanup-renku-ci-deployments/utils.go | 121 +++++++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 cleanup-renku-ci-deployments/go.mod create mode 100644 cleanup-renku-ci-deployments/go.sum create mode 100644 cleanup-renku-ci-deployments/main.go create mode 100644 cleanup-renku-ci-deployments/utils.go diff --git a/cleanup-renku-ci-deployments/go.mod b/cleanup-renku-ci-deployments/go.mod new file mode 100644 index 0000000..f7da23d --- /dev/null +++ b/cleanup-renku-ci-deployments/go.mod @@ -0,0 +1,51 @@ +module github.com/SwissDataScienceCenter/renku-actions/cleanup-renku-ci-deployments + +go 1.24.1 + +require ( + github.com/alecthomas/kong v1.10.0 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // 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/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/cleanup-renku-ci-deployments/go.sum b/cleanup-renku-ci-deployments/go.sum new file mode 100644 index 0000000..614cb76 --- /dev/null +++ b/cleanup-renku-ci-deployments/go.sum @@ -0,0 +1,162 @@ +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.10.0 h1:8K4rGDpT7Iu+jEXCIJUeKqvpwZHbsFRoebLbnzlmrpw= +github.com/alecthomas/kong v1.10.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +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/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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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-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/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= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/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/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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= +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/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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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/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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.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= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +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-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/cleanup-renku-ci-deployments/main.go b/cleanup-renku-ci-deployments/main.go new file mode 100644 index 0000000..744c96d --- /dev/null +++ b/cleanup-renku-ci-deployments/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/url" + "regexp" + + "github.com/alecthomas/kong" + // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + // "k8s.io/utils/ptr" +) + +type SensitiveString string + +func (s SensitiveString) String() string { + return fmt.Sprintf("", len(s)) +} + +func (s SensitiveString) Format(w fmt.State, v rune) { + _, err := w.Write([]byte(s.String())) + if err != nil { + panic(err) + } +} + +type Config struct { + Kubeconfig string `type:"existingfile" help:"Path to the kubeconfig file" default:"~/.kube/config"` + GitlabURL *url.URL `help:"Gitlab url" default:"http://gitlab.dev.renku.ch"` + GitlabToken SensitiveString `help:"Gitlab token" env:"GITLAB_TOKEN"` + DryRun bool `help:"Just log but do not actually execute anything"` +} + +type RemoveArgs struct { + ReleaseRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting releases"` + NamespaceRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting the namespaces"` +} + +type Args struct { + Config `embed:""` + Remove RemoveArgs `cmd:"" aliases:"rm" default:"withargs"` +} + +func main() { + var args Args + kctx := kong.Parse(&args) + log.Printf("Command: %+v", kctx.Command()) + log.Printf("Args: %+v", args) + ctx := context.Background() + + nsRegexs := []*regexp.Regexp{} + for _, regStr := range args.Remove.NamespaceRegex { + regex, err := regexp.CompilePOSIX(regStr) + if err != nil { + log.Fatal(err) + } + nsRegexs = append(nsRegexs, regex) + } + + relRegexs := []*regexp.Regexp{} + for _, regStr := range args.Remove.ReleaseRegex { + regex, err := regexp.CompilePOSIX(regStr) + if err != nil { + log.Fatal(err) + } + relRegexs = append(relRegexs, regex) + } + + clnt, err := k8sClient(args.Kubeconfig) + if err != nil { + log.Fatal(err) + } + + namespaces, err := getNamespaces(ctx, clnt, nsRegexs...) + if err != nil { + log.Fatal(err) + } + log.Printf("Found %d namespaces", len(namespaces)) + + releases, err := getReleases(ctx, clnt, relRegexs...) + if err != nil { + log.Fatal(err) + } + log.Printf("Found %d releases", len(releases)) + + // To cleanup patch sessions to remove finalizer + // Patch jupyter servers to remove their finalizers + // Remove the namespace + asGVR := schema.GroupVersionResource{ + Group: "amalthea.dev", + Version: "v1alpha1", + Resource: "amaltheasessions", + } + jsGVR := schema.GroupVersionResource{ + Group: "amalthea.dev", + Version: "v1alpha1", + Resource: "jupyterservers", + } + + dynClnt, err := getDynClient(args.Kubeconfig) + if err != nil { + log.Fatal(err) + } + + asClnt := dynClnt.Resource(asGVR) + jsClnt := dynClnt.Resource(jsGVR) + // nsClnt := clnt.CoreV1().Namespaces() + // delOptions := metav1.DeleteOptions{ + // GracePeriodSeconds: ptr.To(int64(0)), + // PropagationPolicy: ptr.To(metav1.DeletePropagationForeground), + // } + for _, ns := range namespaces { + log.Printf("Removing finalizers from sesions in namesapce %s", ns.GetName()) + err = removeFinalizers(ctx, jsClnt.Namespace(ns.GetName())) + err = removeFinalizers(ctx, asClnt.Namespace(ns.GetName())) + // err = nsClnt.Delete(ctx, ns.GetName(), delOptions) + } + for _, release := range releases { + log.Printf("Removing finalizers from sesions in release %s in namesapce %s", release.Name, release.Namespace) + err = removeFinalizers(ctx, jsClnt.Namespace(release.Namespace)) + err = removeFinalizers(ctx, asClnt.Namespace(release.Namespace)) + // err = nsClnt.Delete(ctx, release.Namespace, delOptions) + } +} diff --git a/cleanup-renku-ci-deployments/utils.go b/cleanup-renku-ci-deployments/utils.go new file mode 100644 index 0000000..87e15f7 --- /dev/null +++ b/cleanup-renku-ci-deployments/utils.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + //"encoding/json" + "log" + "regexp" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +func k8sClient(kubeconfig string) (*kubernetes.Clientset, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, err + } + clientset, err := kubernetes.NewForConfig(config) + return clientset, err +} + +func getNamespaces(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*regexp.Regexp) ([]v1.Namespace, error) { + namespaces, err := clnt.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + + output := []v1.Namespace{} + if len(regexs) == 0 { + return namespaces.Items, nil + } + for _, ns := range namespaces.Items { + for _, regex := range regexs { + if !regex.MatchString(ns.Name) { + continue + } + log.Printf("Namespace %s MATCHED regex %s", ns.Name, regex) + output = append(output, ns) + } + } + return output, nil +} + +type NamespacedNameSet map[types.NamespacedName]struct{} + +func (n NamespacedNameSet) Values() []types.NamespacedName { + output := []types.NamespacedName{} + for k, _ := range n { + output = append(output, k) + } + return output +} + +func getReleases(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*regexp.Regexp) ([]types.NamespacedName, error) { + namespaces, err := getNamespaces(ctx, clnt) + if err != nil { + return nil, err + } + output := NamespacedNameSet(map[types.NamespacedName]struct{}{}) + for _, ns := range namespaces { + releases, err := clnt.CoreV1().Secrets(ns.GetName()).List(ctx, metav1.ListOptions{FieldSelector: "type=helm.sh/release.v1"}) + if err != nil { + return nil, err + } + for _, release := range releases.Items { + name := release.GetLabels()["name"] + nsn := types.NamespacedName{Namespace: ns.GetName(), Name: name} + if _, found := output[nsn]; found { + continue + } + if len(regexs) == 0 { + output[nsn] = struct{}{} + continue + } + for _, regex := range regexs { + if len(name) > 0 && !regex.MatchString(name) { + continue + } + log.Printf("Release %s MATCHED regex %s", name, regex) + output[nsn] = struct{}{} + } + } + } + return output.Values(), nil +} + +func getDynClient(kubeconfig string) (*dynamic.DynamicClient, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, err + } + dynClient, err := dynamic.NewForConfig(config) + return dynClient, err +} + +func removeFinalizers(ctx context.Context, clnt dynamic.ResourceInterface) error { + // List the CRD objects in a specific namespace (or use "" for all namespaces) + objects, err := clnt.List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + // patch, err := json.Marshal([]map[string]string{ + // {"op": "remove", "path": "/metadata/finalizers"}, + // }) + if err != nil { + return err + } + for _, obj := range objects.Items { + log.Printf("patching %s in namespace %s of kind %s", obj.GetName(), obj.GetNamespace(), obj.GetKind()) + // _, err := clnt.Patch(ctx, obj.GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) + // if err != nil { + // return err + // } + } + return nil +} From 9b8e8be6838ea8827f6b7570a57025e8bc7d74f7 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sun, 13 Apr 2025 20:59:00 +0200 Subject: [PATCH 2/5] feat: add option to remove old gitlab applications --- cleanup-renku-ci-deployments/gitlab.go | 129 +++++++++++++++++++++++++ cleanup-renku-ci-deployments/main.go | 88 ++++++++++++----- cleanup-renku-ci-deployments/utils.go | 48 +++++++-- 3 files changed, 232 insertions(+), 33 deletions(-) create mode 100644 cleanup-renku-ci-deployments/gitlab.go diff --git a/cleanup-renku-ci-deployments/gitlab.go b/cleanup-renku-ci-deployments/gitlab.go new file mode 100644 index 0000000..5e2e9d2 --- /dev/null +++ b/cleanup-renku-ci-deployments/gitlab.go @@ -0,0 +1,129 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + "regexp" + "strings" +) + +type GitlabApplication struct { + ID int `json:"id,omitempty"` + ApplicationID string `json:"application_id,omitempty"` + ApplicationName string `json:"application_name,omitempty"` + CallbackURL string `json:"callback_url,omitempty"` + Confidential bool `json:"confidential,omitempty"` +} + +var GitlabApplicationNotFound error = fmt.Errorf("Cannot find the gitlab application") + +func listGitlabApplications(gitlabURL *url.URL, apiToken SensitiveString) ([]GitlabApplication, error) { + reqURL := gitlabURL.JoinPath("/api/v4/applications").String() + req, err := http.NewRequest("GET", reqURL, nil) + if err != nil { + return []GitlabApplication{}, err + } + req.Header.Add("private-token", string(apiToken)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return []GitlabApplication{}, err + } + var gitlabApps []GitlabApplication + err = json.NewDecoder(res.Body).Decode(&gitlabApps) + if err != nil { + val, _ := httputil.DumpResponse(res, true) + return []GitlabApplication{}, fmt.Errorf("could not decode response because of err %s, response is %s\n", err.Error(), string(val)) + } + defer res.Body.Close() + return gitlabApps, nil +} + +func findGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, name string) (GitlabApplication, error) { + gitlabApps, err := listGitlabApplications(gitlabURL, apiToken) + if err != nil { + return GitlabApplication{}, err + } + for _, app := range gitlabApps { + if app.ApplicationName == name { + return app, nil + } + } + return GitlabApplication{}, GitlabApplicationNotFound +} + +func removeGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, id int) error { + reqURL := gitlabURL.JoinPath(fmt.Sprintf("/api/v4/applications/%d", id)).String() + req, err := http.NewRequest("DELETE", reqURL, nil) + req.Header.Add("private-token", string(apiToken)) + if err != nil { + return err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + if res.StatusCode != 204 { + resContent, err := httputil.DumpResponse(res, true) + if err != nil { + return err + } + return fmt.Errorf("Gitlab responded with unexpected status code %d and content %s", res.StatusCode, resContent) + } + return nil +} + +func cleanupGitlabApps(gitlabURL *url.URL, apiToken SensitiveString, dryRun bool) error { + gitlabApps, err := listGitlabApplications(gitlabURL, apiToken) + if err != nil { + return err + } + for _, app := range gitlabApps { + log.Printf("Checking for app %s", app.ApplicationName) + if !(strings.Contains(app.ApplicationName, "renku-ci") || strings.Contains(app.ApplicationName, "ci-renku")) { + log.Printf("\tapplication %s is not part of a CI deployment, skipping", app.ApplicationName) + continue + } + r := regexp.MustCompile("\\s+") + urls := r.Split(app.CallbackURL, -1) + if len(urls) == 0 { + log.Println("\tdid not find any callback urls, skipping") + continue + } + callbackURLStr := urls[0] + callbackURL, err := url.Parse(callbackURLStr) + if err != nil { + log.Printf("\terror in parsing callback url %s, %s, skipping", app.CallbackURL, err.Error()) + continue + } + renkuURL := *callbackURL + renkuURL.RawFragment = "" + renkuURL.RawPath = "" + renkuURL.Path = "" + renkuURL.RawQuery = "" + tlsError := tls.CertificateVerificationError{Err: fmt.Errorf(""), UnverifiedCertificates: []*x509.Certificate{}} + res, err := http.Get(renkuURL.String()) + if err != nil && !strings.Contains(err.Error(), tlsError.Error()) { + log.Printf("\terror in sending request to the application base url %s, %s, skipping", renkuURL.String(), err.Error()) + continue + } + if res != nil && res.StatusCode < 400 { + log.Printf("\tstatus code from calling %s is <400, will not remove gitlab client", renkuURL.String()) + continue + } + log.Printf("\tremoving gitlab application %s", app.ApplicationName) + if !dryRun { + err = removeGitlabApplication(gitlabURL, apiToken, app.ID) + if err != nil { + log.Printf("\tcannot remove gitlab application %s because of %s", app.ApplicationID, err.Error()) + continue + } + } + } + return nil +} diff --git a/cleanup-renku-ci-deployments/main.go b/cleanup-renku-ci-deployments/main.go index 744c96d..9d51c44 100644 --- a/cleanup-renku-ci-deployments/main.go +++ b/cleanup-renku-ci-deployments/main.go @@ -8,9 +8,7 @@ import ( "regexp" "github.com/alecthomas/kong" - // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - // "k8s.io/utils/ptr" ) type SensitiveString string @@ -38,9 +36,26 @@ type RemoveArgs struct { NamespaceRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting the namespaces"` } +func (r *RemoveArgs) Run(ctx *CmdContext) error { + removeReleases(ctx, r) + return nil +} + +type GitlabApplicationCleanup struct{} + +func (g *GitlabApplicationCleanup) Run(ctx *CmdContext) error { + return cleanupGitlabApps(ctx.GitlabURL, ctx.GitlabToken, ctx.DryRun) +} + type Args struct { - Config `embed:""` - Remove RemoveArgs `cmd:"" aliases:"rm" default:"withargs"` + Remove RemoveArgs `cmd:"" aliases:"rm" help:"Remove Renku deployments"` + GitlabApplicationCleanup GitlabApplicationCleanup `cmd:"gitlab_application_cleanup" aliases:"gac" help:"Remove unused gitlab apps."` + Config `embed:""` +} + +type CmdContext struct { + ctx context.Context + Config } func main() { @@ -48,10 +63,19 @@ func main() { kctx := kong.Parse(&args) log.Printf("Command: %+v", kctx.Command()) log.Printf("Args: %+v", args) - ctx := context.Background() + ctx := CmdContext{ + Config: args.Config, + ctx: context.Background(), + } + err := kctx.Run(&ctx) + if err != nil { + log.Fatalln(err) + } +} +func removeReleases(ctx *CmdContext, args *RemoveArgs) { nsRegexs := []*regexp.Regexp{} - for _, regStr := range args.Remove.NamespaceRegex { + for _, regStr := range args.NamespaceRegex { regex, err := regexp.CompilePOSIX(regStr) if err != nil { log.Fatal(err) @@ -60,7 +84,7 @@ func main() { } relRegexs := []*regexp.Regexp{} - for _, regStr := range args.Remove.ReleaseRegex { + for _, regStr := range args.ReleaseRegex { regex, err := regexp.CompilePOSIX(regStr) if err != nil { log.Fatal(err) @@ -68,26 +92,23 @@ func main() { relRegexs = append(relRegexs, regex) } - clnt, err := k8sClient(args.Kubeconfig) + clnt, err := k8sClient(ctx.Kubeconfig) if err != nil { log.Fatal(err) } - namespaces, err := getNamespaces(ctx, clnt, nsRegexs...) + namespaces, err := getNamespaces(ctx.ctx, clnt, nsRegexs...) if err != nil { log.Fatal(err) } log.Printf("Found %d namespaces", len(namespaces)) - releases, err := getReleases(ctx, clnt, relRegexs...) + releases, err := getReleases(ctx.ctx, clnt, relRegexs...) if err != nil { log.Fatal(err) } log.Printf("Found %d releases", len(releases)) - // To cleanup patch sessions to remove finalizer - // Patch jupyter servers to remove their finalizers - // Remove the namespace asGVR := schema.GroupVersionResource{ Group: "amalthea.dev", Version: "v1alpha1", @@ -99,28 +120,45 @@ func main() { Resource: "jupyterservers", } - dynClnt, err := getDynClient(args.Kubeconfig) + dynClnt, err := getDynClient(ctx.Kubeconfig) if err != nil { log.Fatal(err) } asClnt := dynClnt.Resource(asGVR) jsClnt := dynClnt.Resource(jsGVR) - // nsClnt := clnt.CoreV1().Namespaces() - // delOptions := metav1.DeleteOptions{ - // GracePeriodSeconds: ptr.To(int64(0)), - // PropagationPolicy: ptr.To(metav1.DeletePropagationForeground), - // } + nsClnt := clnt.CoreV1().Namespaces() for _, ns := range namespaces { log.Printf("Removing finalizers from sesions in namesapce %s", ns.GetName()) - err = removeFinalizers(ctx, jsClnt.Namespace(ns.GetName())) - err = removeFinalizers(ctx, asClnt.Namespace(ns.GetName())) - // err = nsClnt.Delete(ctx, ns.GetName(), delOptions) + if !ctx.DryRun { + err := removeFinalizersAndNamespace(ctx.ctx, ns.GetName(), nsClnt, jsClnt, asClnt) + if err != nil { + continue + } + } } for _, release := range releases { log.Printf("Removing finalizers from sesions in release %s in namesapce %s", release.Name, release.Namespace) - err = removeFinalizers(ctx, jsClnt.Namespace(release.Namespace)) - err = removeFinalizers(ctx, asClnt.Namespace(release.Namespace)) - // err = nsClnt.Delete(ctx, release.Namespace, delOptions) + if !ctx.DryRun { + err := removeFinalizersAndNamespace(ctx.ctx, release.Namespace, nsClnt, jsClnt, asClnt) + if err != nil { + continue + } + } + } + for _, release := range releases { + log.Printf("Removing gitlab application for release %s", release.Name) + app, err := findGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, release.Name) + if err != nil { + log.Printf("Cannot find gitlab application for release %s because of error %s, skipping", release.Name, err.Error()) + continue + } + if !ctx.DryRun { + err = removeGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, app.ID) + if err != nil { + log.Printf("Cannot delete gitlab application for release %s because of error %s, skipping", release.Name, err.Error()) + continue + } + } } } diff --git a/cleanup-renku-ci-deployments/utils.go b/cleanup-renku-ci-deployments/utils.go index 87e15f7..2282ac7 100644 --- a/cleanup-renku-ci-deployments/utils.go +++ b/cleanup-renku-ci-deployments/utils.go @@ -2,7 +2,8 @@ package main import ( "context" - //"encoding/json" + + "encoding/json" "log" "regexp" @@ -11,7 +12,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/clientcmd" + "k8s.io/utils/ptr" ) func k8sClient(kubeconfig string) (*kubernetes.Clientset, error) { @@ -104,18 +107,47 @@ func removeFinalizers(ctx context.Context, clnt dynamic.ResourceInterface) error return err } - // patch, err := json.Marshal([]map[string]string{ - // {"op": "remove", "path": "/metadata/finalizers"}, - // }) + patch, err := json.Marshal([]map[string]string{ + {"op": "remove", "path": "/metadata/finalizers"}, + }) if err != nil { return err } for _, obj := range objects.Items { log.Printf("patching %s in namespace %s of kind %s", obj.GetName(), obj.GetNamespace(), obj.GetKind()) - // _, err := clnt.Patch(ctx, obj.GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) - // if err != nil { - // return err - // } + _, err := clnt.Patch(ctx, obj.GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) + if err != nil { + return err + } + } + return nil +} + +func removeFinalizersAndNamespace( + ctx context.Context, + namespace string, + nsClnt typedcorev1.NamespaceInterface, + jsClnt dynamic.NamespaceableResourceInterface, + asClnt dynamic.NamespaceableResourceInterface, +) error { + delOptions := metav1.DeleteOptions{ + GracePeriodSeconds: ptr.To(int64(0)), + PropagationPolicy: ptr.To(metav1.DeletePropagationForeground), + } + err := removeFinalizers(ctx, jsClnt.Namespace(namespace)) + if err != nil { + log.Printf("Cannot remove JypyterServer finalizers because of error %s, skipping", err.Error()) + return err + } + err = removeFinalizers(ctx, asClnt.Namespace(namespace)) + if err != nil { + log.Printf("Cannot remove AmaltheaSession finalizers because of error %s, skipping", err.Error()) + return err + } + err = asClnt.Delete(ctx, namespace, delOptions) + if err != nil { + log.Printf("Cannot remove AmaltheaSession finalizers because of error %s, skipping", err.Error()) + return err } return nil } From 847a2d701b026d2d7b87872d75ec91137826b2b1 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sun, 13 Apr 2025 21:43:01 +0200 Subject: [PATCH 3/5] feat: filter releases by age --- cleanup-renku-ci-deployments/gitlab.go | 20 +++++++++++++++++-- cleanup-renku-ci-deployments/main.go | 27 +++++++++----------------- cleanup-renku-ci-deployments/utils.go | 25 ++++++++++++++++++------ 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/cleanup-renku-ci-deployments/gitlab.go b/cleanup-renku-ci-deployments/gitlab.go index 5e2e9d2..29defae 100644 --- a/cleanup-renku-ci-deployments/gitlab.go +++ b/cleanup-renku-ci-deployments/gitlab.go @@ -21,7 +21,7 @@ type GitlabApplication struct { Confidential bool `json:"confidential,omitempty"` } -var GitlabApplicationNotFound error = fmt.Errorf("Cannot find the gitlab application") +var GitlabApplicationDoesNotExist error = fmt.Errorf("The Gitlab application does not exist") func listGitlabApplications(gitlabURL *url.URL, apiToken SensitiveString) ([]GitlabApplication, error) { reqURL := gitlabURL.JoinPath("/api/v4/applications").String() @@ -54,7 +54,7 @@ func findGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, name st return app, nil } } - return GitlabApplication{}, GitlabApplicationNotFound + return GitlabApplication{}, GitlabApplicationDoesNotExist } func removeGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, id int) error { @@ -78,6 +78,22 @@ func removeGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, id in return nil } +func findAndRemoveGitlabApplication(gitlabURL *url.URL, apiToken SensitiveString, name string, dryRun bool) error { + app, err := findGitlabApplication(gitlabURL, apiToken, name) + if err != nil { + log.Printf("Cannot find gitlab application for release %s because of error %s, skipping", name, err.Error()) + return err + } + if !dryRun { + err = removeGitlabApplication(gitlabURL, apiToken, app.ID) + if err != nil { + log.Printf("Cannot delete gitlab application for release %s because of error %s, skipping", name, err.Error()) + return err + } + } + return nil +} + func cleanupGitlabApps(gitlabURL *url.URL, apiToken SensitiveString, dryRun bool) error { gitlabApps, err := listGitlabApplications(gitlabURL, apiToken) if err != nil { diff --git a/cleanup-renku-ci-deployments/main.go b/cleanup-renku-ci-deployments/main.go index 9d51c44..a49ab12 100644 --- a/cleanup-renku-ci-deployments/main.go +++ b/cleanup-renku-ci-deployments/main.go @@ -6,6 +6,7 @@ import ( "log" "net/url" "regexp" + "time" "github.com/alecthomas/kong" "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,8 +33,9 @@ type Config struct { } type RemoveArgs struct { - ReleaseRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting releases"` - NamespaceRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting the namespaces"` + ReleaseRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting releases"` + NamespaceRegex []string `default:"^.+-ci-.+|^ci-.+" help:"Golang regex for selecting the namespaces"` + MinAge time.Duration `default:"168h" help:"The minimum age used to delete releases, time.ParseDuration used to parse, if set to zero then all ages are deleted"` } func (r *RemoveArgs) Run(ctx *CmdContext) error { @@ -97,13 +99,13 @@ func removeReleases(ctx *CmdContext, args *RemoveArgs) { log.Fatal(err) } - namespaces, err := getNamespaces(ctx.ctx, clnt, nsRegexs...) + namespaces, err := getNamespaces(ctx.ctx, clnt, args.MinAge, nsRegexs...) if err != nil { log.Fatal(err) } log.Printf("Found %d namespaces", len(namespaces)) - releases, err := getReleases(ctx.ctx, clnt, relRegexs...) + releases, err := getReleases(ctx.ctx, clnt, args.MinAge, relRegexs...) if err != nil { log.Fatal(err) } @@ -136,6 +138,8 @@ func removeReleases(ctx *CmdContext, args *RemoveArgs) { continue } } + log.Printf("Removing gitlab application for namespace %s", ns.Name) + findAndRemoveGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, ns.Name, ctx.DryRun) } for _, release := range releases { log.Printf("Removing finalizers from sesions in release %s in namesapce %s", release.Name, release.Namespace) @@ -145,20 +149,7 @@ func removeReleases(ctx *CmdContext, args *RemoveArgs) { continue } } - } - for _, release := range releases { log.Printf("Removing gitlab application for release %s", release.Name) - app, err := findGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, release.Name) - if err != nil { - log.Printf("Cannot find gitlab application for release %s because of error %s, skipping", release.Name, err.Error()) - continue - } - if !ctx.DryRun { - err = removeGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, app.ID) - if err != nil { - log.Printf("Cannot delete gitlab application for release %s because of error %s, skipping", release.Name, err.Error()) - continue - } - } + findAndRemoveGitlabApplication(ctx.GitlabURL, ctx.GitlabToken, release.Name, ctx.DryRun) } } diff --git a/cleanup-renku-ci-deployments/utils.go b/cleanup-renku-ci-deployments/utils.go index 2282ac7..ac6f4ab 100644 --- a/cleanup-renku-ci-deployments/utils.go +++ b/cleanup-renku-ci-deployments/utils.go @@ -2,6 +2,7 @@ package main import ( "context" + "time" "encoding/json" "log" @@ -26,7 +27,7 @@ func k8sClient(kubeconfig string) (*kubernetes.Clientset, error) { return clientset, err } -func getNamespaces(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*regexp.Regexp) ([]v1.Namespace, error) { +func getNamespaces(ctx context.Context, clnt *kubernetes.Clientset, minAge time.Duration, regexs ...*regexp.Regexp) ([]v1.Namespace, error) { namespaces, err := clnt.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -36,12 +37,18 @@ func getNamespaces(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*r if len(regexs) == 0 { return namespaces.Items, nil } + now := time.Now() for _, ns := range namespaces.Items { for _, regex := range regexs { + age := now.Sub(ns.GetCreationTimestamp().Time) + oldEnough := age > minAge || minAge == 0 if !regex.MatchString(ns.Name) { continue } - log.Printf("Namespace %s MATCHED regex %s", ns.Name, regex) + if !oldEnough { + continue + } + log.Printf("Namespace %s MATCHED regex %s and is old enough %v", ns.Name, regex, age) output = append(output, ns) } } @@ -58,12 +65,13 @@ func (n NamespacedNameSet) Values() []types.NamespacedName { return output } -func getReleases(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*regexp.Regexp) ([]types.NamespacedName, error) { - namespaces, err := getNamespaces(ctx, clnt) +func getReleases(ctx context.Context, clnt *kubernetes.Clientset, minAge time.Duration, regexs ...*regexp.Regexp) ([]types.NamespacedName, error) { + namespaces, err := getNamespaces(ctx, clnt, minAge) if err != nil { return nil, err } output := NamespacedNameSet(map[types.NamespacedName]struct{}{}) + now := time.Now() for _, ns := range namespaces { releases, err := clnt.CoreV1().Secrets(ns.GetName()).List(ctx, metav1.ListOptions{FieldSelector: "type=helm.sh/release.v1"}) if err != nil { @@ -72,10 +80,12 @@ func getReleases(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*reg for _, release := range releases.Items { name := release.GetLabels()["name"] nsn := types.NamespacedName{Namespace: ns.GetName(), Name: name} + age := now.Sub(release.GetCreationTimestamp().Time) + oldEnough := age > minAge || minAge == 0 if _, found := output[nsn]; found { continue } - if len(regexs) == 0 { + if len(regexs) == 0 && oldEnough { output[nsn] = struct{}{} continue } @@ -83,7 +93,10 @@ func getReleases(ctx context.Context, clnt *kubernetes.Clientset, regexs ...*reg if len(name) > 0 && !regex.MatchString(name) { continue } - log.Printf("Release %s MATCHED regex %s", name, regex) + if !oldEnough { + continue + } + log.Printf("Release %s MATCHED regex %s and is old enough %v", name, regex, age) output[nsn] = struct{}{} } } From e3d7b66f0db0c80ee895a93926ec69c2e14b4c76 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sun, 13 Apr 2025 21:51:52 +0200 Subject: [PATCH 4/5] chore: setup docker image --- cleanup-renku-ci-deployments/Dockerfile | 16 +++- cleanup-renku-ci-deployments/README.md | 15 +--- cleanup-renku-ci-deployments/entrypoint.sh | 88 ---------------------- cleanup-renku-ci-deployments/go.mod | 2 +- 4 files changed, 14 insertions(+), 107 deletions(-) delete mode 100755 cleanup-renku-ci-deployments/entrypoint.sh diff --git a/cleanup-renku-ci-deployments/Dockerfile b/cleanup-renku-ci-deployments/Dockerfile index f5ab015..70873e7 100644 --- a/cleanup-renku-ci-deployments/Dockerfile +++ b/cleanup-renku-ci-deployments/Dockerfile @@ -1,4 +1,12 @@ -FROM alpine/k8s:1.21.2 -RUN apk add --no-cache jq -COPY entrypoint.sh / -ENTRYPOINT sh /entrypoint.sh +FROM golang:1.24 AS build + +WORKDIR /go/src/app +COPY go.mod go.sum ./ +RUN go mod download +COPY *.go ./ +RUN go vet -v +RUN CGO_ENABLED=0 go build -o /go/bin/app + +FROM gcr.io/distroless/static-debian12 +COPY --from=build /go/bin/app / +ENTRYPOINT ["/app"] diff --git a/cleanup-renku-ci-deployments/README.md b/cleanup-renku-ci-deployments/README.md index 5ff7541..aefd7c7 100644 --- a/cleanup-renku-ci-deployments/README.md +++ b/cleanup-renku-ci-deployments/README.md @@ -2,17 +2,4 @@ This action cleans up old CI renku deployments. -The action has the following regex hardcoded for finding the namespaces and helm releases in which it operates: -`.+-ci-.+|^ci-.+`. This is to ensure that it does not affect other deployments that should be more permanent, such -as the main dev deployment or developers' deployments. - -It uses the following parameters, passed in as environment variables: -- `RENKUBOT_KUBECONFIG` (required) - the kubeconfig used to run the `helm` and `kubectl` commands -- `GITLAB_TOKEN` (required) - the Gitlab token used to cleanup the client applications for the CI deployment -- `KUBECONFIG` (optional, defaults to `/.kubeconfig`) - the location where kubeconfig file will be saved -- `HELM_RELEASE_REGEX` (optional, defaults to `.*`) - additional regex to apply on top of the regex used to find CI deployments -- `MAX_AGE_SECONDS` (optional, defaults to 604800 i.e. 1 week) - the age at or after which deployments are deleted, if set to zero (or negative) the deployment is immedidately deleted -- `DELETE_NAMESPACE` (optional, defaults to true) - whether to delete the K8S namespace or not - -The intended use of this action is to be setup as a scheduled workflow in github -that runs periodically and cleans up old CI renku deploments. +To get help run `go run . --help`. Or you can build the image and run the image with `docker run -ti --rm --help`. diff --git a/cleanup-renku-ci-deployments/entrypoint.sh b/cleanup-renku-ci-deployments/entrypoint.sh deleted file mode 100755 index 23539a5..0000000 --- a/cleanup-renku-ci-deployments/entrypoint.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -set -e - -if test -z "$RENKUBOT_KUBECONFIG" ; then - echo 'Please specify a kubeconfig that can be used for the helm and kubectl commands.' - exit 1 -fi - -if test -z "$GITLAB_TOKEN" ; then - echo 'Please specify a GITLAB TOKEN.' - exit 1 -fi - -export KUBECONFIG=${KUBECONFIG:-"$PWD/.kubeconfig"} -HELM_CI_RELEASE_REGEX=".+-ci-.+|^ci-.+" -HELM_RELEASE_REGEX="${HELM_RELEASE_REGEX:=".*"}" -K8S_CI_NAMESPACE_REGEX=".+-ci-.+|^ci-.+" -MAX_AGE_SECONDS=${MAX_AGE_SECONDS:=604800} -GITLAB_URL="https://gitlab.dev.renku.ch" -DELETE_NAMESPACE=${DELETE_NAMESPACE:="false"} - -echo "$RENKUBOT_KUBECONFIG" > "$KUBECONFIG" && chmod 400 "$KUBECONFIG" -echo "Kubeconfig is at $KUBECONFIG." -KUBECONFIG_LINES=$(wc -l $KUBECONFIG) -echo "Kubeconfig is $KUBECONFIG_LINES long." -echo "GitLab URL is $GITLAB_URL" -echo "Looking for CI releases with regex $HELM_RELEASE_REGEX." -echo "Looking in namespaces with regex $K8S_CI_NAMESPACE_REGEX." -echo "Age threshold for deletion is $MAX_AGE_SECONDS seconds." -echo "Delete namespace: $DELETE_NAMESPACE" - -NOW=$(date +%s) -NAMESPACES=$(kubectl get namespaces -o json | jq -cr ".items | map(select(.metadata.name | test(\"$K8S_CI_NAMESPACE_REGEX\"))) | .[].metadata.name") -for NAMESPACE in $NAMESPACES -do - RELEASES=$(helm list -n $NAMESPACE --all -f "$HELM_CI_RELEASE_REGEX" -o json | jq -cr "map(select(.name | test(\"$HELM_RELEASE_REGEX\"))) | .[].name") - for RELEASE in $RELEASES - do - if [[ ! -z $RELEASE ]] && [[ ! -z $NAMESPACE ]] - then - echo "Checking release $RELEASE in namespace $NAMESPACE." - # extract last deployed date and convert to unix epoch - LAST_DEPLOYED_AT=$(helm -n $NAMESPACE history $RELEASE -o json | jq -r 'last | .updated | sub("\\.[0-9]{6}.*$"; "Z") | fromdateiso8601') - AGE_SECONDS=$(expr $NOW - $LAST_DEPLOYED_AT) - if [[ $AGE_SECONDS -ge $MAX_AGE_SECONDS ]] || [[ $MAX_AGE_SECONDS -le 0 ]] - then - # remove any jupyterservers - they have finalizers that prevent the namespces to be deleted - echo "Deleting all JupyterServers in namespace $NAMESPACE." - kubectl -n $NAMESPACE delete --all --wait --cascade="foreground" jupyterserver - # remove any amaltheasessions - they have finalizers that prevent the namespces to be deleted - echo "Deleting all AmaltheaSessions in namespace $NAMESPACE." - kubectl -n $NAMESPACE delete --all --wait --cascade="foreground" amaltheasession - # remove the gitlab app - APPS=$(curl -s ${GITLAB_URL}/api/v4/applications -H "private-token: ${GITLAB_TOKEN}" | jq -r ".[] | select(.application_name == \"${RELEASE}\") | .id") - for APP in $APPS - do - echo "Deleting Gitlab application client $APP." - curl -X DELETE ${GITLAB_URL}/api/v4/applications/${APP} -H "private-token: ${GITLAB_TOKEN}" - done - # delete the helm chart - echo "Deleting release $RELEASE in namespace $NAMESPACE, with age $AGE_SECONDS." - helm -n $NAMESPACE delete $RELEASE - # wait for helm release to be fully deleted - kubectl -n $NAMESPACE get deployments -o json | jq -r '.items | .[].metadata.name' | xargs -r kubectl -n $NAMESPACE wait --for=delete deployment - kubectl -n $NAMESPACE get statefulsets -o json | jq -r '.items | .[].metadata.name' | xargs -r kubectl -n $NAMESPACE wait --for=delete statefulset - # remove the namespace if required - if [ "$DELETE_NAMESPACE" = "true" ] - then - echo "Deleting namespace $NAMESPACE" - kubectl delete namespace $NAMESPACE --wait - fi - else - echo "Release $RELEASE in namespace $NAMESPACE age is $AGE_SECONDS, not >= to $MAX_AGE_SECONDS, skipping." - fi - else - echo "Release $RELEASE and/or $NAMEPSACE are empty." - fi - done - if [[ "$DELETE_NAMESPACE" = "true" ]] && [[ ! -z $NAMESPACE ]] && [[ -z $RELEASES ]] && [[ $NAMESPACE =~ $HELM_RELEASE_REGEX ]] - then - # Remove the namespace if there are no releases in it - # and the namespace name matches the name of the release - all CI deployments follow this pattern. - # This addresses the case when a CI deployment does not persist but its namespace persists, - # so when the PR is closed only the namespace in k8s remains and should be cleaned up. - echo "Deleting namespace $NAMESPACE" - kubectl delete namespace $NAMESPACE --wait - fi -done diff --git a/cleanup-renku-ci-deployments/go.mod b/cleanup-renku-ci-deployments/go.mod index f7da23d..a187521 100644 --- a/cleanup-renku-ci-deployments/go.mod +++ b/cleanup-renku-ci-deployments/go.mod @@ -7,6 +7,7 @@ require ( k8s.io/api v0.32.3 k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.32.3 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 ) require ( @@ -44,7 +45,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect From e6cf75b98d0b8a897e78936890b0ad1fd4a628c2 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sun, 13 Apr 2025 22:30:01 +0200 Subject: [PATCH 5/5] chore: modify action.yaml --- cleanup-renku-ci-deployments/action.yml | 47 +++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/cleanup-renku-ci-deployments/action.yml b/cleanup-renku-ci-deployments/action.yml index a443435..dd24e88 100644 --- a/cleanup-renku-ci-deployments/action.yml +++ b/cleanup-renku-ci-deployments/action.yml @@ -1,8 +1,43 @@ -name: 'cleanup-renku-ci-deployments' -description: 'Cleanup old Renku CI deployments' +name: "cleanup-renku-ci-deployments" +description: "Cleanup old Renku CI deployments. Things will be deleted if the regex matches namespace OR releases regex." runs: - using: 'docker' - image: 'Dockerfile' + using: "docker" + image: "Dockerfile" + args: + - rm + - "--kubeconfig=${{ inputs.kubeconfig }}" + - "--min-age=${{ inputs.minAge }}" + - "--release-regex=${{ inputs.releaseRegex }}" + - "--namespace-regex=${{ inputs.namespaceRegex }}" + - "--gitlab-token=${{ inputs.gitlabToken }}" + - "--gitlab-url=${{ inputs.gitlabUrl }}" branding: - icon: 'activity' - color: 'blue' + icon: "activity" + color: "blue" +inputs: + kubeconfig: + description: "The path to the kubeconfig file to use." + required: false + default: "/kubeconfig" + minAge: + description: | + The minimum age of a helm release or namespace used to determine if it should be deleted. + Parsed by golang time.ParseDuration(). Allowed units are h, m, s, ns, us, ms. Setting this + to zero will result in essentially no filtering by age being done. + required: false + default: "168h" + releaseRegex: + description: "The regex used to filter Helm releases, must parse to egrep (POSIX ERE) regex." + required: false + default: "^.+-ci-.+|^ci-.+" + namespaceRegex: + description: "The regex used to filter namespaces, must parse to egrep (POSIX ERE) regex." + required: false + default: "^.+-ci-.+|^ci-.+" + gitlabToken: + description: "The token to autheticate with Gitlab so that the OIDC applications can be deleted." + required: true + gitlabURL: + description: "The url for Gitlab." + required: false + default: "https://gitlab.dev.renku.ch"