Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add info package subcommand #2960

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion pkg/cli/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ func (r *Runner) newInfoCommand() *cli.Command {
Usage: "Show information",
Description: `Show information.
e.g.
$ aqua info`,
$ aqua info

See also subcommands
$ aqua info help
`,
Action: r.info,
Subcommands: []*cli.Command{
r.newPackageInfoCommand(),
},
}
}

Expand Down
31 changes: 31 additions & 0 deletions pkg/cli/package_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cli //nolint:dupl

Check failure on line 1 in pkg/cli/package_info.go

View workflow job for this annotation

GitHub Actions / test / test

directive `//nolint:dupl` is unused for linter "dupl" (nolintlint)

import (
"fmt"
"net/http"

"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/controller"
"github.com/urfave/cli/v2"
)

func (r *Runner) newPackageInfoCommand() *cli.Command {
return &cli.Command{
Name: "package",
Usage: "Show package defintions",
Description: `Show definition of packages.
e.g.
$ aqua info package sigstore/cosign`,
Action: r.packageInfoAction,
}
}

func (r *Runner) packageInfoAction(c *cli.Context) error {
param := &config.Param{}
if err := r.setParam(c, "info-package", param); err != nil {
return fmt.Errorf("parse the command line arguments: %w", err)
}

ctrl := controller.InitializePackageInfoCommandController(c.Context, param, http.DefaultClient, r.Runtime)
return ctrl.PackageInfo(c.Context, param, r.LogE, c.Args().Slice()...)

Check failure on line 30 in pkg/cli/package_info.go

View workflow job for this annotation

GitHub Actions / test / test

error returned from external package is unwrapped: sig: func (*github.com/aquaproj/aqua/v2/pkg/controller/packageinfo.Controller).PackageInfo(ctx context.Context, param *github.com/aquaproj/aqua/v2/pkg/config.Param, logE *github.com/sirupsen/logrus.Entry, args ...string) error (wrapcheck)
}
127 changes: 127 additions & 0 deletions pkg/controller/packageinfo/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package packageinfo

import (
"errors"
"encoding/json"
"strings"
"fmt"
"io"
"os"
"context"

"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/config/registry"
"github.com/spf13/afero"
"github.com/aquaproj/aqua/v2/pkg/checksum"
"github.com/aquaproj/aqua/v2/pkg/config/aqua"
"github.com/sirupsen/logrus"
)

var (
errMissingPackages = errors.New("could not find all packages")
)

type Controller struct {
stdout io.Writer
configFinder ConfigFinder
configReader ConfigReader
registryInstaller RegistryInstaller
fs afero.Fs
}

type ConfigReader interface {
Read(logE *logrus.Entry, configFilePath string, cfg *aqua.Config) error
}

type RegistryInstaller interface {
InstallRegistries(ctx context.Context, logE *logrus.Entry, cfg *aqua.Config, cfgFilePath string, checksums *checksum.Checksums) (map[string]*registry.Config, error)
}

type ConfigFinder interface {
Find(wd, configFilePath string, globalConfigFilePaths ...string) (string, error)
Finds(wd, configFilePath string) []string
}

func New(configFinder ConfigFinder, configReader ConfigReader, registInstaller RegistryInstaller, fs afero.Fs) *Controller {
return &Controller{
stdout: os.Stdout,
configFinder: configFinder,
configReader: configReader,
registryInstaller: registInstaller,
fs: fs,
}
}

func (c *Controller) PackageInfo(ctx context.Context, param *config.Param, logE *logrus.Entry, args ...string) error {
cfgFilePath, err := c.configFinder.Find(param.PWD, param.ConfigFilePath, param.GlobalConfigFilePaths...)
if err != nil {
return err

Check failure on line 58 in pkg/controller/packageinfo/controller.go

View workflow job for this annotation

GitHub Actions / test / test

error returned from interface method should be wrapped: sig: func (github.com/aquaproj/aqua/v2/pkg/controller/packageinfo.ConfigFinder).Find(wd string, configFilePath string, globalConfigFilePaths ...string) (string, error) (wrapcheck)
}

cfg := &aqua.Config{}
if err := c.configReader.Read(logE, cfgFilePath, cfg); err != nil {
return err

Check failure on line 63 in pkg/controller/packageinfo/controller.go

View workflow job for this annotation

GitHub Actions / test / test

error returned from interface method should be wrapped: sig: func (github.com/aquaproj/aqua/v2/pkg/controller/packageinfo.ConfigReader).Read(logE *github.com/sirupsen/logrus.Entry, configFilePath string, cfg *github.com/aquaproj/aqua/v2/pkg/config/aqua.Config) error (wrapcheck)
}

var checksums *checksum.Checksums
if cfg.ChecksumEnabled(param.EnforceChecksum, param.Checksum) {
checksums = checksum.New()
checksumFilePath, err := checksum.GetChecksumFilePathFromConfigFilePath(c.fs, cfgFilePath)
if err != nil {
return err

Check failure on line 71 in pkg/controller/packageinfo/controller.go

View workflow job for this annotation

GitHub Actions / test / test

error returned from external package is unwrapped: sig: func github.com/aquaproj/aqua/v2/pkg/checksum.GetChecksumFilePathFromConfigFilePath(fs github.com/spf13/afero.Fs, cfgFilePath string) (string, error) (wrapcheck)
}
if err := checksums.ReadFile(c.fs, checksumFilePath); err != nil {
return fmt.Errorf("read a checksum JSON: %w", err)
}
defer func() {
if err := checksums.UpdateFile(c.fs, checksumFilePath); err != nil {
logE.WithError(err).Error("update a checksum file")
}
}()
}

registryContents, err := c.registryInstaller.InstallRegistries(ctx, logE, cfg, cfgFilePath, checksums)
if err != nil {
return err

Check failure on line 85 in pkg/controller/packageinfo/controller.go

View workflow job for this annotation

GitHub Actions / test / test

error returned from interface method should be wrapped: sig: func (github.com/aquaproj/aqua/v2/pkg/controller/packageinfo.RegistryInstaller).InstallRegistries(ctx context.Context, logE *github.com/sirupsen/logrus.Entry, cfg *github.com/aquaproj/aqua/v2/pkg/config/aqua.Config, cfgFilePath string, checksums *github.com/aquaproj/aqua/v2/pkg/checksum.Checksums) (map[string]*github.com/aquaproj/aqua/v2/pkg/config/registry.Config, error) (wrapcheck)
}

packageInfoByNameByRegistry := make(map[string]map[string]*registry.PackageInfo)
for registryName, registryConfig := range registryContents {
packageInfoByNameByRegistry[registryName]= registryConfig.PackageInfos.ToMap(logE)
}

encoder := json.NewEncoder(c.stdout)
encoder.SetIndent("", " ")

missingPackages := false
for _, name := range args {
registryName, pkgName := registryAndPackage(name)
pkgInfo, ok := packageInfoByNameByRegistry[registryName][pkgName]
if !ok {
logE.WithFields(logrus.Fields {
"registry_name": registryName,
"package_name": pkgName,
}).Error("no such registry or package")
missingPackages = true
continue
}

if err := encoder.Encode(pkgInfo); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This command accepts multiple package names and outputs them as JSON independently.
I think it's weird because the output can't be parsed as JSON.

{
  // ...
}
{
  // ...
}

I think there are several options.

  1. Accept only one package
  2. Return a list of packages
[
  {
    // ...
  },
  {
    // ...
  }
]

Copy link
Author

@plobsing plobsing Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON is a self-delimiting format. There is no parsing ambiguity in a stream of messages. Tools like jq and libraries such as Go's encoding/json have no issues working with such streams.

For example, with the current implementation, I'm able to aggregate a count of installed packages by owner using jq:

❮ ./aqua info package $(./aqua list --installed | awk '{print $3 "," $1}') | jq -r '.repo_owner' | sort | uniq -c 
   1 golangci
   1 goreleaser
   1 int128
   1 reviewdog
   1 rhysd
   1 sigstore
   2 suzuki-shunsuke

Of the alternatives you suggest, I think I prefer "accept only one package". Its got what seems to me like an unnecessary restriction, but the output is direct and simple to work with. It is a subset of the stream-of-objects output format I proposed: just pass a single name if you want a single message of output. If we do choose to restrict the caller to requesting only one package, what should happen if they pass more than one in argv? What about zero packages in argv?

I'd really prefer to avoid the list format. I find it over-encapsulated, less human-readable, and a bit less convenient to work with. There's not really an upside.

return fmt.Errorf("encode info as JSON and output it to stdout: %w", err)
}
}

if missingPackages {
return errMissingPackages
}

return nil
}

