Skip to content

Commit 8a202c7

Browse files
Merge pull request #182 from canonical/fix-bg-tasks
Fix bg tasks
2 parents f611cdd + beeb2d1 commit 8a202c7

File tree

17 files changed

+638
-363
lines changed

17 files changed

+638
-363
lines changed

.dockerignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
repositories
2+
tree-cache
3+
.venv
4+
venv/
5+
__pycache__/
6+
*.so
7+
build/
8+
dist/
9+
.eggs/
10+
wheels/
11+
*.egg-info/
12+
.installed.cfg
13+
*.egg
14+
.pytest_cache/
15+
.coverage
16+
htmlcov/
17+
.tox/
18+
.coverage.*
19+
.cache/
20+
Dockerfile
21+
docker-compose.yml

.github/workflows/CI.yml

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,51 @@ jobs:
9393
run-python:
9494
name: Run Python
9595
runs-on: ubuntu-latest
96+
services:
97+
postgres:
98+
image: postgres:latest
99+
env:
100+
POSTGRES_PASSWORD: postgres
101+
ports:
102+
- 5432:5432
103+
steps:
104+
- uses: actions/checkout@v3
105+
- name: Set up Python 3.12
106+
uses: actions/setup-python@v5
107+
with:
108+
python-version: "3.12"
109+
- name: Install dependencies
110+
run: |
111+
python -m pip install --upgrade pip
112+
pip install -r requirements.txt
113+
- name: Run service
114+
env:
115+
GOOGLE_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
116+
GOOGLE_PRIVATE_KEY_ID: ${{ secrets.PRIVATE_KEY_ID }}
117+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
118+
SECRET_KEY: secret_key
119+
GH_TOKEN: ${{ github.token }}
120+
REPO_ORG: https://github.com/canonical
121+
JIRA_EMAIL: [email protected]
122+
JIRA_TOKEN: jiratoken
123+
JIRA_URL: https://example.atlassian.net
124+
JIRA_LABELS: somelabel
125+
JIRA_COPY_UPDATES_EPIC: WD-9999999
126+
GOOGLE_DRIVE_FOLDER_ID: folderid
127+
COPYDOC_TEMPLATE_ID: templateid
128+
FLASK_DEBUG: 1
129+
run: |
130+
gunicorn webapp.app:app --workers=2 --bind 0.0.0.0:8104 & sleep 3
131+
curl --head --fail --retry-delay 1 --retry 30 --retry-connrefused http://localhost:8104
132+
133+
run-python-redis:
134+
name: Run Python Redis
135+
runs-on: ubuntu-latest
96136
services:
97137
redis:
98138
image: redis
139+
ports:
140+
- 6379:6379
99141
options: >-
100142
--health-cmd "redis-cli ping"
101143
--health-interval 10s
@@ -134,9 +176,10 @@ jobs:
134176
JIRA_COPY_UPDATES_EPIC: WD-9999999
135177
GOOGLE_DRIVE_FOLDER_ID: folderid
136178
COPYDOC_TEMPLATE_ID: templateid
179+
FLASK_DEBUG: 1
137180
run: |
138-
gunicorn webapp.app:app --daemon --bind 0.0.0.0:8104
139-
curl --head --fail --retry-delay 1 --retry 30 --retry-connrefused http://localhost:8104
181+
flask --app webapp.app run --debug & sleep 3
182+
curl --head --fail --retry-delay 1 --retry 30 --retry-connrefused http://localhost:5000
140183
141184
run-image:
142185
name: Run Image
@@ -170,11 +213,11 @@ jobs:
170213
GOOGLE_PRIVATE_KEY_ID: ${{ secrets.PRIVATE_KEY_ID }}
171214
run: |
172215
docker run \
173-
-p 8104:8104 \
216+
-p 8104:80 \
174217
-e SECRET_KEY=secret_key \
175218
-e REDIS_HOST=localhost \
176219
-e REDIS_PORT=6379 \
177-
-e GH_TOKEN=${{ github.token }} \
220+
-e GH_TOKEN=github.token \
178221
-e REPO_ORG=https://github.com/canonical \
179222
-e DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres \
180223

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ ARG BUILD_ID
3232
ENV TALISKER_REVISION_ID="${BUILD_ID}"
3333

3434
# Setup commands to run web service
35+
RUN chmod +x ./entrypoint
3536
ENTRYPOINT ["./entrypoint"]
3637
CMD ["0.0.0.0:80"]

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ If you do not want to use a dedicated cache, a simple filecache has been include
201201

