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: allow command aliases #3224

Merged
merged 8 commits into from
Nov 2, 2024
Merged
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
7 changes: 7 additions & 0 deletions .github/workflows/wc-integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ jobs:
run: aqua -c aqua.yaml g -i kubernetes-sigs/kustomize
working-directory: tests/main

- name: Test command aliases
run: aqua i -l
working-directory: tests/aliases
- name: Test command aliases
run: terraform-013 version
working-directory: tests/aliases

- run: aqua list
- run: aqua list -installed
- run: aqua list -installed -a
Expand Down
25 changes: 25 additions & 0 deletions json-schema/aqua-yaml.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@
"additionalProperties": false,
"type": "object"
},
"CommandAlias": {
"properties": {
"command": {
"type": "string"
},
"alias": {
"type": "string"
},
"no_link": {
"type": "boolean"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"command",
"alias"
]
},
"Config": {
"properties": {
"packages": {
Expand Down Expand Up @@ -80,6 +99,12 @@
},
"vars": {
"type": "object"
},
"command_aliases": {
"items": {
"$ref": "#/$defs/CommandAlias"
},
"type": "array"
}
},
"additionalProperties": false,
Expand Down
29 changes: 18 additions & 11 deletions pkg/config/aqua/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import (
)

type Package struct {
Name string `validate:"required" json:"name,omitempty"`
Registry string `validate:"required" yaml:",omitempty" json:"registry,omitempty" jsonschema:"description=Registry name,example=foo,example=local,default=standard"`
Version string `validate:"required" yaml:",omitempty" json:"version,omitempty"`
Import string `yaml:",omitempty" json:"import,omitempty"`
Tags []string `yaml:",omitempty" json:"tags,omitempty"`
Description string `yaml:",omitempty" json:"description,omitempty"`
Link string `yaml:",omitempty" json:"link,omitempty"`
Update *Update `yaml:",omitempty" json:"update,omitempty"`
FilePath string `json:"-" yaml:"-"`
GoVersionFile string `json:"go_version_file,omitempty" yaml:"go_version_file,omitempty"`
Vars map[string]any `json:"vars,omitempty" yaml:",omitempty"`
Name string `validate:"required" json:"name,omitempty"`
Registry string `validate:"required" yaml:",omitempty" json:"registry,omitempty" jsonschema:"description=Registry name,example=foo,example=local,default=standard"`
Version string `validate:"required" yaml:",omitempty" json:"version,omitempty"`
Import string `yaml:",omitempty" json:"import,omitempty"`
Tags []string `yaml:",omitempty" json:"tags,omitempty"`
Description string `yaml:",omitempty" json:"description,omitempty"`
Link string `yaml:",omitempty" json:"link,omitempty"`
Update *Update `yaml:",omitempty" json:"update,omitempty"`
FilePath string `json:"-" yaml:"-"`
GoVersionFile string `json:"go_version_file,omitempty" yaml:"go_version_file,omitempty"`
Vars map[string]any `json:"vars,omitempty" yaml:",omitempty"`
CommandAliases []*CommandAlias `json:"command_aliases,omitempty" yaml:"command_aliases,omitempty"`
}

type CommandAlias struct {
Command string `validate:"required" json:"command"`
Alias string `validate:"required" json:"alias"`
NoLink bool `yaml:"no_link,omitempty" json:"no_link,omitempty"`
}

type Update struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/cp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (c *Controller) installAndCopy(ctx context.Context, logE *logrus.Entry, par
}
}

if err := c.copy(logE, param, findResult, exeName); err != nil {
if err := c.copy(logE, param, findResult.ExePath, exeName); err != nil {
return err
}
return nil
Expand Down
5 changes: 2 additions & 3 deletions pkg/controller/cp/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import (
"path/filepath"

"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/controller/which"
"github.com/sirupsen/logrus"
)

func (c *Controller) copy(logE *logrus.Entry, param *config.Param, findResult *which.FindResult, exeName string) error {
func (c *Controller) copy(logE *logrus.Entry, param *config.Param, exePath string, exeName string) error {
p := filepath.Join(param.Dest, exeName)
if c.runtime.GOOS == "windows" && filepath.Ext(exeName) == "" {
p += ".exe"
Expand All @@ -18,7 +17,7 @@ func (c *Controller) copy(logE *logrus.Entry, param *config.Param, findResult *w
"exe_name": exeName,
"dest": p,
}).Info("coping a file")
if err := c.packageInstaller.Copy(p, findResult.ExePath); err != nil {
if err := c.packageInstaller.Copy(p, exePath); err != nil {
return fmt.Errorf("copy a file: %w", err)
}
return nil
Expand Down
54 changes: 37 additions & 17 deletions pkg/controller/which/which.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,25 +157,45 @@ func (c *Controller) findExecFileFromPkg(logE *logrus.Entry, registries map[stri
}

for _, file := range pkgInfo.GetFiles() {
if file.Name == exeName {
findResult := &FindResult{
Package: &config.Package{
Package: pkg,
PackageInfo: pkgInfo,
},
File: file,
}
if err := findResult.Package.ApplyVars(); err != nil {
return nil, fmt.Errorf("apply package variables: %w", err)
}
exePath, err := c.getExePath(findResult)
if err != nil {
logE.WithError(err).Error("get the execution file path")
return nil, nil //nolint:nilnil
}
findResult.ExePath = exePath
findResult, err := c.findExecFileFromFile(logE, exeName, pkg, pkgInfo, file)
if err != nil {
return nil, err
}
if findResult != nil {
return findResult, nil
}
}
return nil, nil //nolint:nilnil
}

func (c *Controller) findExecFileFromFile(logE *logrus.Entry, exeName string, pkg *aqua.Package, pkgInfo *registry.PackageInfo, file *registry.File) (*FindResult, error) {
cmds := map[string]struct{}{
file.Name: {},
}
for _, alias := range pkg.CommandAliases {
if file.Name != alias.Command {
continue
}
cmds[alias.Alias] = struct{}{}
}
if _, ok := cmds[exeName]; !ok {
return nil, nil //nolint:nilnil
}
findResult := &FindResult{
Package: &config.Package{
Package: pkg,
PackageInfo: pkgInfo,
},
File: file,
}
if err := findResult.Package.ApplyVars(); err != nil {
return nil, fmt.Errorf("apply package variables: %w", err)
}
exePath, err := c.getExePath(findResult)
if err != nil {
logE.WithError(err).Error("get the execution file path")
return nil, nil //nolint:nilnil
}
findResult.ExePath = exePath
return findResult, nil
}
15 changes: 12 additions & 3 deletions pkg/installpackage/check_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,19 @@ func (is *Installer) checkAndCopyFile(ctx context.Context, logE *logrus.Entry, p
return nil
}
logE.Info("copying an executable file")
if err := is.Copy(filepath.Join(is.copyDir, file.Name), exePath); err != nil {
return err
exeNames := map[string]struct{}{
file.Name: {},
}
for _, alias := range pkg.Package.CommandAliases {
if alias.Command == file.Name {
exeNames[alias.Alias] = struct{}{}
}
}
for exeName := range exeNames {
if err := is.Copy(filepath.Join(is.copyDir, exeName), exePath); err != nil {
return err
}
}

return nil
}

Expand Down
109 changes: 77 additions & 32 deletions pkg/installpackage/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/config/registry"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/suzuki-shunsuke/logrus-error/logerr"
Expand All @@ -27,40 +28,86 @@ func (is *Installer) createLinks(logE *logrus.Entry, pkgs []*config.Package) boo
}

for _, pkg := range pkgs {
pkgInfo := pkg.PackageInfo
for _, file := range pkgInfo.GetFiles() {
if is.realRuntime.IsWindows() {
hardLink := filepath.Join(is.rootDir, "bin", file.Name+".exe")
if f, err := afero.Exists(is.fs, hardLink); err != nil {
logerr.WithError(logE, err).WithFields(logrus.Fields{
"command": file.Name,
}).Error("check if a hard link to aqua-proxy exists")
failed = true
continue
} else if f {
continue
}
logE.WithFields(logrus.Fields{
"command": file.Name,
}).Info("creating a hard link to aqua-proxy")
if err := is.linker.Hardlink(aquaProxyPathOnWindows, hardLink); err != nil {
logerr.WithError(logE, err).WithFields(logrus.Fields{
"command": file.Name,
}).Error("create a hard link to aqua-proxy")
failed = true
}
continue
}
if err := is.createLink(logE, filepath.Join(is.rootDir, "bin", file.Name), filepath.Join("..", proxyName)); err != nil {
logerr.WithError(logE, err).Error("create the symbolic link")
failed = true
continue
}
logE := logE.WithFields(logrus.Fields{
"package_name": pkg.Package.Name,
"package_version": pkg.Package.Version,
})
if is.createPackageLinks(logE, pkg, aquaProxyPathOnWindows) {
failed = true
}
}
return failed
}

func (is *Installer) createPackageLinks(logE *logrus.Entry, pkg *config.Package, aquaProxyPathOnWindows string) bool {
failed := false
pkgInfo := pkg.PackageInfo
for _, file := range pkgInfo.GetFiles() {
logE := logE.WithFields(logrus.Fields{
"command": file.Name,
})
if is.createFileLinks(logE, pkg, file, aquaProxyPathOnWindows) {
failed = true
}
}
return failed
}

func (is *Installer) createFileLinks(logE *logrus.Entry, pkg *config.Package, file *registry.File, aquaProxyPathOnWindows string) bool {
failed := false
cmds := map[string]struct{}{
file.Name: {},
}
for _, alias := range pkg.Package.CommandAliases {
if file.Name != alias.Command {
continue
}
if alias.NoLink {
continue
}
cmds[alias.Alias] = struct{}{}
}
for cmd := range cmds {
if err := is.createCmdLink(logE, file, cmd, aquaProxyPathOnWindows); err != nil {
logerr.WithError(logE, err).Error("create a link to aqua-proxy")
failed = true
}
}
return failed
}

func (is *Installer) createCmdLink(logE *logrus.Entry, file *registry.File, cmd string, aquaProxyPathOnWindows string) error {
if cmd != file.Name {
logE = logE.WithFields(logrus.Fields{
"command_alias": cmd,
})
}
if is.realRuntime.IsWindows() {
if err := is.createHardLink(logE, cmd, aquaProxyPathOnWindows); err != nil {
return fmt.Errorf("create a hard link to aqua-proxy: %w", err)
}
return nil
}
if err := is.createLink(logE, filepath.Join(is.rootDir, "bin", cmd), filepath.Join("..", proxyName)); err != nil {
return fmt.Errorf("create a symbolic link: %w", err)
}
return nil
}

func (is *Installer) createHardLink(logE *logrus.Entry, cmd string, aquaProxyPathOnWindows string) error {
hardLink := filepath.Join(is.rootDir, "bin", cmd+".exe")
if f, err := afero.Exists(is.fs, hardLink); err != nil {
return fmt.Errorf("check if a hard link to aqua-proxy exists: %w", err)
} else if f {
return nil
}
logE.Info("creating a hard link to aqua-proxy")
if err := is.linker.Hardlink(aquaProxyPathOnWindows, hardLink); err != nil {
return fmt.Errorf("create a hard link to aqua-proxy: %w", err)
}
return nil
}

func (is *Installer) recreateHardLinks() error {
binDir := filepath.Join(is.rootDir, "bin")
infos, err := afero.ReadDir(is.fs, binDir)
Expand Down Expand Up @@ -116,9 +163,7 @@ func (is *Installer) createLink(logE *logrus.Entry, linkPath, linkDest string) e
return fmt.Errorf("unexpected file mode %s: %s", linkPath, mode.String())
}
}
logE.WithFields(logrus.Fields{
"command": filepath.Base(linkPath),
}).Info("create a symbolic link")
logE.Info("create a symbolic link")
if err := is.linker.Symlink(linkDest, linkPath); err != nil {
return fmt.Errorf("create a symbolic link: %w", err)
}
Expand Down
19 changes: 19 additions & 0 deletions tests/aliases/aqua.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
# aqua - Declarative CLI Version Manager
# https://aquaproj.github.io/
# checksum:
# enabled: true
# require_checksum: true
# supported_envs:
# - all
registries:
- type: standard
ref: v4.246.0 # renovate: depName=aquaproj/aqua-registry
packages:
- name: hashicorp/[email protected]
- name: hashicorp/terraform
version: v0.13.7
command_aliases:
- command: terraform
alias: terraform-013
# no_link: true
Loading