├── .envsample ├── .github ├── dependabot.yml └── workflows │ ├── automerge.yml │ ├── go.yml │ └── release.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── internal └── workers │ ├── pong.go │ ├── trigger_actions.go │ ├── trigger_actions_test.go │ └── workers.go ├── main.go └── manifests ├── deployment.yml ├── redis.yml └── service.yml /.envsample: -------------------------------------------------------------------------------- 1 | SLACK_SIGNING_SECRET= 2 | SLACK_BOT_TOKEN= 3 | GITHUB_TOKEN= 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: 3 | pull_request_target: 4 | 5 | jobs: 6 | automerge: 7 | runs-on: ubuntu-latest 8 | if: ${{ github.actor == 'dependabot[bot]' }} 9 | steps: 10 | - name: Dependabot metadata 11 | uses: dependabot/fetch-metadata@v1 12 | id: metadata 13 | - name: Wait for status checks 14 | uses: lewagon/wait-on-check-action@v1.3.3 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | ref: ${{ github.event.pull_request.head.sha || github.sha }} 18 | check-regexp: build* 19 | wait-interval: 30 20 | - name: Auto-merge for Dependabot PRs 21 | if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 22 | run: gh pr merge --auto --merge "$PR_URL" 23 | env: 24 | PR_URL: ${{github.event.pull_request.html_url}} 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | services: 13 | redis: 14 | image: redis 15 | ports: 16 | - 6379:6379 17 | options: --health-cmd "redis-cli -h localhost ping" --health-interval 10s --health-timeout 5s --health-retries 15 18 | env: 19 | REDIS_HOST: redis 20 | REDIS_PORT: 6379 21 | env: 22 | REDIS_HOST: redis:6379 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Set up Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version-file: 'go.mod' 30 | 31 | - name: Build 32 | run: go build -v ./... 33 | 34 | - name: Test 35 | run: go test -v ./... 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Docker Image 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | if: "contains(github.ref, 'master')" 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: '0' 17 | token: ${{secrets.GITHUB_TOKEN}} 18 | - run: sudo apt update -qqy && sudo apt install -qqy wget 19 | - run: make releasedeps 20 | - run: git-semv now 21 | - run: make build_image 22 | - uses: docker/login-action@v1 23 | with: 24 | username: ${{ secrets.DOCKERHUB_USERNAME }} 25 | password: ${{ secrets.DOCKERHUB_TOKEN }} 26 | - run: make push_image 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | github-actions-trigger 3 | 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | RUN apk --update add --no-cache ca-certificates tzdata && \ 3 | apk upgrade --no-cache && \ 4 | cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ 5 | rm -rf /var/cache/apk/* 6 | RUN adduser -S bot \ 7 | && echo "bot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ 8 | && echo 'bot:bot' | chpasswd 9 | USER bot 10 | COPY github-actions-trigger / 11 | CMD ["/github-actions-trigger"] 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell git-semv now | sed -e 's/v//g')-$(shell git show --format='%h' --no-patch) 2 | run: 3 | go run . 4 | 5 | 6 | ## release_major: release nke (major) 7 | release_major: releasedeps 8 | git semv major --bump 9 | 10 | .PHONY: release_minor 11 | ## release_minor: release nke (minor) 12 | release_minor: releasedeps 13 | git semv minor --bump 14 | 15 | .PHONY: release_patch 16 | ## release_patch: release nke (patch) 17 | release_patch: releasedeps 18 | git semv patch --bump 19 | 20 | 21 | build: releasedeps 22 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o github-actions-trigger main.go 23 | 24 | build_image: build 25 | export DOCKER_CONTENT_TRUST=1 26 | docker build -t pyama/github-actions-trigger:$(VERSION) . 27 | 28 | push_image: 29 | docker push pyama/github-actions-trigger:$(VERSION) 30 | docker tag pyama/github-actions-trigger:$(VERSION) pyama/github-actions-trigger:latest 31 | docker push pyama/github-actions-trigger:latest 32 | test: 33 | go test github.com/pyama86/github-actions-trigger-bot/... 34 | ## release_major: release nke (major) 35 | release_major: releasedeps 36 | git semv major --bump 37 | 38 | .PHONY: release_minor 39 | ## release_minor: release nke (minor) 40 | release_minor: releasedeps 41 | git semv minor --bump 42 | 43 | .PHONY: release_patch 44 | ## release_patch: release nke (patch) 45 | release_patch: releasedeps 46 | git semv patch --bump 47 | 48 | .PHONY: releasedeps 49 | releasedeps: git-semv 50 | 51 | .PHONY: git-semv 52 | git-semv: 53 | ifeq ($(shell uname),Linux) 54 | which git-semv || (wget https://github.com/linyows/git-semv/releases/download/v1.2.0/git-semv_linux_x86_64.tar.gz && tar zxvf git-semv_linux_x86_64.tar.gz && sudo mv git-semv /usr/bin/) 55 | else 56 | which git-semv > /dev/null || brew tap linyows/git-semv 57 | which git-semv > /dev/null || brew install git-semv 58 | endif 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Actions Triggger Bot 2 | 3 | It is GitHub Actoins Trigger Bot For Slack. 4 | 5 | ## setup 6 | ### 概要 7 | Kubernetes上で動くアプリをSlack Appとして登録し、イベントをサブスクライブし、応答します。 8 | 主に必要な情報はSlack、GitHub(Enterprise)の認証情報です。 9 | 動かしてみたいけど、ハマってよくわからんというかたは、issueを立てていただければサポートします。 10 | 11 | 12 | ### kubernetes 13 | ``` 14 | $ kubectl apply -f manifests 15 | ``` 16 | 17 | GitHub Enterpriseで利用する場合は、エンドポイントの設定が必要です。 18 | 19 | ```yaml 20 | env: 21 | - name: GITHUB_API 22 | value: "https://git.your.example.com/api/v3/" 23 | - name: GITHUB_UPLOADS 24 | value: "https://uploads.your.example.com" 25 | ``` 26 | 27 | Slackの設定はSlack Appを作成し、`Event Subscriptions` メニューから、`Subscribe to bot events` を選択し、`app_mention` イベントをアプリで受け取る必要があります。 28 | 29 | 30 | ## author 31 | - pyama86 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pyama86/github-actions-trigger-bot 2 | 3 | go 1.23.2 4 | 5 | require ( 6 | github.com/caarlos0/env/v6 v6.10.1 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/google/go-github/v36 v36.0.0 9 | github.com/joho/godotenv v1.5.1 10 | github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb 11 | github.com/k0kubun/pp v3.0.1+incompatible 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/slack-go/slack v0.17.0 14 | github.com/thoas/go-funk v0.9.3 15 | golang.org/x/oauth2 v0.30.0 16 | ) 17 | 18 | require ( 19 | github.com/bitly/go-simplejson v0.5.0 // indirect 20 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 21 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 22 | github.com/customerio/gospec v0.0.0-20130710230057-a5cc0e48aa39 // indirect 23 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 24 | github.com/garyburd/redigo v1.6.2 // indirect 25 | github.com/google/go-querystring v1.0.0 // indirect 26 | github.com/gorilla/websocket v1.5.3 // indirect 27 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect 28 | github.com/kr/pretty v0.3.1 // indirect 29 | github.com/mattn/go-colorable v0.1.8 // indirect 30 | github.com/mattn/go-isatty v0.0.12 // indirect 31 | golang.org/x/crypto v0.36.0 // indirect 32 | golang.org/x/net v0.38.0 // indirect 33 | golang.org/x/sys v0.31.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 2 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 3 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 4 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 5 | github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= 6 | github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= 7 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 8 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/customerio/gospec v0.0.0-20130710230057-a5cc0e48aa39 h1:O0YTztXI3XeJXlFhSo4wNb0VBVqSgT+hi/CjNWKvMnY= 11 | github.com/customerio/gospec v0.0.0-20130710230057-a5cc0e48aa39/go.mod h1:OzYUFhPuL2JbjwFwrv6CZs23uBawekc6OZs+g19F0mY= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 16 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 17 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 18 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 19 | github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= 20 | github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 21 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 22 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 23 | github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= 24 | github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 25 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 27 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs= 29 | github.com/google/go-github/v36 v36.0.0/go.mod h1:LFlKC047IOqiglRGNqNb9s/iAPTnnjtlshm+bxp+kwk= 30 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 31 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 32 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 33 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 34 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 35 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 36 | github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb h1:y9LFhCM3gwK94Xz9/h7GcSVLteky9pFHEkP04AqQupA= 37 | github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb/go.mod h1:ziQRRNHCWZe0wVNzF8y8kCWpso0VMpqHJjB19DSenbE= 38 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= 39 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= 40 | github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= 41 | github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= 42 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 43 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 44 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 45 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 46 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 47 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 48 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 49 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 50 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 51 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 52 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 53 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 54 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 55 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 56 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 57 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 60 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 61 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 62 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 63 | github.com/slack-go/slack v0.17.0 h1:Vqd4GGIcwwgEu80GBs3cXoPPho5bkDGSFnuZbSG0NhA= 64 | github.com/slack-go/slack v0.17.0/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk= 65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 67 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 69 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 70 | github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= 71 | github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= 72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 73 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 74 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 75 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 76 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 77 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 78 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 79 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 80 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 81 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 82 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 86 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 87 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 88 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 89 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 90 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 92 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 93 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 94 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 95 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 96 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 97 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 98 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 99 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 100 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 101 | -------------------------------------------------------------------------------- /internal/workers/pong.go: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jrallison/go-workers" 7 | "github.com/slack-go/slack" 8 | "github.com/slack-go/slack/slackevents" 9 | ) 10 | 11 | type PongParam struct { 12 | Event *slackevents.AppMentionEvent 13 | } 14 | 15 | func Pong(message *workers.Msg) { 16 | param := new(PongParam) 17 | if err := parseMessage(param, message); err != nil { 18 | panicWithLog(err) 19 | } 20 | 21 | api := slack.New(os.Getenv("SLACK_BOT_TOKEN")) 22 | if _, _, err := api.PostMessage(param.Event.Channel, slack.MsgOptionText("pong or sing a song?", false)); err != nil { 23 | panicWithLog(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/workers/trigger_actions.go: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | "github.com/caarlos0/env/v6" 13 | "github.com/go-redis/redis/v8" 14 | "github.com/google/go-github/v36/github" 15 | "github.com/jrallison/go-workers" 16 | "github.com/sirupsen/logrus" 17 | "github.com/slack-go/slack" 18 | "github.com/slack-go/slack/slackevents" 19 | "github.com/thoas/go-funk" 20 | "golang.org/x/oauth2" 21 | ) 22 | 23 | var commonRegexp = `\w\-\_\.` 24 | var triggerActionsBaseExp = regexp.MustCompile(`(?P[^\/]+)\/(?P[` + 25 | commonRegexp + `]+)[^\w]+(?P[` + commonRegexp + `]+)`) 26 | var triggerActionsParamsExp = regexp.MustCompile(`([\S]+:[\S]+)`) 27 | 28 | var requireParams = []string{"repo", "org", "task"} 29 | 30 | type TriggerActionsParams struct { 31 | Event *slackevents.AppMentionEvent 32 | } 33 | 34 | type config struct { 35 | UnlockTaskName string `env:"ACTIONS_UNLOCK_TASKNAME" envDefault:"unlock"` 36 | LockKeyParamsKey string `env:"ACTIONS_LOCK_KEY" envDefault:"stage"` 37 | LockValueParamsKey string `env:"ACTIONS_LOCK_VALUE" envDefault:"user"` 38 | LockTTLParamsKey string `env:"ACTIONS_LOCK_TTL" envDefault:"ttl"` 39 | } 40 | 41 | func parseTriggerMessage(text string) map[string]string { 42 | match := triggerActionsBaseExp.FindAllStringSubmatch(text, -1) 43 | result := make(map[string]string) 44 | if len(match) == 0 { 45 | return nil 46 | } 47 | 48 | for i, name := range triggerActionsBaseExp.SubexpNames() { 49 | if i != 0 && name != "" && funk.ContainsString(requireParams, name) { 50 | result[name] = match[0][i] 51 | } 52 | } 53 | 54 | match = triggerActionsParamsExp.FindAllStringSubmatch(text, -1) 55 | if len(match) == 0 { 56 | return result 57 | } 58 | for _, v := range match { 59 | if strings.Index(v[0], ":") > 0 { 60 | kv := strings.SplitN(v[0], ":", 2) 61 | result[kv[0]] = kv[1] 62 | } 63 | } 64 | 65 | return result 66 | } 67 | 68 | func canLock(ctx context.Context, key, value, ttl string) (bool, string, string, error) { 69 | 70 | t, err := time.ParseDuration(ttl) 71 | if err != nil { 72 | return false, "", "", err 73 | } 74 | lock, err := redisClient.SetNX(ctx, key, value, t).Result() 75 | if err != nil { 76 | return false, "", "", err 77 | } 78 | 79 | setTTL, err := redisClient.TTL(ctx, key).Result() 80 | if err != nil { 81 | return false, "", "", err 82 | } 83 | 84 | expireAt := time.Now().Add(setTTL).Format("2006/01/02 15:04:05") 85 | 86 | if !lock { 87 | v, err := redisClient.Get(ctx, key).Result() 88 | if err != nil { 89 | return false, "", "", err 90 | } 91 | 92 | return v == value, v, expireAt, nil 93 | } 94 | return true, value, expireAt, nil 95 | } 96 | 97 | func unlock(ctx context.Context, key string, result map[string]string, cfg *config, param *TriggerActionsParams, api *slack.Client) error { 98 | val, err := redisClient.Get(ctx, key).Result() 99 | if err != nil { 100 | if err == redis.Nil { 101 | if _, _, err := api.PostMessage( 102 | param.Event.Channel, 103 | slack.MsgOptionText(fmt.Sprintf("%s/%s hasn't any lock %s", result["org"], result["repo"], result[cfg.LockKeyParamsKey]), false)); err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | return err 109 | } 110 | 111 | if val == result[cfg.LockValueParamsKey] { 112 | _, err := redisClient.Del(ctx, key).Result() 113 | if err != nil { 114 | return err 115 | } 116 | if _, _, err := api.PostMessage( 117 | param.Event.Channel, 118 | slack.MsgOptionText(fmt.Sprintf("%s/%s release lock from %s", result["org"], result["repo"], val), false)); err != nil { 119 | return err 120 | } 121 | } else { 122 | if _, _, err := api.PostMessage( 123 | param.Event.Channel, 124 | slack.MsgOptionText(fmt.Sprintf("%s/%s don't release lock, because lock owner is %s", result["org"], result["repo"], val), false)); err != nil { 125 | return err 126 | } 127 | } 128 | return nil 129 | 130 | } 131 | func TriggerActions(message *workers.Msg) { 132 | param := new(TriggerActionsParams) 133 | if err := parseMessage(param, message); err != nil { 134 | panicWithLog(err) 135 | } 136 | 137 | api := slack.New(os.Getenv("SLACK_BOT_TOKEN")) 138 | text := strings.Split(param.Event.Text, " ") 139 | result := parseTriggerMessage(strings.Join(text[1:], " ")) 140 | 141 | if result == nil { 142 | if _, _, err := api.PostMessage( 143 | param.Event.Channel, 144 | slack.MsgOptionText("format error, please send me [@botname : ...]", false)); err != nil { 145 | panicWithLog(err) 146 | } 147 | } 148 | 149 | for _, r := range requireParams { 150 | if result[r] == "" { 151 | if _, _, err := api.PostMessage( 152 | param.Event.Channel, 153 | slack.MsgOptionText("format error, please send me [@botname : ...]", false)); err != nil { 154 | panicWithLog(err) 155 | } 156 | return 157 | 158 | } 159 | } 160 | cfg := &config{} 161 | if err := env.Parse(cfg); err != nil { 162 | panicWithLog(err) 163 | } 164 | 165 | ctx := context.Background() 166 | 167 | if result[cfg.LockKeyParamsKey] != "" && result[cfg.LockValueParamsKey] != "" { 168 | key := strings.Join([]string{ 169 | result["org"], 170 | result["repo"], 171 | result[cfg.LockKeyParamsKey], 172 | }, "-") 173 | 174 | if result["task"] == cfg.UnlockTaskName { 175 | if err := unlock(ctx, key, result, cfg, param, api); err != nil { 176 | panicWithLog(err) 177 | } 178 | return 179 | } 180 | 181 | if result[cfg.LockTTLParamsKey] != "" { 182 | getLock, lockValue, expireAt, err := canLock(ctx, key, result[cfg.LockValueParamsKey], result[cfg.LockTTLParamsKey]) 183 | if err != nil { 184 | panicWithLog(err) 185 | } 186 | logrus.Infof("get lock %s from %s", result[cfg.LockValueParamsKey], result[cfg.LockTTLParamsKey]) 187 | 188 | if !getLock { 189 | warn := "*========== WARNING ==========*" 190 | if _, _, err := api.PostMessage( 191 | param.Event.Channel, 192 | slack.MsgOptionText(fmt.Sprintf("%s\n%s/%s is locking from %s until %s\n%s", warn, result["org"], result["repo"], lockValue, expireAt, warn), false)); err != nil { 193 | panicWithLog(err) 194 | } 195 | return 196 | } 197 | } 198 | } 199 | 200 | ts := oauth2.StaticTokenSource( 201 | &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")}, 202 | ) 203 | tc := oauth2.NewClient(ctx, ts) 204 | client := github.NewClient(tc) 205 | 206 | if os.Getenv("GITHUB_API") != "" && os.Getenv("GITHUB_UPLOADS") != "" { 207 | c, err := github.NewEnterpriseClient(os.Getenv("GITHUB_API"), os.Getenv("GITHUB_UPLOADS"), tc) 208 | if err != nil { 209 | panicWithLog(err) 210 | } 211 | client = c 212 | } 213 | 214 | if result["branch"] != "" { 215 | result["ref"] = result["branch"] 216 | 217 | } 218 | 219 | bytes, err := json.Marshal(result) 220 | if err != nil { 221 | panicWithLog(err) 222 | } 223 | 224 | payload := json.RawMessage(bytes) 225 | input := github.DispatchRequestOptions{EventType: result["task"], ClientPayload: &payload} 226 | 227 | startTime := time.Now() 228 | logrus.Infof("github actions payload %s", string(payload)) 229 | _, _, err = client.Repositories.Dispatch(ctx, result["org"], result["repo"], input) 230 | if err != nil { 231 | panicWithLog(err) 232 | } 233 | 234 | var resultMessage = "" 235 | try := 30 236 | perPage := 100 237 | totalCount := 1 238 | L: 239 | for range make([]int, try) { 240 | page := 1 241 | for (page-1)*perPage < totalCount { 242 | wfr, _, err := client.Actions.ListRepositoryWorkflowRuns(ctx, result["org"], result["repo"], &github.ListWorkflowRunsOptions{ 243 | Event: "repository_dispatch", 244 | ListOptions: github.ListOptions{Page: page, PerPage: perPage}, 245 | }) 246 | if err != nil { 247 | logrus.Error(err) 248 | break L 249 | } 250 | totalCount = *wfr.TotalCount 251 | page++ 252 | 253 | if wfr != nil && len(wfr.WorkflowRuns) > 0 { 254 | for _, w := range wfr.WorkflowRuns { 255 | logrus.Infof("task: %s, start_at: %s, created_at: %s", *w.Name, startTime.Local(), w.CreatedAt.Local()) 256 | if startTime.Local().Before(w.CreatedAt.Local()) || startTime.Local().Equal(w.CreatedAt.Local()) { 257 | resultMessage = fmt.Sprintf("%s/%s %s is starting %s", 258 | result["org"], result["repo"], *w.Name, *w.HTMLURL) 259 | break L 260 | } 261 | } 262 | } 263 | } 264 | time.Sleep(1 * time.Second) 265 | } 266 | 267 | if resultMessage == "" { 268 | resultMessage = fmt.Sprintf("%s/%s %s is starting", result["org"], result["repo"], result["task"]) 269 | } 270 | 271 | if _, _, err := api.PostMessage(param.Event.Channel, slack.MsgOptionText(resultMessage, false)); err != nil { 272 | panicWithLog(err) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /internal/workers/trigger_actions_test.go: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/k0kubun/pp" 9 | ) 10 | 11 | func Test_parseTriggerMessage(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | text string 15 | want map[string]string 16 | }{ 17 | { 18 | name: "ok", 19 | text: "org/repo task branch:default", 20 | want: map[string]string{ 21 | "org": "org", 22 | "repo": "repo", 23 | "task": "task", 24 | "branch": "default", 25 | }, 26 | }, 27 | { 28 | name: "unmatch", 29 | text: "unmatch", 30 | want: nil, 31 | }, 32 | { 33 | name: "ok", 34 | text: "org/repo task foo:bar hoge:fuga", 35 | want: map[string]string{ 36 | "org": "org", 37 | "repo": "repo", 38 | "task": "task", 39 | "foo": "bar", 40 | "hoge": "fuga", 41 | }, 42 | }, 43 | { 44 | name: "ok", 45 | text: "org/repo task email:", 46 | want: map[string]string{ 47 | "org": "org", 48 | "repo": "repo", 49 | "task": "task", 50 | "email": "", 51 | }, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | if got := parseTriggerMessage(tt.text); !reflect.DeepEqual(got, tt.want) { 57 | pp.Println(got) 58 | t.Errorf("parseTriggerMessage() = %v, want %v", got, tt.want) 59 | } 60 | }) 61 | } 62 | } 63 | 64 | func Test_canLock(t *testing.T) { 65 | type args struct { 66 | key string 67 | value string 68 | ttl string 69 | } 70 | tests := []struct { 71 | name string 72 | args args 73 | want bool 74 | wantErr bool 75 | }{ 76 | { 77 | name: "ok", 78 | args: args{ 79 | key: "test", 80 | value: "value", 81 | ttl: "3s", 82 | }, 83 | want: true, 84 | wantErr: false, 85 | }, 86 | } 87 | for _, tt := range tests { 88 | ctx := context.Background() 89 | t.Run(tt.name, func(t *testing.T) { 90 | got, _, _, err := canLock(ctx, tt.args.key, tt.args.value, tt.args.ttl) 91 | if (err != nil) != tt.wantErr { 92 | t.Errorf("canLock() error = %v, wantErr %v", err, tt.wantErr) 93 | return 94 | } 95 | if got != tt.want { 96 | t.Errorf("canLock() = %v, want %v", got, tt.want) 97 | } 98 | 99 | got, _, _, err = canLock(ctx, tt.args.key, tt.args.value, tt.args.ttl) 100 | if (err != nil) != tt.wantErr { 101 | t.Errorf("canLock() error = %v, wantErr %v", err, tt.wantErr) 102 | return 103 | } 104 | 105 | if !got { 106 | t.Errorf("canLock() = %v, want %v", got, true) 107 | } 108 | 109 | got, _, _, err = canLock(ctx, tt.args.key, "other user", tt.args.ttl) 110 | if (err != nil) != tt.wantErr { 111 | t.Errorf("canLock() error = %v, wantErr %v", err, tt.wantErr) 112 | return 113 | } 114 | 115 | if got { 116 | t.Errorf("canLock() = %v, want %v", got, false) 117 | } 118 | }) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /internal/workers/workers.go: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strconv" 9 | 10 | "github.com/go-redis/redis/v8" 11 | goworkers "github.com/jrallison/go-workers" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var redisClient *redis.Client 16 | 17 | func init() { 18 | redisURL := "localhost:6379" 19 | redisDB := 0 20 | if os.Getenv("REDIS_URL") != "" { 21 | redisURL = os.Getenv("REDIS_URL") 22 | } 23 | if os.Getenv("REDIS_DB") != "" { 24 | rb, err := strconv.Atoi(os.Getenv("REDIS_DB")) 25 | if err != nil { 26 | panic(err) 27 | } 28 | redisDB = rb 29 | } 30 | redisClient = redis.NewClient( 31 | &redis.Options{ 32 | Addr: redisURL, 33 | Password: os.Getenv("REDIS_PASSWORD"), 34 | DB: redisDB, 35 | }) 36 | } 37 | func parseMessage(s interface{}, message *goworkers.Msg) error { 38 | b, err := message.Args().Encode() 39 | if err != nil { 40 | return runtimeError(err) 41 | } 42 | 43 | if err := json.Unmarshal(b, &s); err != nil { 44 | return runtimeError(err) 45 | } 46 | return nil 47 | } 48 | 49 | func runtimeError(err error) error { 50 | if err != nil { 51 | _, src, line, _ := runtime.Caller(1) 52 | e := fmt.Errorf("file: %s, line: %d, message: %s", src, line, err.Error()) 53 | return e 54 | } 55 | return err 56 | } 57 | 58 | func panicWithLog(e error) { 59 | logrus.Error(e) 60 | panic(e) 61 | } 62 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/joho/godotenv" 13 | goworkers "github.com/jrallison/go-workers" 14 | "github.com/pyama86/github-actions-trigger-bot/internal/workers" 15 | "github.com/sirupsen/logrus" 16 | "github.com/slack-go/slack" 17 | "github.com/slack-go/slack/slackevents" 18 | ) 19 | 20 | func main() { 21 | err := godotenv.Load() 22 | if err != nil { 23 | logrus.Warn("Error loading .env file") 24 | } 25 | reporeg := regexp.MustCompile(`\w+\/\w+`) 26 | 27 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 28 | w.WriteHeader(http.StatusOK) 29 | }) 30 | 31 | http.HandleFunc("/slack/events", func(w http.ResponseWriter, r *http.Request) { 32 | verifier, err := slack.NewSecretsVerifier(r.Header, os.Getenv("SLACK_SIGNING_SECRET")) 33 | if err != nil { 34 | logrus.Error(err) 35 | w.WriteHeader(http.StatusInternalServerError) 36 | return 37 | } 38 | 39 | bodyReader := io.TeeReader(r.Body, &verifier) 40 | body, err := ioutil.ReadAll(bodyReader) 41 | if err != nil { 42 | logrus.Error(err) 43 | w.WriteHeader(http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | if err := verifier.Ensure(); err != nil { 48 | logrus.Error(err) 49 | w.WriteHeader(http.StatusBadRequest) 50 | return 51 | } 52 | 53 | eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken()) 54 | if err != nil { 55 | logrus.Error(err) 56 | w.WriteHeader(http.StatusInternalServerError) 57 | return 58 | } 59 | 60 | switch eventsAPIEvent.Type { 61 | case slackevents.URLVerification: 62 | var res *slackevents.ChallengeResponse 63 | if err := json.Unmarshal(body, &res); err != nil { 64 | logrus.Error(err) 65 | w.WriteHeader(http.StatusInternalServerError) 66 | return 67 | } 68 | w.Header().Set("Content-Type", "text/plain") 69 | if _, err := w.Write([]byte(res.Challenge)); err != nil { 70 | logrus.Error(err) 71 | w.WriteHeader(http.StatusInternalServerError) 72 | return 73 | } 74 | case slackevents.CallbackEvent: 75 | innerEvent := eventsAPIEvent.InnerEvent 76 | switch event := innerEvent.Data.(type) { 77 | case *slackevents.AppMentionEvent: 78 | logrus.Info(event.Text) 79 | // for slack remind 80 | event.Text = strings.Replace(event.Text, "Reminder: ", "", -1) 81 | event.Text = strings.TrimSuffix(event.Text, ".") 82 | 83 | event.Text = strings.Replace(event.Text, "\u00a0", " ", -1) 84 | message := strings.Split(event.Text, " ") 85 | command := message[1] 86 | api := slack.New(os.Getenv("SLACK_BOT_TOKEN")) 87 | 88 | switch { 89 | case command == "ping": 90 | if _, _, err := api.PostMessage(event.Channel, slack.MsgOptionText("pong or sing a song?", false)); err != nil { 91 | logrus.Error(err) 92 | w.WriteHeader(http.StatusInternalServerError) 93 | } 94 | 95 | w.WriteHeader(http.StatusOK) 96 | case command == "help": 97 | if _, _, err := api.PostMessage(event.Channel, slack.MsgOptionText(" ...", false)); err != nil { 98 | logrus.Error(err) 99 | w.WriteHeader(http.StatusInternalServerError) 100 | } 101 | 102 | w.WriteHeader(http.StatusOK) 103 | case reporeg.MatchString(command): 104 | logrus.Info(command) 105 | _, err := goworkers.EnqueueWithOptions("trigger_actions", "Add", workers.TriggerActionsParams{ 106 | Event: event, 107 | }, goworkers.EnqueueOptions{Retry: true}) 108 | if err != nil { 109 | logrus.Error(err) 110 | w.WriteHeader(http.StatusInternalServerError) 111 | } 112 | 113 | } 114 | } 115 | } 116 | }) 117 | redisURL := "localhost:6379" 118 | redisDB := "0" 119 | if os.Getenv("REDIS_URL") != "" { 120 | redisURL = os.Getenv("REDIS_URL") 121 | } 122 | if os.Getenv("REDIS_DB") != "" { 123 | redisDB = os.Getenv("REDIS_DB") 124 | } 125 | 126 | goworkers.Configure(map[string]string{ 127 | "server": redisURL, 128 | "database": redisDB, 129 | "pool": "30", 130 | "process": "1", 131 | "poll_interval": "1", 132 | }) 133 | 134 | goworkers.Process("pong", workers.Pong, 10) 135 | goworkers.Process("trigger_actions", workers.TriggerActions, 10) 136 | go func() { 137 | if err := http.ListenAndServe(":8080", nil); err != nil { 138 | logrus.Fatal(err) 139 | } 140 | }() 141 | 142 | logrus.Info("[INFO] Server listening") 143 | goworkers.Run() 144 | 145 | } 146 | -------------------------------------------------------------------------------- /manifests/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: github-actions-trigger 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | labels: 10 | name: github-actions-trigger 11 | spec: 12 | containers: 13 | - name: github-actions-trigger 14 | imagePullPolicy: Always 15 | image: pyama/github-actions-trigger:latest 16 | tty: true 17 | ports: 18 | - containerPort: 8080 19 | livenessProbe: 20 | initialDelaySeconds: 10 21 | periodSeconds: 10 22 | tcpSocket: 23 | port: 8080 24 | readinessProbe: 25 | initialDelaySeconds: 10 26 | periodSeconds: 10 27 | tcpSocket: 28 | port: 8080 29 | env: 30 | - name: REDIS_URL 31 | value: "redis-service:6379" 32 | - name: SLACK_SIGNING_SECRET 33 | valueFrom: 34 | secretKeyRef: 35 | name: github-actions-trigger 36 | key: slack-signing-token 37 | - name: TZ 38 | value: Asia/Tokyo 39 | - name: SLACK_BOT_TOKEN 40 | valueFrom: 41 | secretKeyRef: 42 | name: github-actions-trigger 43 | key: slack-bot-token 44 | - name: GITHUB_TOKEN 45 | valueFrom: 46 | secretKeyRef: 47 | name: github-actions-trigger 48 | key: github-token 49 | -------------------------------------------------------------------------------- /manifests/redis.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app.kubernetes.io/name: redis 10 | app.kubernetes.io/version: "0.1" 11 | app.kubernetes.io/component: kvs 12 | app.kubernetes.io/part-of: issuer-bot 13 | spec: 14 | containers: 15 | - name: redis 16 | image: redis 17 | tty: true 18 | ports: 19 | - containerPort: 6379 20 | -------------------------------------------------------------------------------- /manifests/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: github-actions-trigger 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | name: github-actions-trigger 9 | ports: 10 | - protocol: TCP 11 | port: 80 12 | targetPort: 8080 13 | --- 14 | apiVersion: v1 15 | kind: Service 16 | metadata: 17 | name: redis-service 18 | spec: 19 | selector: 20 | app.kubernetes.io/name: redis 21 | app.kubernetes.io/part-of: github-actions-trigger 22 | ports: 23 | - protocol: TCP 24 | port: 6379 25 | targetPort: 6379 26 | --------------------------------------------------------------------------------