Skip to content

Commit b0ebe44

Browse files
Add support for fixing style as a task in another process
- Move body of style fix into new worker module - Add Dockerfile/entrypoint defining worker container - Allow log level to be specified in environment (SPACKBOTLOGLEVEL) - Update gh workflow to build images for webservice and worker - Fix branch name used to run pipelines (remove the 'github/' prefix)
1 parent cebf91b commit b0ebe44

18 files changed

Lines changed: 498 additions & 158 deletions

.env-dummy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
# Go to smee.io to generate a URL here
22
SMEE_URL=https://smee.io/CHANGEME
33

4+
# Optionally customize redis host (local test server defined in docker-compose.yml)
5+
REDIS_HOST=rq-server
6+
7+
# Optionally customize redis port
8+
REDIS_PORT=6379
9+
10+
# Optionally configure time before jobs are killed and marked failed (in seconds, default 180s)
11+
WORKER_JOB_TIMEOUT=21600
12+
13+
# Debug level (one of: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
14+
SPACKBOT_LOG_LEVEL=WARNING
15+
416
# You don't need to change this unless you change the docker-compose volumes
517
GITHUB_PRIVATE_KEY=/app/spackbot/spack-bot-develop.private-key.pem
618

.github/workflows/build-deploy.yaml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,24 @@ on:
1212
jobs:
1313
deploy-test-containers:
1414
runs-on: ubuntu-latest
15-
name: Build Spackbot Container
15+
strategy:
16+
fail-fast: false
17+
# matrix: [tag, path to Dockerfile, label]
18+
matrix:
19+
dockerfile: [[spack-bot, ./Dockerfile, Spackbot],
20+
[spackbot-workers, ./workers/Dockerfile, "Spackbot Workers"]]
21+
name: Build ${{matrix.dockerfile[2]}} Container
1622
steps:
1723
- name: Checkout
1824
uses: actions/checkout@v2
1925

2026
- name: Build and Run Test Container
2127
run: |
22-
docker build -t ghcr.io/spack/spack-bot:latest .
23-
docker tag ghcr.io/spack/spack-bot:latest ghcr.io/spack/spack-bot:${GITHUB_SHA::8}
24-
28+
docker build -f ${{matrix.dockerfile[1]}} -t ghcr.io/spack/${{matrix.dockerfile[0]}}:latest .
29+
docker tag ghcr.io/spack/${{matrix.dockerfile[0]}}:latest ghcr.io/spack/${{matrix.dockerfile[0]}}:${GITHUB_SHA::8}
2530
- name: Login and Deploy Test Container
2631
if: (github.event_name != 'pull_request')
2732
run: |
2833
docker images
2934
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin
30-
docker push --all-tags ghcr.io/spack/spack-bot
35+
docker push --all-tags ghcr.io/spack/${{matrix.dockerfile[0]}}

Dockerfile

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,8 @@ EXPOSE 8080
44

55
# dependencies first since they're the slowest
66
COPY requirements.txt .
7-
RUN pip3 install -r requirements.txt
8-
9-
# make the bot trust GitHub's host key (and verify it)
10-
# If GitHub's fingerprint changes, update the code below with a new one:
11-
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
12-
# and rebuild this container.
13-
RUN ssh-keyscan -t rsa github.com 2>/dev/null > github.key \
14-
&& fingerprint=$(ssh-keygen -lf ./github.key | grep -o 'SHA256:[^ ]*') \
15-
&& if [ "$fingerprint" != "SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8" ]; \
16-
then echo "GitHub key is invalid!" && exit 1; \
17-
fi \
18-
&& mkdir -p /root/.ssh \
19-
&& cat ./github.key >> /root/.ssh/known_hosts
207

21-
# use identity file in /git_rsa (the mount point for our public/private keys)
22-
RUN \
23-
echo "Host github.com" >> /root/.ssh/config \
24-
&& echo "IdentityFile /git_rsa/id_rsa" >> /root/.ssh/config
25-
26-
# ensure /root/.ssh has correct permissions
27-
RUN chmod -R go-rwx /root/.ssh
28-
RUN mkdir -p /git_rsa && chmod -R go-rwx /git_rsa
8+
RUN pip3 install -r requirements.txt
299

