Skip to content

Commit

Permalink
feat: support getting package versions from external files (#3363)
Browse files Browse the repository at this point in the history
* feat: support getting package versions from external files

* chore(go): go mod tidy

* fix: fix a lint error

* fix: restrict the value of version_expr for security

* feat: support version_expr_prefix

---------

Co-authored-by: aquaproj-aqua-releaser[bot] <95135029+aquaproj-aqua-releaser[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 0425338 commit 2933920
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 14 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/wc-integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ jobs:
run: diff aqua.yaml expect.yaml
working-directory: tests/insert

- name: Test version_expr readFile
run: aqua which -v terraform
working-directory: tests/version_expr_file
- name: Test version_expr readJSON
run: aqua which -v terraform
working-directory: tests/version_expr_json
- name: Test version_expr readYAML
run: aqua which -v terraform
working-directory: tests/version_expr_yaml

- run: aqua g -i suzuki-shunsuke/tfcmt
working-directory: tests/main
- name: add duplicated package
Expand Down
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
13 changes: 13 additions & 0 deletions pkg/config-reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"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 @@ -97,6 +98,18 @@ func (r *ConfigReader) readPackage(logE *logrus.Entry, configFilePath string, pk
}
return nil, nil
}
if pkg.VersionExpr != "" {
// version_expr
dir := filepath.Dir(configFilePath)
s, err := expr.EvalVersionExpr(r.fs, dir, pkg.VersionExpr)
if err != nil {
return nil, fmt.Errorf("evaluate a version_expr: %w", logerr.WithFields(err, logrus.Fields{
"version_expr": pkg.VersionExpr,
}))
}
pkg.Version = pkg.VersionExprPrefix + s
return nil, nil
}
if pkg.Import == "" {
// version
return nil, nil
Expand Down
26 changes: 14 additions & 12 deletions pkg/config/aqua/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ 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"`
CommandAliases []*CommandAlias `json:"command_aliases,omitempty" yaml:"command_aliases,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"`
VersionExpr string `json:"version_expr,omitempty" yaml:"version_expr,omitempty"`
VersionExprPrefix string `json:"version_expr_prefix,omitempty" yaml:"version_expr_prefix,omitempty"`
Vars map[string]any `json:"vars,omitempty" yaml:",omitempty"`
CommandAliases []*CommandAlias `json:"command_aliases,omitempty" yaml:"command_aliases,omitempty"`
}

type CommandAlias struct {
Expand Down
5 changes: 4 additions & 1 deletion pkg/expr/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ package expr

import "errors"

var errMustBeBoolean = errors.New("the evaluation result must be a boolean")
var (
errMustBeBoolean = errors.New("the evaluation result must be a boolean")
errMustBeString = errors.New("the evaluation result must be a string")
)
94 changes: 94 additions & 0 deletions pkg/expr/version_expr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package expr

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

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

type Reader struct {
pwd string
fs afero.Fs
}

const safeVersionPattern = `^v?\d+\.\d+(\.\d+)*[.-]?((alpha|beta|dev|rc)[.-]?)?\d*`

var safeVersionRegexp = regexp.MustCompile(safeVersionPattern)

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 {
// Don't output error to prevent leaking sensitive information
// Maybe malicious users tries to read a secret file
return "", errors.New("evaluate the expression")
}
s, ok := a.(string)
if !ok {
return "", errMustBeString
}
// Restrict the value of version_expr to a semver for security reason.
// This prevents secrets from being exposed.
if !safeVersionRegexp.MatchString(s) {
// Don't output the valuof of version_expr to prevent leaking sensitive information
// Maybe malicious users tries to read a secret file
return "", errors.New("the evaluation result of version_expr must match with " + safeVersionPattern)
}
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 {
// Don't output error to prevent leaking sensitive information
// Maybe malicious users tries to read a secret file
panic("failed to unmarshal JSON")
}
return a
}

func (r *Reader) readYAML(s string) any {
b := r.read(s)
var a any
if err := yaml.Unmarshal(b, &a); err != nil {
// Don't output error to prevent leaking sensitive information
// Maybe malicious users tries to read a secret file
panic("failed to unmarshal YAML")
}
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}}
16 changes: 16 additions & 0 deletions tests/version_expr_json/aqua.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
# 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: |
readJSON('version.json').version
3 changes: 3 additions & 0 deletions tests/version_expr_json/version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "1.10.1"
}
16 changes: 16 additions & 0 deletions tests/version_expr_yaml/aqua.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
# 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: |
readYAML('version.yaml').version
1 change: 1 addition & 0 deletions tests/version_expr_yaml/version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
version: 1.10.1

0 comments on commit 2933920

Please sign in to comment.