diff --git a/.drone.sec b/.drone.sec new file mode 100644 index 0000000..e69de29 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..24a0ce9 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,31 @@ +build: + image: golang:1.5 + environment: + - GO15VENDOREXPERIMENT=1 + - GOOS=linux + - GOARCH=amd64 + - CGO_ENABLED=0 + commands: + - go get + - go build + - go test + +publish: + docker: + username: drone + password: $$DOCKER_PASS + email: $$DOCKER_EMAIL + repo: plugins/drone-github-release + when: + branch: master + +plugin: + name: GitHub Release + desc: Publish files and artifacts to GitHub releases + type: publish + image: plugins/drone-github-release + labels: + - publish + - github + - release + - dpl diff --git a/.gitignore b/.gitignore index daf913b..5dc0f42 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ _testmain.go *.exe *.test *.prof + +drone-github-release diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..a88acb9 --- /dev/null +++ b/DOCS.md @@ -0,0 +1,17 @@ +Use this plugin for publishing files and artifacts to GitHub releases. You +can override the default configuration with the following parameters: + +* `api_key` - GitHub oauth token with public_repo or repo permission +* `files` - Files to upload to GitHub Release; globs are allowed +* `base_url` - GitHub base URL; only required for GHE +* `upload_url` - GitHub upload URL; only required for GHE + +Sample configuration: + +```yaml +publish: + github_release: + api_key: my_github_api_key + files: + - dist/* +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d8ba038 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +# Docker image for the Drone GitHub Release plugin +# +# cd $GOPATH/src/github.com/drone-plugins/drone-github-release +# GO15VENDOREXPERIMENT=1 CGO_ENABLED=0 go build -a -tags netgo +# docker build --rm=true -t plugins/drone-github-release . + +FROM alpine:3.2 +RUN apk add -U ca-certificates git && rm -rf /var/cache/apk/* +ADD drone-github-release /bin/ +ENTRYPOINT ["/bin/drone-github-release"] diff --git a/LICENSE b/LICENSE index 8f71f43..8dada3e 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.md b/README.md index 94f4ced..db799be 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,47 @@ # drone-github-release -https://github.com/travis-ci/dpl/blob/master/README.md?#github-releases + +Drone plugin for publishing GitHub releases + +## Usage + +Publish a release: + +``` +./drone-github-release < + + + + + \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..797bcec --- /dev/null +++ b/main.go @@ -0,0 +1,262 @@ +package main + +import ( + "errors" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/drone/drone-go/drone" + "github.com/drone/drone-go/plugin" + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +type Params struct { + BaseUrl string `json:"base_url"` + UploadUrl string `json:"upload_url"` + APIKey string `json:"api_key"` + Files []string `json:"files"` +} + +func main() { + r := drone.Repo{} + b := drone.Build{} + w := drone.Workspace{} + v := Params{} + + plugin.Param("repo", &r) + plugin.Param("build", &b) + plugin.Param("workspace", &w) + plugin.Param("vargs", &v) + + plugin.MustParse() + + if b.Event != "tag" { + fmt.Printf("The GitHub Release plugin is only available for tags\n") + os.Exit(0) + + return + } + + if len(v.BaseUrl) == 0 { + v.BaseUrl = "https://api.github.com/" + } else { + if !strings.HasSuffix(v.BaseUrl, "/") { + v.BaseUrl = v.BaseUrl + "/" + } + } + + if len(v.UploadUrl) == 0 { + v.UploadUrl = "https://uploads.github.com/" + } else { + if !strings.HasSuffix(v.UploadUrl, "/") { + v.UploadUrl = v.UploadUrl + "/" + } + } + + if len(v.APIKey) == 0 { + fmt.Printf("You must provide an API key\n") + os.Exit(1) + + return + } + + files := make([]string, 0) + + for _, glob := range v.Files { + globed, err := filepath.Glob(glob) + + if err != nil { + fmt.Printf("Failed to glob %s\n", glob) + os.Exit(1) + + return + } + + if globed != nil { + files = append(files, globed...) + } + } + + baseUrl, err := url.Parse(v.BaseUrl) + + if err != nil { + fmt.Printf("Failed to parse base URL\n") + os.Exit(1) + + return + } + + uploadUrl, err := url.Parse(v.UploadUrl) + + if err != nil { + fmt.Printf("Failed to parse upload URL\n") + os.Exit(1) + + return + } + + ts := oauth2.StaticTokenSource( + &oauth2.Token{ + AccessToken: v.APIKey, + }) + + tc := oauth2.NewClient( + oauth2.NoContext, + ts) + + client := github.NewClient(tc) + client.BaseURL = baseUrl + client.UploadURL = uploadUrl + + release, releaseErr := retrieveRelease( + client, + r.Owner, + r.Name, + filepath.Base(b.Ref)) + + if releaseErr != nil { + fmt.Println(releaseErr) + os.Exit(1) + + return + } + + uploadErr := appendFiles( + client, + r.Owner, + r.Name, + *release.ID, + files) + + if uploadErr != nil { + fmt.Println(uploadErr) + os.Exit(1) + + return + } +} + +func prepareRelease(client *github.Client, owner string, repo string, tag string) (*github.RepositoryRelease, error) { + var release *github.RepositoryRelease + var releaseErr error + + release, releaseErr = retrieveRelease( + client, + owner, + repo, + tag) + + if releaseErr != nil { + return nil, releaseErr + } + + if release != nil { + return release, nil + } + + release, releaseErr = createRelease( + client, + owner, + repo, + tag) + + if releaseErr != nil { + return nil, releaseErr + } + + if release != nil { + return release, nil + } + + return nil, errors.New( + "Failed to retrieve or create a release") +} + +func retrieveRelease(client *github.Client, owner string, repo string, tag string) (*github.RepositoryRelease, error) { + release, _, err := client.Repositories.GetReleaseByTag( + owner, + repo, + tag) + + if err != nil { + return nil, errors.New( + "Failed to retrieve release") + } + + fmt.Printf("Successfully retrieved %s release\n", tag) + return release, nil +} + +func createRelease(client *github.Client, owner string, repo string, tag string) (*github.RepositoryRelease, error) { + release, _, err := client.Repositories.CreateRelease( + owner, + repo, + &github.RepositoryRelease{TagName: github.String(tag)}) + + if err != nil { + return nil, errors.New( + "Failed to create release") + } + + fmt.Printf("Successfully created %s release\n", tag) + return release, nil +} + +func appendFiles(client *github.Client, owner string, repo string, id int, files []string) error { + assets, _, err := client.Repositories.ListReleaseAssets( + owner, + repo, + id, + &github.ListOptions{}) + + if err != nil { + return errors.New( + "Failed to fetch existing assets") + } + + for _, file := range files { + handle, err := os.Open(file) + + if err != nil { + return errors.New( + fmt.Sprintf("Failed to read %s artifact", file)) + } + + for _, asset := range assets { + if *asset.Name == path.Base(file) { + _, deleteErr := client.Repositories.DeleteReleaseAsset( + owner, + repo, + *asset.ID) + + if deleteErr != nil { + return errors.New( + fmt.Sprintf("Failed to delete %s artifact", file)) + } else { + fmt.Printf("Successfully deleted old %s artifact\n", *asset.Name) + } + } + } + + _, _, uploadErr := client.Repositories.UploadReleaseAsset( + owner, + repo, + id, + &github.UploadOptions{Name: path.Base(file)}, + handle) + + if uploadErr != nil { + return errors.New( + fmt.Sprintf("Failed to upload %s artifact", file)) + } else { + fmt.Printf("Successfully uploaded %s artifact\n", file) + } + } + + return nil +} diff --git a/vendor/github.com/drone/drone-go b/vendor/github.com/drone/drone-go new file mode 160000 index 0000000..085fdbd --- /dev/null +++ b/vendor/github.com/drone/drone-go @@ -0,0 +1 @@ +Subproject commit 085fdbd71700fb1a27fdc60b09689ab92cee8ceb diff --git a/vendor/github.com/google/go-github b/vendor/github.com/google/go-github new file mode 160000 index 0000000..47f2593 --- /dev/null +++ b/vendor/github.com/google/go-github @@ -0,0 +1 @@ +Subproject commit 47f2593dad1971eec2f0a0971aa007fef5edbc50 diff --git a/vendor/github.com/google/go-querystring b/vendor/github.com/google/go-querystring new file mode 160000 index 0000000..2a60fc2 --- /dev/null +++ b/vendor/github.com/google/go-querystring @@ -0,0 +1 @@ +Subproject commit 2a60fc2ba6c19de80291203597d752e9ba58e4c0 diff --git a/vendor/golang.org/x/net b/vendor/golang.org/x/net new file mode 160000 index 0000000..4f2fc6c --- /dev/null +++ b/vendor/golang.org/x/net @@ -0,0 +1 @@ +Subproject commit 4f2fc6c1e69d41baf187332ee08fbd2b296f21ed diff --git a/vendor/golang.org/x/oauth2 b/vendor/golang.org/x/oauth2 new file mode 160000 index 0000000..442624c --- /dev/null +++ b/vendor/golang.org/x/oauth2 @@ -0,0 +1 @@ +Subproject commit 442624c9ec9243441e83b374a9e22ac549b5c51d