Skip to content

Commit 359e2f7

Browse files
authored
Add go-install arguments and env vars (#23)
* add go-install arguments and env vars Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * use slice for env var + create store dir if not exists Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
1 parent 6d4d072 commit 359e2f7

File tree

7 files changed

+124
-36
lines changed

7 files changed

+124
-36
lines changed

README.md

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ with:
8484
# arbitrary key-value pairs for the install method
8585
```
8686

87-
| Option | Description |
88-
|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
89-
| `name` | The name of the tool to install. This is used to determine the installation directory and the name of the binary. |
90-
| `version.want` | The version of the tool to install. This can be a specific version, or a version range. |
91-
| `version.constraint` | A constraint on the version of the tool to install. This is used to determine the latest version of the tool to update to. |
92-
| `version.method` | The method to use to determine the latest version of the tool. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
93-
| `version.with` | The configuration options for the version method. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
94-
| `method` | The method to use to install the tool. See the [Install Methods](#install-methods) section for more details. |
95-
| `with` | The configuration options for the install method. See the [Install Methods](#install-methods) section for more details. |
87+
| Option | Description |
88+
|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
89+
| `name` | The name of the tool to install. This is used to determine the installation directory and the name of the binary. |
90+
| `version.want` | The version of the tool to install. This can be a specific version, or a version range. |
91+
| `version.constraint` | A constraint on the version of the tool to install. This is used to determine the latest version of the tool to update to. |
92+
| `version.method` | The method to use to determine the latest version of the tool. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
93+
| `version.with` | The configuration options for the version method. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
94+
| `method` | The method to use to install the tool. See the [Install Methods](#install-methods) section for more details. |
95+
| `with` | The configuration options for the install method. See the [Install Methods](#install-methods) section for more details. |
9696

9797

9898
### Install Methods
@@ -115,20 +115,22 @@ The default version resolver for this method is `github-release`.
115115

116116
The `go-install` install method uses `go install` to install a tool. It requires the following configuration options:
117117

118-
| Option | Description |
119-
|-------------------------|-----------------------------------------------------------------------------|
120-
| `module` | The FQDN to the Go module (e.g. `github.com/anchore/syft`) |
121-
| `entrypoint` (optional) | The path within the repo to the main package for the tool (e.g. `cmd/syft`) |
122-
| `ldflags` (optional) | A list of ldflags to pass to `go install` (e.g. `-X main.version={{ .Version }}`) |
118+
| Option | Description |
119+
|-------------------------|--------------------------------------------------------------------------------------|
120+
| `module` | The FQDN to the Go module (e.g. `github.com/anchore/syft`) |
121+
| `entrypoint` (optional) | The path within the repo to the main package for the tool (e.g. `cmd/syft`) |
122+
| `ldflags` (optional) | A list of ldflags to pass to `go install` (e.g. `-X main.version={{ .Version }}`) |
123+
| `args` (optional) | A list of args/flags to pass to `go install` (e.g. `-tags containers_image_openpgp`) |
124+
| `env` (optional) | A list key=value environment variables to use when running `go install` |
123125

124126
The `module` option allows for a special entry:
125127
- `.` or `path/to/module/on/disk`
126128

127129
The `ldflags` allow for templating with the following variables:
128130

129-
| Variable | Description |
130-
|--------|--------------------------------------------------------------------------------------------|
131-
| `{{ .Version }}` | The resolved version of the tool (which may differe from that of the `version.want` value) |
131+
| Variable | Description |
132+
|--------|-------------------------------------------------------------------------------------------------------|
133+
| `{{ .Version }}` | The resolved version of the tool (which may differe from that of the `version.want` value) |
132134

133135
In addition to these variables, [sprig functions](http://masterminds.github.io/sprig/) are allowed; for example:
134136
```yaml
@@ -145,10 +147,10 @@ The default version resolver for this method is `go-proxy`.
145147

146148
The `hosted-shell` install method uses a hosted shell script to install a tool. It requires the following configuration options:
147149

148-
| Option | Description |
149-
|--------|------------------------------------------------------------|
150-
| `url` | The URL to the hosted shell script (e.g. `https://raw.githubusercontent.com/anchore/syft/main/install.sh`) |
151-
| `args` (optional) | Arguments to pass to the shell script (as a single string) |
150+
| Option | Description |
151+
|--------|------------------------------------------------------------------------------------------------------------|
152+
| `url` | The URL to the hosted shell script (e.g. `https://raw.githubusercontent.com/anchore/syft/main/install.sh`) |
153+
| `args` (optional) | Arguments to pass to the shell script (as a single string) |
152154

153155
If the URL refers to either `github.com` or `raw.githubusercontent.com` then the default version resolver is `github-release`.
154156
Otherwise, the version resolver must be specified manually.
@@ -208,9 +210,9 @@ is a version constraint used.
208210

209211
The `go-proxy` version method reaches out to `proxy.golang.org` to determine the latest version of a Go module. It requires the following configuration options:
210212

211-
| Option | Description |
212-
|--------|----------------------------------------------------------------------------------------------------------------------|
213-
| `module` | The FQDN to the Go module (e.g. `github.com/anchore/syft`) |
213+
| Option | Description |
214+
|--------|------------------------------------------------------------------------------------------------------------------------------------------|
215+
| `module` | The FQDN to the Go module (e.g. `github.com/anchore/syft`) |
214216
| `allow-unresolved-version` | If the latest version cannot be found by the proxy allow for "latest" as a valid value (which `go install` supports) |
215217

216218
The `version.want` option allows a special entry:

cmd/binny/cli/command/add_go_install.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,16 @@ func runAddGoInstallConfig(cmdCfg AddGoInstallConfig, nameVersion string) error
7575
return fmt.Errorf("invalid ldflags: %w", err)
7676
}
7777

