Skip to content

Commit

Permalink
feat: upload extra files to the release (#1333)
Browse files Browse the repository at this point in the history
* feat: upload extra files to the release

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: retry upload

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: go mod tidy

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: globs

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: globs

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: typo

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
  • Loading branch information
caarlos0 committed Feb 11, 2020
1 parent dd35f28 commit d7c5405
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 44 deletions.
2 changes: 2 additions & 0 deletions internal/artifact/artifact.go
Expand Up @@ -27,6 +27,8 @@ const (
UploadableArchive Type = iota
// UploadableBinary is a binary file to be uploaded
UploadableBinary
// UploadableFile is any file that can be uploaded
UploadableFile
// Binary is a binary (output of a gobuild)
Binary
// LinuxPackage is a linux package generated by nfpm
Expand Down
115 changes: 79 additions & 36 deletions internal/pipe/release/release.go
Expand Up @@ -2,6 +2,7 @@ package release

import (
"os"
"path/filepath"
"time"

"github.com/apex/log"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/kamilsk/retry/v4"
"github.com/kamilsk/retry/v4/backoff"
"github.com/kamilsk/retry/v4/strategy"
"github.com/mattn/go-zglob"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -123,55 +125,96 @@ func doPublish(ctx *context.Context, client client.Client) error {
return err
}

var filters = []artifact.Filter{
artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.Checksum),
artifact.ByType(artifact.Signature),
artifact.ByType(artifact.LinuxPackage),
),
extraFiles, err := findFiles(ctx)
if err != nil {
return err
}

for name, path := range extraFiles {
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.Wrapf(err, "failed to upload %s", name)
}
ctx.Artifacts.Add(&artifact.Artifact{
Name: name,
Path: path,
Type: artifact.UploadableFile,
})
}

var filters = artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.Checksum),
artifact.ByType(artifact.Signature),
artifact.ByType(artifact.LinuxPackage),
)

if len(ctx.Config.Release.IDs) > 0 {
filters = append(filters, artifact.ByIDs(ctx.Config.Release.IDs...))
filters = artifact.And(filters, artifact.ByIDs(ctx.Config.Release.IDs...))
}

filters = artifact.Or(filters, artifact.ByType(artifact.UploadableFile))

var g = semerrgroup.New(ctx.Parallelism)
for _, artifact := range ctx.Artifacts.Filter(artifact.And(filters...)).List() {
for _, artifact := range ctx.Artifacts.Filter(filters).List() {
artifact := artifact
g.Go(func() error {
var repeats uint
what := func(try uint) error {
repeats = try + 1
if uploadErr := upload(ctx, client, releaseID, artifact); uploadErr != nil {
log.WithFields(log.Fields{
"try": try,
"artifact": artifact.Name,
}).Warnf("failed to upload artifact, will retry")
return uploadErr
}
return nil
}
how := []func(uint, error) bool{
strategy.Limit(10),
strategy.Backoff(backoff.Linear(50 * time.Millisecond)),
}
if err := retry.Try(ctx, what, how...); err != nil {
return errors.Wrapf(err, "failed to upload %s after %d retries", artifact.Name, repeats)
}
return nil
return upload(ctx, client, releaseID, artifact)
})
}
return g.Wait()
}

func upload(ctx *context.Context, client client.Client, releaseID string, artifact *artifact.Artifact) error {
file, err := os.Open(artifact.Path)
if err != nil {
return err
var repeats uint
what := func(try uint) error {
repeats = try + 1
file, err := os.Open(artifact.Path)
if err != nil {
return err
}
defer file.Close() // nolint: errcheck
log.WithField("file", file.Name()).WithField("name", artifact.Name).Info("uploading to release")
if err := client.Upload(ctx, releaseID, artifact, file); err != nil {
log.WithFields(log.Fields{
"try": try,
"artifact": artifact.Name,
}).Warnf("failed to upload artifact, will retry")
return err
}
return nil
}
how := []func(uint, error) bool{
strategy.Limit(10),
strategy.Backoff(backoff.Linear(50 * time.Millisecond)),
}
if err := retry.Try(ctx, what, how...); err != nil {
return errors.Wrapf(err, "failed to upload %s after %d retries", artifact.Name, repeats)
}
return nil
}

func findFiles(ctx *context.Context) (map[string]string, error) {
var result = map[string]string{}
for _, extra := range ctx.Config.Release.ExtraFiles {
if extra.Glob != "" {
files, err := zglob.Glob(extra.Glob)
if err != nil {
return result, errors.Wrapf(err, "globbing failed for pattern %s", extra.Glob)
}
for _, file := range files {
info, err := os.Stat(file)
if err == nil && info.IsDir() {
log.Debugf("ignoring directory %s", file)
continue
}
var name = filepath.Base(file)
if old, ok := result[name]; ok {
log.Warnf("overriding %s with %s for name %s", old, file, name)
}
result[name] = file
}
}
}
defer file.Close() // nolint: errcheck
log.WithField("file", file.Name()).WithField("name", artifact.Name).Info("uploading to release")
return client.Upload(ctx, releaseID, artifact, file)
return result, nil
}
56 changes: 56 additions & 0 deletions internal/pipe/release/release_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"testing"

Expand Down Expand Up @@ -104,6 +105,9 @@ func TestRunPipeWithIDsThenFilters(t *testing.T) {
Name: "test",
},
IDs: []string{"foo"},
ExtraFiles: []config.ExtraFile{
{Glob: "./testdata/**/*"},
},
},
}
var ctx = context.New(config)
Expand Down Expand Up @@ -146,6 +150,9 @@ func TestRunPipeWithIDsThenFilters(t *testing.T) {
assert.True(t, client.UploadedFile)
assert.Contains(t, client.UploadedFileNames, "bin.deb")
assert.Contains(t, client.UploadedFileNames, "bin.tar.gz")
assert.Contains(t, client.UploadedFileNames, "release1.golden")
assert.Contains(t, client.UploadedFileNames, "release2.golden")
assert.Contains(t, client.UploadedFileNames, "f1")
assert.NotContains(t, client.UploadedFileNames, "filtered.deb")
assert.NotContains(t, client.UploadedFileNames, "filtered.tar.gz")
}
Expand Down Expand Up @@ -219,6 +226,50 @@ func TestRunPipeUploadFailure(t *testing.T) {
assert.False(t, client.UploadedFile)
}

func TestRunPipeExtraFileNotFound(t *testing.T) {
var config = config.Project{
Release: config.Release{
GitHub: config.Repo{
Owner: "test",
Name: "test",
},
ExtraFiles: []config.ExtraFile{
{Glob: "./testdata/release2.golden"},
{Glob: "./nope"},
},
},
}
var ctx = context.New(config)
ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
client := &DummyClient{}
assert.EqualError(t, doPublish(ctx, client), "globbing failed for pattern ./nope: file does not exist")
assert.True(t, client.CreatedRelease)
assert.False(t, client.UploadedFile)
}

func TestRunPipeExtraOverride(t *testing.T) {
var config = config.Project{
Release: config.Release{
GitHub: config.Repo{
Owner: "test",
Name: "test",
},
ExtraFiles: []config.ExtraFile{
{Glob: "./testdata/**/*"},
{Glob: "./testdata/upload_same_name/f1"},
},
},
}
var ctx = context.New(config)
ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
client := &DummyClient{}
assert.NoError(t, doPublish(ctx, client))
assert.True(t, client.CreatedRelease)
assert.True(t, client.UploadedFile)
assert.Contains(t, client.UploadedFileNames, "f1")
assert.True(t, strings.HasSuffix(client.UploadedFilePaths["f1"], "testdata/upload_same_name/f1"))
}

