parent
187cb0b1f2
commit
c097b9bbfa
@ -1,39 +1,29 @@ |
||||
build: |
||||
image: golang:1.5 |
||||
environment: |
||||
- CGO_ENABLED=0 |
||||
commands: |
||||
- make deps |
||||
- make vet |
||||
- make build |
||||
- make test |
||||
workspace: |
||||
base: /go |
||||
|
||||
publish: |
||||
coverage: |
||||
when: |
||||
branch: master |
||||
docker: |
||||
username: $$DOCKER_USER |
||||
password: $$DOCKER_PASS |
||||
email: $$DOCKER_EMAIL |
||||
repo: plugins/drone-github-release |
||||
tag: latest |
||||
pipeline: |
||||
test: |
||||
image: golang:1.6 |
||||
environment: |
||||
- CGO_ENABLED=0 |
||||
commands: |
||||
- go vet |
||||
- go test -cover -coverprofile=coverage.out |
||||
- go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER" -a -tags netgo |
||||
|
||||
latest: |
||||
image: docker |
||||
repo: plugins/github-release |
||||
tags: [ "latest", "1.0", "1" ] |
||||
when: |
||||
branch: master |
||||
docker: |
||||
username: $$DOCKER_USER |
||||
password: $$DOCKER_PASS |
||||
email: $$DOCKER_EMAIL |
||||
repo: plugins/drone-github-release |
||||
tag: develop |
||||
when: |
||||
branch: develop |
||||
event: push |
||||
|
||||
plugin: |
||||
name: GitHub Release |
||||
desc: Publish files and artifacts to GitHub Releases |
||||
type: publish |
||||
image: plugins/drone-github-release |
||||
image: plugins/github-release |
||||
labels: |
||||
- github |
||||
- release |
||||
|
@ -0,0 +1 @@ |
||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwoKcGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBnb2xhbmc6MS42CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBDR09fRU5BQkxFRD0wCiAgICBjb21tYW5kczoKICAgICAgLSBnbyB2ZXQKICAgICAgLSBnbyB0ZXN0IC1jb3ZlciAtY292ZXJwcm9maWxlPWNvdmVyYWdlLm91dAogICAgICAtIGdvIGJ1aWxkIC1sZGZsYWdzICItcyAtdyAtWCBtYWluLmJ1aWxkPSREUk9ORV9CVUlMRF9OVU1CRVIiIC1hIC10YWdzIG5ldGdvCgogIGxhdGVzdDoKICAgIGltYWdlOiBkb2NrZXIKICAgIHJlcG86IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIHRhZ3M6IFsgImxhdGVzdCIsICIxLjAiLCAiMSIgXQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgZXZlbnQ6IHB1c2gKCnBsdWdpbjoKICBuYW1lOiBHaXRIdWIgUmVsZWFzZQogIGRlc2M6IFB1Ymxpc2ggZmlsZXMgYW5kIGFydGlmYWN0cyB0byBHaXRIdWIgUmVsZWFzZXMKICB0eXBlOiBwdWJsaXNoCiAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICBsYWJlbHM6CiAgICAtIGdpdGh1YgogICAgLSByZWxlYXNlCg.5FZW1Auk18CljY9LW5P82r9RM_spPCYMYg82PFj2irM |
@ -1,117 +1,53 @@ |
||||
# drone-github-release |
||||
|
||||
[](http://beta.drone.io/drone-plugins/drone-github-release) |
||||
[](https://aircover.co/drone-plugins/drone-github-release) |
||||
[](https://imagelayers.io/?images=plugins/drone-github-release:latest 'Get your own badge on imagelayers.io') |
||||
[](http://godoc.org/github.com/drone-plugins/drone-github-release) |
||||
[](https://goreportcard.com/report/github.com/drone-plugins/drone-github-release) |
||||
[](https://gitter.im/drone/drone) |
||||
|
||||
Drone plugin to publish files and artifacts to GitHub Release. For the usage information and a listing of the available options please take a look at [the docs](DOCS.md). |
||||
Drone plugin to publish files and artifacts to GitHub Release. For the usage |
||||
information and a listing of the available options please take a look at |
||||
[the docs](DOCS.md). |
||||
|
||||
## Binary |
||||
## Build |
||||
|
||||
Build the binary using `make`: |
||||
Build the binary with the following commands: |
||||
|
||||
``` |
||||
make deps build |
||||
go build |
||||
go test |
||||
``` |
||||
|
||||
### Example |
||||
## Docker |
||||
|
||||
```sh |
||||
./drone-github-release <<EOF |
||||
{ |
||||
"repo": { |
||||
"clone_url": "git://github.com/drone/drone", |
||||
"owner": "drone", |
||||
"name": "drone", |
||||
"full_name": "drone/drone" |
||||
}, |
||||
"system": { |
||||
"link_url": "https://beta.drone.io" |
||||
}, |
||||
"build": { |
||||
"number": 22, |
||||
"status": "success", |
||||
"started_at": 1421029603, |
||||
"finished_at": 1421029813, |
||||
"message": "Update the Readme", |
||||
"author": "johnsmith", |
||||
"author_email": "john.smith@gmail.com" |
||||
"event": "push", |
||||
"branch": "master", |
||||
"commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c", |
||||
"ref": "refs/heads/master" |
||||
}, |
||||
"workspace": { |
||||
"root": "/drone/src", |
||||
"path": "/drone/src/github.com/drone/drone" |
||||
}, |
||||
"vargs": { |
||||
"api_key": "your_api_key", |
||||
"files": [ |
||||
"dist/*.txt", |
||||
"dist/other-file" |
||||
], |
||||
"checksum": [ |
||||
"sha1", |
||||
"sha256", |
||||
"sha512" |
||||
] |
||||
} |
||||
} |
||||
EOF |
||||
``` |
||||
Build the docker image with the following commands: |
||||
|
||||
## Docker |
||||
``` |
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo |
||||
docker build --rm=true -t plugins/github-release . |
||||
``` |
||||
|
||||
Build the container using `make`: |
||||
Please note incorrectly building the image for the correct x64 linux and with |
||||
GCO disabled will result in an error when running the Docker image: |
||||
|
||||
``` |
||||
make deps docker |
||||
docker: Error response from daemon: Container command |
||||
'/bin/drone-github-release' not found or does not exist.. |
||||
``` |
||||
|
||||
### Example |
||||
## Usage |
||||
|
||||
Execute from the working directory: |
||||
|
||||
```sh |
||||
docker run -i plugins/drone-github-release <<EOF |
||||
{ |
||||
"repo": { |
||||
"clone_url": "git://github.com/drone/drone", |
||||
"owner": "drone", |
||||
"name": "drone", |
||||
"full_name": "drone/drone" |
||||
}, |
||||
"system": { |
||||
"link_url": "https://beta.drone.io" |
||||
}, |
||||
"build": { |
||||
"number": 22, |
||||
"status": "success", |
||||
"started_at": 1421029603, |
||||
"finished_at": 1421029813, |
||||
"message": "Update the Readme", |
||||
"author": "johnsmith", |
||||
"author_email": "john.smith@gmail.com" |
||||
"event": "push", |
||||
"branch": "master", |
||||
"commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c", |
||||
"ref": "refs/heads/master" |
||||
}, |
||||
"workspace": { |
||||
"root": "/drone/src", |
||||
"path": "/drone/src/github.com/drone/drone" |
||||
}, |
||||
"vargs": { |
||||
"api_key": "your_api_key", |
||||
"files": [ |
||||
"dist/*.txt", |
||||
"dist/other-file" |
||||
], |
||||
"checksum": [ |
||||
"sha1", |
||||
"sha256", |
||||
"sha512" |
||||
] |
||||
} |
||||
} |
||||
EOF |
||||
docker run --rm \ |
||||
-e DRONE_BUILD_EVENT=tag \ |
||||
-e DRONE_REPO_OWNER=octocat \ |
||||
-e DRONE_REPO_NAME=foo \ |
||||
-e DRONE_COMMIT_REF=refs/heads/master \ |
||||
-e PLUGIN_API_KEY=${HOME}/.ssh/id_rsa \ |
||||
-e PLUGIN_FILES=master \ |
||||
-v $(pwd):$(pwd) \ |
||||
-w $(pwd) \ |
||||
plugins/github-release |
||||
``` |
||||
|
@ -1,38 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"crypto/sha1" |
||||
"crypto/sha256" |
||||
"crypto/sha512" |
||||
"fmt" |
||||
"hash/adler32" |
||||
"hash/crc32" |
||||
"io" |
||||
"io/ioutil" |
||||
"strconv" |
||||
) |
||||
|
||||
func checksum(r io.Reader, method string) (string, error) { |
||||
b, err := ioutil.ReadAll(r) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
switch method { |
||||
case "md5": |
||||
return fmt.Sprintf("%x", md5.Sum(b)), nil |
||||
case "sha1": |
||||
return fmt.Sprintf("%x", sha1.Sum(b)), nil |
||||
case "sha256": |
||||
return fmt.Sprintf("%x", sha256.Sum256(b)), nil |
||||
case "sha512": |
||||
return fmt.Sprintf("%x", sha512.Sum512(b)), nil |
||||
case "adler32": |
||||
return strconv.FormatUint(uint64(adler32.Checksum(b)), 10), nil |
||||
case "crc32": |
||||
return strconv.FormatUint(uint64(crc32.ChecksumIEEE(b)), 10), nil |
||||
default: |
||||
return "", fmt.Errorf("hashing method %s is not supported", method) |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/url" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/google/go-github/github" |
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
type ( |
||||
Repo struct { |
||||
Owner string |
||||
Name string |
||||
} |
||||
|
||||
Build struct { |
||||
Event string |
||||
} |
||||
|
||||
Commit struct { |
||||
Ref string |
||||
} |
||||
|
||||
Config struct { |
||||
APIKey string |
||||
Files []string |
||||
FileExists string |
||||
Checksum []string |
||||
Draft bool |
||||
BaseURL string |
||||
UploadURL string |
||||
} |
||||
|
||||
Plugin struct { |
||||
Repo Repo |
||||
Build Build |
||||
Commit Commit |
||||
Config Config |
||||
} |
||||
) |
||||
|
||||
func (p Plugin) Exec() error { |
||||
var ( |
||||
files []string |
||||
) |
||||
|
||||
if p.Build.Event != "tag" { |
||||
return fmt.Errorf("The GitHub Release plugin is only available for tags") |
||||
} |
||||
|
||||
if p.Config.APIKey == "" { |
||||
return fmt.Errorf("You must provide an API key") |
||||
} |
||||
|
||||
if !fileExistsValues[p.Config.FileExists] { |
||||
return fmt.Errorf("Invalid value for file_exists") |
||||
} |
||||
|
||||
if !strings.HasSuffix(p.Config.BaseURL, "/") { |
||||
p.Config.BaseURL = p.Config.BaseURL + "/" |
||||
} |
||||
|
||||
if !strings.HasSuffix(p.Config.UploadURL, "/") { |
||||
p.Config.UploadURL = p.Config.UploadURL + "/" |
||||
} |
||||
|
||||
for _, glob := range p.Config.Files { |
||||
globed, err := filepath.Glob(glob) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to glob %s. %s", glob, err) |
||||
} |
||||
|
||||
if globed != nil { |
||||
files = append(files, globed...) |
||||
} |
||||
} |
||||
|
||||
if len(p.Config.Checksum) > 0 { |
||||
var ( |
||||
err error |
||||
) |
||||
|
||||
files, err = writeChecksums(files, p.Config.Checksum) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to write checksums. %s", err) |
||||
} |
||||
} |
||||
|
||||
baseURL, err := url.Parse(p.Config.BaseURL) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to parse base URL. %s", err) |
||||
} |
||||
|
||||
uploadURL, err := url.Parse(p.Config.UploadURL) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to parse upload URL. %s", err) |
||||
} |
||||
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: p.Config.APIKey}) |
||||
tc := oauth2.NewClient(oauth2.NoContext, ts) |
||||
|
||||
client := github.NewClient(tc) |
||||
|
||||
client.BaseURL = baseURL |
||||
client.UploadURL = uploadURL |
||||
|
||||
rc := releaseClient{ |
||||
Client: client, |
||||
Owner: p.Repo.Owner, |
||||
Repo: p.Repo.Name, |
||||
Tag: filepath.Base(p.Commit.Ref), |
||||
Draft: p.Config.Draft, |
||||
FileExists: p.Config.FileExists, |
||||
} |
||||
|
||||
release, err := rc.buildRelease() |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to create the release. %s", err) |
||||
} |
||||
|
||||
if err := rc.uploadFiles(*release.ID, files); err != nil { |
||||
return fmt.Errorf("Failed to upload the files. %s", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,125 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"path" |
||||
|
||||
"github.com/google/go-github/github" |
||||
) |
||||
|
||||
// Release holds ties the drone env data and github client together.
|
||||
type releaseClient struct { |
||||
*github.Client |
||||
Owner string |
||||
Repo string |
||||
Tag string |
||||
Draft bool |
||||
FileExists string |
||||
} |
||||
|
||||
func (rc *releaseClient) buildRelease() (*github.RepositoryRelease, error) { |
||||
// first attempt to get a release by that tag
|
||||
release, err := rc.getRelease() |
||||
|
||||
if err != nil && release == nil { |
||||
fmt.Println(err) |
||||
} else if release != nil { |
||||
return release, nil |
||||
} |
||||
|
||||
// if no release was found by that tag, create a new one
|
||||
release, err = rc.newRelease() |
||||
|
||||
if err != nil { |
||||
return nil, fmt.Errorf("Failed to retrieve or create a release: %s", err) |
||||
} |
||||
|
||||
return release, nil |
||||
} |
||||
|
||||
func (rc *releaseClient) getRelease() (*github.RepositoryRelease, error) { |
||||
release, _, err := rc.Client.Repositories.GetReleaseByTag(rc.Owner, rc.Repo, rc.Tag) |
||||
|
||||
if err != nil { |
||||
return nil, fmt.Errorf("Release %s not found", rc.Tag) |
||||
} |
||||
|
||||
fmt.Printf("Successfully retrieved %s release\n", rc.Tag) |
||||
return release, nil |
||||
} |
||||
|
||||
func (rc *releaseClient) newRelease() (*github.RepositoryRelease, error) { |
||||
rr := &github.RepositoryRelease{ |
||||
TagName: github.String(rc.Tag), |
||||
Draft: &rc.Draft, |
||||
} |
||||
|
||||
release, _, err := rc.Client.Repositories.CreateRelease(rc.Owner, rc.Repo, rr) |
||||
|
||||
if err != nil { |
||||
return nil, fmt.Errorf("Failed to create release: %s", err) |
||||
} |
||||
|
||||
fmt.Printf("Successfully created %s release\n", rc.Tag) |
||||
return release, nil |
||||
} |
||||
|
||||
func (rc *releaseClient) uploadFiles(id int, files []string) error { |
||||
assets, _, err := rc.Client.Repositories.ListReleaseAssets(rc.Owner, rc.Repo, id, &github.ListOptions{}) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to fetch existing assets: %s", err) |
||||
} |
||||
|
||||
var uploadFiles []string |
||||
|
||||
files: |
||||
for _, file := range files { |
||||
for _, asset := range assets { |
||||
if *asset.Name == path.Base(file) { |
||||
switch rc.FileExists { |
||||
case "overwrite": |
||||
// do nothing
|
||||
case "fail": |
||||
return fmt.Errorf("Asset file %s already exists", path.Base(file)) |
||||
case "skip": |
||||
fmt.Printf("Skipping pre-existing %s artifact\n", *asset.Name) |
||||
continue files |
||||
default: |
||||
return fmt.Errorf("Internal error, unkown file_exist value %s", rc.FileExists) |
||||
} |
||||
} |
||||
} |
||||
|
||||
uploadFiles = append(uploadFiles, file) |
||||
} |
||||
|
||||
for _, file := range uploadFiles { |
||||
handle, err := os.Open(file) |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("Failed to read %s artifact: %s", file, err) |
||||
} |
||||
|
||||
for _, asset := range assets { |
||||
if *asset.Name == path.Base(file) { |
||||
if _, err := rc.Client.Repositories.DeleteReleaseAsset(rc.Owner, rc.Repo, *asset.ID); err != nil { |
||||
return fmt.Errorf("Failed to delete %s artifact: %s", file, err) |
||||
} |
||||
|
||||
fmt.Printf("Successfully deleted old %s artifact\n", *asset.Name) |
||||
} |
||||
} |
||||
|
||||
uo := &github.UploadOptions{Name: path.Base(file)} |
||||
|
||||
if _, _, err = rc.Client.Repositories.UploadReleaseAsset(rc.Owner, rc.Repo, id, uo, handle); err != nil { |
||||
return fmt.Errorf("Failed to upload %s artifact: %s", file, err) |
||||
} |
||||
|
||||
fmt.Printf("Successfully uploaded %s artifact\n", file) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,14 +0,0 @@ |
||||
package main |
||||
|
||||
import "github.com/drone/drone-go/drone" |
||||
|
||||
// Params are the parameters that the GitHub Release plugin can parse.
|
||||
type Params struct { |
||||
BaseURL string `json:"base_url"` |
||||
UploadURL string `json:"upload_url"` |
||||
APIKey string `json:"api_key"` |
||||
Files drone.StringSlice `json:"files"` |
||||
Checksum drone.StringSlice `json:"checksum"` |
||||
Draft bool `json:"draft"` |
||||
FileExists string `json:"file_exists"` |
||||
} |
@ -0,0 +1,103 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"crypto/sha1" |
||||
"crypto/sha256" |
||||
"crypto/sha512" |
||||
"fmt" |
||||
"hash/adler32" |
||||
"hash/crc32" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"os/exec" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
var ( |
||||
fileExistsValues = map[string]bool{ |
||||
"overwrite": true, |
||||
"fail": true, |
||||
"skip": true, |
||||
} |
||||
) |
||||
|
||||
func execute(cmd *exec.Cmd) error { |
||||
fmt.Println("+", strings.Join(cmd.Args, " ")) |
||||
|
||||
cmd.Stderr = os.Stderr |
||||
cmd.Stdin = os.Stdin |
||||
|
||||
return cmd.Run() |
||||
} |
||||
|
||||
func checksum(r io.Reader, method string) (string, error) { |
||||
b, err := ioutil.ReadAll(r) |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
switch method { |
||||
case "md5": |
||||
return fmt.Sprintf("%x", md5.Sum(b)), nil |
||||
case "sha1": |
||||
return fmt.Sprintf("%x", sha1.Sum(b)), nil |
||||
case "sha256": |
||||
return fmt.Sprintf("%x", sha256.Sum256(b)), nil |
||||
case "sha512": |
||||
return fmt.Sprintf("%x", sha512.Sum512(b)), nil |
||||
case "adler32": |
||||
return strconv.FormatUint(uint64(adler32.Checksum(b)), 10), nil |
||||
case "crc32": |
||||
return strconv.FormatUint(uint64(crc32.ChecksumIEEE(b)), 10), nil |
||||
} |
||||
|
||||
return "", fmt.Errorf("Hashing method %s is not supported", method) |
||||
} |
||||
|
||||
func writeChecksums(files, methods []string) ([]string, error) { |
||||
checksums := make(map[string][]string) |
||||
|
||||
for _, method := range methods { |
||||
for _, file := range files { |
||||
handle, err := os.Open(file) |
||||
|
||||
if err != nil { |
||||
return nil, fmt.Errorf("Failed to read %s artifact: %s", file, err) |
||||
} |
||||
|
||||
hash, err := checksum(handle, method) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
checksums[method] = append(checksums[method], hash, file) |
||||
} |
||||
} |
||||
|
||||
for method, results := range checksums { |
||||
filename := method + "sum.txt" |
||||
f, err := os.Create(filename) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := 0; i < len(results); i += 2 { |
||||
hash := results[i] |
||||
file := results[i+1] |
||||
|
||||
if _, err := f.WriteString(fmt.Sprintf("%s %s\n", hash, file)); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
files = append(files, filename) |
||||
} |
||||
|
||||
return files, nil |
||||
} |
Loading…
Reference in new issue