Skip to content

Commit

Permalink
add go-install arguments and env vars
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
  • Loading branch information
wagoodman committed Jun 6, 2024
1 parent 01de30c commit ab7a5a0
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 36 deletions.
50 changes: 26 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ with:
# arbitrary key-value pairs for the install method
```

| Option | Description |
|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | The name of the tool to install. This is used to determine the installation directory and the name of the binary. |
| `version.want` | The version of the tool to install. This can be a specific version, or a version range. |
| `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. |
| `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. |
| `version.with` | The configuration options for the version method. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
| `method` | The method to use to install the tool. See the [Install Methods](#install-methods) section for more details. |
| `with` | The configuration options for the install method. See the [Install Methods](#install-methods) section for more details. |
| Option | Description |
|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | The name of the tool to install. This is used to determine the installation directory and the name of the binary. |
| `version.want` | The version of the tool to install. This can be a specific version, or a version range. |
| `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. |
| `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. |
| `version.with` | The configuration options for the version method. See the [Version Resolver Methods](#version-resolver-methods) section for more details. |
| `method` | The method to use to install the tool. See the [Install Methods](#install-methods) section for more details. |
| `with` | The configuration options for the install method. See the [Install Methods](#install-methods) section for more details. |


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

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

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

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

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

| Variable | Description |
|--------|--------------------------------------------------------------------------------------------|
| `{{ .Version }}` | The resolved version of the tool (which may differe from that of the `version.want` value) |
| Variable | Description |
|--------|-------------------------------------------------------------------------------------------------------|
| `{{ .Version }}` | The resolved version of the tool (which may differe from that of the `version.want` value) |

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

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

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

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

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:

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

The `version.want` option allows a special entry:
Expand Down
2 changes: 1 addition & 1 deletion test/cli/trait_assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cli

import (
"encoding/json"
"github.com/google/go-cmp/cmp"
"os"
"os/exec"
"path/filepath"
Expand All @@ -11,6 +10,7 @@ import (
"testing"

"github.com/acarl005/stripansi"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)

Expand Down
63 changes: 54 additions & 9 deletions tool/goinstall/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import (
var _ binny.Installer = (*Installer)(nil)

type InstallerParameters struct {
Module string `json:"module" yaml:"module" mapstructure:"module"`
Entrypoint string `json:"entrypoint" yaml:"entrypoint" mapstructure:"entrypoint"`
LDFlags []string `json:"ldflags" yaml:"ldflags" mapstructure:"ldflags"`
Module string `json:"module" yaml:"module" mapstructure:"module"`
Entrypoint string `json:"entrypoint" yaml:"entrypoint" mapstructure:"entrypoint"`
LDFlags []string `json:"ldflags" yaml:"ldflags" mapstructure:"ldflags"`
Args []string `json:"args" yaml:"args" mapstructure:"args"`
Env map[string]any `json:"env" yaml:"env" mapstructure:"env"`
}

type Installer struct {
config InstallerParameters
goInstallRunner func(spec, ldflags, destDir string) error
goInstallRunner func(spec, ldflags string, args []string, env []string, destDir string) error
}

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

if err := i.goInstallRunner(spec, ldflags, destDir); err != nil {
args, err := templateSlice(i.config.Args, version)
if err != nil {
return "", fmt.Errorf("failed to template args: %v", err)
}

env, err := templateSlice(toEnvSlice(i.config.Env), version)
if err != nil {
return "", fmt.Errorf("failed to template env: %v", err)
}

if err := i.goInstallRunner(spec, ldflags, args, env, destDir); err != nil {
return "", fmt.Errorf("failed to install: %v", err)
}

return binPath, nil
}

func templateSlice(in []string, version string) ([]string, error) {
ret := make([]string, len(in))
for i, arg := range in {
rendered, err := templateString(arg, version)
if err != nil {
return nil, err
}

ret[i] = rendered
}
return ret, nil
}

func templateFlags(ldFlags []string, version string) (string, error) {
flags := strings.Join(ldFlags, " ")

tmpl, err := template.New("ldflags").Funcs(sprig.FuncMap()).Parse(flags)
return templateString(flags, version)
}

func templateString(in string, version string) (string, error) {
tmpl, err := template.New("ldflags").Funcs(sprig.FuncMap()).Parse(in)
if err != nil {
return "", err
}
Expand All @@ -84,21 +113,37 @@ func templateFlags(ldFlags []string, version string) (string, error) {
return buf.String(), nil
}

func runGoInstall(spec, ldflags, destDir string) error {
func runGoInstall(spec, ldflags string, userArgs, userEnv []string, destDir string) error {
args := []string{"install"}
args = append(args, userArgs...)

if ldflags != "" {
args = append(args, fmt.Sprintf("-ldflags=%s", ldflags))
}
args = append(args, spec)

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

cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), "GOBIN="+destDir)

// set env vars...
env := os.Environ()
env = append(env, userEnv...)
// always override any conflicting env vars
env = append(env, "GOBIN="+destDir)
cmd.Env = env

output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("installation failed: %v\nOutput: %s", err, output)
}
return nil
}

func toEnvSlice(env map[string]any) []string {
var envSlice []string
for k, v := range env {
envSlice = append(envSlice, fmt.Sprintf("%s=%v", k, v))
}
return envSlice
}
14 changes: 12 additions & 2 deletions tool/goinstall/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func Test_templateFlags(t *testing.T) {
func TestInstaller_InstallTo(t *testing.T) {
type fields struct {
config InstallerParameters
goInstallRunner func(spec, ldflags, destDir string) error
goInstallRunner func(spec, ldflags string, args, env []string, destDir string) error
}
type args struct {
version string
Expand All @@ -88,11 +88,21 @@ func TestInstaller_InstallTo(t *testing.T) {
LDFlags: []string{
"-X github.com/anchore/binny/internal/version.Version={{.Version}}",
},
Args: []string{
"-tags",
"containers_image_openpgp",
},
Env: map[string]any{
"FOO": "BAR",
"BAZ": 0,
},
},
goInstallRunner: func(spec, ldflags, destDir string) error {
goInstallRunner: func(spec, ldflags string, userArgs, userEnv []string, destDir string) error {
assert.Equal(t, "github.com/anchore/binny/cmd/binny@1.2.3", spec)
assert.Equal(t, "-X github.com/anchore/binny/internal/version.Version=1.2.3", ldflags)
assert.Equal(t, "/tmp/to/place", destDir)
assert.Equal(t, []string{"-tags", "containers_image_openpgp"}, userArgs)
assert.Equal(t, []string{"FOO=BAR", "BAZ=0"}, userEnv)
return nil
},
},
Expand Down

0 comments on commit ab7a5a0

Please sign in to comment.