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: support uninstalling packages #2249

Merged
merged 4 commits into from
Sep 16, 2023
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
8 changes: 6 additions & 2 deletions .github/workflows/wc-integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,15 @@ jobs:

- run: aqua update-checksum -a

- name: Test rm
run: aqua rm x-motemen/ghq bats-core/bats-core

- name: Test rm --all
run: aqua rm -a

# Test if global configuration files are read in `aqua list` and `aqua g`
- run: aqua list
working-directory: /tmp

- name: Test update-aqua
run: aqua update-aqua

- run: aqua-installer -v v0.8.1 -i /tmp/aqua
66 changes: 66 additions & 0 deletions pkg/cli/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package cli

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) newRemoveCommand() *cli.Command {
return &cli.Command{
Name: "remove",
Aliases: []string{"rm"},
Usage: "Uninstall packages",
ArgsUsage: `[<registry name>,]<package name> [...]`,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"a"},
Usage: "uninstall all packages",
},
},
Description: `Uninstall packages.

e.g.
$ aqua rm --all
$ aqua rm cli/cli direnv/direnv

If you want to uninstall packages of non standard registry, you need to specify the registry name too.

e.g.
$ aqua rm foo,suzuki-shunsuke/foo

Limitation:
"http" and "go_install" packages can't be removed.
`,
Action: r.removeAction,
}
}

