Migrated to 0.5 structure and updated documentation

This commit is contained in:
Thomas Boerger 2016-08-26 10:38:22 +02:00
parent 187cb0b1f2
commit c097b9bbfa
No known key found for this signature in database
GPG Key ID: 5A388F55283960B6
11 changed files with 572 additions and 463 deletions

View File

@ -1,39 +1,29 @@
build: workspace:
image: golang:1.5 base: /go
environment:
- CGO_ENABLED=0
commands:
- make deps
- make vet
- make build
- make test
publish: pipeline:
coverage: 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: when:
branch: master branch: master
docker: event: push
username: $$DOCKER_USER
password: $$DOCKER_PASS
email: $$DOCKER_EMAIL
repo: plugins/drone-github-release
tag: latest
when:
branch: master
docker:
username: $$DOCKER_USER
password: $$DOCKER_PASS
email: $$DOCKER_EMAIL
repo: plugins/drone-github-release
tag: develop
when:
branch: develop
plugin: plugin:
name: GitHub Release name: GitHub Release
desc: Publish files and artifacts to GitHub Releases desc: Publish files and artifacts to GitHub Releases
type: publish type: publish
image: plugins/drone-github-release image: plugins/github-release
labels: labels:
- github - github
- release - release

1
.drone.yml.sig Normal file
View File

@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwoKcGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBnb2xhbmc6MS42CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBDR09fRU5BQkxFRD0wCiAgICBjb21tYW5kczoKICAgICAgLSBnbyB2ZXQKICAgICAgLSBnbyB0ZXN0IC1jb3ZlciAtY292ZXJwcm9maWxlPWNvdmVyYWdlLm91dAogICAgICAtIGdvIGJ1aWxkIC1sZGZsYWdzICItcyAtdyAtWCBtYWluLmJ1aWxkPSREUk9ORV9CVUlMRF9OVU1CRVIiIC1hIC10YWdzIG5ldGdvCgogIGxhdGVzdDoKICAgIGltYWdlOiBkb2NrZXIKICAgIHJlcG86IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIHRhZ3M6IFsgImxhdGVzdCIsICIxLjAiLCAiMSIgXQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgZXZlbnQ6IHB1c2gKCnBsdWdpbjoKICBuYW1lOiBHaXRIdWIgUmVsZWFzZQogIGRlc2M6IFB1Ymxpc2ggZmlsZXMgYW5kIGFydGlmYWN0cyB0byBHaXRIdWIgUmVsZWFzZXMKICB0eXBlOiBwdWJsaXNoCiAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICBsYWJlbHM6CiAgICAtIGdpdGh1YgogICAgLSByZWxlYXNlCg.5FZW1Auk18CljY9LW5P82r9RM_spPCYMYg82PFj2irM

71
DOCS.md
View File