func TestRunPipeUploadRetry(t *testing.T) {
folder, err := ioutil.TempDir("", "goreleasertest")
assert.NoError(t, err)
Expand Down Expand Up @@ -470,6 +521,7 @@ type DummyClient struct {
CreatedRelease bool
UploadedFile bool
UploadedFileNames []string
UploadedFilePaths map[string]string
FailFirstUpload bool
Lock sync.Mutex
}
Expand All @@ -489,6 +541,9 @@ func (client *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.
func (client *DummyClient) Upload(ctx *context.Context, releaseID string, artifact *artifact.Artifact, file *os.File) error {
client.Lock.Lock()
defer client.Lock.Unlock()
if client.UploadedFilePaths == nil {
client.UploadedFilePaths = map[string]string{}
}
// ensure file is read to better mimic real behavior
_, err := ioutil.ReadAll(file)
if err != nil {
Expand All @@ -503,5 +558,6 @@ func (client *DummyClient) Upload(ctx *context.Context, releaseID string, artifa
}
client.UploadedFile = true
client.UploadedFileNames = append(client.UploadedFileNames, artifact.Name)
client.UploadedFilePaths[artifact.Name] = artifact.Path
return nil
}
Empty file.
Empty file.
22 changes: 14 additions & 8 deletions pkg/config/config.go
Expand Up @@ -178,14 +178,20 @@ type Archive struct {

// Release config used for the GitHub/GitLab release
type Release struct {
GitHub Repo `yaml:",omitempty"`
GitLab Repo `yaml:",omitempty"`
Gitea Repo `yaml:",omitempty"`
Draft bool `yaml:",omitempty"`
Disable bool `yaml:",omitempty"`
Prerelease string `yaml:",omitempty"`
NameTemplate string `yaml:"name_template,omitempty"`
IDs []string `yaml:"ids,omitempty"`
GitHub Repo `yaml:",omitempty"`
GitLab Repo `yaml:",omitempty"`
Gitea Repo `yaml:",omitempty"`
Draft bool `yaml:",omitempty"`
Disable bool `yaml:",omitempty"`
Prerelease string `yaml:",omitempty"`
NameTemplate string `yaml:"name_template,omitempty"`
IDs []string `yaml:"ids,omitempty"`
ExtraFiles []ExtraFile `yaml:"extra_files,omitempty"`
}

// ExtraFile on a release
type ExtraFile struct {
Glob string `yaml:"glob,omitempty"`
}

// NFPM config
Expand Down
27 changes: 27 additions & 0 deletions www/content/release.md
Expand Up @@ -45,6 +45,15 @@ release:
# GitHub.
# Defaults to false.
disable: true

# You can add extra pre-existing files to the release.
# The filename on the release will be the last part of the path (base). If
# another file with the same name exists, the latest one found will be used.
# Defaults to empty.
extra_files:
- glob: ./path/to/file.txt
- glob: ./glob/**/to/**/file/**/*
- glob: ./glob/foo/to/bar/file/foobar/override_from_previous
```

Second, let's see what can be customized in the `release` section for GitLab.
Expand Down Expand Up @@ -73,6 +82,15 @@ release:
# GitLab.
# Defaults to false.
disable: true

# You can add extra pre-existing files to the release.
# The filename on the release will be the last part of the path (base). If
# another file with the same name exists, the latest one found will be used.
# Defaults to empty.
extra_files:
- glob: ./path/to/file.txt
- glob: ./glob/**/to/**/file/**/*
- glob: ./glob/foo/to/bar/file/foobar/override_from_previous
```

You can also configure the `release` section to upload to a [Gitea](https://gitea.io) instance:
Expand All @@ -99,6 +117,15 @@ release:
# Gitea.
# Defaults to false.
disable: true

# You can add extra pre-existing files to the release.
# The filename on the release will be the last part of the path (base). If
# another file with the same name exists, the latest one found will be used.
# Defaults to empty.
extra_files:
- glob: ./path/to/file.txt
- glob: ./glob/**/to/**/file/**/*
- glob: ./glob/foo/to/bar/file/foobar/override_from_previous
```

To enable uploading `tar.gz` and `checksums.txt` files you need to add the following to your Gitea config in `app.ini`:
Expand Down

0 comments on commit d7c5405

Please sign in to comment.