mirror of
https://github.com/strongdm/comply
synced 2026-03-04 17:46:17 +00:00
chore: migrate to go modules
This commit is contained in:
26
vendor/github.com/aktau/github-release/.gitignore
generated
vendored
Normal file
26
vendor/github.com/aktau/github-release/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
|
||||
*.exe
|
||||
20
vendor/github.com/aktau/github-release/LICENSE
generated
vendored
Normal file
20
vendor/github.com/aktau/github-release/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Nicolas Hillegeer
|
||||
|
||||
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
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
83
vendor/github.com/aktau/github-release/Makefile
generated
vendored
Normal file
83
vendor/github.com/aktau/github-release/Makefile
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
LAST_TAG := $(shell git describe --abbrev=0 --tags)
|
||||
|
||||
USER := aktau
|
||||
EXECUTABLE := github-release
|
||||
|
||||
# only include the amd64 binaries, otherwise the github release will become
|
||||
# too big
|
||||
UNIX_EXECUTABLES := \
|
||||
darwin/amd64/$(EXECUTABLE) \
|
||||
freebsd/amd64/$(EXECUTABLE) \
|
||||
linux/amd64/$(EXECUTABLE)
|
||||
WIN_EXECUTABLES := \
|
||||
windows/amd64/$(EXECUTABLE).exe
|
||||
|
||||
COMPRESSED_EXECUTABLES=$(UNIX_EXECUTABLES:%=%.tar.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)
|
||||
|
||||
all: $(EXECUTABLE)
|
||||
|
||||
# the executable used to perform the upload, dogfooding and all...
|
||||
bin/tmp/$(EXECUTABLE):
|
||||
go build -o "$@"
|
||||
|
||||
# arm
|
||||
bin/linux/arm/5/$(EXECUTABLE):
|
||||
GOARM=5 GOARCH=arm GOOS=linux go build -o "$@"
|
||||
bin/linux/arm/7/$(EXECUTABLE):
|
||||
GOARM=7 GOARCH=arm GOOS=linux go build -o "$@"
|
||||
|
||||
# 386
|
||||
bin/darwin/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=darwin go build -o "$@"
|
||||
bin/linux/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=linux go build -o "$@"
|
||||
bin/windows/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=windows go build -o "$@"
|
||||
|
||||
# amd64
|
||||
bin/freebsd/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=freebsd go build -o "$@"
|
||||
bin/darwin/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=darwin go build -o "$@"
|
||||
bin/linux/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=linux go build -o "$@"
|
||||
bin/windows/amd64/$(EXECUTABLE).exe:
|
||||
GOARCH=amd64 GOOS=windows go build -o "$@"
|
||||
|
||||
# compressed artifacts, makes a huge difference (Go executable is ~9MB,
|
||||
# after compressing ~2MB)
|
||||
%.tar.bz2: %
|
||||
tar -jcvf "$<.tar.bz2" "$<"
|
||||
%.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);)
|
||||
|
||||
# install and/or update all dependencies, run this from the project directory
|
||||
# go get -u ./...
|
||||
# go test -i ./
|
||||
dep:
|
||||
go list -f '{{join .Deps "\n"}}' | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs go get -u
|
||||
|
||||
$(EXECUTABLE): dep
|
||||
go build -o "$@"
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
clean:
|
||||
rm go-app || true
|
||||
rm $(EXECUTABLE) || true
|
||||
rm -rf bin/
|
||||
|
||||
.PHONY: clean release dep install
|
||||
132
vendor/github.com/aktau/github-release/README.md
generated
vendored
Normal file
132
vendor/github.com/aktau/github-release/README.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
github-release
|
||||
==============
|
||||
|
||||
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 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
|
||||
dogfooding, check the makefile!
|
||||
|
||||
If you have Go installed, you can just do:
|
||||
|
||||
```sh
|
||||
go get github.com/aktau/github-release
|
||||
```
|
||||
|
||||
This will automatically download, compile and install the app.
|
||||
|
||||
After that you should have a `github-release` executable in your
|
||||
`$GOPATH/bin`.
|
||||
|
||||
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.
|
||||
|
||||
```sh
|
||||
# set your token
|
||||
export GITHUB_TOKEN=...
|
||||
|
||||
# check the help
|
||||
$ github-release --help
|
||||
|
||||
# make your tag and upload
|
||||
$ git tag ... && git push --tags
|
||||
|
||||
# check the current tags and existing releases of the repo
|
||||
$ github-release info -u aktau -r gofinance
|
||||
git tags:
|
||||
- v0.1.0 (commit: https://api.github.com/repos/aktau/gofinance/commits/f562727ce83ce8971a8569a1879219e41d56a756)
|
||||
releases:
|
||||
- v0.1.0, name: 'hoary ungar', description: 'something something dark side 2', id: 166740, tagged: 29/01/2014 at 14:27, published: 30/01/2014 at 16:20, draft: ✔, prerelease: ✗
|
||||
- artifact: github.go, downloads: 0, state: uploaded, type: application/octet-stream, size: 1.9KB, id: 68616
|
||||
|
||||
# create a formal release
|
||||
$ github-release release \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "the wolf of source street" \
|
||||
--description "Not a movie, contrary to popular opinion. Still, my first release!" \
|
||||
--pre-release
|
||||
|
||||
# you've made a mistake, but you can edit the release without
|
||||
# having to delete it first (this also means you can edit without having
|
||||
# to upload your files again)
|
||||
$ github-release edit \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "Highlander II: The Quickening" \
|
||||
--description "This is the actual description!"
|
||||
|
||||
# upload a file, for example the OSX/AMD64 binary of my gofinance app
|
||||
$ github-release upload \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "gofinance-osx-amd64" \
|
||||
--file bin/darwin/amd64/gofinance
|
||||
|
||||
# upload other files...
|
||||
$ github-release upload ...
|
||||
|
||||
# you're not happy with it, so delete it
|
||||
$ github-release delete \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0
|
||||
```
|
||||
|
||||
GitHub Enterprise Support
|
||||
=========================
|
||||
You can point to a different GitHub API endpoint via the environment variable ```GITHUB_API```:
|
||||
|
||||
```
|
||||
export GITHUB_API=http://github.company.com/api/v3
|
||||
```
|
||||
|
||||
Used libraries
|
||||
==============
|
||||
|
||||
| Package | Description | License |
|
||||
| ------------------------------------------------------------------------ | ------------------- | ------- |
|
||||
| [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 |
|
||||
|
||||
Todo
|
||||
====
|
||||
|
||||
- Check if an artifact is already uploaded before starting a new upload
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
Copyright (c) 2014, Nicolas Hillegeer. All rights reserved.
|
||||
32
vendor/github.com/aktau/github-release/assets.go
generated
vendored
Normal file
32
vendor/github.com/aktau/github-release/assets.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ASSET_DOWNLOAD_URI = "/repos/%s/%s/releases/assets/%d"
|
||||
)
|
||||
|
||||
type Asset struct {
|
||||
Url string `json:"url"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContentType string `json:"content_type"`
|
||||
State string `json:"state"`
|
||||
Size uint64 `json:"size"`
|
||||
Downloads uint64 `json:"download_count"`
|
||||
Created time.Time `json:"created_at"`
|
||||
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 {
|
||||
for _, asset := range assets {
|
||||
if asset.Name == name {
|
||||
return asset.Id
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
448
vendor/github.com/aktau/github-release/cmd.go
generated
vendored
Normal file
448
vendor/github.com/aktau/github-release/cmd.go
generated
vendored
Normal file
@@ -0,0 +1,448 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/aktau/github-release/github"
|
||||
)
|
||||
|
||||
func infocmd(opt Options) error {
|
||||
user := nvls(opt.Info.User, EnvUser)
|
||||
repo := nvls(opt.Info.Repo, EnvRepo)
|
||||
token := nvls(opt.Info.Token, EnvToken)
|
||||
tag := opt.Info.Tag
|
||||
|
||||
if user == "" || repo == "" {
|
||||
return fmt.Errorf("user and repo need to be passed as arguments")
|
||||
}
|
||||
|
||||
// Find regular git tags.
|
||||
foundTags, err := Tags(user, repo, token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch tags, %v", err)
|
||||
}
|
||||
if len(foundTags) == 0 {
|
||||
return fmt.Errorf("no tags available for %v/%v", user, repo)
|
||||
}
|
||||
|
||||
tags := foundTags[:0]
|
||||
for _, t := range foundTags {
|
||||
// If the user only requested one tag, filter out the rest.
|
||||
if tag == "" || t.Name == tag {
|
||||
tags = append(tags, t)
|
||||
}
|
||||
}
|
||||
|
||||
renderer := renderInfoText
|
||||
|
||||
if opt.Info.JSON {
|
||||
renderer = renderInfoJSON
|
||||
}
|
||||
|
||||
// List releases + assets.
|
||||
var releases []Release
|
||||
if tag == "" {
|
||||
// Get all releases.
|
||||
vprintf("%v/%v: getting information for all releases\n", user, repo)
|
||||
releases, err = Releases(user, repo, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
releases = []Release{*release}
|
||||
}
|
||||
|
||||
return renderer(tags, releases)
|
||||
}
|
||||
|
||||
func renderInfoText(tags []Tag, releases []Release) error {
|
||||
fmt.Println("tags:")
|
||||
for _, tag := range tags {
|
||||
fmt.Println("-", &tag)
|
||||
}
|
||||
|
||||
fmt.Println("releases:")
|
||||
for _, release := range releases {
|
||||
fmt.Println("-", &release)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderInfoJSON(tags []Tag, releases []Release) error {
|
||||
out := struct {
|
||||
Tags []Tag
|
||||
Releases []Release
|
||||
}{
|
||||
Tags: tags,
|
||||
Releases: releases,
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&out)
|
||||
}
|
||||
|
||||
func uploadcmd(opt Options) error {
|
||||
user := nvls(opt.Upload.User, EnvUser)
|
||||
repo := nvls(opt.Upload.Repo, EnvRepo)
|
||||
token := nvls(opt.Upload.Token, EnvToken)
|
||||
tag := opt.Upload.Tag
|
||||
name := opt.Upload.Name
|
||||
label := opt.Upload.Label
|
||||
file := opt.Upload.File
|
||||
|
||||
vprintln("uploading...")
|
||||
|
||||
if file == nil {
|
||||
return fmt.Errorf("provided file was not valid")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the release corresponding to the entered tag, if any.
|
||||
rel, err := ReleaseOfTag(user, repo, tag, 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)
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", name)
|
||||
if label != "" {
|
||||
v.Set("label", label)
|
||||
}
|
||||
|
||||
url := rel.CleanUploadUrl() + "?" + v.Encode()
|
||||
|
||||
resp, err := github.DoAuthRequest("POST", url, "application/octet-stream",
|
||||
token, nil, file)
|
||||
if err != nil {
|
||||
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",
|
||||
resp.Status, msg)
|
||||
}
|
||||
return fmt.Errorf("could not upload, status code (%v)", 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)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadcmd(opt Options) error {
|
||||
user := nvls(opt.Download.User, EnvUser)
|
||||
repo := nvls(opt.Download.Repo, EnvRepo)
|
||||
token := nvls(opt.Download.Token, EnvToken)
|
||||
tag := opt.Download.Tag
|
||||
name := opt.Download.Name
|
||||
latest := opt.Download.Latest
|
||||
|
||||
vprintln("downloading...")
|
||||
|
||||
if err := ValidateTarget(user, repo, tag, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the release corresponding to the entered tag, if any.
|
||||
var rel *Release
|
||||
var err error
|
||||
if latest {
|
||||
rel, err = LatestRelease(user, repo, token)
|
||||
} else {
|
||||
rel, err = ReleaseOfTag(user, repo, tag, token)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assetID := findAssetID(rel.Assets, name)
|
||||
if assetID == -1 {
|
||||
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.
|
||||
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)
|
||||
resp, err = github.DoAuthRequest("GET", url, "", token, map[string]string{
|
||||
"Accept": "application/octet-stream",
|
||||
}, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch releases, %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
vprintln("GET", resp.Request.URL, "->", resp)
|
||||
|
||||
contentLength, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("github did not respond with 200 OK but with %v", resp.Status)
|
||||
}
|
||||
|
||||
out := os.Stdout // Pipe the asset to stdout by default.
|
||||
if isCharDevice(out) {
|
||||
// If stdout is a char device, assume it's a TTY (terminal). In this
|
||||
// case, don't pipe th easset to stdout, but create it as a file in
|
||||
// the current working folder.
|
||||
if out, err = os.Create(name); err != nil {
|
||||
return fmt.Errorf("could not create file %s", name)
|
||||
}
|
||||
defer out.Close()
|
||||
}
|
||||
|
||||
return mustCopyN(out, resp.Body, contentLength)
|
||||
}
|
||||
|
||||
// mustCopyN attempts to copy exactly N bytes, if this fails, an error is
|
||||
// returned.
|
||||
func mustCopyN(w io.Writer, r io.Reader, n int64) error {
|
||||
an, err := io.Copy(w, r)
|
||||
if an != n {
|
||||
return fmt.Errorf("data did not match content length %d != %d", an, n)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ValidateTarget(user, repo, tag string, latest bool) error {
|
||||
if user == "" {
|
||||
return fmt.Errorf("empty user")
|
||||
}
|
||||
if repo == "" {
|
||||
return fmt.Errorf("empty repo")
|
||||
}
|
||||
if tag == "" && !latest {
|
||||
return fmt.Errorf("empty tag")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateCredentials(user, repo, token, tag string) error {
|
||||
if err := ValidateTarget(user, repo, tag, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if token == "" {
|
||||
return fmt.Errorf("empty token")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func releasecmd(opt Options) error {
|
||||
cmdopt := opt.Release
|
||||
user := nvls(cmdopt.User, EnvUser)
|
||||
repo := nvls(cmdopt.Repo, EnvRepo)
|
||||
token := nvls(cmdopt.Token, EnvToken)
|
||||
tag := cmdopt.Tag
|
||||
name := nvls(cmdopt.Name, tag)
|
||||
desc := nvls(cmdopt.Desc, tag)
|
||||
target := nvls(cmdopt.Target)
|
||||
draft := cmdopt.Draft
|
||||
prerelease := cmdopt.Prerelease
|
||||
|
||||
vprintln("releasing...")
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we need to read the description from stdin.
|
||||
if desc == "-" {
|
||||
b, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read description from stdin: %v", err)
|
||||
}
|
||||
desc = string(b)
|
||||
}
|
||||
|
||||
params := ReleaseCreate{
|
||||
TagName: tag,
|
||||
TargetCommitish: target,
|
||||
Name: name,
|
||||
Body: desc,
|
||||
Draft: draft,
|
||||
Prerelease: prerelease,
|
||||
}
|
||||
|
||||
/* encode params as json */
|
||||
payload, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't encode release creation params, %v", err)
|
||||
}
|
||||
reader := bytes.NewReader(payload)
|
||||
|
||||
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf("/repos/%s/%s/releases", user, repo)
|
||||
resp, err := github.DoAuthRequest("POST", URL, "application/json", token, nil, reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while submitting %v, %v", string(payload), err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
vprintln("RESPONSE:", resp)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
if resp.StatusCode == 422 {
|
||||
return fmt.Errorf("github returned %v (this is probably because the release already exists)",
|
||||
resp.Status)
|
||||
}
|
||||
return fmt.Errorf("github returned %v", resp.Status)
|
||||
}
|
||||
|
||||
if VERBOSITY != 0 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading response, %v", err)
|
||||
}
|
||||
vprintln("BODY:", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func editcmd(opt Options) error {
|
||||
cmdopt := opt.Edit
|
||||
user := nvls(cmdopt.User, EnvUser)
|
||||
repo := nvls(cmdopt.Repo, EnvRepo)
|
||||
token := nvls(cmdopt.Token, EnvToken)
|
||||
tag := cmdopt.Tag
|
||||
name := nvls(cmdopt.Name, tag)
|
||||
desc := nvls(cmdopt.Desc, tag)
|
||||
draft := cmdopt.Draft
|
||||
prerelease := cmdopt.Prerelease
|
||||
|
||||
vprintln("editing...")
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := IdOfTag(user, repo, tag, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vprintf("release %v has id %v\n", tag, id)
|
||||
|
||||
// Check if we need to read the description from stdin.
|
||||
if desc == "-" {
|
||||
b, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read description from stdin: %v", err)
|
||||
}
|
||||
desc = string(b)
|
||||
}
|
||||
|
||||
/* the release create struct works for editing releases as well */
|
||||
params := ReleaseCreate{
|
||||
TagName: tag,
|
||||
Name: name,
|
||||
Body: desc,
|
||||
Draft: draft,
|
||||
Prerelease: prerelease,
|
||||
}
|
||||
|
||||
/* encode the parameters as JSON, as required by the github API */
|
||||
payload, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't encode release creation params, %v", err)
|
||||
}
|
||||
|
||||
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id)
|
||||
resp, err := github.DoAuthRequest("PATCH", URL, "application/json", token, nil, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("while submitting %v, %v", string(payload), err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
vprintln("RESPONSE:", resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == 422 {
|
||||
return fmt.Errorf("github returned %v (this is probably because the release already exists)",
|
||||
resp.Status)
|
||||
}
|
||||
return fmt.Errorf("github returned unexpected status code %v", resp.Status)
|
||||
}
|
||||
|
||||
if VERBOSITY != 0 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading response, %v", err)
|
||||
}
|
||||
vprintln("BODY:", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deletecmd(opt Options) error {
|
||||
user, repo, token, tag := nvls(opt.Delete.User, EnvUser),
|
||||
nvls(opt.Delete.Repo, EnvRepo),
|
||||
nvls(opt.Delete.Token, EnvToken),
|
||||
opt.Delete.Tag
|
||||
vprintln("deleting...")
|
||||
|
||||
id, err := IdOfTag(user, repo, tag, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vprintf("release %v has id %v\n", tag, id)
|
||||
|
||||
baseURL := nvls(EnvApiEndpoint, github.DefaultBaseURL)
|
||||
resp, err := github.DoAuthRequest("DELETE", baseURL+fmt.Sprintf("/repos/%s/%s/releases/%d",
|
||||
user, repo, id), "application/json", token, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("release deletion failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("could not delete the release corresponding to tag %s on repo %s/%s",
|
||||
tag, user, repo)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
vendor/github.com/aktau/github-release/commit.go
generated
vendored
Normal file
6
vendor/github.com/aktau/github-release/commit.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
type Commit struct {
|
||||
Sha string `json:"sha"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
42
vendor/github.com/aktau/github-release/error.go
generated
vendored
Normal file
42
vendor/github.com/aktau/github-release/error.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* usually when something goes wrong, github sends something like this back */
|
||||
type Message struct {
|
||||
Message string `json:"message"`
|
||||
Errors []GithubError `json:"errors"`
|
||||
}
|
||||
|
||||
type GithubError struct {
|
||||
Resource string `json:"resource"`
|
||||
Code string `json:"code"`
|
||||
Field string `json:"field"`
|
||||
}
|
||||
|
||||
/* transforms a stream into a Message, if it's valid json */
|
||||
func ToMessage(r io.Reader) (*Message, error) {
|
||||
var msg Message
|
||||
if err := json.NewDecoder(r).Decode(&msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func (m *Message) String() string {
|
||||
str := fmt.Sprintf("msg: %v, errors: ", m.Message)
|
||||
|
||||
errstr := make([]string, len(m.Errors))
|
||||
for idx, err := range m.Errors {
|
||||
errstr[idx] = fmt.Sprintf("[field: %v, code: %v]",
|
||||
err.Field, err.Code)
|
||||
}
|
||||
|
||||
return str + strings.Join(errstr, ", ")
|
||||
}
|
||||
130
vendor/github.com/aktau/github-release/github-release.go
generated
vendored
Normal file
130
vendor/github.com/aktau/github-release/github-release.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aktau/github-release/github"
|
||||
"github.com/voxelbrain/goptions"
|
||||
)
|
||||
|
||||
const GH_URL = "https://github.com"
|
||||
|
||||
type Options struct {
|
||||
Help goptions.Help `goptions:"-h, --help, description='Show this help'"`
|
||||
Verbosity []bool `goptions:"-v, --verbose, description='Be verbose'"`
|
||||
Quiet bool `goptions:"-q, --quiet, description='Do not print anything, even errors (except if --verbose is specified)'"`
|
||||
Version bool `goptions:"--version, description='Print version'"`
|
||||
|
||||
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"`
|
||||
} `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)'"`
|
||||
} `goptions:"upload"`
|
||||
Release 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 to create a release from'"`
|
||||
Name string `goptions:"-n, --name, description='Name of the release (defaults to tag)'"`
|
||||
Desc string `goptions:"-d, --description, description='Release description, use - for reading a description from stdin (defaults to tag)'"`
|
||||
Target string `goptions:"-c, --target, description='Commit SHA or branch to create release of (defaults to the repository default branch)'"`
|
||||
Draft bool `goptions:"--draft, description='The release is a draft'"`
|
||||
Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"`
|
||||
} `goptions:"release"`
|
||||
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)'"`
|
||||
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)'"`
|
||||
Desc string `goptions:"-d, --description, description='New release description, use - for reading a description from stdin (defaults to tag)'"`
|
||||
Draft bool `goptions:"--draft, description='The release is a draft'"`
|
||||
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'"`
|
||||
} `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'"`
|
||||
} `goptions:"info"`
|
||||
}
|
||||
|
||||
type Command func(Options) error
|
||||
|
||||
var commands = map[goptions.Verbs]Command{
|
||||
"download": downloadcmd,
|
||||
"upload": uploadcmd,
|
||||
"release": releasecmd,
|
||||
"edit": editcmd,
|
||||
"delete": deletecmd,
|
||||
"info": infocmd,
|
||||
}
|
||||
|
||||
var (
|
||||
VERBOSITY = 0
|
||||
)
|
||||
|
||||
var (
|
||||
EnvToken string
|
||||
EnvUser string
|
||||
EnvRepo string
|
||||
EnvApiEndpoint string
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnvToken = os.Getenv("GITHUB_TOKEN")
|
||||
EnvUser = os.Getenv("GITHUB_USER")
|
||||
EnvRepo = os.Getenv("GITHUB_REPO")
|
||||
EnvApiEndpoint = os.Getenv("GITHUB_API")
|
||||
}
|
||||
|
||||
func main() {
|
||||
options := Options{}
|
||||
|
||||
goptions.ParseAndFail(&options)
|
||||
|
||||
if options.Version {
|
||||
fmt.Printf("github-release v%s\n", VERSION)
|
||||
return
|
||||
}
|
||||
|
||||
if len(options.Verbs) == 0 {
|
||||
goptions.PrintHelp()
|
||||
return
|
||||
}
|
||||
|
||||
VERBOSITY = len(options.Verbosity)
|
||||
github.VERBOSITY = VERBOSITY
|
||||
|
||||
if cmd, found := commands[options.Verbs]; found {
|
||||
err := cmd(options)
|
||||
if err != nil {
|
||||
if !options.Quiet {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
vendor/github.com/aktau/github-release/github/debug.go
generated
vendored
Normal file
22
vendor/github.com/aktau/github-release/github/debug.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
||||
}
|
||||
79
vendor/github.com/aktau/github-release/github/file.go
generated
vendored
Normal file
79
vendor/github.com/aktau/github-release/github/file.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
277
vendor/github.com/aktau/github-release/github/github.go
generated
vendored
Normal file
277
vendor/github.com/aktau/github-release/github/github.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
// 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 ""
|
||||
}
|
||||
137
vendor/github.com/aktau/github-release/releases.go
generated
vendored
Normal file
137
vendor/github.com/aktau/github-release/releases.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aktau/github-release/github"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
const (
|
||||
RELEASE_LIST_URI = "/repos/%s/%s/releases"
|
||||
RELEASE_LATEST_URI = "/repos/%s/%s/releases/latest"
|
||||
RELEASE_DATE_FORMAT = "02/01/2006 at 15:04"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Url string `json:"url"`
|
||||
PageUrl string `json:"html_url"`
|
||||
UploadUrl string `json:"upload_url"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"body"`
|
||||
TagName string `json:"tag_name"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
Created *time.Time `json:"created_at"`
|
||||
Published *time.Time `json:"published_at"`
|
||||
Assets []Asset `json:"assets"`
|
||||
}
|
||||
|
||||
func (r *Release) CleanUploadUrl() string {
|
||||
bracket := strings.Index(r.UploadUrl, "{")
|
||||
|
||||
if bracket == -1 {
|
||||
return r.UploadUrl
|
||||
}
|
||||
|
||||
return r.UploadUrl[0:bracket]
|
||||
}
|
||||
|
||||
func (r *Release) String() string {
|
||||
str := make([]string, len(r.Assets)+1)
|
||||
str[0] = fmt.Sprintf(
|
||||
"%s, name: '%s', description: '%s', id: %d, tagged: %s, published: %s, draft: %v, prerelease: %v",
|
||||
r.TagName, r.Name, r.Description, r.Id,
|
||||
timeFmtOr(r.Created, RELEASE_DATE_FORMAT, ""),
|
||||
timeFmtOr(r.Published, RELEASE_DATE_FORMAT, ""),
|
||||
Mark(r.Draft), Mark(r.Prerelease))
|
||||
|
||||
for idx, asset := range r.Assets {
|
||||
str[idx+1] = fmt.Sprintf(" - artifact: %s, downloads: %d, state: %s, type: %s, size: %s, id: %d",
|
||||
asset.Name, asset.Downloads, asset.State, asset.ContentType,
|
||||
humanize.Bytes(asset.Size), asset.Id)
|
||||
}
|
||||
|
||||
return strings.Join(str, "\n")
|
||||
}
|
||||
|
||||
type ReleaseCreate struct {
|
||||
TagName string `json:"tag_name"`
|
||||
TargetCommitish string `json:"target_commitish,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Body string `json:"body"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
}
|
||||
|
||||
func Releases(user, repo, token string) ([]Release, error) {
|
||||
var releases []Release
|
||||
err := github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(RELEASE_LIST_URI, user, repo), &releases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func latestReleaseApi(user, repo, token string) (*Release, error) {
|
||||
var release Release
|
||||
return &release, github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(RELEASE_LATEST_URI, user, repo), &release)
|
||||
}
|
||||
|
||||
func LatestRelease(user, repo, token string) (*Release, error) {
|
||||
// If latestReleaseApi DOESN'T give an error, return the release.
|
||||
if latestRelease, err := latestReleaseApi(user, repo, 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var latestRelIndex = -1
|
||||
maxDate := time.Time{}
|
||||
for i, release := range releases {
|
||||
if relDate := *release.Published; relDate.After(maxDate) {
|
||||
maxDate = relDate
|
||||
latestRelIndex = i
|
||||
}
|
||||
}
|
||||
if latestRelIndex == -1 {
|
||||
return nil, fmt.Errorf("could not find the latest release")
|
||||
}
|
||||
|
||||
vprintln("Scanning ", len(releases), "releases, latest release is", releases[latestRelIndex])
|
||||
return &releases[latestRelIndex], nil
|
||||
}
|
||||
|
||||
func ReleaseOfTag(user, repo, tag, token string) (*Release, error) {
|
||||
releases, err := Releases(user, repo, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
if release.TagName == tag {
|
||||
return &release, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find the release corresponding to tag %s", tag)
|
||||
}
|
||||
|
||||
/* find the release-id of the specified tag */
|
||||
func IdOfTag(user, repo, tag, token string) (int, error) {
|
||||
release, err := ReleaseOfTag(user, repo, tag, token)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return release.Id, nil
|
||||
}
|
||||
28
vendor/github.com/aktau/github-release/tags.go
generated
vendored
Normal file
28
vendor/github.com/aktau/github-release/tags.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aktau/github-release/github"
|
||||
)
|
||||
|
||||
const (
|
||||
TAGS_URI = "/repos/%s/%s/tags"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Commit Commit `json:"commit"`
|
||||
ZipBallUrl string `json:"zipball_url"`
|
||||
TarBallUrl string `json:"tarball_url"`
|
||||
}
|
||||
|
||||
func (t *Tag) String() string {
|
||||
return t.Name + " (commit: " + t.Commit.Url + ")"
|
||||
}
|
||||
|
||||
// Get the tags associated with a repo.
|
||||
func Tags(user, repo, token string) ([]Tag, error) {
|
||||
var tags []Tag
|
||||
return tags, github.Client{Token: token, BaseURL: EnvApiEndpoint}.Get(fmt.Sprintf(TAGS_URI, user, repo), &tags)
|
||||
}
|
||||
9
vendor/github.com/aktau/github-release/term.go
generated
vendored
Normal file
9
vendor/github.com/aktau/github-release/term.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
func Mark(ok bool) string {
|
||||
if ok {
|
||||
return "✔"
|
||||
} else {
|
||||
return "✗"
|
||||
}
|
||||
}
|
||||
52
vendor/github.com/aktau/github-release/util.go
generated
vendored
Normal file
52
vendor/github.com/aktau/github-release/util.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
/* oracle nvl, return first non-empty string */
|
||||
func nvls(xs ...string) string {
|
||||
for _, s := range xs {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// formats time `t` as `fmt` if it is not nil, otherwise returns `def`
|
||||
func timeFmtOr(t *time.Time, fmt, def string) string {
|
||||
if t == nil {
|
||||
return def
|
||||
}
|
||||
return t.Format(fmt)
|
||||
}
|
||||
|
||||
// isCharDevice returns true if f is a character device (panics if f can't
|
||||
// be stat'ed).
|
||||
func isCharDevice(f *os.File) bool {
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return (stat.Mode() & os.ModeCharDevice) != 0
|
||||
}
|
||||
3
vendor/github.com/aktau/github-release/version.go
generated
vendored
Normal file
3
vendor/github.com/aktau/github-release/version.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
const VERSION = "0.7.2"
|
||||
Reference in New Issue
Block a user