github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/distgo/cmd/publish/github_publish.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package publish 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "mime" 22 "net/url" 23 "os" 24 "path" 25 "path/filepath" 26 27 "github.com/google/go-github/github" 28 "github.com/jtacoma/uritemplates" 29 "github.com/pkg/errors" 30 "golang.org/x/oauth2" 31 "gopkg.in/cheggaaa/pb.v1" 32 33 "github.com/palantir/godel/apps/distgo/params" 34 ) 35 36 type GitHubConnectionInfo struct { 37 APIURL string 38 User string 39 Token string 40 41 Owner string 42 Repository string 43 } 44 45 func (g *GitHubConnectionInfo) Publish(buildSpec params.ProductBuildSpec, paths ProductPaths, stdout io.Writer) ([]string, error) { 46 client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( 47 &oauth2.Token{AccessToken: g.Token}, 48 ))) 49 50 // set base URL (should be of the form "https://api.github.com/") 51 apiURL, err := url.Parse(g.APIURL) 52 if err != nil { 53 return nil, errors.Wrapf(err, "failed to parse %s as URL for API calls", g.APIURL) 54 } 55 client.BaseURL = apiURL 56 57 if g.Owner == "" { 58 g.Owner = g.User 59 } 60 61 fmt.Fprintf(stdout, "Creating GitHub release %s for %s/%s...", buildSpec.ProductVersion, g.Owner, g.Repository) 62 releaseRes, _, err := client.Repositories.CreateRelease(context.Background(), g.Owner, g.Repository, &github.RepositoryRelease{ 63 TagName: github.String(buildSpec.ProductVersion), 64 }) 65 if err != nil { 66 // newline to complement "..." output 67 fmt.Fprintln(stdout) 68 69 if ghErr, ok := err.(*github.ErrorResponse); ok && len(ghErr.Errors) > 0 { 70 if ghErr.Errors[0].Code == "already_exists" { 71 return nil, errors.Errorf("GitHub release %s already exists for %s/%s", buildSpec.ProductVersion, g.Owner, g.Repository) 72 } 73 } 74 return nil, errors.Wrapf(err, "failed to create GitHub release %s for %s/%s...", buildSpec.ProductVersion, g.Owner, g.Repository) 75 } 76 fmt.Fprintln(stdout, "done") 77 78 var uploadURLs []string 79 for _, currPath := range paths.artifactPaths { 80 uploadURL, err := g.uploadFileAtPath(client, releaseRes, currPath, stdout) 81 if err != nil { 82 return nil, err 83 } 84 uploadURLs = append(uploadURLs, uploadURL) 85 } 86 return uploadURLs, nil 87 } 88 89 func (g *GitHubConnectionInfo) uploadFileAtPath(client *github.Client, release *github.RepositoryRelease, filePath string, stdout io.Writer) (string, error) { 90 uploadURI, err := uploadURIForProduct(release.GetUploadURL(), path.Base(filePath)) 91 if err != nil { 92 return "", err 93 } 94 95 f, err := os.Open(filePath) 96 if err != nil { 97 return "", errors.Wrapf(err, "failed to open artifact %s for upload", filePath) 98 } 99 uploadRes, _, err := githubUploadReleaseAssetWithProgress(context.Background(), client, uploadURI, f, stdout) 100 if err != nil { 101 return "", errors.Wrapf(err, "failed to upload artifact %s", filePath) 102 } 103 return uploadRes.GetBrowserDownloadURL(), nil 104 } 105 106 // uploadURIForProduct returns an asset upload URI using the provided upload template from the release creation 107 // response. See https://developer.github.com/v3/repos/releases/#response for the specifics of the API. 108 func uploadURIForProduct(githubUploadURLTemplate, name string) (string, error) { 109 const nameTemplate = "name" 110 111 t, err := uritemplates.Parse(githubUploadURLTemplate) 112 if err != nil { 113 return "", errors.Wrapf(err, "failed to parse upload URI template %q", githubUploadURLTemplate) 114 } 115 uploadURI, err := t.Expand(map[string]interface{}{ 116 nameTemplate: name, 117 }) 118 if err != nil { 119 return "", errors.Wrapf(err, "failed to expand URI template %q with %q = %q", githubUploadURLTemplate, nameTemplate, name) 120 } 121 return uploadURI, nil 122 } 123 124 // Based on github.Repositories.UploadReleaseAsset. Adds support for progress reporting. 125 func githubUploadReleaseAssetWithProgress(ctx context.Context, client *github.Client, uploadURI string, file *os.File, stdout io.Writer) (*github.ReleaseAsset, *github.Response, error) { 126 stat, err := file.Stat() 127 if err != nil { 128 return nil, nil, err 129 } 130 if stat.IsDir() { 131 return nil, nil, errors.New("the asset to upload can't be a directory") 132 } 133 134 fmt.Fprintf(stdout, "Uploading %s to %s\n", file.Name(), uploadURI) 135 bar := pb.New(int(stat.Size())).SetUnits(pb.U_BYTES) 136 bar.Output = stdout 137 bar.SetMaxWidth(120) 138 bar.Start() 139 defer bar.Finish() 140 reader := bar.NewProxyReader(file) 141 142 mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) 143 req, err := client.NewUploadRequest(uploadURI, reader, stat.Size(), mediaType) 144 if err != nil { 145 return nil, nil, err 146 } 147 148 asset := new(github.ReleaseAsset) 149 resp, err := client.Do(ctx, req, asset) 150 if err != nil { 151 return nil, resp, err 152 } 153 return asset, resp, nil 154 }