@@ -19,11 +19,13 @@ import (
1919 "fmt"
2020 "io"
2121 "io/ioutil"
22+ "math/rand"
2223 "net/http"
2324 "net/url"
2425 "os"
2526 "path/filepath"
2627 "strings"
28+ "time"
2729
2830 "github.com/pkg/errors"
2931 "helm.sh/helm/v3/pkg/chart/loader"
@@ -41,13 +43,27 @@ import (
4143type GitHub interface {
4244 CreateRelease (ctx context.Context , input * github.Release ) error
4345 GetRelease (ctx context.Context , tag string ) (* github.Release , error )
46+ CreatePullRequest (owner string , repo string , message string , head string , base string ) (string , error )
4447}
4548
4649type HttpClient interface {
4750 Get (url string ) (* http.Response , error )
4851}
4952
50- type DefaultHttpClient struct {
53+ type Git interface {
54+ AddWorktree (workingDir string , committish string ) (string , error )
55+ RemoveWorktree (workingDir string , path string ) error
56+ Add (workingDir string , args ... string ) error
57+ Commit (workingDir string , message string ) error
58+ Push (workingDir string , args ... string ) error
59+ }
60+
61+ type DefaultHttpClient struct {}
62+
63+ var letters = []rune ("abcdefghijklmnopqrstuvwxyz0123456789" )
64+
65+ func init () {
66+ rand .Seed (time .Now ().UnixNano ())
5167}
5268
5369func (c * DefaultHttpClient ) Get (url string ) (resp * http.Response , err error ) {
@@ -58,17 +74,19 @@ type Releaser struct {
5874 config * config.Options
5975 github GitHub
6076 httpClient HttpClient
77+ git Git
6178}
6279
63- func NewReleaser (config * config.Options , github GitHub ) * Releaser {
80+ func NewReleaser (config * config.Options , github GitHub , git Git ) * Releaser {
6481 return & Releaser {
6582 config : config ,
6683 github : github ,
6784 httpClient : & DefaultHttpClient {},
85+ git : git ,
6886 }
6987}
7088
71- // UpdateIndexFile index.yaml file for a give git repo
89+ // UpdateIndexFile updates the index.yaml file for a given Git repo
7290func (r * Releaser ) UpdateIndexFile () (bool , error ) {
7391 // if path doesn't end with index.yaml we can try and fix it
7492 if filepath .Base (r .config .IndexPath ) != "index.yaml" {
@@ -103,13 +121,13 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
103121 return false , err
104122 }
105123
106- fmt .Printf ("====> Using existing index at %s\n " , r .config .IndexPath )
124+ fmt .Printf ("Using existing index at %s\n " , r .config .IndexPath )
107125 indexFile , err = repo .LoadIndexFile (r .config .IndexPath )
108126 if err != nil {
109127 return false , err
110128 }
111129 } else {
112- fmt .Printf ("====> UpdateIndexFile new index at %s\n " , r .config .IndexPath )
130+ fmt .Printf ("UpdateIndexFile new index at %s\n " , r .config .IndexPath )
113131 indexFile = repo .NewIndexFile ()
114132 }
115133
@@ -133,7 +151,7 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
133151 baseName := strings .TrimSuffix (name , filepath .Ext (name ))
134152 tagParts := r .splitPackageNameAndVersion (baseName )
135153 packageName , packageVersion := tagParts [0 ], tagParts [1 ]
136- fmt .Printf ("====> Found %s-%s.tgz\n " , packageName , packageVersion )
154+ fmt .Printf ("Found %s-%s.tgz\n " , packageName , packageVersion )
137155 if _ , err := indexFile .Get (packageName , packageVersion ); err != nil {
138156 if err := r .addToIndexFile (indexFile , downloadUrl .String ()); err != nil {
139157 return false , err
@@ -144,15 +162,62 @@ func (r *Releaser) UpdateIndexFile() (bool, error) {
144162 }
145163 }
146164
147- if update {
148- fmt .Printf ("--> Updating index %s\n " , r .config .IndexPath )
149- indexFile .SortEntries ()
150- return true , indexFile .WriteFile (r .config .IndexPath , 0644 )
151- } else {
152- fmt .Printf ("--> Index %s did not change\n " , r .config .IndexPath )
165+ if ! update {
166+ fmt .Printf ("Index %s did not change\n " , r .config .IndexPath )
167+ return false , nil
168+ }
169+
170+ fmt .Printf ("Updating index %s\n " , r .config .IndexPath )
171+ indexFile .SortEntries ()
172+
173+ if err := indexFile .WriteFile (r .config .IndexPath , 0644 ); err != nil {
174+ return false , err
175+ }
176+
177+ if ! r .config .Push && ! r .config .PR {
178+ return true , nil
179+ }
180+
181+ worktree , err := r .git .AddWorktree ("" , "origin/" + r .config .PagesBranch )
182+ if err != nil {
183+ return false , err
184+ }
185+ defer r .git .RemoveWorktree ("" , worktree )
186+
187+ indexYamlPath := filepath .Join (worktree , "index.yaml" )
188+ if err := copyFile (r .config .IndexPath , indexYamlPath ); err != nil {
189+ return false , err
190+ }
191+ if err := r .git .Add (worktree , indexYamlPath ); err != nil {
192+ return false , err
193+ }
194+ if err := r .git .Commit (worktree , "Update index.yaml" ); err != nil {
195+ return false , err
196+ }
197+
198+ pushURL := fmt .Sprintf ("https://x-access-token:%s@github.com/%s/%s" , r .config .Token , r .config .Owner , r .config .GitRepo )
199+
200+ if r .config .Push {
201+ fmt .Printf ("Pushing to branch %q\n " , r .config .PagesBranch )
202+ if err := r .git .Push (worktree , pushURL , "HEAD:refs/heads/" + r .config .PagesBranch ); err != nil {
203+ return false , err
204+ }
205+ } else if r .config .PR {
206+ branch := fmt .Sprintf ("chart-releaser-%s" , randomString (16 ))
207+
208+ fmt .Printf ("Pushing to branch %q\n " , branch )
209+ if err := r .git .Push (worktree , pushURL , "HEAD:refs/heads/" + branch ); err != nil {
210+ return false , err
211+ }
212+ fmt .Printf ("Creating pull request against branch %q\n " , r .config .PagesBranch )
213+ prURL , err := r .github .CreatePullRequest (r .config .Owner , r .config .GitRepo , "Update index.yaml" , branch , r .config .PagesBranch )
214+ if err != nil {
215+ return false , err
216+ }
217+ fmt .Println ("Pull request created:" , prURL )
153218 }
154219
155- return false , nil
220+ return true , nil
156221}
157222
158223func (r * Releaser ) splitPackageNameAndVersion (pkg string ) []string {
@@ -164,13 +229,13 @@ func (r *Releaser) addToIndexFile(indexFile *repo.IndexFile, url string) error {
164229 arch := filepath .Join (r .config .PackagePath , filepath .Base (url ))
165230
166231 // extract chart metadata
167- fmt .Printf ("====> Extracting chart metadata from %s\n " , arch )
232+ fmt .Printf ("Extracting chart metadata from %s\n " , arch )
168233 c , err := loader .LoadFile (arch )
169234 if err != nil {
170235 return errors .Wrapf (err , "%s is not a helm chart package" , arch )
171236 }
172237 // calculate hash
173- fmt .Printf ("====> Calculating Hash for %s\n " , arch )
238+ fmt .Printf ("Calculating Hash for %s\n " , arch )
174239 hash , err := provenance .DigestFile (arch )
175240 if err != nil {
176241 return err
@@ -187,7 +252,7 @@ func (r *Releaser) addToIndexFile(indexFile *repo.IndexFile, url string) error {
187252 return nil
188253}
189254
190- // CreateReleases finds and uploads helm chart packages to github
255+ // CreateReleases finds and uploads Helm chart packages to GitHub
191256func (r * Releaser ) CreateReleases () error {
192257 packages , err := r .getListOfPackages (r .config .PackagePath )
193258 if err != nil {
@@ -230,3 +295,28 @@ func (r *Releaser) CreateReleases() error {
230295func (r * Releaser ) getListOfPackages (dir string ) ([]string , error ) {
231296 return filepath .Glob (filepath .Join (dir , "*.tgz" ))
232297}
298+
299+ func copyFile (srcFile string , dstFile string ) error {
300+ source , err := os .Open (srcFile )
301+ if err != nil {
302+ return err
303+ }
304+ defer source .Close ()
305+
306+ destination , err := os .Create (dstFile )
307+ if err != nil {
308+ return err
309+ }
310+ defer destination .Close ()
311+
312+ _ , err = io .Copy (destination , source )
313+ return err
314+ }
315+
316+ func randomString (n int ) string {
317+ b := make ([]rune , n )
318+ for i := range b {
319+ b [i ] = letters [rand .Intn (len (letters ))]
320+ }
321+ return string (b )
322+ }
0 commit comments