diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e4f9bec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +# unifying the coding style for different editors and IDEs => editorconfig.org + +; indicate this is the root of the project +root = true + +########################################################### +; common +########################################################### + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +########################################################### +; make +########################################################### + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab + +########################################################### +; markdown +########################################################### + +[*.md] +trim_trailing_whitespace = false + +########################################################### +; golang +########################################################### + +[*.go] +indent_style = tab \ No newline at end of file diff --git a/.gitignore b/.gitignore index daf913b..19ad47f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ _testmain.go *.exe *.test *.prof +vendor +drone-telegram +coverage.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f13c145 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,53 @@ +sudo: required +language: go + +services: + - docker + +go: + - 1.5.4 + - 1.6.3 + - 1.7.1 + - tip + +env: + global: + - DOCKER_CACHE_FILE=${HOME}/docker/cache.tar.gz + +cache: + directories: + - vendor + - ${HOME}/.glide + - ${HOME}/docker + +before_install: + - mkdir -p $GOPATH/bin + - curl https://glide.sh/get | sh + - if [ -f ${DOCKER_CACHE_FILE} ]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load; fi + +install: + - export GO15VENDOREXPERIMENT=1 + - make install + +script: + - make test + - make docker + - if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + mkdir -p $(dirname ${DOCKER_CACHE_FILE}); + docker save $(docker history -q $TRAVIS_REPO_SLUG:latest | grep -v '') | gzip > ${DOCKER_CACHE_FILE}; + fi + +after_success: + # ignore main.go coverage + - sed -i '/main.go/d' coverage.txt + - bash <(curl -s https://codecov.io/bash) -f coverage.txt + # deploy from master + - if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_GO_VERSION" == "1.7.1" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; + make docker_deploy tag=latest; + fi + # deploy from tag + - if [ "$TRAVIS_GO_VERSION" == "1.7.1" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then + docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; + make docker_deploy tag=$TRAVIS_TAG; + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a712e68 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM centurylink/ca-certs + +ADD drone-jenkins / + +ENTRYPOINT ["/drone-jenkins"] diff --git a/Dockerfile.armhf b/Dockerfile.armhf new file mode 100644 index 0000000..2ebc614 --- /dev/null +++ b/Dockerfile.armhf @@ -0,0 +1,8 @@ +FROM armhfbuild/alpine:3.4 + +RUN apk update && \ + apk add ca-certificates && \ + rm -rf /var/cache/apk/* + +ADD drone-telegram /bin/ +ENTRYPOINT ["/bin/drone-telegram"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..527861a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Bo-Yi Wu + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0f8c9d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.PHONY: install build test html update docker_build docker_image docker_deploy clean + +VERSION := $(shell git describe --tags || git rev-parse --short HEAD) +DEPLOY_ACCOUNT := "appleboy" +DEPLOY_IMAGE := "drone-jenkins" + +ifneq ($(shell uname), Darwin) + EXTLDFLAGS = -extldflags "-static" $(null) +else + EXTLDFLAGS = +endif + +install: + glide install + +build: + go build -ldflags="$(EXTLDFLAGS)-s -w -X main.Version=$(VERSION)" + +test: + go test -v -coverprofile=coverage.txt + +html: + go tool cover -html=coverage.txt + +update: + glide up + +docker_build: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags="-X main.Version=$(VERSION)" + +docker_image: + docker build --rm -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) . + +docker: docker_build docker_image + +docker_deploy: +ifeq ($(tag),) + @echo "Usage: make $@ tag=" + @exit 1 +endif + docker tag $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):latest $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag) + docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag) + +clean: + rm -rf coverage.txt ${DEPLOY_IMAGE} diff --git a/README.md b/README.md index 2073283..bc0ae65 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,81 @@ -# drone-jenkins -Trigger jenkins jobs from Drone CI +# drone-telegram + +[![Build Status](https://travis-ci.org/appleboy/drone-telegram.svg?branch=master)](https://travis-ci.org/appleboy/drone-telegram) [![codecov](https://codecov.io/gh/appleboy/drone-telegram/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/drone-telegram) [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/drone-telegram)](https://goreportcard.com/report/github.com/appleboy/drone-telegram) + +[Drone](https://github.com/drone/drone) plugin for sending telegram notifications. + +## Feature + +* [x] Send with Text Message. (`markdown` or `html` format) +* [x] Send with New Photo. +* [x] Send with New Document. +* [x] Send with New Audio. +* [x] Send with New Voice. +* [x] Send with New Location. +* [x] Send with New Venue. +* [x] Send with New Video. +* [x] Send with New Sticker. + +## Build + +Build the binary with the following commands: + +``` +$ make build +``` + +## Testing + +Test the package with the following command: + +``` +$ make test +``` + +## Docker + +Build the docker image with the following commands: + +``` +$ make docker +``` + +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-telegram' not found or does not exist.. +``` + +## Usage + +Execute from the working directory: + +``` +docker run --rm \ + -e PLUGIN_TOKEN=xxxxxxx \ + -e PLUGIN_TO=xxxxxxx \ + -e PLUGIN_MESSAGE=test \ + -e PLUGIN_PHOTO=tests/github.png \ + -e PLUGIN_DOCUMENT=tests/gophercolor.png \ + -e PLUGIN_STICKER=tests/github-logo.png \ + -e PLUGIN_AUDIO=tests/audio.mp3 \ + -e PLUGIN_VOICE=tests/voice.ogg \ + -e PLUGIN_LOCATION=24.9163213,121.1424972 \ + -e PLUGIN_VENUE=24.9163213,121.1424972,title,address \ + -e PLUGIN_VIDEO=tests/video.mp4 \ + -e PLUGIN_DEBUG=true \ + -e PLUGIN_FORMAT=markdown \ + -e DRONE_REPO_OWNER=appleboy \ + -e DRONE_REPO_NAME=go-hello \ + -e DRONE_COMMIT_SHA=e5e82b5eb3737205c25955dcc3dcacc839b7be52 \ + -e DRONE_COMMIT_BRANCH=master \ + -e DRONE_COMMIT_AUTHOR=appleboy \ + -e DRONE_BUILD_NUMBER=1 \ + -e DRONE_BUILD_STATUS=success \ + -e DRONE_BUILD_LINK=http://github.com/appleboy/go-hello \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + appleboy/drone-telegram +``` diff --git a/main.go b/main.go new file mode 100644 index 0000000..643a2ae --- /dev/null +++ b/main.go @@ -0,0 +1,172 @@ +package main + +import ( + "os" + + _ "github.com/joho/godotenv/autoload" + "github.com/urfave/cli" +) + +// Version for command line +var Version string + +func main() { + app := cli.NewApp() + app.Name = "telegram plugin" + app.Usage = "telegram plugin" + app.Action = run + app.Version = Version + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "base.url", + Usage: "jenkins base url", + EnvVar: "PLUGIN_BASE_URL,JENKINS_BASE_URL", + }, + cli.StringFlag{ + Name: "token", + Usage: "jenkins token", + EnvVar: "PLUGIN_TOKEN,JENKINS_TOKEN", + }, + cli.StringSliceFlag{ + Name: "to", + Usage: "telegram user", + EnvVar: "PLUGIN_TO", + }, + cli.StringSliceFlag{ + Name: "message", + Usage: "send telegram message", + EnvVar: "PLUGIN_MESSAGE", + }, + cli.StringSliceFlag{ + Name: "photo", + Usage: "send photo message", + EnvVar: "PLUGIN_PHOTO", + }, + cli.StringSliceFlag{ + Name: "document", + Usage: "send document message", + EnvVar: "PLUGIN_DOCUMENT", + }, + cli.StringSliceFlag{ + Name: "sticker", + Usage: "send sticker message", + EnvVar: "PLUGIN_STICKER", + }, + cli.StringSliceFlag{ + Name: "audio", + Usage: "send audio message", + EnvVar: "PLUGIN_AUDIO", + }, + cli.StringSliceFlag{ + Name: "voice", + Usage: "send voice message", + EnvVar: "PLUGIN_VOICE", + }, + cli.StringSliceFlag{ + Name: "location", + Usage: "send location message", + EnvVar: "PLUGIN_LOCATION", + }, + cli.StringSliceFlag{ + Name: "venue", + Usage: "send venue message", + EnvVar: "PLUGIN_VENUE", + }, + cli.StringSliceFlag{ + Name: "video", + Usage: "send video message", + EnvVar: "PLUGIN_VIDEO", + }, + cli.BoolFlag{ + Name: "debug", + Usage: "enable debug message", + EnvVar: "PLUGIN_DEBUG", + }, + cli.StringFlag{ + Name: "format", + Value: "markdown", + Usage: "telegram message format", + EnvVar: "PLUGIN_FORMAT", + }, + cli.StringFlag{ + Name: "repo.owner", + Usage: "repository owner", + EnvVar: "DRONE_REPO_OWNER", + }, + cli.StringFlag{ + Name: "repo.name", + Usage: "repository name", + EnvVar: "DRONE_REPO_NAME", + }, + cli.StringFlag{ + Name: "commit.sha", + Usage: "git commit sha", + EnvVar: "DRONE_COMMIT_SHA", + }, + cli.StringFlag{ + Name: "commit.branch", + Value: "master", + Usage: "git commit branch", + EnvVar: "DRONE_COMMIT_BRANCH", + }, + cli.StringFlag{ + Name: "commit.author", + Usage: "git author name", + EnvVar: "DRONE_COMMIT_AUTHOR", + }, + cli.StringFlag{ + Name: "commit.message", + Usage: "commit message", + EnvVar: "DRONE_COMMIT_MESSAGE", + }, + cli.StringFlag{ + Name: "build.event", + Value: "push", + Usage: "build event", + EnvVar: "DRONE_BUILD_EVENT", + }, + cli.IntFlag{ + Name: "build.number", + Usage: "build number", + EnvVar: "DRONE_BUILD_NUMBER", + }, + cli.StringFlag{ + Name: "build.status", + Usage: "build status", + Value: "success", + EnvVar: "DRONE_BUILD_STATUS", + }, + cli.StringFlag{ + Name: "build.link", + Usage: "build link", + EnvVar: "DRONE_BUILD_LINK", + }, + } + app.Run(os.Args) +} + +func run(c *cli.Context) error { + plugin := Plugin{ + Repo: Repo{ + Owner: c.String("repo.owner"), + Name: c.String("repo.name"), + }, + Build: Build{ + Number: c.Int("build.number"), + Event: c.String("build.event"), + Status: c.String("build.status"), + Commit: c.String("commit.sha"), + Branch: c.String("commit.branch"), + Author: c.String("commit.author"), + Message: c.String("commit.message"), + Link: c.String("build.link"), + }, + Config: Config{ + URL: c.String("base.url"), + Token: c.String("token"), + Job: c.StringSlice("job"), + }, + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..c4c4228 --- /dev/null +++ b/plugin.go @@ -0,0 +1,72 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "strconv" + "strings" + + "gopkg.in/telegram-bot-api.v4" +) + +type ( + // Repo information. + Repo struct { + Owner string + Name string + } + + // Build information. + Build struct { + Event string + Number int + Commit string + Message string + Branch string + Author string + Status string + Link string + } + + // Config for the plugin. + Config struct { + URL string + Token string + Job []string + } + + // Plugin values. + Plugin struct { + Repo Repo + Build Build + Config Config + } +) + +func trimElement(keys []string) []string { + var newKeys []string + + for _, value := range keys { + value = strings.Trim(value, " ") + if len(value) == 0 { + continue + } + newKeys = append(newKeys, value) + } + + return newKeys +} + +// Exec executes the plugin. +func (p Plugin) Exec() error { + + if len(p.Config.Token) == 0 || len(p.Config.To) == 0 { + log.Println("missing jenkins auth config") + + return errors.New("missing jenkins auth config") + } + + return nil +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..62d5656 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,214 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + + "os" + "testing" +) + +func TestMissingDefaultConfig(t *testing.T) { + var plugin Plugin + + err := plugin.Exec() + + assert.NotNil(t, err) +} + +func TestMissingUserConfig(t *testing.T) { + plugin := Plugin{ + Config: Config{ + Token: "123456789", + }, + } + + err := plugin.Exec() + + assert.NotNil(t, err) +} + +func TestDefaultMessageFormat(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update travis", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + } + + message := plugin.Message(plugin.Repo, plugin.Build) + + assert.Equal(t, []string{"[success] (master)『update travis』by Bo-Yi Wu"}, message) +} + +func TestSendMessage(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update travis by drone plugin", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + + Config: Config{ + Token: os.Getenv("TELEGRAM_TOKEN"), + To: []string{os.Getenv("TELEGRAM_TO"), "中文ID", "1234567890"}, + Message: []string{"Test Telegram Chat Bot From Travis or Local", " "}, + Photo: []string{"tests/github.png", "1234", " "}, + Document: []string{"tests/gophercolor.png", "1234", " "}, + Sticker: []string{"tests/github-logo.png", "tests/github.png", "1234", " "}, + Audio: []string{"tests/audio.mp3", "1234", " "}, + Voice: []string{"tests/voice.ogg", "1234", " "}, + Location: []string{"24.9163213,121.1424972", "1", " "}, + Venue: []string{"35.661777,139.704051,竹北體育館,新竹縣竹北市", "24.9163213,121.1424972", "1", " "}, + Video: []string{"tests/video.mp4", "1234", " "}, + Debug: false, + }, + } + + err := plugin.Exec() + assert.Nil(t, err) + + // disable message + plugin.Config.Message = []string{} + err = plugin.Exec() + assert.Nil(t, err) +} + +func TestBotError(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update travis by drone plugin", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + + Config: Config{ + Token: "appleboy", + To: []string{os.Getenv("TELEGRAM_TO"), "中文ID", "1234567890"}, + Message: []string{"Test Telegram Chat Bot From Travis or Local", " "}, + }, + } + + err := plugin.Exec() + assert.NotNil(t, err) +} + +func TestTrimElement(t *testing.T) { + var input, result []string + + input = []string{"1", " ", "3"} + result = []string{"1", "3"} + + assert.Equal(t, result, trimElement(input)) + + input = []string{"1", "2"} + result = []string{"1", "2"} + + assert.Equal(t, result, trimElement(input)) +} + +func TestParseID(t *testing.T) { + var input []string + var result []int64 + + input = []string{"1", "測試", "3"} + result = []int64{int64(1), int64(3)} + + assert.Equal(t, result, parseID(input)) + + input = []string{"1", "2"} + result = []int64{int64(1), int64(2)} + + assert.Equal(t, result, parseID(input)) +} + +func TestCheckFileExist(t *testing.T) { + var input []string + var result []string + + input = []string{"tests/gophercolor.png", "測試", "3"} + result = []string{"tests/gophercolor.png"} + + assert.Equal(t, result, fileExist(input)) +} + +func TestConvertLocation(t *testing.T) { + var input string + var result Location + var empty bool + + input = "1" + result, empty = convertLocation(input) + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + // strconv.ParseInt: parsing "測試": invalid syntax + input = "測試,139.704051" + result, empty = convertLocation(input) + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + // strconv.ParseInt: parsing "測試": invalid syntax + input = "35.661777,測試" + result, empty = convertLocation(input) + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + input = "35.661777,139.704051" + result, empty = convertLocation(input) + + assert.Equal(t, false, empty) + assert.Equal(t, Location{ + Latitude: float64(35.661777), + Longitude: float64(139.704051), + }, result) + + input = "35.661777,139.704051,title" + result, empty = convertLocation(input) + + assert.Equal(t, false, empty) + assert.Equal(t, Location{ + Title: "title", + Address: "", + Latitude: float64(35.661777), + Longitude: float64(139.704051), + }, result) + + input = "35.661777,139.704051,title,address" + result, empty = convertLocation(input) + + assert.Equal(t, false, empty) + assert.Equal(t, Location{ + Title: "title", + Address: "address", + Latitude: float64(35.661777), + Longitude: float64(139.704051), + }, result) +}