From 56feed0dab547214e8323477afe498ea2178d063 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 4 Oct 2016 21:45:49 +0800 Subject: [PATCH] initial project. Signed-off-by: Bo-Yi Wu --- .editorconfig | 42 ++++++++++++++++ .gitignore | 3 ++ .travis.yml | 53 ++++++++++++++++++++ Dockerfile | 5 ++ Dockerfile.armhf | 8 +++ Makefile | 45 +++++++++++++++++ README.md | 57 +++++++++++++++++++++ glide.yaml | 11 +++++ main.go | 116 +++++++++++++++++++++++++++++++++++++++++++ plugin.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++ plugin_test.go | 111 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 576 insertions(+) create mode 100644 .editorconfig create mode 100644 .travis.yml create mode 100644 Dockerfile create mode 100644 Dockerfile.armhf create mode 100644 Makefile create mode 100644 README.md create mode 100644 glide.yaml create mode 100644 main.go create mode 100644 plugin.go create mode 100644 plugin_test.go 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..6ad8e61 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM centurylink/ca-certs + +ADD drone-telegram / + +ENTRYPOINT ["/drone-telegram"] 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/Makefile b/Makefile new file mode 100644 index 0000000..2774617 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.PHONY: + +VERSION := $(shell git describe --tags || git rev-parse --short HEAD) +DEPLOY_ACCOUNT := "appleboy" +DEPLOY_IMAGE := "drone-telegram" + +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 new file mode 100644 index 0000000..171a4c6 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# 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 plugin for sending telegram notifications. + +## 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 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 \ + appleboy/drone-telegram +``` diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..48a0e12 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,11 @@ +package: github.com/appleboy/drone-telegram +import: +- package: github.com/joho/godotenv + subpackages: + - autoload +- package: github.com/urfave/cli +- package: gopkg.in/telegram-bot-api.v4 +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert diff --git a/main.go b/main.go new file mode 100644 index 0000000..d25336a --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +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: "telegram.token", + Usage: "telegram token", + EnvVar: "PLUGIN_TELEGRAM_TOKEN,TELEGRAM_TOKEN", + }, + cli.StringSliceFlag{ + Name: "to", + Usage: "send message to user", + EnvVar: "PLUGIN_TO", + }, + cli.StringSliceFlag{ + Name: "message", + Usage: "send telegram message", + EnvVar: "PLUGIN_MESSAGE", + }, + 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{ + Token: c.String("telegram.token"), + To: c.StringSlice("to"), + Message: c.StringSlice("message"), + }, + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..1aa60ef --- /dev/null +++ b/plugin.go @@ -0,0 +1,125 @@ +package main + +import ( + "errors" + "fmt" + "log" + "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 { + Token string + To []string + Message []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 +} + +func parseID(keys []string) []int64 { + var newKeys []int64 + + for _, value := range keys { + id, err := strconv.ParseInt(value, 10, 64) + if err != nil { + log.Println(err.Error()) + + continue + } + newKeys = append(newKeys, id) + } + + 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 telegram token or user list") + + return errors.New("missing telegram token or user list") + } + + var message []string + if len(p.Config.Message) > 0 { + message = p.Config.Message + } else { + message = p.Message(p.Repo, p.Build) + } + + bot, err := tgbotapi.NewBotAPI("219364688:AAETlB6U1OZGnadM6-pLC1QUiXdeHlPUu1E") + + if err != nil { + log.Println(err.Error()) + + return err + } + + bot.Debug = true + + // parse ids + ids := parseID(p.Config.To) + + // send message. + for _, user := range ids { + for _, value := range trimElement(message) { + msg := tgbotapi.NewMessage(user, value) + bot.Send(msg) + } + } + + return nil +} + +// Message is plugin default message. +func (p Plugin) Message(repo Repo, build Build) []string { + return []string{fmt.Sprintf("[%s] <%s> (%s)『%s』by %s", + build.Status, + build.Link, + build.Branch, + build.Message, + build.Author, + )} +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..e8c07c2 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,111 @@ +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", " "}, + }, + } + + err := plugin.Exec() + assert.Nil(t, err) + + // disable message + plugin.Config.Message = []string{} + err = plugin.Exec() + assert.Nil(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)) +}