3010
# copy app in last so that everything above can be cached
3111
COPY spackbot /app/spackbot

docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ services:
88
context: .
99
dockerfile: smee/Dockerfile
1010

11+
rq-worker:
12+
build:
13+
context: .
14+
dockerfile: workers/Dockerfile
15+
env_file:
16+
- ./.env
17+
deploy:
18+
replicas: 1
19+
20+
rq-server:
21+
env_file:
22+
- ./.env
23+
image: redis:alpine
24+
expose:
25+
- ${REDIS_PORT}
26+
volumes:
27+
- redis-data:/data
28+
- redis-conf:/usr/local/etc/redis/redis.conf
29+
1130
spackbot:
1231
build:
1332
context: .
@@ -29,3 +48,7 @@ services:
2948
- ./.env
3049
links:
3150
- smee
51+
52+
volumes:
53+
redis-data:
54+
redis-conf:

requirements.txt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
aiohttp
22
gidgethub
33
python_dotenv
4+
rq
45
sh
5-
6-
# Add these so we don't wait for install
7-
mypy
8-
flake8
9-
isort

spackbot/__main__.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#
44
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
55

6-
import logging
76
import os
87

98
import aiohttp
@@ -13,12 +12,12 @@
1312
from gidgethub import aiohttp as gh_aiohttp
1413
from .routes import router
1514
from .auth import authenticate_installation
15+
from .helpers import get_logger
1616

1717
# take environment variables from .env file (if present)
1818
load_dotenv()
1919

20-
logging.basicConfig(level=logging.INFO)
21-
logger = logging.getLogger("spackbot")
20+
logger = get_logger(__name__)
2221

2322
#: Location for authenticatd app to get a token for one of its installations
2423
INSTALLATION_TOKEN_URL = "app/installations/{installation_id}/access_tokens"
@@ -41,13 +40,19 @@ async def main(request):
4140
logger.info(f"Received event {event}")
4241

4342
# get an installation token to make a GitHubAPI for API calls
44-
token = await authenticate_installation(event.data)
43+
installation_id = event.data["installation"]["id"]
44+
token = await authenticate_installation(installation_id)
45+
46+
dispatch_kwargs = {
47+
"installation_id": installation_id,
48+
"token": token,
49+
}
4550

4651
async with aiohttp.ClientSession() as session:
4752
gh = gh_aiohttp.GitHubAPI(session, REQUESTER, oauth_token=token)
4853

4954
# call the appropriate callback for the event
50-
await router.dispatch(event, gh, session=session)
55+
await router.dispatch(event, gh, session=session, **dispatch_kwargs)
5156

5257
# return a "Success"
5358
return web.Response(status=200)

spackbot/auth.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#
44
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
55

6-
import logging
76
import os
87
import re
98
import time
@@ -16,9 +15,6 @@
1615

1716
load_dotenv()
1817

19-
logging.basicConfig(level=logging.INFO)
20-
logger = logging.getLogger("spackbot")
21-
2218
#: Location for authenticatd app to get a token for one of its installations
2319
INSTALLATION_TOKEN_URL = "app/installations/{installation_id}/access_tokens"
2420

@@ -79,14 +75,13 @@ async def renew_jwt():
7975
return await _tokens.get_token("JWT", renew_jwt)
8076

8177

82-
async def authenticate_installation(payload):
78+
async def authenticate_installation(installation_id):
8379
"""Get an installation access token for the application.
8480
8581
Renew the JWT if necessary, then use it to get an installation access
8682
token from github, if necessary.
8783
8884
"""
89-
installation_id = payload["installation"]["id"]
9085

9186
async def renew_installation_token():
9287
async with aiohttp.ClientSession() as session:

spackbot/comments.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33
#
44
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
55

6+
import io
67
import random
8+
import traceback
79
import spackbot.helpers as helpers
810

911

