Skip to content

Commit

Permalink
implement get and describe cmd (perses#310)
Browse files Browse the repository at this point in the history
* implement get cmd

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* implement describe command

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* apply code review

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
  • Loading branch information
Nexucis authored Feb 28, 2022
1 parent f674054 commit 083b142
Show file tree
Hide file tree
Showing 16 changed files with 794 additions and 17 deletions.
6 changes: 5 additions & 1 deletion cmd/percli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"fmt"
"os"

"github.com/perses/perses/internal/cli/cmd/describe"
"github.com/perses/perses/internal/cli/cmd/get"
"github.com/perses/perses/internal/cli/cmd/login"
"github.com/perses/perses/internal/cli/cmd/project"
"github.com/perses/perses/internal/cli/cmd/version"
Expand All @@ -30,9 +32,11 @@ func newRootCommand() *cobra.Command {
Short: "Command line interface to interact with the Perses API",
}

cmd.AddCommand(version.NewCMD())
cmd.AddCommand(describe.NewCMD())
cmd.AddCommand(get.NewCMD())
cmd.AddCommand(login.NewCMD())
cmd.AddCommand(project.NewCMD())
cmd.AddCommand(version.NewCMD())
return cmd
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/gavv/httpexpect/v2 v2.3.1
github.com/labstack/echo/v4 v4.6.3
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/ginkgo v1.11.0 // indirect
github.com/perses/common v0.9.1
github.com/prometheus/common v0.32.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Expand All @@ -332,6 +334,8 @@ github.com/nexucis/lamenv v0.3.1 h1:S292h1WAv9DZ6CvZXYpmdTiApDBmvZNzZqhgTa7Bslk=
github.com/nexucis/lamenv v0.3.1/go.mod h1:O99691ISuawBT33ern7eTyNpyjlHWwhg0JdhkBGjOkU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
Expand Down
101 changes: 101 additions & 0 deletions internal/cli/cmd/describe/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2022 The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package describe

import (
"fmt"

cmdUtils "github.com/perses/perses/internal/cli/utils"
cmdUtilsService "github.com/perses/perses/internal/cli/utils/service"
modelV1 "github.com/perses/perses/pkg/model/api/v1"
"github.com/spf13/cobra"
)

type option struct {
kind modelV1.Kind
project string
name string
output string
resourceService cmdUtilsService.Service
}

func (o *option) complete(args []string) error {
if len(args) < 1 {
return fmt.Errorf(cmdUtils.FormatAvailableResourcesMessage())
} else if len(args) < 2 {
return fmt.Errorf("please specify the name of the resource you want to describe")
} else if len(args) > 2 {
return fmt.Errorf("you cannot have more than two arguments for the command 'describe'")
}
o.name = args[1]

var err error
o.kind, err = cmdUtils.GetKind(args[0])
if err != nil {
return err
}

// Then, if no particular project has been specified through a flag, let's grab the one defined in the CLI config.
if len(o.project) == 0 {
o.project = cmdUtils.GlobalConfig.Project
}

// Finally, get the api client we will need later.
apiClient, err := cmdUtils.GlobalConfig.GetAPIClient()
if err != nil {
return err
}

svc, svcErr := cmdUtilsService.NewService(o.kind, o.project, apiClient)
if svcErr != nil {
return err
}
o.resourceService = svc
return nil
}

func (o *option) validate() error {
return cmdUtils.ValidateAndSetOutput(&o.output)
}

func (o *option) execute() error {
entity, err := o.resourceService.GetResource(o.name)
if err != nil {
return err
}
return cmdUtils.HandleOutput(o.output, entity)
}

func NewCMD() *cobra.Command {
o := &option{}
cmd := &cobra.Command{
Use: "describe [RESOURCE_TYPE] [NAME]",
Short: "Show details of a specific resource",
Example: `
## Describe a particular dashboard.
percli describe dashboard nodeExporter
## Describe a particular dashboard as a JSON object.
percli describe dashboard nodeExporter -ojson
`,
Run: func(cmd *cobra.Command, args []string) {
cmdUtils.HandleError(o.complete(args))
cmdUtils.HandleError(o.validate())
cmdUtils.HandleError(o.execute())
},
}
cmd.Flags().StringVarP(&o.project, "project", "p", o.project, "If present, the project scope for this CLI request.")
cmd.Flags().StringVarP(&o.output, "output", "o", o.output, "One of 'yaml' or 'json'. Default is 'yaml'.")
return cmd
}
128 changes: 128 additions & 0 deletions internal/cli/cmd/get/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2022 The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package get

import (
"fmt"

cmdUtils "github.com/perses/perses/internal/cli/utils"
cmdUtilsService "github.com/perses/perses/internal/cli/utils/service"
modelV1 "github.com/perses/perses/pkg/model/api/v1"
"github.com/spf13/cobra"
)

type option struct {
kind modelV1.Kind
allProject bool
project string
output string
prefix string
resourceService cmdUtilsService.Service
}

func (o *option) complete(args []string) error {
// first let's analyze the args to get what kind of resource we should get and if there is a prefix to use for the filtering.
if len(args) == 0 {
return fmt.Errorf(cmdUtils.FormatAvailableResourcesMessage())
} else if len(args) == 2 {
// In second position in the arguments, you can have a prefix that will be used to filter the resources.
o.prefix = args[1]
} else if len(args) > 2 {
return fmt.Errorf("you cannot have more than two arguments for the command 'get'")
}

var err error
o.kind, err = cmdUtils.GetKind(args[0])
if err != nil {
return err
}

// Then, if no particular project has been specified through a flag, let's grab the one defined in the CLI config.
if len(o.project) == 0 && !o.allProject {
o.project = cmdUtils.GlobalConfig.Project
}

// Finally, get the api client we will need later.
apiClient, err := cmdUtils.GlobalConfig.GetAPIClient()
if err != nil {
return err
}

svc, svcErr := cmdUtilsService.NewService(o.kind, o.project, apiClient)
if svcErr != nil {
return err
}
o.resourceService = svc
return nil
}

func (o *option) validate() error {
// check if project should be defined (through the config or through the flag) for the given resource.
if !o.allProject && len(o.project) == 0 && !cmdUtils.IsGlobalResource(o.kind) {
return fmt.Errorf("no project has been defined for the scope of this command. If you intended to get all resources across projects, please use the flag --all")
}
if len(o.output) > 0 {
// In this particular command, the default display is a matrix.
return cmdUtils.ValidateAndSetOutput(&o.output)
}
return nil
}

func (o *option) execute() error {
resourceList, err := o.resourceService.ListResource(o.prefix)
if err != nil {
return err
}
if len(o.output) > 0 {
return cmdUtils.HandleOutput(o.output, resourceList)
}
entities, err := cmdUtils.ConvertToEntity(resourceList)
if err != nil {
return err
}
data := o.resourceService.BuildMatrix(entities)
cmdUtils.HandlerTable(o.resourceService.GetColumHeader(), data)
return nil
}

func NewCMD() *cobra.Command {
o := &option{}
cmd := &cobra.Command{
Use: "get [RESOURCE_TYPE] [PREFIX]",
Short: "Retrieve any kind of resource from the API.",
Example: `
# List all dashboards in the current project selected.
percli get dashboards
# List all dashboards that begin with a given name in the current project selected.
percli get dashboards node
# List all dashboards in a specific project.
percli get dashboards -p my_project
#List all dashboards as a JSON object.
percli get dashboards -a -ojson
`,
Run: func(cmd *cobra.Command, args []string) {
cmdUtils.HandleError(o.complete(args))
cmdUtils.HandleError(o.validate())
cmdUtils.HandleError(o.execute())
},
}
cmd.Flags().BoolVarP(&o.allProject, "all", "a", o.allProject, "If present, list the requested object(s) across all projects. The project in the current context is ignored even if specified with --project.")
cmd.Flags().StringVarP(&o.project, "project", "p", o.project, "If present, the project scope for this CLI request.")
cmd.Flags().StringVarP(&o.output, "output", "o", o.output, "Kind of display: 'yaml' or 'json'.")
return cmd
}
8 changes: 4 additions & 4 deletions internal/cli/cmd/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
"fmt"

cmdUtils "github.com/perses/perses/internal/cli/utils"
v1 "github.com/perses/perses/pkg/client/api/v1"
"github.com/perses/perses/pkg/client/api"
"github.com/perses/perses/pkg/client/perseshttp"
"github.com/spf13/cobra"
)

