├── .circleci └── config.yml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── SPONSORS ├── bash └── gh-release.bash ├── bindata.go ├── gh-release.go ├── go.mod ├── go.sum └── tests └── test.bats /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | workflows: 4 | version: 2 5 | build: 6 | jobs: 7 | - build 8 | 9 | jobs: 10 | build: 11 | docker: 12 | - image: alpine:latest 13 | steps: 14 | - run: 15 | name: noop 16 | command: echo true 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - 'master' 10 | - 'release' 11 | 12 | jobs: 13 | build: 14 | name: build 15 | runs-on: ubuntu-20.04 16 | container: 17 | image: golang:1.15.7 18 | env: 19 | GOPATH: /go 20 | strategy: 21 | fail-fast: true 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - run: env 26 | - run: make deps 27 | - run: make build 28 | - name: install bats 29 | run: | 30 | git clone https://github.com/bats-core/bats-core.git /tmp/bats-core 31 | cd /tmp/bats-core 32 | ./install.sh /usr/local 33 | - run: make test 34 | env: 35 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | - name: upload packages 37 | uses: actions/upload-artifact@v2 38 | with: 39 | name: build 40 | path: build/**/* 41 | - name: make release 42 | run: | 43 | test "${GITHUB_REF#refs/heads/}" != "release" || make release 44 | env: 45 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | release 2 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Jeff Lindsay 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=gh-release 2 | ARCH=$(shell uname -m) 3 | VERSION=2.3.3 4 | 5 | build: 6 | go-bindata bash 7 | mkdir -p build/Linux && GOOS=linux go build -ldflags "-X main.Version=$(VERSION)" -o build/Linux/$(NAME) 8 | mkdir -p build/Darwin && GOOS=darwin go build -ldflags "-X main.Version=$(VERSION)" -o build/Darwin/$(NAME) 9 | 10 | deps: 11 | go get -u -f github.com/a-urth/go-bindata/... 12 | go get || true 13 | 14 | test: build 15 | bats tests/*.bats 16 | 17 | release: build 18 | rm -rf release && mkdir release 19 | tar -zcf release/$(NAME)_$(VERSION)_linux_$(ARCH).tgz -C build/Linux $(NAME) 20 | tar -zcf release/$(NAME)_$(VERSION)_darwin_$(ARCH).tgz -C build/Darwin $(NAME) 21 | build/$(shell uname)/gh-release create progrium/$(NAME) $(VERSION) $(shell git rev-parse --abbrev-ref HEAD) 22 | 23 | .PHONY: release build 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gh-release 2 | 3 | Utility for creating, deleting, and uploading files to Github Releases. 4 | 5 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/progrium/gh-release/CI) 6 | 7 | ## Getting gh-release 8 | 9 | Download and uncompress the appropriate binary from [releases](https://github.com/progrium/gh-release/releases). 10 | 11 | ## Using gh-release 12 | 13 | You need to have a [Github personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use) defined in your environment as `GITHUB_ACCESS_TOKEN`. 14 | 15 | $ gh-release 16 | Usage: gh-release create|destroy [branch] [name] 17 | 18 | #### Creating a release with assets 19 | 20 | Put any assets you want to upload with your release into a `release` directory. Then call `gh-release`. Here is an example: 21 | 22 | $ gh-release create progrium/gh-release 1.0.0 23 | 24 | This will create a tagged release `v1.0.0` then upload any files found in the `./release` directory. Optional arguments you can pass include the branch to tag the release from and a name for the release. 25 | 26 | See this project's Makefile for a real example of using it in a Makefile. 27 | 28 | #### Destroying a release 29 | 30 | You can destroy a release by the version number you used to create the release: 31 | 32 | $ gh-release destroy progrium/gh-release 1.0.0 33 | 34 | This destroys the release and its assets, as well as the `v1.0.0` tag created for the release. 35 | 36 | ## Sponsor 37 | 38 | This project was made possible thanks to [DigitalOcean](http://digitalocean.com). 39 | 40 | ## License 41 | 42 | BSD 43 | -------------------------------------------------------------------------------- /SPONSORS: -------------------------------------------------------------------------------- 1 | DigitalOcean http://digitalocean.com -------------------------------------------------------------------------------- /bash/gh-release.bash: -------------------------------------------------------------------------------- 1 | 2 | readonly ref_endpoint="${GITHUB_API_URL:-https://api.github.com}/repos/%s/git/refs/tags/%s" 3 | readonly release_endpoint="${GITHUB_API_URL:-https://api.github.com}/repos/%s/releases" 4 | readonly release_json='{"tag_name": "v%s", "name": "%s", "target_commitish": "%s"}' 5 | 6 | release-create() { 7 | declare reponame="$1" version="${2#v}" branch="${3:-master}" name="$4" 8 | local release="$(printf "$release_json" "$version" "$name" "$branch")" 9 | local release_url="$(printf "$release_endpoint" "$reponame")" 10 | local upload_url 11 | echo "Creating release v$version from branch $branch ..." 12 | if [[ -n "$DEBUG" ]]; then 13 | data="$(curl -H "Authorization: token $GITHUB_ACCESS_TOKEN" -d "$release" "$release_url")" 14 | echo "data: $release" 15 | echo "release_url: $release_url" 16 | echo "response: $data" 17 | upload_url="$(echo "$data" | upload-url)" 18 | else 19 | upload_url="$(curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" -d "$release" "$release_url" | upload-url)" 20 | fi 21 | for asset in $(ls -A release); do 22 | local name="$(basename $asset)" 23 | echo "Uploading $name ..." 24 | curl -X POST -H "Content-Type: $(mimetype $name)" -H "Authorization: token $GITHUB_ACCESS_TOKEN" --data-binary "@release/$asset" \ 25 | "$upload_url=$name" > /dev/null 26 | done 27 | } 28 | 29 | release-destroy() { 30 | declare reponame="$1" version="$2" 31 | local release_url="$(printf "$release_endpoint" "$reponame")" 32 | 33 | [[ "$version" == [0-9]* ]] && version="v$version" 34 | 35 | release_id="$(curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" "$release_url" | release-id-from-tagname "$version")" 36 | echo "Deleting release..." 37 | curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" -X DELETE "$release_url/$release_id" 38 | echo "Deleting tag..." 39 | tag_url="$(printf "$ref_endpoint" "$reponame" "$version")" 40 | curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" -X DELETE "$tag_url" 41 | } 42 | 43 | usage() { 44 | echo "Usage: gh-release [-v] subcommand" 45 | echo 46 | echo "Subcommands:" 47 | echo " create [branch] [name]" 48 | echo " destroy " 49 | echo " checksums " 50 | echo 51 | } 52 | 53 | release-checksums() { 54 | declare alg="$1" 55 | echo "Writing $alg checksum files..." 56 | for asset in $(ls -A release); do 57 | cat "release/$asset" | checksum "$alg" > "release/${asset}.$alg" 58 | done 59 | } 60 | 61 | main() { 62 | set -eo pipefail; [[ "$TRACE" ]] && set -x 63 | case "$1" in 64 | create) shift; release-create "$@";; 65 | destroy) shift; release-destroy "$@";; 66 | checksums) shift; release-checksums "$@";; 67 | -v) echo "$VERSION";; 68 | *) usage;; 69 | esac 70 | } 71 | -------------------------------------------------------------------------------- /bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // bash/gh-release.bash 4 | // DO NOT EDIT! 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "compress/gzip" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func bindataRead(data []byte, name string) ([]byte, error) { 21 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 22 | if err != nil { 23 | return nil, fmt.Errorf("Read %q: %v", name, err) 24 | } 25 | 26 | var buf bytes.Buffer 27 | _, err = io.Copy(&buf, gz) 28 | clErr := gz.Close() 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("Read %q: %v", name, err) 32 | } 33 | if clErr != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | type asset struct { 41 | bytes []byte 42 | info os.FileInfo 43 | } 44 | 45 | type bindataFileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | modTime time.Time 50 | } 51 | 52 | func (fi bindataFileInfo) Name() string { 53 | return fi.name 54 | } 55 | func (fi bindataFileInfo) Size() int64 { 56 | return fi.size 57 | } 58 | func (fi bindataFileInfo) Mode() os.FileMode { 59 | return fi.mode 60 | } 61 | func (fi bindataFileInfo) ModTime() time.Time { 62 | return fi.modTime 63 | } 64 | func (fi bindataFileInfo) IsDir() bool { 65 | return false 66 | } 67 | func (fi bindataFileInfo) Sys() interface{} { 68 | return nil 69 | } 70 | 71 | var _bashGhReleaseBash = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x55\xdf\x6f\xdb\x36\x10\x7e\x96\xfe\x8a\xc3\x4d\x6d\xed\x61\xb2\xb0\x6e\x4f\x76\x12\x34\x73\x8d\x35\x58\xd1\x14\xb1\xb3\x15\xf0\x0c\x83\x96\x28\x89\x2b\x45\x0a\x24\x65\xcc\x4b\xfd\xbf\x0f\x94\x48\xc9\xf9\xf1\xd0\x2d\x7d\xb3\xc8\xbb\xef\xbe\xef\xee\xe3\x39\x54\x94\x64\x52\xf0\x03\x28\x9a\x6f\xa9\xc8\x6a\xc9\x84\x39\xc7\xe8\xee\xd7\xab\xd5\xbb\xdb\x5f\xb6\x97\x1f\xaf\xb6\xb7\x37\xef\xa7\x71\x69\x4c\xad\xa7\x49\x42\x6a\x36\x29\x98\x29\x9b\xdd\x24\x95\xd5\x31\x51\xb4\x96\x3a\x79\xa1\x93\x82\x99\x44\xd1\x5c\x27\x86\x14\xf6\x00\x4f\xb1\x39\x25\x9a\x3e\x0f\xdf\x81\x3c\x85\xfb\x97\x96\xe2\xfc\xd5\x1d\x1a\x52\x6c\x05\xa9\x28\x4e\x01\xf7\x2f\x34\xfe\x00\xe8\x3f\xbb\x2f\x43\x54\x41\xcd\x36\x95\x55\xc5\x0c\xd3\xa5\xbb\x39\xbe\x0a\x43\x87\x15\xa7\x8a\x12\x43\x47\x63\xb8\x0b\x83\x8c\xa6\x9c\x28\x0a\x96\x83\x05\x3a\xc7\xe8\x47\x84\x3d\x55\x9a\x49\x61\x45\xbc\xfe\x6e\x7f\x44\xd8\x29\x22\xd2\xd2\x7e\xff\x34\x8d\x2b\xa2\x0d\x55\x47\x04\x97\xf0\x33\x86\x01\x97\x29\xe1\x9e\xed\x39\x46\xa3\x5a\x31\x61\x72\xc0\xe8\x54\x01\x02\x46\x0e\xdb\xfe\x6c\x99\x03\x46\x1d\x3a\x8e\x1f\xe2\x6c\x1b\xc5\x9f\xc4\xf2\x5d\xc6\xf6\xac\x63\xde\xa6\xd3\xb4\x94\x80\x73\x2b\x90\x89\xc2\xe3\xc0\xde\x57\x85\x5c\xc9\xca\xa9\x01\x57\x17\x26\x93\x09\x86\x41\x53\x73\x49\x32\x5f\x32\x6d\x14\x87\x58\x43\xfc\x0e\xf0\xb2\x31\xa5\x54\xec\x1f\x62\x98\x14\x53\x30\xf2\x33\x15\x10\xf9\xe9\xce\xe7\x8b\xe5\x72\xbb\xba\xfe\x6d\xf1\x01\x21\xce\x06\x96\x78\x42\xb8\x51\x1c\xe1\x0b\x74\x35\xe2\x46\x71\x4b\x36\x97\x0a\x88\xd6\xd4\x00\x13\x10\x8d\xb8\x86\xf8\xd2\x53\x1e\xcf\x20\x93\x61\xe0\xfa\xe1\x1a\x3d\xda\x11\x4d\xed\x6f\x88\xda\x3c\x0b\xe2\x24\xdf\xb6\xc8\x56\x73\xdb\x55\xa7\x29\xe8\x64\x7c\x82\x8f\xd7\xcb\x55\xab\x65\x2e\x85\xa1\xc2\xc4\xab\x43\x4d\xa7\x10\x8d\x2a\x56\x51\x73\xa8\x69\x97\x36\xc6\xff\x2c\x38\xce\x88\x21\xf1\x8e\x09\xa2\x0e\x80\x6f\x1c\xfd\xa4\x23\x88\xf0\x67\x18\x04\x01\x46\x27\xcd\x75\x53\xbf\x80\x24\xa3\xfb\x44\x34\x9c\x87\x41\x26\x05\x0d\x8f\x83\x43\x33\xaa\x8d\x92\x87\xaf\xb3\xe8\xeb\x67\xdb\x26\x0c\x7a\xb4\xde\x29\xf6\xd4\xe7\xb1\xec\x19\x96\x78\x64\x02\x2f\x92\x65\xb1\x75\x63\x6c\x48\xd1\x8e\x6c\x78\x1a\x83\x93\xdf\x52\x4e\x4f\x9d\xdc\x8d\xf5\xff\x9a\xf3\x13\xbc\x5d\xbc\x5f\xac\x16\xf7\x39\x25\xd1\xa0\xf3\x71\x61\x43\x8a\xae\xa8\xdd\x3c\x8f\x1b\x9b\x3f\xdd\xd4\x07\x62\xbe\x01\x61\x57\x1e\xad\x4d\x1a\x4d\x0a\xb7\xbf\x9c\xfb\xed\xc1\x14\x8a\x32\xf6\x2f\x7e\x1d\xef\x37\xa0\x9b\x9d\xdd\x83\x44\x78\x5d\x3e\x7e\xd9\x5f\xe8\x69\x2f\x19\xa0\x5b\x8c\x70\xe6\x65\x5c\xc0\x99\x53\x71\x01\xeb\x6e\x59\x6c\x60\x6d\x6f\x36\x27\x59\xce\xac\x4f\xa6\x9d\x82\x97\x34\xfd\xac\x9b\x4a\xc3\x19\xe1\x85\x54\xcc\x94\x95\xbf\x3f\xf5\x7e\x1f\x77\xdf\xfd\x84\x17\xad\xf1\x3d\xe0\x1f\x8a\xb5\xf3\x89\x08\x2f\x7a\x6c\xc8\x19\xa7\xba\x9b\xd7\xd7\x6c\x97\x94\x18\xc0\x87\x4f\xf6\xcb\x00\x87\x16\xdd\xbe\xd5\x21\xe8\xae\x8d\x3a\x4e\xda\x9b\xe1\xe5\x56\x84\x89\x8e\xb0\xad\x18\x53\x09\x35\xab\x69\x4e\x18\x9f\xc1\x7a\x0d\x18\xad\x6e\x2e\xe7\x0b\x84\xcd\x06\x5e\xbe\x84\x36\xe6\xef\x30\x48\xed\xa8\xda\xe7\xcc\x84\xa5\xd3\xf6\x7f\x1c\x04\xba\x64\xb9\x99\xc1\xfd\x3f\x2c\xc0\xe8\x0d\xce\x66\x61\x10\xb8\x8e\x8f\x1f\xc6\xf9\x49\xf4\x81\x7d\x2f\x1f\x85\x0e\xd3\xe8\x83\xe3\xfd\x38\xf0\xeb\x34\xfa\x7d\x71\xb3\xbc\xba\xfe\xd0\xdd\x7c\x6f\x2f\x5a\xd3\xd9\x4f\xaa\x49\x1a\x1e\xc3\x7f\x03\x00\x00\xff\xff\x83\xc5\xca\x92\x58\x08\x00\x00") 72 | 73 | func bashGhReleaseBashBytes() ([]byte, error) { 74 | return bindataRead( 75 | _bashGhReleaseBash, 76 | "bash/gh-release.bash", 77 | ) 78 | } 79 | 80 | func bashGhReleaseBash() (*asset, error) { 81 | bytes, err := bashGhReleaseBashBytes() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | info := bindataFileInfo{name: "bash/gh-release.bash", size: 2136, mode: os.FileMode(493), modTime: time.Unix(1612688600, 0)} 87 | a := &asset{bytes: bytes, info: info} 88 | return a, nil 89 | } 90 | 91 | // Asset loads and returns the asset for the given name. 92 | // It returns an error if the asset could not be found or 93 | // could not be loaded. 94 | func Asset(name string) ([]byte, error) { 95 | cannonicalName := strings.Replace(name, "\\", "/", -1) 96 | if f, ok := _bindata[cannonicalName]; ok { 97 | a, err := f() 98 | if err != nil { 99 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 100 | } 101 | return a.bytes, nil 102 | } 103 | return nil, fmt.Errorf("Asset %s not found", name) 104 | } 105 | 106 | // MustAsset is like Asset but panics when Asset would return an error. 107 | // It simplifies safe initialization of global variables. 108 | func MustAsset(name string) []byte { 109 | a, err := Asset(name) 110 | if err != nil { 111 | panic("asset: Asset(" + name + "): " + err.Error()) 112 | } 113 | 114 | return a 115 | } 116 | 117 | // AssetInfo loads and returns the asset info for the given name. 118 | // It returns an error if the asset could not be found or 119 | // could not be loaded. 120 | func AssetInfo(name string) (os.FileInfo, error) { 121 | cannonicalName := strings.Replace(name, "\\", "/", -1) 122 | if f, ok := _bindata[cannonicalName]; ok { 123 | a, err := f() 124 | if err != nil { 125 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 126 | } 127 | return a.info, nil 128 | } 129 | return nil, fmt.Errorf("AssetInfo %s not found", name) 130 | } 131 | 132 | // AssetNames returns the names of the assets. 133 | func AssetNames() []string { 134 | names := make([]string, 0, len(_bindata)) 135 | for name := range _bindata { 136 | names = append(names, name) 137 | } 138 | return names 139 | } 140 | 141 | // _bindata is a table, holding each asset generator, mapped to its name. 142 | var _bindata = map[string]func() (*asset, error){ 143 | "bash/gh-release.bash": bashGhReleaseBash, 144 | } 145 | 146 | // AssetDir returns the file names below a certain 147 | // directory embedded in the file by go-bindata. 148 | // For example if you run go-bindata on data/... and data contains the 149 | // following hierarchy: 150 | // data/ 151 | // foo.txt 152 | // img/ 153 | // a.png 154 | // b.png 155 | // then AssetDir("data") would return []string{"foo.txt", "img"} 156 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 157 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 158 | // AssetDir("") will return []string{"data"}. 159 | func AssetDir(name string) ([]string, error) { 160 | node := _bintree 161 | if len(name) != 0 { 162 | cannonicalName := strings.Replace(name, "\\", "/", -1) 163 | pathList := strings.Split(cannonicalName, "/") 164 | for _, p := range pathList { 165 | node = node.Children[p] 166 | if node == nil { 167 | return nil, fmt.Errorf("Asset %s not found", name) 168 | } 169 | } 170 | } 171 | if node.Func != nil { 172 | return nil, fmt.Errorf("Asset %s not found", name) 173 | } 174 | rv := make([]string, 0, len(node.Children)) 175 | for childName := range node.Children { 176 | rv = append(rv, childName) 177 | } 178 | return rv, nil 179 | } 180 | 181 | type bintree struct { 182 | Func func() (*asset, error) 183 | Children map[string]*bintree 184 | } 185 | var _bintree = &bintree{nil, map[string]*bintree{ 186 | "bash": &bintree{nil, map[string]*bintree{ 187 | "gh-release.bash": &bintree{bashGhReleaseBash, map[string]*bintree{}}, 188 | }}, 189 | }} 190 | 191 | // RestoreAsset restores an asset under the given directory 192 | func RestoreAsset(dir, name string) error { 193 | data, err := Asset(name) 194 | if err != nil { 195 | return err 196 | } 197 | info, err := AssetInfo(name) 198 | if err != nil { 199 | return err 200 | } 201 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 202 | if err != nil { 203 | return err 204 | } 205 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 206 | if err != nil { 207 | return err 208 | } 209 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 210 | if err != nil { 211 | return err 212 | } 213 | return nil 214 | } 215 | 216 | // RestoreAssets restores an asset under the given directory recursively 217 | func RestoreAssets(dir, name string) error { 218 | children, err := AssetDir(name) 219 | // File 220 | if err != nil { 221 | return RestoreAsset(dir, name) 222 | } 223 | // Dir 224 | for _, child := range children { 225 | err = RestoreAssets(dir, filepath.Join(name, child)) 226 | if err != nil { 227 | return err 228 | } 229 | } 230 | return nil 231 | } 232 | 233 | func _filePath(dir, name string) string { 234 | cannonicalName := strings.Replace(name, "\\", "/", -1) 235 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 236 | } 237 | 238 | -------------------------------------------------------------------------------- /gh-release.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "encoding/json" 8 | "fmt" 9 | "hash" 10 | "io" 11 | "io/ioutil" 12 | "mime" 13 | "os" 14 | "path/filepath" 15 | "strconv" 16 | "strings" 17 | 18 | "github.com/progrium/go-basher" 19 | ) 20 | 21 | var Version string 22 | 23 | func assert(err error) { 24 | if err != nil { 25 | println("!!", err.Error()) 26 | os.Exit(10) 27 | } 28 | } 29 | 30 | func fatal(msg string) { 31 | println("!!", msg) 32 | os.Exit(11) 33 | } 34 | 35 | func UploadUrl(args []string) { 36 | bytes, err := ioutil.ReadAll(os.Stdin) 37 | assert(err) 38 | var release map[string]interface{} 39 | assert(json.Unmarshal(bytes, &release)) 40 | url, ok := release["upload_url"].(string) 41 | if !ok { 42 | println("!! could not find upload_url") 43 | if os.Getenv("DEBUG") != "" { 44 | fmt.Printf("!! response: %s\n", string(bytes)) 45 | } 46 | os.Exit(12) 47 | } 48 | i := strings.Index(url, "{") 49 | if i > -1 { 50 | url = url[:i] 51 | } 52 | i = strings.Index(url, "?") 53 | if i > -1 { 54 | url = url[:i] 55 | } 56 | fmt.Println(url + "?name") 57 | } 58 | 59 | func ReleaseIdFromTagname(args []string) { 60 | tagname := args[0] 61 | bytes, err := ioutil.ReadAll(os.Stdin) 62 | assert(err) 63 | var releases []map[string]interface{} 64 | assert(json.Unmarshal(bytes, &releases)) 65 | for _, release := range releases { 66 | if release["tag_name"].(string) == tagname { 67 | fmt.Println(strconv.Itoa(int(release["id"].(float64)))) 68 | return 69 | } 70 | } 71 | println(fmt.Sprintf("!! no tag_name matching %s found in releases", tagname)) 72 | os.Exit(13) 73 | } 74 | 75 | func MimeType(args []string) { 76 | filename := args[0] 77 | ext := filepath.Ext(filename) 78 | mime.AddExtensionType(".gz", "application/gzip") 79 | mime.AddExtensionType(".tgz", "application/gzip") 80 | mime.AddExtensionType(".tar", "application/tar") 81 | mime.AddExtensionType(".zip", "application/zip") 82 | mimetype := mime.TypeByExtension(ext) 83 | if mimetype != "" { 84 | fmt.Println(mimetype) 85 | } else { 86 | fmt.Println("application/octet-stream") 87 | } 88 | } 89 | 90 | func Checksum(args []string) { 91 | if len(args) < 1 { 92 | fatal("No algorithm specified") 93 | } 94 | var h hash.Hash 95 | switch args[0] { 96 | case "md5": 97 | h = md5.New() 98 | case "sha1": 99 | h = sha1.New() 100 | case "sha256": 101 | h = sha256.New() 102 | default: 103 | fatal("Algorithm '" + args[0] + "' is unsupported") 104 | } 105 | io.Copy(h, os.Stdin) 106 | fmt.Printf("%x\n", h.Sum(nil)) 107 | } 108 | 109 | func main() { 110 | os.Setenv("VERSION", Version) 111 | basher.Application(map[string]func([]string){ 112 | "upload-url": UploadUrl, 113 | "release-id-from-tagname": ReleaseIdFromTagname, 114 | "mimetype": MimeType, 115 | "checksum": Checksum, 116 | }, []string{ 117 | "bash/gh-release.bash", 118 | }, Asset, true) 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gh-release 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/a-urth/go-bindata v0.0.0-20180209162145-df38da164efc // indirect 7 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 8 | github.com/mitchellh/go-homedir v1.1.0 // indirect 9 | github.com/progrium/gh-release v2.2.1+incompatible // indirect 10 | github.com/progrium/go-basher v5.1.2+incompatible 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/a-urth/go-bindata v0.0.0-20180209162145-df38da164efc h1:eXJIPWW4y4xjPWda/7ruw5PRsfcbzKTL9EhNQrBRsOU= 2 | github.com/a-urth/go-bindata v0.0.0-20180209162145-df38da164efc/go.mod h1:D0SbCgK4DQtSNzDQzfek273VqkCnHdFCd+q2ueHGRiE= 3 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= 4 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 5 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 6 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 7 | github.com/progrium/gh-release v1.0.0 h1:8rnQABX78ffom8a2YiP2MWH2fVJW9DzgSBkj9mEZV8w= 8 | github.com/progrium/gh-release v2.2.1+incompatible h1:4onGzyjKl4DHbIMOueT6xzRo79MNH5CG9MKwYHYnI7o= 9 | github.com/progrium/gh-release v2.2.1+incompatible/go.mod h1:RYwpy7vlkYFxs88MEizbUvh5iPkrJVoHZ+W9A+Z0EAA= 10 | github.com/progrium/go-basher v5.1.2+incompatible h1:l2pBdNutpPyINLq2ZyMRcksTYbwh5AnZ5SfhuvOPY8I= 11 | github.com/progrium/go-basher v5.1.2+incompatible/go.mod h1:Oiy7jZEU1mm+gI1dt5MKYwxptmD37q8/UupxnwhMHtI= 12 | -------------------------------------------------------------------------------- /tests/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | set -eo pipefail 4 | readonly build="$BATS_CWD/build/$(uname)/gh-release" 5 | 6 | setup() { 7 | mkdir -p "$(dirname $BASH_SOURCE)/../release" 8 | touch "$(dirname $BASH_SOURCE)/../release/file.tgz" 9 | } 10 | 11 | teardown() { 12 | rm -rf "$(dirname $BASH_SOURCE)/../release" 13 | } 14 | 15 | @test "version" { 16 | cd "$(dirname $BASH_SOURCE)/.." 17 | 18 | run $build create progrium/gh-release test 19 | echo "status: $status" 20 | echo "output: $output" 21 | [[ "$status" -eq 0 ]] 22 | 23 | run $build destroy progrium/gh-release vtest 24 | echo "status: $status" 25 | echo "output: $output" 26 | [[ "$status" -eq 0 ]] 27 | 28 | cd - > /dev/null 29 | } --------------------------------------------------------------------------------