Skip to content

Commit

Permalink
feat: container-registry vulnerabilities (#414)
Browse files Browse the repository at this point in the history
* bumped cr sdk to v1.1

* run go vendor

* added artifacts list and get cmds

* added vulnerabilities commands and autocompletions

* added info to artifacts autocompletions

* added query params

* added new repository cmds

* regen docs

* updated changelog

* refactor redundant constants

* fix typo

* regen docs
  • Loading branch information
glimberea authored Jan 12, 2024
1 parent 86d0e6d commit 1f2115b
Show file tree
Hide file tree
Showing 124 changed files with 11,961 additions and 557 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
### Changed
- When creating a Dataplatform cluster, now the latest version will be used by default

## [v6.7.4] (TBD)

### Added
- Added support for Container-Registry Vulnerabilities
- New `--vulnerability-scanning` flag added to `registry create` and `registry update` commands
- New `artifacts` and `vulnerabilities` commands under `container-registry`
- `repository` command functionality will eventually be moved to `repository delete`. For the time being, both commands are available.

## [v6.7.3] (December 2023)

### Added
Expand Down
54 changes: 54 additions & 0 deletions commands/container-registry/artifacts/artifacts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package artifacts

import (
"context"

"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/completions"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/spf13/cobra"
)

var (
defaultCols = []string{"Id", "TotalVulnerabilities", "FixableVulnerabilities", "MediaType"}
allCols = []string{
"Id", "Repository", "PushCount", "PullCount", "LastPushed", "TotalVulnerabilities",
"FixableVulnerabilities", "MediaType", "URN",
}
)

func ArtifactsCmd() *core.Command {
cmd := &core.Command{
Command: &cobra.Command{
Use: "artifacts",
Aliases: []string{"a", "art", "artifact"},
Short: "Artifacts Operations",
Long: "Manage container registry artifacts. " +
"Artifacts are the individual files stored in a repository.",
TraverseChildren: true,
},
}

cmd.AddCommand(ArtifactsListCmd())
cmd.AddCommand(ArtifactsGetCmd())

return cmd
}

func ArtifactsIds(registryId string, repositoryName string) []string {
artifacts, _, err := client.Must().RegistryClient.ArtifactsApi.RegistriesRepositoriesArtifactsGet(
context.Background(), registryId, repositoryName,
).Execute()
if err != nil {
return nil
}

artifactsConverted, err := json2table.ConvertJSONToTable("items", jsonpaths.ContainerRegistryArtifact, artifacts)
if err != nil {
return nil
}

return completions.NewCompleter(artifactsConverted, "Id").AddInfo("MediaType").ToString()
}
103 changes: 103 additions & 0 deletions commands/container-registry/artifacts/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package artifacts

import (
"context"
"fmt"

"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/registry"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/repository"
"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func ArtifactsGetCmd() *core.Command {
c := core.NewCommand(
context.TODO(), nil, core.CommandBuilder{
Namespace: "container-registry",
Resource: "artifacts",
Verb: "get",
ShortDesc: "Retrieve an artifacts",
LongDesc: "Retrieve an artifact from a repository",
Example: "ionosctl container-registry artifacts get",
PreCmdRun: PreCmdGet,
CmdRun: CmdGet,
InitClient: true,
},
)

c.Command.Flags().StringSlice(constants.ArgCols, nil, tabheaders.ColsMessage(allCols))
_ = c.Command.RegisterFlagCompletionFunc(
constants.ArgCols,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return allCols, cobra.ShellCompDirectiveNoFileComp
},
)

c.AddStringFlag(constants.FlagRegistryId, constants.FlagRegistryIdShort, "", "Registry ID")
_ = c.Command.RegisterFlagCompletionFunc(
constants.FlagRegistryId,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return registry.RegsIds(), cobra.ShellCompDirectiveNoFileComp
},
)

c.AddStringFlag("repository", "", "", "Name of the repository to retrieve artifact from")
_ = c.Command.RegisterFlagCompletionFunc(
"repository", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return repository.RepositoryNames(viper.GetString(core.GetFlagName(c.NS, constants.FlagRegistryId))),
cobra.ShellCompDirectiveNoFileComp
},
)

