-
-
Notifications
You must be signed in to change notification settings - Fork 44
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
base: main
Are you sure you want to change the base?
Changes from all commits
f4c8404
5752fd5
a33d6ae
65cfd7a
1f36abb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package cli //nolint:dupl | ||
|
||
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
|
||
} |
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
|
||
} | ||
|
||
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
|
||
} | ||
|
||
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
|
||
} | ||
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
|
||
} | ||
|
||
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 { | ||
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 | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'sencoding/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
: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 inargv
?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.