78+
if err := validateEnvSlice(iCfg.Env); err != nil {
79+
return err
80+
}
81+
7882
coreInstallParams := goinstall.InstallerParameters{
7983
Module: iCfg.Module,
8084
Entrypoint: iCfg.Entrypoint,
8185
LDFlags: ldFlagsList,
86+
Args: iCfg.Args,
87+
Env: iCfg.Env,
8288
}
8389

8490
installParamMap, err := toMap(coreInstallParams)
@@ -103,3 +109,12 @@ func runAddGoInstallConfig(cmdCfg AddGoInstallConfig, nameVersion string) error
103109

104110
return updateConfiguration(cmdCfg.Config, toolCfg)
105111
}
112+
113+
func validateEnvSlice(env []string) error {
114+
for _, e := range env {
115+
if !strings.Contains(e, "=") {
116+
return fmt.Errorf("invalid env format: %q", e)
117+
}
118+
}
119+
return nil
120+
}

cmd/binny/cli/option/go_install.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package option
33
import "github.com/anchore/clio"
44

55
type GoInstall struct {
6-
Module string `json:"module" yaml:"module" mapstructure:"module"`
7-
Entrypoint string `json:"entrypoint" yaml:"entrypoint" mapstructure:"entrypoint"`
8-
LDFlags string `json:"ld-flags" yaml:"ld-flags" mapstructure:"ld-flags"`
6+
Module string `json:"module" yaml:"module" mapstructure:"module"`
7+
Entrypoint string `json:"entrypoint" yaml:"entrypoint" mapstructure:"entrypoint"`
8+
LDFlags string `json:"ld-flags" yaml:"ld-flags" mapstructure:"ld-flags"`
9+
Args []string `json:"args" yaml:"args" mapstructure:"args"`
10+
Env []string `json:"env" yaml:"env" mapstructure:"env"`
911
}
1012

1113
func (o *GoInstall) AddFlags(flags clio.FlagSet) {
1214
flags.StringVarP(&o.Module, "module", "m", "Go module (e.g. github.com/anchore/syft)")
1315
flags.StringVarP(&o.Entrypoint, "entrypoint", "e", "Entrypoint within the go module (e.g. cmd/syft)")
1416
flags.StringVarP(&o.LDFlags, "ld-flags", "l", "LD flags to pass to the go install command (e.g. -ldflags \"-X main.version=1.0.0\")")
17+
flags.StringArrayVarP(&o.Args, "args", "a", "Additional arguments to pass to the go install command")
18+
flags.StringArrayVarP(&o.Env, "env", "", "Environment variables to pass to the go install command")
1519
}

test/cli/trait_assertions_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cli
22

