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 version expr #3362

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
golang.org/x/oauth2 v0.24.0
golang.org/x/sys v0.28.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down Expand Up @@ -77,5 +78,4 @@ require (
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
3 changes: 3 additions & 0 deletions json-schema/aqua-yaml.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@
"go_version_file": {
"type": "string"
},
"version_expr": {
"type": "string"
},
"vars": {
"type": "object"
},
Expand Down
3 changes: 0 additions & 3 deletions pkg/config-reader/go_version_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ import (
var goVersionPattern = regexp.MustCompile(`(?m)^go (\d+\.\d+.\d+)$`)

func readGoVersionFile(fs afero.Fs, filePath string, pkg *aqua.Package) error {
if pkg.GoVersionFile == "" {
return nil
}
p := filepath.Join(filepath.Dir(filePath), pkg.GoVersionFile)
b, err := afero.ReadFile(fs, p)
if err != nil {
Expand Down
96 changes: 63 additions & 33 deletions pkg/config-reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

"github.com/aquaproj/aqua/v2/pkg/config"
"github.com/aquaproj/aqua/v2/pkg/config/aqua"
"github.com/aquaproj/aqua/v2/pkg/expr"
"github.com/aquaproj/aqua/v2/pkg/osfile"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
Expand Down Expand Up @@ -43,7 +44,15 @@
if err := yaml.NewDecoder(file).Decode(cfg); err != nil {
return fmt.Errorf("parse a configuration file as YAML: %w", err)
}
var configFileDir string
configFileDir := filepath.Dir(configFilePath)
if err := r.readRegistries(configFileDir, cfg); err != nil {
return err
}
r.readPackages(logE, configFilePath, cfg)
return nil
}

func (r *ConfigReader) readRegistries(configFileDir string, cfg *aqua.Config) error {
for _, rgst := range cfg.Registries {
if rgst.Type == "local" {
if strings.HasPrefix(rgst.Path, homePrefix) {
Expand All @@ -52,54 +61,75 @@
}
rgst.Path = filepath.Join(r.homeDir, rgst.Path[6:])
}
if configFileDir == "" {
configFileDir = filepath.Dir(configFilePath)
}
rgst.Path = osfile.Abs(configFileDir, rgst.Path)
}
}
r.readImports(logE, configFilePath, cfg)
return nil
}

func (r *ConfigReader) readImports(logE *logrus.Entry, configFilePath string, cfg *aqua.Config) {
func (r *ConfigReader) readPackages(logE *logrus.Entry, configFilePath string, cfg *aqua.Config) {
pkgs := []*aqua.Package{}
for _, pkg := range cfg.Packages {
if pkg == nil {
continue
}
if pkg.Import == "" {
if err := readGoVersionFile(r.fs, configFilePath, pkg); err != nil {
logerr.WithError(logE, err).Error("read a go version file")
continue
}
subPkgs, err := r.readPackage(logE, configFilePath, pkg)
if err != nil {
logerr.WithError(logE, err).Error("read a package")
continue
}
if subPkgs == nil {
pkgs = append(pkgs, pkg)
continue
}
logE := logE.WithField("import", pkg.Import)
p := filepath.Join(filepath.Dir(configFilePath), pkg.Import)
filePaths, err := afero.Glob(r.fs, p)
pkgs = append(pkgs, subPkgs...)
}
cfg.Packages = pkgs
}

func (r *ConfigReader) readPackage(logE *logrus.Entry, configFilePath string, pkg *aqua.Package) ([]*aqua.Package, error) {
dir := filepath.Dir(configFilePath)
if pkg.GoVersionFile != "" {
// go_version_file
if err := readGoVersionFile(r.fs, configFilePath, pkg); err != nil {
return nil, fmt.Errorf("read a go version file: %w", logerr.WithFields(err, logrus.Fields{
"go_version_file": pkg.GoVersionFile,
}))
}
return nil, nil //nolint:nilnil

Check failure on line 99 in pkg/config-reader/reader.go

View workflow job for this annotation

GitHub Actions / test / test

directive `//nolint:nilnil` is unused for linter "nilnil" (nolintlint)
}
if pkg.VersionExpr != "" {
// version_expr
s, err := expr.EvalVersionExpr(r.fs, dir, pkg.VersionExpr)
if err != nil {
logerr.WithError(logE, err).Error("read files with glob pattern")
continue
return nil, fmt.Errorf("evaluate a version_expr: %w", logerr.WithFields(err, logrus.Fields{
"version_expr": pkg.VersionExpr,
}))
}
sort.Strings(filePaths)
for _, filePath := range filePaths {
logE := logE.WithField("imported_file", filePath)
subCfg := &aqua.Config{}
if err := r.Read(logE, filePath, subCfg); err != nil {
logerr.WithError(logE, err).Error("read an import file")
continue
}
for _, pkg := range subCfg.Packages {
pkg.FilePath = filePath
if err := readGoVersionFile(r.fs, filePath, pkg); err != nil {
logerr.WithError(logE, err).Error("read a go version file")
continue
}
pkgs = append(pkgs, pkg)
}
pkg.Version = s
return nil, nil //nolint:nilnil

Check failure on line 110 in pkg/config-reader/reader.go

View workflow job for this annotation

GitHub Actions / test / test

directive `//nolint:nilnil` is unused for linter "nilnil" (nolintlint)
}
if pkg.Import == "" {
// version
return nil, nil //nolint:nilnil

Check failure on line 114 in pkg/config-reader/reader.go

View workflow job for this annotation

GitHub Actions / test / test

directive `//nolint:nilnil` is unused for linter "nilnil" (nolintlint)
}
// import
logE = logE.WithField("import", pkg.Import)
p := filepath.Join(filepath.Dir(configFilePath), pkg.Import)
filePaths, err := afero.Glob(r.fs, p)
if err != nil {
return nil, fmt.Errorf("find files with a glob pattern: %w", err)
}
sort.Strings(filePaths)
pkgs := []*aqua.Package{}
for _, filePath := range filePaths {
logE := logE.WithField("imported_file", filePath)
subCfg := &aqua.Config{}
if err := r.Read(logE, filePath, subCfg); err != nil {
logerr.WithError(logE, err).Error("read an import file")
continue
}
pkgs = append(pkgs, subCfg.Packages...)
}
cfg.Packages = pkgs
return pkgs, nil
}
1 change: 1 addition & 0 deletions pkg/config/aqua/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Package struct {
Update *Update `yaml:",omitempty" json:"update,omitempty"`
FilePath string `json:"-" yaml:"-"`
GoVersionFile string `json:"go_version_file,omitempty" yaml:"go_version_file,omitempty"`
VersionExpr string `json:"version_expr,omitempty" yaml:"version_expr,omitempty"`
Vars map[string]any `json:"vars,omitempty" yaml:",omitempty"`
CommandAliases []*CommandAlias `json:"command_aliases,omitempty" yaml:"command_aliases,omitempty"`
}
Expand Down
75 changes: 75 additions & 0 deletions pkg/expr/version_expr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package expr

import (
"encoding/json"
"fmt"
"path/filepath"
"strings"

"github.com/expr-lang/expr"
"github.com/spf13/afero"
"gopkg.in/yaml.v3"
)

type Reader struct {
pwd string
fs afero.Fs
}

func EvalVersionExpr(fs afero.Fs, pwd string, expression string) (string, error) {
r := Reader{fs: fs, pwd: pwd}
compiled, err := expr.Compile(expression, expr.Env(map[string]any{
"readFile": r.readFile,
"readJSON": r.readJSON,
"readYAML": r.readYAML,
}))
if err != nil {
return "", fmt.Errorf("parse the expression: %w", err)
}
a, err := expr.Run(compiled, map[string]any{
"readFile": r.readFile,
"readJSON": r.readJSON,
"readYAML": r.readYAML,
})
if err != nil {
return "", fmt.Errorf("evaluate the expression: %w", err)
}
s, ok := a.(string)
if !ok {
return "", errMustBeBoolean
}
return s, nil
}

func (r *Reader) read(s string) []byte {
if !filepath.IsAbs(s) {
s = filepath.Join(r.pwd, s)
}
b, err := afero.ReadFile(r.fs, s)
if err != nil {
panic(err)
}
return b
}

func (r *Reader) readFile(s string) string {
return strings.TrimSpace(string(r.read(s)))
}

func (r *Reader) readJSON(s string) any {
b := r.read(s)
var a any
if err := json.Unmarshal(b, &a); err != nil {
panic(err)
}
return a
}

func (r *Reader) readYAML(s string) any {
b := r.read(s)
var a any
if err := yaml.Unmarshal(b, &a); err != nil {
panic(err)
}
return a
}
1 change: 1 addition & 0 deletions tests/version_expr_file/.terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.10.2
18 changes: 18 additions & 0 deletions tests/version_expr_file/aqua.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/main/json-schema/aqua-yaml.json
# aqua - Declarative CLI Version Manager
# https://aquaproj.github.io/
# checksum:
# enabled: true
# require_checksum: true
# supported_envs:
# - all
registries:
- type: standard
ref: v4.276.0 # renovate: depName=aquaproj/aqua-registry
packages:
- name: hashicorp/terraform
version_expr: |
"v" + readFile('.terraform-version')
# version_template: v{{readFile '.terraform-version'}}
# version_template: v{{(readYAML 'foo.yaml').version}}
Loading