1012
async def tell_joke(gh):
1113
"""
1214
Tell a joke to ease the PR tension!
1315
"""
14-
joke = await gh.getitem(
15-
"https://official-joke-api.appspot.com/jokes/programming/random"
16-
)
16+
try:
17+
joke = await gh.getitem(
18+
"https://official-joke-api.appspot.com/jokes/programming/random"
19+
)
20+
except Exception:
21+
return "To be honest, I haven't heard any good jokes lately."
22+
1723
joke = joke[0]
1824
return f"> {joke['setup']}\n *{joke['punchline']}*\n😄️"
1925

@@ -57,6 +63,31 @@ def get_style_message(output):
5763
"""
5864

5965

66+
def get_style_error_message(e_type, e_value, tb):
67+
"""
68+
Given job failure details, format an error message to post. The
69+
parameters e_type, e_value, and tb (for traceback) should be the same as
70+
returned by sys.exc_info().
71+
"""
72+
buffer = io.StringIO()
73+
traceback.print_tb(tb, file=buffer)
74+
tb_contents = buffer.getvalue()
75+
buffer.close()
76+
77+
return f"""
78+
I encountered an error attempting to format style.
79+
<details>
80+
<summary><b>Details</b></summary>
81+
82+
```bash
83+
Error: {e_type}, {e_value}
84+
Stack trace:
85+
{tb_contents}
86+
```
87+
</details>
88+
"""
89+
90+
6091
commands_message = f"""
6192
You can interact with me in many ways!
6293
@@ -70,8 +101,14 @@ def get_style_message(output):
70101
If you need help or see there might be an issue with me, open an issue [here](https://github.com/spack/spack-bot/issues)
71102
"""
72103

73-
style_message = """
74-
It looks like you had an issue with style checks! To fix this, you can run:
104+
style_message = f"""
105+
It looks like you had an issue with style checks! I can help with that if you ask me! Just say:
106+
107+
`{helpers.botname} fix style`
108+
109+
... and I'll try to fix style and push a commit to your fork with the fix.
110+
111+
Alternatively, you can run:
75112
76113
```bash
77114
$ spack style --fix

spackbot/handlers/labels.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
55

66
import re
7-
import logging
87

9-
logging.basicConfig(level=logging.INFO)
10-
logger = logging.getLogger("spackbot")
8+
import spackbot.helpers as helpers
9+
10+
logger = helpers.get_logger(__name__)
1111

1212

1313
#: ``label_patterns`` maps labels to patterns that tell us to apply the labels.
@@ -163,7 +163,18 @@ async def add_labels(event, gh):
163163
attr_matches = []
164164
# Pattern matches for for each attribute are or'd together
165165
for attr, patterns in pattern_dict.items():
166-
attr_matches.append(any(p.search(file[attr]) for p in patterns))
166+
# 'patch' is an example of an attribute that is not required to
167+
# appear in response when listing pull request files. See here:
168+
#
169+
# https://docs.github.com/en/rest/pulls/pulls#list-pull-requests-files
170+
#
171+
# If we don't get some attribute in the response, no labels that
172+
# depend on finding a match in that attribute should be added.
173+
attr_matches.append(
174+
any(p.search(file[attr]) for p in patterns)
175+
if attr in file
176+
else False
177+
)
167178
# If all attributes have at least one pattern match, we add the label
168179
if all(attr_matches):
169180
labels.append(label)

spackbot/handlers/pipelines.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
#
44
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
55

6-
import logging
76
import os
87
import urllib.parse
98
import spackbot.helpers as helpers
109

1110
import aiohttp
1211

13-
logger = logging.getLogger(__name__)
12+
logger = helpers.get_logger(__name__)
1413

1514
# We can only make the request with a GITLAB TOKEN
1615
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN")
@@ -50,7 +49,7 @@ async def run_pipeline(event, gh):
5049

5150
# We need the branch name plus number to assemble the GitLab CI
5251
branch = pr["head"]["ref"]
53-
branch = f"github/pr{number}_{branch}"
52+
branch = f"pr{number}_{branch}"
5453
branch = urllib.parse.quote_plus(branch)
5554

5655
url = f"{helpers.gitlab_spack_project_url}/pipeline?ref={branch}"

0 commit comments

Comments
 (0)