33
import (
44
"encoding/json"
5-
"github.com/google/go-cmp/cmp"
65
"os"
76
"os/exec"
87
"path/filepath"
@@ -11,6 +10,7 @@ import (
1110
"testing"
1211

1312
"github.com/acarl005/stripansi"
13+
"github.com/google/go-cmp/cmp"
1414
"github.com/stretchr/testify/require"
1515
)
1616

tool/goinstall/installer.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ type InstallerParameters struct {
2121
Module string `json:"module" yaml:"module" mapstructure:"module"`
2222
Entrypoint string `json:"entrypoint" yaml:"entrypoint" mapstructure:"entrypoint"`
2323
LDFlags []string `json:"ldflags" yaml:"ldflags" mapstructure:"ldflags"`
24+
Args []string `json:"args" yaml:"args" mapstructure:"args"`
25+
Env []string `json:"env" yaml:"env" mapstructure:"env"`
2426
}
2527

2628
type Installer struct {
2729
config InstallerParameters
28-
goInstallRunner func(spec, ldflags, destDir string) error
30+
goInstallRunner func(spec, ldflags string, args []string, env []string, destDir string) error
2931
}
3032

3133
func NewInstaller(cfg InstallerParameters) Installer {
@@ -57,17 +59,57 @@ func (i Installer) InstallTo(version, destDir string) (string, error) {
5759
return "", fmt.Errorf("failed to template ldflags: %v", err)
5860
}
5961

60-
if err := i.goInstallRunner(spec, ldflags, destDir); err != nil {
62+
args, err := templateSlice(i.config.Args, version)
63+
if err != nil {
64+
return "", fmt.Errorf("failed to template args: %v", err)
65+
}
66+
67+
if err := validateEnvSlice(i.config.Env); err != nil {
68+
return "", err
69+
}
70+
71+
env, err := templateSlice(i.config.Env, version)
72+
if err != nil {
73+
return "", fmt.Errorf("failed to template env: %v", err)
74+
}
75+
76+
if err := i.goInstallRunner(spec, ldflags, args, env, destDir); err != nil {
6177
return "", fmt.Errorf("failed to install: %v", err)
6278
}
6379

6480
return binPath, nil
6581
}
6682

83+
func validateEnvSlice(env []string) error {
84+
for _, e := range env {
85+
if !strings.Contains(e, "=") {
86+
return fmt.Errorf("invalid env format: %q", e)
87+
}
88+
}
89+
return nil
90+
}
91+
92+
func templateSlice(in []string, version string) ([]string, error) {
93+
ret := make([]string, len(in))
94+
for i, arg := range in {
95+
rendered, err := templateString(arg, version)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
ret[i] = rendered
101+
}
102+
return ret, nil
103+
}
104+
67105
func templateFlags(ldFlags []string, version string) (string, error) {
68106
flags := strings.Join(ldFlags, " ")
69107

70-
tmpl, err := template.New("ldflags").Funcs(sprig.FuncMap()).Parse(flags)
108+
return templateString(flags, version)
109+
}
110+
111+
func templateString(in string, version string) (string, error) {
112+
tmpl, err := template.New("ldflags").Funcs(sprig.FuncMap()).Parse(in)
71113
if err != nil {
72114
return "", err
73115
}
@@ -84,17 +126,25 @@ func templateFlags(ldFlags []string, version string) (string, error) {
84126
return buf.String(), nil
85127
}
86128

87-
func runGoInstall(spec, ldflags, destDir string) error {
129+
func runGoInstall(spec, ldflags string, userArgs, userEnv []string, destDir string) error {
88130
args := []string{"install"}
131+
args = append(args, userArgs...)
132+
89133
if ldflags != "" {
90134
args = append(args, fmt.Sprintf("-ldflags=%s", ldflags))
91135
}
92136
args = append(args, spec)
93137

94-
log.Trace("running: go " + strings.Join(args, " "))
138+
log.WithFields("env-vars", len(userEnv)).Trace("running: go " + strings.Join(args, " "))
95139

96140
cmd := exec.Command("go", args...)
97-
cmd.Env = append(os.Environ(), "GOBIN="+destDir)
141+
142+
// set env vars...
143+
env := os.Environ()
144+
env = append(env, userEnv...)
145+
// always override any conflicting env vars
146+
env = append(env, "GOBIN="+destDir)
147+
cmd.Env = env
98148

99149
output, err := cmd.CombinedOutput()
100150
if err != nil {

tool/goinstall/installer_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func Test_templateFlags(t *testing.T) {
6666
func TestInstaller_InstallTo(t *testing.T) {
6767
type fields struct {
6868
config InstallerParameters
69-
goInstallRunner func(spec, ldflags, destDir string) error
69+
goInstallRunner func(spec, ldflags string, args, env []string, destDir string) error
7070
}
7171
type args struct {
7272
version string
@@ -88,11 +88,21 @@ func TestInstaller_InstallTo(t *testing.T) {
8888
LDFlags: []string{
8989
"-X github.com/anchore/binny/internal/version.Version={{.Version}}",
9090
},
91+
Args: []string{
92+
"-tags",
93+
"containers_image_openpgp",
94+
},
95+
Env: []string{
96+
"FOO=BAR",
97+
"BAZ=0",
98+
},
9199
},
92-
goInstallRunner: func(spec, ldflags, destDir string) error {
100+
goInstallRunner: func(spec, ldflags string, userArgs, userEnv []string, destDir string) error {
93101
assert.Equal(t, "github.com/anchore/binny/cmd/binny@1.2.3", spec)
94102
assert.Equal(t, "-X github.com/anchore/binny/internal/version.Version=1.2.3", ldflags)
95103
assert.Equal(t, "/tmp/to/place", destDir)
104+
assert.Equal(t, []string{"-tags", "containers_image_openpgp"}, userArgs)
105+
assert.Equal(t, []string{"FOO=BAR", "BAZ=0"}, userEnv)
96106
return nil
97107
},
98108
},

tool/install.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ func makeStagingArea(store *binny.Store, toolName string) (string, func(), error
9191
if err != nil {
9292
return "", cleanup, fmt.Errorf("failed to get absolute path for store root: %w", err)
9393
}
94+
95+
if _, err := os.Stat(absRoot); os.IsNotExist(err) {
96+
if err := os.MkdirAll(absRoot, 0755); err != nil {
97+
return "", cleanup, fmt.Errorf("failed to create store root: %w", err)
98+
}
99+
}
100+
94101
tmpdir, err := os.MkdirTemp(absRoot, fmt.Sprintf("binny-install-%s-", toolName))
95102
if err != nil {
96103
return "", cleanup, fmt.Errorf("failed to create temp directory: %w", err)

0 commit comments

Comments
 (0)