diff --git a/api/go.mod b/api/go.mod index 9b7d87e1d..b0cb53852 100644 --- a/api/go.mod +++ b/api/go.mod @@ -51,13 +51,15 @@ require ( go.etcd.io/bbolt v1.3.7 golang.org/x/crypto v0.7.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/mod v0.9.0 golang.org/x/oauth2 v0.6.0 golang.org/x/sync v0.1.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.26.1 - k8s.io/apimachinery v0.26.1 - k8s.io/client-go v0.26.1 + k8s.io/api v0.27.4 + k8s.io/apimachinery v0.27.4 + k8s.io/client-go v0.27.4 + k8s.io/metrics v0.27.4 software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 ) @@ -90,8 +92,8 @@ require ( github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.1.0 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -101,6 +103,7 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.15 // indirect @@ -141,7 +144,6 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.6.0 // indirect @@ -153,10 +155,10 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/api/go.sum b/api/go.sum index 97dfadcae..c41426a5e 100644 --- a/api/go.sum +++ b/api/go.sum @@ -96,7 +96,6 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= @@ -137,12 +136,10 @@ github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjR github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -152,6 +149,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= @@ -195,9 +193,12 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -258,8 +259,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -291,8 +290,8 @@ github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -330,7 +329,7 @@ github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa3 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= @@ -530,7 +529,6 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -555,20 +553,22 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= -k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= -k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= -k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs= +k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y= +k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs= +k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk= +k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/metrics v0.27.4 h1:2s04bods7rA507iouGbxD55YrKNlFjLYzm30noOl9Sk= +k8s.io/metrics v0.27.4/go.mod h1:kRvfhFC7wCQEFvu6H92uiV7v05z3Ty/vtluYT5D2Xpk= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/api/http/handler/kubernetes/handler.go b/api/http/handler/kubernetes/handler.go index 17a3fa0e0..6156739c9 100644 --- a/api/http/handler/kubernetes/handler.go +++ b/api/http/handler/kubernetes/handler.go @@ -53,6 +53,10 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza endpointRouter.Use(h.kubeClient) endpointRouter.PathPrefix("/nodes_limits").Handler(httperror.LoggerHandler(h.getKubernetesNodesLimits)).Methods(http.MethodGet) + endpointRouter.Path("/metrics/nodes").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForAllNodes)).Methods(http.MethodGet) + endpointRouter.Path("/metrics/nodes/{name}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForNode)).Methods(http.MethodGet) + endpointRouter.Path("/metrics/pods/namespace/{namespace}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForAllPods)).Methods(http.MethodGet) + endpointRouter.Path("/metrics/pods/namespace/{namespace}/{name}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForPod)).Methods(http.MethodGet) endpointRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllers)).Methods(http.MethodGet) endpointRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllers)).Methods(http.MethodPut) endpointRouter.Handle("/ingresses/delete", httperror.LoggerHandler(h.deleteKubernetesIngresses)).Methods(http.MethodPost) diff --git a/api/http/handler/kubernetes/metrics.go b/api/http/handler/kubernetes/metrics.go new file mode 100644 index 000000000..5d4ebe61b --- /dev/null +++ b/api/http/handler/kubernetes/metrics.go @@ -0,0 +1,247 @@ +package kubernetes + +import ( + "net/http" + + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" + portainer "github.com/portainer/portainer/api" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// @id getKubernetesMetricsForAllNodes +// @summary Get a list of nodes with their live metrics +// @description Get a list of nodes with their live metrics +// @description **Access policy**: authenticated +// @tags kubernetes +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param id path int true "Environment (Endpoint) identifier" +// @success 200 {object} v1beta1.NodeMetricsList "Success" +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /kubernetes/{id}/metrics/nodes [get] +func (handler *Handler) getKubernetesMetricsForAllNodes(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return httperror.BadRequest( + "Invalid environment identifier route variable", + err, + ) + } + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if handler.DataStore.IsErrObjectNotFound(err) { + return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) + } else if err != nil { + return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + } + + cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) + if err != nil { + return httperror.InternalServerError( + "failed to create metrics KubeClient", + nil, + ) + } + + metrics, err := cli.MetricsV1beta1().NodeMetricses().List(r.Context(), v1.ListOptions{}) + if err != nil { + return httperror.InternalServerError( + "Failed to fetch metrics", + err, + ) + } + + return response.JSON(w, metrics) +} + +// @id getKubernetesMetricsForNode +// @summary Get live metrics for a node +// @description Get live metrics for a node +// @description **Access policy**: authenticated +// @tags kubernetes +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param id path int true "Environment (Endpoint) identifier" +// @param name path string true "Node identifier" +// @success 200 {object} v1beta1.NodeMetrics "Success" +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /kubernetes/{id}/metrics/nodes/{name} [get] +func (handler *Handler) getKubernetesMetricsForNode(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return httperror.BadRequest( + "Invalid environment identifier route variable", + err, + ) + } + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if handler.DataStore.IsErrObjectNotFound(err) { + return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) + } else if err != nil { + return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + } + + cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) + if err != nil { + return httperror.InternalServerError( + "failed to create metrics KubeClient", + nil, + ) + } + + nodeName, err := request.RetrieveRouteVariableValue(r, "name") + if err != nil { + return httperror.BadRequest( + "Invalid node identifier route variable", + err, + ) + } + + metrics, err := cli.MetricsV1beta1().NodeMetricses().Get( + r.Context(), + nodeName, + v1.GetOptions{}, + ) + if err != nil { + return httperror.InternalServerError( + "Failed to fetch metrics", + err, + ) + } + + return response.JSON(w, metrics) +} + +// @id getKubernetesMetricsForAllPods +// @summary Get a list of pods with their live metrics +// @description Get a list of pods with their live metrics +// @description **Access policy**: authenticated +// @tags kubernetes +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param id path int true "Environment (Endpoint) identifier" +// @param namespace path string true "Namespace" +// @success 200 {object} v1beta1.PodMetricsList "Success" +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /kubernetes/{id}/metrics/pods/{namespace} [get] +func (handler *Handler) getKubernetesMetricsForAllPods(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return httperror.BadRequest( + "Invalid environment identifier route variable", + err, + ) + } + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if handler.DataStore.IsErrObjectNotFound(err) { + return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) + } else if err != nil { + return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + } + + cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) + if err != nil { + return httperror.InternalServerError( + "failed to create metrics KubeClient", + nil, + ) + } + + namespace, err := request.RetrieveRouteVariableValue(r, "namespace") + if err != nil { + return httperror.BadRequest( + "Invalid namespace identifier route variable", + err, + ) + } + + metrics, err := cli.MetricsV1beta1().PodMetricses(namespace).List(r.Context(), v1.ListOptions{}) + if err != nil { + return httperror.InternalServerError( + "Failed to fetch metrics", + err, + ) + } + + return response.JSON(w, metrics) +} + +// @id getKubernetesMetricsForPod +// @summary Get live metrics for a pod +// @description Get live metrics for a pod +// @description **Access policy**: authenticated +// @tags kubernetes +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param id path int true "Environment (Endpoint) identifier" +// @param namespace path string true "Namespace" +// @param name path string true "Pod identifier" +// @success 200 {object} v1beta1.PodMetrics "Success" +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /kubernetes/{id}/metrics/pods/{namespace}/{name} [get] +func (handler *Handler) getKubernetesMetricsForPod(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return httperror.BadRequest( + "Invalid environment identifier route variable", + err, + ) + } + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if handler.DataStore.IsErrObjectNotFound(err) { + return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) + } else if err != nil { + return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + } + + cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) + if err != nil { + return httperror.InternalServerError( + "failed to create metrics KubeClient", + nil, + ) + } + + namespace, err := request.RetrieveRouteVariableValue(r, "namespace") + if err != nil { + return httperror.BadRequest( + "Invalid namespace identifier route variable", + err, + ) + } + + podName, err := request.RetrieveRouteVariableValue(r, "name") + if err != nil { + return httperror.BadRequest( + "Invalid pod identifier route variable", + err, + ) + } + + metrics, err := cli.MetricsV1beta1().PodMetricses(namespace).Get( + r.Context(), + podName, + v1.GetOptions{}, + ) + if err != nil { + return httperror.InternalServerError( + "Failed to fetch metrics", + err, + ) + } + + return response.JSON(w, metrics) +} diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index 5cc6050b1..aa57d5fbe 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -15,6 +15,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + metricsv "k8s.io/metrics/pkg/client/clientset/versioned" ) const ( @@ -225,6 +226,34 @@ func (factory *ClientFactory) createRemoteClient(endpointURL string) (*kubernete return kubernetes.NewForConfig(config) } +func (factory *ClientFactory) CreateRemoteMetricsClient(endpoint *portainer.Endpoint) (*metricsv.Clientset, error) { + endpointURL := fmt.Sprintf("https://%s/kubernetes", endpoint.URL) + + signature, err := factory.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage) + if err != nil { + return nil, err + } + + config, err := clientcmd.BuildConfigFromFlags(endpointURL, "") + if err != nil { + return nil, err + } + + config.Insecure = true + config.QPS = DefaultKubeClientQPS + config.Burst = DefaultKubeClientBurst + + config.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &agentHeaderRoundTripper{ + signatureHeader: signature, + publicKeyHeader: factory.signatureService.EncodedPublicKey(), + roundTripper: rt, + } + }) + + return metricsv.NewForConfig(config) +} + func buildLocalClient() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { diff --git a/app/kubernetes/metrics/metrics.js b/app/kubernetes/metrics/metrics.js deleted file mode 100644 index c374bd2e0..000000000 --- a/app/kubernetes/metrics/metrics.js +++ /dev/null @@ -1,114 +0,0 @@ -import angular from 'angular'; -import PortainerError from 'Portainer/error'; -import { KubernetesCommonParams } from 'Kubernetes/models/common/params'; - -class KubernetesMetricsService { - /* @ngInject */ - constructor($async, KubernetesMetrics) { - this.$async = $async; - this.KubernetesMetrics = KubernetesMetrics; - - this.capabilitiesAsync = this.capabilitiesAsync.bind(this); - - this.getPodAsync = this.getPodAsync.bind(this); - this.getNodeAsync = this.getNodeAsync.bind(this); - - this.getPodsAsync = this.getPodsAsync.bind(this); - this.getNodesAsync = this.getNodesAsync.bind(this); - } - - /** - * GET - */ - async capabilitiesAsync(endpointID) { - try { - await this.KubernetesMetrics().capabilities({ endpointId: endpointID }).$promise; - } catch (err) { - throw new PortainerError('Unable to retrieve metrics', err); - } - } - - capabilities(endpointID) { - return this.$async(this.capabilitiesAsync, endpointID); - } - - /** - * Stats of Node - * - * @param {string} nodeName - */ - async getNodeAsync(nodeName) { - try { - const params = new KubernetesCommonParams(); - params.id = nodeName; - const data = await this.KubernetesMetrics().getNode(params).$promise; - return data; - } catch (err) { - throw new PortainerError('Unable to retrieve node stats', err); - } - } - - getNode(nodeName) { - return this.$async(this.getNodeAsync, nodeName); - } - - /** - * Stats - * - * @param {string} namespace - * @param {string} podName - */ - async getPodAsync(namespace, podName) { - try { - const params = new KubernetesCommonParams(); - params.id = podName; - const data = await this.KubernetesMetrics(namespace).getPod(params).$promise; - return data; - } catch (err) { - throw new PortainerError('Unable to retrieve pod stats', err); - } - } - - getPod(namespace, podName) { - return this.$async(this.getPodAsync, namespace, podName); - } - - /** - * Stats of Nodes in cluster - * - * @param {string} endpointID - */ - async getNodesAsync(endpointID) { - try { - const data = await this.KubernetesMetrics().getNodes({ endpointId: endpointID }).$promise; - return data; - } catch (err) { - throw new PortainerError('Unable to retrieve nodes stats', err); - } - } - - getNodes(endpointID) { - return this.$async(this.getNodesAsync, endpointID); - } - - /** - * Stats of Pods in a namespace - * - * @param {string} namespace - */ - async getPodsAsync(namespace) { - try { - const data = await this.KubernetesMetrics(namespace).getPods().$promise; - return data; - } catch (err) { - throw new PortainerError('Unable to retrieve pod stats', err); - } - } - - getPods(namespace) { - return this.$async(this.getPodsAsync, namespace); - } -} - -export default KubernetesMetricsService; -angular.module('portainer.kubernetes').service('KubernetesMetricsService', KubernetesMetricsService); diff --git a/app/kubernetes/metrics/rest.js b/app/kubernetes/metrics/rest.js deleted file mode 100644 index 1dc97c212..000000000 --- a/app/kubernetes/metrics/rest.js +++ /dev/null @@ -1,39 +0,0 @@ -angular.module('portainer.kubernetes').factory('KubernetesMetrics', [ - '$resource', - 'API_ENDPOINT_ENDPOINTS', - 'EndpointProvider', - function KubernetesMetrics($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return function (namespace) { - const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/apis/metrics.k8s.io/v1beta1'; - const podUrl = `${url}${namespace ? '/namespaces/:namespace' : ''}/pods/:id`; - - return $resource( - url, - { - endpointId: EndpointProvider.endpointID, - namespace: namespace, - }, - { - capabilities: { method: 'GET' }, - getPod: { - method: 'GET', - url: podUrl, - }, - getNode: { - method: 'GET', - url: `${url}/nodes/:id`, - }, - getPods: { - method: 'GET', - url: `${url}/namespaces/:namespace/pods`, - }, - getNodes: { - method: 'GET', - url: `${url}/nodes`, - }, - } - ); - }; - }, -]); diff --git a/app/kubernetes/views/applications/stats/statsController.js b/app/kubernetes/views/applications/stats/statsController.js index 01f3458a7..65a43199e 100644 --- a/app/kubernetes/views/applications/stats/statsController.js +++ b/app/kubernetes/views/applications/stats/statsController.js @@ -4,10 +4,11 @@ import _ from 'lodash-es'; import filesizeParser from 'filesize-parser'; import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper'; import KubernetesPodConverter from 'Kubernetes/pod/converter'; +import { getMetricsForPod } from '@/react/kubernetes/services/service.ts'; class KubernetesApplicationStatsController { /* @ngInject */ - constructor($async, $state, $interval, $document, Notifications, KubernetesPodService, KubernetesNodeService, KubernetesMetricsService, ChartService) { + constructor($async, $state, $interval, $document, Notifications, KubernetesPodService, KubernetesNodeService, ChartService) { this.$async = $async; this.$state = $state; this.$interval = $interval; @@ -15,7 +16,6 @@ class KubernetesApplicationStatsController { this.Notifications = Notifications; this.KubernetesPodService = KubernetesPodService; this.KubernetesNodeService = KubernetesNodeService; - this.KubernetesMetricsService = KubernetesMetricsService; this.ChartService = ChartService; this.onInit = this.onInit.bind(this); @@ -84,7 +84,7 @@ class KubernetesApplicationStatsController { getStats() { return this.$async(async () => { try { - const stats = await this.KubernetesMetricsService.getPod(this.state.transition.namespace, this.state.transition.podName); + const stats = await getMetricsForPod(this.$state.params.endpointId, this.state.transition.namespace, this.state.transition.podName); const container = _.find(stats.containers, { name: this.state.transition.containerName }); if (container) { const memory = filesizeParser(container.usage.memory); @@ -126,7 +126,7 @@ class KubernetesApplicationStatsController { }; try { - await this.KubernetesMetricsService.getPod(this.state.transition.namespace, this.state.transition.podName); + await getMetricsForPod(this.$state.params.endpointId, this.state.transition.namespace, this.state.transition.podName); } catch (error) { this.state.getMetrics = false; this.state.viewReady = true; diff --git a/app/kubernetes/views/cluster/clusterController.js b/app/kubernetes/views/cluster/clusterController.js index 37c7f3822..81d711849 100644 --- a/app/kubernetes/views/cluster/clusterController.js +++ b/app/kubernetes/views/cluster/clusterController.js @@ -3,27 +3,17 @@ import _ from 'lodash-es'; import filesizeParser from 'filesize-parser'; import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper'; import { KubernetesResourceReservation } from 'Kubernetes/models/resource-reservation/models'; +import { getMetricsForAllNodes } from '@/react/kubernetes/services/service.ts'; class KubernetesClusterController { /* @ngInject */ - constructor( - $async, - $state, - Authentication, - Notifications, - LocalStorage, - KubernetesNodeService, - KubernetesMetricsService, - KubernetesApplicationService, - KubernetesEndpointService - ) { + constructor($async, $state, Notifications, LocalStorage, Authentication, KubernetesNodeService, KubernetesApplicationService, KubernetesEndpointService) { this.$async = $async; this.$state = $state; this.Authentication = Authentication; this.Notifications = Notifications; this.LocalStorage = LocalStorage; this.KubernetesNodeService = KubernetesNodeService; - this.KubernetesMetricsService = KubernetesMetricsService; this.KubernetesApplicationService = KubernetesApplicationService; this.KubernetesEndpointService = KubernetesEndpointService; @@ -108,7 +98,7 @@ class KubernetesClusterController { async getResourceUsage(endpointId) { try { - const nodeMetrics = await this.KubernetesMetricsService.getNodes(endpointId); + const nodeMetrics = await getMetricsForAllNodes(endpointId); const resourceUsageList = nodeMetrics.items.map((i) => i.usage); const clusterResourceUsage = resourceUsageList.reduce((total, u) => { total.CPU += KubernetesResourceReservationHelper.parseCPU(u.cpu); diff --git a/app/kubernetes/views/cluster/node/nodeController.js b/app/kubernetes/views/cluster/node/nodeController.js index c80e5a15e..e26d00f18 100644 --- a/app/kubernetes/views/cluster/node/nodeController.js +++ b/app/kubernetes/views/cluster/node/nodeController.js @@ -9,6 +9,7 @@ import { KubernetesNodeTaintEffects, KubernetesNodeAvailabilities } from 'Kubern import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper'; import { KubernetesNodeHelper } from 'Kubernetes/node/helper'; import { confirmUpdateNode } from '@/react/kubernetes/cluster/NodeView/ConfirmUpdateNode'; +import { getMetricsForNode } from '@/react/kubernetes/services/service.ts'; class KubernetesNodeController { /* @ngInject */ @@ -22,7 +23,6 @@ class KubernetesNodeController { KubernetesPodService, KubernetesApplicationService, KubernetesEndpointService, - KubernetesMetricsService, Authentication ) { this.$async = $async; @@ -34,7 +34,6 @@ class KubernetesNodeController { this.KubernetesPodService = KubernetesPodService; this.KubernetesApplicationService = KubernetesApplicationService; this.KubernetesEndpointService = KubernetesEndpointService; - this.KubernetesMetricsService = KubernetesMetricsService; this.Authentication = Authentication; this.onInit = this.onInit.bind(this); @@ -300,7 +299,7 @@ class KubernetesNodeController { async getNodeUsageAsync() { try { const nodeName = this.$transition$.params().name; - const node = await this.KubernetesMetricsService.getNode(nodeName); + const node = await getMetricsForNode(this.$state.params.endpointId, nodeName); this.resourceUsage = new KubernetesResourceReservation(); this.resourceUsage.CPU = KubernetesResourceReservationHelper.parseCPU(node.usage.cpu); this.resourceUsage.Memory = KubernetesResourceReservationHelper.megaBytesValue(node.usage.memory); diff --git a/app/kubernetes/views/cluster/node/stats/statsController.js b/app/kubernetes/views/cluster/node/stats/statsController.js index e56424508..9b5448c99 100644 --- a/app/kubernetes/views/cluster/node/stats/statsController.js +++ b/app/kubernetes/views/cluster/node/stats/statsController.js @@ -3,17 +3,17 @@ import moment from 'moment'; import filesizeParser from 'filesize-parser'; import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper'; import { PORTAINER_FADEOUT } from '@/constants'; +import { getMetricsForNode } from '@/react/kubernetes/services/service.ts'; class KubernetesNodeStatsController { /* @ngInject */ - constructor($async, $state, $interval, $document, Notifications, KubernetesNodeService, KubernetesMetricsService, ChartService) { + constructor($async, $state, $interval, $document, Notifications, KubernetesNodeService, ChartService) { this.$async = $async; this.$state = $state; this.$interval = $interval; this.$document = $document; this.Notifications = Notifications; this.KubernetesNodeService = KubernetesNodeService; - this.KubernetesMetricsService = KubernetesMetricsService; this.ChartService = ChartService; this.onInit = this.onInit.bind(this); @@ -79,7 +79,7 @@ class KubernetesNodeStatsController { getStats() { return this.$async(async () => { try { - const stats = await this.KubernetesMetricsService.getNode(this.state.transition.nodeName); + const stats = await getMetricsForNode(this.$state.params.endpointId, this.state.transition.nodeName); if (stats) { const memory = filesizeParser(stats.usage.memory); const cpu = KubernetesResourceReservationHelper.parseCPU(stats.usage.cpu); @@ -111,7 +111,7 @@ class KubernetesNodeStatsController { }; try { - const nodeMetrics = await this.KubernetesMetricsService.getNode(this.state.transition.nodeName); + const nodeMetrics = await getMetricsForNode(this.$state.params.endpointId, this.state.transition.nodeName); if (nodeMetrics) { const node = await this.KubernetesNodeService.get(this.state.transition.nodeName); diff --git a/app/kubernetes/views/configure/configureController.js b/app/kubernetes/views/configure/configureController.js index 44bac337d..d500440dc 100644 --- a/app/kubernetes/views/configure/configureController.js +++ b/app/kubernetes/views/configure/configureController.js @@ -11,23 +11,13 @@ import { buildConfirmButton } from '@@/modals/utils'; import { confirm } from '@@/modals/confirm'; import { getIsRBACEnabled } from '@/react/kubernetes/cluster/getIsRBACEnabled'; import { ModalType } from '@@/modals/Modal/types'; +import { getMetricsForAllNodes } from '@/react/kubernetes/services/service.ts'; class KubernetesConfigureController { /* #region CONSTRUCTOR */ /* @ngInject */ - constructor( - $async, - $state, - $scope, - Notifications, - KubernetesStorageService, - EndpointService, - EndpointProvider, - KubernetesResourcePoolService, - KubernetesIngressService, - KubernetesMetricsService - ) { + constructor($async, $state, $scope, Notifications, KubernetesStorageService, EndpointService, EndpointProvider, KubernetesResourcePoolService, KubernetesIngressService) { this.$async = $async; this.$state = $state; this.$scope = $scope; @@ -37,7 +27,6 @@ class KubernetesConfigureController { this.EndpointProvider = EndpointProvider; this.KubernetesResourcePoolService = KubernetesResourcePoolService; this.KubernetesIngressService = KubernetesIngressService; - this.KubernetesMetricsService = KubernetesMetricsService; this.IngressClassTypes = KubernetesIngressClassTypes; @@ -192,24 +181,26 @@ class KubernetesConfigureController { } enableMetricsServer() { - if (this.formValues.UseServerMetrics) { - this.state.metrics.userClick = true; - this.state.metrics.pending = true; - this.KubernetesMetricsService.capabilities(this.endpoint.Id) - .then(() => { + return this.$async(async () => { + if (this.formValues.UseServerMetrics) { + this.state.metrics.userClick = true; + this.state.metrics.pending = true; + try { + await getMetricsForAllNodes(this.endpoint.Id); this.state.metrics.isServerRunning = true; this.state.metrics.pending = false; + this.state.metrics.userClick = false; this.formValues.UseServerMetrics = true; - }) - .catch(() => { + } catch (_) { this.state.metrics.isServerRunning = false; this.state.metrics.pending = false; this.formValues.UseServerMetrics = false; - }); - } else { - this.state.metrics.userClick = false; - this.formValues.UseServerMetrics = false; - } + } + } else { + this.state.metrics.userClick = false; + this.formValues.UseServerMetrics = false; + } + }); } async configureAsync() { diff --git a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js index 7eb5c865c..43ead5e22 100644 --- a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js +++ b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js @@ -16,6 +16,7 @@ import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { updateIngressControllerClassMap, getIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/utils'; import { confirmUpdate } from '@@/modals/confirm'; import { confirmUpdateNamespace } from '@/react/kubernetes/namespaces/ItemView/ConfirmUpdateNamespace'; +import { getMetricsForAllNodes, getMetricsForAllPods } from '@/react/kubernetes/services/service.ts'; class KubernetesResourcePoolController { /* #region CONSTRUCTOR */ @@ -28,8 +29,6 @@ class KubernetesResourcePoolController { Notifications, LocalStorage, EndpointService, - KubernetesNodeService, - KubernetesMetricsService, KubernetesResourceQuotaService, KubernetesResourcePoolService, KubernetesEventService, @@ -47,8 +46,6 @@ class KubernetesResourcePoolController { Notifications, LocalStorage, EndpointService, - KubernetesNodeService, - KubernetesMetricsService, KubernetesResourceQuotaService, KubernetesResourcePoolService, KubernetesEventService, @@ -322,7 +319,7 @@ class KubernetesResourcePoolController { async getResourceUsage(namespace) { try { - const namespaceMetrics = await this.KubernetesMetricsService.getPods(namespace); + const namespaceMetrics = await getMetricsForAllPods(this.$state.params.endpointId, namespace); // extract resource usage of all containers within each pod of the namespace const containerResourceUsageList = namespaceMetrics.items.flatMap((i) => i.containers.map((c) => c.usage)); const namespaceResourceUsage = containerResourceUsageList.reduce((total, u) => { @@ -369,7 +366,7 @@ class KubernetesResourcePoolController { const name = this.$state.params.id; - const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get('', { getQuota: true })]); + const [nodes, pools] = await Promise.all([getMetricsForAllNodes, this.KubernetesResourcePoolService.get('', { getQuota: true })]); this.ingressControllers = []; if (this.state.ingressAvailabilityPerNamespace) { diff --git a/app/react/kubernetes/services/service.ts b/app/react/kubernetes/services/service.ts index 6889f0d26..3a2c445e4 100644 --- a/app/react/kubernetes/services/service.ts +++ b/app/react/kubernetes/services/service.ts @@ -6,8 +6,11 @@ import { withError } from '@/react-tools/react-query'; import axios, { parseAxiosError } from '@/portainer/services/axios'; import { EnvironmentId } from '@/react/portainer/environments/types'; import { isFulfilled } from '@/portainer/helpers/promise-utils'; - -import { Service } from './types'; +import { + NodeMetrics, + NodeMetric, + Service, +} from '@/react/kubernetes/services/types'; export const queryKeys = { clusterServices: (environmentId: EnvironmentId) => @@ -105,3 +108,67 @@ export async function deleteServices({ throw parseAxiosError(e as Error, 'Unable to delete service(s)'); } } + +export async function getMetricsForAllNodes(environmentId: EnvironmentId) { + try { + const { data: nodes } = await axios.get( + `kubernetes/${environmentId}/metrics/nodes`, + {} + ); + return nodes; + } catch (e) { + throw parseAxiosError( + e as Error, + 'Unable to retrieve metrics for all nodes' + ); + } +} + +export async function getMetricsForNode( + environmentId: EnvironmentId, + nodeName: string +) { + try { + const { data: node } = await axios.get( + `kubernetes/${environmentId}/metrics/nodes/${nodeName}`, + {} + ); + return node; + } catch (e) { + throw parseAxiosError(e as Error, 'Unable to retrieve metrics for node'); + } +} + +export async function getMetricsForAllPods( + environmentId: EnvironmentId, + namespace: string +) { + try { + const { data: pods } = await axios.get( + `kubernetes/${environmentId}/metrics/pods/namespace/${namespace}`, + {} + ); + return pods; + } catch (e) { + throw parseAxiosError( + e as Error, + 'Unable to retrieve metrics for all pods' + ); + } +} + +export async function getMetricsForPod( + environmentId: EnvironmentId, + namespace: string, + podName: string +) { + try { + const { data: pod } = await axios.get( + `kubernetes/${environmentId}/metrics/pods/namespace/${namespace}/${podName}`, + {} + ); + return pod; + } catch (e) { + throw parseAxiosError(e as Error, 'Unable to retrieve metrics for pod'); + } +} diff --git a/app/react/kubernetes/services/types.ts b/app/react/kubernetes/services/types.ts index 6971b4e4c..53ec3e514 100644 --- a/app/react/kubernetes/services/types.ts +++ b/app/react/kubernetes/services/types.ts @@ -39,3 +39,24 @@ export type Service = { CreationTimestamp: string; Applications?: Application[]; }; + +export type NodeMetrics = { + items: NodeMetric[]; +}; + +export type NodeMetric = { + metadata: NodeMetricMetadata; + timestamp: Date; + usage: Usage; + window: string; +}; + +export type NodeMetricMetadata = { + creationTimestamp: Date; + name: string; +}; + +export type Usage = { + cpu: string; + memory: string; +};