@ -1,30 +1,61 @@
Use this plugin for publishing files and artifacts to GitHub releases. You Use this plugin for publishing files and artifacts to GitHub releases. Be aware
can override the default configuration with the following parameters: that you can use this plugin only for tags, GitHub doesn't support the release
of branches.
* `api_key` - GitHub oauth token with public_repo or repo permission ## Config
* `files` - Files to upload to GitHub Release, globs are allowed
* `file_exists` - What to do if an file asset already exists, supported values: **overwrite** (default), **skip** and **fail**
* `checksum` - Checksum takes hash methods to include in your GitHub release for the files specified. Supported hash methods include md5, sha1, sha256, sha512, adler32, and crc32.
* `draft` - create a draft release if set to true
* `base_url` - GitHub base URL, only required for GHE
* `upload_url` - GitHub upload URL, only required for GHE
Sample configuration: The following parameters are used to configure the plugin:
```yaml * **api_key** - GitHub oauth token with public_repo or repo permission
publish: * **files** - files to upload to GitHub Release, globs are allowed
github_release: * **file_exists** - what to do if an file asset already exists, supported values: **overwrite** (default), **skip** and **fail**
api_key: my_github_api_key * **checksum** - checksum takes hash methods to include in your GitHub release for the files specified. Supported hash methods include md5, sha1, sha256, sha512, adler32, and crc32.
files: dist/* * **draft** - create a draft release if set to true
checksum: sha1 * **base_url** - GitHub base URL, only required for GHE
* **upload_url** - GitHub upload URL, only required for GHE
The following secret values can be set to configure the plugin.
* **GITHUB_RELEASE_API_KEY** - corresponds to **api_key**
* **GITHUB_RELEASE_BASE_URL** - corresponds to **base_url**
* **GITHUB_RELEASE_UPLOAD_URL** - corresponds to **upload_url**
It is highly recommended to put the **GITHUB_RELEASE_API_KEY** into a secret so
it is not exposed to users. This can be done using the drone-cli.
```bash
drone secret add --image=plugins/github-release \
octocat/hello-world GITHUB_RELEASE_API_KEY my_github_api_key
``` ```
or Then sign the YAML file after all secrets are added.
```bash
drone sign octocat/hello-world
```
See [secrets](http://readme.drone.io/0.5/usage/secrets/) for additional
information on secrets
## Examples
The following is a sample configuration in your .drone.yml file:
```yaml ```yaml
publish: pipeline:
github_release: github_release:
api_key: my_github_api_key image: plugins/github-release
files: dist/*
when:
event: tag
```
An example for generating checksums and upload additional files:
```yaml
pipeline:
github_release:
image: plugins/github-release
files: files:
- dist/* - dist/*
- bin/binary.exe - bin/binary.exe
@ -35,4 +66,6 @@ publish:
- sha512 - sha512
- adler32 - adler32
- crc32 - crc32
when:
event: tag
``` ```

View File

@ -1,9 +1,4 @@
# Docker image for the Drone GitHub Release plugin FROM alpine:3.4
#
# cd $GOPATH/src/github.com/drone-plugins/drone-github-release
# make deps build docker
FROM alpine:3.3
RUN apk update && \ RUN apk update && \
apk add \ apk add \

132
README.md
View File

@ -1,117 +1,53 @@
# drone-github-release # drone-github-release
[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-github-release/status.svg)](http://beta.drone.io/drone-plugins/drone-github-release) [![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-github-release/status.svg)](http://beta.drone.io/drone-plugins/drone-github-release)
[![Coverage Status](https://aircover.co/badges/drone-plugins/drone-github-release/coverage.svg)](https://aircover.co/drone-plugins/drone-github-release) [![Go Doc](https://godoc.org/github.com/drone-plugins/drone-github-release?status.svg)](http://godoc.org/github.com/drone-plugins/drone-github-release)
[![](https://badge.imagelayers.io/plugins/drone-github-release:latest.svg)](https://imagelayers.io/?images=plugins/drone-github-release:latest 'Get your own badge on imagelayers.io') [![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-github-release)](https://goreportcard.com/report/github.com/drone-plugins/drone-github-release)
[![Join the chat at https://gitter.im/drone/drone](https://badges.gitter.im/Join%20Chat.svg)](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
```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
``` ```
## Docker ## Docker
Build the container using `make`: Build the docker image with the following commands:
``` ```
make deps docker CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo
docker build --rm=true -t plugins/github-release .
``` ```
### Example 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:
```
docker: Error response from daemon: Container command
'/bin/drone-github-release' not found or does not exist..
```
## Usage
Execute from the working directory:
```sh ```sh
docker run -i plugins/drone-github-release <<EOF docker run --rm \
{ -e DRONE_BUILD_EVENT=tag \
"repo": { -e DRONE_REPO_OWNER=octocat \
"clone_url": "git://github.com/drone/drone", -e DRONE_REPO_NAME=foo \
"owner": "drone", -e DRONE_COMMIT_REF=refs/heads/master \
"name": "drone", -e PLUGIN_API_KEY=${HOME}/.ssh/id_rsa \
"full_name": "drone/drone" -e PLUGIN_FILES=master \
}, -v $(pwd):$(pwd) \
"system": { -w $(pwd) \
"link_url": "https://beta.drone.io" plugins/github-release
},
"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
``` ```

View File

@ -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)
}
}

364
main.go
View File

@ -2,276 +2,120 @@ package main
import ( import (
"fmt" "fmt"
"net/url"
"os" "os"
"path"
"path/filepath"
"strings"
"github.com/drone/drone-go/drone" "github.com/Sirupsen/logrus"
"github.com/drone/drone-go/plugin" "github.com/joho/godotenv"
"github.com/google/go-github/github" "github.com/urfave/cli"
"golang.org/x/oauth2"
) )
var ( var build = "0" // build number set at compile-time
buildCommit string
)
func main() { func main() {
fmt.Printf("Drone GitHub Release Plugin built from %s\n", buildCommit) app := cli.NewApp()
app.Name = "github-release plugin"
app.Usage = "github-release plugin"
app.Action = run
app.Version = fmt.Sprintf("1.0.%s", build)
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "api-key",
Usage: "api key to access github api",
EnvVar: "PLUGIN_API_KEY,GITHUB_RELEASE_API_KEY",
},
cli.StringSliceFlag{
Name: "files",
Usage: "list of files to upload",
EnvVar: "PLUGIN_FILES,GITHUB_RELEASE_FILES",
},
cli.StringFlag{
Name: "file-exists",
Value: "overwrite",
Usage: "what to do if file already exist",
EnvVar: "PLUGIN_FILE_EXISTS,GITHUB_RELEASE_FILE_EXISTS",
},
cli.StringSliceFlag{
Name: "checksum",
Usage: "generate specific checksums",
EnvVar: "PLUGIN_CHECKSUM,GITHUB_RELEASE_CHECKSUM",
},
cli.BoolFlag{
Name: "draft",
Usage: "create a draft release",
EnvVar: "PLUGIN_DRAFT,GITHUB_RELEASE_DRAFT",
},
cli.StringFlag{
Name: "base-url",
Value: "https://api.github.com/",
Usage: "api url, needs to be changed for ghe",
EnvVar: "PLUGIN_BASE_URL,GITHUB_RELEASE_BASE_URL",
},
cli.StringFlag{
Name: "upload-url",
Value: "https://uploads.github.com/",
Usage: "upload url, needs to be changed for ghe",
EnvVar: "PLUGIN_UPLOAD_URL,GITHUB_RELEASE_UPLOAD_URL",
},
workspace := drone.Workspace{} cli.StringFlag{
repo := drone.Repo{} Name: "repo.owner",
build := drone.Build{} Usage: "repository owner",
vargs := Params{} EnvVar: "DRONE_REPO_OWNER",
},
plugin.Param("workspace", &workspace) cli.StringFlag{
plugin.Param("repo", &repo) Name: "repo.name",
plugin.Param("build", &build) Usage: "repository name",
plugin.Param("vargs", &vargs) EnvVar: "DRONE_REPO_NAME",
plugin.MustParse() },
cli.StringFlag{
if build.Event != "tag" { Name: "build.event",
fmt.Printf("The GitHub Release plugin is only available for tags\n") Value: "push",
os.Exit(0) Usage: "build event",
EnvVar: "DRONE_BUILD_EVENT",
},
cli.StringFlag{
Name: "commit.ref",
Value: "refs/heads/master",
Usage: "git commit ref",
EnvVar: "DRONE_COMMIT_REF",
},
cli.StringFlag{
Name: "env-file",
Usage: "source env file",
},
} }
if vargs.FileExists == "" { if err := app.Run(os.Args); err != nil {
vargs.FileExists = "overwrite" logrus.Fatal(err)
}
if !fileExistsValues[vargs.FileExists] {
fmt.Printf("invalid value for file_exists: use [empty], overwrite, skip or fail")
}
if vargs.BaseURL == "" {
vargs.BaseURL = "https://api.github.com/"
} else if !strings.HasSuffix(vargs.BaseURL, "/") {
vargs.BaseURL = vargs.BaseURL + "/"
}
if vargs.UploadURL == "" {
vargs.UploadURL = "https://uploads.github.com/"
} else if !strings.HasSuffix(vargs.UploadURL, "/") {
vargs.UploadURL = vargs.UploadURL + "/"
}
if vargs.APIKey == "" {
fmt.Printf("You must provide an API key\n")
os.Exit(1)
}
if workspace.Path != "" {
os.Chdir(workspace.Path)
}
var files []string
for _, glob := range vargs.Files.Slice() {
globed, err := filepath.Glob(glob)
if err != nil {
fmt.Printf("Failed to glob %s\n", glob)
os.Exit(1)
}
if globed != nil {
files = append(files, globed...)
}
}
if vargs.Checksum.Len() > 0 {
var err error
files, err = writeChecksums(files, vargs.Checksum.Slice())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
baseURL, err := url.Parse(vargs.BaseURL)
if err != nil {
fmt.Printf("Failed to parse base URL\n")
os.Exit(1)
}
uploadURL, err := url.Parse(vargs.UploadURL)
if err != nil {
fmt.Printf("Failed to parse upload URL\n")
os.Exit(1)
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: vargs.APIKey})
tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(tc)
client.BaseURL = baseURL
client.UploadURL = uploadURL
rc := releaseClient{
Client: client,
Owner: repo.Owner,
Repo: repo.Name,
Tag: filepath.Base(build.Ref),
Draft: vargs.Draft,
FileExists: vargs.FileExists,
}
release, err := rc.buildRelease()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := rc.uploadFiles(*release.ID, files); err != nil {
fmt.Println(err)
os.Exit(1)
} }
} }
var fileExistsValues = map[string]bool{ func run(c *cli.Context) error {
"overwrite": true, if c.String("env-file") != "" {
"fail": true, _ = godotenv.Load(c.String("env-file"))
"skip": true, }
}
plugin := Plugin{
// Release holds ties the drone env data and github client together. Repo: Repo{
type releaseClient struct { Owner: c.String("repo.owner"),
*github.Client Name: c.String("repo.name"),
Owner string },
Repo string Build: Build{
Tag string Event: c.String("build.event"),
Draft bool },
FileExists string Commit: Commit{
} Ref: c.String("commit.sha"),
},
func (rc *releaseClient) buildRelease() (*github.RepositoryRelease, error) { Config: Config{
APIKey: c.String("api-key"),
// first attempt to get a release by that tag Files: c.StringSlice("api-key"),
release, err := rc.getRelease() FileExists: c.String("file-exists"),
if err != nil && release == nil { Checksum: c.StringSlice("checksum"),
fmt.Println(err) Draft: c.Bool("draft"),
} else if release != nil { BaseURL: c.String("base-url"),
return release, nil UploadURL: c.String("upload-url"),
} },
}
// if no release was found by that tag, create a new one
release, err = rc.newRelease() return plugin.Exec()
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
}
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
} }

134
plugin.go Normal file
View File

@ -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
}

125
release.go Normal file
View File

@ -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
}

View File

@ -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"`
}

103
utils.go Normal file
View File

@ -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
}