diff --git a/.vscode/launch.json b/.vscode/launch.json index 83ae3e76c..c89f2c88c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -133,6 +133,17 @@ "cluster", "connect" ] + }, + { + "name": "status", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/rhoas", + "env": {}, + "args": [ + "status" + ] } ] } \ No newline at end of file diff --git a/cmd/rhoas/main.go b/cmd/rhoas/main.go index 537557bad..d51cb9c33 100644 --- a/cmd/rhoas/main.go +++ b/cmd/rhoas/main.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "os" @@ -10,7 +9,7 @@ import ( "github.com/bf2fc6cc711aee1a0c2a/cli/internal/build" "github.com/bf2fc6cc711aee1a0c2a/cli/internal/config" - serviceapiclient "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/serviceapi/client" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/serviceapi" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/factory" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/root" @@ -42,16 +41,9 @@ func main() { return } - // Attempt to unwrap the descriptive API error message - var apiError serviceapiclient.GenericOpenAPIError - if ok := errors.As(err, &apiError); ok { - errModel := apiError.Model() - - e, ok := errModel.(serviceapiclient.Error) - if ok { - fmt.Fprintf(stderr, "Error: %v\n", *e.Reason) - os.Exit(1) - } + if e, ok := serviceapi.GetAPIError(err); ok { + fmt.Fprintf(stderr, "Error: %v\n", e.GetReason()) + os.Exit(1) } if err = cmdutil.CheckSurveyError(err); err != nil { diff --git a/docs/commands/rhoas.adoc b/docs/commands/rhoas.adoc index fc48f6bcd..691acd299 100644 --- a/docs/commands/rhoas.adoc +++ b/docs/commands/rhoas.adoc @@ -41,3 +41,5 @@ Kafka instances * link:rhoas_logout.adoc[rhoas logout] - Log out from RHOAS * link:rhoas_serviceaccount.adoc[rhoas serviceaccount] - Create, list, delete and update service accounts +* link:rhoas_status.adoc[rhoas status] - View the status of all currently +used services diff --git a/docs/commands/rhoas_kafka.adoc b/docs/commands/rhoas_kafka.adoc index 9476fd3bb..0e49851a5 100644 --- a/docs/commands/rhoas_kafka.adoc +++ b/docs/commands/rhoas_kafka.adoc @@ -41,8 +41,6 @@ instance * link:rhoas_kafka_describe.adoc[rhoas kafka describe] - View all configuration values of a Kafka instance * link:rhoas_kafka_list.adoc[rhoas kafka list] - List all Kafka instances -* link:rhoas_kafka_status.adoc[rhoas kafka status] - View status of a -Kafka instance * link:rhoas_kafka_topics.adoc[rhoas kafka topics] - Create, list and delete Kafka topics * link:rhoas_kafka_use.adoc[rhoas kafka use] - Set the current Kafka diff --git a/docs/commands/rhoas_status.adoc b/docs/commands/rhoas_status.adoc new file mode 100644 index 000000000..683fe837d --- /dev/null +++ b/docs/commands/rhoas_status.adoc @@ -0,0 +1,47 @@ +== rhoas status + +View the status of all currently used services + +=== Synopsis + +View status information of your currently used services. Choose to view +the status of all services with rhoas status or specific +services with rhoas status  + +To use a different service run rhoas use. Example: rhoas +kafka use –id=1nh3qkcXuBGMlbIPDqhNbswIZCB. Services available: +[kafka] + +.... +rhoas status [args] [flags] +.... + +=== Examples + +.... +# view the status of all services +$ rhoas status + +# view the status of the used Kafka +$ rhoas status kafka + +# view the status of your services in JSON +$ rhoas status -o json +.... + +=== Options + +.... + -h, --help help for status + -o, --output string Format to display the Kafka instance. Choose from: "json", "yaml", "yml" +.... + +=== Options inherited from parent commands + +.... + -d, --debug Enable debug mode +.... + +=== SEE ALSO + +* link:rhoas.adoc[rhoas] - RHOAS CLI diff --git a/docs/guides/using-the-cli.adoc b/docs/guides/using-the-cli.adoc index f55fc533f..fb91b17c2 100644 --- a/docs/guides/using-the-cli.adoc +++ b/docs/guides/using-the-cli.adoc @@ -28,19 +28,21 @@ Created new Kafka instance: NOTE: A Kafka instance will automatically be "used" when created. -=== Viewing the status of a Kafka instance +=== Viewing the status of your current services -Use the `rhoas kafka status` command to view the status of the current Kafka instance. +Use the `rhoas status` command to view the status of all current services. [source,shell] ---- $ rhoas kafka status - ID NAME STATUS - ----------------------------- ------------ ---------- - 1lVumk6qWk3ZRQfCssZNNTGvR6Q endaskafka complete ----- -TIP: You can pass the `--id` flag to view the status of a different Kafka instance. + Kafka + ------------------------------------------------------------------------------------ + ID: 1nh3qkcXuBGMlbIPDqhNbswIZCB + Name: my-kafka-instance + Status: ready + Bootstrap URL: my-kafka-instance--nh-qkcxubgmlbipdqhnbswizcb.kafka.devshift.org:443 +---- === Viewing a list of Kafka instances diff --git a/go.mod b/go.mod index ad00672f6..51de4b64d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-homedir v1.1.0 + github.com/openconfig/goyang v0.2.4 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect github.com/segmentio/kafka-go v0.4.8 diff --git a/go.sum b/go.sum index 999aa54be..ab40d896f 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= @@ -160,6 +161,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/protobuf v3.11.4+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -230,6 +232,7 @@ github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/landoop/tableprinter v0.0.0-20200805134727-ea32388e35c1 h1:xUwSaTDYl+Ib5OoFxWJnqYFG9N31++qfeNXzTZ1cc8o= github.com/landoop/tableprinter v0.0.0-20200805134727-ea32388e35c1/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= @@ -281,7 +284,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openconfig/gnmi v0.0.0-20200414194230-1597cc0f2600/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A= +github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= +github.com/openconfig/goyang v0.2.4 h1:xGmGr3zuhq9ASCu5jRdtMFdRnixhbg8TJEQ0nylyvxA= +github.com/openconfig/goyang v0.2.4/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8= +github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -374,6 +383,7 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -416,6 +426,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -529,6 +540,7 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/config/type.go b/internal/config/type.go index e93564d25..d5af71526 100644 --- a/internal/config/type.go +++ b/internal/config/type.go @@ -38,3 +38,8 @@ type ServiceConfigMap struct { type KafkaConfig struct { ClusterID string `json:"clusterId"` } + +func (c *Config) HasKafka() bool { + return c.Services.Kafka != nil && + c.Services.Kafka.ClusterID != "" +} diff --git a/pkg/api/serviceapi/api_errors.go b/pkg/api/serviceapi/api_errors.go new file mode 100644 index 000000000..dfb03338e --- /dev/null +++ b/pkg/api/serviceapi/api_errors.go @@ -0,0 +1,43 @@ +package serviceapi + +import ( + "errors" + "fmt" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/serviceapi/client" +) + +const ( + NotFoundErrCode = "MGD-SERV-API-7" +) + +type Error struct { + Err error +} + +func (e *Error) Error() string { + return fmt.Sprint(e.Err) +} + +func (e *Error) Unwrap() error { + return e.Err +} + +func GetAPIError(err error) (e client.Error, ok bool) { + var apiError client.GenericOpenAPIError + if ok = errors.As(err, &apiError); ok { + errModel := apiError.Model() + + e, ok = errModel.(client.Error) + } + + return e, ok +} + +func IsNotFoundError(err error) bool { + mappedErr, ok := GetAPIError(err) + if !ok { + return false + } + + return mappedErr.GetCode() == NotFoundErrCode +} diff --git a/pkg/cmd/kafka/kafka.go b/pkg/cmd/kafka/kafka.go index bbd008b03..98d134367 100644 --- a/pkg/cmd/kafka/kafka.go +++ b/pkg/cmd/kafka/kafka.go @@ -11,7 +11,6 @@ import ( "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/delete" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/describe" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/list" - "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/status" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/topics" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/kafka/use" ) @@ -42,7 +41,6 @@ func NewKafkaCommand(f *factory.Factory) *cobra.Command { delete.NewDeleteCommand(f), list.NewListCommand(f), use.NewUseCommand(f), - status.NewStatusCommand(f), topics.NewTopicsCommand(f), ) return cmd diff --git a/pkg/cmd/kafka/status/status.go b/pkg/cmd/kafka/status/status.go deleted file mode 100644 index 57dcb1017..000000000 --- a/pkg/cmd/kafka/status/status.go +++ /dev/null @@ -1,106 +0,0 @@ -package status - -import ( - "context" - "fmt" - "os" - - "github.com/MakeNowJust/heredoc" - "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/dump" - pkgKafka "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/kafka" - - "github.com/spf13/cobra" - - "github.com/bf2fc6cc711aee1a0c2a/cli/internal/config" - "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/factory" - "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/connection" -) - -type Options struct { - id string - - Config config.IConfig - Connection func() (connection.Connection, error) -} - -func NewStatusCommand(f *factory.Factory) *cobra.Command { - opts := &Options{ - Config: f.Config, - Connection: f.Connection, - } - - cmd := &cobra.Command{ - Use: "status", - Short: "View status of a Kafka instance", - Long: heredoc.Doc(` - View the status of a Kafka instance. - - The values shown as part of the status are: ID, Name, Status, Bootstrap Server Host - `), - Example: heredoc.Doc(` - # view the status of the current Kafka instance - $ rhoas kafka status - - # view the status of a Kafka instance using its ID - $ rhoas kafka status --id "1nYlgkt87xelHT1wnOdEyGhFhaO" - `), - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, _ []string) error { - cfg, err := opts.Config.Load() - if err != nil { - return err - } - - if opts.id != "" { - return runStatus(opts) - } - - var kafkaConfig *config.KafkaConfig - if cfg.Services.Kafka == kafkaConfig || cfg.Services.Kafka.ClusterID == "" { - return fmt.Errorf("No Kafka instance selected. Use the '--id' flag or set one in context with the 'use' command") - } - - opts.id = cfg.Services.Kafka.ClusterID - - return runStatus(opts) - }, - } - - cmd.Flags().StringVar(&opts.id, "id", "", "ID of the Kafka instance you want to get the status from") - - return cmd -} - -func runStatus(opts *Options) error { - connection, err := opts.Connection() - if err != nil { - return err - } - - api := connection.API() - - res, _, apiErr := api.Kafka.GetKafkaById(context.Background(), opts.id).Execute() - pkgKafka.TransformKafkaRequest(&res) - - if apiErr.Error() != "" { - return fmt.Errorf("Unable to get Kafka instance: %w", apiErr) - } - - type kafkaStatus struct { - ID string `header:"ID"` - Name string `header:"Name"` - Status string `header:"Status"` - BootstrapHost string `header:"Bootstrap Server Host"` - } - - statusInfo := &kafkaStatus{ - ID: res.GetId(), - Name: res.GetName(), - Status: res.GetStatus(), - BootstrapHost: res.GetBootstrapServerHost(), - } - - dump.Table(os.Stdout, statusInfo) - - return nil -} diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index abb56a3a2..ad5520ea1 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -3,6 +3,8 @@ package root import ( "flag" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/status" + "github.com/MakeNowJust/heredoc" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/arguments" "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/cluster" @@ -51,6 +53,7 @@ func NewRootCommand(cmdFactory *factory.Factory, version string) *cobra.Command cmd.AddCommand(kafka.NewKafkaCommand(cmdFactory)) cmd.AddCommand(serviceaccount.NewServiceAccountCommand(cmdFactory)) cmd.AddCommand(cluster.NewClusterCommand(cmdFactory)) + cmd.AddCommand(status.NewStatusCommand(cmdFactory)) cmd.AddCommand(completion.CompletionCmd) return cmd diff --git a/pkg/cmd/status/status.go b/pkg/cmd/status/status.go new file mode 100644 index 000000000..1298c1279 --- /dev/null +++ b/pkg/cmd/status/status.go @@ -0,0 +1,137 @@ +package status + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmdutil/flags" + + "github.com/bf2fc6cc711aee1a0c2a/cli/internal/config" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/color" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/connection" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/dump" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/iostreams" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/logging" + pkgStatus "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/status" + + "github.com/MakeNowJust/heredoc" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/cmd/factory" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +const ( + kafkaSvcName = "kafka" +) + +var validServices = []string{kafkaSvcName} + +type Options struct { + IO *iostreams.IOStreams + Config config.IConfig + Logger func() (logging.Logger, error) + Connection func() (connection.Connection, error) + + outputFormat string + services []string +} + +func NewStatusCommand(f *factory.Factory) *cobra.Command { + opts := &Options{ + IO: f.IOStreams, + Config: f.Config, + Connection: f.Connection, + Logger: f.Logger, + services: validServices, + } + + cmd := &cobra.Command{ + Use: "status [args]", + Short: "View the status of all currently used services", + Long: heredoc.Docf(` + View status information of your currently used services. + Choose to view the status of all services with %v or specific services with %v + + To use a different service run %v. Example: %v. + Services available: %v + `, color.CodeSnippet("rhoas status"), + color.CodeSnippet("rhoas status "), + color.CodeSnippet("rhoas use"), + color.CodeSnippet("rhoas kafka use --id=1nh3qkcXuBGMlbIPDqhNbswIZCB"), + color.Info(fmt.Sprintf("%v", validServices))), + Example: heredoc.Doc(` + # view the status of all services + $ rhoas status + + # view the status of the used Kafka + $ rhoas status kafka + + # view the status of your services in JSON + $ rhoas status -o json + `), + ValidArgs: []string{kafkaSvcName}, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + for _, s := range args { + if !flags.IsValidInput(s, validServices...) { + return fmt.Errorf("Invalid service '%v' specified", s) + } + } + + opts.services = args + } + + return runStatus(opts) + }, + } + + cmd.Flags().StringVarP(&opts.outputFormat, "output", "o", "", "Format to display the Kafka instance. Choose from: \"json\", \"yaml\", \"yml\"") + + return cmd +} + +func runStatus(opts *Options) error { + pkgOpts := &pkgStatus.Options{ + Config: opts.Config, + Connection: opts.Connection, + Logger: opts.Logger, + Services: opts.services, + } + + logger, err := opts.Logger() + if err != nil { + return err + } + + if len(opts.services) > 0 { + logger.Debug("Requesting status of the following services:", opts.services) + } + + status, ok, err := pkgStatus.Get(context.Background(), pkgOpts) + if err != nil { + return err + } + + if !ok { + logger.Info("\nNo services are currently used.") + return nil + } + + stdout := opts.IO.Out + switch opts.outputFormat { + case "json": + data, _ := json.Marshal(status) + _ = dump.JSON(stdout, data) + return nil + case "yaml", "yml": + data, _ := yaml.Marshal(status) + _ = dump.YAML(os.Stdout, data) + return nil + } + + pkgStatus.Print(stdout, status) + + return nil +} diff --git a/pkg/status/status.go b/pkg/status/status.go new file mode 100644 index 000000000..3dec01f8f --- /dev/null +++ b/pkg/status/status.go @@ -0,0 +1,183 @@ +package status + +import ( + "context" + "fmt" + "io" + "reflect" + "text/tabwriter" + + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/color" + + "github.com/bf2fc6cc711aee1a0c2a/cli/internal/config" + serviceapi "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/serviceapi" + serviceapiclient "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/serviceapi/client" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/connection" + "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/logging" + "github.com/openconfig/goyang/pkg/indent" +) + +const tagTitle = "title" + +type Status struct { + Kafka *KafkaStatus `json:"kafka,omitempty" title:"Kafka"` +} + +type KafkaStatus struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + BootstrapServerHost string `json:"bootstrap_server_host,omitempty" title:"Bootstrap URL"` +} + +type Options struct { + Config config.IConfig + Logger func() (logging.Logger, error) + Connection func() (connection.Connection, error) + + // request specific services + Services []string +} + +// Get gets the status of all services currently set in the user config +func Get(ctx context.Context, opts *Options) (status *Status, ok bool, err error) { + cfg, err := opts.Config.Load() + if err != nil { + return nil, false, err + } + connection, err := opts.Connection() + if err != nil { + return nil, false, err + } + logger, err := opts.Logger() + if err != nil { + return nil, false, err + } + + status = &Status{} + api := connection.API() + + if stringInSlice("kafka", opts.Services) { + kafkaCfg := cfg.Services.Kafka + if cfg.HasKafka() { + // nolint:govet + kafkaStatus, err := getKafkaStatus(ctx, api.Kafka, kafkaCfg.ClusterID) + if err != nil { + if serviceapi.IsNotFoundError(err) { + logger.Infof("Kafka instance with ID %v not found.", color.Info(kafkaCfg.ClusterID)) + logger.Info("Run", color.CodeSnippet("rhoas kafka use --id="), "to use another Kafka instance.") + } + } else { + status.Kafka = kafkaStatus + ok = true + } + } else { + logger.Debug("No Kafka instance is currently used, skipping status check") + } + } + + return status, ok, err +} + +// Print prints the status information of all set services +func Print(w io.Writer, status *Status) { + v := reflect.ValueOf(status).Elem() + + indirectVal := reflect.Indirect(v) + for i := 0; i < indirectVal.NumField(); i++ { + fieldType := indirectVal.Type().Field(i) + fieldVal := indirectVal.Field(i) + + if !fieldVal.IsNil() { + title := getTitle(&fieldType) + fmt.Fprintln(w, "") + printServiceStatus(w, title, fieldVal) + } + } +} + +// print the status of service v +func printServiceStatus(w io.Writer, name string, v reflect.Value) { + indentWriter := indent.NewWriter(w, " ") + + // set table padding + padding := 5 + + // create a new tabwriter + tw := tabwriter.NewWriter(indentWriter, 0, 0, padding, ' ', tabwriter.TabIndent) + + // tracks the longest row in chars so we can set an equal length divider + maxRowLen := 0 + // iterate over every field in the type + + indirectV := reflect.Indirect(v) + for i := 0; i < indirectV.NumField(); i++ { + // get field type metadata + fieldType := indirectV.Type().Field(i) + + // get value of the field + fieldValue := indirectV.Field(i) + + // get the title to use for the field + title := getTitle(&fieldType) + + // print the row and take note of its character length + len, _ := fmt.Fprintf(tw, "%v:\t\t%v\n", title, fieldValue) + if len > maxRowLen { + maxRowLen = len + } + } + // print the service header + fmt.Fprintln(indentWriter, name) + // print the title divider + fmt.Fprintln(indentWriter, createDivider(maxRowLen+padding)) + + tw.Flush() +} + +// get title of the field from the "title" tag +// If the tag does not exist, use the name of the field +func getTitle(f *reflect.StructField) string { + // Get the field tag value + tag := f.Tag.Get(tagTitle) + if tag == "" { + tag = f.Name + } + + return tag +} + +// create a divider for the top of the table of n length +func createDivider(n int) string { + b := "-" + for i := 0; i <= n; i++ { + b += "-" + } + + return b +} + +func getKafkaStatus(ctx context.Context, api serviceapiclient.DefaultApi, id string) (status *KafkaStatus, err error) { + kafka, _, apiErr := api.GetKafkaById(ctx, id).Execute() + if apiErr.Error() != "" { + return nil, apiErr + } + + status = &KafkaStatus{ + ID: kafka.GetId(), + Name: kafka.GetName(), + Status: kafka.GetStatus(), + BootstrapServerHost: kafka.GetBootstrapServerHost(), + } + + return status, err +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +}