1
0
mirror of https://github.com/strongdm/comply synced 2025-12-06 14:24:12 +00:00

go.mod: update gitlab dep to v0.30.1

Releases after github.com/xanzy/go-gitlab@v0.31.0 introduce breaking
changes to the NewClient call.

Addresses #94
This commit is contained in:
Justin McCarthy
2020-09-14 11:31:56 -07:00
parent 8baf787ed7
commit 7468711b3b
978 changed files with 160705 additions and 82515 deletions

View File

@@ -1,26 +1,7 @@
github-release
go-app
bin/
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
/github-release
/go-app
*.exe
/bin
/var/cache

10
vendor/github.com/aktau/github-release/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
## Releasing new versions
1) Bump the version in github/version.go
2) Add a commit with message "github-release v1.2.3"
3) Run `git tag v1.2.3` where "1.2.3" stands in for the version you actually
want.
4) Run `make release`. Be sure to have `GITHUB_TOKEN` set in your environment.

89
vendor/github.com/aktau/github-release/Gopkg.lock generated vendored Normal file
View File

@@ -0,0 +1,89 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:6f9339c912bbdda81302633ad7e99a28dfa5a639c864061f1929510a9a64aa74"
name = "github.com/dustin/go-humanize"
packages = ["."]
pruneopts = "UT"
revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e"
version = "v1.0.0"
[[projects]]
digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "UT"
revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a"
version = "v1.8.0"
[[projects]]
digest = "1:5c56664d98f37f0ee54bf572b0b189a3910c34c31052fc7d58b282c449b079fb"
name = "github.com/inconshreveable/log15"
packages = ["."]
pruneopts = "UT"
revision = "b30bc20e4fd12cec79a9aae62e91cfcf458bd253"
version = "v2.15"
[[projects]]
digest = "1:de0adde670b2119824a1252b61a0e989669f8b24af874e399bec0e0538b2f928"
name = "github.com/kevinburke/rest"
packages = ["."]
pruneopts = "UT"
revision = "0d2892b400f81cdfb979e2f718e6070fae17a507"
version = "2.2"
[[projects]]
digest = "1:0109cf4321a15313ec895f42e723e1f76121c6975ea006abfa20012272ec0937"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "68e95eba382c972aafde02ead2cd2426a8a92480"
version = "v0.1.6"
[[projects]]
digest = "1:0c58d31abe2a2ccb429c559b6292e7df89dcda675456fecc282fa90aa08273eb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "7b513a986450394f7bbf1476909911b3aa3a55ce"
version = "v0.0.12"
[[projects]]
branch = "master"
digest = "1:f2dda646a25cf3b9f6f6931d86ef0b85b64979697e5833cdcbc0c23f2586d996"
name = "github.com/tomnomnom/linkheader"
packages = ["."]
pruneopts = "UT"
revision = "02ca5825eb8097f10d9cc53da78481a85ad84e04"
[[projects]]
digest = "1:d1d1683ae67edaebf7e51151f934943bf2f53b71260818d843ab7233aa145533"
name = "github.com/voxelbrain/goptions"
packages = ["."]
pruneopts = "UT"
revision = "26cb8b04692384f4dc269de3b5fcf3e2ef78573e"
version = "2.5.11"
[[projects]]
branch = "master"
digest = "1:8cab10971112233c82c83683a517378038eba1c20e71b29c592b73fa212437b3"
name = "golang.org/x/sys"
packages = [
"internal/unsafeheader",
"unix",
]
pruneopts = "UT"
revision = "bc7a7d42d5c30f4d0fe808715c002826ce2c624e"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/dustin/go-humanize",
"github.com/kevinburke/rest",
"github.com/tomnomnom/linkheader",
"github.com/voxelbrain/goptions",
]
solver-name = "gps-cdcl"
solver-version = 1

24
vendor/github.com/aktau/github-release/Gopkg.toml generated vendored Normal file
View File

@@ -0,0 +1,24 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
[[constraint]]
name = "github.com/dustin/go-humanize"
version = "1.0.0"
[[constraint]]
name = "github.com/kevinburke/rest"
version = "2.2.0"
[[constraint]]
name = "github.com/tomnomnom/linkheader"
branch = "master"
[[constraint]]
name = "github.com/voxelbrain/goptions"
version = "2.5.11"
[prune]
go-tests = true
unused-packages = true

View File

@@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2014 Nicolas Hillegeer
Copyright (c) 2014-2017 Nicolas Hillegeer
Copyright (c) 2020 Meter, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -1,6 +1,8 @@
SHELL=/bin/bash -o pipefail
LAST_TAG := $(shell git describe --abbrev=0 --tags)
USER := aktau
USER := github-release
EXECUTABLE := github-release
# only include the amd64 binaries, otherwise the github release will become
@@ -12,7 +14,7 @@ UNIX_EXECUTABLES := \
WIN_EXECUTABLES := \
windows/amd64/$(EXECUTABLE).exe
COMPRESSED_EXECUTABLES=$(UNIX_EXECUTABLES:%=%.tar.bz2) $(WIN_EXECUTABLES:%.exe=%.zip)
COMPRESSED_EXECUTABLES=$(UNIX_EXECUTABLES:%=%.bz2) $(WIN_EXECUTABLES:%.exe=%.zip)
COMPRESSED_EXECUTABLE_TARGETS=$(COMPRESSED_EXECUTABLES:%=bin/%)
UPLOAD_CMD = bin/tmp/$(EXECUTABLE) upload -u $(USER) -r $(EXECUTABLE) -t $(LAST_TAG) -n $(subst /,-,$(FILE)) -f bin/$(FILE)
@@ -21,7 +23,7 @@ all: $(EXECUTABLE)
# the executable used to perform the upload, dogfooding and all...
bin/tmp/$(EXECUTABLE):
go build -o "$@"
go build -v -o "$@"
# arm
bin/linux/arm/5/$(EXECUTABLE):
@@ -49,19 +51,28 @@ bin/windows/amd64/$(EXECUTABLE).exe:
# compressed artifacts, makes a huge difference (Go executable is ~9MB,
# after compressing ~2MB)
%.tar.bz2: %
tar -jcvf "$<.tar.bz2" "$<"
%.bz2: %
bzip2 --keep "$<"
%.zip: %.exe
zip "$@" "$<"
# git tag -a v$(RELEASE) -m 'release $(RELEASE)'
release: clean
$(MAKE) bin/tmp/$(EXECUTABLE) $(COMPRESSED_EXECUTABLE_TARGETS)
git push && git push --tags
git log --format=%B $(LAST_TAG) -1 | \
bin/tmp/$(EXECUTABLE) release -u $(USER) -r $(EXECUTABLE) \
-t $(LAST_TAG) -n $(LAST_TAG) -d - || true
$(foreach FILE,$(COMPRESSED_EXECUTABLES),$(UPLOAD_CMD);)
ifndef GITHUB_TOKEN
@echo "Please set GITHUB_TOKEN in the environment to perform a release"
exit 1
endif
docker run --rm --volume $(PWD)/var/cache:/root/.cache/go-build \
--env GITHUB_TOKEN=$(GITHUB_TOKEN) \
--volume "$(PWD)":/go/src/github.com/github-release/github-release \
--workdir /go/src/github.com/github-release/github-release \
meterup/ubuntu-golang:latest \
./release \
"$(MAKE) bin/tmp/$(EXECUTABLE) $(COMPRESSED_EXECUTABLE_TARGETS) && \
git log --format=%B $(LAST_TAG) -1 | \
bin/tmp/$(EXECUTABLE) release -u $(USER) -r $(EXECUTABLE) \
-t $(LAST_TAG) -n $(LAST_TAG) -d - || true && \
$(foreach FILE,$(COMPRESSED_EXECUTABLES),$(UPLOAD_CMD);)"
# install and/or update all dependencies, run this from the project directory
# go get -u ./...
@@ -80,4 +91,7 @@ clean:
rm $(EXECUTABLE) || true
rm -rf bin/
test:
go test ./...
.PHONY: clean release dep install

View File

