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  }