c.AddStringFlag(constants.FlagArtifactId, "", "", "ID/digest of the artifact")
_ = c.Command.RegisterFlagCompletionFunc(
constants.FlagArtifactId,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return ArtifactsIds(
viper.GetString(core.GetFlagName(c.NS, constants.FlagRegistryId)),
viper.GetString(core.GetFlagName(c.NS, "repository")),
),
cobra.ShellCompDirectiveNoFileComp
},
)

return c
}

func PreCmdGet(c *core.PreCommandConfig) error {
return core.CheckRequiredFlags(c.Command, c.NS, constants.FlagRegistryId, "repository", constants.FlagArtifactId)
}

func CmdGet(c *core.CommandConfig) error {
cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
regId := viper.GetString(core.GetFlagName(c.NS, constants.FlagRegistryId))
repo := viper.GetString(core.GetFlagName(c.NS, "repository"))
artId := viper.GetString(core.GetFlagName(c.NS, constants.FlagArtifactId))

var arts interface{}
var err error

arts, _, err = client.Must().RegistryClient.ArtifactsApi.RegistriesRepositoriesArtifactsFindByDigest(
c.Context, regId, repo, artId,
).Execute()
if err != nil {
return err
}

out, err := jsontabwriter.GenerateOutput(
"", jsonpaths.ContainerRegistryArtifact, arts,
tabheaders.GetHeaders(allCols, defaultCols, cols),
)
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
}
191 changes: 191 additions & 0 deletions commands/container-registry/artifacts/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package artifacts

import (
"context"
"fmt"

"github.com/fatih/structs"
"github.com/ionos-cloud/ionosctl/v6/commands/cloudapi-v6/query"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/registry"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/repository"
"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
cloudapiv6 "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6"
"github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6/resources"
ionoscloud "github.com/ionos-cloud/sdk-go-container-registry"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func ArtifactsListCmd() *core.Command {
c := core.NewCommand(
context.TODO(), nil, core.CommandBuilder{
Namespace: "container-registry",
Resource: "artifacts",
Verb: "list",
Aliases: []string{"l", "ls"},
ShortDesc: "List registry or repository artifacts",
LongDesc: "List all artifacts in a registry or repository",
Example: "ionosctl container-registry artifacts list",
PreCmdRun: PreCmdList,
CmdRun: CmdList,
InitClient: true,
},
)

c.Command.Flags().StringSlice(constants.ArgCols, nil, tabheaders.ColsMessage(allCols))
_ = c.Command.RegisterFlagCompletionFunc(
constants.ArgCols,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return allCols, cobra.ShellCompDirectiveNoFileComp
},
)

c.AddStringFlag(constants.FlagRegistryId, constants.FlagRegistryIdShort, "", "Registry ID")
_ = c.Command.RegisterFlagCompletionFunc(
constants.FlagRegistryId,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return registry.RegsIds(), cobra.ShellCompDirectiveNoFileComp
},
)

c.AddStringFlag("repository", "", "", "Name of the repository to list artifacts from")
_ = c.Command.RegisterFlagCompletionFunc(
"repository", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return repository.RepositoryNames(viper.GetString(core.GetFlagName(c.NS, constants.FlagRegistryId))),
cobra.ShellCompDirectiveNoFileComp
},
)

c.AddBoolFlag(constants.ArgAll, constants.ArgAllShort, false, "List all artifacts in the registry")
c.AddSetFlag(
cloudapiv6.ArgOrderBy, "", "-pullcount", []string{
"-pullcount", "-pushcount", "-lastPush",
"-lastPull", "-lastScan", "-vulnTotalCount", "-vulnFixableCount", "pullCount", "pushCount", "lastPush",
"lastPull", "lastScan", "vulnTotalCount", "vulnFixableCount",
}, cloudapiv6.ArgOrderByDescription,
)
c.AddStringSliceFlag(
cloudapiv6.ArgFilters, cloudapiv6.ArgFiltersShort, []string{""}, cloudapiv6.ArgFiltersDescription,
)
c.AddInt32Flag(constants.FlagMaxResults, constants.FlagMaxResultsShort, 100, "Maximum number of results to display")

