Skip to content

Commit

Permalink
Add option to push to gh-pages
Browse files Browse the repository at this point in the history
Signed-off-by: Reinhard Nägele <unguiculus@gmail.com>
  • Loading branch information
unguiculus committed Aug 10, 2020
1 parent 41c62a4 commit cb0563b
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 18 deletions.
6 changes: 5 additions & 1 deletion cr/cmd/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cmd

import (
"github.com/helm/chart-releaser/pkg/config"
"github.com/helm/chart-releaser/pkg/git"
"github.com/helm/chart-releaser/pkg/github"
"github.com/helm/chart-releaser/pkg/releaser"
"github.com/spf13/cobra"
Expand All @@ -35,7 +36,7 @@ given GitHub repository's releases.
return err
}
ghc := github.NewClient(config.Owner, config.GitRepo, config.Token, config.GitBaseURL, config.GitUploadURL)
releaser := releaser.NewReleaser(config, ghc)
releaser := releaser.NewReleaser(config, ghc, &git.Git{})
_, err = releaser.UpdateIndexFile()
return err
},
Expand All @@ -56,4 +57,7 @@ func init() {
flags.StringP("token", "t", "", "GitHub Auth Token (only needed for private repos)")
flags.StringP("git-base-url", "b", "https://api.github.com/", "GitHub Base URL (only needed for private GitHub)")
flags.StringP("git-upload-url", "u", "https://uploads.github.com/", "GitHub Upload URL (only needed for private GitHub)")
flags.String("pages-branch", "gh-pages", "The GitHub pages branch")
flags.Bool("push", false, "Push index.yaml to the GitHub Pages branch (must not be set if --pr is set)")
flags.Bool("pr", false, "Create a pull request for index.yaml against the GitHub Pages branch (must not be set if --push is set)")
}
3 changes: 2 additions & 1 deletion cr/cmd/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cmd

import (
"github.com/helm/chart-releaser/pkg/config"
"github.com/helm/chart-releaser/pkg/git"
"github.com/helm/chart-releaser/pkg/github"
"github.com/helm/chart-releaser/pkg/releaser"
"github.com/spf13/cobra"
Expand All @@ -32,7 +33,7 @@ var uploadCmd = &cobra.Command{
return err
}
ghc := github.NewClient(config.Owner, config.GitRepo, config.Token, config.GitBaseURL, config.GitUploadURL)
releaser := releaser.NewReleaser(config, ghc)
releaser := releaser.NewReleaser(config, ghc, &git.Git{})
return releaser.CreateReleases()
},
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type Options struct {
GitBaseURL string `mapstructure:"git-base-url"`
GitUploadURL string `mapstructure:"git-upload-url"`
Commit string `mapstructure:"commit"`
PagesBranch string `mapstructure:"pages-branch"`
Push bool `mapstructure:"push"`
PR bool `mapstructure:"pr"`
}

func LoadConfiguration(cfgFile string, cmd *cobra.Command, requiredFlags []string) (*Options, error) {
Expand Down Expand Up @@ -89,6 +92,10 @@ func LoadConfiguration(cfgFile string, cmd *cobra.Command, requiredFlags []strin
return nil, errors.Wrap(err, "Error unmarshaling configuration")
}

if opts.Push && opts.PR {
return nil, errors.New("specify either --push or --pr, but not both")
}

elem := reflect.ValueOf(opts).Elem()
for _, requiredFlag := range requiredFlags {
fieldName := kebabCaseToTitleCamelCase(requiredFlag)
Expand Down
62 changes: 62 additions & 0 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package git

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
)

type Git struct{}

// AddWorktree creates a new Git worktree with a detached HEAD for the given committish and returns its path.
func (g *Git) AddWorktree(workingDir string, committish string) (string, error) {
dir, err := ioutil.TempDir("", "chart-releaser-")
if err != nil {
return "", err
}
command := exec.Command("git", "worktree", "add", "--detach", dir, committish)

if err := runCommand(workingDir, command); err != nil {
return "", err
}
return dir, nil
}

// RemoveWorktree removes the Git worktree with the given path.
func (g *Git) RemoveWorktree(workingDir string, path string) error {
command := exec.Command("git", "worktree", "remove", path, "--force")
return runCommand(workingDir, command)
}

// Add runs 'git add' with the given args.
func (g *Git) Add(workingDir string, args ...string) error {
if len(args) == 0 {
return fmt.Errorf("no args specified")
}
addArgs := []string{"add"}
addArgs = append(addArgs, args...)
command := exec.Command("git", addArgs...)
return runCommand(workingDir, command)
}

// Commit runs 'git commit' with the given message. the commit is signed off.
func (g *Git) Commit(workingDir string, message string) error {
command := exec.Command("git", "commit", "--message", message, "--signoff")
return runCommand(workingDir, command)
}

// Push runs 'git push' with the given args.
func (g *Git) Push(workingDir string, args ...string) error {
pushArgs := []string{"push"}
pushArgs = append(pushArgs, args...)
command := exec.Command("git", pushArgs...)
return runCommand(workingDir, command)
}

func runCommand(workingDir string, command *exec.Cmd) error {
command.Dir = workingDir
command.Stdout = os.Stdout
command.Stderr = os.Stderr
return command.Run()
}
23 changes: 23 additions & 0 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@ func (c *Client) CreateRelease(ctx context.Context, input *Release) error {
return nil
}

// CreatePullRequest creates a pull request in the repository specified by repoURL.
// The return value is the pull request URL.
func (c *Client) CreatePullRequest(owner string, repo string, message string, head string, base string) (string, error) {
split := strings.SplitN(message, "\n", 2)
title := split[0]

pr := &github.NewPullRequest{
Title: &title,
Head: &head,
Base: &base,
}
if len(split) == 2 {
body := strings.TrimSpace(split[1])
pr.Body = &body
}

pullRequest, _, err := c.PullRequests.Create(context.Background(), owner, repo, pr)
if err != nil {
return "", err
}
return *pullRequest.HTMLURL, nil
}

// UploadAsset uploads specified assets to a given release object
func (c *Client) uploadReleaseAsset(ctx context.Context, releaseID int64, filename string) error {

Expand Down
122 changes: 106 additions & 16 deletions pkg/releaser/releaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart/loader"
Expand All @@ -41,13 +43,27 @@ import (
type GitHub interface {
CreateRelease(ctx context.Context, input *github.Release) error
GetRelease(ctx context.Context, tag string) (*github.Release, error)
CreatePullRequest(owner string, repo string, message string, head string, base string) (string, error)
}

type HttpClient interface {
Get(url string) (*http.Response, error)
}

type DefaultHttpClient struct {
type Git interface {
AddWorktree(workingDir string, committish string) (string, error)
RemoveWorktree(workingDir string, path string) error
Add(workingDir string, args ...string) error
Commit(workingDir string, message string) error
Push(workingDir string, args ...string) error
}

type DefaultHttpClient struct{}

var letters = []rune("abcdefghijklmnopqrstuvwxyz0123456789")

func init() {
rand.Seed(time.Now().UnixNano())
}

func (c *DefaultHttpClient) Get(url string) (resp *http.Response, err error) {
Expand All @@ -58,17 +74,19 @@ type Releaser struct {
config *config.Options
github GitHub
httpClient HttpClient
git Git
}

func NewReleaser(config *config.Options, github GitHub) *Releaser {
func NewReleaser(config *config.Options, github GitHub, git Git) *Releaser {
return &Releaser{
config: config,
github: github,
httpClient: &DefaultHttpClient{},
git: git,
}
}

// UpdateIndexFile index.yaml file for a give git repo
// UpdateIndexFile updates the index.yaml file for a given Git repo
func (r *Releaser) UpdateIndexFile() (bool, error) {
// if path doesn't end with index.yaml we can try and fix it
if filepath.Base(r.config.IndexPath) != "index.yaml" {
Expand Down Expand Up @@ -103,13 +121,13 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
return false, err
}

fmt.Printf("====> Using existing index at %s\n", r.config.IndexPath)
fmt.Printf("Using existing index at %s\n", r.config.IndexPath)
indexFile, err = repo.LoadIndexFile(r.config.IndexPath)
if err != nil {
return false, err
}
} else {
fmt.Printf("====> UpdateIndexFile new index at %s\n", r.config.IndexPath)
fmt.Printf("UpdateIndexFile new index at %s\n", r.config.IndexPath)
indexFile = repo.NewIndexFile()
}

Expand All @@ -133,7 +151,7 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
baseName := strings.TrimSuffix(name, filepath.Ext(name))
tagParts := r.splitPackageNameAndVersion(baseName)
packageName, packageVersion := tagParts[0], tagParts[1]
fmt.Printf("====> Found %s-%s.tgz\n", packageName, packageVersion)
fmt.Printf("Found %s-%s.tgz\n", packageName, packageVersion)
if _, err := indexFile.Get(packageName, packageVersion); err != nil {
if err := r.addToIndexFile(indexFile, downloadUrl.String()); err != nil {
return false, err
Expand All @@ -144,15 +162,62 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
}
}

if update {
fmt.Printf("--> Updating index %s\n", r.config.IndexPath)
indexFile.SortEntries()
return true, indexFile.WriteFile(r.config.IndexPath, 0644)
} else {
fmt.Printf("--> Index %s did not change\n", r.config.IndexPath)
if !update {
fmt.Printf("Index %s did not change\n", r.config.IndexPath)
return false, nil
}

fmt.Printf("Updating index %s\n", r.config.IndexPath)
indexFile.SortEntries()

if err := indexFile.WriteFile(r.config.IndexPath, 0644); err != nil {
return false, err
}

if !r.config.Push && !r.config.PR {
return true, nil
}

worktree, err := r.git.AddWorktree("", "origin/"+r.config.PagesBranch)
if err != nil {
return false, err
}
defer r.git.RemoveWorktree("", worktree)

indexYamlPath := filepath.Join(worktree, "index.yaml")
if err := copyFile(r.config.IndexPath, indexYamlPath); err != nil {
return false, err
}
if err := r.git.Add(worktree, indexYamlPath); err != nil {
return false, err
}
if err := r.git.Commit(worktree, "Update index.yaml"); err != nil {
return false, err
}

pushURL := fmt.Sprintf("https://x-access-token:%s@github.com/%s/%s", r.config.Token, r.config.Owner, r.config.GitRepo)

if r.config.Push {
fmt.Printf("Pushing to branch %q\n", r.config.PagesBranch)
if err := r.git.Push(worktree, pushURL, "HEAD:refs/heads/"+r.config.PagesBranch); err != nil {
return false, err
}
} else if r.config.PR {
branch := fmt.Sprintf("chart-releaser-%s", randomString(16))

fmt.Printf("Pushing to branch %q\n", branch)
if err := r.git.Push(worktree, pushURL, "HEAD:refs/heads/"+branch); err != nil {
return false, err
}
fmt.Printf("Creating pull request against branch %q\n", r.config.PagesBranch)
prURL, err := r.github.CreatePullRequest(r.config.Owner, r.config.GitRepo, "Update index.yaml", branch, r.config.PagesBranch)
if err != nil {
return false, err
}
fmt.Println("Pull request created:", prURL)
}

return false, nil
return true, nil
}

func (r *Releaser) splitPackageNameAndVersion(pkg string) []string {
Expand All @@ -164,13 +229,13 @@ func (r *Releaser) addToIndexFile(indexFile *repo.IndexFile, url string) error {
arch := filepath.Join(r.config.PackagePath, filepath.Base(url))

// extract chart metadata
fmt.Printf("====> Extracting chart metadata from %s\n", arch)
fmt.Printf("Extracting chart metadata from %s\n", arch)
c, err := loader.LoadFile(arch)
if err != nil {
return errors.Wrapf(err, "%s is not a helm chart package", arch)
}
// calculate hash
fmt.Printf("====> Calculating Hash for %s\n", arch)
fmt.Printf("Calculating Hash for %s\n", arch)
hash, err := provenance.DigestFile(arch)
if err != nil {
return err
Expand All @@ -187,7 +252,7 @@ func (r *Releaser) addToIndexFile(indexFile *repo.IndexFile, url string) error {
return nil
}

// CreateReleases finds and uploads helm chart packages to github
// CreateReleases finds and uploads Helm chart packages to GitHub
func (r *Releaser) CreateReleases() error {
packages, err := r.getListOfPackages(r.config.PackagePath)
if err != nil {
Expand Down Expand Up @@ -230,3 +295,28 @@ func (r *Releaser) CreateReleases() error {
func (r *Releaser) getListOfPackages(dir string) ([]string, error) {
return filepath.Glob(filepath.Join(dir, "*.tgz"))
}

func copyFile(srcFile string, dstFile string) error {
source, err := os.Open(srcFile)
if err != nil {
return err
}
defer source.Close()

destination, err := os.Create(dstFile)
if err != nil {
return err
}
defer destination.Close()

_, err = io.Copy(destination, source)
return err
}

func randomString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
5 changes: 5 additions & 0 deletions pkg/releaser/releaser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func (f *FakeGitHub) GetRelease(ctx context.Context, tag string) (*github.Releas
return release, nil
}

func (f *FakeGitHub) CreatePullRequest(owner string, repo string, message string, head string, base string) (string, error) {
f.Called(owner, repo, message, head, base)
return "https://github.com/owner/repo/pull/42", nil
}

func TestReleaser_UpdateIndexFile(t *testing.T) {
indexDir, _ := ioutil.TempDir(".", "index")
defer os.RemoveAll(indexDir)
Expand Down

0 comments on commit cb0563b

Please sign in to comment.