func (r *Runner) removeAction(c *cli.Context) error {
tracer, err := startTrace(c.String("trace"))
if err != nil {
return err
}
defer tracer.Stop()

cpuProfiler, err := startCPUProfile(c.String("cpu-profile"))
if err != nil {
return err
}
defer cpuProfiler.Stop()

param := &config.Param{}
if err := r.setParam(c, "remove", param); err != nil {
return fmt.Errorf("parse the command line arguments: %w", err)
}
param.SkipLink = true
ctrl := controller.InitializeRemoveCommandController(c.Context, param, http.DefaultClient, r.Runtime)
if err := ctrl.Remove(c.Context, r.LogE, param); err != nil {
return err //nolint:wrapcheck
}
return nil
}
1 change: 1 addition & 0 deletions pkg/cli/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ func (r *Runner) Run(ctx context.Context, args ...string) error {
r.newCpCommand(),
r.newRootDirCommand(),
r.newUpdateChecksumCommand(),
r.newRemoveCommand(),
},
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/config/registry/package_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package registry
import (
"fmt"
"path"
"path/filepath"
"strings"

"github.com/aquaproj/aqua/v2/pkg/runtime"
Expand Down Expand Up @@ -556,6 +557,18 @@ func (p *PackageInfo) getDefaultCmdName() string {
return path.Base(p.GetName())
}

func (p *PackageInfo) PkgPath() string {
switch p.Type {
case PkgInfoTypeGitHubArchive, PkgInfoTypeGoBuild, PkgInfoTypeGitHubContent, PkgInfoTypeGitHubRelease:
return filepath.Join(p.Type, "github.com", p.RepoOwner, p.RepoName)
case PkgInfoTypeCargo:
return filepath.Join(p.Type, "crates.io", p.Crate)
case PkgInfoTypeGoInstall, PkgInfoTypeHTTP:
return ""
}
return ""
}

func (p *PackageInfo) SLSASourceURI() string {
sp := p.SLSAProvenance
if sp == nil {
Expand Down
33 changes: 33 additions & 0 deletions pkg/controller/remove/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package remove

import (
"github.com/aquaproj/aqua/v2/pkg/config"
reader "github.com/aquaproj/aqua/v2/pkg/config-reader"
rgst "github.com/aquaproj/aqua/v2/pkg/install-registry"
"github.com/aquaproj/aqua/v2/pkg/runtime"
"github.com/spf13/afero"
)

type Controller struct {
rootDir string
fs afero.Fs
runtime *runtime.Runtime
configFinder ConfigFinder
configReader reader.ConfigReader
registryInstaller rgst.Installer
}

func New(param *config.Param, fs afero.Fs, rt *runtime.Runtime, configFinder ConfigFinder, configReader reader.ConfigReader, registryInstaller rgst.Installer) *Controller {
return &Controller{
rootDir: param.RootDir,
fs: fs,
runtime: rt,
configFinder: configFinder,
configReader: configReader,
registryInstaller: registryInstaller,
}
}

type ConfigFinder interface {
Find(wd, configFilePath string, globalConfigFilePaths ...string) (string, error)
}
116 changes: 116 additions & 0 deletions pkg/controller/remove/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package remove

import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"

"github.com/aquaproj/aqua/v2/pkg/checksum"
"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/config/aqua"
"github.com/aquaproj/aqua/v2/pkg/config/registry"
"github.com/sirupsen/logrus"
"github.com/suzuki-shunsuke/logrus-error/logerr"
)

func (c *Controller) Remove(ctx context.Context, logE *logrus.Entry, param *config.Param) error {
if param.All {
logE.Info("removing all packages")
return c.removeAll(param.RootDir)
}

if len(param.Args) == 0 {
return nil
}

cfgFilePath, err := c.configFinder.Find(param.PWD, param.ConfigFilePath, param.GlobalConfigFilePaths...)
if err != nil {
return fmt.Errorf("find a configuration file: %w", err)
}

cfg := &aqua.Config{}
if err := c.configReader.Read(cfgFilePath, cfg); err != nil {
return fmt.Errorf("read a configuration file: %w", err)
}

var checksums *checksum.Checksums
if cfg.ChecksumEnabled() {
checksums = checksum.New()
checksumFilePath, err := checksum.GetChecksumFilePathFromConfigFilePath(c.fs, cfgFilePath)
if err != nil {
return err //nolint: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 fmt.Errorf("install registries: %w", err)
}

return c.removePackages(logE, param, registryContents)
}

func (c *Controller) removePackages(logE *logrus.Entry, param *config.Param, registryContents map[string]*registry.Config) error {
for _, pkgName := range param.Args {
logE := logE.WithField("package_name", pkgName)
pkg, err := findPkg(pkgName, registryContents)
if err != nil {
return fmt.Errorf("find a package from registries: %w", logerr.WithFields(err, logrus.Fields{
"package_name": pkgName,
}))
}
path := pkg.PkgPath()
if path == "" {
logE.WithField("package_type", pkg.Type).Warn("this package type can't be removed")
continue
}
pkgPath := filepath.Join(param.RootDir, "pkgs", path)
logE.Info("removing a package")
if err := c.fs.RemoveAll(pkgPath); err != nil {
return fmt.Errorf("remove a package: %w", logerr.WithFields(err, logrus.Fields{
"package_name": pkgName,
}))
}
}
return nil
}

func parsePkgName(pkgName string) (string, string) {
registryName, pkgName, ok := strings.Cut(pkgName, ",")
if ok {
return registryName, pkgName
}
return "standard", registryName
}

func findPkg(pkgName string, registryContents map[string]*registry.Config) (*registry.PackageInfo, error) {
registryName, pkgName := parsePkgName(pkgName)
rgCfg, ok := registryContents[registryName]
if !ok {
return nil, errors.New("unknown registry")
}
for _, pkg := range rgCfg.PackageInfos {
if pkgName != pkg.GetName() {
continue
}
return pkg, nil
}
return nil, errors.New("unknown package")
}

func (c *Controller) removeAll(rootDir string) error {
if err := c.fs.RemoveAll(filepath.Join(rootDir, "pkgs")); err != nil {
return fmt.Errorf("remove all packages $AQUA_ROOT_DIR/pkgs: %w", err)
}
return nil
}
40 changes: 40 additions & 0 deletions pkg/controller/remove/remove_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package remove

import "testing"

func Test_parsePkgName(t *testing.T) {
t.Parallel()
data := []struct {
name string
input string
registryName string
pkgName string
}{
{
name: "normal",
input: "cli/cli",
registryName: "standard",
pkgName: "cli/cli",
},
{
name: "custom registry",
input: "foo,cli/cli",
registryName: "foo",
pkgName: "cli/cli",
},
}

for _, d := range data {
d := d
t.Run(d.name, func(t *testing.T) {
t.Parallel()
r, p := parsePkgName(d.input)
if r != d.registryName {
t.Fatalf("registry name: wanted %s, got %s", d.registryName, r)
}
if p != d.pkgName {
t.Fatalf("package name: wanted %s, got %s", d.pkgName, p)
}
})
}
}
52 changes: 52 additions & 0 deletions pkg/controller/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/aquaproj/aqua/v2/pkg/controller/initpolicy"
"github.com/aquaproj/aqua/v2/pkg/controller/install"
"github.com/aquaproj/aqua/v2/pkg/controller/list"
"github.com/aquaproj/aqua/v2/pkg/controller/remove"
"github.com/aquaproj/aqua/v2/pkg/controller/updateaqua"
"github.com/aquaproj/aqua/v2/pkg/controller/updatechecksum"
"github.com/aquaproj/aqua/v2/pkg/controller/which"
Expand Down Expand Up @@ -756,3 +757,54 @@ func InitializeInfoCommandController(ctx context.Context, param *config.Param, r
)
return &info.Controller{}
}

func InitializeRemoveCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *remove.Controller {
wire.Build(
remove.New,
wire.NewSet(
finder.NewConfigFinder,
wire.Bind(new(remove.ConfigFinder), new(*finder.ConfigFinder)),
),
wire.NewSet(
reader.New,
wire.Bind(new(reader.ConfigReader), new(*reader.ConfigReaderImpl)),
),
wire.NewSet(
registry.New,
wire.Bind(new(registry.Installer), new(*registry.InstallerImpl)),
),
afero.NewOsFs,
wire.NewSet(
download.NewGitHubContentFileDownloader,
wire.Bind(new(domain.GitHubContentFileDownloader), new(*download.GitHubContentFileDownloader)),
),
wire.NewSet(
github.New,
wire.Bind(new(github.RepositoriesService), new(*github.RepositoriesServiceImpl)),
wire.Bind(new(download.GitHubContentAPI), new(*github.RepositoriesServiceImpl)),
),
wire.NewSet(
cosign.NewVerifier,
wire.Bind(new(cosign.Verifier), new(*cosign.VerifierImpl)),
),
download.NewHTTPDownloader,
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(slsa.Verifier), new(*slsa.VerifierImpl)),
),
wire.NewSet(
slsa.NewExecutor,
wire.Bind(new(slsa.Executor), new(*slsa.ExecutorImpl)),
),
)
return &remove.Controller{}
}
Loading
Loading