1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-05-21 22:53:07 +02:00

feat: support custom tokens in Homebrew & Scoop (#1650)

This commit is contained in:
Radek Simko 2020-07-06 21:12:41 +01:00 committed by GitHub
parent 0d4f605388
commit ab8bb7f2f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 258 additions and 75 deletions

View File

@ -18,11 +18,23 @@ type Info struct {
URL string URL string
} }
type Repo struct {
Owner string
Name string
}
func (r Repo) String() string {
if r.Owner == "" && r.Name == "" {
return ""
}
return r.Owner + "/" + r.Name
}
// Client interface. // Client interface.
type Client interface { type Client interface {
CreateRelease(ctx *context.Context, body string) (releaseID string, err error) CreateRelease(ctx *context.Context, body string) (releaseID string, err error)
ReleaseURLTemplate(ctx *context.Context) (string, error) ReleaseURLTemplate(ctx *context.Context) (string, error)
CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo config.Repo, content []byte, path, message string) (err error) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo Repo, content []byte, path, message string) (err error)
Upload(ctx *context.Context, releaseID string, artifact *artifact.Artifact, file *os.File) (err error) Upload(ctx *context.Context, releaseID string, artifact *artifact.Artifact, file *os.File) (err error)
} }
@ -30,13 +42,26 @@ type Client interface {
func New(ctx *context.Context) (Client, error) { func New(ctx *context.Context) (Client, error) {
log.WithField("type", ctx.TokenType).Info("token type") log.WithField("type", ctx.TokenType).Info("token type")
if ctx.TokenType == context.TokenTypeGitHub { if ctx.TokenType == context.TokenTypeGitHub {
return NewGitHub(ctx) return NewGitHub(ctx, ctx.Token)
} }
if ctx.TokenType == context.TokenTypeGitLab { if ctx.TokenType == context.TokenTypeGitLab {
return NewGitLab(ctx) return NewGitLab(ctx, ctx.Token)
} }
if ctx.TokenType == context.TokenTypeGitea { if ctx.TokenType == context.TokenTypeGitea {
return NewGitea(ctx) return NewGitea(ctx, ctx.Token)
}
return nil, nil
}
func NewWithToken(ctx *context.Context, token string) (Client, error) {
if ctx.TokenType == context.TokenTypeGitHub {
return NewGitHub(ctx, token)
}
if ctx.TokenType == context.TokenTypeGitLab {
return NewGitLab(ctx, token)
}
if ctx.TokenType == context.TokenTypeGitea {
return NewGitea(ctx, token)
} }
return nil, nil return nil, nil
} }

12
internal/client/config.go Normal file
View File

@ -0,0 +1,12 @@
package client
import (
"github.com/goreleaser/goreleaser/pkg/config"
)
func RepoFromRef(ref config.RepoRef) Repo {
return Repo{
Owner: ref.Owner,
Name: ref.Name,
}
}

View File

@ -34,12 +34,12 @@ func getInstanceURL(apiURL string) (string, error) {
} }
// NewGitea returns a gitea client implementation. // NewGitea returns a gitea client implementation.
func NewGitea(ctx *context.Context) (Client, error) { func NewGitea(ctx *context.Context, token string) (Client, error) {
instanceURL, err := getInstanceURL(ctx.Config.GiteaURLs.API) instanceURL, err := getInstanceURL(ctx.Config.GiteaURLs.API)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := gitea.NewClient(instanceURL, ctx.Token) client := gitea.NewClient(instanceURL, token)
transport := &http.Transport{ transport := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
// nolint: gosec // nolint: gosec
@ -56,7 +56,7 @@ func NewGitea(ctx *context.Context) (Client, error) {
func (c *giteaClient) CreateFile( func (c *giteaClient) CreateFile(
ctx *context.Context, ctx *context.Context,
commitAuthor config.CommitAuthor, commitAuthor config.CommitAuthor,
repo config.Repo, repo Repo,
content []byte, content []byte,
path, path,
message string, message string,

View File

@ -245,7 +245,7 @@ func TestGiteaCreateFile(t *testing.T) {
client := giteaClient{} client := giteaClient{}
ctx := context.Context{} ctx := context.Context{}
author := config.CommitAuthor{} author := config.CommitAuthor{}
repo := config.Repo{} repo := Repo{}
content := []byte{} content := []byte{}
path := "" path := ""
message := "" message := ""

View File

@ -25,9 +25,9 @@ type githubClient struct {
} }
// NewGitHub returns a github client implementation. // NewGitHub returns a github client implementation.
func NewGitHub(ctx *context.Context) (Client, error) { func NewGitHub(ctx *context.Context, token string) (Client, error) {
ts := oauth2.StaticTokenSource( ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: ctx.Token}, &oauth2.Token{AccessToken: token},
) )
httpClient := oauth2.NewClient(ctx, ts) httpClient := oauth2.NewClient(ctx, ts)
base := httpClient.Transport.(*oauth2.Transport).Base base := httpClient.Transport.(*oauth2.Transport).Base
@ -59,7 +59,7 @@ func NewGitHub(ctx *context.Context) (Client, error) {
func (c *githubClient) CreateFile( func (c *githubClient) CreateFile(
ctx *context.Context, ctx *context.Context,
commitAuthor config.CommitAuthor, commitAuthor config.CommitAuthor,
repo config.Repo, repo Repo,
content []byte, content []byte,
path, path,
message string, message string,

View File

@ -11,34 +11,37 @@ import (
func TestNewGitHubClient(t *testing.T) { func TestNewGitHubClient(t *testing.T) {
t.Run("good urls", func(t *testing.T) { t.Run("good urls", func(t *testing.T) {
_, err := NewGitHub(context.New(config.Project{ ctx := context.New(config.Project{
GitHubURLs: config.GitHubURLs{ GitHubURLs: config.GitHubURLs{
API: "https://github.mycompany.com/api", API: "https://github.mycompany.com/api",
Upload: "https://github.mycompany.com/upload", Upload: "https://github.mycompany.com/upload",
}, },
})) })
_, err := NewGitHub(ctx, ctx.Token)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("bad api url", func(t *testing.T) { t.Run("bad api url", func(t *testing.T) {
_, err := NewGitHub(context.New(config.Project{ ctx := context.New(config.Project{
GitHubURLs: config.GitHubURLs{ GitHubURLs: config.GitHubURLs{
API: "://github.mycompany.com/api", API: "://github.mycompany.com/api",
Upload: "https://github.mycompany.com/upload", Upload: "https://github.mycompany.com/upload",
}, },
})) })
_, err := NewGitHub(ctx, ctx.Token)
require.EqualError(t, err, `parse "://github.mycompany.com/api": missing protocol scheme`) require.EqualError(t, err, `parse "://github.mycompany.com/api": missing protocol scheme`)
}) })
t.Run("bad upload url", func(t *testing.T) { t.Run("bad upload url", func(t *testing.T) {
_, err := NewGitHub(context.New(config.Project{ ctx := context.New(config.Project{
GitHubURLs: config.GitHubURLs{ GitHubURLs: config.GitHubURLs{
API: "https://github.mycompany.com/api", API: "https://github.mycompany.com/api",
Upload: "not a url:4994", Upload: "not a url:4994",
}, },
})) })
_, err := NewGitHub(ctx, ctx.Token)
require.EqualError(t, err, `parse "not a url:4994": first path segment in URL cannot contain colon`) require.EqualError(t, err, `parse "not a url:4994": first path segment in URL cannot contain colon`)
}) })
@ -46,7 +49,7 @@ func TestNewGitHubClient(t *testing.T) {
func TestGitHubUploadReleaseIDNotInt(t *testing.T) { func TestGitHubUploadReleaseIDNotInt(t *testing.T) {
var ctx = context.New(config.Project{}) var ctx = context.New(config.Project{})
client, err := NewGitHub(ctx) client, err := NewGitHub(ctx, ctx.Token)
require.NoError(t, err) require.NoError(t, err)
require.EqualError( require.EqualError(
@ -69,7 +72,7 @@ func TestGitHubReleaseURLTemplate(t *testing.T) {
}, },
}, },
}) })
client, err := NewGitHub(ctx) client, err := NewGitHub(ctx, ctx.Token)
require.NoError(t, err) require.NoError(t, err)
urlTpl, err := client.ReleaseURLTemplate(ctx) urlTpl, err := client.ReleaseURLTemplate(ctx)
@ -85,7 +88,7 @@ func TestGitHubCreateReleaseWrongNameTemplate(t *testing.T) {
NameTemplate: "{{.dddddddddd", NameTemplate: "{{.dddddddddd",
}, },
}) })
client, err := NewGitHub(ctx) client, err := NewGitHub(ctx, ctx.Token)
require.NoError(t, err) require.NoError(t, err)
str, err := client.CreateRelease(ctx, "") str, err := client.CreateRelease(ctx, "")

View File

@ -26,8 +26,7 @@ type gitlabClient struct {
} }
// NewGitLab returns a gitlab client implementation. // NewGitLab returns a gitlab client implementation.
func NewGitLab(ctx *context.Context) (Client, error) { func NewGitLab(ctx *context.Context, token string) (Client, error) {
token := ctx.Token
transport := &http.Transport{ transport := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
// nolint: gosec // nolint: gosec
@ -54,7 +53,7 @@ func NewGitLab(ctx *context.Context) (Client, error) {
func (c *gitlabClient) CreateFile( func (c *gitlabClient) CreateFile(
ctx *context.Context, ctx *context.Context,
commitAuthor config.CommitAuthor, commitAuthor config.CommitAuthor,
repo config.Repo, repo Repo,
content []byte, // the content of the formula.rb content []byte, // the content of the formula.rb
path, // the path to the formula.rb path, // the path to the formula.rb
message string, // the commit msg message string, // the commit msg

View File

@ -47,7 +47,7 @@ func TestGitLabReleaseURLTemplate(t *testing.T) {
}, },
}, },
}) })
client, err := NewGitLab(ctx) client, err := NewGitLab(ctx, ctx.Token)
assert.NoError(t, err) assert.NoError(t, err)
urlTpl, err := client.ReleaseURLTemplate(ctx) urlTpl, err := client.ReleaseURLTemplate(ctx)

View File

@ -88,11 +88,13 @@ func (Pipe) Default(ctx *context.Context) error {
} }
if brew.GitHub.String() != "" { if brew.GitHub.String() != "" {
deprecate.Notice(ctx, "brews.github") deprecate.Notice(ctx, "brews.github")
brew.Tap = brew.GitHub brew.Tap.Owner = brew.GitHub.Owner
brew.Tap.Name = brew.GitHub.Name
} }
if brew.GitLab.String() != "" { if brew.GitLab.String() != "" {
deprecate.Notice(ctx, "brews.gitlab") deprecate.Notice(ctx, "brews.gitlab")
brew.Tap = brew.GitLab brew.Tap.Owner = brew.GitLab.Owner
brew.Tap.Name = brew.GitLab.Name
} }
if brew.CommitAuthor.Name == "" { if brew.CommitAuthor.Name == "" {
brew.CommitAuthor.Name = "goreleaserbot" brew.CommitAuthor.Name = "goreleaserbot"
@ -129,11 +131,24 @@ func contains(ss []string, s string) bool {
return false return false
} }
func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) error { func doRun(ctx *context.Context, brew config.Homebrew, cl client.Client) error {
if brew.Tap.Name == "" { if brew.Tap.Name == "" {
return pipe.Skip("brew section is not configured") return pipe.Skip("brew section is not configured")
} }
if brew.Tap.Token != "" {
token, err := tmpl.New(ctx).ApplySingleEnvOnly(brew.Tap.Token)
if err != nil {
return err
}
log.Debug("using custom token to publish homebrew formula")
c, err := client.NewWithToken(ctx, token)
if err != nil {
return err
}
cl = c
}
// TODO: properly cover this with tests // TODO: properly cover this with tests
var filters = []artifact.Filter{ var filters = []artifact.Filter{
artifact.Or( artifact.Or(
@ -160,7 +175,7 @@ func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) err
return ErrNoArchivesFound return ErrNoArchivesFound
} }
content, err := buildFormula(ctx, brew, client, archives) content, err := buildFormula(ctx, brew, cl, archives)
if err != nil { if err != nil {
return err return err
} }
@ -169,7 +184,7 @@ func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) err
var path = filepath.Join(ctx.Config.Dist, filename) var path = filepath.Join(ctx.Config.Dist, filename)
log.WithField("formula", path).Info("writing") log.WithField("formula", path).Info("writing")
if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil { //nolint: gosec if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil { //nolint: gosec
return errors.Wrap(err, "failed to write brew tap") return errors.Wrap(err, "failed to write brew formula")
} }
if strings.TrimSpace(brew.SkipUpload) == "true" { if strings.TrimSpace(brew.SkipUpload) == "true" {
@ -182,7 +197,7 @@ func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) err
return pipe.Skip("prerelease detected with 'auto' upload, skipping homebrew publish") return pipe.Skip("prerelease detected with 'auto' upload, skipping homebrew publish")
} }
repo := brew.Tap repo := client.RepoFromRef(brew.Tap)
var gpath = buildFormulaPath(brew.Folder, filename) var gpath = buildFormulaPath(brew.Folder, filename)
log.WithField("formula", gpath). log.WithField("formula", gpath).
@ -190,7 +205,7 @@ func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) err
Info("pushing") Info("pushing")
var msg = fmt.Sprintf("Brew formula update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag) var msg = fmt.Sprintf("Brew formula update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag)
return client.CreateFile(ctx, brew.CommitAuthor, repo, []byte(content), gpath, msg) return cl.CreateFile(ctx, brew.CommitAuthor, repo, []byte(content), gpath, msg)
} }
func buildFormulaPath(folder, filename string) string { func buildFormulaPath(folder, filename string) string {

View File

@ -269,7 +269,7 @@ func TestRunPipeForMultipleArmVersions(t *testing.T) {
Dependencies: []config.HomebrewDependency{{Name: "zsh"}, {Name: "bash", Type: "recommended"}}, Dependencies: []config.HomebrewDependency{{Name: "zsh"}, {Name: "bash", Type: "recommended"}},
Conflicts: []string{"gtk+", "qt"}, Conflicts: []string{"gtk+", "qt"},
Install: `bin.install "{{ .ProjectName }}"`, Install: `bin.install "{{ .ProjectName }}"`,
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -365,7 +365,7 @@ func TestRunPipeNoDarwin64Build(t *testing.T) {
Config: config.Project{ Config: config.Project{
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -383,7 +383,7 @@ func TestRunPipeMultipleArchivesSameOsBuild(t *testing.T) {
config.Project{ config.Project{
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -537,7 +537,7 @@ func TestRunPipeBinaryRelease(t *testing.T) {
config.Project{ config.Project{
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -566,7 +566,7 @@ func TestRunPipeNoUpload(t *testing.T) {
Release: config.Release{}, Release: config.Release{},
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -618,7 +618,7 @@ func TestRunEmptyTokenType(t *testing.T) {
Release: config.Release{}, Release: config.Release{},
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -653,7 +653,7 @@ func TestRunTokenTypeNotImplementedForBrew(t *testing.T) {
Release: config.Release{}, Release: config.Release{},
Brews: []config.Homebrew{ Brews: []config.Homebrew{
{ {
Tap: config.Repo{ Tap: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -743,7 +743,7 @@ func (dc *DummyClient) ReleaseURLTemplate(ctx *context.Context) (string, error)
return "https://dummyhost/download/{{ .Tag }}/{{ .ArtifactName }}", nil return "https://dummyhost/download/{{ .Tag }}/{{ .ArtifactName }}", nil
} }
func (dc *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo config.Repo, content []byte, path, msg string) (err error) { func (dc *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo client.Repo, content []byte, path, msg string) (err error) {
dc.CreatedFile = true dc.CreatedFile = true
dc.Content = string(content) dc.Content = string(content)
return return

View File

@ -550,7 +550,7 @@ func (c *DummyClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
return "", nil return "", nil
} }
func (c *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo config.Repo, content []byte, path, msg string) (err error) { func (c *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo client.Repo, content []byte, path, msg string) (err error) {
return return
} }
@ -570,7 +570,7 @@ func (c *DummyClient) Upload(ctx *context.Context, releaseID string, artifact *a
} }
if c.FailFirstUpload { if c.FailFirstUpload {
c.FailFirstUpload = false c.FailFirstUpload = false
return client.RetriableError{errors.New("upload failed, should retry")} return client.RetriableError{Err: errors.New("upload failed, should retry")}
} }
c.UploadedFile = true c.UploadedFile = true
c.UploadedFileNames = append(c.UploadedFileNames, artifact.Name) c.UploadedFileNames = append(c.UploadedFileNames, artifact.Name)

View File

@ -34,6 +34,7 @@ func (Pipe) Publish(ctx *context.Context) error {
if ctx.SkipPublish { if ctx.SkipPublish {
return pipe.ErrSkipPublishEnabled return pipe.ErrSkipPublishEnabled
} }
client, err := client.New(ctx) client, err := client.New(ctx)
if err != nil { if err != nil {
return err return err
@ -60,15 +61,24 @@ func (Pipe) Default(ctx *context.Context) error {
return nil return nil
} }
func doRun(ctx *context.Context, client client.Client) error { func doRun(ctx *context.Context, cl client.Client) error {
if ctx.Config.Scoop.Bucket.Name == "" { scoop := ctx.Config.Scoop
if scoop.Bucket.Name == "" {
return pipe.Skip("scoop section is not configured") return pipe.Skip("scoop section is not configured")
} }
// TODO mavogel: in another PR if scoop.Bucket.Token != "" {
// check if release pipe is not configured! token, err := tmpl.New(ctx).ApplySingleEnvOnly(scoop.Bucket.Token)
// if ctx.Config.Release.Disable { if err != nil {
// } return err
}
log.Debug("using custom token to publish scoop manifest")
c, err := client.NewWithToken(ctx, token)
if err != nil {
return err
}
cl = c
}
// TODO: multiple archives // TODO: multiple archives
if ctx.Config.Archives[0].Format == "binary" { if ctx.Config.Archives[0].Format == "binary" {
@ -85,9 +95,9 @@ func doRun(ctx *context.Context, client client.Client) error {
return ErrNoWindows return ErrNoWindows
} }
var path = ctx.Config.Scoop.Name + ".json" var path = scoop.Name + ".json"
data, err := dataFor(ctx, client, archives) data, err := dataFor(ctx, cl, archives)
if err != nil { if err != nil {
return err return err
} }
@ -99,10 +109,10 @@ func doRun(ctx *context.Context, client client.Client) error {
if ctx.SkipPublish { if ctx.SkipPublish {
return pipe.ErrSkipPublishEnabled return pipe.ErrSkipPublishEnabled
} }
if strings.TrimSpace(ctx.Config.Scoop.SkipUpload) == "true" { if strings.TrimSpace(scoop.SkipUpload) == "true" {
return pipe.Skip("scoop.skip_upload is true") return pipe.Skip("scoop.skip_upload is true")
} }
if strings.TrimSpace(ctx.Config.Scoop.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" { if strings.TrimSpace(scoop.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
return pipe.Skip("release is prerelease") return pipe.Skip("release is prerelease")
} }
if ctx.Config.Release.Draft { if ctx.Config.Release.Draft {
@ -113,15 +123,16 @@ func doRun(ctx *context.Context, client client.Client) error {
} }
commitMessage, err := tmpl.New(ctx). commitMessage, err := tmpl.New(ctx).
Apply(ctx.Config.Scoop.CommitMessageTemplate) Apply(scoop.CommitMessageTemplate)
if err != nil { if err != nil {
return err return err
} }
return client.CreateFile( repo := client.RepoFromRef(scoop.Bucket)
return cl.CreateFile(
ctx, ctx,
ctx.Config.Scoop.CommitAuthor, scoop.CommitAuthor,
ctx.Config.Scoop.Bucket, repo,
content.Bytes(), content.Bytes(),
path, path,
commitMessage, commitMessage,

View File

@ -113,7 +113,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -156,7 +156,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -216,7 +216,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -259,7 +259,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -319,7 +319,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -377,7 +377,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -420,7 +420,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -498,7 +498,7 @@ func Test_doRun(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -539,7 +539,7 @@ func Test_doRun(t *testing.T) {
Draft: true, Draft: true,
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -589,7 +589,7 @@ func Test_doRun(t *testing.T) {
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
SkipUpload: "auto", SkipUpload: "auto",
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -633,7 +633,7 @@ func Test_doRun(t *testing.T) {
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
SkipUpload: "true", SkipUpload: "true",
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -673,7 +673,7 @@ func Test_doRun(t *testing.T) {
Disable: true, Disable: true,
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -713,7 +713,7 @@ func Test_doRun(t *testing.T) {
Draft: true, Draft: true,
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -780,7 +780,7 @@ func Test_buildManifest(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -820,7 +820,7 @@ func Test_buildManifest(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -862,7 +862,7 @@ func Test_buildManifest(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -968,7 +968,7 @@ func TestWrapInDirectory(t *testing.T) {
}, },
}, },
Scoop: config.Scoop{ Scoop: config.Scoop{
Bucket: config.Repo{ Bucket: config.RepoRef{
Owner: "test", Owner: "test",
Name: "test", Name: "test",
}, },
@ -1034,7 +1034,7 @@ func (dc *DummyClient) ReleaseURLTemplate(ctx *context.Context) (string, error)
return "", nil return "", nil
} }
func (dc *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo config.Repo, content []byte, path, msg string) (err error) { func (dc *DummyClient) CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo client.Repo, content []byte, path, msg string) (err error) {
dc.CreatedFile = true dc.CreatedFile = true
dc.Content = string(content) dc.Content = string(content)
return return

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@ -176,6 +177,41 @@ func (t *Template) Apply(s string) (string, error) {
return out.String(), err return out.String(), err
} }
type ExpectedSingleEnvErr struct{}
func (e ExpectedSingleEnvErr) Error() string {
return "expected {{ .Env.VAR_NAME }} only (no plain-text or other interpolation)"
}
// ApplySingleEnvOnly enforces template to only contain a single environment variable
// and nothing else.
func (t *Template) ApplySingleEnvOnly(s string) (string, error) {
s = strings.TrimSpace(s)
if len(s) == 0 {
return "", nil
}
// text/template/parse (lexer) could be used here too,
// but regexp reduces the complexity and should be sufficient,
// given the context is mostly discouraging users from bad practice
// of hard-coded credentials, rather than catch all possible cases
envOnlyRe := regexp.MustCompile(`^{{\s*\.Env\.[^.\s}]+\s*}}$`)
if !envOnlyRe.Match([]byte(s)) {
return "", ExpectedSingleEnvErr{}
}
var out bytes.Buffer
tmpl, err := template.New("tmpl").
Option("missingkey=error").
Parse(s)
if err != nil {
return "", err
}
err = tmpl.Execute(&out, t.fields)
return out.String(), err
}
func replace(replacements map[string]string, original string) string { func replace(replacements map[string]string, original string) string {
result := replacements[original] result := replacements[original]
if result == "" { if result == "" {

View File

@ -4,6 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"text/template"
"github.com/goreleaser/goreleaser/internal/artifact" "github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/config"
@ -212,6 +213,72 @@ func TestFuncMap(t *testing.T) {
} }
} }
func TestApplySingleEnvOnly(t *testing.T) {
ctx := context.New(config.Project{
Env: []string{
"FOO=value",
"BAR=another",
},
})
testCases := []struct {
name string
tpl string
expectedErr error
}{
{
"empty tpl",
"",
nil,
},
{
"whitespaces",
" ",
nil,
},
{
"plain-text only",
"raw-token",
ExpectedSingleEnvErr{},
},
{
"variable with spaces",
"{{ .Env.FOO }}",
nil,
},
{
"variable without spaces",
"{{.Env.FOO}}",
nil,
},
{
"variable with outer spaces",
" {{ .Env.FOO }} ",
nil,
},
{
"unknown variable",
"{{ .Env.UNKNOWN }}",
template.ExecError{},
},
{
"other interpolation",
"{{ .ProjectName }}",
ExpectedSingleEnvErr{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := New(ctx).ApplySingleEnvOnly(tc.tpl)
if tc.expectedErr != nil {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestInvalidTemplate(t *testing.T) { func TestInvalidTemplate(t *testing.T) {
ctx := context.New(config.Project{}) ctx := context.New(config.Project{})
ctx.Git.CurrentTag = "v1.1.1" ctx.Git.CurrentTag = "v1.1.1"

View File

@ -34,11 +34,22 @@ type GiteaURLs struct {
} }
// Repo represents any kind of repo (github, gitlab, etc). // Repo represents any kind of repo (github, gitlab, etc).
// to upload releases into.
type Repo struct { type Repo struct {
Owner string `yaml:",omitempty"` Owner string `yaml:",omitempty"`
Name string `yaml:",omitempty"` Name string `yaml:",omitempty"`
} }
// RepoRef represents any kind of repo which may differ
// from the one we are building from and may therefore
// also require separate authentication
// e.g. Homebrew Tap, Scoop bucket.
type RepoRef struct {
Owner string `yaml:",omitempty"`
Name string `yaml:",omitempty"`
Token string `yaml:",omitempty"`
}
// HomebrewDependency represents Homebrew dependency. // HomebrewDependency represents Homebrew dependency.
type HomebrewDependency struct { type HomebrewDependency struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty"`
@ -78,7 +89,7 @@ func (r Repo) String() string {
// Homebrew contains the brew section. // Homebrew contains the brew section.
type Homebrew struct { type Homebrew struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty"`
Tap Repo `yaml:",omitempty"` Tap RepoRef `yaml:",omitempty"`
CommitAuthor CommitAuthor `yaml:"commit_author,omitempty"` CommitAuthor CommitAuthor `yaml:"commit_author,omitempty"`
Folder string `yaml:",omitempty"` Folder string `yaml:",omitempty"`
Caveats string `yaml:",omitempty"` Caveats string `yaml:",omitempty"`
@ -105,7 +116,7 @@ type Homebrew struct {
// Scoop contains the scoop.sh section. // Scoop contains the scoop.sh section.
type Scoop struct { type Scoop struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty"`
Bucket Repo `yaml:",omitempty"` Bucket RepoRef `yaml:",omitempty"`
CommitAuthor CommitAuthor `yaml:"commit_author,omitempty"` CommitAuthor CommitAuthor `yaml:"commit_author,omitempty"`
CommitMessageTemplate string `yaml:"commit_msg_template,omitempty"` CommitMessageTemplate string `yaml:"commit_msg_template,omitempty"`
Homepage string `yaml:",omitempty"` Homepage string `yaml:",omitempty"`

View File

@ -44,6 +44,8 @@ brews:
tap: tap:
owner: repo-owner owner: repo-owner
name: homebrew-tap name: homebrew-tap
# Optionally a token can be provided, if it differs from the token provided to GoReleaser
token: {{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}
# Template for the url which is determined by the given Token (github or gitlab) # Template for the url which is determined by the given Token (github or gitlab)
# Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}" # Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"

View File

@ -21,6 +21,8 @@ scoop:
bucket: bucket:
owner: user owner: user
name: scoop-bucket name: scoop-bucket
# Optionally a token can be provided, if it differs from the token provided to GoReleaser
token: {{ .Env.SCOOP_BUCKET_GITHUB_TOKEN }}
# Git author used to commit to the repository. # Git author used to commit to the repository.
# Defaults are shown. # Defaults are shown.