202202
```bash
203203
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres
204-
docker run -d -p 6379:6379 valkey/valkey
204+
docker run -d -p 6379:6379 redis
205205
```
206206

207207
#### Virtual Environment
@@ -253,7 +253,7 @@ Please note, make sure the containers for postgres and valkey are already runnin
253253

254254
```bash
255255
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres
256-
docker run -d -p 6379:6379 valkey/valkey
256+
docker run -d -p 6379:6379 redis
257257
```
258258

259259
You can optionally use dotrun to start the service. When the 1.1.0-rc1 branch is merged, then we can use dotrun without the `--release` flag.

app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from webapp.app import app # noqa F401
1+
from webapp.app import app # noqa: F401

entrypoint

100755100644
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,5 @@ activate() {
2323
RUN_COMMAND="${RUN_COMMAND} --reload --log-level debug --timeout 9999"
2424
fi
2525

26-
# Run new migrations if needed
27-
flask db upgrade
28-
2926
${RUN_COMMAND}
3027
}

webapp/__init__.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from werkzeug.middleware.proxy_fix import ProxyFix
88

99
from webapp.cache import init_cache
10-
from webapp.celery import init_celery
1110
from webapp.context import RegexConverter, base_context, clear_trailing_slash
1211
from webapp.gdrive import init_gdrive
12+
from webapp.github import init_github
1313
from webapp.jira import init_jira
1414
from webapp.models import init_db
1515
from webapp.sso import init_sso
@@ -47,8 +47,8 @@ def create_app():
4747
# Initialize gdrive
4848
init_gdrive(app)
4949

50-
# Initialize celery
51-
init_celery(app)
50+
# Initialize github
51+
init_github(app)
5252

5353
return app
5454

@@ -67,7 +67,8 @@ def set_security_headers(response):
6767
if flask.request.endpoint in flask.current_app.view_functions:
6868
view_func = flask.current_app.view_functions[flask.request.endpoint]
6969
add_xframe_options_header = not hasattr(
70-
view_func, "_exclude_xframe_options_header"
70+
view_func,
71+
"_exclude_xframe_options_header",
7172
)
7273

7374
if add_xframe_options_header and "X-Frame-Options" not in response.headers:
@@ -106,10 +107,14 @@ def set_cache_control_headers(response):
106107

107108
max_age = response.cache_control.max_age
108109
stale_while_revalidate = response.cache_control._get_cache_value(
109-
"stale-while-revalidate", False, int
110+
"stale-while-revalidate",
111+
False,
112+
int,
110113
)
111114
stale_if_error = response.cache_control._get_cache_value(
112-
"stale-if-error", False, int
115+
"stale-if-error",
116+
False,
117+
int,
113118
)
114119

115120
if type(max_age) is not int:
@@ -142,7 +147,9 @@ def set_cache_control_headers(response):
142147
#
143148
# An additional day will hopefully be long enough for most cases.
144149
response.cache_control._set_cache_value(
145-
"stale-while-revalidate", "86400", int
150+
"stale-while-revalidate",
151+
"86400",
152+
int,
146153
)
147154

148155
if type(stale_if_error) is not int:
@@ -164,7 +171,9 @@ def set_cache_control_headers(response):
164171
#
165172
# So we set this to 5 minutes following expiry as a trade-off.
166173
response.cache_control._set_cache_value(
167-
"stale-if-error", "300", int
174+
"stale-if-error",
175+
"300",
176+
int,
168177
)
169178

