magefiles/packagecloud/push.go (143 lines of code) (raw):
package packagecloud
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/jpillora/backoff"
"github.com/magefile/mage/sh"
"github.com/sourcegraph/conc/pool"
)
var (
ignoredPackageCloudErrors = []string{
"architecture: Unrecognized CPU architecture",
"filename: has already been taken",
}
retryPackageCloudErrors = []string{
"502 Bad Gateway",
"504 Gateway Timeout",
"HTTP status code 520",
}
failedToRunPackageCloudCommandError = errors.New("failed to run PackageCloud command after 5 tries")
)
type packageCloudError struct {
err string
}
func newPackageCloudError(err string) *packageCloudError {
return &packageCloudError{err: err}
}
func (p *packageCloudError) isIgnored() bool {
for _, msg := range ignoredPackageCloudErrors {
if strings.Contains(p.err, msg) {
return true
}
}
return false
}
func (p *packageCloudError) isRetryable() bool {
for _, msg := range retryPackageCloudErrors {
if strings.Contains(p.err, msg) {
return true
}
}
return false
}
func (p *packageCloudError) Error() string {
return p.err
}
type PushOpts struct {
URL string
Namespace string
Token string
Branch string
Dist string
Flavor string
Concurrency int
DryRun bool
}
func Push(opts PushOpts) error {
releases, err := Releases(opts.Dist, opts.Branch, opts.Token, opts.URL, false)
if err != nil {
return err
}
packages, err := filepath.Glob(fmt.Sprintf("out/%s/*.%s", opts.Dist, opts.Dist))
if err != nil {
return err
}
pool := pool.New().WithMaxGoroutines(opts.Concurrency).WithErrors()
for _, release := range releases {
release := release
if opts.Flavor == "" || strings.Contains(release, opts.Flavor) {
for _, pkg := range packages {
pkg := pkg
pool.Go(func() error {
args := []string{
"push",
"--verbose",
"--url",
opts.URL,
fmt.Sprintf("%s/%s", opts.Namespace, release),
pkg,
}
fmt.Println("Pushing to PackageCloud with args: ", args)
if opts.DryRun {
return nil
}
return newPackageCloudCommand(args).run()
})
}
}
}
return pool.Wait()
}
type execFunc func(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error)
type packageCloudCommand struct {
args []string
backoff backoff.Backoff
stdout io.Writer
stderr io.Writer
exec execFunc
}
func newPackageCloudCommand(args []string) *packageCloudCommand {
return &packageCloudCommand{
args: args,
backoff: backoff.Backoff{
Min: time.Second,
Max: 5 * time.Second,
},
stdout: os.Stdout,
stderr: os.Stderr,
exec: sh.Exec,
}
}
func (p *packageCloudCommand) run() error {
for i := 0; i < 5; i++ {
time.Sleep(p.backoff.Duration())
_, err := fmt.Fprintf(p.stdout, "Running PackageCloud upload command \"package_cloud %+v\" try #%d\n", p.args, i+1)
if err != nil {
return err
}
var out bytes.Buffer
stdout := io.MultiWriter(&out, p.stdout)
stderr := io.MultiWriter(&out, p.stderr)
_, err = p.exec(
nil,
stdout,
stderr,
"package_cloud",
p.args...,
)
pkgCloudErr := newPackageCloudError(out.String())
if err == nil || pkgCloudErr.isIgnored() {
return nil
} else if pkgCloudErr.isRetryable() {
continue
}
return err
}
return failedToRunPackageCloudCommandError
}