@@ -5,34 +5,31 @@ A small commandline app written in Go that allows you to easily create
and delete releases of your projects on Github. In addition it allows
you to attach files to those releases.
It interacts with the [github releases API](http://developer.github.com/v3/repos/releases).
Though it's entirely possibly to [do all these things with
cURL](https://github.com/blog/1645-releases-api-preview), It's not
really that user-friendly. For example, you need to first query the API
to find the id of the release you want, before you can upload an
artifact. `github-release` takes care of those little details.
It interacts with the [github releases API][releases-api]. Though it's entirely
possible to [do all these things with cURL][curl], it's not really that
user-friendly. For example, you need to first query the API to find the id of
the release you want, before you can upload an artifact. `github-release` takes
care of those little details.
[curl]: https://github.com/blog/1645-releases-api-preview
[releases-api]: https://developer.github.com/v3/repos/releases
It might still be a bit rough around the edges, pull requests are
welcome!
**NOTE**: I've been made aware of the existence of the
[gothub](https://github.com/itchio/gothub) fork. Since I have very little
time to work on the project and have been a really bad maintainer, I suggest
checking it out to see if your issues have been solved there.
How to install
==============
If you don't have the Go toolset installed, and you don't want to, but
still want to use the app, you can download binaries for your platform
on the [releases
page](https://github.com/aktau/github-release/releases/latest). Yes, that's
page](https://github.com/github-release/github-release/releases/latest). Yes, that's
dogfooding, check the makefile!
If you have Go installed, you can just do:
```sh
go get github.com/aktau/github-release
go get github.com/github-release/github-release
```
This will automatically download, compile and install the app.
@@ -43,11 +40,11 @@ After that you should have a `github-release` executable in your
How to use
==========
**NOTE**: for these examples I've [created a github
token](https://help.github.com/articles/creating-an-access-token-for-command-line-use)
and set it as the env variable `GITHUB_TOKEN`. `github-release` will
automatically pick it up from the environment so that you don't have to
pass it as an argument.
**NOTE**: for these examples I've [created a github token][token] and set it as
the env variable `GITHUB_TOKEN`. `github-release` will automatically pick it up
from the environment so that you don't have to pass it as an argument.
[token]: https://help.github.com/articles/creating-an-access-token-for-command-line-use
```sh
# set your token
@@ -120,6 +117,7 @@ Used libraries
| [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) | humanize file sizes | MIT |
| [github.com/tomnomnom/linkheader](https://github.com/tomnomnom/linkheader) | GH API pagination | MIT |
| [github.com/voxelbrain/goptions](https://github.com/voxelbrain/goptions) | option parsing | BSD |
| [github.com/kevinburke/rest](https://github.com/kevinburke/rest) | HTTP client | MIT |
Todo
====
@@ -129,4 +127,5 @@ Todo
Copyright
=========
Copyright (c) 2014, Nicolas Hillegeer. All rights reserved.
Copyright (c) 2014-2017, Nicolas Hillegeer. All rights reserved.
Copyright (c) 2020, Meter, Inc. All rights reserved.

View File

@@ -1,11 +1,21 @@
package main
import (
"fmt"
"net/http"
"time"
"github.com/github-release/github-release/github"
)
const (
ASSET_DOWNLOAD_URI = "/repos/%s/%s/releases/assets/%d"
// GET /repos/:owner/:repo/releases/assets/:id
// DELETE /repos/:owner/:repo/releases/assets/:id
ASSET_URI = "/repos/%s/%s/releases/assets/%d"
// API: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
// GET /repos/:owner/:repo/releases/:id/assets
ASSET_RELEASE_LIST_URI = "/repos/%s/%s/releases/%d/assets"
)
type Asset struct {
@@ -20,13 +30,29 @@ type Asset struct {
Published time.Time `json:"published_at"`
}
// findAssetID returns the asset ID if name can be found in assets,
// otherwise returns -1.
func findAssetID(assets []Asset, name string) int {
// findAsset returns the asset if an asset with name can be found in assets,
// otherwise returns nil.
func findAsset(assets []Asset, name string) *Asset {
for _, asset := range assets {
if asset.Name == name {
return asset.Id
return &asset
}
}
return -1
return nil
}
// Delete sends a HTTP DELETE request for the given asset to Github. Returns
// nil if the asset was deleted OR there was nothing to delete.
func (a *Asset) Delete(user, repo, token string) error {
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) +
fmt.Sprintf(ASSET_URI, user, repo, a.Id)
resp, err := github.DoAuthRequest("DELETE", URL, "application/json", token, nil, nil)
if err != nil {
return fmt.Errorf("failed to delete asset %s (ID: %d), HTTP error: %b", a.Name, a.Id, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("failed to delete asset %s (ID: %d), status: %s", a.Name, a.Id, resp.Status)
}
return nil
}

View File

@@ -11,11 +11,12 @@ import (
"os"
"strconv"
"github.com/aktau/github-release/github"
"github.com/github-release/github-release/github"
)
func infocmd(opt Options) error {
user := nvls(opt.Info.User, EnvUser)
authUser := nvls(opt.Info.AuthUser, EnvAuthUser)
repo := nvls(opt.Info.Repo, EnvRepo)
token := nvls(opt.Info.Token, EnvToken)
tag := opt.Info.Tag
@@ -25,7 +26,7 @@ func infocmd(opt Options) error {
}
// Find regular git tags.
foundTags, err := Tags(user, repo, token)
foundTags, err := Tags(user, repo, authUser, token)
if err != nil {
return fmt.Errorf("could not fetch tags, %v", err)
}
@@ -52,14 +53,14 @@ func infocmd(opt Options) error {
if tag == "" {
// Get all releases.
vprintf("%v/%v: getting information for all releases\n", user, repo)
releases, err = Releases(user, repo, token)
releases, err = Releases(user, repo, authUser, token)
if err != nil {
return err
}
} else {
// Get only one release.
vprintf("%v/%v/%v: getting information for the release\n", user, repo, tag)
release, err := ReleaseOfTag(user, repo, tag, token)
release, err := ReleaseOfTag(user, repo, tag, authUser, token)
if err != nil {
return err
}
@@ -99,6 +100,7 @@ func renderInfoJSON(tags []Tag, releases []Release) error {
func uploadcmd(opt Options) error {
user := nvls(opt.Upload.User, EnvUser)
authUser := nvls(opt.Upload.AuthUser, EnvAuthUser)
repo := nvls(opt.Upload.Repo, EnvRepo)
token := nvls(opt.Upload.Token, EnvToken)
tag := opt.Upload.Tag
@@ -118,19 +120,37 @@ func uploadcmd(opt Options) error {
}
// Find the release corresponding to the entered tag, if any.
rel, err := ReleaseOfTag(user, repo, tag, token)
rel, err := ReleaseOfTag(user, repo, tag, authUser, token)
if err != nil {
return err
}
// If asked to replace, first delete the existing asset, if any.
if assetID := findAssetID(rel.Assets, name); opt.Upload.Replace && assetID != -1 {
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) +
fmt.Sprintf(ASSET_DOWNLOAD_URI, user, repo, assetID)
resp, err := github.DoAuthRequest("DELETE", URL, "application/json", token, nil, nil)
if err != nil || resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("could not replace asset %s (ID: %d), deletion failed (error: %v, status: %s)",
name, assetID, err, resp.Status)
// If the user has attempted to upload this asset before, someone could
// expect it to be present in the release struct (rel.Assets). However,
// we have to separately ask for the specific assets of this release.
// Reason: the assets in the Release struct do not contain incomplete
// uploads (which regrettably happen often using the Github API). See
// issue #26.
var assets []Asset
client := github.NewClient(authUser, token, nil)
client.SetBaseURL(EnvApiEndpoint)
err = client.Get(fmt.Sprintf(ASSET_RELEASE_LIST_URI, user, repo, rel.Id), &assets)
if err != nil {
return err
}
// Incomplete (failed) uploads will have their state set to new. These
// assets are (AFAIK) useless in all cases. The only thing they will do
// is prevent the upload of another asset of the same name. To work
// around this GH API weirdness, let's just delete assets if:
//
// 1. Their state is new.
// 2. The user explicitly asked to delete/replace the asset with -R.
if asset := findAsset(assets, name); asset != nil &&
(asset.State == "new" || opt.Upload.Replace) {
vprintf("asset (id: %d) already existed in state %s: removing...\n", asset.Id, asset.Name)
if err := asset.Delete(user, repo, token); err != nil {
return fmt.Errorf("could not replace asset: %v", err)
}
}
@@ -148,21 +168,39 @@ func uploadcmd(opt Options) error {
return fmt.Errorf("can't create upload request to %v, %v", url, err)
}
defer resp.Body.Close()
vprintln("RESPONSE:", resp)
if resp.StatusCode != http.StatusCreated {
if msg, err := ToMessage(resp.Body); err == nil {
return fmt.Errorf("could not upload, status code (%v), %v",
var r io.Reader = resp.Body
if VERBOSITY != 0 {
r = io.TeeReader(r, os.Stderr)
}
var asset *Asset
// For HTTP status 201 and 502, Github will return a JSON encoding of
// the (partially) created asset.
if resp.StatusCode == http.StatusBadGateway || resp.StatusCode == http.StatusCreated {
vprintf("ASSET: ")
asset = new(Asset)
if err := json.NewDecoder(r).Decode(&asset); err != nil {
return fmt.Errorf("upload failed (%s), could not unmarshal asset (err: %v)", resp.Status, err)
}
} else {
vprintf("BODY: ")
if msg, err := ToMessage(r); err == nil {
return fmt.Errorf("could not upload, status code (%s), %v",
resp.Status, msg)
}
return fmt.Errorf("could not upload, status code (%v)", resp.Status)
return fmt.Errorf("could not upload, status code (%s)", resp.Status)
}
if VERBOSITY != 0 {
vprintf("BODY: ")
if _, err := io.Copy(os.Stderr, resp.Body); err != nil {
return fmt.Errorf("while reading response, %v", err)
if resp.StatusCode == http.StatusBadGateway {
// 502 means the upload failed, but GitHub still retains metadata
// (an asset in state "new"). Attempt to delete that now since it
// would clutter the list of release assets.
vprintf("asset (id: %d) failed to upload, it's now in state %s: removing...\n", asset.Id, asset.Name)
if err := asset.Delete(user, repo, token); err != nil {
return fmt.Errorf("upload failed (%s), could not delete partially uploaded asset (ID: %d, err: %v) in order to cleanly reset GH API state, please try again", resp.Status, asset.Id, err)
}
return fmt.Errorf("could not upload, status code (%s)", resp.Status)
}
return nil
@@ -170,6 +208,7 @@ func uploadcmd(opt Options) error {
func downloadcmd(opt Options) error {
user := nvls(opt.Download.User, EnvUser)
authUser := nvls(opt.Download.AuthUser, EnvAuthUser)
repo := nvls(opt.Download.Repo, EnvRepo)
token := nvls(opt.Download.Token, EnvToken)
tag := opt.Download.Tag
@@ -186,25 +225,25 @@ func downloadcmd(opt Options) error {
var rel *Release
var err error
if latest {
rel, err = LatestRelease(user, repo, token)
rel, err = LatestRelease(user, repo, authUser, token)
} else {
rel, err = ReleaseOfTag(user, repo, tag, token)
rel, err = ReleaseOfTag(user, repo, tag, authUser, token)
}
if err != nil {
return err
}
assetID := findAssetID(rel.Assets, name)
if assetID == -1 {
asset := findAsset(rel.Assets, name)
if asset == nil {
return fmt.Errorf("coud not find asset named %s", name)
}
var resp *http.Response
if token == "" {
// Use the regular github.com site it we don't have a token.
// Use the regular github.com site if we don't have a token.
resp, err = http.Get("https://github.com" + fmt.Sprintf("/%s/%s/releases/download/%s/%s", user, repo, tag, name))
} else {
url := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf(ASSET_DOWNLOAD_URI, user, repo, assetID)
url := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf(ASSET_URI, user, repo, asset.Id)
resp, err = github.DoAuthRequest("GET", url, "", token, map[string]string{
"Accept": "application/octet-stream",
}, nil)
@@ -345,6 +384,7 @@ func releasecmd(opt Options) error {
func editcmd(opt Options) error {
cmdopt := opt.Edit
user := nvls(cmdopt.User, EnvUser)
authUser := nvls(cmdopt.AuthUser, EnvAuthUser)
repo := nvls(cmdopt.Repo, EnvRepo)
token := nvls(cmdopt.Token, EnvToken)
tag := cmdopt.Tag
@@ -359,7 +399,7 @@ func editcmd(opt Options) error {
return err
}
id, err := IdOfTag(user, repo, tag, token)
id, err := IdOfTag(user, repo, tag, authUser, token)
if err != nil {
return err
}
@@ -422,9 +462,10 @@ func deletecmd(opt Options) error {
nvls(opt.Delete.Repo, EnvRepo),
nvls(opt.Delete.Token, EnvToken),
opt.Delete.Tag
authUser := nvls(opt.Delete.AuthUser, EnvAuthUser)
vprintln("deleting...")
id, err := IdOfTag(user, repo, tag, token)
id, err := IdOfTag(user, repo, tag, authUser, token)
if err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"os"
"github.com/aktau/github-release/github"
"github.com/github-release/github-release/github"
"github.com/voxelbrain/goptions"
)
@@ -18,22 +18,24 @@ type Options struct {
goptions.Verbs
Download struct {
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Latest bool `goptions:"-l, --latest, description='Download latest release (required if tag is not specified)',mutexgroup='input'"`
Tag string `goptions:"-t, --tag, description='Git tag to download from (required if latest is not specified)', mutexgroup='input',obligatory"`
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Latest bool `goptions:"-l, --latest, description='Download latest release (required if tag is not specified)',mutexgroup='input'"`
Tag string `goptions:"-t, --tag, description='Git tag to download from (required if latest is not specified)', mutexgroup='input',obligatory"`
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
} `goptions:"download"`
Upload struct {
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, description='Git tag to upload to', obligatory"`
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
Label string `goptions:"-l, --label, description='Label (description) of the file'"`
File *os.File `goptions:"-f, --file, description='File to upload (use - for stdin)', rdonly, obligatory"`
Replace bool `goptions:"-R, --replace, description='Replace asset with same name if it already exists (WARNING: not atomic, failure to upload will remove the original asset too)'"`
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, description='Git tag to upload to', obligatory"`
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
Label string `goptions:"-l, --label, description='Label (description) of the file'"`
File *os.File `goptions:"-f, --file, description='File to upload (use - for stdin)', rdonly, obligatory"`
Replace bool `goptions:"-R, --replace, description='Replace asset with same name if it already exists (WARNING: not atomic, failure to upload will remove the original asset too)'"`
} `goptions:"upload"`
Release struct {
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
@@ -49,6 +51,7 @@ type Options struct {
Edit struct {
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, obligatory, description='Git tag to edit the release of'"`
Name string `goptions:"-n, --name, description='New name of the release (defaults to tag)'"`
@@ -57,17 +60,19 @@ type Options struct {
Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"`
} `goptions:"edit"`
Delete struct {
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, obligatory, description='Git tag of release to delete'"`
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, obligatory, description='Git tag of release to delete'"`
} `goptions:"delete"`
Info struct {
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, description='Git tag to query (optional)'"`
JSON bool `goptions:"-j, --json, description='Emit info as JSON instead of text'"`
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
Tag string `goptions:"-t, --tag, description='Git tag to query (optional)'"`
JSON bool `goptions:"-j, --json, description='Emit info as JSON instead of text'"`
} `goptions:"info"`
}
@@ -87,6 +92,9 @@ var (
)
var (
// The user whose token is being used to authenticate to the API. If unset,
// EnvUser is used.
EnvAuthUser string
EnvToken string
EnvUser string
EnvRepo string
@@ -96,8 +104,13 @@ var (
func init() {
EnvToken = os.Getenv("GITHUB_TOKEN")
EnvUser = os.Getenv("GITHUB_USER")
EnvAuthUser = os.Getenv("GITHUB_AUTH_USER")
EnvRepo = os.Getenv("GITHUB_REPO")
EnvApiEndpoint = os.Getenv("GITHUB_API")
if EnvAuthUser == "" {
EnvAuthUser = EnvUser
}
}
func main() {
@@ -106,7 +119,7 @@ func main() {
goptions.ParseAndFail(&options)
if options.Version {
fmt.Printf("github-release v%s\n", VERSION)
fmt.Printf("github-release v%s\n", github.VERSION)
return
}

View File

@@ -1,22 +0,0 @@
package github
import (
"fmt"
"os"
)
func vprintln(a ...interface{}) (int, error) {
if VERBOSITY > 0 {
return fmt.Fprintln(os.Stderr, a...)
}
return 0, nil
}
func vprintf(format string, a ...interface{}) (int, error) {
if VERBOSITY > 0 {
return fmt.Fprintf(os.Stderr, format, a...)
}
return 0, nil
}

View File

@@ -1,79 +0,0 @@
package github
import (
"bytes"
"errors"
"fmt"
"io"
"os"
)
func GetFileSize(f *os.File) (int64, error) {
/* first try stat */
off, err := fsizeStat(f)
if err != nil {
/* if that fails, try seek */
return fsizeSeek(f)
}
return off, nil
}
func fsizeStat(f *os.File) (int64, error) {
fi, err := f.Stat()
if err != nil {
return 0, err
}
return fi.Size(), nil
}
func fsizeSeek(f *os.File) (int64, error) {
off, err := f.Seek(0, 2)
if err != nil {
return 0, fmt.Errorf("seeking did not work, stdin is not" +
"supported yet because github doesn't support chunking" +
"requests (and I haven't implemented detecting stdin and" +
"buffering yet")
}
_, err = f.Seek(0, 0)
if err != nil {
return 0, fmt.Errorf("could not seek back in the file")
}
return off, nil
}
// materializeFile takes a physical file or stream (named pipe, user input,
// ...) and returns an io.Reader and the number of bytes that can be read
// from it.
func materializeFile(f *os.File) (io.Reader, int64, error) {
fi, err := f.Stat()
if err != nil {
return nil, 0, err
}
// If the file is actually a char device (like user typed input)
// or a named pipe (like a streamed in file), buffer it up.
//
// When uploading a file, you need to either explicitly set the
// Content-Length header or send a chunked request. Since the
// github upload server doesn't accept chunked encoding, we have
// to set the size of the file manually. Since a stream doesn't have a
// predefined length, it's read entirely into a byte buffer.
if fi.Mode()&(os.ModeCharDevice|os.ModeNamedPipe) == 1 {
vprintln("input was a stream, buffering up")
var buf bytes.Buffer
n, err := buf.ReadFrom(f)
if err != nil {
return nil, 0, errors.New("req: could not buffer up input stream: " + err.Error())
}
return &buf, n, err
}
// We know the os.File is most likely an actual file now.
n, err := GetFileSize(f)
return f, n, err
}

View File

@@ -1,277 +0,0 @@
// Package github is a mini-library for querying the GitHub v3 API that
// takes care of authentication (with tokens only) and pagination.
package github
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"reflect"
"github.com/tomnomnom/linkheader"
)
const DefaultBaseURL = "https://api.github.com"
// Set to values > 0 to control verbosity, for debugging.
var VERBOSITY = 0
// DoAuthRequest ...
//
// TODO: This function is amazingly ugly (separate headers, token, no API
// URL constructions, et cetera).
func DoAuthRequest(method, url, mime, token string, headers map[string]string, body io.Reader) (*http.Response, error) {
req, err := newAuthRequest(method, url, mime, token, headers, body)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Client collects a few options that can be set when contacting the GitHub
// API, such as authorization tokens. Methods called on Client will supply
// these options when calling the API.
type Client struct {
Token string // Github API token, used when set.
BaseURL string // Github API URL, defaults to DefaultBaseURL if unset.
}
// Get fetches uri (relative URL) from the GitHub API and unmarshals the
// response into v. It takes care of pagination transparantly.
func (c Client) Get(uri string, v interface{}) error {
if c.BaseURL == "" {
c.BaseURL = DefaultBaseURL
}
rc, err := c.getPaginated(uri)
if err != nil {
return err
}
defer rc.Close()
var r io.Reader = rc
if VERBOSITY > 0 {
vprintln("BODY:")
r = io.TeeReader(rc, os.Stderr)
}
// Github may return paginated responses. If so, githubGetPaginated will
// return a reader which yields the concatenation of all pages. These
// reponses are _separate_ JSON arrays. Standard json.Unmarshal() or
// json.Decoder.Decode() will not have the expected result when
// unmarshalling into v. For example, a 2-page response:
//
// 1. [{...}, {...}, {...}]
// 2. [{...}]
//
// If v is a slice type, we'd like to decode the four objects from the
// two pages into a single slice. However, if we just use
// json.Decoder.Decode(), that won't work. v will be overridden each
// time.
//
// For this reason, we use two very ugly things.
//
// 1. We analyze v with reflect to see if it's a slice.
// 2. If so, we use the json.Decoder token API and reflection to
// dynamically add new elements into the slice, ignoring the
// boundaries between JSON arrays.
//
// This is a lot of work, and feels very stupid. An alternative would be
// removing the outermost ][ in the intermediate responses, which would
// be even more finnicky. Another alternative would be to explicitly
// expose a pagination API, forcing clients of this code to deal with
// it. That's how the go-github library does it. But why solve a problem
// sensibly if one can power through it with reflection (half-joking)?
sl := reflect.Indirect(reflect.ValueOf(v)) // Get the reflect.Value of the slice so we can append to it.
t := sl.Type()
if t.Kind() != reflect.Slice {
// Not a slice, not going to handle special pagination JSON stream
// semantics since it likely wouldn't work properly anyway. If this
// is a non-paginated stream, it should work.
return json.NewDecoder(r).Decode(v)
}
t = t.Elem() // Extract the type of the slice's elements.
// Use streaming Token API to append all elements of the JSON stream
// arrays (pagination) to the slice.
for dec := json.NewDecoder(r); ; {
tok, err := dec.Token()
if err != nil {
if err == io.EOF {
return nil // Natural end of the JSON stream.
}
return err
}
vprintf("TOKEN %T: %v\n", tok, tok)
// Check for tokens until we get an opening array brace. If we're
// not in an array, we can't decode an array element later, which
// would result in an error.
if tok != json.Delim('[') {
continue
}
// Read the array, appending all elements to the slice.
for dec.More() {
it := reflect.New(t) // Interface to a valid pointer to an object of the same type as the slice elements.
vprintf("OBJECT %T: %v\n", it, it)
if err := dec.Decode(it.Interface()); err != nil {
return err
}
sl.Set(reflect.Append(sl, it.Elem()))
}
}
}
// getPaginated returns a reader that yields the concatenation of the
// paginated responses to a query (URI).
//
// TODO: Rework the API so we can cleanly append per_page=100 as a URL
// parameter.
func (c Client) getPaginated(uri string) (io.ReadCloser, error) {
// Parse the passed-in URI to make sure we don't lose any values when
// setting our own params.
u, err := url.Parse(c.BaseURL + uri)
if err != nil {
return nil, err
}
v := u.Query()
v.Set("per_page", "100") // The default is 30, this makes it less likely for Github to rate-limit us.
if c.Token != "" {
v.Set("access_token", c.Token)
}
u.RawQuery = v.Encode()
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("expected '200 OK' but received '%v' (url: %s)", resp.Status, resp.Request.URL)
}
vprintln("GET (top-level)", resp.Request.URL, "->", resp)
// If the HTTP response is paginated, it will contain a Link header.
links := linkheader.Parse(resp.Header.Get("Link"))
if len(links) == 0 {
return resp.Body, nil // No pagination.
}
// In this case, fetch all pages and concatenate them.
r, w := io.Pipe()
done := make(chan struct{}) // Backpressure from the pipe writer.
responses := make(chan *http.Response, 5) // Allow 5 concurrent HTTP requests.
responses <- resp
// URL fetcher goroutine. Fetches paginated responses until no more
// pages can be found. Closes the write end of the pipe if fetching a
// page fails.
go func() {
defer close(responses) // Signal that no more requests are coming.
for len(links) > 0 {
URL := nextLink(links)
if URL == "" {
return // We're done.
}
resp, err := http.Get(URL)
links = linkheader.Parse(resp.Header.Get("Link"))
if err != nil {
w.CloseWithError(err)
return
}
select {
case <-done:
return // The body concatenator goroutine signals it has stopped.
case responses <- resp: // Schedule the request body to be written to the pipe.
}
}
}()
// Body concatenator goroutine. Writes each response into the pipe
// sequentially. Closes the write end of the pipe if the HTTP status is
// not 200 or the body can't be read.
go func() {
defer func() {
// Drain channel and close bodies, stop leaks.
for resp := range responses {
resp.Body.Close()
}
}()
defer close(done) // Signal that we're done writing all requests, or an error occurred.
for resp := range responses {
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
w.CloseWithError(fmt.Errorf("expected '200 OK' but received '%v' (url: %s)", resp.Status, resp.Request.URL))
return
}
_, err := io.Copy(w, resp.Body)
resp.Body.Close()
if err != nil {
vprintln("error: io.Copy: ", err)
w.CloseWithError(err)
return
}
}
w.Close()
}()
return r, nil
}
// Create a new request that sends the auth token.
func newAuthRequest(method, url, mime, token string, headers map[string]string, body io.Reader) (*http.Request, error) {
vprintln("creating request:", method, url, mime, token)
var n int64 // content length
var err error
if f, ok := body.(*os.File); ok {
// Retrieve the content-length and buffer up if necessary.
body, n, err = materializeFile(f)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
// net/http automatically does this if req.Body is of type
// (bytes.Reader|bytes.Buffer|strings.Reader). Sadly, we also need to
// handle *os.File.
if n != 0 {
vprintln("setting content-length to", n)
req.ContentLength = n
}
if mime != "" {
req.Header.Set("Content-Type", mime)
}
req.Header.Set("Authorization", "token "+token)
for k, v := range headers {
req.Header.Set(k, v)
}
return req, nil
}
// nextLink returns the HTTP header Link annotated with 'next', "" otherwise.
func nextLink(links linkheader.Links) string {
for _, link := range links {
if link.Rel == "next" && link.URL != "" {
return link.URL
}
}
return ""
}

12
vendor/github.com/aktau/github-release/release generated vendored Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
apt-get update && apt-get install zip
git branch --set-upstream-to=origin/master release
set -x
exec /bin/bash -c "$@"
}
main "$@"

View File

@@ -5,7 +5,7 @@ import (
"strings"
"time"
"github.com/aktau/github-release/github"
"github.com/github-release/github-release/github"
"github.com/dustin/go-humanize"
)
@@ -67,9 +67,11 @@ type ReleaseCreate struct {
Prerelease bool `json:"prerelease"`
}
func Releases(user, repo, token string) ([]Release, error) {
func Releases(user, repo, authUser, token string) ([]Release, error) {
var releases []Release
err := github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(RELEASE_LIST_URI, user, repo), &releases)
client := github.NewClient(authUser, token, nil)
client.SetBaseURL(EnvApiEndpoint)
err := client.Get(fmt.Sprintf(RELEASE_LIST_URI, user, repo), &releases)
if err != nil {
return nil, err
}
@@ -77,20 +79,22 @@ func Releases(user, repo, token string) ([]Release, error) {
return releases, nil
}
func latestReleaseApi(user, repo, token string) (*Release, error) {
func latestReleaseApi(user, repo, authUser, token string) (*Release, error) {
var release Release
return &release, github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(RELEASE_LATEST_URI, user, repo), &release)
client := github.NewClient(authUser, token, nil)
client.SetBaseURL(EnvApiEndpoint)
return &release, client.Get(fmt.Sprintf(RELEASE_LATEST_URI, user, repo), &release)
}
func LatestRelease(user, repo, token string) (*Release, error) {
func LatestRelease(user, repo, authUser, token string) (*Release, error) {
// If latestReleaseApi DOESN'T give an error, return the release.
if latestRelease, err := latestReleaseApi(user, repo, token); err == nil {
if latestRelease, err := latestReleaseApi(user, repo, authUser, token); err == nil {
return latestRelease, nil
}
// The enterprise api doesnt support the latest release endpoint. Get
// all releases and compare the published date to get the latest.
releases, err := Releases(user, repo, token)
releases, err := Releases(user, repo, authUser, token)
if err != nil {
return nil, err
}
@@ -111,8 +115,8 @@ func LatestRelease(user, repo, token string) (*Release, error) {
return &releases[latestRelIndex], nil
}
func ReleaseOfTag(user, repo, tag, token string) (*Release, error) {
releases, err := Releases(user, repo, token)
func ReleaseOfTag(user, repo, tag, authUser, token string) (*Release, error) {
releases, err := Releases(user, repo, authUser, token)
if err != nil {
return nil, err
}
@@ -127,8 +131,8 @@ func ReleaseOfTag(user, repo, tag, token string) (*Release, error) {
}
/* find the release-id of the specified tag */
func IdOfTag(user, repo, tag, token string) (int, error) {
release, err := ReleaseOfTag(user, repo, tag, token)
func IdOfTag(user, repo, tag, authUser, token string) (int, error) {
release, err := ReleaseOfTag(user, repo, tag, authUser, token)
if err != nil {
return 0, err
}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/aktau/github-release/github"
"github.com/github-release/github-release/github"
)
const (
@@ -22,7 +22,9 @@ func (t *Tag) String() string {
}
// Get the tags associated with a repo.
func Tags(user, repo, token string) ([]Tag, error) {
func Tags(user, repo, authUser, token string) ([]Tag, error) {
var tags []Tag
return tags, github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(TAGS_URI, user, repo), &tags)
client := github.NewClient(authUser, token, nil)
client.SetBaseURL(EnvApiEndpoint)
return tags, client.Get(fmt.Sprintf(TAGS_URI, user, repo), &tags)
}

View File

@@ -6,7 +6,7 @@ import (
"time"
)
/* oracle nvl, return first non-empty string */
/* nvls returns the first value in xs that is not empty. */
func nvls(xs ...string) string {
for _, s := range xs {
if s != "" {

View File

@@ -1,3 +1 @@
package main
const VERSION = "0.7.2"