func registryAndPackage(name string) (string, string) {
r, p, ok := strings.Cut(name, ",")
if ok {
return r, p
}
return "standard", name
}
55 changes: 55 additions & 0 deletions pkg/controller/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
finder "github.com/aquaproj/aqua/v2/pkg/config-finder"
reader "github.com/aquaproj/aqua/v2/pkg/config-reader"
"github.com/aquaproj/aqua/v2/pkg/controller/allowpolicy"
"github.com/aquaproj/aqua/v2/pkg/controller/packageinfo"
"github.com/aquaproj/aqua/v2/pkg/controller/cp"
"github.com/aquaproj/aqua/v2/pkg/controller/denypolicy"
cexec "github.com/aquaproj/aqua/v2/pkg/controller/exec"
Expand Down Expand Up @@ -886,6 +887,60 @@ func InitializeInfoCommandController(ctx context.Context, param *config.Param, r
return &info.Controller{}
}

func InitializePackageInfoCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *packageinfo.Controller {
wire.Build(
packageinfo.New,
wire.NewSet(
finder.NewConfigFinder,
wire.Bind(new(packageinfo.ConfigFinder), new(*finder.ConfigFinder)),
),
wire.NewSet(
github.New,
wire.Bind(new(github.RepositoriesService), new(*github.RepositoriesServiceImpl)),
wire.Bind(new(download.GitHubContentAPI), new(*github.RepositoriesServiceImpl)),
),
wire.NewSet(
registry.New,
wire.Bind(new(packageinfo.RegistryInstaller), new(*registry.Installer)),
),
wire.NewSet(
download.NewGitHubContentFileDownloader,
wire.Bind(new(registry.GitHubContentFileDownloader), new(*download.GitHubContentFileDownloader)),
),
wire.NewSet(
reader.New,
wire.Bind(new(packageinfo.ConfigReader), new(*reader.ConfigReader)),
),
afero.NewOsFs,
download.NewHTTPDownloader,
wire.NewSet(
cosign.NewVerifier,
wire.Bind(new(installpackage.CosignVerifier), new(*cosign.Verifier)),
wire.Bind(new(registry.CosignVerifier), new(*cosign.Verifier)),
),
wire.NewSet(
exec.New,
wire.Bind(new(cosign.Executor), new(*exec.Executor)),
wire.Bind(new(slsa.CommandExecutor), new(*exec.Executor)),
),
wire.NewSet(
download.NewDownloader,
wire.Bind(new(download.ClientAPI), new(*download.Downloader)),
),
wire.NewSet(
slsa.New,
wire.Bind(new(installpackage.SLSAVerifier), new(*slsa.Verifier)),
wire.Bind(new(registry.SLSAVerifier), new(*slsa.Verifier)),
),
wire.NewSet(
slsa.NewExecutor,
wire.Bind(new(slsa.Executor), new(*slsa.ExecutorImpl)),
),

)
return &packageinfo.Controller{}
}

func InitializeRemoveCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *remove.Controller {
wire.Build(
remove.New,
Expand Down
18 changes: 18 additions & 0 deletions pkg/controller/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading