Skip to content

Commit

Permalink
fix git version resolver and add docs
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Sep 20, 2023
1 parent 1831d60 commit 7cb0554
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .binny.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tools:
- name: binny
version:
# since the module is ., "current" means whatever is checked out
# since the module is . then "current" means whatever is checked out
want: current
method: go-install
with:
Expand Down
200 changes: 179 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,190 @@ curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh
## Usage

Keep a configuration in your repo with the binaries you want to manage. For example:

```yaml
# .binny.yaml
- name: gh
version:
want: v2.33.0
method: github-release
with:
repo: cli/cli
tools:
- name: gh
version:
want: v2.33.0
method: github-release
with:
repo: cli/cli

- name: quill
version:
want: v0.4.1
method: github-release
with:
repo: anchore/quill

- name: chronicle
version:
want: v0.7.0
method: github-release
with:
repo: anchore/chronicle
```
- name: quill
version:
want: v0.4.1
method: github-release
with:
repo: anchore/quill
Then you can run:
- `binny install` to install all tools in the configuration
- `binny install <name>` to install a specific tool
- `binny check` to verify all configured tools are installed
- `binny update-lock` to update any pinned versions in the configuration with the latest available versions

You can add tools to the configuration one of two ways:
- manually, by adding a new entry to the configuration file (see the [Configuration](#configuration) section below)
- with the `binny add <method>` commands, which will handle the configuration for you

- name: chronicle
version:
want: v0.7.0

## Configuration

The configuration file is a YAML file with a list of tools to manage. Each tool has a name, a version, and
a method for installing it. You can optionally specify a specific method for checking the latest version of
the tool, however, this is not necessary as all install methods have a default version resolver.


### Tool Configuration

Each tool has the following configuration options:

```yaml
name: chronicle
version:
want: v0.7.0
constraint: <= v0.9.0
method: github-release
with:
repo: anchore/chronicle
# arbitrary key-value pairs for the version resolver method
with:
# arbitrary key-value pairs for the install method
```

Then you can run:
- `benny install` to install all binaries in the configuration
- `benny install <name>` to install a specific binary
- `benny check` to verify all configured binaries are installed
- `benny update-lock` to update any pinned versions in the configuration with the latest available versions
| 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

Install methods specify where the tool binary should be pulled or built from.


#### `github-release`

The `github-release` install method uses the GitHub Releases API to download the latest release of a tool. It requires the following configuration options:

| Option | Description |
|--------|-------------------------------------------------------------------------------------------------|
| `repo` | The GitHub repository to reference releases from. This should be in the format `<owner>/<repo>` |

The default version resolver for this method is `github-release`.


#### `go-install`

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 }}`) |

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) |

In addition to these variables, [sprig functions](http://masterminds.github.io/sprig/) are allowed; for example:
```yaml
ldflags:
- -X main.buildDate={{ now | date "2006-01-02T15:04:05Z07:00" }}
```

For more information about sprig functions, see the [sprig documentation](http://masterminds.github.io/sprig/).

The default version resolver for this method is `go-proxy`.


#### `hosted-shell`

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) |

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.



### Version Methods

The version method specifies how to determine the latest version for a tool.

#### `git`

The `git` version method will use a git repo on disk as a source for resolving versions via tags. It requires the following configuration options:

| Option | Description |
|--------|----------------------------------------|
| `path` | The path to the git repository on disk |

The `version.want` option allows a special entry:
- `current`: use the current commit checked out in the repo

**note**: this method is still under development. Currently it is most useful for tools that are being used where that are developed:

```yaml
- name: binny
version:
# since the module is . then "current" means whatever is checked out
want: current
method: go-install
with:
# aka: github.com/anchore/binny, without going through github / goproxy (stay local)
module: .
entrypoint: cmd/binny
ldflags:
- -X main.version={{ .Version }}
- -X main.gitCommit={{ .Version }}
- -X main.gitDescription={{ .Version }}
# note: sprig functions are available: http://masterminds.github.io/sprig/
- -X main.buildDate={{ now | date "2006-01-02T15:04:05Z07:00" }}
```

#### `github-release`

The `github-release` version method uses the GitHub Releases API to determine the latest release of a tool. It requires the following configuration options:

| Option | Description |
|--------|-------------------------------------------------------------------------------------------------|
| `repo` | The GitHub repository to reference releases from. This should be in the format `<owner>/<repo>` |

The `version.want` option allows a special entry:
- `latest`: don't pin to a version, use the latest available

#### `go-proxy`

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`) |