170179
return response
@@ -267,7 +276,8 @@ def __init__(
267276
def not_found_error(error):
268277
return (
269278
flask.render_template(
270-
template_404, message=error.description
279+
template_404,
280+
message=error.description,
271281
),
272282
404,
273283
)
@@ -278,7 +288,8 @@ def not_found_error(error):
278288
def internal_error(error):
279289
return (
280290
flask.render_template(
281-
template_500, message=error.description
291+
template_500,
292+
message=error.description,
282293
),
283294
500,
284295
)
@@ -294,7 +305,8 @@ def status_check():
294305
@self.route("/favicon.ico")
295306
def favicon():
296307
return flask.send_file(
297-
favicon_path, mimetype="image/vnd.microsoft.icon"
308+
favicon_path,
309+
mimetype="image/vnd.microsoft.icon",
298310
)
299311

300312
elif favicon_url:

webapp/app.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
from flask import render_template
44

55
from webapp import create_app
6-
from webapp.github import init_github
6+
from webapp.celery import init_celery
77
from webapp.routes.jira import jira_blueprint
88
from webapp.routes.product import product_blueprint
99
from webapp.routes.tree import tree_blueprint
1010
from webapp.routes.user import user_blueprint
11+
from webapp.scheduled_tasks import init_scheduled_tasks
1112
from webapp.sso import login_required
1213

1314
app = create_app()
1415

15-
# Tasks
16-
init_github(app)
16+
# Initialize celery
17+
celery_app = init_celery(app)
18+
19+
# Initialize scheduled tasks
20+
init_scheduled_tasks()
21+
1722

1823
# Server-side routes
1924
app.register_blueprint(tree_blueprint)
@@ -28,13 +33,15 @@
2833
@login_required
2934
def index():
3035
return render_template(
31-
"index.html", is_dev=environ.get("FLASK_ENV") == "development"
36+
"index.html",
37+
is_dev=environ.get("FLASK_ENV") == "development",
3238
)
3339

3440

3541
@app.route("/app/<path:path>")
3642
@login_required
3743
def webpage(path):
3844
return render_template(
39-
"index.html", is_dev=environ.get("FLASK_ENV") == "development"
45+
"index.html",
46+
is_dev=environ.get("FLASK_ENV") == "development",
4047
)

webapp/cache.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,6 @@
1010
from redis import exceptions as redis_exceptions
1111

1212

13-
def init_cache(app: Flask):
14-
try:
15-
cache = RedisCache(app)
16-
except Exception as e:
17-
cache = FileCache(app)
18-
app.logger.info(
19-
f"Error: {e} Redis cache is not available."
20-
" Using FileCache instead."
21-
)
22-
app.config["CACHE"] = cache
23-
return cache
24-
25-
2613
class Cache(ABC):
2714
"""Abstract Cache class"""
2815

@@ -32,22 +19,18 @@ class Cache(ABC):
3219
@abstractmethod
3320
def get(self, key: str):
3421
"""Get a value from the cache"""
35-
pass
3622

3723
@abstractmethod
3824
def set(self, key: str, value: Any):
3925
"""Set a value in the cache"""
40-
pass
4126

4227
@abstractmethod
4328
def delete(self, key: str):
4429
"""Delete a value from the cache"""
45-
pass
4630

4731
@abstractmethod
4832
def is_available(self):
4933
"""Check if the cache is available"""
50-
pass
5134

5235

5336
class RedisCache(Cache):
@@ -139,7 +122,8 @@ def connect(self):
139122

140123
path_exists = Path(self.cache_path).exists()
141124
path_writable = Path(self.cache_path).is_dir() and os.access(
142-
self.cache_path, os.W_OK
125+
self.cache_path,
126+
os.W_OK,
143127
)
144128
if not path_exists and path_writable:
145129
raise ConnectionError("Cache directory is not writable")
@@ -151,7 +135,13 @@ def save_to_file(self, key: str, value: Any):
151135
data = json.dumps(value)
152136
# Delete the file if it exists
153137
if Path(self.cache_path + "/" + key).exists():
154-
os.remove(self.cache_path + "/" + key)
138+
try:
139+
os.remove(self.cache_path + "/" + key)
140+
except FileNotFoundError:
141+
# We catch this as due to different processes potentially
142+
# accessing this method, deleting a file could face a race
143+
# condition.
144+
self.logger.info("File already removed")
155145
# Create base directory if it does not exist
156146
if not Path(self.cache_path).exists():
157147
Path(self.cache_path).mkdir(parents=True, exist_ok=True)
@@ -165,7 +155,7 @@ def load_from_file(self, key: str):
165155
# Check if the file exists
166156
if not Path(self.cache_path + "/" + key).exists():
167157
return None
168-
with open(self.cache_path + "/" + key, "r") as f:
158+
with open(self.cache_path + "/" + key) as f:
169159
data = f.read()
170160
return json.loads(data)
171161

@@ -187,3 +177,15 @@ def onerror(*args, **kwargs):
187177
os.chmod(self.cache_path + "/" + key, 0o777)
188178

189179
return shutil.rmtree(self.cache_path + "/" + key, onerror=onerror)
180+
181+
182+
def init_cache(app: Flask) -> Cache:
183+
try:
184+
cache = RedisCache(app)
185+
except Exception as e:
186+
cache = FileCache(app)
187+
msg = f"Error: {e} Redis cache is not available."
188+
" Using FileCache instead."
189+
app.logger.info(msg)
190+
app.config["CACHE"] = cache
191+
return cache

0 commit comments

Comments
 (0)