├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── example-bashbot-github-action-gate.yaml │ ├── example-bashbot-github-action.yaml │ ├── example-notify-slack.yaml │ ├── pr.yaml │ ├── release.yaml │ └── update-asdf-versions.yaml ├── .gitignore ├── .tool-versions ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── charts └── bashbot │ ├── .gitignore │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── _helpers.tpl │ ├── clusterrolebinding.yaml │ ├── configmap.yaml │ ├── deployment.yaml │ └── serviceaccount.yaml │ ├── test-complete.sh │ ├── test-deployment.sh │ └── values.yaml ├── cmd ├── install-dependencies.go ├── root.go ├── run.go ├── send-file.go ├── send-message.go └── version.go ├── entrypoint.sh ├── examples ├── README.md ├── aqi │ ├── README.md │ ├── aqi.sh │ ├── aqi.yaml │ └── test.sh ├── asdf │ ├── README.md │ ├── asdf.yaml │ └── test.sh ├── describe │ ├── README.md │ └── describe.yaml ├── get-file-from-repo │ ├── README.md │ ├── get-file-from-repo.json │ └── get-file-from-repo.sh ├── help │ ├── README.md │ └── help.yaml ├── info │ ├── README.md │ ├── get-info.sh │ ├── info.yaml │ └── test.sh ├── kubernetes │ ├── README.md │ ├── kubernetes-manifests-withsa.yaml │ ├── kubernetes-manifests.yaml │ └── test.sh ├── latest-release │ ├── README.md │ └── latest-release.yaml ├── list-examples │ ├── README.md │ └── list-examples.yaml ├── list │ ├── README.md │ └── list.yaml ├── ping │ ├── README.md │ ├── ping.yaml │ └── test.sh ├── regex │ ├── README.md │ ├── regex.yaml │ └── test.sh ├── rip-mp3 │ ├── README.md │ └── rip-mp3.yaml ├── trigger-github-action │ ├── README.md │ ├── example-gate-config.yaml │ ├── github-action.sh │ ├── trigger-gate.sh │ ├── trigger-github-action.yaml │ └── trigger.sh └── version │ ├── README.md │ ├── get-version.sh │ └── version.yaml ├── go.mod ├── go.sum ├── internal └── slack │ ├── models.go │ └── slack.go ├── main.go ├── sample-config.yaml └── sample-env-file /.dockerignore: -------------------------------------------------------------------------------- 1 | #Ignoring git and cache folders 2 | .git 3 | .git* 4 | .cache 5 | **.env* 6 | .DS_Store 7 | Dockerfile 8 | vendor/* 9 | config.yaml 10 | bashbot-log-* -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "gomod" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/example-bashbot-github-action-gate.yaml: -------------------------------------------------------------------------------- 1 | # Name: example-bashbot-github-action-gate.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action demonstrates how to trigger a GitHub action from Bashbot, 4 | # spawning another instance of bashbot to act as a manual approval gate. 5 | name: Example Bashbot Triggered GitHub Action Gate 6 | on: 7 | repository_dispatch: 8 | types: 9 | - trigger-github-action-gate 10 | 11 | jobs: 12 | build: 13 | name: Example Bashbot Triggered GitHub Action Gate 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | - 20 | name: Install bashbot with asdf 21 | uses: asdf-vm/actions/install@v2 22 | with: 23 | tool_versions: | 24 | bashbot 2.0.5 25 | yq 4.30.6 26 | - 27 | name: Run Bashbot as debugging gate to job 28 | env: 29 | BASHBOT_CONFIG_FILEPATH: ./examples/trigger-github-action/example-gate-config.yaml 30 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 31 | SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} 32 | SLACK_CHANNEL: ${{ github.event.client_payload.channel }} 33 | SLACK_USERID: ${{ github.event.client_payload.user_id }} 34 | LOG_LEVEL: info 35 | LOG_FORMAT: text 36 | GIT_TOKEN: ${{ github.token }} 37 | run: | 38 | asdf global yq 4.30.6 39 | # Send user notification bashbot gate has started 40 | bashbot send-message \ 41 | --log-level "${LOG_LEVEL}" \ 42 | --log-format "${LOG_FORMAT}" \ 43 | --channel ${SLACK_CHANNEL} \ 44 | --msg "Bashbot gate started by <@${SLACK_USERID}>. To see a full list of options, run:" 45 | 46 | bashbot send-message \ 47 | --log-level "${LOG_LEVEL}" \ 48 | --log-format "${LOG_FORMAT}" \ 49 | --channel ${SLACK_CHANNEL} \ 50 | --msg "!example-gate help" 51 | 52 | # Start bashbot 53 | bashbot run \ 54 | --log-level "${LOG_LEVEL}" \ 55 | --log-format "${LOG_FORMAT}" 56 | -------------------------------------------------------------------------------- /.github/workflows/example-bashbot-github-action.yaml: -------------------------------------------------------------------------------- 1 | # Name: example-bashbot-github-action.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action demonstrates how to trigger a GitHub action from Bashbot. 4 | name: Example Bashbot Triggered GitHub Action 5 | on: 6 | repository_dispatch: 7 | types: 8 | - trigger-github-action 9 | 10 | jobs: 11 | build: 12 | name: Example Bashbot Triggered GitHub Action 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Install stuff with asdf 17 | uses: asdf-vm/actions/install@v2 18 | with: 19 | tool_versions: | 20 | bashbot 2.0.5 21 | yq 4.30.6 22 | - 23 | name: Send Slack Message With Bashbot Binary 24 | env: 25 | BASHBOT_CONFIG_FILEPATH: ./config.yaml 26 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 27 | SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} 28 | SLACK_CHANNEL: ${{ github.event.client_payload.channel }} 29 | SLACK_USERID: ${{ github.event.client_payload.user_id }} 30 | GIT_TOKEN: ${{ github.token }} 31 | run: | 32 | cat < $BASHBOT_CONFIG_FILEPATH 33 | admins: 34 | - trigger: bashbotexample 35 | appName: Bashbot Example 36 | userIds: 37 | - "UP3BBQX34" 38 | privateChannelId: "GPFMM5MD2" 39 | logChannelId: "CPJ1NFPL7" 40 | messages: [] 41 | tools: [] 42 | dependencies: [] 43 | EOF 44 | bashbot send-message \ 45 | --channel ${SLACK_CHANNEL} \ 46 | --msg "<@${SLACK_USERID}> Bashbot triggered this job: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" 47 | -------------------------------------------------------------------------------- /.github/workflows/example-notify-slack.yaml: -------------------------------------------------------------------------------- 1 | # Name: example-notify-slack.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action demonstrates how to trigger a Slack Notification from Bashbot. 4 | name: Example Bashbot Notify Slack 5 | on: 6 | repository_dispatch: 7 | types: 8 | - trigger-slack-notify 9 | 10 | jobs: 11 | build: 12 | name: Example Bashbot Notify Slack 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Install Bashbot via asdf 17 | uses: asdf-vm/actions/install@v2 18 | with: 19 | tool_versions: bashbot 2.0.5 20 | - 21 | name: Send Slack Message With Bashbot Binary 22 | env: 23 | BASHBOT_CONFIG_FILEPATH: ./config.yaml 24 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 25 | SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} 26 | run: | 27 | cat < $BASHBOT_CONFIG_FILEPATH 28 | admins: 29 | - trigger: bashbotexample 30 | appName: Bashbot Notify Example 31 | userIds: 32 | - "UP3BBQX34" 33 | privateChannelId: "GPFMM5MD2" 34 | logChannelId: "CPJ1NFPL7" 35 | messages: [] 36 | tools: [] 37 | dependencies: [] 38 | EOF 39 | bashbot send-message \ 40 | --channel ${{ github.event.client_payload.channel }} \ 41 | --msg "${{ github.event.client_payload.text }}" 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | # Name: pr.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action will run go lint/unit tests as well as 4 | # build a docker container and test it against a KinD cluster. 5 | # See Makefile for more details (make help). 6 | name: PR Tests 7 | on: 8 | pull_request_target: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | 14 | anchore-container-scan: 15 | name: Anchore Container Scan 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout the code 19 | uses: actions/checkout@v3 20 | - name: Build the Docker image 21 | run: make docker-build 22 | - name: Run the Anchore scan action itself with GitHub Advanced Security code scanning integration enabled 23 | uses: anchore/scan-action@main 24 | with: 25 | image: "bashbot:local" 26 | fail-build: false 27 | acs-report-enable: true 28 | - name: Upload Anchore Scan Report 29 | uses: github/codeql-action/upload-sarif@v2 30 | with: 31 | sarif_file: results.sarif 32 | 33 | codeql-code-scan: 34 | name: CodeQL 35 | runs-on: ubuntu-latest 36 | permissions: 37 | actions: read 38 | contents: read 39 | security-events: write 40 | 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | language: [ 'go' ] 45 | 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v3 49 | 50 | - name: Install Golang via asdf 51 | uses: asdf-vm/actions/install@v2 52 | with: 53 | tool_versions: golang 1.19.4 54 | 55 | - name: Initialize CodeQL 56 | uses: github/codeql-action/init@v2 57 | with: 58 | languages: ${{ matrix.language }} 59 | 60 | - run: | 61 | asdf global golang 1.19.4 62 | make go-setup 63 | make go-build 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v2 67 | unit_test: 68 | name: Lint and Unit Tests 69 | runs-on: ubuntu-latest 70 | steps: 71 | - 72 | name: Checkout 73 | uses: actions/checkout@v3 74 | with: 75 | fetch-depth: 0 76 | - 77 | name: Install stuff with asdf 78 | uses: asdf-vm/actions/install@v2 79 | with: 80 | tool_versions: | 81 | action-validator 0.1.2 82 | dockle 0.4.5 83 | helm 3.8.1 84 | golangci-lint 1.44.2 85 | yq 4.22.1 86 | - 87 | name: Lint Actions 88 | run: make test-lint-actions 89 | 90 | - 91 | name: Lint Go 92 | run: make test-lint 93 | 94 | - 95 | name: Unit Tests 96 | run: make test-go 97 | 98 | # - 99 | # name: Docker Login 100 | # uses: docker/login-action@v2 101 | # with: 102 | # registry: docker.io 103 | # username: ${{ secrets.REGISTRY_USERNAME }} 104 | # password: ${{ secrets.REGISTRY_PASSWORD }} 105 | 106 | # - 107 | # name: Lint Container Using Dockle 108 | # run: make test-docker 109 | 110 | integration_test: 111 | name: KinD Integration Tests 112 | # needs: [unit_test] 113 | runs-on: ubuntu-latest 114 | steps: 115 | - 116 | name: Checkout 117 | uses: actions/checkout@v3 118 | with: 119 | fetch-depth: 0 120 | - 121 | name: KinD Test 122 | env: 123 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 124 | SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} 125 | AIRQUALITY_API_KEY: ${{ secrets.AIRQUALITY_API_KEY }} 126 | BASHBOT_CONFIG_FILEPATH: /bashbot/config.yaml 127 | GIT_TOKEN: not-used 128 | run: | 129 | cp sample-config.yaml config.yaml 130 | if [[ -z "$SLACK_BOT_TOKEN" ]] || [[ -z "$SLACK_APP_TOKEN" ]]; then 131 | echo "Missing github secret(s): SLACK_BOT_TOKEN, SLACK_APP_TOKEN" 132 | exit 1 133 | fi 134 | cat sample-env-file | envsubst > .env \ 135 | && make test-kind \ 136 | && rm -rf .env \ 137 | && echo "Deployment assets and KinD cluster removed" 138 | - 139 | name: Debug 140 | if: ${{ always() }} 141 | shell: bash 142 | run: | 143 | make help 144 | ls -alF 145 | kubectl version 146 | helm version 147 | helm list -n bashbot 148 | echo "Deployments:" 149 | kubectl --namespace bashbot get deployments -o wide 150 | echo "Pods:" 151 | kubectl --namespace bashbot get pods -o wide 152 | echo "Service Accounts:" 153 | kubectl --namespace bashbot get serviceaccounts 154 | echo "Secrets:" 155 | kubectl --namespace bashbot get secrets 156 | echo "Configmaps:" 157 | kubectl --namespace bashbot get configmaps 158 | # echo "bashbot Config:" 159 | # kubectl --namespace bashbot get configmaps bashbot-configmap -o jsonpath='{.data.config\.json}' | jq '.' || true 160 | echo "Describe bashbot pod:" 161 | kubectl --namespace bashbot describe pod $(kubectl --namespace bashbot get pods | grep bashbot | awk '{print $1}') || true 162 | echo "Logs:" 163 | kubectl --namespace bashbot logs $(kubectl --namespace bashbot get pods | grep bashbot | awk '{print $1}') || true 164 | make kind-cleanup || true 165 | 166 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # Name: release.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action will build and push a multi-arch docker container, 4 | # cross compile go-binaries and save as release artifacts, 5 | # and package a helm chart as gh-pages env, when triggered by 6 | # pushing a new git tag (that starts with the letter 'v'). 7 | name: Release Bashbot 8 | on: 9 | push: 10 | branches: 11 | - main 12 | paths: 13 | - 'charts/**' 14 | 15 | jobs: 16 | release: 17 | name: Release Bashbot 18 | runs-on: ubuntu-latest 19 | steps: 20 | - 21 | name: Checkout Bashbot source 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | - 26 | name: Install stuff with asdf 27 | uses: asdf-vm/actions/install@v2 28 | with: 29 | tool_versions: | 30 | golang 1.19.4 31 | yq 4.30.6 32 | - 33 | name: Set tag environment variable 34 | run: | 35 | echo "RELEASE_VERSION=$(make version)" >> $GITHUB_ENV 36 | echo "RELEASE_VERSION_NO_V=$(make version)" | sed -e 's/v//g' >> $GITHUB_ENV 37 | - 38 | name: Build go-binaries 39 | run: | 40 | make go-setup 41 | make go-cross-compile 42 | echo "Go-Binaries created: $(ls bin -alF)" 43 | - 44 | name: Set up QEMU 45 | id: qemu 46 | uses: docker/setup-qemu-action@v2 47 | - 48 | name: Set up Docker Buildx 49 | id: buildx 50 | uses: docker/setup-buildx-action@v2 51 | - 52 | name: Docker Login 53 | uses: docker/login-action@v2 54 | with: 55 | registry: docker.io 56 | username: ${{ secrets.REGISTRY_USERNAME }} 57 | password: ${{ secrets.REGISTRY_PASSWORD }} 58 | - 59 | name: Login to GitHub Container Registry 60 | uses: docker/login-action@v1 61 | with: 62 | registry: ghcr.io 63 | username: ${{ github.repository_owner }} 64 | password: ${{ secrets.GIT_TOKEN }} 65 | - 66 | name: Build and push 67 | id: docker_build 68 | uses: docker/build-push-action@v4 69 | with: 70 | push: true 71 | context: . 72 | platforms: linux/amd64,linux/arm64 73 | tags: | 74 | ghcr.io/${{ github.repository_owner }}/bashbot:latest 75 | ghcr.io/${{ github.repository_owner }}/bashbot:${{ env.RELEASE_VERSION }} 76 | mathewfleisch/bashbot:latest 77 | mathewfleisch/bashbot:${{ env.RELEASE_VERSION }} 78 | cache-from: type=registry,ref=mathewfleisch/bashbot:latest 79 | cache-to: type=inline 80 | - 81 | name: Build and push root container 82 | id: docker_build_root 83 | uses: docker/build-push-action@v4 84 | with: 85 | push: true 86 | context: . 87 | platforms: linux/amd64,linux/arm64 88 | tags: | 89 | ghcr.io/${{ github.repository_owner }}/bashbot:latest-root 90 | ghcr.io/${{ github.repository_owner }}/bashbot:${{ env.RELEASE_VERSION }}-root 91 | mathewfleisch/bashbot:latest-root 92 | mathewfleisch/bashbot:${{ env.RELEASE_VERSION }}-root 93 | cache-from: type=registry,ref=mathewfleisch/bashbot:latest 94 | cache-to: type=inline 95 | build-args: NRUSER=root 96 | - 97 | name: Configure Git 98 | run: | 99 | git config user.name "$GITHUB_ACTOR" 100 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 101 | - 102 | name: Run chart-releaser 103 | uses: helm/chart-releaser-action@v1.5.0 104 | env: 105 | CR_TOKEN: "${{ secrets.GIT_TOKEN }}" 106 | CR_SKIP_EXISTING: true 107 | - 108 | name: Cut Github Release 109 | uses: ncipollo/release-action@v1 110 | with: 111 | token: ${{ secrets.GIT_TOKEN }} 112 | allowUpdates: true 113 | artifacts: "bin/*" 114 | tag: bashbot-${{ env.RELEASE_VERSION }} 115 | - 116 | name: Install Bashbot via asdf from release artifacts 117 | uses: asdf-vm/actions/install@v2 118 | with: 119 | tool_versions: bashbot ${{ env.RELEASE_VERSION_NO_V }} 120 | - 121 | name: Notify Release Channel 122 | env: 123 | BASHBOT_CONFIG_FILEPATH: ./config.yaml 124 | SLACK_CHANNEL: C02A1SH4GLT 125 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 126 | SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} 127 | run: | 128 | cat < $BASHBOT_CONFIG_FILEPATH 129 | admins: 130 | - trigger: bashbotrelease 131 | appName: Bashbot Releases 132 | userIds: 133 | - "UP3BBQX34" 134 | privateChannelId: "GPFMM5MD2" 135 | logChannelId: "CPJ1NFPL7" 136 | messages: [] 137 | tools: [] 138 | dependencies: [] 139 | EOF 140 | bashbot version 141 | bashbot send-message \ 142 | --channel ${SLACK_CHANNEL} \ 143 | --msg "Bashbot has been released and multi-arch containers have been pushed to and !" 144 | -------------------------------------------------------------------------------- /.github/workflows/update-asdf-versions.yaml: -------------------------------------------------------------------------------- 1 | # Name: update-asdf-versions.yaml 2 | # Author: Mathew Fleisch 3 | # Description: This action will iterate through the .tool-versions file 4 | # and grab the latest version for each line, unless it is pinned 5 | # in the pin file. If there are changes to the .tool-versions file 6 | # the action will automatically tag a new version number. 7 | name: Update asdf versions 8 | on: 9 | schedule: # trigger Sundays at 12:20am PT (19:20UTC) 10 | - cron: '20 19 * * 0' 11 | workflow_dispatch: 12 | jobs: 13 | build: 14 | name: Update asdf versions 15 | runs-on: 16 | - self-hosted 17 | - generic-runner 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | token: ${{ secrets.GIT_TOKEN }} 23 | fetch-depth: 0 24 | 25 | - name: "Update asdf versions" 26 | run: | 27 | echo "Current .tool-versions:" 28 | cat .tool-versions 29 | touch new-tool-versions 30 | for dep in $(cat .tool-versions | cut -d' ' -f1); do 31 | echo "$dep $(asdf latest $dep)" >> new-tool-versions 32 | done 33 | echo "Latest .tool-versions:" 34 | cat new-tool-versions 35 | cat new-tool-versions > .tool-versions 36 | git config user.name github-actions 37 | git config user.email github-actions@github.com 38 | git pull origin main 39 | if [ -n "$(git status -s)" ]; then 40 | git add .tool-versions 41 | git commit -m "New asdf dependencies updated" 42 | git push origin main 43 | else 44 | echo "There were no new updates to asdf dependencies..." 45 | fi 46 | 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.mov 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | *.gif 9 | *.mp4 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | .env* 18 | .DS_Store 19 | */vendor/* 20 | vendor/* 21 | 22 | config.yaml 23 | bashbot-log-* 24 | 25 | tmp/* 26 | bin/* 27 | 28 | # bashbot -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | helm 3.18.2 2 | kubectl 1.33.1 3 | kubectx 0.9.5 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 as builder 2 | 3 | # yq required to parse version from helm chart and inject into build 4 | RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -q -O /usr/bin/yq \ 5 | && chmod +x /usr/bin/yq 6 | 7 | WORKDIR /bashbot 8 | COPY . . 9 | RUN make 10 | 11 | FROM alpine:latest 12 | LABEL maintainer="Mathew Fleisch " 13 | ENV BASHBOT_CONFIG_FILEPATH=/bashbot/config.yaml 14 | ENV BASHBOT_ENV_VARS_FILEPATH "" 15 | ENV SLACK_BOT_TOKEN "" 16 | ENV SLACK_APP_TOKEN "" 17 | ENV LOG_LEVEL "info" 18 | ENV LOG_FORMAT "text" 19 | ARG NRUSER=bb 20 | 21 | RUN apk add --update --no-cache bash curl git make jq yq \ 22 | && rm -rf /var/cache/apk/* \ 23 | && rm /bin/sh && ln -s /bin/bash /bin/sh \ 24 | && if [[ "${NRUSER}" != "root" ]]; then \ 25 | addgroup -S ${NRUSER} && \ 26 | adduser -D -S ${NRUSER} -G ${NRUSER}; \ 27 | fi 28 | 29 | WORKDIR /bashbot 30 | COPY --from=builder --chown=${NRUSER}:${NRUSER} /bashbot/bin/bashbot-* /usr/local/bin/bashbot 31 | COPY . . 32 | RUN chmod +x /usr/local/bin/bashbot \ 33 | && mkdir -p /usr/asdf \ 34 | && chown -R ${NRUSER}:${NRUSER} /usr/asdf \ 35 | && chown -R ${NRUSER}:${NRUSER} /usr/local/bin \ 36 | && chown -R ${NRUSER}:${NRUSER} /bashbot 37 | USER ${NRUSER} 38 | HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "which", "bashbot" ] 39 | CMD [ "/bashbot/entrypoint.sh" ] 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mathew Fleisch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOOS?=$(shell go env GOOS) 2 | GOARCH?=$(shell go env GOARCH) 3 | VERSION?=$(shell make version) 4 | LATEST_VERSION?=$(shell curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 5 | BINARY?=bin/bashbot 6 | SRC_LOCATION?=main.go 7 | LDFLAGS="-X github.com/mathew-fleisch/bashbot/cmd.Version=${VERSION}" 8 | GO_BUILD=go build -ldflags=$(LDFLAGS) 9 | # Public builds: REGISTRY_NAME=mathewfleisch/bashbot or REGISTRY_NAME=ghcr.io/mathew-fleisch/bashbot 10 | REGISTRY_NAME?=bashbot 11 | # For latest tag: REGISTRY_TAG=latest" 12 | REGISTRY_TAG?=local 13 | NRUSER?=bb 14 | BASHBOT_LOG_LEVEL?=info 15 | BASHBOT_LOG_TYPE?=text 16 | TESTING_CHANNEL?=C034FNXS3FA 17 | ADMIN_CHANNEL?=GPFMM5MD2 18 | NAMESPACE?=bashbot 19 | BOTNAME?=bashbot 20 | HELM_CONFIG_YAML?=$(PWD)/config.yaml 21 | HELM_TOOL_VERSIONS?=$(PWD)/.tool-versions 22 | HELM_ENV?=${PWD}/.env 23 | 24 | 25 | ##@ Misc stuff 26 | 27 | .PHONY: help 28 | help: ## this 29 | @echo "+---------------------------------------------------------------+" 30 | @echo "| ____ _ ____ _ |" 31 | @echo "| | _ \ | | | _ \ | | |" 32 | @echo "| | |_) | __ _ ___| |__ | |_) | ___ | |_ |" 33 | @echo "| | _ < / _' / __| '_ \| _ < / _ \| __| |" 34 | @echo "| | |_) | (_| \__ \ | | | |_) | (_) | |_ |" 35 | @echo "| |____/ \__,_|___/_| |_|____/ \___/ \__| |" 36 | @echo "| |" 37 | @echo "| makefile targets |" 38 | @echo "+---------------------------------------------------------------+" 39 | @echo "$(VERSION)" 40 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-18s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 41 | 42 | .PHONY: version 43 | version: ## get the current helm chart version 44 | @yq e '.version' charts/bashbot/Chart.yaml 45 | 46 | .PHONY: bump-patch 47 | bump-patch: ## Bump-patch the semantic version of the helm chart using semver tool 48 | sed -i 's/'$(shell make version)'/v'$(shell semver bump patch $(shell make version))'/g' charts/bashbot/Chart.yaml 49 | 50 | .PHONY: bump-minor 51 | bump-minor: ## Bump-minor the semantic version of the helm chart using semver tool 52 | sed -i 's/'$(shell make version)'/v'$(shell semver bump minor $(shell make version))'/g' charts/bashbot/Chart.yaml 53 | 54 | .PHONY: bump-major 55 | bump-major: ## Bump-major the semantic version of the helm chart using semver tool 56 | sed -i 's/'$(shell make version)'/v'$(shell semver bump major $(shell make version))'/g' charts/bashbot/Chart.yaml 57 | 58 | .PHONY: install-latest 59 | install-latest: ## install the latest version of the bashbot binary to /usr/local/bin/bashbot with wget 60 | wget -q -O /usr/local/bin/bashbot https://github.com/mathew-fleisch/bashbot/releases/download/$(LATEST_VERSION)/bashbot-$(GOOS)-$(GOARCH) 61 | chmod +x /usr/local/bin/bashbot 62 | ifeq ($(shell uname -s),Darwin) 63 | @echo "To add bashbot to an allowlist:" 64 | @echo "xattr -d com.apple.quarantine /usr/local/bin/bashbot" 65 | endif 66 | bashbot version 67 | @echo "Run 'bashbot --help' for more information" 68 | 69 | .PHONY: update-asdf-deps 70 | update-asdf-deps: ## trigger github action to update asdf dependencies listed in .tool-versions (requires GIT_TOKEN) 71 | @curl -s -H "Accept: application/vnd.github.everest-preview+json" \ 72 | -H "Authorization: token $(GIT_TOKEN)" \ 73 | --request POST \ 74 | --data '{"event_type": "trigger-asdf-update"}' \ 75 | https://api.github.com/repos/mathew-fleisch/bashbot/dispatches 76 | @echo "Updating asdf dependencies via github-action: https://github.com/mathew-fleisch/bashbot/actions/workflows/update-asdf-versions.yaml" 77 | 78 | .PHONY: gif 79 | gif: ## Create a gif from a quicktime screen recording that has been exported to .mp4 from imovie 80 | @echo "Generating gif" 81 | @ffmpeg -i examples/$(example)/$(example).mp4 -r 10 -pix_fmt rgb24 examples/$(example)/$(example).gif 82 | 83 | ##@ Go stuff 84 | 85 | .PHONY: go-build 86 | go-build: go-clean go-setup ## build a go-binary for this host system-arch 87 | CGO_ENABLED=0 $(GO_BUILD) -o $(BINARY)-$(GOOS)-$(GOARCH) $(SRC_LOCATION) 88 | 89 | .PHONY: go-clean 90 | go-clean: ## delete any existing binaries at ./bin/* 91 | echo "Removing any existing go-binaries" 92 | rm -rf $(BINARY)* 93 | 94 | .PHONY: go-setup 95 | go-setup: ## install go-dependencies 96 | go mod tidy 97 | go get github.com/slack-go/slack@master 98 | go get github.com/sirupsen/logrus 99 | go get -u golang.org/x/sys 100 | go mod vendor 101 | go install -v ./... 102 | 103 | 104 | .PHONY: go-cross-compile 105 | go-cross-compile: go-clean go-setup ## build go-binaries for linux/darwin amd64/arm64 106 | GOOS=linux GOARCH=amd64 $(GO_BUILD) -o $(BINARY)-linux-amd64 $(SRC_LOCATION) 107 | GOOS=linux GOARCH=arm64 $(GO_BUILD) -o $(BINARY)-linux-arm64 $(SRC_LOCATION) 108 | GOOS=darwin GOARCH=amd64 $(GO_BUILD) -o $(BINARY)-darwin-amd64 $(SRC_LOCATION) 109 | GOOS=darwin GOARCH=arm64 $(GO_BUILD) -o $(BINARY)-darwin-arm64 $(SRC_LOCATION) 110 | 111 | .PHONY: go-run 112 | go-run: ## run the bashbot source code with go 113 | @go run $(SRC_LOCATION) 114 | 115 | .PHONY: go-version 116 | go-version: ## run the bashbot source code with the version argument 117 | @go run $(SRC_LOCATION) version 118 | 119 | 120 | ##@ Docker stuff 121 | 122 | 123 | .PHONY: docker-build 124 | docker-build: ## build and tag bashbot:local 125 | 126 | docker build --build-arg NRUSER=$(NRUSER) -t $(REGISTRY_NAME):$(REGISTRY_TAG) . 127 | 128 | .PHONY: docker-run 129 | docker-run: ## run an existing build of $(REGISTRY_NAME):$(REGISTRY_TAG) 130 | @echo "Public Builds: REGISTRY_NAME=mathewfleisch/bashbot or REGISTRY_NAME=ghcr.io/mathew-fleisch/bashbot" 131 | @echo "for latest tag: REGISTRY_TAG=latest" 132 | docker run -it --rm \ 133 | -v $(HELM_CONFIG_YAML):/bashbot/config.yaml \ 134 | -e BASHBOT_CONFIG_FILEPATH="/bashbot/config.yaml" \ 135 | -v $(HELM_TOOL_VERSIONS):/bashbot/.tool-versions \ 136 | -e GIT_TOKEN \ 137 | -e AIRQUALITY_API_KEY \ 138 | -e SLACK_BOT_TOKEN \ 139 | -e SLACK_APP_TOKEN \ 140 | -e LOG_LEVEL="$(BASHBOT_LOG_LEVEL)" \ 141 | -e LOG_FORMAT="$(BASHBOT_LOG_TYPE)" \ 142 | $(REGISTRY_NAME):$(REGISTRY_TAG) 143 | 144 | .PHONY: docker-run-bash 145 | docker-run-bash: ## run an exsting build of $(REGISTRY_NAME):$(REGISTRY_TAG) but override the entrypoint with /bin/bash 146 | @echo "Public Builds: REGISTRY_NAME=mathewfleisch/bashbot or REGISTRY_NAME=ghcr.io/mathew-fleisch/bashbot" 147 | @echo "for latest tag: REGISTRY_TAG=latest" 148 | docker run -it --rm --entrypoint bash \ 149 | -v $(HELM_CONFIG_YAML):/bashbot/config.yaml \ 150 | -e BASHBOT_CONFIG_FILEPATH="/bashbot/config.yaml" \ 151 | -v $(HELM_TOOL_VERSIONS):/bashbot/.tool-versions \ 152 | -e GIT_TOKEN \ 153 | -e AIRQUALITY_API_KEY \ 154 | -e SLACK_BOT_TOKEN \ 155 | -e SLACK_APP_TOKEN \ 156 | -e LOG_LEVEL="$(BASHBOT_LOG_LEVEL)" \ 157 | -e LOG_FORMAT="$(BASHBOT_LOG_TYPE)" \ 158 | $(REGISTRY_NAME):$(REGISTRY_TAG) 159 | 160 | 161 | 162 | ##@ Kubernetes stuff 163 | 164 | 165 | .PHONY: test-kind 166 | test-kind: kind-setup helm-install test-run ## run KinD tests 167 | 168 | test-run: ## run tests designed for bashbot running in kubernetes 169 | @echo "Testing: $(NAMESPACE) $(BOTNAME)" 170 | ./charts/bashbot/test-deployment.sh $(NAMESPACE) $(BOTNAME) 171 | ./examples/ping/test.sh $(NAMESPACE) $(BOTNAME) 172 | ./examples/aqi/test.sh $(NAMESPACE) $(BOTNAME) || true 173 | ./examples/asdf/test.sh $(NAMESPACE) $(BOTNAME) 174 | ./examples/info/test.sh $(NAMESPACE) $(BOTNAME) 175 | ./examples/regex/test.sh $(NAMESPACE) $(BOTNAME) 176 | ./examples/kubernetes/test.sh $(NAMESPACE) $(BOTNAME) 177 | ./charts/bashbot/test-complete.sh $(NAMESPACE) $(BOTNAME) 178 | 179 | .PHONY: kind-setup 180 | kind-setup: docker-build ## setup a KinD cluster to test bashbot's helm chart 181 | kind create cluster || true 182 | kind load docker-image $(REGISTRY_NAME):$(REGISTRY_TAG) 183 | 184 | .PHONY: kind-cleanup 185 | kind-cleanup: ## delete any KinD cluster set up for bashbot 186 | kind delete cluster 187 | 188 | 189 | .PHONY: helm-install 190 | helm-install: helm-uninstall ## install bashbot via helm into an existing KinD cluster to /usr/local/bin/bashbot 191 | kubectl create namespace $(NAMESPACE) || true 192 | @echo "Creating kubernetes secrets from $(HELM_ENV)" 193 | @echo "kubectl --namespace $(NAMESPACE) get secret $(BOTNAME)-env" 194 | @kubectl --namespace $(NAMESPACE) create secret generic $(BOTNAME)-env \ 195 | $(shell cat $(HELM_ENV) | grep -vE '^#' | sed -e 's/export\ /--from-literal=/g' | tr '\n' ' '); 196 | helm upgrade $(BOTNAME) charts/bashbot \ 197 | --install \ 198 | --timeout 2m0s \ 199 | --namespace $(NAMESPACE) \ 200 | --set namespace=$(NAMESPACE) \ 201 | --set botname=$(BOTNAME) \ 202 | --set image.repository=$(REGISTRY_NAME) \ 203 | --set image.tag=$(REGISTRY_TAG) \ 204 | --set log_level=$(BASHBOT_LOG_LEVEL) \ 205 | --set log_format=$(BASHBOT_LOG_TYPE) \ 206 | --set-file '\.tool-versions'=$(HELM_TOOL_VERSIONS) \ 207 | --set-file 'config\.yaml'=$(HELM_CONFIG_YAML) \ 208 | --debug \ 209 | --wait 210 | sleep 3 211 | timeout 30s make pod-logs 2>/dev/null || sleep 30 212 | 213 | .PHONY: helm-uninstall 214 | helm-uninstall: ## uninstall bashbot via helm/kubectl from an existing cluster 215 | helm uninstall $(BOTNAME) --namespace $(NAMESPACE) 2>/dev/null || true 216 | kubectl --namespace $(NAMESPACE) delete secret $(BOTNAME)-env --ignore-not-found=true 217 | kubectl delete clusterrolebinding $(BOTNAME) --ignore-not-found=true 218 | kubectl delete namespace $(NAMESPACE) --ignore-not-found=true 219 | 220 | .PHONY: pod-get 221 | pod-get: ## with an existing pod bashbot pod running, use kubectl to get the pod name 222 | @kubectl --namespace $(NAMESPACE) get pods \ 223 | --template '{{range .items}}{{.metadata.name}}{{end}}' \ 224 | --selector=app=$(BOTNAME) 225 | 226 | .PHONY: pod-logs 227 | pod-logs: ## with an existing pod bashbot pod running, use kubectl to display the logs of the pod 228 | kubectl -n $(NAMESPACE) logs -f $(shell make pod-get) \ 229 | | sed -e 's/\\*\\n/\n/g' 230 | 231 | .PHONY: pod-logs-json 232 | pod-logs-json: ## with an existing pod bashbot pod running, use kubectl to display the json logs of the pod and pipe to jq 233 | kubectl -n $(NAMESPACE) logs -f $(shell make pod-get) \ 234 | | jq -Rr '. as $$line | try (fromjson | .) catch $$line' 235 | 236 | .PHONY: pod-delete 237 | pod-delete: ## with an existing pod bashbot pod running, use kubectl to delete it 238 | kubectl -n $(NAMESPACE) delete pod $(shell make pod-get) --ignore-not-found=true 239 | 240 | .PHONY: pod-exec 241 | pod-exec: ## with an existing pod bashbot pod running, use kubectl to exec into it 242 | kubectl -n $(NAMESPACE) exec -it $(shell make pod-get) -- bash 243 | 244 | .PHONY: pod-exec-test 245 | pod-exec-test: ## with an existing pod bashbot pod running, use kubectl to exec into it and run the test-suite 246 | kubectl -n $(NAMESPACE) exec $(shell make pod-get) -- \ 247 | bash -c '. /usr/asdf/asdf.sh && make test-run' 248 | 249 | 250 | ##@ Linters and Tests 251 | 252 | 253 | .PHONY: test-lint-actions 254 | test-lint-actions: ## lint github actions with action-validator 255 | find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \ 256 | | xargs -I {} action-validator --verbose {} 257 | 258 | .PHONY: test-lint 259 | test-lint: ## lint go source with golangci-lint 260 | golangci-lint --skip-dirs-use-default --verbose run || true 261 | 262 | .PHONY: test-docker 263 | test-docker: ## use dockle to test the dockerfile for best practices 264 | export DOCKER_CONTENT_TRUST=1 \ 265 | && make docker-build \ 266 | && dockle $(REGISTRY_NAME):$(REGISTRY_TAG) 267 | 268 | # go test -cover -v ./... 269 | .PHONY: test-go 270 | test-go: ## run go coverage tests 271 | @echo "no tests..." 272 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bashbot 2 | 3 | [![Release](https://github.com/mathew-fleisch/bashbot/actions/workflows/release.yaml/badge.svg)](https://github.com/mathew-fleisch/bashbot/actions/workflows/release.yaml) | 4 | [Docker Hub](https://hub.docker.com/r/mathewfleisch/bashbot/tags?page=1&ordering=last_updated) | [ghcr](https://github.com/mathew-fleisch/bashbot/pkgs/container/bashbot) 5 | 6 | BashBot is a slack bot written in golang for infrastructure/devops teams. A socket connection to slack provides bashbot with a stream of text from each channel it is invited to, and uses regular expressions to determine when to trigger bash commands. A [configuration file](sample-config.yaml) defines a list of commands that can be run in public and/or private channels. Restricting certain commands to private channels gives granular control, over which users can execute them. Bashbot allows infrastructure/devops teams to extend the tools and scripts they already use to manage their environments, into slack, that also acts as an execution log, and leverages slack's access controls. 7 | 8 | See the [examples](examples) directory for more information about deploying, configuring and customizing Bashbot for your team. The most basic example of a bashbot command will just echo a string back to the user when triggered. 9 | 10 | ```yaml 11 | name: Ping/Pong 12 | description: Return pong on pings 13 | envvars: [] 14 | dependencies: [] 15 | help: "!bashbot ping" 16 | trigger: ping 17 | location: ./ 18 | command: 19 | - echo "pong" 20 | parameters: [] 21 | log: true 22 | ephemeral: false 23 | response: text 24 | permissions: 25 | - all 26 | ``` 27 | 28 | 29 | 30 | In this example, a user triggers a Jenkins job using Bashbot and another instance of Bashbot is deployed in a Jenkins job as a gating mechanism. The configuration for the secondary Bashbot could get info about the Jenkins job/host and provides controls to manually decide if the job should pass or fail, at a certain stage in the build.This method of deploying Bashbot gives basic Jenkins controls (trigger, pass, fail) to users in an organization, without giving them access to Jenkins itself. Bashbot commands can be restricted to private channels to limit access within slack. 31 | 32 | 33 | 34 | --- 35 | 36 | ## Slack tokens 37 | 38 | Bashbot uses the Slack API's "[Socket Mode](https://api.slack.com/apis/connections/socket)" to connect to the slack servers over a socket connection and uses ***no webhooks/ingress*** to trigger commands. Bashbot "subscribes" to message and emoji events and determins if a command should be executed and what command should be executed by parsing the data in each event. To run Bashbot, you must have a "[Bot User OAuth Token](https://api.slack.com/authentication/token-types#bot)" and an "[App-Level Token](https://api.slack.com/authentication/token-types#app)". 39 | 40 | - Click "Create New App" from the [Slack Apps](https://api.slack.com/apps) page and follow the "From Scratch" prompts to give your instance of bashbot a unique name and a workspace for it to be installed in 41 | - The "Basic Information" page gives controls to set a profile picture toward the bottom (make sure to save any changes) 42 | - Enable "Socket Mode" from the "Socket Mode" page and add the default scopes `conversations.write` and note the "[App-Level Token](https://api.slack.com/authentication/token-types#app)" that is generated to save in the .env file as `SLACK_APP_TOKEN` 43 | - Enable events from the "Event Subscriptions" page and add the following bot event subscriptions and save changes 44 | - `app_mention` 45 | - `message.channels` 46 | - `message.groups` 47 | - `reaction_added` 48 | - `reaction_removed` 49 | - From the "OAuth & Permissions" page, after setting the following "scopes" or permissions, install Bashbot in your workspace (this will require administrator approval of your slack workspace) and note the "[Bot User OAuth Token](https://api.slack.com/authentication/token-types#bot)" to save in the .env file as `SLACK_BOT_TOKEN` 50 | - `app_mentions:read` 51 | - `channels:history` 52 | - `channels:read` 53 | - `chat:write` 54 | - `files:write` 55 | - `groups:history` 56 | - `groups:read` 57 | - `incoming-webhook` 58 | - `reactions:read` 59 | - `reactions:write` 60 | - `users:read` 61 | 62 | --- 63 | 64 | ## Installation, setup and configuration 65 | 66 | [Example deployments and commands](examples) 67 | 68 | Bashbot can be run as a go binary or as a container and requires a slack-bot-token, slack-app-token and a config.yaml. The go binary takes flags to set the slack-bot-token, slack-app-token and path to the config.yaml file and the container uses environment variables to trigger a go binary by [entrypoint.sh](entrypoint.sh). The [Examples](examples) directory of this repository, has many deployment examples, commands used in automated tests, and can illustrate how bashbot can be used to trigger automation, or as automation itself, by leveraging secrets from the host running bashbot. For instance, one command might use an api token to curl a third-party api, and return the response back to the slack user, once triggered ([aqi example](examples/aqi)). If deployed in a kubernetes cluster, with a service-account to access the kube-api, bashbot can allow slack users to execute (hopefully curated) kubectl/helm commands, for an SRE/Ops focused deployment (get/describe/delete pods/deployments/secrets etc). 69 | 70 | ***Steps To Prove It's Working*** 71 | 72 | - Invite the BashBot into a channel by typing `@BashBot` 73 | - Slackbot should respond with the message: `OK! I’ve invited @BashBot to this channel.` 74 | - Now type `!bashbot help` 75 | - If all is configured correctly, you should see BashBot respond immediately with `Processing command...` and momentarily post a full list of commands that are defined in config.yaml 76 | 77 | --- 78 | 79 | ## Automation 80 | 81 | Included in this repository, one github action is executed on commits to pull requests, and another is executed on merges to the main branch: 82 | 83 | 1. On [pull requests to the main branch](.github/workflows/pr.yaml) [![pr](https://github.com/mathew-fleisch/bashbot/actions/workflows/pr.yaml/badge.svg)](https://github.com/mathew-fleisch/bashbot/actions/workflows/pr.yaml), four jobs are run on every commit: 84 | - linting and unit tests are run under the `unit_tests` job 85 | - a container is built and scanned by the [anchore](https://anchore.com/) container scanning tool 86 | - the golang code is analyzed by [codeql](https://codeql.github.com/) SAST tool 87 | - a container is built and deployed in a kind cluster, to run automated tests, to maintain/verify basic functionality (see [Makefile](Makefile) target `make help` for more information) 88 | 2. The [![release](https://github.com/mathew-fleisch/bashbot/actions/workflows/release.yaml/badge.svg)](https://github.com/mathew-fleisch/bashbot/actions/workflows/release.yaml) action will: 89 | - cross compile go-binaries for linux/amd64, linux/arm64, darwin/amd64, and darwin/arm64 90 | - package and release a helm chart with the chart-releaser action 91 | - add the go-binaries as release artifacts 92 | - use the buildx docker plugin to build and push a container for amd64/arm64 to docker hub and ghcr. 93 | - use asdf to install the new version of bashbot 94 | - notify the bashbot test slack workspace 95 | 3. The [![update-asdf-versions](https://github.com/mathew-fleisch/bashbot/actions/workflows/update-asdf-versions.yaml/badge.svg)](https://github.com/mathew-fleisch/bashbot/actions/workflows/update-asdf-versions.yaml) action will check for new versions of dependencies installed with the [asdf version manager](https://asdf-vm.com/), found in the [.tool-versions](.tool-versions) file. 96 | 97 | ### Makefile 98 | 99 | run `make help` for a full list of targets. 100 | 101 | Note: [yq](https://github.com/mikefarah/yq) is a dependency of running many makefile targets and can be installed with the binary: `wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -q -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq` 102 | 103 | ```text 104 | +---------------------------------------------------------------+ 105 | | ____ _ ____ _ | 106 | | | _ \ | | | _ \ | | | 107 | | | |_) | __ _ ___| |__ | |_) | ___ | |_ | 108 | | | _ < / _' / __| '_ \| _ < / _ \| __| | 109 | | | |_) | (_| \__ \ | | | |_) | (_) | |_ | 110 | | |____/ \__,_|___/_| |_|____/ \___/ \__| | 111 | | | 112 | | makefile targets | 113 | +---------------------------------------------------------------+ 114 | v2.0.5 115 | 116 | Usage: 117 | make 118 | 119 | Go stuff 120 | go-build build a go-binary for this host system-arch 121 | go-clean delete any existing binaries at ./bin/* 122 | go-setup install go-dependencies 123 | go-cross-compile build go-binaries for linux/darwin amd64/arm64 124 | go-run run the bashbot source code with go 125 | go-version run the bashbot source code with the version argument 126 | 127 | Docker stuff 128 | docker-build build and tag $(REGISTRY_NAME):$(REGISTRY_TAG) 129 | docker-run run an existing build of $(REGISTRY_NAME):$(REGISTRY_TAG) 130 | docker-run-bash run an exsting build of $(REGISTRY_NAME):$(REGISTRY_TAG) but override the entrypoint with /bin/bash 131 | 132 | Kubernetes stuff 133 | test-kind run KinD tests 134 | test-run run tests designed for bashbot running in kubernetes 135 | kind-setup setup a KinD cluster to test bashbot's helm chart 136 | kind-cleanup delete any KinD cluster set up for bashbot 137 | version get the current helm chart version 138 | bump-patch Bump-patch the semantic version of the helm chart using semver tool 139 | bump-minor Bump-minor the semantic version of the helm chart using semver tool 140 | bump-major Bump-major the semantic version of the helm chart using semver tool 141 | helm-install install bashbot via helm into an existing KinD cluster to /usr/local/bin/bashbot 142 | helm-uninstall uninstall bashbot via helm/kubectl from an existing cluster 143 | pod-get with an existing pod bashbot pod running, use kubectl to get the pod name 144 | pod-logs with an existing pod bashbot pod running, use kubectl to display the logs of the pod 145 | pod-logs-json with an existing pod bashbot pod running, use kubectl to display the json logs of the pod and pipe to jq 146 | pod-delete with an existing pod bashbot pod running, use kubectl to delete it 147 | pod-exec with an existing pod bashbot pod running, use kubectl to exec into it 148 | pod-exec-test with an existing pod bashbot pod running, use kubectl to exec into it and run the test-suite 149 | 150 | Linters and Tests 151 | test-lint-actions lint github actions with action-validator 152 | test-lint lint go source with golangci-lint 153 | test-docker use dockle to test the dockerfile for best practices 154 | test-go run go coverage tests 155 | 156 | Other stuff 157 | help this 158 | install-latest install the latest version of the bashbot binary to /usr/local/bin/bashbot with wget 159 | update-asdf-deps trigger github action to update asdf dependencies listed in .tool-versions (requires GIT_TOKEN) 160 | ``` 161 | 162 | Any of these environment variables can be overridden by exporting a new value before running any makefile target. 163 | 164 | ```makefile 165 | GOOS?=$(shell go env GOOS) 166 | GOARCH?=$(shell go env GOARCH) 167 | VERSION?=$(shell make version) 168 | LATEST_VERSION?=$(shell curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 169 | BINARY?=bin/bashbot 170 | SRC_LOCATION?=main.go 171 | # Public builds: REGISTRY_NAME=mathewfleisch/bashbot or REGISTRY_NAME=ghcr.io/mathew-fleisch/bashbot 172 | REGISTRY_NAME?=bashbot 173 | # For latest tag: REGISTRY_TAG=latest" 174 | REGISTRY_TAG?=local 175 | NRUSER?=bb 176 | BASHBOT_LOG_LEVEL?=info 177 | BASHBOT_LOG_TYPE?=text 178 | TESTING_CHANNEL?=C034FNXS3FA 179 | ADMIN_CHANNEL?=GPFMM5MD2 180 | NAMESPACE?=bashbot 181 | BOTNAME?=bashbot 182 | HELM_CONFIG_YAML?=$(PWD)/config.yaml 183 | HELM_TOOL_VERSIONS?=$(PWD)/.tool-versions 184 | HELM_ENV?=${PWD}/.env 185 | ``` 186 | 187 | -------------------------------------------------------------------------------- /charts/bashbot/.gitignore: -------------------------------------------------------------------------------- 1 | config.yaml 2 | .env 3 | .tool-versions -------------------------------------------------------------------------------- /charts/bashbot/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/bashbot/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: bashbot 3 | description: Bashbot is a slack ChatOps tool for Infrastructure and DevOps teams. 4 | type: application 5 | version: v2.0.6 6 | appVersion: v2.0.6 7 | -------------------------------------------------------------------------------- /charts/bashbot/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "bashbot.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "bashbot.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "bashbot.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "bashbot.labels" -}} 37 | helm.sh/chart: {{ include "bashbot.chart" . }} 38 | {{ include "bashbot.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "bashbot.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "bashbot.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "bashbot.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "bashbot.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/bashbot/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "bashbot.serviceAccountName" . }} 6 | namespace: {{ .Values.namespace }} 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: ClusterRole 10 | name: cluster-admin 11 | subjects: 12 | - kind: ServiceAccount 13 | name: {{ include "bashbot.serviceAccountName" . }} 14 | namespace: {{ .Values.namespace }} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /charts/bashbot/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ .Values.botname }}-configmap 5 | namespace: {{ .Values.namespace }} 6 | data: 7 | {{- if index .Values "config.yaml" }} 8 | .tool-versions: | 9 | {{ index .Values ".tool-versions" | indent 3 }} 10 | config.yaml: | 11 | {{ index .Values "config.yaml" | indent 3 }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/bashbot/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: {{ .Values.botname }} 6 | name: {{ .Values.botname }} 7 | namespace: {{ .Values.namespace }} 8 | spec: 9 | progressDeadlineSeconds: 600 10 | replicas: 1 11 | revisionHistoryLimit: 0 12 | selector: 13 | matchLabels: 14 | app: {{ .Values.botname }} 15 | strategy: 16 | type: Recreate 17 | template: 18 | metadata: 19 | creationTimestamp: null 20 | labels: 21 | app: {{ .Values.botname }} 22 | spec: 23 | containers: 24 | - env: 25 | - name: LOG_LEVEL 26 | value: {{ .Values.log_level }} 27 | - name: LOG_FORMAT 28 | value: {{ .Values.log_format }} 29 | - name: BOTNAME 30 | value: {{ .Values.botname }} 31 | - name: NAMESPACE 32 | value: {{ .Values.namespace }} 33 | - name: SLACK_BOT_TOKEN 34 | valueFrom: 35 | secretKeyRef: 36 | name: {{ .Values.botname }}-env 37 | key: SLACK_BOT_TOKEN 38 | - name: SLACK_APP_TOKEN 39 | valueFrom: 40 | secretKeyRef: 41 | name: {{ .Values.botname }}-env 42 | key: SLACK_APP_TOKEN 43 | - name: GIT_TOKEN 44 | valueFrom: 45 | secretKeyRef: 46 | name: {{ .Values.botname }}-env 47 | key: GIT_TOKEN 48 | optional: true 49 | - name: AIRQUALITY_API_KEY 50 | valueFrom: 51 | secretKeyRef: 52 | name: {{ .Values.botname }}-env 53 | key: AIRQUALITY_API_KEY 54 | optional: true 55 | - name: NASA_API_KEY 56 | valueFrom: 57 | secretKeyRef: 58 | name: {{ .Values.botname }}-env 59 | key: NASA_API_KEY 60 | optional: true 61 | - name: GIPHY_API_KEY 62 | valueFrom: 63 | secretKeyRef: 64 | name: {{ .Values.botname }}-env 65 | key: GIPHY_API_KEY 66 | optional: true 67 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 68 | imagePullPolicy: {{ .Values.pullPolicy }} 69 | name: {{ .Values.botname }} 70 | command: 71 | {{- range .Values.image.command }} 72 | - {{. | quote }} 73 | {{- end }} 74 | args: 75 | {{- range .Values.image.args }} 76 | - {{. | quote }} 77 | {{- end }} 78 | resources: {} 79 | terminationMessagePath: /dev/termination-log 80 | terminationMessagePolicy: File 81 | workingDir: /bashbot 82 | volumeMounts: 83 | - name: {{ .Values.botname }}-configmap 84 | mountPath: /bashbot/config.yaml 85 | subPath: config.yaml 86 | - name: {{ .Values.botname }}-configmap 87 | mountPath: /bashbot/.tool-versions 88 | subPath: .tool-versions 89 | volumes: 90 | - name: {{ .Values.botname }}-configmap 91 | configMap: 92 | name: {{ .Values.botname }}-configmap 93 | dnsPolicy: ClusterFirst 94 | restartPolicy: Always 95 | {{- if .Values.serviceAccount.create -}}{{printf "\n" }} 96 | serviceAccount: {{ include "bashbot.serviceAccountName" . }} 97 | serviceAccountName: {{ include "bashbot.serviceAccountName" . }} 98 | automountServiceAccountToken: true 99 | {{- end }} 100 | schedulerName: default-scheduler 101 | securityContext: {} 102 | terminationGracePeriodSeconds: 0 103 | -------------------------------------------------------------------------------- /charts/bashbot/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "bashbot.serviceAccountName" . }} 6 | labels: 7 | {{- include "bashbot.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | namespace: {{ .Values.namespace }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /charts/bashbot/test-complete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "Deployment test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | 16 | main() { 17 | local ns=${1:-bashbot} 18 | local dn=${2:-bashbot} 19 | # Retry loop (20/$i with 3 second delay between loops) 20 | for i in {3..1}; do 21 | # Get the expected number of replicas for this deployment 22 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 23 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 24 | [ -z "$expectedReplicas" ] && expectedReplicas=1 25 | # Get the number of replicas that are ready for this deployment 26 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 27 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 28 | [ -z "$readyReplicas" ] && readyReplicas=0 29 | 30 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 31 | test $readyReplicas -eq $expectedReplicas \ 32 | && test 1 -eq 1 33 | if [ $? -eq 0 ]; then 34 | echo "Bashbot deployment confirmed!" 35 | kubectl --namespace ${ns} get deployments 36 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 37 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 38 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":tada: :tada: All Tests Complete!!! :tada: :tada:"' 39 | exit 0 40 | fi 41 | 42 | # Since the deployment was not ready, try again $i more times 43 | echo "Deployment not found or not ready. $i more attempts..." 44 | sleep 3 45 | done 46 | 47 | # The retry loop has exited without finding a stable deployment 48 | echo "Bashbot deployment failed :(" 49 | # Display some debug information and fail test 50 | kubectl --namespace ${ns} get deployments 51 | kubectl --namespace ${ns} get pods -o wide 52 | exit 1 53 | } 54 | 55 | # Usage: ./test-complete.sh [namespace] [deployment] 56 | namespace=${1:-bashbot} 57 | deploymentName=${2:-bashbot} 58 | 59 | main $namespace $deploymentName 60 | -------------------------------------------------------------------------------- /charts/bashbot/test-deployment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "Deployment test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | 16 | main() { 17 | local ns=${1:-bashbot} 18 | local dn=${2:-bashbot} 19 | # Retry loop (20/$i with 3 second delay between loops) 20 | for i in {30..1}; do 21 | # Get the expected number of replicas for this deployment 22 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 23 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 24 | [ -z "$expectedReplicas" ] && expectedReplicas=1 25 | # Get the number of replicas that are ready for this deployment 26 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 27 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 28 | [ -z "$readyReplicas" ] && readyReplicas=0 29 | 30 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 31 | test $readyReplicas -eq $expectedReplicas \ 32 | && test 1 -eq 1 33 | if [ $? -eq 0 ]; then 34 | echo "Bashbot deployment confirmed!" 35 | kubectl --namespace ${ns} get deployments 36 | found_connected=0 37 | for j in {30..1}; do 38 | bashbot_pod=$(kubectl -n ${ns} get pods --template '{{range .items}}{{.metadata.name}}{{end}}' --selector=app=${dn}) 39 | last_log_line=$(kubectl -n ${ns} logs $bashbot_pod | sed -e 's/\\*\\n/\n/g') 40 | # Tail the last line of the bashbot pod's log looking 41 | # for the string 'Bashbot is now connected to slack' 42 | if [[ $last_log_line =~ "Bashbot is now connected to slack" ]]; then 43 | echo "Bashbot connected to slack successfully!" 44 | 45 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 46 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "Bashbot connected to slack! Running automated tests..."' 47 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 48 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot help"' 49 | sleep 3 50 | found_connected=1 51 | break 52 | fi 53 | echo "Bashbot not yet connected to slack. $j more attempts..." 54 | sleep 3 55 | done 56 | [ $found_connected -eq 1 ] && exit 0 || exit 1 57 | fi 58 | 59 | # Since the deployment was not ready, try again $i more times 60 | echo "Deployment not found or not ready. $i more attempts..." 61 | sleep 3 62 | done 63 | 64 | # The retry loop has exited without finding a stable deployment 65 | echo "Bashbot deployment failed :(" 66 | # Display some debug information and fail test 67 | kubectl --namespace ${ns} get deployments 68 | kubectl --namespace ${ns} get pods -o wide 69 | exit 1 70 | } 71 | 72 | # Usage: ./test-deployment.sh [namespace] [deployment] 73 | namespace=${1:-bashbot} 74 | deploymentName=${2:-bashbot} 75 | 76 | main $namespace $deploymentName 77 | -------------------------------------------------------------------------------- /charts/bashbot/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for bashbot. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | botname: bashbot 6 | namespace: bashbot 7 | log_level: info 8 | log_format: text 9 | 10 | image: 11 | # repository: ghcr.io/mathew-fleisch/bashbot 12 | repository: mathewfleisch/bashbot 13 | pullPolicy: IfNotPresent 14 | # Overrides the image tag whose default is the chart appVersion. 15 | # tag: latest 16 | command: 17 | - /bashbot/entrypoint.sh 18 | # - /bin/bash 19 | # args: 20 | # - -c 21 | # - 'echo "Hello, world!" && sleep 3600' 22 | 23 | imagePullSecrets: [] 24 | 25 | serviceAccount: 26 | # Specifies whether a service account should be created (necessary for running kubectl commands). 27 | create: true 28 | # Annotations to add to the service account 29 | annotations: {} 30 | # The name of the service account to use. 31 | # If not set and create is true, a name is generated using the fullname template 32 | 33 | podAnnotations: {} 34 | 35 | podSecurityContext: {} 36 | # fsGroup: 2000 37 | 38 | securityContext: {} 39 | # capabilities: 40 | # drop: 41 | # - ALL 42 | # readOnlyRootFilesystem: true 43 | # runAsNonRoot: true 44 | # runAsUser: 1000 45 | 46 | nodeSelector: {} 47 | 48 | tolerations: [] 49 | 50 | affinity: {} 51 | 52 | -------------------------------------------------------------------------------- /cmd/install-dependencies.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // installDependenciesCmd represents the installDependencies command 8 | var installDependenciesCmd = &cobra.Command{ 9 | Use: "install-dependencies", 10 | Short: "Cycle through dependencies array in config file to install extra dependencies", 11 | Run: func(cmd *cobra.Command, _ []string) { 12 | slackClient := initSlackClient(cmd) 13 | slackClient.InstallVendorDependencies() 14 | }, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(installDependenciesCmd) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mathew-fleisch/bashbot/internal/slack" 8 | "github.com/spf13/cobra" 9 | 10 | homedir "github.com/mitchellh/go-homedir" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | var cfgFile string 15 | 16 | // rootCmd represents the base command when called without any subcommands 17 | var rootCmd = &cobra.Command{ 18 | Use: "bashbot", 19 | Short: "Bashbot Slack bot", 20 | Long: ` 21 | ____ _ ____ _ 22 | | _ \ | | | _ \ | | 23 | | |_) | __ _ ___| |__ | |_) | ___ | |_ 24 | | _ < / _' / __| '_ \| _ < / _ \| __| 25 | | |_) | (_| \__ \ | | | |_) | (_) | |_ 26 | |____/ \__,_|___/_| |_|____/ \___/ \__| 27 | Bashbot is a slack bot, written in golang, that can be configured 28 | to run bash commands or scripts based on a configuration file. 29 | `, 30 | } 31 | 32 | // Execute adds all child commands to the root command and sets flags appropriately. 33 | // This is called by main.main(). It only needs to happen once to the rootCmd. 34 | func Execute() { 35 | cobra.CheckErr(rootCmd.Execute()) 36 | } 37 | 38 | func init() { 39 | cobra.OnInitialize(initConfig) 40 | 41 | rootCmd.PersistentFlags().String("config-file", "", "[REQUIRED] Filepath to config.yaml file (or environment variable BASHBOT_CONFIG_FILEPATH set)") 42 | rootCmd.PersistentFlags().String("slack-bot-token", "", "[REQUIRED] Slack bot token used to authenticate with api (or environment variable SLACK_BOT_TOKEN set)") 43 | rootCmd.PersistentFlags().String("slack-app-token", "", "[REQUIRED] Slack app token used to authenticate with api (or environment variable SLACK_APP_TOKEN set)") 44 | rootCmd.PersistentFlags().String("log-level", "info", "Log elevel to display (info,debug,warn,error)") 45 | rootCmd.PersistentFlags().String("log-format", "text", "Display logs as json or text") 46 | 47 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 48 | } 49 | 50 | // initConfig reads in config file and ENV variables if set. 51 | func initConfig() { 52 | if cfgFile != "" { 53 | // Use config file from the flag. 54 | viper.SetConfigFile(cfgFile) 55 | } else { 56 | // Find home directory. 57 | home, err := homedir.Dir() 58 | cobra.CheckErr(err) 59 | 60 | // Search config in home directory with name ".bashbot" (without extension). 61 | viper.AddConfigPath(home) 62 | viper.SetConfigName(".bashbot") 63 | } 64 | 65 | viper.AutomaticEnv() // read in environment variables that match 66 | 67 | // If a config file is found, read it in. 68 | if err := viper.ReadInConfig(); err == nil { 69 | fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 70 | } 71 | } 72 | 73 | func initSlackClient(cmd *cobra.Command) *slack.Client { 74 | configFile, _ := cmd.Flags().GetString("config-file") 75 | slackBotToken, _ := cmd.Flags().GetString("slack-bot-token") 76 | slackAppToken, _ := cmd.Flags().GetString("slack-app-token") 77 | logLevel, _ := cmd.Flags().GetString("log-level") 78 | logFormat, _ := cmd.Flags().GetString("log-format") 79 | if logLevel != "" && logFormat != "" { 80 | slack.ConfigureLogger(logLevel, logFormat) 81 | } 82 | return slack.NewSlackClient(configFile, slackBotToken, slackAppToken) 83 | } 84 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // runCmd represents the run command 8 | var runCmd = &cobra.Command{ 9 | Use: "run", 10 | Short: "Run bashbot", 11 | Run: func(cmd *cobra.Command, _ []string) { 12 | slackClient := initSlackClient(cmd) 13 | slackClient.Run() 14 | }, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(runCmd) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/send-file.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // sendFileCmd represents the sendFile command 9 | var sendFileCmd = &cobra.Command{ 10 | Use: "send-file", 11 | Short: "Send file to slack channel", 12 | Run: func(cmd *cobra.Command, _ []string) { 13 | channel, _ := cmd.Flags().GetString("channel") 14 | if channel == "" { 15 | log.Fatal("--channel flag is required") 16 | } 17 | file, _ := cmd.Flags().GetString("file") 18 | if file == "" { 19 | log.Fatal("--file flag is required") 20 | } 21 | slackClient := initSlackClient(cmd) 22 | err := slackClient.SendFileToChannel(channel, file) 23 | if err != nil { 24 | log.Error(err) 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | rootCmd.AddCommand(sendFileCmd) 31 | 32 | sendFileCmd.Flags().String("channel", "", "Slack channel to send file to") 33 | sendFileCmd.Flags().String("file", "", "Filepath/filename to send to slack channel") 34 | } 35 | -------------------------------------------------------------------------------- /cmd/send-message.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // sendMessageCmd represents the sendMessage command 9 | var sendMessageCmd = &cobra.Command{ 10 | Use: "send-message", 11 | Short: "Send stand-alone slack message", 12 | Run: func(cmd *cobra.Command, _ []string) { 13 | channel, _ := cmd.Flags().GetString("channel") 14 | if channel == "" { 15 | log.Fatal("--channel flag is required") 16 | } 17 | msg, _ := cmd.Flags().GetString("msg") 18 | user, _ := cmd.Flags().GetString("user") 19 | slackClient := initSlackClient(cmd) 20 | if user != "" { 21 | slackClient.SendMessageToUser(channel, user, msg) 22 | return 23 | } 24 | slackClient.SendMessageToChannel(channel, msg) 25 | }, 26 | } 27 | 28 | func init() { 29 | rootCmd.AddCommand(sendMessageCmd) 30 | 31 | sendMessageCmd.Flags().String("channel", "", "Slack channel to send stand-alone message to") 32 | sendMessageCmd.Flags().String("user", "", "Slack user to send stand-alone message to") 33 | sendMessageCmd.Flags().String("msg", "", "Message to send to slack channel/user") 34 | } 35 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var ( 11 | Version = "development" 12 | ) 13 | 14 | // versionCmd represents the version command 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Get current version", 18 | Run: func(_ *cobra.Command, _ []string) { 19 | operatingSystem := runtime.GOOS 20 | systemArchitecture := runtime.GOARCH 21 | fmt.Printf("bashbot-%s-%s\t %s\n", operatingSystem, systemArchitecture, Version) 22 | }, 23 | } 24 | 25 | func init() { 26 | rootCmd.AddCommand(versionCmd) 27 | } 28 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC1090 3 | 4 | echo " 5 | ____ _ ____ _ 6 | | _ \ | | | _ \ | | 7 | | |_) | __ _ ___| |__ | |_) | ___ | |_ 8 | | _ < / _' / __| '_ \| _ < / _ \| __| 9 | | |_) | (_| \__ \ | | | |_) | (_) | |_ 10 | |____/ \__,_|___/_| |_|____/ \___/ \__|" 11 | 12 | if ! command -v bashbot > /dev/null; then 13 | echo "bashbot is not installed. Please install bashbot and try again." 14 | exit 1 15 | fi 16 | 17 | # If .env file is present, load it. 18 | if [ -f "$BASHBOT_ENV_VARS_FILEPATH" ]; then 19 | . "$BASHBOT_ENV_VARS_FILEPATH" 20 | fi 21 | 22 | if ! [ -f "$BASHBOT_CONFIG_FILEPATH" ]; then 23 | echo "bashbot config file not found. Please create one and try again." 24 | exit 1 25 | fi 26 | 27 | if [ -z "$SLACK_BOT_TOKEN" ]; then 28 | echo "SLACK_BOT_TOKEN is not set. Please set it and try again." 29 | exit 1 30 | fi 31 | 32 | if [ -z "$SLACK_APP_TOKEN" ]; then 33 | echo "SLACK_APP_TOKEN is not set. Please set it and try again." 34 | exit 1 35 | fi 36 | mkdir -p vendor 37 | sleep 4 38 | 39 | # If the log-level doesn't exist, set it to 'info' 40 | LOG_LEVEL=${LOG_LEVEL:-info} 41 | # If the log-format doesn't exist, set it to 'text' 42 | LOG_FORMAT=${LOG_FORMAT:-text} 43 | 44 | # Run install-dependencies path 45 | bashbot install-dependencies \ 46 | --log-level "$LOG_LEVEL" \ 47 | --log-format "$LOG_FORMAT" 48 | 49 | # Run Bashbot binary passing the config file and the Slack token 50 | bashbot run \ 51 | --log-level "$LOG_LEVEL" \ 52 | --log-format "$LOG_FORMAT" 53 | -------------------------------------------------------------------------------- /examples/aqi/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Get Air Quality Index By Zip Code 2 | 3 | In this example, a curl is executed via bash script from the [Air Now API](https://docs.airnowapi.org/) and the response json is parsed via jq to send a formatted message back to slack. 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `!bashbot aqi [zip]` in a slack channel where Bashbot is also a member. The script is expected to exist before execution at the relative path `./examples/aqi` and requires the following environment variables to be set: `AIRQUALITY_API_KEY` The `zip` parameter is validated by building a list of all five digit integers with a for loop. This command requires [jq](https://stedolan.github.io/jq/) and [curl](https://curl.se/) to be installed on the host machine. 10 | 11 | ```yaml 12 | name: Air Quality Index 13 | description: Get air quality index by zip code 14 | envvars: 15 | - AIRQUALITY_API_KEY 16 | dependencies: 17 | - curl 18 | - jq 19 | help: "!bashbot aqi [zip-code]" 20 | trigger: aqi 21 | location: /bashbot/vendor/bashbot/examples/aqi 22 | command: 23 | - "./aqi.sh ${zip}" 24 | parameters: 25 | - name: zip 26 | allowed: [] 27 | description: any zip code 28 | match: (^\d{5}$)|(^\d{9}$)|(^\d{5}-\d{4}$) 29 | log: true 30 | ephemeral: false 31 | response: text 32 | permissions: 33 | - all 34 | ``` 35 | 36 | --- 37 | 38 | There is one script ([aqi.sh](aqi.sh)) associated with this example and takes one argument/parameter to retrieve the air quality index value for specific zip codes. The raw json response from the [Air Now API](https://docs.airnowapi.org/) before [aqi.sh](aqi.sh) parses the values that are displayed in slack: 39 | 40 | ```json 41 | [ 42 | { 43 | "DateObserved": "2021-08-25 ", 44 | "HourObserved": 3, 45 | "LocalTimeZone": "PST", 46 | "ReportingArea": "San Francisco", 47 | "StateCode": "CA", 48 | "Latitude": 37.75, 49 | "Longitude": -122.43, 50 | "ParameterName": "O3", 51 | "AQI": 32, 52 | "Category": { 53 | "Number": 1, 54 | "Name": "Good" 55 | } 56 | }, 57 | { 58 | "DateObserved": "2021-08-25 ", 59 | "HourObserved": 3, 60 | "LocalTimeZone": "PST", 61 | "ReportingArea": "San Francisco", 62 | "StateCode": "CA", 63 | "Latitude": 37.75, 64 | "Longitude": -122.43, 65 | "ParameterName": "PM2.5", 66 | "AQI": 30, 67 | "Category": { 68 | "Number": 1, 69 | "Name": "Good" 70 | } 71 | } 72 | ] 73 | ``` 74 | -------------------------------------------------------------------------------- /examples/aqi/aqi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$AIRQUALITY_API_KEY" ]; then 4 | echo "Missing Air Now API Key..." 5 | echo "" 6 | exit 0 7 | fi 8 | 9 | zip=$1 10 | if [ -z "$zip" ]; then 11 | echo "Usage: $0 [zip]" 12 | exit 0 13 | fi 14 | 15 | RESPONSES_420=("alright, alright, alright" "pass that shit" "AQI: hazy for dayyzzz" "420 blaze it!" "marijuana... marijuana... marijuana..." "how much for a zip? lul") 16 | EMOJIS_420=("stoned_moon" "partyweed" "nice" "lolface" "hehehe" "fingerguns" "dancing-waggle" "blazeit420" "420") 17 | if [ "$zip" == "42069" ] || [ "$zip" == "69420" ]; then 18 | stoned_emoji=":${EMOJIS_420[$RANDOM % ${#EMOJIS_420[@]}]}:" 19 | stoned_response=${RESPONSES_420[$RANDOM % ${#RESPONSES_420[@]}]} 20 | 21 | echo "$stoned_emoji $stoned_response" 22 | exit 0 23 | fi 24 | 25 | response=$(curl -s "http://www.airnowapi.org/aq/observation/zipCode/current/?zipCode=${zip}&distance=5&format=application/json&API_KEY=${AIRQUALITY_API_KEY}") 26 | 27 | if [[ "$response" == "[]" ]]; then 28 | echo "There is no for this zip: $zip" 29 | exit 0 30 | fi 31 | 32 | aqi=$(echo "$response" | jq '.[0]') 33 | reporting_area=$(echo "$aqi" | jq -r '.ReportingArea') 34 | aqi_value=$(echo "$aqi" | jq -r '.AQI') 35 | time_stamp="$(echo "$aqi" | jq -r '.DateObserved')$(echo "$aqi" | jq -r '.HourObserved'):00" 36 | category=$(echo "$aqi" | jq -r '.Category.Name') 37 | case $category in 38 | "Good") emoji=":large_green_circle:";; 39 | "Moderate") emoji=":large_yellow_circle:";; 40 | "Unhealthy for Sensitive Groups") emoji=":large_orange_circle:";; 41 | "Unhealthy") emoji=":red_circle:";; 42 | "Very Unhealthy") emoji=":large_purple_circle:";; 43 | "Hazardous") emoji=":black_circle:";; 44 | esac 45 | 46 | echo "$emoji The in $reporting_area is $aqi_value ($category) as of $time_stamp"; 47 | -------------------------------------------------------------------------------- /examples/aqi/aqi.yaml: -------------------------------------------------------------------------------- 1 | name: Air Quality Index 2 | description: Get air quality index by zip code 3 | envvars: 4 | - AIRQUALITY_API_KEY 5 | dependencies: 6 | - curl 7 | - jq 8 | help: "!bashbot aqi [zip-code]" 9 | trigger: aqi 10 | location: /bashbot/vendor/bashbot/examples/aqi 11 | command: 12 | - "./aqi.sh ${zip}" 13 | parameters: 14 | - name: zip 15 | allowed: [] 16 | description: any zip code 17 | match: (^\d{5}$)|(^\d{9}$)|(^\d{5}-\d{4}$) 18 | log: true 19 | ephemeral: false 20 | response: text 21 | permissions: 22 | - all 23 | -------------------------------------------------------------------------------- /examples/aqi/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "aqi test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | main() { 16 | local ns=${1:-bashbot} 17 | local dn=${2:-bashbot} 18 | # Retry loop (20/$i with 3 second delay between loops) 19 | for i in {30..1}; do 20 | # Get the expected number of replicas for this deployment 21 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 22 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 23 | [ -z "$expectedReplicas" ] && expectedReplicas=1 24 | # Get the number of replicas that are ready for this deployment 25 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 26 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 27 | [ -z "$readyReplicas" ] && readyReplicas=0 28 | 29 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 30 | test $readyReplicas -eq $expectedReplicas \ 31 | && test 1 -eq 1 32 | if [ $? -eq 0 ]; then 33 | # echo "Bashbot deployment confirmed!" 34 | # kubectl --namespace ${ns} get deployments 35 | found_res=0 36 | for j in {3..1}; do 37 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 38 | # Send `!bashbot aqi [zip-code]` via bashbot binary within bashbot pod 39 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 40 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot aqi 90210"' 41 | sleep 5 42 | last_log_line=$(kubectl -n ${ns} logs --tail 10 $bashbot_pod) 43 | # Tail the last line of the bashbot pod's log looking 44 | # for the string 'Air Quality Index' to prove the info script 45 | # is showing the correct value for whoami 46 | if [[ $last_log_line =~ "Air Quality Index" ]]; then 47 | echo "aqi test successful!" 48 | found_res=1 49 | 50 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 51 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: aqi test successful!\n> Saw \"Air Quality Index\" in bashbot logs"' 52 | exit 0 53 | fi 54 | echo "Bashbot aqi test failed. $j more attempts..." 55 | sleep 5 56 | done 57 | # Don't require aqi tests to pass for the whole test to pass 58 | [ $found_res -eq 1 ] && exit 0 || exit 1 59 | fi 60 | 61 | # Since the deployment was not ready, try again $i more times 62 | echo "Deployment not found or not ready. $i more attempts..." 63 | sleep 5 64 | done 65 | 66 | # The retry loop has exited without finding a stable deployment 67 | echo "Bashbot deployment failed :(" 68 | # Display some debug information and fail test 69 | kubectl --namespace ${ns} get deployments 70 | kubectl --namespace ${ns} get pods -o wide 71 | 72 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 73 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: aqi test failed!"' 74 | exit 1 75 | } 76 | 77 | # Usage: ./test.sh [namespace] [deployment] 78 | namespace=${1:-bashbot} 79 | deploymentName=${2:-bashbot} 80 | 81 | main $namespace $deploymentName 82 | -------------------------------------------------------------------------------- /examples/asdf/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - List asdf Plugins 2 | 3 | In this example, a few asdf commands are combined to display the installed versions for each of the pre-installed ([in container](../../.tool-versions)) asdf plugins. 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `!bashbot asdf` in a slack channel where Bashbot is also a member. There is no external script for this command, takes no arugments/parameters, and expects asdf and asdf plugins to already be installed on the host machine/container. 10 | 11 | ```yaml 12 | name: List asdf plugins 13 | description: List the installed asdf plugins and their versions 14 | envvars: [] 15 | dependencies: [] 16 | help: "!bashbot asdf" 17 | trigger: asdf 18 | location: /bashbot/ 19 | command: 20 | - ". /usr/asdf/asdf.sh && asdf list" 21 | parameters: [] 22 | log: true 23 | ephemeral: false 24 | response: code 25 | permissions: 26 | - all 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/asdf/asdf.yaml: -------------------------------------------------------------------------------- 1 | name: List asdf plugins 2 | description: List the installed asdf plugins and their versions 3 | envvars: [] 4 | dependencies: [] 5 | help: "!bashbot asdf" 6 | trigger: asdf 7 | location: /bashbot/ 8 | command: 9 | - ". /usr/asdf/asdf.sh && asdf list" 10 | parameters: [] 11 | log: true 12 | ephemeral: false 13 | response: code 14 | permissions: 15 | - all -------------------------------------------------------------------------------- /examples/asdf/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "asdf test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | main() { 16 | local ns=${1:-bashbot} 17 | local dn=${2:-bashbot} 18 | # Retry loop (20/$i with 3 second delay between loops) 19 | for i in {30..1}; do 20 | # Get the expected number of replicas for this deployment 21 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 22 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 23 | [ -z "$expectedReplicas" ] && expectedReplicas=1 24 | # Get the number of replicas that are ready for this deployment 25 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 26 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 27 | [ -z "$readyReplicas" ] && readyReplicas=0 28 | 29 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 30 | test $readyReplicas -eq $expectedReplicas \ 31 | && test 1 -eq 1 32 | if [ $? -eq 0 ]; then 33 | # echo "Bashbot deployment confirmed!" 34 | # kubectl --namespace ${ns} get deployments 35 | found_asdf=0 36 | for j in {3..1}; do 37 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 38 | asdf_found=$(kubectl --namespace ${ns} exec $bashbot_pod -- bash -c '. /usr/asdf/asdf.sh && command -v asdf') 39 | # Tail the last line of the bashbot pod's log looking 40 | # for the string 'Bashbot is now connected to slack' 41 | if [ -n "$asdf_found" ]; then 42 | echo "asdf found!" 43 | found_asdf=1 44 | 45 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 46 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: asdf installed successfully!"' 47 | break 48 | fi 49 | echo "Bashbot dependency test failed (asdf). $j more attempts..." 50 | sleep 5 51 | done 52 | 53 | found_res=0 54 | for j in {3..1}; do 55 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 56 | # Send `!bashbot asdf` via bashbot binary within bashbot pod 57 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 58 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot asdf"' 59 | sleep 5 60 | last_log_line=$(kubectl -n ${ns} logs --tail 100 $bashbot_pod) 61 | # Tail the last line of the bashbot pod's log looking 62 | # for the string 'kubectl' to prove the info script 63 | # is showing the correct value for whoami 64 | if [[ $last_log_line =~ "kubectl" ]]; then 65 | echo "asdf test successful!" 66 | found_res=1 67 | 68 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 69 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: asdf test successful!\n> Saw \"kubectl\" installed via asdf in bashbot logs"' 70 | break 71 | fi 72 | echo "Bashbot aqi test failed. $j more attempts..." 73 | sleep 5 74 | done 75 | [ $found_asdf -eq 1 ] && [ $found_res -eq 1 ] && exit 0 || exit 1 76 | fi 77 | 78 | # Since the deployment was not ready, try again $i more times 79 | echo "Deployment not found or not ready. $i more attempts..." 80 | sleep 5 81 | done 82 | 83 | # The retry loop has exited without finding a stable deployment 84 | echo "Bashbot deployment failed :(" 85 | # Display some debug information and fail test 86 | kubectl --namespace ${ns} get deployments 87 | kubectl --namespace ${ns} get pods -o wide 88 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 89 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: dependency test (asdf) failed!"' 90 | exit 1 91 | } 92 | 93 | # Usage: ./test.sh [namespace] [deployment] 94 | namespace=${1:-bashbot} 95 | deploymentName=${2:-bashbot} 96 | 97 | main $namespace $deploymentName 98 | -------------------------------------------------------------------------------- /examples/describe/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Describe Command 2 | 3 | In this example, the configuration yaml file for Bashbot is parsed via [yq](https://github.com/mikefarah/yq) to display the trigger and name of each command in the file. 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `!bashbot describe [command]` in a slack channel where Bashbot is also a member. There is no external script for this command, and expects yq to already be installed, and the environment variable `BASHBOT_CONFIG_FILEPATH` to be pointing at the running configuration, on the host machine. 10 | 11 | ```yaml 12 | name: Describe Bashbot [command] 13 | description: Show the yaml object for a specific command 14 | envvars: 15 | - BASHBOT_CONFIG_FILEPATH 16 | dependencies: 17 | - yq 18 | help: "!bashbot describe [command]" 19 | trigger: describe 20 | location: /bashbot/ 21 | command: 22 | - yq e '.tools[] | select(.trigger=="${command}")' ${BASHBOT_CONFIG_FILEPATH} 23 | parameters: 24 | - name: command 25 | allowed: [] 26 | description: a command to describe ('bashbot list') 27 | source: 28 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 29 | log: true 30 | ephemeral: false 31 | response: code 32 | permissions: 33 | - all 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/describe/describe.yaml: -------------------------------------------------------------------------------- 1 | name: Describe Bashbot [command] 2 | description: Show the yaml object for a specific command 3 | envvars: 4 | - BASHBOT_CONFIG_FILEPATH 5 | dependencies: 6 | - yq 7 | help: "!bashbot describe [command]" 8 | trigger: describe 9 | location: /bashbot/ 10 | command: 11 | - yq e '.tools[] | select(.trigger=="${command}")' ${BASHBOT_CONFIG_FILEPATH} 12 | parameters: 13 | - name: command 14 | allowed: [] 15 | description: a command to describe ('bashbot list') 16 | source: 17 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 18 | log: true 19 | ephemeral: false 20 | response: code 21 | permissions: 22 | - all -------------------------------------------------------------------------------- /examples/get-file-from-repo/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Get File from (Private) Repo 2 | 3 | In this example, a bash script is executed to download a file from a repository (that could be private) to the local file system. This command is useful for pulling fresh configuration without rebuilding bashbot. 4 | 5 | ## Bashbot configuration 6 | 7 | This command is triggered by sending `bashbot get-file-from-repo` in a slack channel where Bashbot is also a member. The script is expected to exist before execution at the relative path `./examples/get-file-from-repo/get-file-from-repo.sh` and requires the following environment variables to be set: `github_token github_org github_repo github_branch github_filename output_filename`. This command requires [curl](https://curl.se/) to be installed on the host machine. 8 | 9 | ```json 10 | { 11 | "name": "Update running configuration", 12 | "description": "Pulls a fresh configuration json file from github (could be private repo with GIT_TOKEN environment variable set)", 13 | "envvars": ["github_token", "github_org", "github_repo", "github_branch", "github_filename", "output_filename"], 14 | "dependencies": ["curl"], 15 | "help": "bashbot get-file-from-repo", 16 | "trigger": "get-file-from-repo", 17 | "location": "./examples/get-file-from-repo", 18 | "command": [ 19 | "github_org=mathew-fleisch", 20 | "&& github_repo=bashbot", 21 | "&& github_filename=sample-config.yaml", 22 | "&& github_branch=main", 23 | "&& output_filename=${BASHBOT_CONFIG_FILEPATH}" 24 | ], 25 | "": "./get-file-from-repo.sh", 26 | "parameters": [], 27 | "log": false, 28 | "ephemeral": false, 29 | "response": "code", 30 | "permissions": ["all"] 31 | } 32 | ``` 33 | 34 | ## Bashbot script 35 | 36 | This script expects environment variables to be set before executing and is designed to download a single file from a public or private repository. This command is useful to update the configuration json of a running instance of Bashbot without rebuilding or restarting. Each command that is executed in Bashbot will re-parse the json file at `$BASHBOT_CONFIG_FILEPATH`. Running this command will replace the running configuration json. 37 | 38 | ```bash 39 | github_base="${github_base:-api.github.com}" 40 | expected_variables="github_token github_org github_repo github_branch github_filename output_filename" 41 | for expect in $expected_variables; do 42 | if [[ -z "${!expect}" ]]; then 43 | echo "Missing environment variable $expect" 44 | echo "Expected: $expected_variables" 45 | exit 1 46 | fi 47 | done 48 | echo "Downloading ${github_filename} from: https://api.github.com/repos/${github_org}/${github_repo}/contents/${github_filename}?ref=${github_branch}" 49 | echo "To: ${output_filename}" 50 | curl -H "Authorization: token $github_token" \ 51 | -H "Accept: application/vnd.github.v3+json" \ 52 | -H "Content-Type: application/json" \ 53 | -m 15 \ 54 | -o ${output_filename} \ 55 | -sL https://${github_base}/repos/${github_org}/${github_repo}/contents/${github_filename}?ref=${github_branch} 2>&1 56 | ``` 57 | -------------------------------------------------------------------------------- /examples/get-file-from-repo/get-file-from-repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Update running configuration", 3 | "description": "Pulls a fresh configuration json file from github (could be private repo with GIT_TOKEN environment variable set)", 4 | "envvars": ["github_token", "github_org", "github_repo", "github_branch", "github_filename", "output_filename"], 5 | "dependencies": ["curl"], 6 | "help": "bashbot get-file-from-repo", 7 | "trigger": "get-file-from-repo", 8 | "location": "./examples/get-file-from-repo", 9 | "command": [ 10 | "github_org=mathew-fleisch", 11 | "&& github_repo=bashbot", 12 | "&& github_filename=sample-config.yaml", 13 | "&& github_branch=main", 14 | "&& output_filename=${BASHBOT_CONFIG_FILEPATH}" 15 | ], 16 | "": "./get-file-from-repo.sh", 17 | "parameters": [], 18 | "log": false, 19 | "ephemeral": false, 20 | "response": "code", 21 | "permissions": ["all"] 22 | } -------------------------------------------------------------------------------- /examples/get-file-from-repo/get-file-from-repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2154 3 | set -eou pipefail 4 | 5 | github_base="${github_base:-api.github.com}" 6 | expected_variables="github_token github_org github_repo github_branch github_filename output_filename" 7 | for expect in $expected_variables; do 8 | if [[ -z "${!expect}" ]]; then 9 | echo "Missing environment variable $expect" 10 | echo "Expected: $expected_variables" 11 | exit 1 12 | fi 13 | done 14 | echo "Downloading ${github_filename} from: https://${github_base}/repos/${github_org}/${github_repo}/contents/${github_filename}?ref=${github_branch}" 15 | echo "To: ${output_filename}" 16 | curl -H "Authorization: token $github_token" \ 17 | -H "Accept: application/vnd.github.v3+json" \ 18 | -H "Content-Type: application/json" \ 19 | -m 15 \ 20 | -o ${output_filename} \ 21 | -sL https://${github_base}/repos/${github_org}/${github_repo}/contents/${github_filename}?ref=${github_branch} 2>&1 22 | 23 | -------------------------------------------------------------------------------- /examples/help/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Help Dialog 2 | 3 | In this example, the configuration yaml file for Bashbot is parsed via yq to display the help and description values for each command 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `!bashbot help` in a slack channel where Bashbot is also a member. There is no external script for this command, takes no arugments/parameters, and expects yq to already be installed, and the environment variable `BASHBOT_CONFIG_FILEPATH` to be pointing at the running configuration, on the host machine. 10 | 11 | ```yaml 12 | name: BashBot Help 13 | description: Show this message 14 | envvars: 15 | - BASHBOT_CONFIG_FILEPATH 16 | dependencies: 17 | - yq 18 | help: "!bashbot help" 19 | trigger: help 20 | location: /bashbot/ 21 | command: 22 | - echo "BashBot is a tool for infrastructure/devops teams to automate tasks triggered by slash-command-like declarative configuration" && 23 | - echo '```' && 24 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 25 | - "| sed -e 's/\\\"//g'" 26 | - "| sed -e 's/:/ -/g' &&" 27 | - echo '```' 28 | parameters: [] 29 | log: true 30 | ephemeral: false 31 | response: text 32 | permissions: 33 | - all 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/help/help.yaml: -------------------------------------------------------------------------------- 1 | name: BashBot Help 2 | description: Show this message 3 | envvars: 4 | - BASHBOT_CONFIG_FILEPATH 5 | dependencies: 6 | - yq 7 | help: "!bashbot help" 8 | trigger: help 9 | location: /bashbot/ 10 | command: 11 | - echo "BashBot is a tool for infrastructure/devops teams to automate tasks triggered by slash-command-like declarative configuration" && 12 | - echo '```' && 13 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 14 | - "| sed -e 's/\\\"//g'" 15 | - "| sed -e 's/:/ -/g' &&" 16 | - echo '```' 17 | parameters: [] 18 | log: true 19 | ephemeral: false 20 | response: text 21 | permissions: 22 | - all -------------------------------------------------------------------------------- /examples/info/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Get Info 2 | 3 | In this example, a bash script is executed to return information about the environment Bashbot is running in, and the channel/user name/id from which it was executed. 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `bashbot info` in a slack channel where Bashbot is also a member. The script is expected to exist before execution at the relative path `./examples/version/get-info.sh` and requires no additional input to execute. It takes no arguments/parameters and returns `stdout` as a slack message, in the channel it was executed from. 10 | 11 | ```yaml 12 | name: Get User/Channel Info 13 | description: Get information about the user and channel command is being run from 14 | envvars: [] 15 | dependencies: [] 16 | help: "!bashbot info" 17 | trigger: info 18 | location: /bashbot/vendor/bashbot/examples/info 19 | command: 20 | - "./get-info.sh" 21 | parameters: [] 22 | log: true 23 | ephemeral: false 24 | response: code 25 | permissions: 26 | - all 27 | ``` 28 | 29 | ## Bashbot script 30 | 31 | Every Bashbot command, executed in slack, sets five environment variables describing "where," "when," and "who" executed the Bashbot command itself. The [get-info.sh](get-info.sh) script prints these environment variables and some other basic information about the host Bashbot is running from. -------------------------------------------------------------------------------- /examples/info/get-info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Username[id]: ${TRIGGERED_USER_NAME}[${TRIGGERED_USER_ID}]" 4 | echo " Channel[id]: ${TRIGGERED_CHANNEL_NAME}[${TRIGGERED_CHANNEL_ID}]" 5 | echo " Trigged at: ${TRIGGERED_AT}" 6 | echo "------------------------------------------" 7 | echo " Date: $(date)" 8 | echo " uname -a: $(uname -a)" 9 | echo " uptime: $(uptime)" 10 | echo " whoami: $(whoami)" -------------------------------------------------------------------------------- /examples/info/info.yaml: -------------------------------------------------------------------------------- 1 | name: Get User/Channel Info 2 | description: Get information about the user and channel command is being run from 3 | envvars: [] 4 | dependencies: [] 5 | help: "!bashbot info" 6 | trigger: info 7 | location: /bashbot/vendor/bashbot/examples/info 8 | command: 9 | - "./get-info.sh" 10 | parameters: [] 11 | log: true 12 | ephemeral: false 13 | response: code 14 | permissions: 15 | - all -------------------------------------------------------------------------------- /examples/info/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "container/user info test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | main() { 16 | local ns=${1:-bashbot} 17 | local dn=${2:-bashbot} 18 | # Retry loop (20/$i with 3 second delay between loops) 19 | for i in {30..1}; do 20 | # Get the expected number of replicas for this deployment 21 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 22 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 23 | [ -z "$expectedReplicas" ] && expectedReplicas=1 24 | # Get the number of replicas that are ready for this deployment 25 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 26 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 27 | [ -z "$readyReplicas" ] && readyReplicas=0 28 | 29 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 30 | test $readyReplicas -eq $expectedReplicas \ 31 | && test 1 -eq 1 32 | if [ $? -eq 0 ]; then 33 | # echo "Bashbot deployment confirmed!" 34 | # kubectl --namespace ${ns} get deployments 35 | found_user=0 36 | for j in {3..1}; do 37 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 38 | # Send `!bashbot info` via bashbot binary within bashbot pod 39 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 40 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot info"' 41 | sleep 5 42 | last_log_line=$(kubectl -n ${ns} logs --tail 10 $bashbot_pod) 43 | # Tail the last line of the bashbot pod's log looking 44 | # for the string 'whoami: bb' to prove the info script 45 | # is showing the correct value for whoami 46 | if [[ $last_log_line =~ "whoami: bb" ]]; then 47 | echo "container/user info test successful!" 48 | found_user=1 49 | 50 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 51 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: container/user info test successful!\n> Saw \"whoami: bb\" in bashbot logs"' 52 | exit 0 53 | fi 54 | echo "Bashbot container/user info test failed. $j more attempts..." 55 | sleep 5 56 | done 57 | # Don't require container/user info tests to pass for the whole test to pass 58 | [ $found_user -eq 1 ] && exit 0 || exit 1 59 | fi 60 | 61 | # Since the deployment was not ready, try again $i more times 62 | echo "Deployment not found or not ready. $i more attempts..." 63 | sleep 5 64 | done 65 | 66 | # The retry loop has exited without finding a stable deployment 67 | echo "Bashbot deployment failed :(" 68 | # Display some debug information and fail test 69 | kubectl --namespace ${ns} get deployments 70 | kubectl --namespace ${ns} get pods -o wide 71 | 72 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 73 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: container/user info test failed!"' 74 | exit 1 75 | } 76 | 77 | # Usage: ./test.sh [namespace] [deployment] 78 | namespace=${1:-bashbot} 79 | deploymentName=${2:-bashbot} 80 | 81 | main $namespace $deploymentName 82 | -------------------------------------------------------------------------------- /examples/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot in Kubernetes 2 | 3 | If Bashbot is installed with helm into a kubernetes cluster, with a service-account to make kube-api calls, it can be used as an SRE tool to quickly run (approved) troubleshooting kubectl commands to query status of the cluster that Bashbot is running in. Often times deleting specific pods is all a kubernetes cluster needs, to recover from a bad state (kubernetes version of "turning it off and on again"). Giving Bashbot the ability to carry out these actions (in private channels to restrict access within slack), can decrease time to resolution and provides a framework to codify runbooks as code. 4 | 5 | ## kubectl cluster-info 6 | 7 | ```yaml 8 | name: Kubectl cluster-info 9 | description: Return cluster-info by kubectl command 10 | envvars: [] 11 | dependencies: [] 12 | help: "!bashbot k-info" 13 | trigger: k-info 14 | location: /bashbot/ 15 | command: 16 | - ". /usr/asdf/asdf.sh && kubectl cluster-info" 17 | parameters: [] 18 | log: true 19 | ephemeral: false 20 | response: code 21 | permissions: 22 | - all 23 | ``` 24 | 25 | ## kubectl get pod 26 | 27 | ```yaml 28 | name: kubectl get bashbot pod 29 | description: Get the bashbot pod using kubectl 30 | envvars: 31 | - BOTNAME 32 | - NAMESPACE 33 | dependencies: [] 34 | help: "!bashbot k-get-pod" 35 | trigger: k-get-pod 36 | location: /bashbot/ 37 | command: 38 | - ". /usr/asdf/asdf.sh" 39 | - "&& kubectl --namespace ${NAMESPACE} get pods | grep -E \"NAME|${BOTNAME}\"" 40 | parameters: [] 41 | log: true 42 | ephemeral: false 43 | response: code 44 | permissions: 45 | - all 46 | ``` 47 | 48 | ## kubectl -n [namespace] delete pod [pod-name] 49 | 50 | ```yaml 51 | name: kubectl -n [namespace] delete pod [podname] 52 | description: Delete a pod 53 | envvars: [] 54 | dependencies: [] 55 | help: "!bashbot k-delete-pod [namespace] [podname]" 56 | trigger: k-delete-pod 57 | location: /bashbot/ 58 | command: 59 | - ". /usr/asdf/asdf.sh" 60 | - "&& kubectl -n ${namespace} delete pod ${podname} --ignore-not-found=true" 61 | parameters: 62 | - name: namespace 63 | allowed: [] 64 | description: List all of the namespaces in the cluster 65 | source: 66 | - ". /usr/asdf/asdf.sh" 67 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 68 | - name: podname 69 | allowed: [] 70 | description: List all of the pods in the cluster by name 71 | source: 72 | - ". /usr/asdf/asdf.sh" 73 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 74 | log: true 75 | ephemeral: false 76 | response: code 77 | permissions: 78 | - all 79 | ``` 80 | 81 | ## kubectl -n [namespace] decribe pod [podname] 82 | 83 | ```yaml 84 | name: kubectl -n [namespace] describe pod [podname] 85 | description: Return pod information 86 | envvars: [] 87 | dependencies: [] 88 | help: "!bashbot k-describe-pod [namespace] [podname]" 89 | trigger: k-describe-pod 90 | location: /bashbot/ 91 | command: 92 | - ". /usr/asdf/asdf.sh" 93 | - "&& kubectl -n ${namespace} describe pod ${podname}" 94 | parameters: 95 | - name: namespace 96 | allowed: [] 97 | description: List all of the namespaces in the cluster 98 | source: 99 | - ". /usr/asdf/asdf.sh" 100 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 101 | - name: podname 102 | allowed: [] 103 | description: List all of the pods in the cluster by name 104 | source: 105 | - ". /usr/asdf/asdf.sh" 106 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 107 | log: true 108 | ephemeral: false 109 | response: file 110 | permissions: 111 | - all 112 | ``` 113 | 114 | ## kubectl -n [namespace] logs -f [pod-name] 115 | 116 | ```yaml 117 | name: kubectl -n [namespace] logs --tail 10 [podname] 118 | description: Return last 10 lines of pod logs 119 | envvars: [] 120 | dependencies: [] 121 | help: "!bashbot k-pod-logs [namespace] [podname]" 122 | trigger: k-pod-logs 123 | location: /bashbot/ 124 | command: 125 | - ". /usr/asdf/asdf.sh" 126 | - "&& kubectl -n ${namespace} logs --tail 10 ${podname}" 127 | parameters: 128 | - name: namespace 129 | allowed: [] 130 | description: List all of the namespaces in the cluster 131 | source: 132 | - ". /usr/asdf/asdf.sh" 133 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 134 | - name: podname 135 | allowed: [] 136 | description: List all of the pods in the cluster by name 137 | source: 138 | - ". /usr/asdf/asdf.sh" 139 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 140 | log: true 141 | ephemeral: false 142 | response: code 143 | permissions: 144 | - all 145 | ``` 146 | -------------------------------------------------------------------------------- /examples/kubernetes/kubernetes-manifests-withsa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: bashbot/templates/serviceaccount.yaml 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: bashbot 7 | labels: 8 | helm.sh/chart: bashbot-v2.0.5 9 | app.kubernetes.io/name: bashbot 10 | app.kubernetes.io/instance: bashbot 11 | app.kubernetes.io/version: "v2.0.5" 12 | app.kubernetes.io/managed-by: Helm 13 | --- 14 | # Source: bashbot/templates/configmap.yaml 15 | apiVersion: v1 16 | kind: ConfigMap 17 | metadata: 18 | name: bashbot-configmap 19 | namespace: bashbot 20 | data: 21 | config.yaml: | 22 | admins: 23 | - trigger: "!bashbot" 24 | appName: BashBot 25 | userIds: 26 | - UP3BBQX34 27 | privateChannelId: GPFMM5MD2 28 | logChannelId: CPJ1NFPL7 29 | messages: 30 | - active: true 31 | name: welcome 32 | text: Witness the power of %s 33 | - active: true 34 | name: processing_command 35 | text: ":robot_face: Processing command..." 36 | - active: true 37 | name: processing_raw_command 38 | text: ":smiling_imp: Processing raw command..." 39 | - active: true 40 | name: command_not_found 41 | text: ":thinking_face: Command not found..." 42 | - active: true 43 | name: incorrect_parameters 44 | text: ":face_with_monocle: Incorrect number of parameters" 45 | - active: true 46 | name: invalid_parameter 47 | text: ":face_with_monocle: Invalid parameter value: %s" 48 | - active: true 49 | name: ephemeral 50 | text: ":shushing_face: Message only shown to user who triggered it." 51 | - active: true 52 | name: unauthorized 53 | text: |- 54 | :skull_and_crossbones: You are not authorized to use this command in this channel. 55 | Allowed in: [%s] 56 | - active: true 57 | name: missingenvvar 58 | text: ":skull_and_crossbones: This command requires this environment variable to be set: [%s]" 59 | - active: true 60 | name: missingdependency 61 | text: ":skull_and_crossbones: This command requires: [%s]" 62 | tools: 63 | - name: BashBot Help 64 | description: Show this message 65 | envvars: 66 | - BASHBOT_CONFIG_FILEPATH 67 | dependencies: 68 | - yq 69 | help: "!bashbot help" 70 | trigger: help 71 | location: ./ 72 | command: 73 | - echo "BashBot is a tool for infrastructure/devops teams to automate tasks triggered by slash-command-like declarative configuration" && 74 | - echo '```' && 75 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 76 | - "| sed -e 's/\\\"//g'" 77 | - "| sed -e 's/:/ -/g' &&" 78 | - echo '```' 79 | parameters: [] 80 | log: true 81 | ephemeral: false 82 | response: text 83 | permissions: 84 | - all 85 | - name: Air Quality Index 86 | description: Get air quality index by zip code 87 | envvars: 88 | - AIRQUALITY_API_KEY 89 | dependencies: 90 | - curl 91 | - jq 92 | help: "!bashbot aqi [zip-code]" 93 | trigger: aqi 94 | location: ./vendor/bashbot/examples/aqi 95 | command: 96 | - "./aqi.sh ${zip}" 97 | parameters: 98 | - name: zip 99 | allowed: [] 100 | description: any zip code 101 | match: (^\d{5}$)|(^\d{9}$)|(^\d{5}-\d{4}$) 102 | log: true 103 | ephemeral: false 104 | response: text 105 | permissions: 106 | - all 107 | - name: Get User/Channel Info 108 | description: Get information about the user and channel command is being run from 109 | envvars: [] 110 | dependencies: [] 111 | help: "!bashbot info" 112 | trigger: info 113 | location: ./vendor/bashbot/examples/info 114 | command: 115 | - "./get-info.sh" 116 | parameters: [] 117 | log: true 118 | ephemeral: false 119 | response: code 120 | permissions: 121 | - all 122 | - name: Display Configuration 123 | description: dump configuration as yaml blob 124 | envvars: [] 125 | dependencies: 126 | - yq 127 | help: "!bashbot dump" 128 | trigger: dump 129 | location: ./ 130 | command: 131 | - yq e '.' ${BASHBOT_CONFIG_FILEPATH} 132 | parameters: [] 133 | log: true 134 | ephemeral: false 135 | response: file 136 | permissions: 137 | - all 138 | - name: List Available Bashbot Commands 139 | description: List all of the possible commands stored in bashbot 140 | envvars: 141 | - BASHBOT_CONFIG_FILEPATH 142 | dependencies: 143 | - yq 144 | help: "!bashbot list" 145 | trigger: list 146 | location: ./ 147 | command: 148 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 149 | parameters: [] 150 | log: true 151 | ephemeral: false 152 | response: code 153 | permissions: 154 | - all 155 | - name: List Example Commands 156 | description: List commands from bashbot example commands 157 | envvars: [] 158 | dependencies: [] 159 | help: "!bashbot list-examples" 160 | trigger: list-examples 161 | location: ./vendor/bashbot/examples 162 | command: 163 | - find . -name "*.json" 164 | - "| xargs -I {} bash -c" 165 | - "'export example=$(basename {} .json)" 166 | - "&& printf \"%21s - %s\" \"$example\" \"https://github.com/mathew-fleisch/bashbot/tree/main/examples/$example\"" 167 | - "&& echo'" 168 | - "| sort -k 2" 169 | parameters: [] 170 | log: true 171 | ephemeral: false 172 | response: code 173 | permissions: 174 | - all 175 | - name: Regular expression example 176 | description: With great power, comes great responsibility 177 | envvars: [] 178 | dependencies: [] 179 | help: "!bashbot regex $([command])" 180 | trigger: regex 181 | location: ./ 182 | command: 183 | - ". /usr/asdf/asdf.sh && ${command} || true" 184 | parameters: 185 | - name: command 186 | allowed: [] 187 | description: This should allow any text to be used as input 188 | match: .* 189 | log: false 190 | ephemeral: false 191 | response: code 192 | permissions: 193 | - GPFMM5MD2 194 | - name: Curl Example 195 | description: Pass a valid url to curl 196 | envvars: [] 197 | dependencies: 198 | - curl 199 | help: "!bashbot curl [url]" 200 | trigger: curl 201 | location: ./ 202 | command: 203 | - curl -s ${url} | jq -r ".body" | tr "\n" " " 204 | parameters: 205 | - name: url 206 | allowed: [] 207 | description: A valid url (Expecting json with key body) 208 | match: ^(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 209 | log: true 210 | ephemeral: false 211 | response: code 212 | permissions: 213 | - all 214 | - name: Describe Bashbot [command] 215 | description: Show the yaml object for a specific command 216 | envvars: 217 | - BASHBOT_CONFIG_FILEPATH 218 | dependencies: 219 | - yq 220 | help: "!bashbot describe [command]" 221 | trigger: describe 222 | location: ./ 223 | command: 224 | - yq e '.tools[] | select(.trigger=="${command}")' ${BASHBOT_CONFIG_FILEPATH} 225 | parameters: 226 | - name: command 227 | allowed: [] 228 | description: a command to describe ('bashbot list') 229 | source: 230 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 231 | log: true 232 | ephemeral: false 233 | response: code 234 | permissions: 235 | - all 236 | - name: Environment variable test 237 | description: Show an example of how to only run a command if specific env vars are defined. 238 | envvars: 239 | - TRIGGERED_USER_NAME 240 | - TRIGGERED_USER_ID 241 | - TRIGGERED_CHANNEL_NAME 242 | - TRIGGERED_CHANNEL_ID 243 | dependencies: [] 244 | help: "!bashbot env-var-test" 245 | trigger: env-var-test 246 | location: ./ 247 | command: 248 | - "echo \"Username[id]: ${TRIGGERED_USER_NAME}[${TRIGGERED_USER_ID}]\"" 249 | - "&& echo \" Channel[id]: ${TRIGGERED_CHANNEL_NAME}[${TRIGGERED_CHANNEL_ID}] <@${TRIGGERED_USER_ID}>\"" 250 | parameters: [] 251 | log: true 252 | ephemeral: false 253 | response: code 254 | permissions: 255 | - all 256 | - name: Date or Uptime 257 | description: Show the current time or uptime 258 | envvars: [] 259 | dependencies: [] 260 | help: "!bashbot time" 261 | trigger: time 262 | location: ./ 263 | command: 264 | - "echo \"Date/time: $(${command})\"" 265 | parameters: 266 | - name: command 267 | allowed: 268 | - date 269 | - uptime 270 | log: true 271 | ephemeral: false 272 | response: code 273 | permissions: 274 | - all 275 | - name: Get Bashbot Version 276 | description: Displays the currently running version of Bashbot 277 | envvars: [] 278 | dependencies: [] 279 | help: "!bashbot version" 280 | trigger: version 281 | location: ./vendor/bashbot/examples/version 282 | command: 283 | - "./get-version.sh" 284 | parameters: [] 285 | log: true 286 | ephemeral: false 287 | response: code 288 | permissions: 289 | - all 290 | - name: Ping/Pong 291 | description: Return pong on pings 292 | help: "!bashbot ping" 293 | trigger: ping 294 | location: ./ 295 | command: 296 | - echo "pong" 297 | parameters: [] 298 | log: true 299 | ephemeral: false 300 | response: text 301 | permissions: 302 | - all 303 | - name: List asdf dependencies 304 | description: Return a list of the dependencies installed from asdf 305 | envvars: [] 306 | dependencies: [] 307 | help: "!bashbot asdf" 308 | trigger: asdf 309 | location: ./ 310 | command: 311 | - ". /usr/asdf/asdf.sh && asdf list" 312 | parameters: [] 313 | log: true 314 | ephemeral: false 315 | response: code 316 | permissions: 317 | - all 318 | - name: Kubectl cluster-info 319 | description: Return cluster-info by kubectl command 320 | envvars: [] 321 | dependencies: [] 322 | help: "!bashbot k-info" 323 | trigger: k-info 324 | location: ./ 325 | command: 326 | - ". /usr/asdf/asdf.sh && kubectl cluster-info" 327 | parameters: [] 328 | log: true 329 | ephemeral: false 330 | response: code 331 | permissions: 332 | - all 333 | - name: kubectl get bashbot pod 334 | description: Get the bashbot pod using kubectl 335 | envvars: 336 | - BOTNAME 337 | - NAMESPACE 338 | dependencies: [] 339 | help: "!bashbot k-get-pod" 340 | trigger: k-get-pod 341 | location: ./ 342 | command: 343 | - ". /usr/asdf/asdf.sh" 344 | - "&& kubectl --namespace ${NAMESPACE} get pods | grep -E \"NAME|${BOTNAME}\"" 345 | parameters: [] 346 | log: true 347 | ephemeral: false 348 | response: code 349 | permissions: 350 | - all 351 | - name: kubectl -n [namespace] delete pod [podname] 352 | description: Delete a pod 353 | envvars: [] 354 | dependencies: [] 355 | help: "!bashbot k-delete-pod [namespace] [podname]" 356 | trigger: k-delete-pod 357 | location: ./ 358 | command: 359 | - ". /usr/asdf/asdf.sh" 360 | - "&& kubectl -n ${namespace} delete pod ${podname} --ignore-not-found=true" 361 | parameters: 362 | - name: namespace 363 | allowed: [] 364 | description: List all of the namespaces in the cluster 365 | source: 366 | - ". /usr/asdf/asdf.sh" 367 | - "&& kubectl get namespaces | grep -v NAME | awk \"{print $1}\"" 368 | - name: podname 369 | allowed: [] 370 | description: List all of the pods in the cluster by name 371 | source: 372 | - ". /usr/asdf/asdf.sh" 373 | - "&& kubectl get pods -A | grep -v NAME | awk \"{print $2}\"" 374 | log: true 375 | ephemeral: false 376 | response: code 377 | permissions: 378 | - all 379 | - name: kubectl -n [namespace] describe pod [podname] 380 | description: Return pod information 381 | envvars: [] 382 | dependencies: [] 383 | help: "!bashbot k-describe-pod [namespace] [podname]" 384 | trigger: k-describe-pod 385 | location: ./ 386 | command: 387 | - ". /usr/asdf/asdf.sh" 388 | - "&& kubectl -n ${namespace} describe pod ${podname}" 389 | parameters: 390 | - name: namespace 391 | allowed: [] 392 | description: List all of the namespaces in the cluster 393 | source: 394 | - ". /usr/asdf/asdf.sh" 395 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 396 | - name: podname 397 | allowed: [] 398 | description: List all of the pods in the cluster by name 399 | source: 400 | - ". /usr/asdf/asdf.sh" 401 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 402 | log: true 403 | ephemeral: false 404 | response: file 405 | permissions: 406 | - all 407 | - name: kubectl -n [namespace] logs --tail 10 [podname] 408 | description: Return last 10 lines of pod logs 409 | envvars: [] 410 | dependencies: [] 411 | help: "!bashbot k-pod-logs [namespace] [podname]" 412 | trigger: k-pod-logs 413 | location: ./ 414 | command: 415 | - ". /usr/asdf/asdf.sh" 416 | - "&& kubectl -n ${namespace} logs --tail 10 ${podname}" 417 | parameters: 418 | - name: namespace 419 | allowed: [] 420 | description: List all of the namespaces in the cluster 421 | source: 422 | - ". /usr/asdf/asdf.sh" 423 | - "&& kubectl get namespaces | grep -v NAME | awk \"{print $1}\"" 424 | - name: podname 425 | allowed: [] 426 | description: List all of the pods in the cluster by name 427 | source: 428 | - ". /usr/asdf/asdf.sh" 429 | - "&& kubectl get pods -A | grep -v NAME | awk \"{print $2}\"" 430 | log: true 431 | ephemeral: false 432 | response: code 433 | permissions: 434 | - all 435 | - name: Get Latest Bashbot Version 436 | description: Returns the latest version of Bashbot via curl 437 | envvars: [] 438 | dependencies: [] 439 | help: "!bashbot latest-release" 440 | trigger: latest-release 441 | location: ./ 442 | command: 443 | - latest_version=$(curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 444 | - "&& echo \"The latest version of : \"" 445 | parameters: [] 446 | log: true 447 | ephemeral: false 448 | response: text 449 | permissions: 450 | - all 451 | - name: Trigger a Github Action 452 | description: Triggers an example Github Action job by repository dispatch 453 | envvars: 454 | - GIT_TOKEN 455 | dependencies: [] 456 | help: "!bashbot trigger-github-action" 457 | trigger: trigger-github-action 458 | location: ./vendor/bashbot/examples/trigger-github-action 459 | command: 460 | - "export REPO_OWNER=mathew-fleisch" 461 | - "&& export REPO_NAME=bashbot" 462 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 463 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 464 | - "&& echo \"Running this example github action: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action.yaml\"" 465 | - "&& ./trigger.sh" 466 | parameters: [] 467 | log: true 468 | ephemeral: false 469 | response: text 470 | permissions: 471 | - GPFMM5MD2 472 | - name: Trigger a Github Action Bashbot Gate 473 | description: Triggers an example Github Action job, gated by bashbot, triggered by repository dispatch 474 | envvars: 475 | - GIT_TOKEN 476 | dependencies: [] 477 | help: "!bashbot trigger-github-action-gate" 478 | trigger: trigger-github-action-gate 479 | location: ./vendor/bashbot/examples/trigger-github-action 480 | command: 481 | - "export REPO_OWNER=mathew-fleisch" 482 | - "&& export REPO_NAME=bashbot" 483 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 484 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 485 | - "&& echo \"Running this example github action, gated by bashbot: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action-gate.yaml\"" 486 | - "&& ./trigger-gate.sh" 487 | parameters: [] 488 | log: true 489 | ephemeral: false 490 | response: text 491 | permissions: 492 | - GPFMM5MD2 493 | dependencies: 494 | - name: Install Dependencies with asdf 495 | install: 496 | - "mkdir -p /usr/asdf; " 497 | - "git clone --depth 1 https://github.com/asdf-vm/asdf.git /usr/asdf --branch v0.8.1; " 498 | - ". /usr/asdf/asdf.sh; " 499 | - "cat /bashbot/.tool-versions " 500 | - "| grep -vE '^#' " 501 | - "| awk '{print $1}' " 502 | - "| xargs -I {} asdf plugin add {}; " 503 | - "asdf install; " 504 | - "echo 'asdf installed and configured!'; " 505 | - "echo 'Source asdf before command to use installed dependencies:'; " 506 | - "echo '. /usr/asdf/asdf.sh'; " 507 | - "asdf list; echo " 508 | - name: "Bashbot Inception: download bashbot source code into vendor directory" 509 | install: 510 | - "rm -rf bashbot || true; " 511 | - "git clone https://github.com/mathew-fleisch/bashbot.git" 512 | 513 | .tool-versions: | 514 | helm 3.10.3 515 | kubectl 1.26.0 516 | kubectx 0.9.4 517 | kustomize 4.5.7 518 | --- 519 | # Source: bashbot/templates/clusterrolebinding.yaml 520 | apiVersion: rbac.authorization.k8s.io/v1 521 | kind: ClusterRoleBinding 522 | metadata: 523 | name: bashbot 524 | namespace: bashbot 525 | roleRef: 526 | apiGroup: rbac.authorization.k8s.io 527 | kind: ClusterRole 528 | name: cluster-admin 529 | subjects: 530 | - kind: ServiceAccount 531 | name: bashbot 532 | namespace: bashbot 533 | --- 534 | # Source: bashbot/templates/deployment.yaml 535 | apiVersion: apps/v1 536 | kind: Deployment 537 | metadata: 538 | labels: 539 | app: bashbot 540 | name: bashbot 541 | namespace: bashbot 542 | spec: 543 | progressDeadlineSeconds: 600 544 | replicas: 1 545 | revisionHistoryLimit: 0 546 | selector: 547 | matchLabels: 548 | app: bashbot 549 | strategy: 550 | type: Recreate 551 | template: 552 | metadata: 553 | creationTimestamp: null 554 | labels: 555 | app: bashbot 556 | spec: 557 | containers: 558 | - env: 559 | - name: LOG_LEVEL 560 | value: info 561 | - name: LOG_FORMAT 562 | value: text 563 | - name: BOTNAME 564 | value: bashbot 565 | - name: NAMESPACE 566 | value: bashbot 567 | - name: SLACK_BOT_TOKEN 568 | valueFrom: 569 | secretKeyRef: 570 | name: bashbot-env 571 | key: SLACK_BOT_TOKEN 572 | - name: SLACK_APP_TOKEN 573 | valueFrom: 574 | secretKeyRef: 575 | name: bashbot-env 576 | key: SLACK_APP_TOKEN 577 | - name: GIT_TOKEN 578 | valueFrom: 579 | secretKeyRef: 580 | name: bashbot-env 581 | key: GIT_TOKEN 582 | optional: true 583 | - name: AIRQUALITY_API_KEY 584 | valueFrom: 585 | secretKeyRef: 586 | name: bashbot-env 587 | key: AIRQUALITY_API_KEY 588 | optional: true 589 | image: "mathewfleisch/bashbot:v2.0.5" 590 | imagePullPolicy: 591 | name: bashbot 592 | command: 593 | - "/bashbot/entrypoint.sh" 594 | args: 595 | resources: {} 596 | terminationMessagePath: /dev/termination-log 597 | terminationMessagePolicy: File 598 | workingDir: /bashbot 599 | volumeMounts: 600 | - name: bashbot-configmap 601 | mountPath: /bashbot/config.yaml 602 | subPath: config.yaml 603 | - name: bashbot-configmap 604 | mountPath: /bashbot/.tool-versions 605 | subPath: .tool-versions 606 | volumes: 607 | - name: bashbot-configmap 608 | configMap: 609 | name: bashbot-configmap 610 | dnsPolicy: ClusterFirst 611 | restartPolicy: Always 612 | 613 | serviceAccount: bashbot 614 | serviceAccountName: bashbot 615 | automountServiceAccountToken: true 616 | schedulerName: default-scheduler 617 | securityContext: {} 618 | terminationGracePeriodSeconds: 0 619 | -------------------------------------------------------------------------------- /examples/kubernetes/kubernetes-manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: bashbot/templates/configmap.yaml 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: bashbot-configmap 7 | namespace: bashbot 8 | data: 9 | config.yaml: | 10 | admins: 11 | - trigger: "!bashbot" 12 | appName: BashBot 13 | userIds: 14 | - UP3BBQX34 15 | privateChannelId: GPFMM5MD2 16 | logChannelId: CPJ1NFPL7 17 | messages: 18 | - active: true 19 | name: welcome 20 | text: Witness the power of %s 21 | - active: true 22 | name: processing_command 23 | text: ":robot_face: Processing command..." 24 | - active: true 25 | name: processing_raw_command 26 | text: ":smiling_imp: Processing raw command..." 27 | - active: true 28 | name: command_not_found 29 | text: ":thinking_face: Command not found..." 30 | - active: true 31 | name: incorrect_parameters 32 | text: ":face_with_monocle: Incorrect number of parameters" 33 | - active: true 34 | name: invalid_parameter 35 | text: ":face_with_monocle: Invalid parameter value: %s" 36 | - active: true 37 | name: ephemeral 38 | text: ":shushing_face: Message only shown to user who triggered it." 39 | - active: true 40 | name: unauthorized 41 | text: |- 42 | :skull_and_crossbones: You are not authorized to use this command in this channel. 43 | Allowed in: [%s] 44 | - active: true 45 | name: missingenvvar 46 | text: ":skull_and_crossbones: This command requires this environment variable to be set: [%s]" 47 | - active: true 48 | name: missingdependency 49 | text: ":skull_and_crossbones: This command requires: [%s]" 50 | tools: 51 | - name: BashBot Help 52 | description: Show this message 53 | envvars: 54 | - BASHBOT_CONFIG_FILEPATH 55 | dependencies: 56 | - yq 57 | help: "!bashbot help" 58 | trigger: help 59 | location: ./ 60 | command: 61 | - echo "BashBot is a tool for infrastructure/devops teams to automate tasks triggered by slash-command-like declarative configuration" && 62 | - echo '```' && 63 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 64 | - "| sed -e 's/\\\"//g'" 65 | - "| sed -e 's/:/ -/g' &&" 66 | - echo '```' 67 | parameters: [] 68 | log: true 69 | ephemeral: false 70 | response: text 71 | permissions: 72 | - all 73 | - name: Air Quality Index 74 | description: Get air quality index by zip code 75 | envvars: 76 | - AIRQUALITY_API_KEY 77 | dependencies: 78 | - curl 79 | - jq 80 | help: "!bashbot aqi [zip-code]" 81 | trigger: aqi 82 | location: ./vendor/bashbot/examples/aqi 83 | command: 84 | - "./aqi.sh ${zip}" 85 | parameters: 86 | - name: zip 87 | allowed: [] 88 | description: any zip code 89 | match: (^\d{5}$)|(^\d{9}$)|(^\d{5}-\d{4}$) 90 | log: true 91 | ephemeral: false 92 | response: text 93 | permissions: 94 | - all 95 | - name: Get User/Channel Info 96 | description: Get information about the user and channel command is being run from 97 | envvars: [] 98 | dependencies: [] 99 | help: "!bashbot info" 100 | trigger: info 101 | location: ./vendor/bashbot/examples/info 102 | command: 103 | - "./get-info.sh" 104 | parameters: [] 105 | log: true 106 | ephemeral: false 107 | response: code 108 | permissions: 109 | - all 110 | - name: Display Configuration 111 | description: dump configuration as yaml blob 112 | envvars: [] 113 | dependencies: 114 | - yq 115 | help: "!bashbot dump" 116 | trigger: dump 117 | location: ./ 118 | command: 119 | - yq e '.' ${BASHBOT_CONFIG_FILEPATH} 120 | parameters: [] 121 | log: true 122 | ephemeral: false 123 | response: file 124 | permissions: 125 | - all 126 | - name: List Available Bashbot Commands 127 | description: List all of the possible commands stored in bashbot 128 | envvars: 129 | - BASHBOT_CONFIG_FILEPATH 130 | dependencies: 131 | - yq 132 | help: "!bashbot list" 133 | trigger: list 134 | location: ./ 135 | command: 136 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 137 | parameters: [] 138 | log: true 139 | ephemeral: false 140 | response: code 141 | permissions: 142 | - all 143 | - name: List Example Commands 144 | description: List commands from bashbot example commands 145 | envvars: [] 146 | dependencies: [] 147 | help: "!bashbot list-examples" 148 | trigger: list-examples 149 | location: ./vendor/bashbot/examples 150 | command: 151 | - find . -name "*.json" 152 | - "| xargs -I {} bash -c" 153 | - "'export example=$(basename {} .json)" 154 | - "&& printf \"%21s - %s\" \"$example\" \"https://github.com/mathew-fleisch/bashbot/tree/main/examples/$example\"" 155 | - "&& echo'" 156 | - "| sort -k 2" 157 | parameters: [] 158 | log: true 159 | ephemeral: false 160 | response: code 161 | permissions: 162 | - all 163 | - name: Regular expression example 164 | description: With great power, comes great responsibility 165 | envvars: [] 166 | dependencies: [] 167 | help: "!bashbot regex $([command])" 168 | trigger: regex 169 | location: ./ 170 | command: 171 | - ". /usr/asdf/asdf.sh && ${command} || true" 172 | parameters: 173 | - name: command 174 | allowed: [] 175 | description: This should allow any text to be used as input 176 | match: .* 177 | log: false 178 | ephemeral: false 179 | response: code 180 | permissions: 181 | - GPFMM5MD2 182 | - name: Curl Example 183 | description: Pass a valid url to curl 184 | envvars: [] 185 | dependencies: 186 | - curl 187 | help: "!bashbot curl [url]" 188 | trigger: curl 189 | location: ./ 190 | command: 191 | - curl -s ${url} | jq -r ".body" | tr "\n" " " 192 | parameters: 193 | - name: url 194 | allowed: [] 195 | description: A valid url (Expecting json with key body) 196 | match: ^(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 197 | log: true 198 | ephemeral: false 199 | response: code 200 | permissions: 201 | - all 202 | - name: Describe Bashbot [command] 203 | description: Show the yaml object for a specific command 204 | envvars: 205 | - BASHBOT_CONFIG_FILEPATH 206 | dependencies: 207 | - yq 208 | help: "!bashbot describe [command]" 209 | trigger: describe 210 | location: ./ 211 | command: 212 | - yq e '.tools[] | select(.trigger=="${command}")' ${BASHBOT_CONFIG_FILEPATH} 213 | parameters: 214 | - name: command 215 | allowed: [] 216 | description: a command to describe ('bashbot list') 217 | source: 218 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 219 | log: true 220 | ephemeral: false 221 | response: code 222 | permissions: 223 | - all 224 | - name: Environment variable test 225 | description: Show an example of how to only run a command if specific env vars are defined. 226 | envvars: 227 | - TRIGGERED_USER_NAME 228 | - TRIGGERED_USER_ID 229 | - TRIGGERED_CHANNEL_NAME 230 | - TRIGGERED_CHANNEL_ID 231 | dependencies: [] 232 | help: "!bashbot env-var-test" 233 | trigger: env-var-test 234 | location: ./ 235 | command: 236 | - "echo \"Username[id]: ${TRIGGERED_USER_NAME}[${TRIGGERED_USER_ID}]\"" 237 | - "&& echo \" Channel[id]: ${TRIGGERED_CHANNEL_NAME}[${TRIGGERED_CHANNEL_ID}] <@${TRIGGERED_USER_ID}>\"" 238 | parameters: [] 239 | log: true 240 | ephemeral: false 241 | response: code 242 | permissions: 243 | - all 244 | - name: Date or Uptime 245 | description: Show the current time or uptime 246 | envvars: [] 247 | dependencies: [] 248 | help: "!bashbot time" 249 | trigger: time 250 | location: ./ 251 | command: 252 | - "echo \"Date/time: $(${command})\"" 253 | parameters: 254 | - name: command 255 | allowed: 256 | - date 257 | - uptime 258 | log: true 259 | ephemeral: false 260 | response: code 261 | permissions: 262 | - all 263 | - name: Get Bashbot Version 264 | description: Displays the currently running version of Bashbot 265 | envvars: [] 266 | dependencies: [] 267 | help: "!bashbot version" 268 | trigger: version 269 | location: ./vendor/bashbot/examples/version 270 | command: 271 | - "./get-version.sh" 272 | parameters: [] 273 | log: true 274 | ephemeral: false 275 | response: code 276 | permissions: 277 | - all 278 | - name: Ping/Pong 279 | description: Return pong on pings 280 | help: "!bashbot ping" 281 | trigger: ping 282 | location: ./ 283 | command: 284 | - echo "pong" 285 | parameters: [] 286 | log: true 287 | ephemeral: false 288 | response: text 289 | permissions: 290 | - all 291 | - name: List asdf dependencies 292 | description: Return a list of the dependencies installed from asdf 293 | envvars: [] 294 | dependencies: [] 295 | help: "!bashbot asdf" 296 | trigger: asdf 297 | location: ./ 298 | command: 299 | - ". /usr/asdf/asdf.sh && asdf list" 300 | parameters: [] 301 | log: true 302 | ephemeral: false 303 | response: code 304 | permissions: 305 | - all 306 | - name: Kubectl cluster-info 307 | description: Return cluster-info by kubectl command 308 | envvars: [] 309 | dependencies: [] 310 | help: "!bashbot k-info" 311 | trigger: k-info 312 | location: ./ 313 | command: 314 | - ". /usr/asdf/asdf.sh && kubectl cluster-info" 315 | parameters: [] 316 | log: true 317 | ephemeral: false 318 | response: code 319 | permissions: 320 | - all 321 | - name: kubectl get bashbot pod 322 | description: Get the bashbot pod using kubectl 323 | envvars: 324 | - BOTNAME 325 | - NAMESPACE 326 | dependencies: [] 327 | help: "!bashbot k-get-pod" 328 | trigger: k-get-pod 329 | location: ./ 330 | command: 331 | - ". /usr/asdf/asdf.sh" 332 | - "&& kubectl --namespace ${NAMESPACE} get pods | grep -E \"NAME|${BOTNAME}\"" 333 | parameters: [] 334 | log: true 335 | ephemeral: false 336 | response: code 337 | permissions: 338 | - all 339 | - name: kubectl -n [namespace] delete pod [podname] 340 | description: Delete a pod 341 | envvars: [] 342 | dependencies: [] 343 | help: "!bashbot k-delete-pod [namespace] [podname]" 344 | trigger: k-delete-pod 345 | location: ./ 346 | command: 347 | - ". /usr/asdf/asdf.sh" 348 | - "&& kubectl -n ${namespace} delete pod ${podname} --ignore-not-found=true" 349 | parameters: 350 | - name: namespace 351 | allowed: [] 352 | description: List all of the namespaces in the cluster 353 | source: 354 | - ". /usr/asdf/asdf.sh" 355 | - "&& kubectl get namespaces | grep -v NAME | awk \"{print $1}\"" 356 | - name: podname 357 | allowed: [] 358 | description: List all of the pods in the cluster by name 359 | source: 360 | - ". /usr/asdf/asdf.sh" 361 | - "&& kubectl get pods -A | grep -v NAME | awk \"{print $2}\"" 362 | log: true 363 | ephemeral: false 364 | response: code 365 | permissions: 366 | - all 367 | - name: kubectl -n [namespace] describe pod [podname] 368 | description: Return pod information 369 | envvars: [] 370 | dependencies: [] 371 | help: "!bashbot k-describe-pod [namespace] [podname]" 372 | trigger: k-describe-pod 373 | location: ./ 374 | command: 375 | - ". /usr/asdf/asdf.sh" 376 | - "&& kubectl -n ${namespace} describe pod ${podname}" 377 | parameters: 378 | - name: namespace 379 | allowed: [] 380 | description: List all of the namespaces in the cluster 381 | source: 382 | - ". /usr/asdf/asdf.sh" 383 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 384 | - name: podname 385 | allowed: [] 386 | description: List all of the pods in the cluster by name 387 | source: 388 | - ". /usr/asdf/asdf.sh" 389 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 390 | log: true 391 | ephemeral: false 392 | response: file 393 | permissions: 394 | - all 395 | - name: kubectl -n [namespace] logs --tail 10 [podname] 396 | description: Return last 10 lines of pod logs 397 | envvars: [] 398 | dependencies: [] 399 | help: "!bashbot k-pod-logs [namespace] [podname]" 400 | trigger: k-pod-logs 401 | location: ./ 402 | command: 403 | - ". /usr/asdf/asdf.sh" 404 | - "&& kubectl -n ${namespace} logs --tail 10 ${podname}" 405 | parameters: 406 | - name: namespace 407 | allowed: [] 408 | description: List all of the namespaces in the cluster 409 | source: 410 | - ". /usr/asdf/asdf.sh" 411 | - "&& kubectl get namespaces | grep -v NAME | awk \"{print $1}\"" 412 | - name: podname 413 | allowed: [] 414 | description: List all of the pods in the cluster by name 415 | source: 416 | - ". /usr/asdf/asdf.sh" 417 | - "&& kubectl get pods -A | grep -v NAME | awk \"{print $2}\"" 418 | log: true 419 | ephemeral: false 420 | response: code 421 | permissions: 422 | - all 423 | - name: Get Latest Bashbot Version 424 | description: Returns the latest version of Bashbot via curl 425 | envvars: [] 426 | dependencies: [] 427 | help: "!bashbot latest-release" 428 | trigger: latest-release 429 | location: ./ 430 | command: 431 | - latest_version=$(curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 432 | - "&& echo \"The latest version of : \"" 433 | parameters: [] 434 | log: true 435 | ephemeral: false 436 | response: text 437 | permissions: 438 | - all 439 | - name: Trigger a Github Action 440 | description: Triggers an example Github Action job by repository dispatch 441 | envvars: 442 | - GIT_TOKEN 443 | dependencies: [] 444 | help: "!bashbot trigger-github-action" 445 | trigger: trigger-github-action 446 | location: ./vendor/bashbot/examples/trigger-github-action 447 | command: 448 | - "export REPO_OWNER=mathew-fleisch" 449 | - "&& export REPO_NAME=bashbot" 450 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 451 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 452 | - "&& echo \"Running this example github action: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action.yaml\"" 453 | - "&& ./trigger.sh" 454 | parameters: [] 455 | log: true 456 | ephemeral: false 457 | response: text 458 | permissions: 459 | - GPFMM5MD2 460 | - name: Trigger a Github Action Bashbot Gate 461 | description: Triggers an example Github Action job, gated by bashbot, triggered by repository dispatch 462 | envvars: 463 | - GIT_TOKEN 464 | dependencies: [] 465 | help: "!bashbot trigger-github-action-gate" 466 | trigger: trigger-github-action-gate 467 | location: ./vendor/bashbot/examples/trigger-github-action 468 | command: 469 | - "export REPO_OWNER=mathew-fleisch" 470 | - "&& export REPO_NAME=bashbot" 471 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 472 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 473 | - "&& echo \"Running this example github action, gated by bashbot: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action-gate.yaml\"" 474 | - "&& ./trigger-gate.sh" 475 | parameters: [] 476 | log: true 477 | ephemeral: false 478 | response: text 479 | permissions: 480 | - GPFMM5MD2 481 | dependencies: 482 | - name: Install Dependencies with asdf 483 | install: 484 | - "mkdir -p /usr/asdf; " 485 | - "git clone --depth 1 https://github.com/asdf-vm/asdf.git /usr/asdf --branch v0.8.1; " 486 | - ". /usr/asdf/asdf.sh; " 487 | - "cat /bashbot/.tool-versions " 488 | - "| grep -vE '^#' " 489 | - "| awk '{print $1}' " 490 | - "| xargs -I {} asdf plugin add {}; " 491 | - "asdf install; " 492 | - "echo 'asdf installed and configured!'; " 493 | - "echo 'Source asdf before command to use installed dependencies:'; " 494 | - "echo '. /usr/asdf/asdf.sh'; " 495 | - "asdf list; echo " 496 | - name: "Bashbot Inception: download bashbot source code into vendor directory" 497 | install: 498 | - "rm -rf bashbot || true; " 499 | - "git clone https://github.com/mathew-fleisch/bashbot.git" 500 | 501 | .tool-versions: | 502 | helm 3.10.3 503 | kubectl 1.26.0 504 | kubectx 0.9.4 505 | kustomize 4.5.7 506 | --- 507 | # Source: bashbot/templates/deployment.yaml 508 | apiVersion: apps/v1 509 | kind: Deployment 510 | metadata: 511 | labels: 512 | app: bashbot 513 | name: bashbot 514 | namespace: bashbot 515 | spec: 516 | progressDeadlineSeconds: 600 517 | replicas: 1 518 | revisionHistoryLimit: 0 519 | selector: 520 | matchLabels: 521 | app: bashbot 522 | strategy: 523 | type: Recreate 524 | template: 525 | metadata: 526 | creationTimestamp: null 527 | labels: 528 | app: bashbot 529 | spec: 530 | containers: 531 | - env: 532 | - name: LOG_LEVEL 533 | value: info 534 | - name: LOG_FORMAT 535 | value: text 536 | - name: BOTNAME 537 | value: bashbot 538 | - name: NAMESPACE 539 | value: bashbot 540 | - name: SLACK_BOT_TOKEN 541 | valueFrom: 542 | secretKeyRef: 543 | name: bashbot-env 544 | key: SLACK_BOT_TOKEN 545 | - name: SLACK_APP_TOKEN 546 | valueFrom: 547 | secretKeyRef: 548 | name: bashbot-env 549 | key: SLACK_APP_TOKEN 550 | - name: GIT_TOKEN 551 | valueFrom: 552 | secretKeyRef: 553 | name: bashbot-env 554 | key: GIT_TOKEN 555 | optional: true 556 | - name: AIRQUALITY_API_KEY 557 | valueFrom: 558 | secretKeyRef: 559 | name: bashbot-env 560 | key: AIRQUALITY_API_KEY 561 | optional: true 562 | image: "mathewfleisch/bashbot:v2.0.5" 563 | imagePullPolicy: 564 | name: bashbot 565 | command: 566 | - "/bashbot/entrypoint.sh" 567 | args: 568 | resources: {} 569 | terminationMessagePath: /dev/termination-log 570 | terminationMessagePolicy: File 571 | workingDir: /bashbot 572 | volumeMounts: 573 | - name: bashbot-configmap 574 | mountPath: /bashbot/config.yaml 575 | subPath: config.yaml 576 | - name: bashbot-configmap 577 | mountPath: /bashbot/.tool-versions 578 | subPath: .tool-versions 579 | volumes: 580 | - name: bashbot-configmap 581 | configMap: 582 | name: bashbot-configmap 583 | dnsPolicy: ClusterFirst 584 | restartPolicy: Always 585 | schedulerName: default-scheduler 586 | securityContext: {} 587 | terminationGracePeriodSeconds: 0 588 | -------------------------------------------------------------------------------- /examples/kubernetes/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "Kubernetes test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | main() { 16 | local ns=${1:-bashbot} 17 | local dn=${2:-bashbot} 18 | # Retry loop (20/$i with 3 second delay between loops) 19 | for i in {30..1}; do 20 | # Get the expected number of replicas for this deployment 21 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 22 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 23 | [ -z "$expectedReplicas" ] && expectedReplicas=1 24 | # Get the number of replicas that are ready for this deployment 25 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 26 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 27 | [ -z "$readyReplicas" ] && readyReplicas=0 28 | 29 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 30 | test $readyReplicas -eq $expectedReplicas \ 31 | && test 1 -eq 1 32 | if [ $? -eq 0 ]; then 33 | # echo "Bashbot deployment confirmed!" 34 | # kubectl --namespace ${ns} get deployments 35 | found_pod=0 36 | for j in {3..1}; do 37 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 38 | # Send `!bashbot k-get-pod` via bashbot binary within bashbot pod 39 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 40 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot k-get-pod"' 41 | sleep 5 42 | last_log_line=$(kubectl -n ${ns} logs $bashbot_pod | grep $bashbot_pod) 43 | # Tail the last line of the bashbot pod's log looking 44 | # for the bashbot pod name 45 | if [[ $last_log_line =~ $bashbot_pod ]]; then 46 | echo "kubectl commands successful!" 47 | echo "pod found in logs: $bashbot_pod" 48 | found_pod=1 49 | 50 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 51 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: Bashbot can run kubectl commands. pod found in logs: \`'$bashbot_pod'\`"' 52 | exit 0 53 | fi 54 | echo "kubectl test failed. $j more attempts..." 55 | sleep 5 56 | done 57 | [ $found_pod -eq 1 ] && exit 0 || exit 1 58 | fi 59 | 60 | # Since the deployment was not ready, try again $i more times 61 | echo "Deployment not found or not ready. $i more attempts..." 62 | sleep 5 63 | done 64 | 65 | # The retry loop has exited without finding a stable deployment 66 | echo "Bashbot deployment failed :(" 67 | # Display some debug information and fail test 68 | kubectl --namespace ${ns} get deployments 69 | kubectl --namespace ${ns} get pods -o wide 70 | 71 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 72 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: kubectl test failed!"' 73 | exit 1 74 | } 75 | 76 | # Usage: ./test.sh [namespace] [deployment] 77 | namespace=${1:-bashbot} 78 | deploymentName=${2:-bashbot} 79 | 80 | main $namespace $deploymentName 81 | -------------------------------------------------------------------------------- /examples/latest-release/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Latest Release 2 | 3 | In this example, a curl is used to determine the latest version of Bashbot through the github release api 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `!bashbot latest-release` in a slack channel where Bashbot is also a member. This command requires no external script and simply returns a formatted message including links to Bashbot's source code and latest release. This command requires [curl](https://curl.se/) to be installed on the host machine. 10 | 11 | ```yaml 12 | name: Get Latest Bashbot Version 13 | description: Returns the latest version of Bashbot via curl 14 | envvars: [] 15 | dependencies: 16 | - curl 17 | help: "!bashbot latest-release" 18 | trigger: latest-release 19 | location: /bashbot/ 20 | command: 21 | - latest_version=$(curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 22 | - "&& echo \"The latest version of : \"" 23 | parameters: [] 24 | log: true 25 | ephemeral: false 26 | response: text 27 | permissions: 28 | - all 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/latest-release/latest-release.yaml: -------------------------------------------------------------------------------- 1 | name: Get Latest Bashbot Version 2 | description: Returns the latest version of Bashbot via curl 3 | envvars: [] 4 | dependencies: 5 | - curl 6 | help: "!bashbot latest-release" 7 | trigger: latest-release 8 | location: /bashbot/ 9 | command: 10 | - latest_version=$(curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 11 | - "&& echo \"The latest version of : \"" 12 | parameters: [] 13 | log: true 14 | ephemeral: false 15 | response: text 16 | permissions: 17 | - all -------------------------------------------------------------------------------- /examples/list-examples/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - List Example Commands 2 | 3 | In this example, all of the yaml filenames are aggregated and sorted as a list. 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `!bashbot list-examples` in a slack channel where Bashbot is also a member. There is no external script for this command, takes no arugments/parameters, and expects Bashbot's examples directory to exist. This command takes no arguments. 10 | 11 | ```yaml 12 | name: List Example Commands 13 | description: List commands from bashbot example commands 14 | envvars: [] 15 | dependencies: [] 16 | help: "!bashbot list-examples" 17 | trigger: list-examples 18 | location: /bashbot/vendor/bashbot/examples 19 | command: 20 | - find . -name "*.yaml" 21 | - "| xargs -I {} bash -c" 22 | - "'export example=$(basename {} .yaml)" 23 | - "&& echo \"- \"" 24 | - "&& echo'" 25 | - "| sort -k 2" 26 | parameters: [] 27 | log: true 28 | ephemeral: false 29 | response: code 30 | permissions: 31 | - all 32 | ``` 33 | -------------------------------------------------------------------------------- /examples/list-examples/list-examples.yaml: -------------------------------------------------------------------------------- 1 | name: List Example Commands 2 | description: List commands from bashbot example commands 3 | envvars: [] 4 | dependencies: [] 5 | help: "!bashbot list-examples" 6 | trigger: list-examples 7 | location: /bashbot/vendor/bashbot/examples 8 | command: 9 | - find . -name "*.yaml" 10 | - "| xargs -I {} bash -c" 11 | - "'export example=$(basename {} .yaml)" 12 | - "&& echo \"- \"" 13 | - "&& echo'" 14 | - "| sort -k 2" 15 | parameters: [] 16 | log: true 17 | ephemeral: false 18 | response: code 19 | permissions: 20 | - all -------------------------------------------------------------------------------- /examples/list/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - List Commands 2 | 3 | In this example, the configuration yaml file for Bashbot is parsed via yq to display the trigger and name of each command in the file. 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `!bashbot list` in a slack channel where Bashbot is also a member. There is no external script for this command, takes no arugments/parameters, and expects yq to already be installed, and the environment variable `BASHBOT_CONFIG_FILEPATH` to be pointing at the running configuration, on the host machine. 10 | 11 | ```yaml 12 | name: List Available Bashbot Commands 13 | description: List all of the possible commands stored in bashbot 14 | envvars: 15 | - BASHBOT_CONFIG_FILEPATH 16 | dependencies: 17 | - yq 18 | help: "!bashbot list" 19 | trigger: list 20 | location: /bashbot/ 21 | command: 22 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 23 | parameters: [] 24 | log: true 25 | ephemeral: false 26 | response: code 27 | permissions: 28 | - all 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/list/list.yaml: -------------------------------------------------------------------------------- 1 | name: List Available Bashbot Commands 2 | description: List all of the possible commands stored in bashbot 3 | envvars: 4 | - BASHBOT_CONFIG_FILEPATH 5 | dependencies: 6 | - yq 7 | help: "!bashbot list" 8 | trigger: list 9 | location: /bashbot/ 10 | command: 11 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 12 | parameters: [] 13 | log: true 14 | ephemeral: false 15 | response: code 16 | permissions: 17 | - all -------------------------------------------------------------------------------- /examples/ping/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Ping/Pong 2 | 3 | In this example, a 'pong' response is returned to the user. This command is useful to verify Bashbot is running and as a template for hard-coded call-and-response message. For instance, common links, dates and/or milestones can be inserted in place of `echo \"pong\"` to share with your team. 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `bashbot ping` in a slack channel where Bashbot is also a member. This command requires no external script and simply returns a message 'pong' back to the user. 10 | 11 | ```yaml 12 | name: Ping/Pong 13 | description: Return pong on pings 14 | envvars: [] 15 | dependencies: [] 16 | help: "!bashbot ping" 17 | trigger: ping 18 | location: /bashbot/ 19 | command: 20 | - echo "pong" 21 | parameters: [] 22 | log: true 23 | ephemeral: false 24 | response: text 25 | permissions: 26 | - all 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/ping/ping.yaml: -------------------------------------------------------------------------------- 1 | name: Ping/Pong 2 | description: Return pong on pings 3 | envvars: [] 4 | dependencies: [] 5 | help: "!bashbot ping" 6 | trigger: ping 7 | location: /bashbot/ 8 | command: 9 | - echo "pong" 10 | parameters: [] 11 | log: true 12 | ephemeral: false 13 | response: text 14 | permissions: 15 | - all -------------------------------------------------------------------------------- /examples/ping/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "Ping/Pong test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | main() { 16 | local ns=${1:-bashbot} 17 | local dn=${2:-bashbot} 18 | # Retry loop (20/$i with 3 second delay between loops) 19 | for i in {30..1}; do 20 | # Get the expected number of replicas for this deployment 21 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 22 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 23 | [ -z "$expectedReplicas" ] && expectedReplicas=1 24 | # Get the number of replicas that are ready for this deployment 25 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 26 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 27 | [ -z "$readyReplicas" ] && readyReplicas=0 28 | 29 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 30 | test $readyReplicas -eq $expectedReplicas \ 31 | && test 1 -eq 1 32 | if [ $? -eq 0 ]; then 33 | # echo "Bashbot deployment confirmed!" 34 | # kubectl --namespace ${ns} get deployments 35 | found_pong=0 36 | for j in {3..1}; do 37 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 38 | # Send `!bashbot ping` via bashbot binary within bashbot pod 39 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 40 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot ping"' 41 | sleep 5 42 | last_log_line=$(kubectl -n ${ns} logs --tail 10 $bashbot_pod) 43 | # Tail the last line of the bashbot pod's log looking 44 | # for the string 'Bashbot is now connected to slack' 45 | if [[ $last_log_line =~ "pong" ]]; then 46 | echo "Ping/pong test successful!" 47 | found_pong=1 48 | 49 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 50 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: Ping/pong test successful!"' 51 | exit 0 52 | fi 53 | echo "Bashbot ping/pong test failed. $j more attempts..." 54 | sleep 5 55 | done 56 | # Don't require ping/pong tests to pass for the whole test to pass 57 | [ $found_pong -eq 1 ] && exit 0 || exit 1 58 | fi 59 | 60 | # Since the deployment was not ready, try again $i more times 61 | echo "Deployment not found or not ready. $i more attempts..." 62 | sleep 5 63 | done 64 | 65 | # The retry loop has exited without finding a stable deployment 66 | echo "Bashbot deployment failed :(" 67 | # Display some debug information and fail test 68 | kubectl --namespace ${ns} get deployments 69 | kubectl --namespace ${ns} get pods -o wide 70 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 71 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: ping/pong test failed!"' 72 | exit 1 73 | } 74 | 75 | # Usage: ./test.sh [namespace] [deployment] 76 | namespace=${1:-bashbot} 77 | deploymentName=${2:-bashbot} 78 | 79 | main $namespace $deploymentName 80 | -------------------------------------------------------------------------------- /examples/regex/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Regex Command 2 | 3 | In this example, a url is validated with a regular expression and curl is used to return the output as a message. Afterwards, the following (terrifying) examples, the user can pass any string directly to bash, and the stdout/stderr is returned as a message. 4 | 5 | 6 | 7 | ## Bashbot Configuration 8 | 9 | This command is triggered by sending `bashbot curl [url]` in a slack channel where Bashbot is also a member. There is no external script for this command, and expects curl to already be installed. 10 | 11 | ```yaml 12 | name: Curl Example 13 | description: Pass a valid url to curl 14 | envvars: [] 15 | dependencies: 16 | - curl 17 | help: "!bashbot curl [url]" 18 | trigger: curl 19 | location: /bashbot/ 20 | command: 21 | - curl -s ${url} | jq -r ".body" | tr "\n" " " 22 | parameters: 23 | - name: url 24 | allowed: [] 25 | description: A valid url (Expecting json with key body) 26 | match: ^(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 27 | log: true 28 | ephemeral: false 29 | response: code 30 | permissions: 31 | - all 32 | ``` 33 | 34 | To match telephone numbers: `` 35 | 36 | Note: Don't do this... 37 | 38 | ```yaml 39 | name: Regular expression example 40 | description: With great power, comes great responsibility 41 | envvars: [] 42 | dependencies: [] 43 | help: "!bashbot regex $([command])" 44 | trigger: regex 45 | location: ./ 46 | command: 47 | - ". /usr/asdf/asdf.sh && ${command} || true" 48 | parameters: 49 | - name: command 50 | allowed: [] 51 | description: This should allow any text to be used as input 52 | match: .* 53 | log: false 54 | ephemeral: false 55 | response: code 56 | permissions: 57 | - PRIVATE-CHANNEL-ID 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/regex/regex.yaml: -------------------------------------------------------------------------------- 1 | name: Curl Example 2 | description: Pass a valid url to curl 3 | envvars: [] 4 | dependencies: 5 | - curl 6 | help: "!bashbot curl [url]" 7 | trigger: curl 8 | location: /bashbot/ 9 | command: 10 | - curl -s ${url} | jq -r ".body" | tr "\n" " " 11 | parameters: 12 | - name: url 13 | allowed: [] 14 | description: A valid url (Expecting json with key body) 15 | match: ^(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 16 | log: true 17 | ephemeral: false 18 | response: code 19 | permissions: 20 | - all -------------------------------------------------------------------------------- /examples/regex/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086,SC2181 3 | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | # set -x # debug 8 | 9 | cleanup() { 10 | echo "Regex test complete!" 11 | } 12 | 13 | trap cleanup EXIT 14 | TESTING_CHANNEL=${TESTING_CHANNEL:-C034FNXS3FA} 15 | ADMIN_CHANNEL=${ADMIN_CHANNEL:-GPFMM5MD2} 16 | 17 | main() { 18 | local ns=${1:-bashbot} 19 | local dn=${2:-bashbot} 20 | # Retry loop (20/$i with 3 second delay between loops) 21 | for i in {30..1}; do 22 | # Get the expected number of replicas for this deployment 23 | expectedReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.replicas}' get deployment ${dn}) 24 | # If the '.status.replicas' value is empty/not-set, set the default number of replicas to '1' 25 | [ -z "$expectedReplicas" ] && expectedReplicas=1 26 | # Get the number of replicas that are ready for this deployment 27 | readyReplicas=$(kubectl --namespace ${ns} -o jsonpath='{.status.readyReplicas}' get deployment ${dn}) 28 | # If the .status.readyReplicas value is empty/not-set, set the default number of "ready" replicas to '0' 29 | [ -z "$readyReplicas" ] && readyReplicas=0 30 | 31 | # Test that the number of "ready" replicas match the number of expected replicas for this deployment 32 | test $readyReplicas -eq $expectedReplicas \ 33 | && test 1 -eq 1 34 | if [ $? -eq 0 ]; then 35 | # echo "Bashbot deployment confirmed!" 36 | # kubectl --namespace ${ns} get deployments 37 | found_regex=0 38 | for j in {3..1}; do 39 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 40 | # Send `!bashbot curl https://jsonplaceholder.typicode.com/posts/1 | jq -r ‘.body’`\ 41 | # via bashbot binary within bashbot pod and expect a body value that contains 'consequuntur' in the response 42 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 43 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot curl https://jsonplaceholder.typicode.com/posts/1"' 44 | sleep 5 45 | last_log_line=$(kubectl -n ${ns} logs --tail 10 $bashbot_pod | grep -v "bashbot-log") 46 | # Tail the last line of the bashbot pod's log looking 47 | # for the string 'Bashbot is now connected to slack' 48 | if [[ $last_log_line =~ "consequuntur" ]]; then 49 | echo "regex test successful! JQ parsed curl of https://jsonplaceholder.typicode.com/posts/1 api, returned expected json object containing string (consequuntur)" 50 | found_regex=1 51 | 52 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 53 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: regex test successful!\n> JQ parsed curl of https://jsonplaceholder.typicode.com/posts/1 api, returned expected json object containing string (consequuntur)"' 54 | break 55 | fi 56 | echo "Bashbot regex test failed. $j more attempts..." 57 | sleep 5 58 | done 59 | 60 | 61 | found_admin=0 62 | for j in {3..1}; do 63 | bashbot_pod=$(kubectl -n ${ns} get pods -o jsonpath='{.items[0].metadata.name}') 64 | # Expect first call to fail in wrong channel 65 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 66 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "> Expect protected test fails, and retry in private channel..."' 67 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 68 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg "!bashbot regex env | grep BASHBOT_CONFIG_FILEPATH | cut -d= -f2"' 69 | # One cannot interpolate existing environment variables, but instead grep them from the env command 70 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 71 | 'bashbot send-message --channel '${ADMIN_CHANNEL}' --msg "!bashbot regex env | grep BASHBOT_CONFIG_FILEPATH | cut -d= -f2"' 72 | sleep 5 73 | last_log_line=$(kubectl -n ${ns} logs --tail 10 $bashbot_pod | grep -v "bashbot-log") 74 | # Tail the last line of the bashbot pod's log looking 75 | # for the string 'Bashbot is now connected to slack' 76 | if [[ $last_log_line =~ config\.yaml ]]; then 77 | echo "Private regex test successful! The environment variables include a config.yaml value for BASHBOT_CONFIG_FILEPATH" 78 | found_admin=1 79 | 80 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 81 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":large_green_circle: Private regex test successful!\n> The environment variables include a config.yaml value for BASHBOT_CONFIG_FILEPATH"' 82 | break 83 | fi 84 | echo "Bashbot private regex test failed. $j more attempts..." 85 | sleep 5 86 | done 87 | 88 | 89 | # Don't require regex tests to pass for the whole test to pass 90 | if [ $found_regex -eq 1 ] && [ $found_admin -eq 1 ]; then 91 | exit 0 92 | else 93 | exit 1 94 | fi 95 | fi 96 | 97 | # Since the deployment was not ready, try again $i more times 98 | echo "Deployment not found or not ready. $i more attempts..." 99 | sleep 5 100 | done 101 | 102 | # The retry loop has exited without finding a stable deployment 103 | echo "Bashbot deployment failed :(" 104 | # Display some debug information and fail test 105 | kubectl --namespace ${ns} get deployments 106 | kubectl --namespace ${ns} get pods -o wide 107 | 108 | kubectl --namespace ${ns} exec $bashbot_pod -- bash -c \ 109 | 'bashbot send-message --channel '${TESTING_CHANNEL}' --msg ":red_circle: regex test failed!"' 110 | exit 1 111 | } 112 | 113 | # Usage: ./test.sh [namespace] [deployment] 114 | namespace=${1:-bashbot} 115 | deploymentName=${2:-bashbot} 116 | 117 | main $namespace $deploymentName 118 | -------------------------------------------------------------------------------- /examples/rip-mp3/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Rip-Mp3 (from youtube) Command 2 | 3 | In this example, a youtube url is passed to the rip-mp3 command with the [youtube-dl tool](https://youtube-dl.org/) installed on the bashbot host. An audio file is stripped from the youtube video, and returned as a message with the `bashbot send-file` command. A full example can be found at [rip-mp3.yaml](rip-mp3.yaml) 4 | 5 | ```text 6 | !bashbot rip-mp3 https://www.youtube.com/watch?v=dQw4w9WgXcQ 7 | ``` 8 | 9 | 10 | 11 | ## Bashbot Configuration 12 | 13 | This command depends on [ffmpeg](https://ffmpeg.org/), which also depends on python3. By default, Bashbot container use a non-root user at runtime, and cannot install additional tools with apk, and in this example, these dependencies are installed using the [latest-root](https://hub.docker.com/r/mathewfleisch/bashbot/tags?page=1&ordering=last_updated&name=latest-root) base-image, from the `dependencies` object (though it would be better to bake these steps into your base image). 14 | 15 | ```yaml 16 | dependencies: 17 | - name: "Install youtube-dl tool" 18 | install: 19 | - "apk add python3 py3-pip ffmpeg &&" 20 | - "ln -s /usr/bin/python3 /usr/local/bin/python &&" 21 | - "wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl &&" 22 | - "chmod +x /usr/local/bin/youtube-dl" 23 | ``` 24 | 25 | In the values.yaml of the bashbot helm chart, the runtime user can be overridden to root by using the desired tag with `-root` appended (i.e. `latest-root` or `v2.0.5-root`) 26 | 27 | ```yaml 28 | image: 29 | repository: mathewfleisch/bashbot 30 | tag: latest-root 31 | ``` 32 | 33 | The command configuration for rip-mp3 defines the dependencies that must be present on the host, and one parameter that uses a regular expression to match a valid youtube url. That url is passed to the youtube-dl tool, and an mp3 file is then passed back to the channel it was triggered from using the bashbot cli tool. 34 | 35 | ```yaml 36 | name: Rip mp3 from Youtube 37 | description: Use the youtube-dl tool to download the audio file from a youtube videop 38 | envvars: [] 39 | dependencies: 40 | - python3 41 | - ffmpeg 42 | - youtube-dl 43 | help: "!bashbot rip-mp3 [youtube-url]" 44 | trigger: rip-mp3 45 | location: ./ 46 | command: 47 | - "youtube-dl -q -o 'bashbot-youtube-rip-${TRIGGERED_AT}.%(ext)s' -x --audio-format mp3 ${url} &&" 48 | - "bashbot send-file --channel ${TRIGGERED_CHANNEL_ID} --file ${PWD}/bashbot-youtube-rip-${TRIGGERED_AT}.mp3" 49 | parameters: 50 | - name: url 51 | allowed: [] 52 | description: A valid url (Expecting youtube) 53 | match: ^(https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 54 | log: true 55 | ephemeral: false 56 | response: text 57 | permissions: 58 | - all 59 | ``` 60 | -------------------------------------------------------------------------------- /examples/rip-mp3/rip-mp3.yaml: -------------------------------------------------------------------------------- 1 | botname: bashbot 2 | namespace: bashbot 3 | log_level: info 4 | log_format: text 5 | 6 | image: 7 | repository: mathewfleisch/bashbot 8 | tag: latest-root 9 | pullPolicy: IfNotPresent 10 | command: 11 | - /bashbot/entrypoint.sh 12 | serviceAccount: 13 | create: false 14 | 15 | config.yaml: | 16 | admins: 17 | - trigger: "!bashbot" 18 | appName: Bashbot 19 | userIds: 20 | - UUC6G0TT8 21 | privateChannelId: C023GSDGJEP 22 | logChannelId: C017PPHT4HY 23 | messages: 24 | - active: true 25 | name: welcome 26 | text: Witness the power of %s 27 | - active: true 28 | name: processing_command 29 | text: ":robot_face: Processing command..." 30 | - active: true 31 | name: processing_raw_command 32 | text: ":smiling_imp: Processing raw command..." 33 | - active: true 34 | name: command_not_found 35 | text: ":thinking_face: Command not found..." 36 | - active: true 37 | name: incorrect_parameters 38 | text: ":face_with_monocle: Incorrect number of parameters" 39 | - active: true 40 | name: invalid_parameter 41 | text: ":face_with_monocle: Invalid parameter value: %s" 42 | - active: true 43 | name: ephemeral 44 | text: ":shushing_face: Message only shown to user who triggered it." 45 | - active: true 46 | name: unauthorized 47 | text: |- 48 | :skull_and_crossbones: You are not authorized to use this command in this channel. 49 | Allowed in: [%s] 50 | - active: true 51 | name: missingenvvar 52 | text: ":skull_and_crossbones: This command requires this environment variable to be set: [%s]" 53 | - active: true 54 | name: missingdependency 55 | text: ":skull_and_crossbones: This command requires: [%s]" 56 | tools: 57 | - name: Rip mp3 from Youtube 58 | description: Use the youtube-dl tool to download the audio file from a youtube videop 59 | envvars: [] 60 | dependencies: 61 | - python3 62 | - ffmpeg 63 | - youtube-dl 64 | help: "!bashbot rip-mp3 [youtube-url]" 65 | trigger: rip-mp3 66 | location: ./ 67 | command: 68 | - "youtube-dl -q -o 'bashbot-youtube-rip-${TRIGGERED_AT}.%(ext)s' -x --audio-format mp3 ${url} &&" 69 | - "bashbot send-file --channel ${TRIGGERED_CHANNEL_ID} --file ${PWD}/bashbot-youtube-rip-${TRIGGERED_AT}.mp3" 70 | parameters: 71 | - name: url 72 | allowed: [] 73 | description: A valid url (Expecting youtube) 74 | match: ^(https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 75 | log: true 76 | ephemeral: false 77 | response: text 78 | permissions: 79 | - all 80 | dependencies: 81 | - name: "Install youtube-dl tool" 82 | install: 83 | - "apk add python3 py3-pip ffmpeg &&" 84 | - "ln -s /usr/bin/python3 /usr/local/bin/python &&" 85 | - "wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl &&" 86 | - "chmod +x /usr/local/bin/youtube-dl" 87 | -------------------------------------------------------------------------------- /examples/trigger-github-action/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Trigger Github Action 2 | 3 | In this example, a curl is executed via bash script and triggers a github action. The github action downloads the latest version of the bashbot binary to reply back to the caller upon completion of the job. This command has four parts: Bashbot configuration, curl trigger, github-action yaml, github-action script. 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `!bashbot trigger-github-action` in a slack channel where Bashbot is also a member. The script is expected to exist before execution at the relative path `./examples/trigger-github-action` and requires the following environment variables to be set: `BASHBOT_CONFIG_FILEPATH SLACK_BOT_TOKEN SLACK_APP_TOKEN SLACK_CHANNEL SLACK_USERID REPO_OWNER REPO_NAME GITHUB_TOKEN GITHUB_RUN_ID`. The permissions array restricts this command to only run in a specific slack channel. 10 | 11 | ```yaml 12 | name: Trigger a Github Action 13 | description: Triggers an example Github Action job by repository dispatch 14 | envvars: 15 | - GIT_TOKEN 16 | dependencies: [] 17 | help: "!bashbot trigger-github-action" 18 | trigger: trigger-github-action 19 | location: /bashbot/vendor/bashbot/examples/trigger-github-action 20 | command: 21 | - "export REPO_OWNER=mathew-fleisch" 22 | - "&& export REPO_NAME=bashbot" 23 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 24 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 25 | - "&& echo \"Running this example github action: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action.yaml\"" 26 | - "&& ./trigger.sh" 27 | parameters: [] 28 | log: true 29 | ephemeral: false 30 | response: text 31 | permissions: 32 | - GPFMM5MD2 33 | ``` 34 | 35 | ## Bashbot scripts 36 | 37 | There are two scripts associated with this Bashbot command: [trigger.sh](trigger.sh) and [github-action.sh](github-action.sh). The [trigger.sh](trigger.sh) script sends off a POST request via curl to the repository's dispatch function to trigger a github action. The [github-action](../.github/workflows/example-bashbot-github-action.yaml) uses the [github-action.sh](github-action.sh) script to simulate a long running job, and return back status to slack via Bashbot binary. 38 | 39 | ### trigger.sh and trigger-gate.sh 40 | 41 | The scripts [examples/trigger-github-action/trigger.sh](examples/trigger-github-action/trigger.sh) and [examples/trigger-github-action/trigger-gate.sh](examples/trigger-github-action/trigger-gate.sh), use curl to trigger one of two github actions. [.github/workflows/example-bashbot-github-action.yaml](.github/workflows/example-bashbot-github-action.yaml) shows how bashbot can be used to trigger a github action, and also used to craft a custom pass/failure message back to slack. [.github/workflows/example-bashbot-github-action-gate.yaml](.github/workflows/example-bashbot-github-action-gate.yaml) shows how bashbot can be used to trigger a github action, and also used as a gating mechanism to pass/fail the job manually from slack. 42 | -------------------------------------------------------------------------------- /examples/trigger-github-action/example-gate-config.yaml: -------------------------------------------------------------------------------- 1 | 2 | admins: 3 | - trigger: "!example-gate" 4 | appName: Bashbot Example Gate 5 | userIds: 6 | - "UP3BBQX34" 7 | privateChannelId: "GPFMM5MD2" 8 | logChannelId: "CPJ1NFPL7" 9 | messages: 10 | - active: true 11 | name: processing_command 12 | text: ":robot_face: Processing command..." 13 | - active: true 14 | name: command_not_found 15 | text: ":thinking_face: Command not found..." 16 | - active: true 17 | name: incorrect_parameters 18 | text: ":face_with_monocle: Incorrect number of parameters" 19 | - active: true 20 | name: invalid_parameter 21 | text: ":face_with_monocle: Invalid parameter value: %s" 22 | - active: true 23 | name: ephemeral 24 | text: ":shushing_face: Message only shown to user who triggered it." 25 | - active: true 26 | name: unauthorized 27 | text: |- 28 | :skull_and_crossbones: You are not authorized to use this command in this channel. 29 | Allowed in: [%s] 30 | - active: true 31 | name: missingenvvar 32 | text: ":skull_and_crossbones: This command requires this environment variable to be set: [%s]" 33 | - active: true 34 | name: missingdependency 35 | text: ":skull_and_crossbones: This command requires: [%s]" 36 | tools: 37 | - name: BashBot Gate Help 38 | description: Show this message 39 | envvars: 40 | - BASHBOT_CONFIG_FILEPATH 41 | dependencies: 42 | - yq 43 | help: "!example-gate help" 44 | trigger: help 45 | location: "./" 46 | command: 47 | - "echo 'BashBot gate running in a Github Action will auto-fail in 5min after startup' &&" 48 | - "echo '```!example-gate exit 0 - pass job successfully' &&" 49 | - "echo '!example-gate exit 1 - fail job' &&" 50 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 51 | - "| sed -e 's/\\\"//g'" 52 | - "| sed -e 's/:/ -/g' &&" 53 | - "echo '```'" 54 | parameters: [] 55 | log: true 56 | ephemeral: false 57 | response: text 58 | permissions: 59 | - GPFMM5MD2 60 | - name: Info about host 61 | description: Show debugging information about host 62 | envvars: [] 63 | dependencies: [] 64 | help: "!example-gate info" 65 | trigger: info 66 | location: "./examples/info" 67 | command: 68 | - "./get-info.sh" 69 | parameters: [] 70 | log: true 71 | ephemeral: false 72 | response: file 73 | permissions: 74 | - GPFMM5MD2 75 | - name: Debug state of host 76 | description: With great power, comes great responsibility 77 | envvars: [] 78 | dependencies: [] 79 | help: "!example-gate debug $([command])" 80 | trigger: debug 81 | location: ./ 82 | command: 83 | - "${command} || true" 84 | parameters: 85 | - name: command 86 | allowed: [] 87 | description: This should allow any text to be used as input 88 | match: .* 89 | log: false 90 | ephemeral: false 91 | response: code 92 | permissions: 93 | - GPFMM5MD2 94 | dependencies: [] -------------------------------------------------------------------------------- /examples/trigger-github-action/github-action.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | github_base="${github_base:-api.github.com}" 4 | expected_variables="BASHBOT_CONFIG_FILEPATH SLACK_BOT_TOKEN SLACK_CHANNEL SLACK_USERID REPO_OWNER REPO_NAME GITHUB_TOKEN GITHUB_RUN_ID" 5 | for expect in $expected_variables; do 6 | if [[ -z "${!expect}" ]]; then 7 | echo "Missing environment variable $expect" 8 | echo "Expected: $expected_variables" 9 | exit 1 10 | fi 11 | done 12 | headers="-sH \"Accept: application/vnd.github.everest-preview+json\" -H \"Authorization: token ${GITHUB_TOKEN}\"" 13 | LATEST_VERSION=$(curl ${headers} https://${github_base}/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest | grep tag_name | cut -d '"' -f 4) 14 | arch=amd64 15 | [ $(uname -m) == "aarch64" ] && arch=arm64 16 | os=$(uname | tr '[:upper:]' '[:lower:]') 17 | wget -q https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${LATEST_VERSION}/bashbot-${os}-${arch} -O bashbot 18 | chmod +x bashbot 19 | ./bashbot send-message \ 20 | --channel ${SLACK_CHANNEL} \ 21 | --msg "<@${SLACK_USERID}> Bashbot triggered this job" -------------------------------------------------------------------------------- /examples/trigger-github-action/trigger-gate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086 3 | 4 | github_base="${github_base:-api.github.com}" 5 | expected_variables="BASHBOT_CONFIG_FILEPATH SLACK_CHANNEL SLACK_USERID REPO_OWNER REPO_NAME GIT_TOKEN" 6 | for expect in $expected_variables; do 7 | if [[ -z "${!expect}" ]]; then 8 | echo "Missing environment variable $expect" 9 | echo "Expected: $expected_variables" 10 | exit 0 11 | fi 12 | done 13 | 14 | curl -s \ 15 | -X POST \ 16 | -H "Accept: application/vnd.github.everest-preview+json" \ 17 | -H "Authorization: token ${GIT_TOKEN}" \ 18 | --data '{"event_type":"trigger-github-action-gate","client_payload": {"channel":"'${SLACK_CHANNEL}'", "user_id": "'${SLACK_USERID}'"}}' \ 19 | "https://${github_base}/repos/${REPO_OWNER}/${REPO_NAME}/dispatches" 20 | -------------------------------------------------------------------------------- /examples/trigger-github-action/trigger-github-action.yaml: -------------------------------------------------------------------------------- 1 | name: Trigger a Github Action 2 | description: Triggers an example Github Action job by repository dispatch 3 | envvars: 4 | - GIT_TOKEN 5 | dependencies: [] 6 | help: "!bashbot trigger-github-action" 7 | trigger: trigger-github-action 8 | location: /bashbot/vendor/bashbot/examples/trigger-github-action 9 | command: 10 | - "export REPO_OWNER=mathew-fleisch" 11 | - "&& export REPO_NAME=bashbot" 12 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 13 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 14 | - "&& echo \"Running this example github action: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action.yaml\"" 15 | - "&& ./trigger.sh" 16 | parameters: [] 17 | log: true 18 | ephemeral: false 19 | response: text 20 | permissions: 21 | - GPFMM5MD2 -------------------------------------------------------------------------------- /examples/trigger-github-action/trigger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2086 3 | 4 | github_base="${github_base:-api.github.com}" 5 | expected_variables="BASHBOT_CONFIG_FILEPATH SLACK_CHANNEL SLACK_USERID REPO_OWNER REPO_NAME GIT_TOKEN" 6 | for expect in $expected_variables; do 7 | if [[ -z "${!expect}" ]]; then 8 | echo "Missing environment variable $expect" 9 | echo "Expected: $expected_variables" 10 | exit 0 11 | fi 12 | done 13 | 14 | curl -s \ 15 | -X POST \ 16 | -H "Accept: application/vnd.github.everest-preview+json" \ 17 | -H "Authorization: token ${GIT_TOKEN}" \ 18 | --data '{"event_type":"trigger-github-action","client_payload": {"channel":"'${SLACK_CHANNEL}'", "user_id": "'${SLACK_USERID}'"}}' \ 19 | "https://${github_base}/repos/${REPO_OWNER}/${REPO_NAME}/dispatches" 20 | -------------------------------------------------------------------------------- /examples/version/README.md: -------------------------------------------------------------------------------- 1 | # Bashbot Example - Get Version 2 | 3 | In this example, a bash script is executed, attempting three methods of returning the currently running version of Bashbot. 4 | 5 | 6 | 7 | ## Bashbot configuration 8 | 9 | This command is triggered by sending `bashbot version` in a slack channel where Bashbot is also a member. The script is expected to exist before execution at the relative path `./examples/version/get-version.sh` and requires no additional input to execute. It takes no arguments/parameters and returns `stdout` as a slack message, in the channel it was executed from. 10 | 11 | ```yaml 12 | name: Get Bashbot Version 13 | description: Displays the currently running version of Bashbot 14 | envvars: [] 15 | dependencies: [] 16 | help: "!bashbot version" 17 | trigger: version 18 | location: /bashbot/vendor/bashbot/examples/version 19 | command: 20 | - "./get-version.sh" 21 | parameters: [] 22 | log: true 23 | ephemeral: false 24 | response: code 25 | permissions: 26 | - all 27 | ``` 28 | 29 | ## Bashbot script 30 | 31 | The [get-version.sh](get-version.sh) first checks to see if Bashbot is already available via `$PATH` and prints that version, if possible. Next, if the binary is being run from the `./bin` directory, this script will grab the correct os/architecture from `uname` and attempt to print that version. If both of those methods fail, a [Makefile](../../Makefile) target (`go-version`) is executed, provided the Makefile and go source exists. If all of those methods are unsuccessful, an error message is returned to slack that the version could not be determined. 32 | 33 | ***Note: `exit 0` is used in success/failure states to ensure error messages are returned to slack. If `exit 1` is used for error states, a generic error message is returned to slack and `stderr` is suppressed.*** 34 | 35 | ```bash 36 | # First check if bashbot is installed and pull that version 37 | if command -v bashbot > /dev/null; then 38 | command -v bashbot 39 | bashbot version 40 | exit 0 41 | fi 42 | 43 | # Next check if ./bin/bashbot-${os}-${arch} exists and pull that version 44 | arch=amd64 45 | [ "$(uname -m)" == "aarch64" ] && arch=arm64 46 | os=$(uname | tr '[:upper:]' '[:lower:]') 47 | if [ -f "./bin/bashbot-${os}-${arch}" ]; then 48 | echo "./bin/bashbot-${os}-${arch} version" 49 | ./bin/bashbot-${os}-${arch} version 50 | exit 0 51 | fi 52 | 53 | # Finally, check if bashbot go source exists and pull that version 54 | go_filename=cmd/bashbot/bashbot.go 55 | if [[ -f "./${go_filename}" ]] && [[ -f "./Makefile" ]]; then 56 | make go-setup 57 | make go-version 58 | exit 0 59 | fi 60 | 61 | echo "Could not determine the current version of bashbot" 62 | exit 0 63 | ``` 64 | -------------------------------------------------------------------------------- /examples/version/get-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # First check if bashbot is installed and pull that version 4 | if command -v bashbot > /dev/null; then 5 | command -v bashbot 6 | bashbot version 7 | exit 0 8 | fi 9 | 10 | # Next check if ./bin/bashbot-${os}-${arch} exists and pull that version 11 | arch=amd64 12 | [ "$(uname -m)" == "aarch64" ] && arch=arm64 13 | os=$(uname | tr '[:upper:]' '[:lower:]') 14 | if [ -f "./bin/bashbot-${os}-${arch}" ]; then 15 | echo "./bin/bashbot-${os}-${arch} version" 16 | ./bin/bashbot-${os}-${arch} version 17 | exit 0 18 | fi 19 | 20 | # Finally, check if bashbot go source exists and pull that version 21 | go_filename=cmd/bashbot/bashbot.go 22 | if [[ -f "./${go_filename}" ]] && [[ -f "./Makefile" ]]; then 23 | make go-setup 24 | make go-version 25 | exit 0 26 | fi 27 | 28 | echo "Could not determine the current version of bashbot" 29 | exit 0 -------------------------------------------------------------------------------- /examples/version/version.yaml: -------------------------------------------------------------------------------- 1 | name: Get Bashbot Version 2 | description: Displays the currently running version of Bashbot 3 | envvars: [] 4 | dependencies: [] 5 | help: "!bashbot version" 6 | trigger: version 7 | location: /bashbot/vendor/bashbot/examples/version 8 | command: 9 | - "./get-version.sh" 10 | parameters: [] 11 | log: true 12 | ephemeral: false 13 | response: code 14 | permissions: 15 | - all -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mathew-fleisch/bashbot 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/mitchellh/go-homedir v1.1.0 7 | github.com/sirupsen/logrus v1.9.3 8 | github.com/slack-go/slack v0.14.0 9 | github.com/spf13/cobra v1.7.0 10 | github.com/spf13/viper v1.19.0 11 | gopkg.in/yaml.v2 v2.4.0 12 | ) 13 | 14 | require ( 15 | github.com/fsnotify/fsnotify v1.7.0 // indirect 16 | github.com/gorilla/websocket v1.5.0 // indirect 17 | github.com/hashicorp/hcl v1.0.0 // indirect 18 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 19 | github.com/magiconair/properties v1.8.7 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 22 | github.com/sagikazarmark/locafero v0.4.0 // indirect 23 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 24 | github.com/sourcegraph/conc v0.3.0 // indirect 25 | github.com/spf13/afero v1.11.0 // indirect 26 | github.com/spf13/cast v1.6.0 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | github.com/subosito/gotenv v1.6.0 // indirect 29 | go.uber.org/atomic v1.9.0 // indirect 30 | go.uber.org/multierr v1.9.0 // indirect 31 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 32 | golang.org/x/sys v0.18.0 // indirect 33 | golang.org/x/text v0.14.0 // indirect 34 | gopkg.in/ini.v1 v1.67.0 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 6 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 7 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 8 | github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= 9 | github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 10 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 11 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 12 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 13 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 14 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 15 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 16 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 17 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 18 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 19 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 20 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 21 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 22 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 23 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 24 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 25 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 26 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 27 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 28 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 31 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 32 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 33 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 34 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 35 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 36 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 37 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 38 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 39 | github.com/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k= 40 | github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= 41 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 42 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 43 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 44 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 45 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 46 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 47 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 48 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 49 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 50 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 51 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 52 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 53 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 54 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 55 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 56 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 57 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 58 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 59 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 60 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 62 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 63 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 64 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 65 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 66 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 67 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 68 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 69 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 70 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 71 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 72 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 73 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 75 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 76 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 77 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 80 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 81 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 82 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 83 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 84 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | -------------------------------------------------------------------------------- /internal/slack/models.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | type Admin struct { 4 | Trigger string `yaml:"trigger"` 5 | AppName string `yaml:"appName"` 6 | UserIds []string `yaml:"userIds"` 7 | PrivateChannelId string `yaml:"privateChannelId"` 8 | LogChannelId string `yaml:"logChannelId"` 9 | } 10 | 11 | type Message struct { 12 | Active bool `yaml:"active"` 13 | Name string `yaml:"name"` 14 | Text string `yaml:"text"` 15 | } 16 | 17 | type Tool struct { 18 | Name string `yaml:"name"` 19 | Description string `yaml:"description"` 20 | Help string `yaml:"help"` 21 | Trigger string `yaml:"trigger"` 22 | Location string `yaml:"location"` 23 | Command []string `yaml:"command"` 24 | Permissions []string `yaml:"permissions"` 25 | Log bool `yaml:"log"` 26 | Ephemeral bool `yaml:"ephemeral"` 27 | Response string `yaml:"response"` 28 | Parameters []Parameter `yaml:"parameters"` 29 | Envvars []string `yaml:"envvars"` 30 | Dependencies []string `yaml:"dependencies"` 31 | } 32 | 33 | type Parameter struct { 34 | Name string `yaml:"name"` 35 | Allowed []string `yaml:"allowed"` 36 | Description string `yaml:"description,omitempty"` 37 | Source []string `yaml:"source,omitempty"` 38 | Match string `yaml:"match,omitempty"` 39 | } 40 | 41 | type Dependency struct { 42 | Name string `yaml:"name"` 43 | Install []string `yaml:"install"` 44 | } 45 | 46 | type Channel struct { 47 | Id string `yaml:"id"` 48 | Created int `yaml:"created"` 49 | IsOpen bool `yaml:"is_open"` 50 | IsGroup bool `yaml:"is_group"` 51 | IsShared bool `yaml:"is_shared"` 52 | IsIm bool `yaml:"is_im"` 53 | IsExtShared bool `yaml:"is_ext_shared"` 54 | IsOrgShared bool `yaml:"is_org_shared"` 55 | IsPendingExtShared bool `yaml:"is_pending_ext_shared"` 56 | IsPrivate bool `yaml:"is_private"` 57 | IsMpim bool `yaml:"is_mpim"` 58 | Unlinked int `yaml:"unlinked"` 59 | NameNormalized string `yaml:"name_normalized"` 60 | NumMembers int `yaml:"num_members"` 61 | Priority int `yaml:"priority"` 62 | User string `yaml:"user"` 63 | Name string `yaml:"name"` 64 | Creator string `yaml:"creator"` 65 | IsArchived bool `yaml:"is_archived"` 66 | Members string `yaml:"members"` 67 | Topic Topic `yaml:"topic"` 68 | Purpose Topic `yaml:"purpose"` 69 | IsChannel bool `yaml:"is_channel"` 70 | IsGeneral bool `yaml:"is_general"` 71 | IsMember bool `yaml:"is_member"` 72 | Local string `yaml:"locale"` 73 | } 74 | 75 | type Topic struct { 76 | Value string `yaml:"value"` 77 | Creator string `yaml:"creator"` 78 | LastSet int `yaml:"last_set"` 79 | } 80 | 81 | type Config struct { 82 | Admins []Admin `yaml:"admins"` 83 | Messages []Message `yaml:"messages"` 84 | Tools []Tool `yaml:"tools"` 85 | Dependencies []Dependency `yaml:"dependencies"` 86 | } 87 | 88 | func (c *Config) GetTool(trigger string) Tool { 89 | for i := range c.Tools { 90 | if c.Tools[i].Trigger == trigger { 91 | return c.Tools[i] 92 | } 93 | } 94 | return Tool{} 95 | } 96 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/mathew-fleisch/bashbot/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /sample-config.yaml: -------------------------------------------------------------------------------- 1 | admins: 2 | - trigger: "!bashbot" 3 | appName: BashBot 4 | userIds: 5 | - UP3BBQX34 6 | privateChannelId: GPFMM5MD2 7 | logChannelId: CPJ1NFPL7 8 | messages: 9 | - active: true 10 | name: welcome 11 | text: Witness the power of %s 12 | - active: true 13 | name: processing_command 14 | text: ":robot_face: Processing command..." 15 | - active: true 16 | name: processing_raw_command 17 | text: ":smiling_imp: Processing raw command..." 18 | - active: true 19 | name: command_not_found 20 | text: ":thinking_face: Command not found..." 21 | - active: true 22 | name: incorrect_parameters 23 | text: ":face_with_monocle: Incorrect number of parameters" 24 | - active: true 25 | name: invalid_parameter 26 | text: ":face_with_monocle: Invalid parameter value: %s" 27 | - active: true 28 | name: ephemeral 29 | text: ":shushing_face: Message only shown to user who triggered it." 30 | - active: true 31 | name: unauthorized 32 | text: |- 33 | :skull_and_crossbones: You are not authorized to use this command in this channel. 34 | Allowed in: [%s] 35 | - active: true 36 | name: missingenvvar 37 | text: ":skull_and_crossbones: This command requires this environment variable to be set: [%s]" 38 | - active: true 39 | name: missingdependency 40 | text: ":skull_and_crossbones: This command requires: [%s]" 41 | tools: 42 | - name: BashBot Help 43 | description: Show this message 44 | envvars: 45 | - BASHBOT_CONFIG_FILEPATH 46 | dependencies: 47 | - yq 48 | help: "!bashbot help" 49 | trigger: help 50 | location: ./ 51 | command: 52 | - echo "BashBot is a tool for infrastructure/devops teams to automate tasks triggered by slash-command-like declarative configuration" && 53 | - echo '```' && 54 | - "yq e '.tools[] | {.help: .description}' \"${BASHBOT_CONFIG_FILEPATH}\"" 55 | - "| sed -e 's/\\\"//g'" 56 | - "| sed -e 's/:/ -/g' &&" 57 | - echo '```' 58 | parameters: [] 59 | log: true 60 | ephemeral: false 61 | response: text 62 | permissions: 63 | - all 64 | - name: Air Quality Index 65 | description: Get air quality index by zip code 66 | envvars: 67 | - AIRQUALITY_API_KEY 68 | dependencies: 69 | - curl 70 | - jq 71 | help: "!bashbot aqi [zip-code]" 72 | trigger: aqi 73 | location: ./vendor/bashbot/examples/aqi 74 | command: 75 | - "./aqi.sh ${zip}" 76 | parameters: 77 | - name: zip 78 | allowed: [] 79 | description: any zip code 80 | match: (^\d{5}$)|(^\d{9}$)|(^\d{5}-\d{4}$) 81 | log: true 82 | ephemeral: false 83 | response: text 84 | permissions: 85 | - all 86 | - name: Get User/Channel Info 87 | description: Get information about the user and channel command is being run from 88 | envvars: [] 89 | dependencies: [] 90 | help: "!bashbot info" 91 | trigger: info 92 | location: ./vendor/bashbot/examples/info 93 | command: 94 | - "./get-info.sh" 95 | parameters: [] 96 | log: true 97 | ephemeral: false 98 | response: code 99 | permissions: 100 | - all 101 | - name: Display Configuration 102 | description: dump configuration as yaml blob 103 | envvars: [] 104 | dependencies: 105 | - yq 106 | help: "!bashbot dump" 107 | trigger: dump 108 | location: ./ 109 | command: 110 | - yq e '.' ${BASHBOT_CONFIG_FILEPATH} 111 | parameters: [] 112 | log: true 113 | ephemeral: false 114 | response: file 115 | permissions: 116 | - all 117 | - name: List Available Bashbot Commands 118 | description: List all of the possible commands stored in bashbot 119 | envvars: 120 | - BASHBOT_CONFIG_FILEPATH 121 | dependencies: 122 | - yq 123 | help: "!bashbot list" 124 | trigger: list 125 | location: ./ 126 | command: 127 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 128 | parameters: [] 129 | log: true 130 | ephemeral: false 131 | response: code 132 | permissions: 133 | - all 134 | - name: List Example Commands 135 | description: List commands from bashbot example commands 136 | envvars: [] 137 | dependencies: [] 138 | help: "!bashbot list-examples" 139 | trigger: list-examples 140 | location: ./vendor/bashbot/examples 141 | command: 142 | - find . -name "*.yaml" 143 | - "| xargs -I {} bash -c" 144 | - "'export example=$(basename {} .yaml)" 145 | - "&& echo \"- \"" 146 | - "&& echo'" 147 | - "| sort -k 2" 148 | parameters: [] 149 | log: true 150 | ephemeral: false 151 | response: code 152 | permissions: 153 | - all 154 | - name: Regular expression example 155 | description: With great power, comes great responsibility 156 | envvars: [] 157 | dependencies: [] 158 | help: "!bashbot regex $([command])" 159 | trigger: regex 160 | location: ./ 161 | command: 162 | - ". /usr/asdf/asdf.sh && ${command} || true" 163 | parameters: 164 | - name: command 165 | allowed: [] 166 | description: This should allow any text to be used as input 167 | match: .* 168 | log: false 169 | ephemeral: false 170 | response: code 171 | permissions: 172 | - GPFMM5MD2 173 | - name: Curl Example 174 | description: Pass a valid url to curl 175 | envvars: [] 176 | dependencies: 177 | - curl 178 | help: "!bashbot curl [url]" 179 | trigger: curl 180 | location: ./ 181 | command: 182 | - curl -s ${url} | jq -r ".body" | tr "\n" " " 183 | parameters: 184 | - name: url 185 | allowed: [] 186 | description: A valid url (Expecting json with key body) 187 | match: ^(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$ 188 | log: true 189 | ephemeral: false 190 | response: code 191 | permissions: 192 | - all 193 | - name: Describe Bashbot [command] 194 | description: Show the yaml object for a specific command 195 | envvars: 196 | - BASHBOT_CONFIG_FILEPATH 197 | dependencies: 198 | - yq 199 | help: "!bashbot describe [command]" 200 | trigger: describe 201 | location: ./ 202 | command: 203 | - yq e '.tools[] | select(.trigger=="${command}")' ${BASHBOT_CONFIG_FILEPATH} 204 | parameters: 205 | - name: command 206 | allowed: [] 207 | description: a command to describe ('bashbot list') 208 | source: 209 | - yq e '.tools[] | .trigger' ${BASHBOT_CONFIG_FILEPATH} 210 | log: true 211 | ephemeral: false 212 | response: code 213 | permissions: 214 | - all 215 | - name: Environment variable test 216 | description: Show an example of how to only run a command if specific env vars are defined. 217 | envvars: 218 | - TRIGGERED_USER_NAME 219 | - TRIGGERED_USER_ID 220 | - TRIGGERED_CHANNEL_NAME 221 | - TRIGGERED_CHANNEL_ID 222 | dependencies: [] 223 | help: "!bashbot env-var-test" 224 | trigger: env-var-test 225 | location: ./ 226 | command: 227 | - "echo \"Username[id]: ${TRIGGERED_USER_NAME}[${TRIGGERED_USER_ID}]\"" 228 | - "&& echo \" Channel[id]: ${TRIGGERED_CHANNEL_NAME}[${TRIGGERED_CHANNEL_ID}] <@${TRIGGERED_USER_ID}>\"" 229 | parameters: [] 230 | log: true 231 | ephemeral: false 232 | response: code 233 | permissions: 234 | - all 235 | - name: Date or Uptime 236 | description: Show the current time or uptime 237 | envvars: [] 238 | dependencies: [] 239 | help: "!bashbot time" 240 | trigger: time 241 | location: ./ 242 | command: 243 | - "echo \"Date/time: $(${command})\"" 244 | parameters: 245 | - name: command 246 | allowed: 247 | - date 248 | - uptime 249 | log: true 250 | ephemeral: false 251 | response: code 252 | permissions: 253 | - all 254 | - name: Get Bashbot Version 255 | description: Displays the currently running version of Bashbot 256 | envvars: [] 257 | dependencies: [] 258 | help: "!bashbot version" 259 | trigger: version 260 | location: ./vendor/bashbot/examples/version 261 | command: 262 | - "./get-version.sh" 263 | parameters: [] 264 | log: true 265 | ephemeral: false 266 | response: code 267 | permissions: 268 | - all 269 | - name: Ping/Pong 270 | description: Return pong on pings 271 | help: "!bashbot ping" 272 | trigger: ping 273 | location: ./ 274 | command: 275 | - echo "pong" 276 | parameters: [] 277 | log: true 278 | ephemeral: false 279 | response: text 280 | permissions: 281 | - all 282 | - name: List asdf dependencies 283 | description: Return a list of the dependencies installed from asdf 284 | envvars: [] 285 | dependencies: [] 286 | help: "!bashbot asdf" 287 | trigger: asdf 288 | location: ./ 289 | command: 290 | - ". /usr/asdf/asdf.sh && asdf list" 291 | parameters: [] 292 | log: true 293 | ephemeral: false 294 | response: code 295 | permissions: 296 | - all 297 | - name: Kubectl cluster-info 298 | description: Return cluster-info by kubectl command 299 | envvars: [] 300 | dependencies: [] 301 | help: "!bashbot k-info" 302 | trigger: k-info 303 | location: ./ 304 | command: 305 | - ". /usr/asdf/asdf.sh && kubectl cluster-info" 306 | parameters: [] 307 | log: true 308 | ephemeral: false 309 | response: code 310 | permissions: 311 | - all 312 | - name: kubectl get bashbot pod 313 | description: Get the bashbot pod using kubectl 314 | envvars: 315 | - BOTNAME 316 | - NAMESPACE 317 | dependencies: [] 318 | help: "!bashbot k-get-pod" 319 | trigger: k-get-pod 320 | location: ./ 321 | command: 322 | - ". /usr/asdf/asdf.sh" 323 | - "&& kubectl --namespace ${NAMESPACE} get pods | grep -E \"NAME|${BOTNAME}\"" 324 | parameters: [] 325 | log: true 326 | ephemeral: false 327 | response: code 328 | permissions: 329 | - all 330 | - name: kubectl -n [namespace] delete pod [podname] 331 | description: Delete a pod 332 | envvars: [] 333 | dependencies: [] 334 | help: "!bashbot k-delete-pod [namespace] [podname]" 335 | trigger: k-delete-pod 336 | location: ./ 337 | command: 338 | - ". /usr/asdf/asdf.sh" 339 | - "&& kubectl -n ${namespace} delete pod ${podname} --ignore-not-found=true" 340 | parameters: 341 | - name: namespace 342 | allowed: [] 343 | description: List all of the namespaces in the cluster 344 | source: 345 | - ". /usr/asdf/asdf.sh" 346 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 347 | - name: podname 348 | allowed: [] 349 | description: List all of the pods in the cluster by name 350 | source: 351 | - ". /usr/asdf/asdf.sh" 352 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 353 | log: true 354 | ephemeral: false 355 | response: code 356 | permissions: 357 | - all 358 | - name: kubectl -n [namespace] describe pod [podname] 359 | description: Return pod information 360 | envvars: [] 361 | dependencies: [] 362 | help: "!bashbot k-describe-pod [namespace] [podname]" 363 | trigger: k-describe-pod 364 | location: ./ 365 | command: 366 | - ". /usr/asdf/asdf.sh" 367 | - "&& kubectl -n ${namespace} describe pod ${podname}" 368 | parameters: 369 | - name: namespace 370 | allowed: [] 371 | description: List all of the namespaces in the cluster 372 | source: 373 | - ". /usr/asdf/asdf.sh" 374 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 375 | - name: podname 376 | allowed: [] 377 | description: List all of the pods in the cluster by name 378 | source: 379 | - ". /usr/asdf/asdf.sh" 380 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 381 | log: true 382 | ephemeral: false 383 | response: file 384 | permissions: 385 | - all 386 | - name: kubectl -n [namespace] logs --tail 10 [podname] 387 | description: Return last 10 lines of pod logs 388 | envvars: [] 389 | dependencies: [] 390 | help: "!bashbot k-pod-logs [namespace] [podname]" 391 | trigger: k-pod-logs 392 | location: ./ 393 | command: 394 | - ". /usr/asdf/asdf.sh" 395 | - "&& kubectl -n ${namespace} logs --tail 10 ${podname}" 396 | parameters: 397 | - name: namespace 398 | allowed: [] 399 | description: List all of the namespaces in the cluster 400 | source: 401 | - ". /usr/asdf/asdf.sh" 402 | - "&& kubectl get namespaces | grep -v NAME | awk '{print $1}'" 403 | - name: podname 404 | allowed: [] 405 | description: List all of the pods in the cluster by name 406 | source: 407 | - ". /usr/asdf/asdf.sh" 408 | - "&& kubectl get pods -A | grep -v NAME | awk '{print $2}'" 409 | log: true 410 | ephemeral: false 411 | response: code 412 | permissions: 413 | - all 414 | - name: Get Latest Bashbot Version 415 | description: Returns the latest version of Bashbot via curl 416 | envvars: [] 417 | dependencies: [] 418 | help: "!bashbot latest-release" 419 | trigger: latest-release 420 | location: ./ 421 | command: 422 | - latest_version=$(curl -s https://api.github.com/repos/mathew-fleisch/bashbot/releases/latest | grep tag_name | cut -d '"' -f 4) 423 | - "&& echo \"The latest version of : \"" 424 | parameters: [] 425 | log: true 426 | ephemeral: false 427 | response: text 428 | permissions: 429 | - all 430 | - name: Trigger a Github Action 431 | description: Triggers an example Github Action job by repository dispatch 432 | envvars: 433 | - GIT_TOKEN 434 | dependencies: [] 435 | help: "!bashbot trigger-github-action" 436 | trigger: trigger-github-action 437 | location: ./vendor/bashbot/examples/trigger-github-action 438 | command: 439 | - "export REPO_OWNER=mathew-fleisch" 440 | - "&& export REPO_NAME=bashbot" 441 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 442 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 443 | - "&& echo \"Running this example github action: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action.yaml\"" 444 | - "&& ./trigger.sh" 445 | parameters: [] 446 | log: true 447 | ephemeral: false 448 | response: text 449 | permissions: 450 | - GPFMM5MD2 451 | - name: Trigger a Github Action Bashbot Gate 452 | description: Triggers an example Github Action job, gated by bashbot, triggered by repository dispatch 453 | envvars: 454 | - GIT_TOKEN 455 | dependencies: [] 456 | help: "!bashbot trigger-github-action-gate" 457 | trigger: trigger-github-action-gate 458 | location: ./vendor/bashbot/examples/trigger-github-action 459 | command: 460 | - "export REPO_OWNER=mathew-fleisch" 461 | - "&& export REPO_NAME=bashbot" 462 | - "&& export SLACK_CHANNEL=${TRIGGERED_CHANNEL_ID}" 463 | - "&& export SLACK_USERID=${TRIGGERED_USER_ID}" 464 | - "&& echo \"Running this example github action, gated by bashbot: https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/.github/workflows/example-bashbot-github-action-gate.yaml\"" 465 | - "&& ./trigger-gate.sh" 466 | parameters: [] 467 | log: true 468 | ephemeral: false 469 | response: text 470 | permissions: 471 | - GPFMM5MD2 472 | dependencies: 473 | - name: Install Dependencies with asdf 474 | install: 475 | - "mkdir -p /usr/asdf; " 476 | - "git clone --depth 1 https://github.com/asdf-vm/asdf.git /usr/asdf --branch v0.8.1; " 477 | - ". /usr/asdf/asdf.sh; " 478 | - "cat /bashbot/.tool-versions " 479 | - "| grep -vE '^#' " 480 | - "| awk '{print $1}' " 481 | - "| xargs -I {} asdf plugin add {}; " 482 | - "asdf install; " 483 | - "echo 'asdf installed and configured!'; " 484 | - "echo 'Source asdf before command to use installed dependencies:'; " 485 | - "echo '. /usr/asdf/asdf.sh'; " 486 | - "asdf list; echo " 487 | - name: "Bashbot Inception: download bashbot source code into vendor directory" 488 | install: 489 | - "rm -rf bashbot || true; " 490 | - "git clone https://github.com/mathew-fleisch/bashbot.git" 491 | -------------------------------------------------------------------------------- /sample-env-file: -------------------------------------------------------------------------------- 1 | export SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN} 2 | export SLACK_APP_TOKEN=${SLACK_APP_TOKEN} 3 | export GIT_TOKEN=${GIT_TOKEN} 4 | export AIRQUALITY_API_KEY=${AIRQUALITY_API_KEY} 5 | export GIPHY_API_KEY=${GIPHY_API_KEY} 6 | export NASA_API_KEY=${NASA_API_KEY} 7 | --------------------------------------------------------------------------------