1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-22 04:08:49 +02:00
Carlos Alexandro Becker b28a28efb6
feat: allow to template commit author (#2826)
* feat: allow to template commit author

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

* chore: fmt

Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
2022-01-11 09:15:28 -03:00

359 lines
8.7 KiB
Go

// Package krew implements Piper and Publisher, providing krew plugin manifest
// creation and upload to a repository (aka krew plugin index).
//
// nolint:tagliatelle
package krew
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"
"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/pipe"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"gopkg.in/yaml.v2"
)
const (
krewConfigExtra = "KrewConfig"
manifestsFolder = "plugins"
kind = "Plugin"
apiVersion = "krew.googlecontainertools.github.com/v1alpha2"
)
var ErrNoArchivesFound = errors.New("no archives found")
// Pipe for krew manifest deployment.
type Pipe struct{}
func (Pipe) String() string { return "krew plugin manifest" }
func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.Krews) == 0 }
func (Pipe) Default(ctx *context.Context) error {
for i := range ctx.Config.Krews {
krew := &ctx.Config.Krews[i]
krew.CommitAuthor = commitauthor.Default(krew.CommitAuthor)
if krew.CommitMessageTemplate == "" {
krew.CommitMessageTemplate = "Krew manifest update for {{ .ProjectName }} version {{ .Tag }}"
}
if krew.Name == "" {
krew.Name = ctx.Config.ProjectName
}
}
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 _, krew := range ctx.Config.Krews {
err := doRun(ctx, krew, cli)
if err != nil {
return err
}
}
return nil
}
func doRun(ctx *context.Context, krew config.Krew, cl client.Client) error {
if krew.Name == "" {
return pipe.Skip("krew: manifest name is not set")
}
if krew.Description == "" {
return fmt.Errorf("krew: manifest description is not set")
}
if krew.ShortDescription == "" {
return fmt.Errorf("krew: manifest short description is not set")
}
filters := []artifact.Filter{
artifact.Or(
artifact.ByGoos("darwin"),
artifact.ByGoos("linux"),
artifact.ByGoos("windows"),
),
artifact.Or(
artifact.ByGoarch("amd64"),
artifact.ByGoarch("arm64"),
artifact.ByGoarch("all"),
artifact.And(
artifact.ByGoarch("arm"),
artifact.ByGoarm(krew.Goarm),
),
),
artifact.ByType(artifact.UploadableArchive),
artifact.OnlyReplacingUnibins,
}
if len(krew.IDs) > 0 {
filters = append(filters, artifact.ByIDs(krew.IDs...))
}
archives := ctx.Artifacts.Filter(artifact.And(filters...)).List()
if len(archives) == 0 {
return ErrNoArchivesFound
}
krew, err := templateFields(ctx, krew)
if err != nil {
return err
}
content, err := buildmanifest(ctx, krew, cl, archives)
if err != nil {
return err
}
filename := krew.Name + ".yaml"
yamlPath := filepath.Join(ctx.Config.Dist, filename)
log.WithField("manifest", yamlPath).Info("writing")
if err := os.WriteFile(yamlPath, []byte(content), 0o644); err != nil { //nolint: gosec
return fmt.Errorf("failed to write krew manifest: %w", err)
}
ctx.Artifacts.Add(&artifact.Artifact{
Name: filename,
Path: yamlPath,
Type: artifact.KrewPluginManifest,
Extra: map[string]interface{}{
krewConfigExtra: krew,
},
})
return nil
}
func templateFields(ctx *context.Context, krew config.Krew) (config.Krew, error) {
t := tmpl.New(ctx)
var err error
krew.Name, err = t.Apply(krew.Name)
if err != nil {
return config.Krew{}, err
}
krew.Homepage, err = t.Apply(krew.Homepage)
if err != nil {
return config.Krew{}, err
}
krew.Description, err = t.Apply(krew.Description)
if err != nil {
return config.Krew{}, err
}
krew.Caveats, err = t.Apply(krew.Caveats)
if err != nil {
return config.Krew{}, err
}
krew.ShortDescription, err = t.Apply(krew.ShortDescription)
if err != nil {
return config.Krew{}, err
}
return krew, nil
}
func buildmanifest(ctx *context.Context, krew config.Krew, client client.Client, artifacts []*artifact.Artifact) (string, error) {
data, err := manifestFor(ctx, krew, client, artifacts)
if err != nil {
return "", err
}
return doBuildManifest(data)
}
func doBuildManifest(data Manifest) (string, error) {
out, err := yaml.Marshal(data)
if err != nil {
return "", fmt.Errorf("krew: failed to marshal yaml: %w", err)
}
return string(out), nil
}
func manifestFor(ctx *context.Context, cfg config.Krew, cl client.Client, artifacts []*artifact.Artifact) (Manifest, error) {
result := Manifest{
APIVersion: apiVersion,
Kind: kind,
Metadata: Metadata{
Name: cfg.Name,
},
Spec: Spec{
Homepage: cfg.Homepage,
Version: "v" + ctx.Version,
ShortDescription: cfg.ShortDescription,
Description: cfg.Description,
Caveats: cfg.Caveats,
},
}
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
}
goarch := []string{art.Goarch}
if art.Goarch == "all" {
goarch = []string{"amd64", "arm64"}
}
for _, arch := range goarch {
bins := art.ExtraOr(artifact.ExtraBinaries, []string{}).([]string)
if len(bins) != 1 {
return result, fmt.Errorf("krew: only one binary per archive allowed, got %d on %q", len(bins), art.Name)
}
result.Spec.Platforms = append(result.Spec.Platforms, Platform{
Bin: bins[0],
URI: url,
Sha256: sum,
Selector: Selector{
MatchLabels: MatchLabels{
Os: art.Goos,
Arch: arch,
},
},
})
}
}
sort.Slice(result.Spec.Platforms, func(i, j int) bool {
return result.Spec.Platforms[i].URI > result.Spec.Platforms[j].URI
})
return result, nil
}
// Publish krew manifest.
func (Pipe) Publish(ctx *context.Context) error {
cli, err := client.New(ctx)
if err != nil {
return err
}
return publishAll(ctx, cli)
}
func publishAll(ctx *context.Context, cli client.Client) error {
skips := pipe.SkipMemento{}
for _, manifest := range ctx.Artifacts.Filter(artifact.ByType(artifact.KrewPluginManifest)).List() {
err := doPublish(ctx, manifest, cli)
if err != nil && pipe.IsSkip(err) {
skips.Remember(err)
continue
}
if err != nil {
return err
}
}
return skips.Evaluate()
}
func doPublish(ctx *context.Context, manifest *artifact.Artifact, cl client.Client) error {
cfg := manifest.Extra[krewConfigExtra].(config.Krew)
var err error
cl, err = client.NewIfToken(ctx, cl, cfg.Index.Token)
if err != nil {
return err
}
if strings.TrimSpace(cfg.SkipUpload) == "true" {
return pipe.Skip("krews.skip_upload is set")
}
if strings.TrimSpace(cfg.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
return pipe.Skip("prerelease detected with 'auto' upload, skipping krew publish")
}
repo := client.RepoFromRef(cfg.Index)
gpath := buildManifestPath(manifestsFolder, manifest.Name)
log.WithField("manifest", gpath).
WithField("repo", repo.String()).
Info("pushing")
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
}
content, err := os.ReadFile(manifest.Path)
if err != nil {
return err
}
return cl.CreateFile(ctx, author, repo, content, gpath, msg)
}
func buildManifestPath(folder, filename string) string {
return path.Join(folder, filename)
}
type Manifest struct {
APIVersion string `yaml:"apiVersion,omitempty"`
Kind string `yaml:"kind,omitempty"`
Metadata Metadata `yaml:"metadata,omitempty"`
Spec Spec `yaml:"spec,omitempty"`
}
type Metadata struct {
Name string `yaml:"name,omitempty"`
}
type MatchLabels struct {
Os string `yaml:"os,omitempty"`
Arch string `yaml:"arch,omitempty"`
}
type Selector struct {
MatchLabels MatchLabels `yaml:"matchLabels,omitempty"`
}
type Platform struct {
Bin string `yaml:"bin,omitempty"`
URI string `yaml:"uri,omitempty"`
Sha256 string `yaml:"sha256,omitempty"`
Selector Selector `yaml:"selector,omitempty"`
}
type Spec struct {
Version string `yaml:"version,omitempty"`
Platforms []Platform `yaml:"platforms,omitempty"`
ShortDescription string `yaml:"shortDescription,omitempty"`
Homepage string `yaml:"homepage,omitempty"`
Caveats string `yaml:"caveats,omitempty"`
Description string `yaml:"description,omitempty"`
}