From c8c2953d7bdaf69febe3cda1e12cbe98040a9c50 Mon Sep 17 00:00:00 2001 From: Otto Bittner Date: Fri, 24 Mar 2023 11:51:18 +0100 Subject: [PATCH] cli: add status cmd The new command allows checking the status of an upgrade and which versions are installed. Also remove the unused restclient. And make GetConstellationVersion a function. --- bazel/toolchains/go_module_deps.bzl | 540 +----------------- cli/cmd/root.go | 1 + cli/internal/cloudcmd/BUILD.bazel | 1 + cli/internal/cloudcmd/status.go | 92 +++ cli/internal/cloudcmd/upgrade.go | 95 +-- cli/internal/cloudcmd/upgrade_test.go | 4 +- cli/internal/cmd/BUILD.bazel | 9 + cli/internal/cmd/recover_test.go | 8 +- cli/internal/cmd/status.go | 175 ++++++ cli/internal/cmd/status_test.go | 193 +++++++ cli/internal/cmd/upgradecheck.go | 4 +- cli/internal/helm/client.go | 67 ++- docs/docs/reference/cli.md | 29 + docs/docs/workflows/upgrade.md | 35 ++ go.mod | 3 +- internal/kubernetes/kubectl/BUILD.bazel | 24 +- internal/kubernetes/kubectl/kubectl.go | 49 +- internal/kubernetes/kubectl/restclient.go | 70 --- .../kubernetes/kubectl/restclient_test.go | 143 ----- 19 files changed, 707 insertions(+), 835 deletions(-) create mode 100644 cli/internal/cloudcmd/status.go create mode 100644 cli/internal/cmd/status.go create mode 100644 cli/internal/cmd/status_test.go delete mode 100644 internal/kubernetes/kubectl/restclient.go delete mode 100644 internal/kubernetes/kubectl/restclient_test.go diff --git a/bazel/toolchains/go_module_deps.bzl b/bazel/toolchains/go_module_deps.bzl index df9aa140e..442480a48 100644 --- a/bazel/toolchains/go_module_deps.bzl +++ b/bazel/toolchains/go_module_deps.bzl @@ -71,14 +71,6 @@ def go_dependencies(): sum = "h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=", version = "v0.0.0-20180502004556-fa1af6a1f4f5", ) - go_repository( - name = "com_github_ajg_form", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/ajg/form", - sum = "h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=", - version = "v1.5.1", - ) go_repository( name = "com_github_alcortesm_tgz", @@ -113,14 +105,6 @@ def go_dependencies(): sum = "h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=", version = "v0.0.0-20190924025748-f65c72e2690d", ) - go_repository( - name = "com_github_andreasbriese_bbloom", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/AndreasBriese/bbloom", - sum = "h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=", - version = "v0.0.0-20190306092124-e2d15f34fcf9", - ) go_repository( name = "com_github_andybalholm_brotli", @@ -557,14 +541,6 @@ def go_dependencies(): sum = "h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=", version = "v0.2.0", ) - go_repository( - name = "com_github_aymerick_raymond", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/aymerick/raymond", - sum = "h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns=", - version = "v2.0.3-0.20180322193309-b565731e1464+incompatible", - ) go_repository( name = "com_github_azure_azure_amqp_common_go_v2", @@ -1085,14 +1061,6 @@ def go_dependencies(): sum = "h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=", version = "v5.3.0", ) - go_repository( - name = "com_github_cheekybits_is", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/cheekybits/is", - sum = "h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=", - version = "v0.0.0-20150225183255-68e9c0620927", - ) go_repository( name = "com_github_chzyer_logex", @@ -1166,22 +1134,6 @@ def go_dependencies(): sum = "h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=", version = "v0.0.0-20200109182630-33d98a066a53", ) - go_repository( - name = "com_github_cloudykit_jet", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/CloudyKit/jet", - sum = "h1:rZgFj+Gtf3NMi/U5FvCvhzaxzW/TaPYgUYx3bAPz9DE=", - version = "v2.1.3-0.20180809161101-62edd43e4f88+incompatible", - ) - go_repository( - name = "com_github_cloudykit_jet_v3", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/CloudyKit/jet/v3", - sum = "h1:1PwO5w5VCtlUUl+KTOBsTGZlhjWkcybsGaAau52tOy8=", - version = "v3.0.0", - ) go_repository( name = "com_github_cloudykit_jet_v6", @@ -1246,8 +1198,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/cockroachdb/logtags", - sum = "h1:6jduT9Hfc0njg5jJ1DdKCFPdMBrp/mdZfCpa5h+WM74=", - version = "v0.0.0-20211118104740-dabe8e521a4f", + sum = "h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=", + version = "v0.0.0-20190617123548-eb05cc24525f", ) go_repository( name = "com_github_cockroachdb_redact", @@ -1257,14 +1209,6 @@ def go_dependencies(): sum = "h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=", version = "v1.1.3", ) - go_repository( - name = "com_github_cockroachdb_sentry_go", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/cockroachdb/sentry-go", - sum = "h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM=", - version = "v0.6.1-cockroachdb.2", - ) go_repository( name = "com_github_codahale_hdrhistogram", @@ -1636,30 +1580,6 @@ def go_dependencies(): sum = "h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA=", version = "v0.1.1", ) - go_repository( - name = "com_github_dgraph_io_badger", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/dgraph-io/badger", - sum = "h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Evo=", - version = "v1.6.0", - ) - go_repository( - name = "com_github_dgraph_io_badger_v2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/dgraph-io/badger/v2", - sum = "h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=", - version = "v2.2007.4", - ) - go_repository( - name = "com_github_dgraph_io_ristretto", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/dgraph-io/ristretto", - sum = "h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=", - version = "v0.0.3-0.20200630154024-f66de99634de", - ) go_repository( name = "com_github_dgrijalva_jwt_go", @@ -1669,14 +1589,6 @@ def go_dependencies(): sum = "h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=", version = "v3.2.0+incompatible", ) - go_repository( - name = "com_github_dgryski_go_farm", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/dgryski/go-farm", - sum = "h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=", - version = "v0.0.0-20190423205320-6a90982ecee2", - ) go_repository( name = "com_github_dgryski_go_rendezvous", @@ -1711,14 +1623,6 @@ def go_dependencies(): sum = "h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=", version = "v3.0.0-20221208165359-362910506bc2", ) - go_repository( - name = "com_github_djherbis_atime", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/djherbis/atime", - sum = "h1:rgwVbP/5by8BvvjBNrbh64Qz33idKT3pSnMSJsxhi0g=", - version = "v1.1.0", - ) go_repository( name = "com_github_dnaeon_go_vcr", @@ -1923,14 +1827,6 @@ def go_dependencies(): sum = "h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY=", version = "v0.9.1", ) - go_repository( - name = "com_github_etcd_io_bbolt", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/etcd-io/bbolt", - sum = "h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=", - version = "v1.3.3", - ) go_repository( name = "com_github_etcd_io_gofail", @@ -1996,14 +1892,6 @@ def go_dependencies(): sum = "h1:a4DFiKFJiDRGFD1qIcqGLX/WlUMD9dyLSLDt+9QZgt8=", version = "v0.0.0-20150708232844-fd3d7953fd52", ) - go_repository( - name = "com_github_fasthttp_contrib_websocket", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/fasthttp-contrib/websocket", - sum = "h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc=", - version = "v0.0.0-20160511215533-1f3b11f56072", - ) go_repository( name = "com_github_fatih_camelcase", @@ -2038,14 +1926,6 @@ def go_dependencies(): sum = "h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=", version = "v1.0.3", ) - go_repository( - name = "com_github_flosch_pongo2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/flosch/pongo2", - sum = "h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc=", - version = "v0.0.0-20190707114632-bbf5a6c351f4", - ) go_repository( name = "com_github_flosch_pongo2_v4", @@ -2146,14 +2026,6 @@ def go_dependencies(): sum = "h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=", version = "v2.4.0", ) - go_repository( - name = "com_github_gavv_httpexpect", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gavv/httpexpect", - sum = "h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=", - version = "v2.0.0+incompatible", - ) go_repository( name = "com_github_gertd_go_pluralize", @@ -2213,14 +2085,6 @@ def go_dependencies(): sum = "h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=", version = "v0.3.5", ) - go_repository( - name = "com_github_go_check_check", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/go-check/check", - sum = "h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=", - version = "v0.0.0-20180628173108-788fd7840127", - ) go_repository( name = "com_github_go_chi_chi", @@ -2366,14 +2230,6 @@ def go_dependencies(): sum = "h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=", version = "v0.0.0-20170121215854-22fa46961aab", ) - go_repository( - name = "com_github_go_ole_go_ole", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/go-ole/go-ole", - sum = "h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=", - version = "v1.2.6", - ) go_repository( name = "com_github_go_openapi_analysis", @@ -2648,30 +2504,6 @@ def go_dependencies(): sum = "h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=", version = "v0.2.3", ) - go_repository( - name = "com_github_gobwas_httphead", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gobwas/httphead", - sum = "h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - version = "v0.1.0", - ) - go_repository( - name = "com_github_gobwas_pool", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gobwas/pool", - sum = "h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - version = "v0.2.1", - ) - go_repository( - name = "com_github_gobwas_ws", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gobwas/ws", - sum = "h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=", - version = "v1.1.0", - ) go_repository( name = "com_github_goccy_go_json", @@ -2727,8 +2559,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/gogo/googleapis", - sum = "h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=", - version = "v1.4.1", + sum = "h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI=", + version = "v1.4.0", ) go_repository( name = "com_github_gogo_protobuf", @@ -2738,14 +2570,6 @@ def go_dependencies(): sum = "h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", version = "v1.3.2", ) - go_repository( - name = "com_github_gogo_status", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gogo/status", - sum = "h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA=", - version = "v1.1.0", - ) go_repository( name = "com_github_golang_glog", @@ -2768,8 +2592,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/golang-jwt/jwt", - sum = "h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=", - version = "v3.2.2+incompatible", + sum = "h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=", + version = "v3.2.1+incompatible", ) go_repository( name = "com_github_golang_jwt_jwt_v4", @@ -2921,8 +2745,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/google/go-querystring", - sum = "h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=", - version = "v1.1.0", + sum = "h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=", + version = "v1.0.0", ) go_repository( name = "com_github_google_go_replayers_grpcreplay", @@ -3173,8 +2997,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/gopherjs/gopherjs", - sum = "h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=", - version = "v1.17.2", + sum = "h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=", + version = "v0.0.0-20181017120253-0766667cb4d1", ) go_repository( name = "com_github_gordonklaus_ineffassign", @@ -3233,22 +3057,14 @@ def go_dependencies(): sum = "h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=", version = "v1.8.0", ) - go_repository( - name = "com_github_gorilla_securecookie", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/gorilla/securecookie", - sum = "h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=", - version = "v1.1.1", - ) go_repository( name = "com_github_gorilla_websocket", build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/gorilla/websocket", - sum = "h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=", - version = "v1.5.0", + sum = "h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=", + version = "v1.4.2", ) go_repository( name = "com_github_gosuri_uitable", @@ -3625,14 +3441,6 @@ def go_dependencies(): sum = "h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w=", version = "v1.3.0", ) - go_repository( - name = "com_github_hydrogen18_memlistener", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/hydrogen18/memlistener", - sum = "h1:KyZDvZ/GGn+r+Y3DKZ7UOQ/TP4xV6HNkrwiVMB1GnNY=", - version = "v0.0.0-20200120041712-dcc25e7acd91", - ) go_repository( name = "com_github_iancoleman_strcase", @@ -3659,14 +3467,6 @@ def go_dependencies(): sum = "h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=", version = "v0.3.13", ) - go_repository( - name = "com_github_imkira_go_interpol", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/imkira/go-interpol", - sum = "h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=", - version = "v1.1.0", - ) go_repository( name = "com_github_in_toto_in_toto_golang", @@ -3702,38 +3502,6 @@ def go_dependencies(): sum = "h1:JyZjdMQu9Kl/wLXe9xA6s1X+tF6BWsQPFGJMEeCfWzE=", version = "v0.2.0", ) - go_repository( - name = "com_github_iris_contrib_blackfriday", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/iris-contrib/blackfriday", - sum = "h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4=", - version = "v2.0.0+incompatible", - ) - go_repository( - name = "com_github_iris_contrib_go_uuid", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/iris-contrib/go.uuid", - sum = "h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE=", - version = "v2.0.0+incompatible", - ) - go_repository( - name = "com_github_iris_contrib_httpexpect_v2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/iris-contrib/httpexpect/v2", - sum = "h1:A69ilxKGW1jDRKK5UAhjTL4uJYh3RjD4qzt9vNZ7fpY=", - version = "v2.3.1", - ) - go_repository( - name = "com_github_iris_contrib_i18n", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/iris-contrib/i18n", - sum = "h1:Kyp9KiXwsyZRTeoNjgVCrWks7D8ht9+kg6yCjh8K97o=", - version = "v0.0.0-20171121225848-987a633949d0", - ) go_repository( name = "com_github_iris_contrib_jade", @@ -3743,14 +3511,6 @@ def go_dependencies(): sum = "h1:WoYdfyJFfZIUgqNAeOyRfTNQZOksSlZ6+FnXR3AEpX0=", version = "v1.1.4", ) - go_repository( - name = "com_github_iris_contrib_pongo2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/iris-contrib/pongo2", - sum = "h1:zGP7pW51oi5eQZMIlGA3I+FHY9/HOQWDB+572yin0to=", - version = "v0.0.1", - ) go_repository( name = "com_github_iris_contrib_schema", @@ -3996,22 +3756,6 @@ def go_dependencies(): sum = "h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=", version = "v1.3.0", ) - go_repository( - name = "com_github_joker_hpp", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/Joker/hpp", - sum = "h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=", - version = "v1.0.0", - ) - go_repository( - name = "com_github_joker_jade", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/Joker/jade", - sum = "h1:mreN1m/5VJ/Zc3b4pzj9qU6D9SRQ6Vm+3KfI328t3S8=", - version = "v1.0.1-0.20190614124447-d475f43051e7", - ) go_repository( name = "com_github_jonboulle_clockwork", @@ -4077,22 +3821,6 @@ def go_dependencies(): sum = "h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=", version = "v4.20.0+incompatible", ) - go_repository( - name = "com_github_juju_errors", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/juju/errors", - sum = "h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok=", - version = "v0.0.0-20181118221551-089d3ea4e4d5", - ) - go_repository( - name = "com_github_juju_loggo", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/juju/loggo", - sum = "h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8=", - version = "v0.0.0-20180524022052-584905176618", - ) go_repository( name = "com_github_juju_ratelimit", @@ -4102,14 +3830,6 @@ def go_dependencies(): sum = "h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=", version = "v1.0.1", ) - go_repository( - name = "com_github_juju_testing", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/juju/testing", - sum = "h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs=", - version = "v0.0.0-20180920084828-472a3e8b2073", - ) go_repository( name = "com_github_julienschmidt_httprouter", @@ -4119,14 +3839,6 @@ def go_dependencies(): sum = "h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=", version = "v1.3.0", ) - go_repository( - name = "com_github_k0kubun_colorstring", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/k0kubun/colorstring", - sum = "h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=", - version = "v0.0.0-20150214042306-9440f1994b88", - ) go_repository( name = "com_github_k0kubun_go_ansi", @@ -4168,22 +3880,6 @@ def go_dependencies(): sum = "h1:grB/oCf5baZhmYIeDMfgN3LYrtEcmK8pbxlRvEZ2pgw=", version = "v12.2.0-beta5", ) - go_repository( - name = "com_github_kataras_jwt", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/kataras/jwt", - sum = "h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk=", - version = "v0.1.8", - ) - go_repository( - name = "com_github_kataras_neffos", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/kataras/neffos", - sum = "h1:swTzKZ3Mo2sIQ8ATKSKf0xDG1tuhr6w4tZmmRsvCYlg=", - version = "v0.0.20", - ) go_repository( name = "com_github_kataras_pio", @@ -4259,14 +3955,6 @@ def go_dependencies(): sum = "h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=", version = "v1.15.12", ) - go_repository( - name = "com_github_klauspost_cpuid", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/klauspost/cpuid", - sum = "h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=", - version = "v1.2.1", - ) go_repository( name = "com_github_klauspost_cpuid_v2", @@ -4473,14 +4161,6 @@ def go_dependencies(): sum = "h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=", version = "v1.1.0", ) - go_repository( - name = "com_github_lufia_plan9stats", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/lufia/plan9stats", - sum = "h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=", - version = "v0.0.0-20211012122336-39d0f177ccd0", - ) go_repository( name = "com_github_lyft_protoc_gen_star", @@ -4633,14 +4313,6 @@ def go_dependencies(): sum = "h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=", version = "v1.2.0", ) - go_repository( - name = "com_github_matryer_try", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/matryer/try", - sum = "h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o=", - version = "v0.0.0-20161228173917-9ac251b645a2", - ) go_repository( name = "com_github_mattn_go_colorable", @@ -4706,14 +4378,6 @@ def go_dependencies(): sum = "h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=", version = "v0.0.1", ) - go_repository( - name = "com_github_mattn_goveralls", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/mattn/goveralls", - sum = "h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc=", - version = "v0.0.2", - ) go_repository( name = "com_github_matttproud_golang_protobuf_extensions", @@ -4755,22 +4419,6 @@ def go_dependencies(): sum = "h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=", version = "v0.4.0", ) - go_repository( - name = "com_github_mediocregopher_mediocre_go_lib", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/mediocregopher/mediocre-go-lib", - sum = "h1:3dQJqqDouawQgl3gBE1PNHKFkJYGEuFb1DbSlaxdosE=", - version = "v0.0.0-20181029021733-cb65787f37ed", - ) - go_repository( - name = "com_github_mediocregopher_radix_v3", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/mediocregopher/radix/v3", - sum = "h1:HI8EgkaM7WzsrFpYAkOXIgUKbjNonb2Ne7K6Le61Pmg=", - version = "v3.8.0", - ) go_repository( name = "com_github_mediocregopher_radix_v4", @@ -4861,14 +4509,6 @@ def go_dependencies(): sum = "h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=", version = "v0.0.0-20190812172519-36a3d3bbc4f3", ) - go_repository( - name = "com_github_minio_highwayhash", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/minio/highwayhash", - sum = "h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=", - version = "v1.0.2", - ) go_repository( name = "com_github_mistifyio_go_zfs", @@ -5071,14 +4711,6 @@ def go_dependencies(): sum = "h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=", version = "v1.0.0", ) - go_repository( - name = "com_github_moul_http2curl", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/moul/http2curl", - sum = "h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=", - version = "v1.0.0", - ) go_repository( name = "com_github_mr_tron_base58", @@ -5138,22 +4770,14 @@ def go_dependencies(): sum = "h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=", version = "v0.3.2", ) - go_repository( - name = "com_github_nats_io_jwt_v2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/nats-io/jwt/v2", - sum = "h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=", - version = "v2.3.0", - ) go_repository( name = "com_github_nats_io_nats_go", build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/nats-io/nats.go", - sum = "h1:zvLE7fGBQYW6MWaFaRdsgm9qT39PJDQoju+DS8KsO1g=", - version = "v1.16.0", + sum = "h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=", + version = "v1.9.1", ) go_repository( name = "com_github_nats_io_nats_server_v2", @@ -5168,8 +4792,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/nats-io/nkeys", - sum = "h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=", - version = "v0.3.0", + sum = "h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=", + version = "v0.1.3", ) go_repository( name = "com_github_nats_io_nuid", @@ -5569,14 +5193,6 @@ def go_dependencies(): sum = "h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=", version = "v1.1.1", ) - go_repository( - name = "com_github_power_devops_perfstat", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/power-devops/perfstat", - sum = "h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=", - version = "v0.0.0-20210106213030-5aafc221ea8c", - ) go_repository( name = "com_github_poy_onpar", @@ -5819,8 +5435,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/ryanuber/columnize", - sum = "h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=", - version = "v2.1.0+incompatible", + sum = "h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M=", + version = "v0.0.0-20160712163229-9b3edd62028f", ) go_repository( name = "com_github_ryanuber_go_glob", @@ -5882,14 +5498,6 @@ def go_dependencies(): sum = "h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=", version = "v3.13.1", ) - go_repository( - name = "com_github_sclevine_agouti", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/sclevine/agouti", - sum = "h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4=", - version = "v3.0.0+incompatible", - ) go_repository( name = "com_github_sean_seed", @@ -5947,14 +5555,6 @@ def go_dependencies(): sum = "h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=", version = "v1.3.0", ) - go_repository( - name = "com_github_shirou_gopsutil_v3", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/shirou/gopsutil/v3", - sum = "h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=", - version = "v3.22.8", - ) go_repository( name = "com_github_shopify_goreferrer", @@ -6125,8 +5725,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/smartystreets/assertions", - sum = "h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs=", - version = "v1.13.0", + sum = "h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=", + version = "v1.0.0", ) go_repository( name = "com_github_smartystreets_go_aws_auth", @@ -6331,14 +5931,6 @@ def go_dependencies(): sum = "h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ=", version = "v2.6.4", ) - go_repository( - name = "com_github_tdewolff_test", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/tdewolff/test", - sum = "h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=", - version = "v1.0.7", - ) go_repository( name = "com_github_tedsuo_ifrit", @@ -6420,22 +6012,6 @@ def go_dependencies(): sum = "h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=", version = "v1.1.0", ) - go_repository( - name = "com_github_tklauser_go_sysconf", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/tklauser/go-sysconf", - sum = "h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=", - version = "v0.3.10", - ) - go_repository( - name = "com_github_tklauser_numcpus", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/tklauser/numcpus", - sum = "h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=", - version = "v0.4.0", - ) go_repository( name = "com_github_tmc_grpc_websocket_proxy", @@ -6466,8 +6042,8 @@ def go_dependencies(): build_file_generation = "on", build_file_proto_mode = "disable_global", importpath = "github.com/ugorji/go", - sum = "h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=", - version = "v1.2.7", + sum = "h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=", + version = "v1.1.7", ) go_repository( name = "com_github_ugorji_go_codec", @@ -6525,14 +6101,6 @@ def go_dependencies(): sum = "h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=", version = "v1.2.1", ) - go_repository( - name = "com_github_valyala_tcplisten", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/valyala/tcplisten", - sum = "h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=", - version = "v1.0.0", - ) go_repository( name = "com_github_vbatts_tar_split", @@ -6735,14 +6303,6 @@ def go_dependencies(): sum = "h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=", version = "v0.0.3-0.20170626215501-b2862e3d0a77", ) - go_repository( - name = "com_github_yalp_jsonpath", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/yalp/jsonpath", - sum = "h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=", - version = "v0.0.0-20180802001716-5cc68e5049a0", - ) go_repository( name = "com_github_yosssi_ace", @@ -6785,30 +6345,6 @@ def go_dependencies(): sum = "h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=", version = "v0.8.0", ) - go_repository( - name = "com_github_yudai_gojsondiff", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/yudai/gojsondiff", - sum = "h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=", - version = "v1.0.0", - ) - go_repository( - name = "com_github_yudai_golcs", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/yudai/golcs", - sum = "h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=", - version = "v0.0.0-20170316035057-ecda9a501e82", - ) - go_repository( - name = "com_github_yudai_pp", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/yudai/pp", - sum = "h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=", - version = "v2.0.1+incompatible", - ) go_repository( name = "com_github_yuin_goldmark", @@ -6818,14 +6354,6 @@ def go_dependencies(): sum = "h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=", version = "v1.4.13", ) - go_repository( - name = "com_github_yusufpapurcu_wmi", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "github.com/yusufpapurcu/wmi", - sum = "h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=", - version = "v1.2.2", - ) go_repository( name = "com_github_yvasiyarov_go_metrics", @@ -8015,14 +7543,6 @@ def go_dependencies(): sum = "h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=", version = "v1.2.1", ) - go_repository( - name = "in_gopkg_go_playground_validator_v8", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "gopkg.in/go-playground/validator.v8", - sum = "h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=", - version = "v8.18.2", - ) go_repository( name = "in_gopkg_go_playground_validator_v9", @@ -8065,14 +7585,6 @@ def go_dependencies(): sum = "h1:BJa69CDh0awSsLUmZ9+BowBdokpduDZSM9Zk8oKHfN4=", version = "v1.0.5", ) - go_repository( - name = "in_gopkg_mgo_v2", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "gopkg.in/mgo.v2", - sum = "h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=", - version = "v2.0.0-20180705113604-9856a29383ce", - ) go_repository( name = "in_gopkg_natefinch_lumberjack_v2", @@ -8642,14 +8154,6 @@ def go_dependencies(): sum = "h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY=", version = "v0.0.0-20230313181309-38a27ef9d749", ) - go_repository( - name = "io_moul_http2curl", - build_file_generation = "on", - build_file_proto_mode = "disable_global", - importpath = "moul.io/http2curl", - sum = "h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=", - version = "v1.0.0", - ) go_repository( name = "io_opencensus_go", diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 55ad00358..e916738b8 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -59,6 +59,7 @@ func NewRootCmd() *cobra.Command { rootCmd.AddCommand(cmd.NewTerminateCmd()) rootCmd.AddCommand(cmd.NewVersionCmd()) rootCmd.AddCommand(cmd.NewIAMCmd()) + rootCmd.AddCommand(cmd.NewStatusCmd()) return rootCmd } diff --git a/cli/internal/cloudcmd/BUILD.bazel b/cli/internal/cloudcmd/BUILD.bazel index 43355bab4..9a4974c77 100644 --- a/cli/internal/cloudcmd/BUILD.bazel +++ b/cli/internal/cloudcmd/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "create.go", "iam.go", "rollback.go", + "status.go", "terminate.go", "upgrade.go", "validators.go", diff --git a/cli/internal/cloudcmd/status.go b/cli/internal/cloudcmd/status.go new file mode 100644 index 000000000..0e62294dc --- /dev/null +++ b/cli/internal/cloudcmd/status.go @@ -0,0 +1,92 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package cloudcmd + +import ( + "context" + "fmt" + + updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +// TargetVersions bundles version information about the target versions of a cluster. +type TargetVersions struct { + // image version + image string + // CSP specific path to the image + imageReference string + // kubernetes version + kubernetes string +} + +// NewTargetVersions returns the target versions for the cluster. +func NewTargetVersions(nodeVersion updatev1alpha1.NodeVersion) (TargetVersions, error) { + return TargetVersions{ + image: nodeVersion.Spec.ImageVersion, + imageReference: nodeVersion.Spec.ImageReference, + kubernetes: nodeVersion.Spec.KubernetesClusterVersion, + }, nil +} + +// Image return the image version. +func (c *TargetVersions) Image() string { + return c.image +} + +// ImagePath return the image path. +func (c *TargetVersions) ImagePath() string { + return c.imageReference +} + +// Kubernetes return the Kubernetes version. +func (c *TargetVersions) Kubernetes() string { + return c.kubernetes +} + +// ClusterStatus returns a map from node name to NodeStatus. +func ClusterStatus(ctx context.Context, kubeclient kubeClient) (map[string]NodeStatus, error) { + nodes, err := kubeclient.GetNodes(ctx) + if err != nil { + return nil, fmt.Errorf("getting nodes: %w", err) + } + + clusterStatus := map[string]NodeStatus{} + for _, node := range nodes { + clusterStatus[node.ObjectMeta.Name] = NewNodeStatus(node) + } + + return clusterStatus, nil +} + +// NodeStatus bundles status information about a node. +type NodeStatus struct { + kubeletVersion string + imageVersion string +} + +// NewNodeStatus returns a new NodeStatus. +func NewNodeStatus(node corev1.Node) NodeStatus { + return NodeStatus{ + kubeletVersion: node.Status.NodeInfo.KubeletVersion, + imageVersion: node.ObjectMeta.Annotations["constellation.edgeless.systems/node-image"], + } +} + +// KubeletVersion returns the kubelet version of the node. +func (n *NodeStatus) KubeletVersion() string { + return n.kubeletVersion +} + +// ImageVersion returns the node image of the node. +func (n *NodeStatus) ImageVersion() string { + return n.imageVersion +} + +type kubeClient interface { + GetNodes(ctx context.Context) ([]corev1.Node, error) +} diff --git a/cli/internal/cloudcmd/upgrade.go b/cli/internal/cloudcmd/upgrade.go index 6566df901..8609cc3df 100644 --- a/cli/internal/cloudcmd/upgrade.go +++ b/cli/internal/cloudcmd/upgrade.go @@ -40,10 +40,24 @@ import ( // ErrInProgress signals that an upgrade is in progress inside the cluster. var ErrInProgress = errors.New("upgrade in progress") +// GetConstellationVersion queries the constellation-version object for a given field. +func GetConstellationVersion(ctx context.Context, client DynamicInterface) (updatev1alpha1.NodeVersion, error) { + raw, err := client.GetCurrent(ctx, "constellation-version") + if err != nil { + return updatev1alpha1.NodeVersion{}, err + } + var nodeVersion updatev1alpha1.NodeVersion + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.UnstructuredContent(), &nodeVersion); err != nil { + return updatev1alpha1.NodeVersion{}, fmt.Errorf("converting unstructured to NodeVersion: %w", err) + } + + return nodeVersion, nil +} + // Upgrader handles upgrading the cluster's components using the CLI. type Upgrader struct { stableInterface stableInterface - dynamicInterface dynamicInterface + dynamicInterface DynamicInterface helmClient helmInterface imageFetcher imageFetcher outWriter io.Writer @@ -75,7 +89,7 @@ func NewUpgrader(outWriter io.Writer, log debugLog) (*Upgrader, error) { return &Upgrader{ stableInterface: &stableClient{client: kubeClient}, - dynamicInterface: &dynamicClient{client: unstructuredClient}, + dynamicInterface: &NodeVersionClient{client: unstructuredClient}, helmClient: helmClient, imageFetcher: image.New(), outWriter: outWriter, @@ -168,7 +182,7 @@ func (u *Upgrader) KubernetesVersion() (string, error) { // CurrentImage returns the currently used image version of the cluster. func (u *Upgrader) CurrentImage(ctx context.Context) (string, error) { - nodeVersion, err := u.getConstellationVersion(ctx) + nodeVersion, err := GetConstellationVersion(ctx, u.dynamicInterface) if err != nil { return "", fmt.Errorf("getting constellation-version: %w", err) } @@ -177,7 +191,7 @@ func (u *Upgrader) CurrentImage(ctx context.Context) (string, error) { // CurrentKubernetesVersion returns the currently used Kubernetes version. func (u *Upgrader) CurrentKubernetesVersion(ctx context.Context) (string, error) { - nodeVersion, err := u.getConstellationVersion(ctx) + nodeVersion, err := GetConstellationVersion(ctx, u.dynamicInterface) if err != nil { return "", fmt.Errorf("getting constellation-version: %w", err) } @@ -248,7 +262,7 @@ func (u *Upgrader) applyNodeVersion(ctx context.Context, nodeVersion updatev1alp } u.log.Debugf("Triggering NodeVersion upgrade now") // Send the updated NodeVersion resource - updated, err := u.dynamicInterface.update(ctx, &unstructured.Unstructured{Object: raw}) + updated, err := u.dynamicInterface.Update(ctx, &unstructured.Unstructured{Object: raw}) if err != nil { return updatev1alpha1.NodeVersion{}, fmt.Errorf("updating NodeVersion: %w", err) } @@ -262,7 +276,7 @@ func (u *Upgrader) applyNodeVersion(ctx context.Context, nodeVersion updatev1alp } func (u *Upgrader) checkClusterStatus(ctx context.Context) (updatev1alpha1.NodeVersion, error) { - nodeVersion, err := u.getConstellationVersion(ctx) + nodeVersion, err := GetConstellationVersion(ctx, u.dynamicInterface) if err != nil { return updatev1alpha1.NodeVersion{}, fmt.Errorf("retrieving current image: %w", err) } @@ -306,18 +320,38 @@ func (u *Upgrader) updateK8s(nodeVersion *updatev1alpha1.NodeVersion, newCluster return &configMap, nil } -// getFromConstellationVersion queries the constellation-version object for a given field. -func (u *Upgrader) getConstellationVersion(ctx context.Context) (updatev1alpha1.NodeVersion, error) { - raw, err := u.dynamicInterface.getCurrent(ctx, "constellation-version") - if err != nil { - return updatev1alpha1.NodeVersion{}, err - } - var nodeVersion updatev1alpha1.NodeVersion - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.UnstructuredContent(), &nodeVersion); err != nil { - return updatev1alpha1.NodeVersion{}, fmt.Errorf("converting unstructured to NodeVersion: %w", err) - } +// NodeVersionClient implements the DynamicInterface interface to interact with NodeVersion objects. +type NodeVersionClient struct { + client dynamic.Interface +} - return nodeVersion, nil +// NewNodeVersionClient returns a new NodeVersionClient. +func NewNodeVersionClient(client dynamic.Interface) *NodeVersionClient { + return &NodeVersionClient{client: client} +} + +// GetCurrent returns the current NodeVersion object. +func (u *NodeVersionClient) GetCurrent(ctx context.Context, name string) (*unstructured.Unstructured, error) { + return u.client.Resource(schema.GroupVersionResource{ + Group: "update.edgeless.systems", + Version: "v1alpha1", + Resource: "nodeversions", + }).Get(ctx, name, metav1.GetOptions{}) +} + +// Update updates the NodeVersion object. +func (u *NodeVersionClient) Update(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return u.client.Resource(schema.GroupVersionResource{ + Group: "update.edgeless.systems", + Version: "v1alpha1", + Resource: "nodeversions", + }).Update(ctx, obj, metav1.UpdateOptions{}) +} + +// DynamicInterface is a general interface to query custom resources. +type DynamicInterface interface { + GetCurrent(ctx context.Context, name string) (*unstructured.Unstructured, error) + Update(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) } // upgradeInProgress checks if an upgrade is in progress. @@ -338,11 +372,6 @@ func upgradeInProgress(nodeVersion updatev1alpha1.NodeVersion) bool { return false } -type dynamicInterface interface { - getCurrent(ctx context.Context, name string) (*unstructured.Unstructured, error) - update(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) -} - type stableInterface interface { getCurrentConfigMap(ctx context.Context, name string) (*corev1.ConfigMap, error) updateConfigMap(ctx context.Context, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) @@ -350,28 +379,6 @@ type stableInterface interface { kubernetesVersion() (string, error) } -type dynamicClient struct { - client dynamic.Interface -} - -// getCurrent returns the current image definition. -func (u *dynamicClient) getCurrent(ctx context.Context, name string) (*unstructured.Unstructured, error) { - return u.client.Resource(schema.GroupVersionResource{ - Group: "update.edgeless.systems", - Version: "v1alpha1", - Resource: "nodeversions", - }).Get(ctx, name, metav1.GetOptions{}) -} - -// update updates the image definition. -func (u *dynamicClient) update(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return u.client.Resource(schema.GroupVersionResource{ - Group: "update.edgeless.systems", - Version: "v1alpha1", - Resource: "nodeversions", - }).Update(ctx, obj, metav1.UpdateOptions{}) -} - type stableClient struct { client kubernetes.Interface } diff --git a/cli/internal/cloudcmd/upgrade_test.go b/cli/internal/cloudcmd/upgrade_test.go index 7e136d10b..5c0c20534 100644 --- a/cli/internal/cloudcmd/upgrade_test.go +++ b/cli/internal/cloudcmd/upgrade_test.go @@ -426,11 +426,11 @@ type stubDynamicClient struct { updateErr error } -func (u *stubDynamicClient) getCurrent(_ context.Context, _ string) (*unstructured.Unstructured, error) { +func (u *stubDynamicClient) GetCurrent(_ context.Context, _ string) (*unstructured.Unstructured, error) { return u.object, u.getErr } -func (u *stubDynamicClient) update(_ context.Context, updatedObject *unstructured.Unstructured) (*unstructured.Unstructured, error) { +func (u *stubDynamicClient) Update(_ context.Context, updatedObject *unstructured.Unstructured) (*unstructured.Unstructured, error) { u.updatedObject = updatedObject return u.updatedObject, u.updateErr } diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index b2717126a..ac49e963e 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -21,6 +21,7 @@ go_library( "miniup.go", "recover.go", "spinner.go", + "status.go", "terminate.go", "upgrade.go", "upgradeapply.go", @@ -67,6 +68,7 @@ go_library( "//internal/versions", "//internal/versionsapi", "//internal/versionsapi/fetcher", + "//operators/constellation-node-operator/api/v1alpha1", "//verify/verifyproto", "@com_github_mattn_go_isatty//:go-isatty", "@com_github_siderolabs_talos_pkg_machinery//config/encoder", @@ -74,6 +76,7 @@ go_library( "@com_github_spf13_cobra//:cobra", "@io_k8s_api//core/v1:core", "@io_k8s_apimachinery//pkg/runtime", + "@io_k8s_client_go//dynamic", "@io_k8s_client_go//tools/clientcmd", "@io_k8s_client_go//tools/clientcmd/api/latest", "@io_k8s_sigs_yaml//:yaml", @@ -97,6 +100,7 @@ go_test( "init_test.go", "recover_test.go", "spinner_test.go", + "status_test.go", "terminate_test.go", "upgradeapply_test.go", "upgradecheck_test.go", @@ -111,6 +115,7 @@ go_test( "//bootstrapper/initproto", "//cli/internal/cloudcmd", "//cli/internal/clusterid", + "//cli/internal/helm", "//cli/internal/iamid", "//disk-mapper/recoverproto", "//internal/atls", @@ -130,6 +135,7 @@ go_test( "//internal/variant", "//internal/versions", "//internal/versionsapi", + "//operators/constellation-node-operator/api/v1alpha1", "//verify/verifyproto", "@com_github_spf13_afero//:afero", "@com_github_spf13_cobra//:cobra", @@ -137,6 +143,9 @@ go_test( "@com_github_stretchr_testify//require", "@in_gopkg_yaml_v3//:yaml_v3", "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured", + "@io_k8s_apimachinery//pkg/runtime", "@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//status", diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go index 39d6c28a6..9e05266d6 100644 --- a/cli/internal/cmd/recover_test.go +++ b/cli/internal/cmd/recover_test.go @@ -33,7 +33,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + grpcstatus "google.golang.org/grpc/status" ) func TestRecoverCmdArgumentValidation(t *testing.T) { @@ -63,8 +63,8 @@ func TestRecoverCmdArgumentValidation(t *testing.T) { func TestRecover(t *testing.T) { someErr := errors.New("error") - unavailableErr := status.Error(codes.Unavailable, "unavailable") - lbErr := status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: read tcp`) + unavailableErr := grpcstatus.Error(codes.Unavailable, "unavailable") + lbErr := grpcstatus.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: read tcp`) testCases := map[string]struct { doer *stubDoer @@ -336,7 +336,7 @@ func (d *stubDoer) Do(context.Context) error { if len(d.returns) > 1 { d.returns = d.returns[1:] } else { - d.returns = []error{status.Error(codes.Unavailable, "unavailable")} + d.returns = []error{grpcstatus.Error(codes.Unavailable, "unavailable")} } return err } diff --git a/cli/internal/cmd/status.go b/cli/internal/cmd/status.go new file mode 100644 index 000000000..27253d4c1 --- /dev/null +++ b/cli/internal/cmd/status.go @@ -0,0 +1,175 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package cmd + +import ( + "context" + "fmt" + "strings" + + "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/helm" + "github.com/edgelesssys/constellation/v2/internal/constants" + "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" + "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" + "github.com/spf13/afero" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/clientcmd" +) + +// NewStatusCmd returns a new cobra.Command for the statuus command. +func NewStatusCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "status", + Short: "show status of a Constellation cluster", + Long: "Show status of a constellation cluster.\n\n" + + "Shows microservice, image and Kubernetes versions installed in the cluster. Also show status of current version upgrades.", + Args: cobra.NoArgs, + RunE: runStatus, + } + return cmd +} + +// runStatus runs the terminate command. +func runStatus(cmd *cobra.Command, args []string) error { + log, err := newCLILogger(cmd) + if err != nil { + return fmt.Errorf("creating logger: %w", err) + } + defer log.Sync() + + kubeClient := kubectl.New() + + fileHandler := file.NewHandler(afero.NewOsFs()) + kubeConfig, err := fileHandler.Read(constants.AdminConfFilename) + if err != nil { + return fmt.Errorf("reading admin.conf: %w", err) + } + + // need kubectl client to fetch nodes. + if err := kubeClient.Initialize(kubeConfig); err != nil { + return err + } + + restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfig) + if err != nil { + return fmt.Errorf("creating k8s client config from kubeconfig: %w", err) + } + // need unstructed client to fetch NodeVersion CRD. + unstructuredClient, err := dynamic.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("setting up custom resource client: %w", err) + } + + // need helm client to fetch service versions. + helmClient, err := helm.NewClient(kubectl.New(), constants.AdminConfFilename, constants.HelmNamespace, log) + if err != nil { + return fmt.Errorf("setting up helm client: %w", err) + } + + output, err := status(cmd.Context(), kubeClient, helmClient, cloudcmd.NewNodeVersionClient(unstructuredClient)) + if err != nil { + return fmt.Errorf("getting status: %w", err) + } + + cmd.Print(output) + return nil +} + +// status queries the cluster for the relevant status information and returns the output string. +func status(ctx context.Context, kubeClient kubeClient, helmClient helmClient, dynamicInterface cloudcmd.DynamicInterface) (string, error) { + nodeVersion, err := cloudcmd.GetConstellationVersion(ctx, dynamicInterface) + if err != nil { + return "", fmt.Errorf("getting constellation version: %w", err) + } + if len(nodeVersion.Status.Conditions) != 1 { + return "", fmt.Errorf("expected exactly one condition, got %d", len(nodeVersion.Status.Conditions)) + } + + targetVersions, err := cloudcmd.NewTargetVersions(nodeVersion) + if err != nil { + return "", fmt.Errorf("getting configured versions: %w", err) + } + + serviceVersions, err := helmClient.Versions() + if err != nil { + return "", fmt.Errorf("getting service versions: %w", err) + } + + status, err := cloudcmd.ClusterStatus(ctx, kubeClient) + if err != nil { + return "", fmt.Errorf("getting cluster status: %w", err) + } + + return statusOutput(targetVersions, serviceVersions, status, nodeVersion), nil +} + +// statusOutput creates the status cmd output string by formatting the received information. +func statusOutput(targetVersions cloudcmd.TargetVersions, serviceVersions helm.ServiceVersions, status map[string]cloudcmd.NodeStatus, nodeVersion v1alpha1.NodeVersion) string { + builder := strings.Builder{} + + builder.WriteString(targetVersionsString(targetVersions)) + builder.WriteString(serviceVersionsString(serviceVersions)) + builder.WriteString(fmt.Sprintf("Cluster status: %s\n", nodeVersion.Status.Conditions[0].Message)) + builder.WriteString(nodeStatusString(status, targetVersions)) + + return builder.String() +} + +// nodeStatusString creates the node status part of the output string. +func nodeStatusString(status map[string]cloudcmd.NodeStatus, targetVersions cloudcmd.TargetVersions) string { + var upToDateImages int + var upToDateK8s int + for _, node := range status { + if node.KubeletVersion() == targetVersions.Kubernetes() { + upToDateK8s++ + } + if node.ImageVersion() == targetVersions.ImagePath() { + upToDateImages++ + } + } + + builder := strings.Builder{} + if upToDateImages != len(status) || upToDateK8s != len(status) { + builder.WriteString(fmt.Sprintf("\tImage: %d/%d\n", upToDateImages, len(status))) + builder.WriteString(fmt.Sprintf("\tKubernetes: %d/%d\n", upToDateK8s, len(status))) + } + + return builder.String() +} + +// serviceVersionsString creates the service versions part of the output string. +func serviceVersionsString(versions helm.ServiceVersions) string { + builder := strings.Builder{} + builder.WriteString("Installed service versions:\n") + builder.WriteString(fmt.Sprintf("\tCilium: %s\n", versions.Cilium())) + builder.WriteString(fmt.Sprintf("\tcert-manager: %s\n", versions.CertManager())) + builder.WriteString(fmt.Sprintf("\tconstellation-operators: %s\n", versions.ConstellationOperators())) + builder.WriteString(fmt.Sprintf("\tconstellation-services: %s\n", versions.ConstellationServices())) + return builder.String() +} + +// targetVersionsString creates the target versions part of the output string. +func targetVersionsString(target cloudcmd.TargetVersions) string { + builder := strings.Builder{} + builder.WriteString("Target versions:\n") + builder.WriteString(fmt.Sprintf("\tImage: %s\n", target.Image())) + builder.WriteString(fmt.Sprintf("\tKubernetes: %s\n", target.Kubernetes())) + + return builder.String() +} + +type kubeClient interface { + GetNodes(ctx context.Context) ([]corev1.Node, error) +} + +type helmClient interface { + Versions() (helm.ServiceVersions, error) +} diff --git a/cli/internal/cmd/status_test.go b/cli/internal/cmd/status_test.go new file mode 100644 index 000000000..670ac0ba8 --- /dev/null +++ b/cli/internal/cmd/status_test.go @@ -0,0 +1,193 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package cmd + +import ( + "context" + "testing" + + "github.com/edgelesssys/constellation/v2/cli/internal/helm" + updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +const successOutput = `Target versions: + Image: v1.1.0 + Kubernetes: v1.2.3 +Installed service versions: + Cilium: v1.0.0 + cert-manager: v1.0.0 + constellation-operators: v1.1.0 + constellation-services: v1.1.0 +Cluster status: Node version of every node is up to date +` + +const inProgressOutput = `Target versions: + Image: v1.1.0 + Kubernetes: v1.2.3 +Installed service versions: + Cilium: v1.0.0 + cert-manager: v1.0.0 + constellation-operators: v1.1.0 + constellation-services: v1.1.0 +Cluster status: Some node versions are out of date + Image: 1/2 + Kubernetes: 1/2 +` + +// TestStatus checks that the status function produces the correct strings. +func TestStatus(t *testing.T) { + testCases := map[string]struct { + kubeClient stubKubeClient + helmClient stubHelmClient + nodeVersion updatev1alpha1.NodeVersion + dynamicErr error + expectedOutput string + wantErr bool + }{ + "success": { + kubeClient: stubKubeClient{ + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + Annotations: map[string]string{ + "constellation.edgeless.systems/node-image": "v1.1.0", + }, + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.3", + }, + }, + }, + }, + }, + helmClient: stubHelmClient{ + serviceVersions: helm.NewServiceVersions("v1.0.0", "v1.0.0", "v1.1.0", "v1.1.0"), + }, + nodeVersion: updatev1alpha1.NodeVersion{ + Spec: updatev1alpha1.NodeVersionSpec{ + ImageVersion: "v1.1.0", + ImageReference: "v1.1.0", + KubernetesClusterVersion: "v1.2.3", + }, + Status: updatev1alpha1.NodeVersionStatus{ + Conditions: []metav1.Condition{ + { + Message: "Node version of every node is up to date", + }, + }, + }, + }, + expectedOutput: successOutput, + }, + "one of two nodes not upgraded": { + kubeClient: stubKubeClient{ + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "outdated", + Annotations: map[string]string{ + "constellation.edgeless.systems/node-image": "v1.0.0", + }, + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.2", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "uptodate", + Annotations: map[string]string{ + "constellation.edgeless.systems/node-image": "v1.1.0", + }, + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.3", + }, + }, + }, + }, + }, + helmClient: stubHelmClient{ + serviceVersions: helm.NewServiceVersions("v1.0.0", "v1.0.0", "v1.1.0", "v1.1.0"), + }, + nodeVersion: updatev1alpha1.NodeVersion{ + Spec: updatev1alpha1.NodeVersionSpec{ + ImageVersion: "v1.1.0", + ImageReference: "v1.1.0", + KubernetesClusterVersion: "v1.2.3", + }, + Status: updatev1alpha1.NodeVersionStatus{ + Conditions: []metav1.Condition{ + { + Message: "Some node versions are out of date", + }, + }, + }, + }, + expectedOutput: inProgressOutput, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.nodeVersion) + require.NoError(err) + output, err := status(context.Background(), tc.kubeClient, tc.helmClient, &stubDynamicInterface{data: unstructured.Unstructured{Object: raw}, err: tc.dynamicErr}) + if tc.wantErr { + assert.Error(err) + return + } + require.NoError(err) + assert.Equal(tc.expectedOutput, output) + }) + } +} + +type stubKubeClient struct { + nodes []corev1.Node + err error +} + +func (s stubKubeClient) GetNodes(_ context.Context) ([]corev1.Node, error) { + return s.nodes, s.err +} + +type stubHelmClient struct { + serviceVersions helm.ServiceVersions + err error +} + +func (s stubHelmClient) Versions() (helm.ServiceVersions, error) { + return s.serviceVersions, s.err +} + +type stubDynamicInterface struct { + data unstructured.Unstructured + err error +} + +func (s *stubDynamicInterface) GetCurrent(_ context.Context, _ string) (*unstructured.Unstructured, error) { + return &s.data, s.err +} + +func (s *stubDynamicInterface) Update(_ context.Context, _ *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return &s.data, s.err +} diff --git a/cli/internal/cmd/upgradecheck.go b/cli/internal/cmd/upgradecheck.go index 45737a303..052253076 100644 --- a/cli/internal/cmd/upgradecheck.go +++ b/cli/internal/cmd/upgradecheck.go @@ -264,7 +264,7 @@ func (v *versionCollector) currentVersions(ctx context.Context) (serviceVersion return "", "", "", fmt.Errorf("setting up helm client: %w", err) } - serviceVersion, err = helmClient.Versions() + serviceVersions, err := helmClient.Versions() if err != nil { return "", "", "", fmt.Errorf("getting service versions: %w", err) } @@ -279,7 +279,7 @@ func (v *versionCollector) currentVersions(ctx context.Context) (serviceVersion return "", "", "", fmt.Errorf("getting image version: %w", err) } - return serviceVersion, imageVersion, k8sVersion, nil + return serviceVersions.ConstellationServices(), imageVersion, k8sVersion, nil } // supportedVersions returns slices of supported versions. diff --git a/cli/internal/helm/client.go b/cli/internal/helm/client.go index 2107be723..38b7ad6d8 100644 --- a/cli/internal/helm/client.go +++ b/cli/internal/helm/client.go @@ -39,6 +39,9 @@ const ( DenyDestructive = false ) +// ErrConfirmationMissing signals that an action requires user confirmation. +var ErrConfirmationMissing = errors.New("action requires user confirmation") + // Client handles interaction with helm and the cluster. type Client struct { config *action.Configuration @@ -144,13 +147,30 @@ func (c *Client) Upgrade(ctx context.Context, config *config.Config, timeout tim } // Versions queries the cluster for running versions and returns a map of releaseName -> version. -func (c *Client) Versions() (string, error) { - serviceVersion, err := c.currentVersion(constellationServicesInfo.releaseName) +func (c *Client) Versions() (ServiceVersions, error) { + ciliumVersion, err := c.currentVersion(ciliumInfo.releaseName) if err != nil { - return "", fmt.Errorf("getting constellation-services version: %w", err) + return ServiceVersions{}, fmt.Errorf("getting %s version: %w", ciliumInfo.releaseName, err) + } + certManagerVersion, err := c.currentVersion(certManagerInfo.releaseName) + if err != nil { + return ServiceVersions{}, fmt.Errorf("getting %s version: %w", certManagerInfo.releaseName, err) + } + operatorsVersion, err := c.currentVersion(constellationOperatorsInfo.releaseName) + if err != nil { + return ServiceVersions{}, fmt.Errorf("getting %s version: %w", constellationOperatorsInfo.releaseName, err) + } + servicesVersion, err := c.currentVersion(constellationServicesInfo.releaseName) + if err != nil { + return ServiceVersions{}, fmt.Errorf("getting %s version: %w", constellationServicesInfo.releaseName, err) } - return compatibility.EnsurePrefixV(serviceVersion), nil + return ServiceVersions{ + cilium: compatibility.EnsurePrefixV(ciliumVersion), + certManager: compatibility.EnsurePrefixV(certManagerVersion), + constellationOperators: compatibility.EnsurePrefixV(operatorsVersion), + constellationServices: compatibility.EnsurePrefixV(servicesVersion), + }, nil } // currentVersion returns the version of the currently installed helm release. @@ -174,8 +194,43 @@ func (c *Client) currentVersion(release string) (string, error) { return rel[0].Chart.Metadata.Version, nil } -// ErrConfirmationMissing signals that an action requires user confirmation. -var ErrConfirmationMissing = errors.New("action requires user confirmation") +// ServiceVersions bundles the versions of all services that are part of Constellation. +type ServiceVersions struct { + cilium string + certManager string + constellationOperators string + constellationServices string +} + +// NewServiceVersions returns a new ServiceVersions struct. +func NewServiceVersions(cilium, certManager, constellationOperators, constellationServices string) ServiceVersions { + return ServiceVersions{ + cilium: cilium, + certManager: certManager, + constellationOperators: constellationOperators, + constellationServices: constellationServices, + } +} + +// Cilium returns the version of the Cilium release. +func (s ServiceVersions) Cilium() string { + return s.cilium +} + +// CertManager returns the version of the cert-manager release. +func (s ServiceVersions) CertManager() string { + return s.certManager +} + +// ConstellationOperators returns the version of the constellation-operators release. +func (s ServiceVersions) ConstellationOperators() string { + return s.constellationOperators +} + +// ConstellationServices returns the version of the constellation-services release. +func (s ServiceVersions) ConstellationServices() string { + return s.constellationServices +} // TODO: v2.8: remove fileHandler argument. func (c *Client) upgradeRelease( diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 76089955e..42a52d274 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -34,6 +34,7 @@ Commands: * [azure](#constellation-iam-create-azure): Create IAM configuration on Microsoft Azure for your Constellation cluster * [gcp](#constellation-iam-create-gcp): Create IAM configuration on GCP for your Constellation cluster * [destroy](#constellation-iam-destroy): Destroy an IAM configuration and delete local Terraform files +* [status](#constellation-status): show status of a Constellation cluster ## constellation config @@ -673,3 +674,31 @@ constellation iam destroy [flags] --force disable version compatibility checks - might result in corrupted clusters ``` +## constellation status + +show status of a Constellation cluster + +### Synopsis + +Show status of a constellation cluster. + +Shows microservice, image and Kubernetes versions installed in the cluster. Also show status of current version upgrades. + +``` +constellation status [flags] +``` + +### Options + +``` + -h, --help help for status +``` + +### Options inherited from parent commands + +``` + --config string path to the configuration file (default "constellation-conf.yaml") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters +``` + diff --git a/docs/docs/workflows/upgrade.md b/docs/docs/workflows/upgrade.md index cb0dffbf3..89968ea54 100644 --- a/docs/docs/workflows/upgrade.md +++ b/docs/docs/workflows/upgrade.md @@ -57,3 +57,38 @@ If you are interested, you can monitor pods restarting in the `kube-system` name Image and Kubernetes upgrades take longer. For each node in your cluster, a new node has to be created and joined. The process usually takes up to ten minutes per node. + +## Check the status + +Upgrades are asynchronous operations. +After you run `upgrade apply`, it will take a while until the upgrade has completed. +To understand if an upgrade is finished, you can run: + +```bash +constellation status +``` + +This command displays the following information: + +* The installed services and their versions +* The image and Kubernetes version the cluster is expecting on each node +* How many nodes are up to date + +Here's an example output: + +```shell-session +Target versions: + Image: v2.6.0 + Kubernetes: v1.25.8 +Installed service versions: + Cilium: v1.12.1 + cert-manager: v1.10.0 + constellation-operators: v2.6.0 + constellation-services: v2.6.0 +Cluster status: Some node versions are out of date + Image: 23/25 + Kubernetes: 25/25 +``` + +This output indicates that the cluster is running Kubernetes version `1.25.8`, and all nodes have the appropriate binaries installed. +23 out of 25 nodes have already upgraded to the targeted image version of `2.6.0`, while two are still in progress. diff --git a/go.mod b/go.mod index 50de63e15..ff6c5aa01 100644 --- a/go.mod +++ b/go.mod @@ -113,7 +113,6 @@ require ( k8s.io/apiextensions-apiserver v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/apiserver v0.26.3 - k8s.io/cli-runtime v0.26.3 k8s.io/client-go v0.26.3 k8s.io/cluster-bootstrap v0.26.3 k8s.io/kubelet v0.26.3 @@ -123,6 +122,8 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require k8s.io/cli-runtime v0.26.3 // indirect + require ( cloud.google.com/go v0.110.0 // indirect cloud.google.com/go/iam v0.12.0 // indirect diff --git a/internal/kubernetes/kubectl/BUILD.bazel b/internal/kubernetes/kubectl/BUILD.bazel index 00d04c6ab..9af1ccb90 100644 --- a/internal/kubernetes/kubectl/BUILD.bazel +++ b/internal/kubernetes/kubectl/BUILD.bazel @@ -3,29 +3,20 @@ load("//bazel/go:go_test.bzl", "go_test") go_library( name = "kubectl", - srcs = [ - "kubectl.go", - "restclient.go", - ], + srcs = ["kubectl.go"], importpath = "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl", visibility = ["//:__subpackages__"], deps = [ "@io_k8s_api//core/v1:core", "@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:apiextensions", "@io_k8s_apiextensions_apiserver//pkg/client/clientset/clientset/typed/apiextensions/v1:apiextensions", - "@io_k8s_apimachinery//pkg/api/meta", "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", "@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured", "@io_k8s_apimachinery//pkg/runtime", "@io_k8s_apimachinery//pkg/runtime/schema", "@io_k8s_apimachinery//pkg/runtime/serializer", - "@io_k8s_cli_runtime//pkg/resource", - "@io_k8s_client_go//discovery", - "@io_k8s_client_go//discovery/cached/memory", "@io_k8s_client_go//dynamic", "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//rest", - "@io_k8s_client_go//restmapper", "@io_k8s_client_go//scale/scheme", "@io_k8s_client_go//tools/clientcmd", "@io_k8s_client_go//util/retry", @@ -34,16 +25,7 @@ go_library( go_test( name = "kubectl_test", - srcs = [ - "kubectl_test.go", - "restclient_test.go", - ], + srcs = ["kubectl_test.go"], embed = [":kubectl"], - deps = [ - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@io_k8s_client_go//rest", - "@io_k8s_client_go//tools/clientcmd", - "@io_k8s_client_go//tools/clientcmd/api", - ], + deps = ["@com_github_stretchr_testify//assert"], ) diff --git a/internal/kubernetes/kubectl/kubectl.go b/internal/kubernetes/kubectl/kubectl.go index 3ad175ca8..0a171c8bf 100644 --- a/internal/kubernetes/kubectl/kubectl.go +++ b/internal/kubernetes/kubectl/kubectl.go @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/scale/scheme" @@ -35,7 +34,6 @@ type Kubectl struct { kubernetes.Interface dynamicClient dynamic.Interface apiextensionClient apiextensionsclientv1.ApiextensionsV1Interface - builder *resource.Builder } // New returns an empty Kubectl client. Need to call Initialize before usable. @@ -67,12 +65,6 @@ func (k *Kubectl) Initialize(kubeconfig []byte) error { } k.apiextensionClient = apiextensionClient - restClientGetter, err := newRESTClientGetter(kubeconfig) - if err != nil { - return fmt.Errorf("creating k8s RESTClientGetter from kubeconfig: %w", err) - } - k.builder = resource.NewBuilder(restClientGetter).Unstructured() - return nil } @@ -92,22 +84,6 @@ func (k *Kubectl) ApplyCRD(ctx context.Context, rawCRD []byte) error { return err } -// parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it. -func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - _ = v1.AddToScheme(sch) - obj, groupVersionKind, err := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode(crdString, nil, nil) - if err != nil { - return nil, fmt.Errorf("decoding crd: %w", err) - } - if groupVersionKind.Kind == "CustomResourceDefinition" { - return obj.(*v1.CustomResourceDefinition), nil - } - - return nil, errors.New("parsed []byte, but did not find a CRD") -} - // GetCRDs retrieves all custom resource definitions currently installed in the cluster. func (k *Kubectl) GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) { crds, err := k.apiextensionClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{}) @@ -159,6 +135,15 @@ func (k *Kubectl) ListAllNamespaces(ctx context.Context) (*corev1.NamespaceList, return k.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) } +// GetNodes returns all nodes in the cluster. +func (k *Kubectl) GetNodes(ctx context.Context) ([]corev1.Node, error) { + nodes, err := k.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("listing nodes: %w", err) + } + return nodes.Items, nil +} + // AddTolerationsToDeployment adds [K8s tolerations] to the deployment, identified // by name and namespace. // @@ -213,3 +198,19 @@ func (k *Kubectl) AddNodeSelectorsToDeployment(ctx context.Context, selectors ma } return nil } + +// parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it. +func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) { + sch := runtime.NewScheme() + _ = scheme.AddToScheme(sch) + _ = v1.AddToScheme(sch) + obj, groupVersionKind, err := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode(crdString, nil, nil) + if err != nil { + return nil, fmt.Errorf("decoding crd: %w", err) + } + if groupVersionKind.Kind == "CustomResourceDefinition" { + return obj.(*v1.CustomResourceDefinition), nil + } + + return nil, errors.New("parsed []byte, but did not find a CRD") +} diff --git a/internal/kubernetes/kubectl/restclient.go b/internal/kubernetes/kubectl/restclient.go deleted file mode 100644 index ab9b750df..000000000 --- a/internal/kubernetes/kubectl/restclient.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubectl - -import ( - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/client-go/tools/clientcmd" -) - -// restClientGetter implements k8s.io/cli-runtime/pkg/resource.RESTClientGetter. -type restClientGetter struct { - clientconfig clientcmd.ClientConfig -} - -// newRESTClientGetter creates a new restClientGetter using a kubeconfig. -func newRESTClientGetter(kubeconfig []byte) (*restClientGetter, error) { - clientconfig, err := clientcmd.NewClientConfigFromBytes(kubeconfig) - if err != nil { - return nil, err - } - - rawconfig, err := clientconfig.RawConfig() - if err != nil { - return nil, err - } - - clientconfig = clientcmd.NewDefaultClientConfig(rawconfig, &clientcmd.ConfigOverrides{}) - - return &restClientGetter{clientconfig}, nil -} - -// ToRESTConfig returns k8s REST client config. -func (r *restClientGetter) ToRESTConfig() (*rest.Config, error) { - return r.clientconfig.ClientConfig() -} - -// ToDiscoveryClient creates new k8s discovery client from restClientGetter. -func (r *restClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { - restconfig, err := r.clientconfig.ClientConfig() - if err != nil { - return nil, err - } - dc, err := discovery.NewDiscoveryClientForConfig(restconfig) - if err != nil { - return nil, err - } - return memory.NewMemCacheClient(dc), nil -} - -// ToRESTMapper creates new k8s RESTMapper from restClientGetter. -func (r *restClientGetter) ToRESTMapper() (meta.RESTMapper, error) { - dc, err := r.ToDiscoveryClient() - if err != nil { - return nil, err - } - return restmapper.NewDeferredDiscoveryRESTMapper(dc), nil -} - -// ToRawKubeConfigLoader returns the inner k8s ClientConfig. -func (r *restClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { - return r.clientconfig -} diff --git a/internal/kubernetes/kubectl/restclient_test.go b/internal/kubernetes/kubectl/restclient_test.go deleted file mode 100644 index 33ce73743..000000000 --- a/internal/kubernetes/kubectl/restclient_test.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubectl - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/require" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" -) - -const testingKubeconfig = ` -apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: "" - server: https://192.0.2.0:6443 - name: kubernetes -contexts: -- context: - cluster: kubernetes - user: kubernetes-admin - name: kubernetes-admin@kubernetes -current-context: kubernetes-admin@kubernetes -kind: Config -preferences: {} -users: -- name: kubernetes-admin - user: - client-certificate-data: "" - client-key-data: "" -` - -type stubClientConfig struct { - rawConfigConfig clientcmdapi.Config - rawConfigErr error - clientConfigConfig *restclient.Config - clientConfigErr error - namespaceString string - namespaceOverridden bool - namespaceErr error - configAccessResult clientcmd.ConfigAccess -} - -func (s *stubClientConfig) RawConfig() (clientcmdapi.Config, error) { - return s.rawConfigConfig, s.rawConfigErr -} - -func (s *stubClientConfig) ClientConfig() (*restclient.Config, error) { - return s.clientConfigConfig, s.clientConfigErr -} - -func (s *stubClientConfig) Namespace() (string, bool, error) { - return s.namespaceString, s.namespaceOverridden, s.namespaceErr -} - -func (s *stubClientConfig) ConfigAccess() clientcmd.ConfigAccess { - return s.configAccessResult -} - -func TestNewRESTClientGetter(t *testing.T) { - require := require.New(t) - result, err := newRESTClientGetter([]byte(testingKubeconfig)) - require.NoError(err) - require.NotNil(result) -} - -func TestToRESTConfig(t *testing.T) { - require := require.New(t) - getter := restClientGetter{ - clientconfig: &stubClientConfig{ - clientConfigConfig: &restclient.Config{}, - }, - } - result, err := getter.ToRESTConfig() - require.NoError(err) - require.NotNil(result) -} - -func TestToDiscoveryClient(t *testing.T) { - require := require.New(t) - getter := restClientGetter{ - clientconfig: &stubClientConfig{ - clientConfigConfig: &restclient.Config{}, - }, - } - result, err := getter.ToDiscoveryClient() - require.NoError(err) - require.NotNil(result) -} - -func TestToDiscoveryClientFail(t *testing.T) { - require := require.New(t) - getter := restClientGetter{ - clientconfig: &stubClientConfig{ - clientConfigErr: errors.New("someErr"), - }, - } - _, err := getter.ToDiscoveryClient() - require.Error(err) -} - -func TestToRESTMapper(t *testing.T) { - require := require.New(t) - getter := restClientGetter{ - clientconfig: &stubClientConfig{ - clientConfigConfig: &restclient.Config{}, - }, - } - result, err := getter.ToRESTMapper() - require.NoError(err) - require.NotNil(result) -} - -func TestToRESTMapperFail(t *testing.T) { - require := require.New(t) - getter := restClientGetter{ - clientconfig: &stubClientConfig{ - clientConfigErr: errors.New("someErr"), - }, - } - _, err := getter.ToRESTMapper() - require.Error(err) -} - -func TestToRawKubeConfigLoader(t *testing.T) { - clientConfig := stubClientConfig{ - clientConfigConfig: &restclient.Config{}, - } - require := require.New(t) - getter := restClientGetter{ - clientconfig: &clientConfig, - } - result := getter.ToRawKubeConfigLoader() - require.Equal(&clientConfig, result) -}