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