1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-22 04:08:49 +02:00
Carlos A Becker 53bbc6546f
fix: goamd64 should allow the only range from v1 to v4
It was just allowing v2 and v3 due to some misreading on my side.
This commit fixes it to allow v1, v2, v3 and v4.

refs #3016

Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
2022-04-13 21:30:08 -03:00

501 lines
12 KiB
Go

package aur
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/client"
"github.com/goreleaser/goreleaser/internal/commitauthor"
"github.com/goreleaser/goreleaser/internal/git"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"golang.org/x/crypto/ssh"
)
const (
aurExtra = "AURConfig"
defaultSSHCommand = "ssh -i {{ .KeyPath }} -o StrictHostKeyChecking=accept-new -F /dev/null"
defaultCommitMsg = "Update to {{ .Tag }}"
)
var ErrNoArchivesFound = errors.New("no linux archives found")
// Pipe for arch linux's AUR pkgbuild.
type Pipe struct{}
func (Pipe) String() string { return "arch user repositories" }
func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.AURs) == 0 }
func (Pipe) Default(ctx *context.Context) error {
for i := range ctx.Config.AURs {
pkg := &ctx.Config.AURs[i]
pkg.CommitAuthor = commitauthor.Default(pkg.CommitAuthor)
if pkg.CommitMessageTemplate == "" {
pkg.CommitMessageTemplate = defaultCommitMsg
}
if pkg.Name == "" {
pkg.Name = ctx.Config.ProjectName
}
if !strings.HasSuffix(pkg.Name, "-bin") {
pkg.Name += "-bin"
}
if len(pkg.Conflicts) == 0 {
pkg.Conflicts = []string{ctx.Config.ProjectName}
}
if len(pkg.Provides) == 0 {
pkg.Provides = []string{ctx.Config.ProjectName}
}
if pkg.Rel == "" {
pkg.Rel = "1"
}
if pkg.GitSSHCommand == "" {
pkg.GitSSHCommand = defaultSSHCommand
}
if pkg.Goamd64 == "" {
pkg.Goamd64 = "v1"
}
}
return nil
}
func (Pipe) Run(ctx *context.Context) error {
cli, err := client.New(ctx)
if err != nil {
return err
}
return runAll(ctx, cli)
}
func runAll(ctx *context.Context, cli client.Client) error {
for _, aur := range ctx.Config.AURs {
err := doRun(ctx, aur, cli)
if err != nil {
return err
}
}
return nil
}
func doRun(ctx *context.Context, aur config.AUR, cl client.Client) error {
name, err := tmpl.New(ctx).Apply(aur.Name)
if err != nil {
return err
}
aur.Name = name
filters := []artifact.Filter{
artifact.ByGoos("linux"),
artifact.Or(
artifact.And(
artifact.ByGoarch("amd64"),
artifact.ByGoamd64(aur.Goamd64),
),
artifact.ByGoarch("arm64"),
artifact.ByGoarch("386"),
artifact.And(
artifact.ByGoarch("arm"),
artifact.Or(
artifact.ByGoarm("7"),
artifact.ByGoarm("6"),
),
),
),
artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
),
}
if len(aur.IDs) > 0 {
filters = append(filters, artifact.ByIDs(aur.IDs...))
}
archives := ctx.Artifacts.Filter(artifact.And(filters...)).List()
if len(archives) == 0 {
return ErrNoArchivesFound
}
pkg, err := tmpl.New(ctx).Apply(aur.Package)
if err != nil {
return err
}
if strings.TrimSpace(pkg) == "" {
art := archives[0]
switch art.Type {
case artifact.UploadableBinary:
name := art.Name
bin := art.ExtraOr(artifact.ExtraBinary, art.Name).(string)
pkg = fmt.Sprintf(`install -Dm755 "./%s "${pkgdir}/usr/bin/%s"`, name, bin)
case artifact.UploadableArchive:
for _, bin := range art.ExtraOr(artifact.ExtraBinaries, []string{}).([]string) {
pkg = fmt.Sprintf(`install -Dm755 "./%s" "${pkgdir}/usr/bin/%[1]s"`, bin)
break
}
}
log.Warnf("guessing package to be %q", pkg)
}
aur.Package = pkg
for _, info := range []struct {
name, tpl, ext string
kind artifact.Type
}{
{
name: "PKGBUILD",
tpl: aurTemplateData,
ext: ".pkgbuild",
kind: artifact.PkgBuild,
},
{
name: ".SRCINFO",
tpl: srcInfoTemplate,
ext: ".srcinfo",
kind: artifact.SrcInfo,
},
} {
pkgContent, err := buildPkgFile(ctx, aur, cl, archives, info.tpl)
if err != nil {
return err
}
path := filepath.Join(ctx.Config.Dist, "aur", aur.Name+info.ext)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("failed to write %s: %w", info.kind, err)
}
log.WithField("file", path).Info("writing")
if err := os.WriteFile(path, []byte(pkgContent), 0o644); err != nil { //nolint: gosec
return fmt.Errorf("failed to write %s: %w", info.kind, err)
}
ctx.Artifacts.Add(&artifact.Artifact{
Name: info.name,
Path: path,
Type: info.kind,
Extra: map[string]interface{}{
aurExtra: aur,
artifact.ExtraID: aur.Name,
},
})
}
return nil
}
func buildPkgFile(ctx *context.Context, pkg config.AUR, client client.Client, artifacts []*artifact.Artifact, tpl string) (string, error) {
data, err := dataFor(ctx, pkg, client, artifacts)
if err != nil {
return "", err
}
return applyTemplate(ctx, tpl, data)
}
func fixLines(s string) string {
lines := strings.Split(s, "\n")
var result []string
for _, line := range lines {
l := strings.TrimSpace(line)
if l == "" {
result = append(result, "")
continue
}
result = append(result, " "+l)
}
return strings.Join(result, "\n")
}
func applyTemplate(ctx *context.Context, tpl string, data templateData) (string, error) {
t := template.Must(
template.New(data.Name).
Funcs(template.FuncMap{
"fixLines": fixLines,
"pkgArray": toPkgBuildArray,
}).
Parse(tpl),
)
var out bytes.Buffer
if err := t.Execute(&out, data); err != nil {
return "", err
}
content, err := tmpl.New(ctx).Apply(out.String())
if err != nil {
return "", err
}
out.Reset()
// Sanitize the template output and get rid of trailing whitespace.
var (
r = strings.NewReader(content)
s = bufio.NewScanner(r)
)
for s.Scan() {
l := strings.TrimRight(s.Text(), " ")
_, _ = out.WriteString(l)
_ = out.WriteByte('\n')
}
if err := s.Err(); err != nil {
return "", err
}
return out.String(), nil
}
func toPkgBuildArray(ss []string) string {
result := make([]string, 0, len(ss))
for _, s := range ss {
result = append(result, fmt.Sprintf("'%s'", s))
}
return strings.Join(result, " ")
}
func toPkgBuildArch(arch string) string {
switch arch {
case "amd64":
return "x86_64"
case "386":
return "i686"
case "arm64":
return "aarch64"
case "arm6":
return "armv6h"
case "arm7":
return "armv7h"
default:
return "invalid" // should never get here
}
}
func dataFor(ctx *context.Context, cfg config.AUR, cl client.Client, artifacts []*artifact.Artifact) (templateData, error) {
result := templateData{
Name: cfg.Name,
Desc: cfg.Description,
Homepage: cfg.Homepage,
Version: fmt.Sprintf("%d.%d.%d", ctx.Semver.Major, ctx.Semver.Minor, ctx.Semver.Patch),
License: cfg.License,
Rel: cfg.Rel,
Maintainers: cfg.Maintainers,
Contributors: cfg.Contributors,
Provides: cfg.Provides,
Conflicts: cfg.Conflicts,
Depends: cfg.Depends,
OptDepends: cfg.OptDepends,
Package: cfg.Package,
}
for _, art := range artifacts {
sum, err := art.Checksum("sha256")
if err != nil {
return result, err
}
if cfg.URLTemplate == "" {
url, err := cl.ReleaseURLTemplate(ctx)
if err != nil {
return result, err
}
cfg.URLTemplate = url
}
url, err := tmpl.New(ctx).WithArtifact(art, map[string]string{}).Apply(cfg.URLTemplate)
if err != nil {
return result, err
}
releasePackage := releasePackage{
DownloadURL: url,
SHA256: sum,
Arch: toPkgBuildArch(art.Goarch + art.Goarm),
Format: art.ExtraOr(artifact.ExtraFormat, "").(string),
}
result.ReleasePackages = append(result.ReleasePackages, releasePackage)
result.Arches = append(result.Arches, releasePackage.Arch)
}
sort.Strings(result.Arches)
sort.Slice(result.ReleasePackages, func(i, j int) bool {
return result.ReleasePackages[i].Arch < result.ReleasePackages[j].Arch
})
return result, nil
}
// Publish the PKGBUILD and .SRCINFO files to the AUR repository.
func (Pipe) Publish(ctx *context.Context) error {
skips := pipe.SkipMemento{}
for _, pkgs := range ctx.Artifacts.Filter(
artifact.Or(
artifact.ByType(artifact.PkgBuild),
artifact.ByType(artifact.SrcInfo),
),
).GroupByID() {
err := doPublish(ctx, pkgs)
if err != nil && pipe.IsSkip(err) {
skips.Remember(err)
continue
}
if err != nil {
return err
}
}
return skips.Evaluate()
}
func doPublish(ctx *context.Context, pkgs []*artifact.Artifact) error {
cfg := pkgs[0].Extra[aurExtra].(config.AUR)
if strings.TrimSpace(cfg.SkipUpload) == "true" {
return pipe.Skip("aur.skip_upload is set")
}
if strings.TrimSpace(cfg.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
return pipe.Skip("prerelease detected with 'auto' upload, skipping aur publish")
}
key, err := tmpl.New(ctx).Apply(cfg.PrivateKey)
if err != nil {
return err
}
key, err = keyPath(key)
if err != nil {
return err
}
url, err := tmpl.New(ctx).Apply(cfg.GitURL)
if err != nil {
return err
}
if url == "" {
return pipe.Skip("aur.git_url is empty")
}
sshcmd, err := tmpl.New(ctx).WithExtraFields(tmpl.Fields{
"KeyPath": key,
}).Apply(cfg.GitSSHCommand)
if err != nil {
return err
}
msg, err := tmpl.New(ctx).Apply(cfg.CommitMessageTemplate)
if err != nil {
return err
}
author, err := commitauthor.Get(ctx, cfg.CommitAuthor)
if err != nil {
return err
}
parent := filepath.Join(ctx.Config.Dist, "aur", "repos")
cwd := filepath.Join(parent, cfg.Name)
if err := os.MkdirAll(parent, 0o755); err != nil {
return err
}
env := []string{fmt.Sprintf("GIT_SSH_COMMAND=%s", sshcmd)}
if err := runGitCmds(ctx, parent, env, [][]string{
{"clone", url, cfg.Name},
}); err != nil {
return fmt.Errorf("failed to setup local AUR repo: %w", err)
}
if err := runGitCmds(ctx, cwd, env, [][]string{
// setup auth et al
{"config", "--local", "user.name", author.Name},
{"config", "--local", "user.email", author.Email},
{"config", "--local", "commit.gpgSign", "false"},
{"config", "--local", "init.defaultBranch", "master"},
}); err != nil {
return fmt.Errorf("failed to setup local AUR repo: %w", err)
}
for _, pkg := range pkgs {
bts, err := os.ReadFile(pkg.Path)
if err != nil {
return fmt.Errorf("failed to read %s: %w", pkg.Name, err)
}
if err := os.WriteFile(filepath.Join(cwd, pkg.Name), bts, 0o644); err != nil {
return fmt.Errorf("failed to write %s: %w", pkg.Name, err)
}
}
log.WithField("repo", url).WithField("name", cfg.Name).Info("pushing")
if err := runGitCmds(ctx, cwd, env, [][]string{
{"add", "-A", "."},
{"commit", "-m", msg},
{"push", "origin", "HEAD"},
}); err != nil {
return fmt.Errorf("failed to push %q (%q): %w", cfg.Name, url, err)
}
return nil
}
func keyPath(key string) (string, error) {
if key == "" {
return "", pipe.Skip("aur.private_key is empty")
}
path := key
if _, err := ssh.ParsePrivateKey([]byte(key)); err == nil {
// if it can be parsed as a valid private key, we write it to a
// temp file and use that path on GIT_SSH_COMMAND.
f, err := os.CreateTemp("", "id_*")
if err != nil {
return "", fmt.Errorf("failed to store private key: %w", err)
}
defer f.Close()
// the key needs to EOF at an empty line, seems like github actions
// is somehow removing them.
if !strings.HasSuffix(key, "\n") {
key += "\n"
}
if _, err := f.Write([]byte(key)); err != nil {
return "", fmt.Errorf("failed to store private key: %w", err)
}
if err := f.Close(); err != nil {
return "", fmt.Errorf("failed to store private key: %w", err)
}
path = f.Name()
}
if _, err := os.Stat(path); err != nil {
return "", fmt.Errorf("could not stat aur.private_key: %w", err)
}
// in any case, ensure the key has the correct permissions.
if err := os.Chmod(path, 0o600); err != nil {
return "", fmt.Errorf("failed to ensure aur.private_key permissions: %w", err)
}
return path, nil
}
func runGitCmds(ctx *context.Context, cwd string, env []string, cmds [][]string) error {
for _, cmd := range cmds {
args := append([]string{"-C", cwd}, cmd...)
if _, err := git.Clean(git.RunWithEnv(ctx, env, args...)); err != nil {
return fmt.Errorf("%q failed: %w", strings.Join(cmd, " "), err)
}
}
return nil
}