The `version.want` option allows a special entry:
- `latest`: don't pin to a version, use the latest available
2 changes: 1 addition & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ tasks:
- "{{ .TOOL_DIR }}/binny"
status:
- "test -f {{ .TOOL_DIR }}/binny"
# we just need a release of benny, doesn't matter which one (binny will update itself, this is just a bootstrap step)
# we just need a release of binny, doesn't matter which one (binny will update itself, this is just a bootstrap step)
# note: on the first release of binny, switch to the curl approach
# cmd: "curl -sSfL https://raw.githubusercontent.com/{{ .OWNER }}/{{ .PROJECT }}/main/install.sh | sh -s -- -b {{ .TOOL_DIR }}"
cmd: "go build -o {{ .TOOL_DIR }}/binny ./cmd/binny"
Expand Down
4 changes: 2 additions & 2 deletions cmd/binny/cli/command/update_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (p updateLockYamlPatcher) PatchYaml(node *yaml.Node) error {
}

func getUpdatedConfig(cfg option.Core, names []string) (*option.Core, error) {
nameSet := strset.New()
nameSet := strset.New(names...)
if len(names) == 0 {
nameSet.Add(cfg.Tools.Names()...)
}
Expand All @@ -88,7 +88,7 @@ func getUpdatedConfig(cfg option.Core, names []string) (*option.Core, error) {

newVersion, err := t.UpdateVersion(intent.Want, intent.Constraint)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to update version for tool %q: %w", toolCfg.Name, err)
}

if newVersion == toolCfg.Version.Want {
Expand Down
23 changes: 17 additions & 6 deletions tool/git/version_resolver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package git

import (
"errors"
"fmt"

"github.com/go-git/go-git/v5"
Expand All @@ -27,6 +28,10 @@ func NewVersionResolver(cfg VersionResolutionParameters) *VersionResolver {
}

func (v VersionResolver) UpdateVersion(want, constraint string) (string, error) {
if want == "current" {
// always use the same reference
return want, nil
}
return v.ResolveVersion(want, constraint)
}

Expand Down Expand Up @@ -62,7 +67,7 @@ func headCommit(repoPath string) (string, error) {
}
ref, err := r.Head()
if err != nil {
return "", fmt.Errorf("unable fetch head: %w", err)
return "", fmt.Errorf("unable to fetch head for %q: %w", repoPath, err)
}
return ref.Hash().String(), nil
}
Expand All @@ -73,22 +78,28 @@ func byReference(repoPath, ref string) (string, error) {
return "", fmt.Errorf("unable to open repo: %w", err)
}

// try by tag first...
plumbRef, err := r.Tag(ref)
if err != nil {
return "", fmt.Errorf("unable fetch tag: %w", err)
if !errors.Is(err, git.ErrTagNotFound) {
return "", fmt.Errorf("unable to fetch tag for %q: %w", ref, err)
}
}

if plumbRef != nil {
return plumbRef.Name().String(), nil
}

plumbRef, err = r.Reference(plumbing.ReferenceName(ref), true)
// then by hash...
commit, err := r.CommitObject(plumbing.NewHash(ref))
if err != nil {
return "", fmt.Errorf("unable fetch reference: %w", err)
if !errors.Is(err, plumbing.ErrReferenceNotFound) {
return "", fmt.Errorf("unable to fetch hash for %q: %w", ref, err)
}
}

if plumbRef != nil {
return plumbRef.Hash().String(), nil
if commit != nil {
return commit.Hash.String(), nil
}

return "", nil
Expand Down
2 changes: 1 addition & 1 deletion tool/resolve_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func ResolveVersion(tool binny.VersionResolver, intent binny.VersionIntent) (str

resolvedVersion, err := tool.ResolveVersion(want, constraint)
if err != nil {
return "", err
return "", fmt.Errorf("failed to resolve version: %w", err)
}

if constraint != "" {
Expand Down

0 comments on commit 7cb0554

Please sign in to comment.