diff --git a/README.md b/README.md index 351dff3169..cfde632183 100644 --- a/README.md +++ b/README.md @@ -1056,6 +1056,20 @@ k9s: --- +## Custom Workload View + +You can customize the workload view with CRDs or any resources you want to see on this view. + +To do so, you can go to the `workloadGVR` view, you'll be able to see all your custom GVRs. You can also create, edit, delete them. + +You can also describe by pressing `d` or simulate them by pressing `enter`. + +You can create new one from this view, this will ask you for a custom GVR name and will set default values (to comment or uncomment). + +There is a way to add a custom GVR to you cluster context or to delete them, they will be added on top of the default workloads GVRS. + +--- + ## Contributors Without the contributions from these fine folks, this project would be a total dud! diff --git a/go.mod b/go.mod index 0ecd9be213..b69dbb26fe 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.23.0 require ( github.com/adrg/xdg v0.5.3 - github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10 - github.com/anchore/grype v0.84.0 - github.com/anchore/syft v1.16.0 + github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 + github.com/anchore/grype v0.85.0 + github.com/anchore/syft v1.17.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 @@ -26,18 +26,18 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/text v0.19.0 + golang.org/x/text v0.20.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.16.2 + helm.sh/helm/v3 v3.16.3 k8s.io/api v0.31.3 k8s.io/apiextensions-apiserver v0.31.3 k8s.io/apimachinery v0.31.3 k8s.io/cli-runtime v0.31.3 k8s.io/client-go v0.31.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.31.1 - k8s.io/metrics v0.31.2 + k8s.io/kubectl v0.31.3 + k8s.io/metrics v0.31.3 sigs.k8s.io/yaml v1.4.0 ) @@ -61,7 +61,8 @@ require ( github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/ProtonMail/go-crypto v1.1.2 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 // indirect github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect @@ -70,7 +71,7 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f // indirect - github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3 // indirect + github.com/anchore/stereoscope v0.0.9 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect @@ -87,7 +88,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/ansi v0.4.2 // indirect + github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/cloudflare/circl v1.3.8 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd v1.7.23 // indirect @@ -100,7 +101,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/containerd/ttrpc v1.2.5 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cyphar/filepath-securejoin v0.3.1 // indirect + github.com/cyphar/filepath-securejoin v0.3.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect github.com/distribution/reference v0.6.0 // indirect @@ -125,7 +126,7 @@ require ( github.com/felixge/fgprof v0.9.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gdamore/encoding v1.0.1 // indirect github.com/github/go-spdx/v2 v2.3.2 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect @@ -253,7 +254,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/saferwall/pe v1.5.4 // indirect + github.com/saferwall/pe v1.5.5 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect @@ -271,7 +272,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/sylabs/sif/v2 v2.19.2 // indirect + github.com/sylabs/sif/v2 v2.20.0 // indirect github.com/sylabs/squashfs v1.0.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/ulikunitz/xz v0.5.12 // indirect @@ -297,14 +298,14 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.171.0 // indirect @@ -325,7 +326,7 @@ require ( modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.33.1 // indirect + modernc.org/sqlite v1.34.1 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect diff --git a/go.sum b/go.sum index c2011531aa..ac68a97af3 100644 --- a/go.sum +++ b/go.sum @@ -228,8 +228,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0= +github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= @@ -240,8 +240,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10 h1:3xmanFdoQEH0REvPA+gLm3Km0/981F4z2a/7ADTlv8k= -github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10/go.mod h1:h6Ly2hlKjQoPtI3rA8oB5afSmB/XimhcY55xbuW4Dwo= +github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 h1:bIG3WsfosZsJ5LMC7PB9J/ekFM3a0j0ZEDvN3ID6GTI= +github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837/go.mod h1:tRQVKkjYeejrh9AdM0s1esbwtMU7rdHAHSQWkv4qskE= github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 h1:h7+GCqazHVS5GDJYYS6wjjglYi8xFnVWMdSUukoImTM= github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77/go.mod h1:qbev5czQeyDO74fPNThiEKYkgt0mx1axb+5wQcxDPFY= github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q= @@ -256,14 +256,14 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= -github.com/anchore/grype v0.84.0 h1:3BwhY+ctiNBmdGd53R36xw5ndC7WhSSKEc0fqaAErw0= -github.com/anchore/grype v0.84.0/go.mod h1:CQQGcLEJBaQ8b5x3ut3T4WoO0KHnjKMFHrh5J6c4Fh4= +github.com/anchore/grype v0.85.0 h1:+zcwBieBxQqp5sjfhblEsBPEsvMPpsWDxnX18GDB1H4= +github.com/anchore/grype v0.85.0/go.mod h1:8+byyl7POwrm6D/rya93DIZ70+vnWLVe+nSBmQ/wnoc= github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f h1:dAQPIrQ3a5PBqZeZ+B9NGZsGmodk4NO9OjDIsQmQyQM= github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= -github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3 h1:T1LMkKwzSg1s8+xylq51xroPoo83Nt3zQPvdRRDB9Bw= -github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3/go.mod h1:jgLIzIwEkfMhjhKmuzaSEeU2/vFCqlO3XTrSeSLfOaM= -github.com/anchore/syft v1.16.0 h1:iHPqE2q7gmvRDdmh5/897ycRbetfmLwor17/YBNVQNw= -github.com/anchore/syft v1.16.0/go.mod h1:x8JNItb+Dj3xwG1tRfyCbJj9Xl/vlcBfXz7q3M2GmjA= +github.com/anchore/stereoscope v0.0.9 h1:rLhYWe/CXhDq/UCUWQ3U5xtpGk4RFnssKaM0bHhs5us= +github.com/anchore/stereoscope v0.0.9/go.mod h1:c2oGDU0R+llJObsatBSenjYPV1raKhMq9GEqe8J56EI= +github.com/anchore/syft v1.17.0 h1:Ghi7nKPsYSqy9pCRbGyQkxbs5s1xbyb3gN98Ile+z/s= +github.com/anchore/syft v1.17.0/go.mod h1:vrsnY0NKCYAPdpS/LDmiIZUo8soCq+uxICk3p0GUG2U= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -318,7 +318,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -332,14 +331,14 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc= -github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E= +github.com/charmbracelet/bubbletea v1.2.3 h1:d9MdMsANIYZB5pE1KkRqaUV6GfsiWm+/9z4fTuGVm9I= +github.com/charmbracelet/bubbletea v1.2.3/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= -github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= +github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= +github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -347,7 +346,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -392,8 +390,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE= -github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= +github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= +github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= 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= @@ -491,8 +489,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= -github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1048,8 +1046,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/saferwall/pe v1.5.4 h1:tLmMggEMUfeqrpJ25zS/okUQmyFdD5xWKL2+z9njCqg= -github.com/saferwall/pe v1.5.4/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ= +github.com/saferwall/pe v1.5.5 h1:GGbzKjXDm7i+1K6riOgtgblyTdRmTbr3r11IzjovAK8= +github.com/saferwall/pe v1.5.5/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -1128,8 +1126,8 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/sylabs/sif/v2 v2.19.2 h1:KKcUKnbnT69rN1WWHRYoAVKFpqnXpNJ36kmQLpp86Uc= -github.com/sylabs/sif/v2 v2.19.2/go.mod h1:nhX6D/CJntHDWspNLXLe+yct0cd5lm8HJ7VIW6hgKrw= +github.com/sylabs/sif/v2 v2.20.0 h1:RfDHEltUrchZbp/XGcWaw3nRSbufoNWqvwmf91/Q2gI= +github.com/sylabs/sif/v2 v2.20.0/go.mod h1:z6dq3B7QXK0pD71n15kAapven+gE+PZAIPOewBTNDpU= github.com/sylabs/squashfs v1.0.0 h1:xAyMS21ogglkuR5HaY55PCfqY3H32ma9GkasTYo28Zg= github.com/sylabs/squashfs v1.0.0/go.mod h1:rhWzvgefq1X+R+LZdts10hfMsTg3g74OfGunW8tvg/4= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= @@ -1249,10 +1247,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1293,8 +1289,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1352,11 +1348,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -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/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1400,8 +1394,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1494,22 +1488,18 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -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/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1521,11 +1511,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1863,8 +1852,8 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.16.2 h1:Y9v7ry+ubQmi+cb5zw1Llx8OKHU9Hk9NQ/+P+LGBe2o= -helm.sh/helm/v3 v3.16.2/go.mod h1:SyTXgKBjNqi2NPsHCW5dDAsHqvGIu0kdNYNH9gQaw70= +helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= +helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1890,10 +1879,10 @@ 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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.31.1 h1:ih4JQJHxsEggFqDJEHSOdJ69ZxZftgeZvYo7M/cpp24= -k8s.io/kubectl v0.31.1/go.mod h1:aNuQoR43W6MLAtXQ/Bu4GDmoHlbhHKuyD49lmTC8eJM= -k8s.io/metrics v0.31.2 h1:sQhujR9m3HN/Nu/0fTfTscjnswQl0qkQAodEdGBS0N4= -k8s.io/metrics v0.31.2/go.mod h1:QqqyReApEWO1UEgXOSXiHCQod6yTxYctbAAQBWZkboU= +k8s.io/kubectl v0.31.3 h1:3r111pCjPsvnR98oLLxDMwAeM6OPGmPty6gSKaLTQes= +k8s.io/kubectl v0.31.3/go.mod h1:lhMECDCbJN8He12qcKqs2QfmVo9Pue30geovBVpH5fs= +k8s.io/metrics v0.31.3 h1:DkT9I3gFlb2/z+/4BMY7WrQ/PnbukuV4Yli82v/KBCM= +k8s.io/metrics v0.31.3/go.mod h1:2w9gpd8z+13oJmaPR6p3kDyrDqnxSyoKpnOw2qLIdhI= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= @@ -1916,8 +1905,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk= +modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/config/alias.go b/internal/config/alias.go index 426d41d76f..a1f59f8a99 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -31,7 +31,7 @@ type Aliases struct { // NewAliases return a new alias. func NewAliases() *Aliases { return &Aliases{ - Alias: make(Alias, 50), + Alias: make(Alias, 56), } } @@ -190,6 +190,7 @@ func (a *Aliases) loadDefaultAliases() { a.declare("pulses", "pulse", "pu", "hz") a.declare("xrays", "xray", "x") a.declare("workloads", "workload", "wk") + a.declare("workloadgvrs", "workloadgvr", "wkg") } // Save alias to disk. diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index c67f4f5824..f3fa33eecc 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -111,7 +111,7 @@ func TestAliasesLoad(t *testing.T) { a := config.NewAliases() assert.Nil(t, a.Load(path.Join(config.AppConfigDir, "plain.yaml"))) - assert.Equal(t, 54, len(a.Alias)) + assert.Equal(t, 57, len(a.Alias)) } func TestAliasesSave(t *testing.T) { diff --git a/internal/config/config.go b/internal/config/config.go index a5da1dfa89..110b8da7a3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -53,6 +53,21 @@ func (c *Config) ContextAliasesPath() string { return AppContextAliasesFile(ct.GetClusterName(), c.K9s.activeContextName) } +// ContextWorkloadPath returns a context specific workload file spec. +func (c *Config) ContextWorkloadPath() string { + ct, err := c.K9s.ActiveContext() + if err != nil { + return "" + } + + return AppContextWorkloadFile(ct.GetClusterName(), c.K9s.activeContextName) +} + +// ContextWorkloadDir returns the workload directory (which contains the custom GVRs) +func (c *Config) ContextWorkloadDir() string { + return AppWorkloadsDir() +} + // ContextPluginsPath returns a context specific plugins file spec. func (c *Config) ContextPluginsPath() (string, error) { ct, err := c.K9s.ActiveContext() diff --git a/internal/config/files.go b/internal/config/files.go index 2b246d5172..6aecc3aec8 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -200,11 +200,21 @@ func AppContextDir(cluster, context string) string { return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context)) } +// AppWorkloadsDir generates a valid workload folder path +func AppWorkloadsDir() string { + return filepath.Join(AppContextsDir, "workloads") +} + // AppContextAliasesFile generates a valid context specific aliases file path. func AppContextAliasesFile(cluster, context string) string { return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "aliases.yaml") } +// AppContextWorkloadFile generates a valid context specific workload file path. +func AppContextWorkloadFile(cluster, context string) string { + return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "workloads.yaml") +} + // AppContextPluginsFile generates a valid context specific plugins file path. func AppContextPluginsFile(cluster, context string) string { return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "plugins.yaml") diff --git a/internal/config/workload.go b/internal/config/workload.go new file mode 100644 index 0000000000..e1c740b824 --- /dev/null +++ b/internal/config/workload.go @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "errors" + "fmt" + "os" + "path" + + "github.com/derailed/k9s/internal/client" + "gopkg.in/yaml.v2" +) + +var ( + // Template represents the template of new workload gvr + Template = []byte(`name: "test.com/v1alpha1/myCRD" +status: + cellName: "Status" + # na: true +readiness: + cellName: "Current" + # The cellExtraName will be shown as cellName/cellExtraName + cellExtraName: "Desired" + # na: true +validity: + replicas: + cellCurrentName: "Current" + cellDesiredName: "Desired" + # cellAllName: "Ready" + matchs: + - cellName: "State" + cellValue: "Ready"`) +) + +var ( + // defaultGvr represent the default values uses if a custom gvr is set without status, validity or readiness + defaultGvr = WorkloadGVR{ + Status: &GVRStatus{CellName: "Status"}, + Validity: &GVRValidity{Matchs: []Match{{CellName: "Ready", Value: "True"}}}, + Readiness: &GVRReadiness{CellName: "Ready"}, + } + + // defaultConfigGVRs represents the default configurations + defaultConfigGVRs = map[string]WorkloadGVR{ + "apps/v1/deployments": { + Name: "apps/v1/deployments", + Readiness: &GVRReadiness{CellName: "Ready"}, + Validity: &GVRValidity{ + Replicas: Replicas{CellAllName: "Ready"}, + }, + }, + "apps/v1/daemonsets": { + Name: "apps/v1/daemonsets", + Readiness: &GVRReadiness{CellName: "Ready", CellExtraName: "Desired"}, + Validity: &GVRValidity{ + Replicas: Replicas{CellDesiredName: "Desired", CellCurrentName: "Ready"}, + }, + }, + "apps/v1/replicasets": { + Name: "apps/v1/replicasets", + Readiness: &GVRReadiness{CellName: "Current", CellExtraName: "Desired"}, + Validity: &GVRValidity{ + Replicas: Replicas{CellDesiredName: "Desired", CellCurrentName: "Current"}, + }, + }, + "apps/v1/statefulSets": { + Name: "apps/v1/statefulSets", + Status: &GVRStatus{CellName: "Ready"}, + Readiness: &GVRReadiness{CellName: "Ready"}, + Validity: &GVRValidity{ + Replicas: Replicas{CellAllName: "Ready"}, + }, + }, + "v1/pods": { + Name: "v1/pods", + Status: &GVRStatus{CellName: "Status"}, + Readiness: &GVRReadiness{CellName: "Ready"}, + Validity: &GVRValidity{ + Matchs: []Match{ + {CellName: "Status", Value: "Running"}, + }, + Replicas: Replicas{CellAllName: "Ready"}, + }, + }, + } +) + +type CellName string + +type GVRStatus struct { + NA bool `json:"na" yaml:"na"` + CellName CellName `json:"cellName" yaml:"cellName"` +} + +type GVRReadiness struct { + NA bool `json:"na" yaml:"na"` + CellName CellName `json:"cellName" yaml:"cellName"` + CellExtraName CellName `json:"cellExtraName" yaml:"cellExtraName"` +} + +type Match struct { + CellName CellName `json:"cellName" yaml:"cellName"` + Value string `json:"cellValue" yaml:"cellValue"` +} + +type Replicas struct { + CellCurrentName CellName `json:"cellCurrentName" yaml:"cellCurrentName"` + CellDesiredName CellName `json:"cellDesiredName" yaml:"cellDesiredName"` + CellAllName CellName `json:"cellAllName" yaml:"cellAllName"` +} + +type GVRValidity struct { + NA bool `json:"na" yaml:"na"` + Matchs []Match `json:"matchs,omitempty" yaml:"matchs,omitempty"` + Replicas Replicas `json:"replicas" yaml:"replicas"` +} + +type WorkloadGVR struct { + Name string `json:"name" yaml:"name"` + Status *GVRStatus `json:"status,omitempty" yaml:"status,omitempty"` + Readiness *GVRReadiness `json:"readiness,omitempty" yaml:"readiness,omitempty"` + Validity *GVRValidity `json:"validity,omitempty" yaml:"validity,omitempty"` +} + +type WorkloadConfig struct { + GVRFilenames []string `yaml:"wkg"` +} + +// NewWorkloadGVRs returns the default GVRs to use if no custom config is set +// The workloadDir represent the directory of the custom workloads, the gvrNames are the custom gvrs names +func NewWorkloadGVRs(workloadDir string, gvrNames []string) ([]WorkloadGVR, error) { + workloadGVRs := make([]WorkloadGVR, 0) + for _, gvr := range defaultConfigGVRs { + workloadGVRs = append(workloadGVRs, gvr) + } + + var errs error + + // Append custom GVRS + if len(gvrNames) != 0 { + for _, filename := range gvrNames { + wkgvr, err := GetWorkloadGVRFromFile(path.Join(workloadDir, fmt.Sprintf("%s.%s", filename, "yaml"))) + if err != nil { + errs = errors.Join(errs, err) + continue + } + workloadGVRs = append(workloadGVRs, wkgvr) + } + } + + return workloadGVRs, errs +} + +// GetWorkloadGVRFromFile returns a gvr from a filepath +func GetWorkloadGVRFromFile(filepath string) (WorkloadGVR, error) { + yamlFile, err := os.ReadFile(filepath) + if err != nil { + return WorkloadGVR{}, err + } + + var wkgvr WorkloadGVR + if err = yaml.Unmarshal(yamlFile, &wkgvr); err != nil { + return WorkloadGVR{}, err + } + + return wkgvr, nil +} + +// GetGVR will return the GVR defined by the WorkloadGVR's name +func (wgvr WorkloadGVR) GetGVR() client.GVR { + return client.NewGVR(wgvr.Name) +} + +// ApplyDefault will complete the GVR with missing values +// If it's an existing GVR's name, it will apply their corresponding default values +// If it's an unknown resources without readiness, status or validity it will use the default ones +func (wkgvr *WorkloadGVR) ApplyDefault() { + // Apply default values + existingGvr, ok := defaultConfigGVRs[wkgvr.Name] + if ok { + wkgvr.applyDefaultValues(existingGvr) + } else { + wkgvr.applyDefaultValues(defaultGvr) + } +} + +func (wkgvr *WorkloadGVR) applyDefaultValues(defaultGVR WorkloadGVR) { + if wkgvr.Status == nil { + wkgvr.Status = defaultGVR.Status + } + + if wkgvr.Readiness == nil { + wkgvr.Readiness = defaultGVR.Readiness + } + + if wkgvr.Validity == nil { + wkgvr.Validity = defaultGVR.Validity + } +} diff --git a/internal/dao/registry.go b/internal/dao/registry.go index f512bc60cd..20fbe51a4b 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -66,6 +66,7 @@ func NewMeta() *Meta { func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { m := Accessors{ client.NewGVR("workloads"): &Workload{}, + client.NewGVR("workloadgvrs"): &WorkloadGVR{}, client.NewGVR("contexts"): &Context{}, client.NewGVR("containers"): &Container{}, client.NewGVR("scans"): &ImageScan{}, @@ -214,6 +215,14 @@ func loadK9s(m ResourceMetas) { ShortNames: []string{"wk"}, Categories: []string{k9sCat}, } + m[client.NewGVR("workloadgvrs")] = metav1.APIResource{ + Name: "workloadgvrs", + Kind: "Workloadgvr", + SingularName: "workloadgvr", + Namespaced: true, + ShortNames: []string{"wkg"}, + Categories: []string{k9sCat}, + } m[client.NewGVR("pulses")] = metav1.APIResource{ Name: "pulses", Kind: "Pulse", diff --git a/internal/dao/workload.go b/internal/dao/workload.go index 604c6ca9ad..f943185742 100644 --- a/internal/dao/workload.go +++ b/internal/dao/workload.go @@ -13,6 +13,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/meta" @@ -21,23 +22,22 @@ import ( ) const ( - StatusOK = "OK" DegradedStatus = "DEGRADED" + NotAvailable = "n/a" ) var ( - SaGVR = client.NewGVR("v1/serviceaccounts") - PvcGVR = client.NewGVR("v1/persistentvolumeclaims") - PcGVR = client.NewGVR("scheduling.k8s.io/v1/priorityclasses") - CmGVR = client.NewGVR("v1/configmaps") - SecGVR = client.NewGVR("v1/secrets") - PodGVR = client.NewGVR("v1/pods") - SvcGVR = client.NewGVR("v1/services") - DsGVR = client.NewGVR("apps/v1/daemonsets") - StsGVR = client.NewGVR("apps/v1/statefulSets") - DpGVR = client.NewGVR("apps/v1/deployments") - RsGVR = client.NewGVR("apps/v1/replicasets") - resList = []client.GVR{PodGVR, SvcGVR, DsGVR, StsGVR, DpGVR, RsGVR} + SaGVR = client.NewGVR("v1/serviceaccounts") + PvcGVR = client.NewGVR("v1/persistentvolumeclaims") + PcGVR = client.NewGVR("scheduling.k8s.io/v1/priorityclasses") + CmGVR = client.NewGVR("v1/configmaps") + SecGVR = client.NewGVR("v1/secrets") + PodGVR = client.NewGVR("v1/pods") + SvcGVR = client.NewGVR("v1/services") + DsGVR = client.NewGVR("apps/v1/daemonsets") + StsGVR = client.NewGVR("apps/v1/statefulSets") + DpGVR = client.NewGVR("apps/v1/deployments") + RsGVR = client.NewGVR("apps/v1/replicasets") ) // Workload tracks a select set of resources in a given namespace. @@ -80,6 +80,62 @@ func (w *Workload) Delete(ctx context.Context, path string, propagation *metav1. return dial.Namespace(ns).Delete(ctx, n, opts) } +// List fetch workloads. +func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error) { + oo := make([]runtime.Object, 0, 100) + + workloadGVRs, _ := ctx.Value(internal.KeyWorkloadGVRs).([]config.WorkloadGVR) + for i, wkgvr := range workloadGVRs { + // Apply default values + workloadGVRs[i].ApplyDefault() + + table, err := a.fetch(ctx, workloadGVRs[i].GetGVR(), ns) + if err != nil { + log.Warn().Msgf("could not fetch gvr %s: %q", workloadGVRs[i].Name, err) + continue + } + + for _, r := range table.Rows { + ns, ts := a.getNamespaceAndTimestamp(r) + + oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []interface{}{ + workloadGVRs[i].GetGVR().String(), + ns, + r.Cells[indexOf("Name", table.ColumnDefinitions)], + a.getStatus(wkgvr, table.ColumnDefinitions, r.Cells), + a.getReadiness(wkgvr, table.ColumnDefinitions, r.Cells), + a.getValidity(wkgvr, table.ColumnDefinitions, r.Cells), + ts, + }}}) + } + } + + return oo, nil +} + +// getNamespaceAndTimestamp will retrieve the namespace and the timestamp of a given resource +func (a *Workload) getNamespaceAndTimestamp(r metav1.TableRow) (string, metav1.Time) { + var ( + ns string + ts metav1.Time + ) + + if obj := r.Object.Object; obj != nil { + if m, err := meta.Accessor(obj); err == nil { + ns = m.GetNamespace() + ts = m.GetCreationTimestamp() + } + } else { + var m metav1.PartialObjectMetadata + if err := json.Unmarshal(r.Object.Raw, &m); err == nil { + ns = m.GetNamespace() + ts = m.CreationTimestamp + } + } + + return ns, ts +} + func (a *Workload) fetch(ctx context.Context, gvr client.GVR, ns string) (*metav1.Table, error) { a.Table.gvr = gvr oo, err := a.Table.List(ctx, ns) @@ -97,106 +153,136 @@ func (a *Workload) fetch(ctx context.Context, gvr client.GVR, ns string) (*metav return tt, nil } -// List fetch workloads. -func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error) { - oo := make([]runtime.Object, 0, 100) - for _, gvr := range resList { - table, err := a.fetch(ctx, gvr, ns) - if err != nil { - return nil, err - } - var ( - ns string - ts metav1.Time - ) - for _, r := range table.Rows { - if obj := r.Object.Object; obj != nil { - if m, err := meta.Accessor(obj); err == nil { - ns = m.GetNamespace() - ts = m.GetCreationTimestamp() - } - } else { - var m metav1.PartialObjectMetadata - if err := json.Unmarshal(r.Object.Raw, &m); err == nil { - ns = m.GetNamespace() - ts = m.CreationTimestamp - } - } - stat := status(gvr, r, table.ColumnDefinitions) - oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []interface{}{ - gvr.String(), - ns, - r.Cells[indexOf("Name", table.ColumnDefinitions)], - stat, - readiness(gvr, r, table.ColumnDefinitions), - validity(stat), - ts, - }}}) - } +// getStatus will retrieve the status of the resource depending of it's configuration +func (wk *Workload) getStatus(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + status := NotAvailable + + if wkgvr.Status == nil || wkgvr.Status.NA { + return status } - return oo, nil + if statusIndex := indexOf(string(wkgvr.Status.CellName), cd); statusIndex != -1 { + status = valueToString(cells[statusIndex]) + + } + + return status } -// Helpers... - -func readiness(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { - switch gvr { - case PodGVR, DpGVR, StsGVR: - return r.Cells[indexOf("Ready", h)].(string) - case RsGVR, DsGVR: - c := r.Cells[indexOf("Ready", h)].(int64) - d := r.Cells[indexOf("Desired", h)].(int64) - return fmt.Sprintf("%d/%d", c, d) - case SvcGVR: +// getReadiness will retrieve the readiness of the resource depending of it's configuration +func (wk *Workload) getReadiness(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + ready := NotAvailable + + if wkgvr.Readiness == nil || wkgvr.Readiness.NA { + return ready + } + + if readyIndex := indexOf(string(wkgvr.Readiness.CellName), cd); readyIndex != -1 { + ready = valueToString(cells[readyIndex]) + } + + if extrReadyIndex := indexOf(string(wkgvr.Readiness.CellExtraName), cd); extrReadyIndex != -1 { + ready = fmt.Sprintf("%s/%s", ready, valueToString(cells[extrReadyIndex])) + } + + return ready +} + +// getValidity will retrieve the validity of the resource depending of it's configuration +func (wk *Workload) getValidity(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + if wkgvr.Validity == nil || wkgvr.Validity.NA { return "" } - return render.NAValue + if validity := getMatchesValidity(wkgvr, cd, cells); validity == DegradedStatus { + return DegradedStatus + } + + if validity := getReplicasValidity(wkgvr, cd, cells); validity == DegradedStatus { + return DegradedStatus + } + + return "" } -func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { - switch gvr { - case PodGVR: - if status := r.Cells[indexOf("Status", h)]; status == render.PhaseCompleted { - return StatusOK - } else if !isReady(r.Cells[indexOf("Ready", h)].(string)) || status != render.PhaseRunning { - return DegradedStatus +// getMatchesValidity retrieve the validity depending if all the matches are fullfiled or not +func getMatchesValidity(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + for _, m := range wkgvr.Validity.Matchs { + v := "" + if matchCellNameIndex := indexOf(string(m.CellName), cd); matchCellNameIndex != -1 { + v = valueToString(cells[matchCellNameIndex]) } - case DpGVR, StsGVR: - if !isReady(r.Cells[indexOf("Ready", h)].(string)) { + + if v != m.Value { return DegradedStatus } - case RsGVR, DsGVR: - rd, ok1 := r.Cells[indexOf("Ready", h)].(int64) - de, ok2 := r.Cells[indexOf("Desired", h)].(int64) - if ok1 && ok2 { - if !isReady(fmt.Sprintf("%d/%d", rd, de)) { - return DegradedStatus - } - break - } - rds, oks1 := r.Cells[indexOf("Ready", h)].(string) - des, oks2 := r.Cells[indexOf("Desired", h)].(string) - if oks1 && oks2 { - if !isReady(fmt.Sprintf("%s/%s", rds, des)) { - return DegradedStatus - } - } - case SvcGVR: - default: - return render.MissingValue + + } + + return "" +} + +// getReplicasValidity returns the validity corresponding of the replicas from 2 cells or a single one +func getReplicasValidity(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + if getReplicasGrouped(wkgvr, cd, cells) == DegradedStatus { + return DegradedStatus + } + + if getReplicasSeparated(wkgvr, cd, cells) == DegradedStatus { + return DegradedStatus } - return StatusOK + return "" } -func validity(status string) string { - if status != "DEGRADED" { +// getReplicasGrouped returns the validity corresponding of the replicas from one cell +func getReplicasGrouped(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + if wkgvr.Validity.Replicas.CellAllName == "" { return "" } - return status + allCellNameIndex := indexOf(string(wkgvr.Validity.Replicas.CellAllName), cd) + if allCellNameIndex < 0 { + return "" + } + + if !isReady(valueToString(cells[allCellNameIndex])) { + return DegradedStatus + } + + return "" +} + +// getReplicasSeparated returns the validity corresponding of the replicas from 2 cells (current/desired) +func getReplicasSeparated(wkgvr config.WorkloadGVR, cd []metav1.TableColumnDefinition, cells []interface{}) string { + if wkgvr.Validity.Replicas.CellCurrentName == "" || wkgvr.Validity.Replicas.CellDesiredName == "" { + return "" + } + + currentIndex := indexOf(string(wkgvr.Validity.Replicas.CellCurrentName), cd) + desiredIndex := indexOf(string(wkgvr.Validity.Replicas.CellDesiredName), cd) + + if currentIndex < 0 || desiredIndex < 0 { + return "" + } + + if !isReady(fmt.Sprintf("%s/%s", valueToString(cells[desiredIndex]), valueToString(cells[currentIndex]))) { + return DegradedStatus + } + + return "" +} + +func valueToString(v interface{}) string { + if sv, ok := v.(string); ok { + return sv + } + + if iv, ok := v.(int64); ok { + return strconv.Itoa(int(iv)) + } + + return "" } func isReady(s string) bool { @@ -222,6 +308,10 @@ func isReady(s string) bool { } func indexOf(n string, defs []metav1.TableColumnDefinition) int { + if n == "" { + return -1 + } + for i, d := range defs { if d.Name == n { return i diff --git a/internal/dao/workloadGVR.go b/internal/dao/workloadGVR.go new file mode 100644 index 0000000000..efcd1d6730 --- /dev/null +++ b/internal/dao/workloadGVR.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/render" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/runtime" +) + +var _ Accessor = (*WorkloadGVR)(nil) + +type WorkloadGVR struct { + NonResource +} + +func NewWorkloadGVR(f Factory) *WorkloadGVR { + a := WorkloadGVR{} + a.Init(f, client.NewGVR("workloadGVR")) + + return &a +} + +// List returns a collection of aliases. +func (a *WorkloadGVR) List(ctx context.Context, _ string) ([]runtime.Object, error) { + workloadsDir, _ := ctx.Value(internal.KeyDir).(string) + clusterContext, _ := ctx.Value(internal.KeyPath).(string) + + // List files from custom workload directory + ff, err := os.ReadDir(workloadsDir) + if err != nil { + return nil, err + } + + // Generate workload list from custom gvrs + oo := make([]runtime.Object, len(ff)) + for i, f := range ff { + if fi, err := f.Info(); err == nil { + oo[i] = render.WorkloadGVRRes{ + Filepath: fi, + InContext: a.isInContext(clusterContext, fi.Name())} + } + } + + return oo, nil +} + +func (a *WorkloadGVR) isInContext(ctxPath, filename string) bool { + // Read cluster context config + content, err := os.ReadFile(ctxPath) + if err != nil { + return false + } + + // Unmarshal cluster config + var config config.WorkloadConfig + if err := yaml.Unmarshal(content, &config); err != nil { + return false + } + + // Check if custom GVR is in context + for _, n := range config.GVRFilenames { + if n == strings.TrimSuffix(filename, filepath.Ext(filename)) { + return true + } + } + + return false + +} + +// Get fetch a resource. +func (a *WorkloadGVR) Get(_ context.Context, _ string) (runtime.Object, error) { + return nil, errors.New("nyi") +} diff --git a/internal/keys.go b/internal/keys.go index d18bc11d36..1728dc66ac 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -36,4 +36,5 @@ const ( KeyWait ContextKey = "wait" KeyPodCounting ContextKey = "podCounting" KeyEnableImgScan ContextKey = "vulScan" + KeyWorkloadGVRs ContextKey = "workloadGVRs" ) diff --git a/internal/model/registry.go b/internal/model/registry.go index e2129c3bff..9480e68ac0 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -17,6 +17,10 @@ var Registry = map[string]ResourceMeta{ DAO: &dao.Workload{}, Renderer: &render.Workload{}, }, + "workloadgvrs": { + DAO: &dao.WorkloadGVR{}, + Renderer: &render.WorkloadGVR{}, + }, // Custom... "references": { DAO: &dao.Reference{}, diff --git a/internal/render/workloadGVR.go b/internal/render/workloadGVR.go new file mode 100644 index 0000000000..6296cb7d75 --- /dev/null +++ b/internal/render/workloadGVR.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/derailed/k9s/internal/model1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type WorkloadGVR struct { + Base +} + +// Header returns a header row. +func (WorkloadGVR) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NANE"}, + model1.HeaderColumn{Name: "INCONTEXT"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, + } +} + +// Render renders a K8s resource to screen. +func (wgvr WorkloadGVR) Render(o interface{}, ns string, r *model1.Row) error { + res, ok := o.(WorkloadGVRRes) + if !ok { + return fmt.Errorf("expected WorkloadGVRRes, but got %T", o) + } + + r.ID = res.Filepath.Name() + r.Fields = model1.Fields{ + strings.TrimSuffix(res.Filepath.Name(), filepath.Ext(res.Filepath.Name())), + strconv.FormatBool(res.InContext), + "", + timeToAge(res.Filepath.ModTime()), + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +type WorkloadGVRRes struct { + Filepath os.FileInfo + InContext bool +} + +// GetObjectKind returns a schema object. +func (a WorkloadGVRRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (a WorkloadGVRRes) DeepCopyObject() runtime.Object { + return a +} diff --git a/internal/view/exec.go b/internal/view/exec.go index 618dc74623..799a951bb3 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -126,11 +126,21 @@ func edit(a *App, opts shellOpts) bool { err error ) for _, e := range editorEnvVars { - env := os.Getenv(e) - if env == "" { + editorCommand := os.Getenv(e) + if editorCommand == "" { continue } - if bin, err = exec.LookPath(env); err == nil { + + // Ensure to check only the EDITOR binary + editorCommandWithArgs := strings.Split(editorCommand, " ") + + if bin, err = exec.LookPath(editorCommandWithArgs[0]); err == nil { + + // Brings back possible parameters set on the EDITOR + if len(editorCommandWithArgs) > 1 { + opts.args = append(editorCommandWithArgs[1:], opts.args...) + } + break } } diff --git a/internal/view/registrar.go b/internal/view/registrar.go index d199a44988..830e258f42 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -63,6 +63,9 @@ func miscViewers(vv MetaViewers) { vv[client.NewGVR("workloads")] = MetaViewer{ viewerFn: NewWorkload, } + vv[client.NewGVR("workloadgvrs")] = MetaViewer{ + viewerFn: NewWorkloadGVR, + } vv[client.NewGVR("contexts")] = MetaViewer{ viewerFn: NewContext, } diff --git a/internal/view/workload.go b/internal/view/workload.go index 78bba38fa2..e1d8009315 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -6,16 +6,19 @@ package view import ( "context" "fmt" + "os" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" + "gopkg.in/yaml.v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -29,6 +32,7 @@ func NewWorkload(gvr client.GVR) ResourceViewer { w := Workload{ ResourceViewer: NewBrowser(gvr), } + w.SetContextFn(w.workloadContext) w.GetTable().SetEnterFn(w.showRes) w.AddBindKeysFn(w.bindKeys) w.GetTable().SetSortCol("KIND", true) @@ -36,6 +40,32 @@ func NewWorkload(gvr client.GVR) ResourceViewer { return &w } +// workloadContext will set the configuration's values of the workloadGVRs in the context to be used in the dao/workload +func (n *Workload) workloadContext(ctx context.Context) context.Context { + var gvrFilenames []string + + // Retrieve workload cluster context config file + ctxWorkloadPath := n.App().Config.ContextWorkloadPath() + + // Read config file + wkg := config.WorkloadConfig{} + configData, err := os.ReadFile(ctxWorkloadPath) + if err == nil { + if err := yaml.Unmarshal(configData, &wkg); err == nil { + gvrFilenames = wkg.GVRFilenames + } + } + + // Init workload GVRs with default values and from cluster context config + wkgvs, err := config.NewWorkloadGVRs(n.App().Config.ContextWorkloadDir(), gvrFilenames) + if err != nil { + n.App().Flash().Errf("unable to find custom workload GVR: %q", err) + } + + // Set in context + return context.WithValue(ctx, internal.KeyWorkloadGVRs, wkgvs) +} + func (w *Workload) bindDangerousKeys(aa *ui.KeyActions) { aa.Bulk(ui.KeyMap{ ui.KeyE: ui.NewKeyActionWithOpts("Edit", w.editCmd, @@ -63,6 +93,7 @@ func (w *Workload) bindKeys(aa *ui.KeyActions) { ui.KeyShiftA: ui.NewKeyAction("Sort Age", w.GetTable().SortColCmd(ageCol, true), false), ui.KeyY: ui.NewKeyAction(yamlAction, w.yamlCmd, true), ui.KeyD: ui.NewKeyAction("Describe", w.describeCmd, true), + ui.KeyG: ui.NewKeyAction("Custom GVRs", w.workloadGVRView, true), }) } @@ -166,6 +197,16 @@ func (w *Workload) describeCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +func (a *Workload) workloadGVRView(evt *tcell.EventKey) *tcell.EventKey { + co := NewWorkloadGVR(client.NewGVR("workloadgvrs")) + if err := a.App().inject(co, false); err != nil { + a.App().Flash().Err(err) + return nil + } + + return evt +} + func (w *Workload) editCmd(evt *tcell.EventKey) *tcell.EventKey { path := w.GetTable().GetSelectedItem() if path == "" { diff --git a/internal/view/workloadGVR.go b/internal/view/workloadGVR.go new file mode 100644 index 0000000000..4350b6768c --- /dev/null +++ b/internal/view/workloadGVR.go @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/ui/dialog" + "github.com/derailed/tcell/v2" + "github.com/derailed/tview" + "gopkg.in/yaml.v3" +) + +const workloadGVRTitle = "workloadGVR" + +type WorkloadGVR struct { + ResourceViewer +} + +func NewWorkloadGVR(gvr client.GVR) ResourceViewer { + a := WorkloadGVR{ + ResourceViewer: NewBrowser(gvr), + } + a.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue) + a.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorAliceBlue).Attributes(tcell.AttrNone)) + a.AddBindKeysFn(a.bindKeys) + a.SetContextFn(a.workloadGVRContext) + + return &a +} + +// Init initializes the view. +func (a *WorkloadGVR) Init(ctx context.Context) error { + if err := a.ResourceViewer.Init(ctx); err != nil { + return err + } + a.GetTable().GetModel().SetNamespace(client.NotNamespaced) + + return nil +} + +// workloadGVRContext initialise the worjloadGVR context +func (a *WorkloadGVR) workloadGVRContext(ctx context.Context) context.Context { + ctx = context.WithValue(ctx, internal.KeyDir, a.App().Config.ContextWorkloadDir()) + return context.WithValue(ctx, internal.KeyPath, a.App().Config.ContextWorkloadPath()) +} + +func (a *WorkloadGVR) bindKeys(aa *ui.KeyActions) { + aa.Delete(ui.KeyN, ui.KeyD, ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace, ui.KeyShiftD) + aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD) + aa.Bulk(ui.KeyMap{ + ui.KeyShiftA: ui.NewKeyActionWithOpts("Context: add", a.addtoCurrentCtx, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyShiftD: ui.NewKeyActionWithOpts("Context: delete", a.deletefromCurrentCtx, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyC: ui.NewKeyActionWithOpts("Create custom GVR", a.createCustomCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyR: ui.NewKeyActionWithOpts("Delete custom GVR", a.deleteCustomCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyE: ui.NewKeyActionWithOpts("Edit custom GVR", a.editCustomCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyShiftG: ui.NewKeyAction("Sort GVR", a.GetTable().SortColCmd("NAME", true), false), + tcell.KeyEnter: ui.NewKeyAction("Simulate", a.simulateCmd, true), + ui.KeyD: ui.NewKeyAction("Show", a.describeCmd, true), + }) +} + +// describeCmd will show a custom GVR +func (a *WorkloadGVR) describeCmd(evt *tcell.EventKey) *tcell.EventKey { + sel := a.GetTable().GetSelectedItem() + if sel == "" { + return evt + } + + // Retrieve custom workload gvr filepath + pathFile := path.Join(a.App().Config.ContextWorkloadDir(), sel) + data, err := os.ReadFile(pathFile) + if err != nil { + a.App().Flash().Err(err) + return nil + } + + // Describe custom workload GVR + details := NewDetails(a.App(), "Describe", pathFile, contentYAML, true).Update(string(data)) + if err := a.App().inject(details, false); err != nil { + a.App().Flash().Err(err) + return nil + } + + return nil +} + +// createCustomCmd will create a custom worklad GVR wiht default template using a specified GVR's name +func (a *WorkloadGVR) createCustomCmd(evt *tcell.EventKey) *tcell.EventKey { + var GVRName string + + // Generate creation form + form, err := a.makeCreateForm(&GVRName) + if err != nil { + return nil + } + confirm := tview.NewModalForm("", form) + confirm.SetText(fmt.Sprintf("Set GVR Name %s %s", a.GVR(), a.App().Config.ContextWorkloadDir())) + confirm.SetDoneFunc(func(int, string) { + a.cleanupClusterContext() + a.dismissDialog() + }) + a.App().Content.AddPage("NewGVRModal", confirm, false, false) + a.App().Content.ShowPage("NewGVRModal") + + return nil +} + +func (a *WorkloadGVR) makeCreateForm(sel *string) (*tview.Form, error) { + // Generate create form + f := tview.NewForm() + f.SetItemPadding(0) + f.SetButtonsAlign(tview.AlignCenter). + SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). + SetButtonTextColor(tview.Styles.PrimaryTextColor). + SetLabelColor(tcell.ColorAqua). + SetFieldTextColor(tcell.ColorOrange) + + f.AddInputField("GVR Filename", "", 0, nil, func(changed string) { + *sel = changed + }) + + f.AddButton("OK", func() { + defer a.dismissDialog() + + // Generate new filename / filepath + filename := fmt.Sprintf("%s.yaml", *sel) + filePathName := path.Join(a.App().Config.ContextWorkloadDir(), filename) + + // Create new GVR file + if err := os.WriteFile(filePathName, config.Template, 0644); err != nil { + a.App().Flash().Errf("Failed to create file: %q", err) + return + } + + a.Stop() + defer a.Start() + if !edit(a.App(), shellOpts{clear: true, args: []string{filePathName}}) { + a.App().Flash().Err(errors.New("Failed to launch editor")) + return + } + }) + f.AddButton("Cancel", func() { + a.dismissDialog() + }) + + return f, nil +} + +func (a *WorkloadGVR) dismissDialog() { + a.App().Content.RemovePage("NewGVRModal") +} + +// editCustomCmd will edit the current custom workloadGVR +func (a *WorkloadGVR) editCustomCmd(evt *tcell.EventKey) *tcell.EventKey { + sel := a.GetTable().GetSelectedItem() + if sel == "" { + return evt + } + + // Edit existing custom GVR + a.Stop() + defer a.Start() + if !edit(a.App(), shellOpts{clear: true, args: []string{path.Join(a.App().Config.ContextWorkloadDir(), sel)}}) { + a.App().Flash().Err(errors.New("Failed to launch editor")) + return nil + } + + a.cleanupClusterContext() + + return nil +} + +// deleteCustomCmd will delete the custom workload GVR +func (a *WorkloadGVR) deleteCustomCmd(evt *tcell.EventKey) *tcell.EventKey { + sel := a.GetTable().GetSelectedItem() + if sel == "" { + return evt + } + + // Remove custom GRV (with prompt) + filePath := path.Join(a.App().Config.ContextWorkloadDir(), sel) + msg := fmt.Sprintf("Are you sure to delete the custom gvr: %s", strings.TrimSuffix(sel, filepath.Ext(sel))) + dialog.ShowConfirm(a.App().Styles.Dialog(), a.App().Content.Pages, "Confirm Deletion", msg, func() { + if err := os.Remove(filePath); err != nil { + a.App().Flash().Errf("could not delete GVR: %q", err) + return + } + + a.cleanupClusterContext() + }, func() {}) + + return nil +} + +// addtoCurrentCtx will add the GVR to the current cluster context +func (a *WorkloadGVR) addtoCurrentCtx(evt *tcell.EventKey) *tcell.EventKey { + sel := a.GetTable().GetSelectedItem() + if sel == "" { + return evt + } + + // Add custom GVR to cluster context + filenames := make([]string, 0) + ctxWorkloadPath := a.App().Config.ContextWorkloadPath() + content, err := os.ReadFile(ctxWorkloadPath) + if err == nil { + var ctxConfig config.WorkloadConfig + if err := yaml.Unmarshal(content, &ctxConfig); err != nil { + a.App().Flash().Errf("could not read workload context configuration: %q", err) + return nil + } + filenames = ctxConfig.GVRFilenames + } + + filenames = append(filenames, strings.TrimSuffix(sel, filepath.Ext(sel))) + + // Ensure there is no duplicate + m := make(map[string]string) + for _, n := range filenames { + m[n] = n + } + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + + // Save new config + data, err := yaml.Marshal(config.WorkloadConfig{GVRFilenames: keys}) + if err != nil { + a.App().Flash().Errf("could not marshal new configuration: %q", err) + return nil + } + + if err := os.WriteFile(ctxWorkloadPath, data, 0644); err != nil { + a.App().Flash().Errf("could not write new configuration: %q", err) + return nil + } + + a.cleanupClusterContext() + + return nil +} + +// deletefromCurrentCtx will delete the gvr from the current cluster context +func (a *WorkloadGVR) deletefromCurrentCtx(evt *tcell.EventKey) *tcell.EventKey { + sel := a.GetTable().GetSelectedItem() + if sel == "" { + return evt + } + + // Delete custom GVR from cluster context + filenames := make([]string, 0) + ctxWorkloadPath := a.App().Config.ContextWorkloadPath() + content, err := os.ReadFile(ctxWorkloadPath) + if err == nil { + var ctxConfig config.WorkloadConfig + if err := yaml.Unmarshal(content, &ctxConfig); err != nil { + a.App().Flash().Errf("could not unmarshal configuration: %q", err) + return nil + } + filenames = ctxConfig.GVRFilenames + } + + // Ensure there is no duplicate + m := make(map[string]string) + for _, n := range filenames { + if n != strings.TrimSuffix(sel, filepath.Ext(sel)) { + m[n] = n + } + } + keys := make([]string, 0, len(m)) + for filename := range m { + if _, err := os.Stat(fmt.Sprintf("%s.yaml", path.Join(a.App().Config.ContextWorkloadDir(), filename))); err == nil || !errors.Is(err, os.ErrNotExist) { + keys = append(keys, filename) + } + } + + // Save new config + data, err := yaml.Marshal(config.WorkloadConfig{GVRFilenames: keys}) + if err != nil { + a.App().Flash().Errf("could not marshal new configuration: %q", err) + return nil + } + + if err := os.WriteFile(ctxWorkloadPath, data, 0644); err != nil { + a.App().Flash().Errf("could not write new configuration: %q", err) + return nil + } + + a.cleanupClusterContext() + + return nil +} + +func (a *WorkloadGVR) cleanupClusterContext() { + validFilenames := make([]string, 0) + + // Get cluster context + content, err := os.ReadFile(a.App().Config.ContextWorkloadPath()) + if err != nil { + return + } + var ctxConfig config.WorkloadConfig + if err := yaml.Unmarshal(content, &ctxConfig); err != nil { + return + } + + // Check if each files exists + // Cleanup the one that doesn't exists + for _, filename := range ctxConfig.GVRFilenames { + if _, err := os.Stat(fmt.Sprintf("%s.yaml", path.Join(a.App().Config.ContextWorkloadDir(), filename))); err == nil || !errors.Is(err, os.ErrNotExist) { + validFilenames = append(validFilenames, filename) + } + } + + // Save new cluster context + data, err := yaml.Marshal(config.WorkloadConfig{GVRFilenames: validFilenames}) + if err != nil { + a.App().Flash().Errf("could not marshal new configuration: %q", err) + return + } + + if err := os.WriteFile(a.App().Config.ContextWorkloadPath(), data, 0644); err != nil { + a.App().Flash().Errf("could not write new configuration: %q", err) + return + } +} + +// simulateCmd will show the custom workload GVR in the workload view +func (a *WorkloadGVR) simulateCmd(evt *tcell.EventKey) *tcell.EventKey { + co := NewWorkload(client.NewGVR("workloads")) + co.SetContextFn(a.singleWorkloadCtx) + if err := a.App().inject(co, false); err != nil { + a.App().Flash().Err(err) + return nil + } + + return evt +} + +// singleWorkloadCtx will set the selected workloadGVR to the context to be simulated on the workload view +func (a *WorkloadGVR) singleWorkloadCtx(ctx context.Context) context.Context { + wkgvrFilename := a.GetTable().GetSelectedItem() + if wkgvrFilename == "" { + return ctx + } + + workloadcustomDir := a.App().Config.ContextWorkloadDir() + wkgvr, err := config.GetWorkloadGVRFromFile(path.Join(workloadcustomDir, wkgvrFilename)) + if err != nil { + a.App().Flash().Errf("could not retrieve workload gvr from file: %q", err) + return ctx + } + + return context.WithValue(ctx, internal.KeyWorkloadGVRs, []config.WorkloadGVR{wkgvr}) +} diff --git a/plugins/argo-rollouts.yaml b/plugins/argo-rollouts.yaml index b6e7fec8f5..34c4fb87c4 100644 --- a/plugins/argo-rollouts.yaml +++ b/plugins/argo-rollouts.yaml @@ -26,7 +26,7 @@ plugins: background: false args: - -c - - kubectl argo rollouts get rollout $NAME --context $CONTEXT -n $NAMESPACE -w |& less + - kubectl argo rollouts get rollout $NAME --context $CONTEXT -n $NAMESPACE -w argo-rollouts-promote: shortCut: p confirm: true