return c
}

func PreCmdList(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlagsSets(
c.Command, c.NS, []string{constants.FlagRegistryId, "repository"},
[]string{constants.FlagRegistryId, constants.ArgAll},
); err != nil {
return err
}

if !viper.IsSet(core.GetFlagName(c.NS, constants.ArgAll)) && viper.IsSet(
core.GetFlagName(
c.NS, cloudapiv6.ArgFilters,
),
) {
return fmt.Errorf("flag --%s can only be used with --%s", cloudapiv6.ArgFilters, constants.ArgAll)
}

return query.ValidateFilters(c, []string{"vulnerabilityId"}, "Filters available: vulnerabilityId")
}

func CmdList(c *core.CommandConfig) error {
cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
regId := viper.GetString(core.GetFlagName(c.NS, constants.FlagRegistryId))
defCols := defaultCols

var arts interface{}
var err error

queryParams, err := query.GetListQueryParams(c)
if err != nil {
return err
}

if viper.IsSet(core.GetFlagName(c.NS, constants.ArgAll)) {
arts, _, err = buildListAllRequest(regId, queryParams).Execute()
if err != nil {
return err
}

defCols = append(defCols, "Repository")
} else {
repo := viper.GetString(core.GetFlagName(c.NS, "repository"))

arts, _, err = buildListRequest(regId, repo, queryParams).Execute()
if err != nil {
return err
}
}

out, err := jsontabwriter.GenerateOutput(
"items", jsonpaths.ContainerRegistryArtifact, arts,
tabheaders.GetHeaders(allCols, defCols, cols),
)
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
}

func buildListAllRequest(
regId string, queryParams resources.ListQueryParams,
) ionoscloud.ApiRegistriesArtifactsGetRequest {
if structs.IsZero(queryParams) {
return client.Must().RegistryClient.ArtifactsApi.RegistriesArtifactsGet(
context.Background(), regId,
)
}

req := client.Must().RegistryClient.ArtifactsApi.RegistriesArtifactsGet(context.Background(), regId)

if queryParams.OrderBy != nil {
req = req.OrderBy(*queryParams.OrderBy)
}

if queryParams.MaxResults != nil {
req = req.Limit(*queryParams.MaxResults)
}

if queryParams.Filters != nil {
vulnId, ok := (*queryParams.Filters)["vulnerabilityId"]
if ok {
req = req.FilterVulnerabilityId(vulnId[0])
}
}

return req
}

func buildListRequest(
regId string, repo string, queryParams resources.ListQueryParams,
) ionoscloud.ApiRegistriesRepositoriesArtifactsGetRequest {
if structs.IsZero(queryParams) {
return client.Must().RegistryClient.ArtifactsApi.RegistriesRepositoriesArtifactsGet(
context.Background(), regId, repo,
)
}

req := client.Must().RegistryClient.ArtifactsApi.RegistriesRepositoriesArtifactsGet(
context.Background(), regId, repo,
)

if queryParams.OrderBy != nil {
req = req.OrderBy(*queryParams.OrderBy)
}

if queryParams.MaxResults != nil {
req = req.Limit(*queryParams.MaxResults)
}

return req
}
4 changes: 4 additions & 0 deletions commands/container-registry/container-registry.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package container_registry

import (
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/artifacts"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/location"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/name"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/registry"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/repository"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/token"
"github.com/ionos-cloud/ionosctl/v6/commands/container-registry/vulnerabilities"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/spf13/cobra"
)
Expand All @@ -28,6 +30,8 @@ func ContainerRegistryCmd() *core.Command {
contregCmd.AddCommand(location.RegLocationsListCmd())
contregCmd.AddCommand(name.RegNamesCmd())
contregCmd.AddCommand(repository.RegRepoDeleteCmd())
contregCmd.AddCommand(artifacts.ArtifactsCmd())
contregCmd.AddCommand(vulnerabilities.VulnerabilitiesCmd())

return contregCmd
}
Loading

0 comments on commit 1f2115b

Please sign in to comment.