type option struct {
projectName string
apiClient v1.ProjectInterface
apiClient api.ClientInterface
}

func (o *option) complete(args []string) error {
Expand All @@ -39,7 +39,7 @@ func (o *option) complete(args []string) error {
if err != nil {
return err
}
o.apiClient = apiClient.V1().Project()
o.apiClient = apiClient
return nil
}

Expand All @@ -50,7 +50,7 @@ func (o *option) execute() error {
return nil
} else {
// in case the project is provided we should verify if it exists first
_, err := o.apiClient.Get(o.projectName)
_, err := o.apiClient.V1().Project().Get(o.projectName)
if err != nil {
if errors.Is(err, perseshttp.RequestNotFoundError) {
return fmt.Errorf("project %q doesn't exist", o.projectName)
Expand Down
15 changes: 5 additions & 10 deletions internal/cli/cmd/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package version

import (
cmdUtils "github.com/perses/perses/internal/cli/utils"
v1 "github.com/perses/perses/pkg/client/api/v1"
"github.com/perses/perses/pkg/client/api"
"github.com/prometheus/common/version"
"github.com/spf13/cobra"
)
Expand All @@ -34,24 +34,19 @@ type outputVersion struct {
type option struct {
short bool
output string
apiClient v1.HealthInterface
apiClient api.ClientInterface
}

func (o *option) complete() {
apiClient, err := cmdUtils.GlobalConfig.GetAPIClient()
if err != nil {
return
}
o.apiClient = apiClient.V1().Health()
o.apiClient = apiClient
}

func (o *option) validate() error {
if o.output != "" {
return cmdUtils.ValidateOutput(o.output)
} else {
o.output = "yaml"
return nil
}
return cmdUtils.ValidateAndSetOutput(&o.output)
}

func (o *option) execute() error {
Expand All @@ -66,7 +61,7 @@ func (o *option) execute() error {
clientVersion.Commit = version.Revision
}
if o.apiClient != nil {
health, err := o.apiClient.Check()
health, err := o.apiClient.V1().Health().Check()
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 083b142

Please sign in to comment.