Skip to content

Commit a8acd0f

Browse files
author
Alan Christie
committed
Merge branch 'staging' into multistage-poetry-build
2 parents 8b807ab + 8d7f945 commit a8acd0f

35 files changed

+1471
-792
lines changed

.dockerignore

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
.git/
33
venv/
44
docs/
5-
doc_templates/
65
design_docs/
76
logs/
87
media/

.github/workflows/build-dev.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
with:
7474
dockerfile: Dockerfile
7575
- name: Set up Python
76-
uses: actions/setup-python@v4
76+
uses: actions/setup-python@v5
7777
with:
7878
python-version: '3.11'
7979
- name: Run pre-commit (all files)

.github/workflows/build-production.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ jobs:
120120
with:
121121
dockerfile: Dockerfile
122122
- name: Set up Python
123-
uses: actions/setup-python@v4
123+
uses: actions/setup-python@v5
124124
with:
125125
python-version: '3.11'
126126
- name: Run pre-commit (all files)

.github/workflows/build-staging.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ jobs:
142142
with:
143143
dockerfile: Dockerfile
144144
- name: Set up Python
145-
uses: actions/setup-python@v4
145+
uses: actions/setup-python@v5
146146
with:
147147
python-version: '3.11'
148148
- name: Run pre-commit (all files)

.pylintrc

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ disable = C, F, R,
1212
fixme,
1313
import-error,
1414
imported-auth-user,
15+
new-db-field-with-default,

Dockerfile

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ WORKDIR /code
4747
COPY nginx.conf /etc/nginx/nginx.conf
4848
COPY django_nginx.conf /etc/nginx/sites-available/default.conf
4949
COPY proxy_params /etc/nginx/frag_proxy_params
50-
5150
RUN ln -s /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled
5251

5352
COPY . ./

api/remote_ispyb_connector.py

+20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import threading
23
import time
34
import traceback
@@ -11,6 +12,8 @@
1112
ISPyBRetrieveFailed,
1213
)
1314

15+
logger: logging.Logger = logging.getLogger(__name__)
16+
1417

1518
class SSHConnector(Connector):
1619
def __init__(
@@ -33,6 +36,7 @@ def __init__(
3336

3437
self.conn_inactivity = conn_inactivity
3538
self.lock = threading.Lock()
39+
self.conn = None
3640
self.server = None
3741
self.last_activity_ts = None
3842

@@ -48,6 +52,12 @@ def __init__(
4852
'db_name': db,
4953
}
5054
self.remote_connect(**creds)
55+
logger.debug(
56+
"Started host=%s username=%s local_bind_port=%s",
57+
ssh_host,
58+
ssh_user,
59+
self.server.local_bind_port,
60+
)
5161

5262
else:
5363
self.connect(
@@ -58,12 +68,14 @@ def __init__(
5868
port=port,
5969
conn_inactivity=conn_inactivity,
6070
)
71+
logger.debug("Started host=%s user=%s port=%s", host, user, port)
6172

6273
def remote_connect(
6374
self, ssh_host, ssh_user, ssh_pass, db_host, db_port, db_user, db_pass, db_name
6475
):
6576
sshtunnel.SSH_TIMEOUT = 10.0
6677
sshtunnel.TUNNEL_TIMEOUT = 10.0
78+
sshtunnel.DEFAULT_LOGLEVEL = logging.CRITICAL
6779
self.conn_inactivity = int(self.conn_inactivity)
6880

6981
self.server = sshtunnel.SSHTunnelForwarder(
@@ -123,3 +135,11 @@ def call_sp_retrieve(self, procname, args):
123135
if result == []:
124136
raise ISPyBNoResultException
125137
return result
138+
139+
def stop(self):
140+
if self.server is not None:
141+
self.server.stop()
142+
self.server = None
143+
self.conn = None
144+
self.last_activity_ts = None
145+
logger.debug("Server stopped")

api/security.py

+33-17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Dict, Optional, Union
77
from wsgiref.util import FileWrapper
88

9+
from django.conf import settings
910
from django.db.models import Q
1011
from django.http import Http404, HttpResponse
1112
from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
@@ -91,6 +92,7 @@ def get_conn() -> Optional[Connector]:
9192
# Assume the credentials are invalid if there is no host.
9293
# If a host is not defined other properties are useless.
9394
if not credentials["host"]:
95+
logger.debug("No ISPyB host - cannot return a connector")
9496
return None
9597

9698
conn: Optional[Connector] = None
@@ -114,19 +116,28 @@ def get_configured_connector() -> Optional[Union[Connector, SSHConnector]]:
114116
return None
115117

116118

119+
def ping_configured_connector() -> bool:
120+
"""Pings the connector. If a connection can be obtained it is immediately closed.
121+
The ping simply provides a way to check the credentials are valid and
122+
a connection can be made.
123+
"""
124+
conn: Optional[Union[Connector, SSHConnector]] = None
125+
if connector == 'ispyb':
126+
conn = get_conn()
127+
elif connector == 'ssh_ispyb':
128+
conn = get_remote_conn()
129+
if conn is not None:
130+
conn.stop()
131+
return conn is not None
132+
133+
117134
class ISpyBSafeQuerySet(viewsets.ReadOnlyModelViewSet):
118135
def get_queryset(self):
119136
"""
120137
Optionally restricts the returned purchases to a given proposals
121138
"""
122139
# The list of proposals this user can have
123140
proposal_list = self.get_proposals_for_user(self.request.user)
124-
# Add in the ones everyone has access to
125-
# (unless we already have it)
126-
for open_proposal in self.get_open_proposals():
127-
if open_proposal not in proposal_list:
128-
proposal_list.append(open_proposal)
129-
130141
logger.debug(
131142
'is_authenticated=%s, proposal_list=%s',
132143
self.request.user.is_authenticated,
@@ -141,12 +152,10 @@ def get_queryset(self):
141152
def get_open_proposals(self):
142153
"""
143154
Returns the list of proposals anybody can access.
155+
They are defined via an environment variable
156+
and are made available as a list of strings (Project titles)
144157
"""
145-
if os.environ.get("TEST_SECURITY_FLAG", False):
146-
return ["lb00000"]
147-
else:
148-
# All of well-known (built-in) public Projects (Proposals/Visits)
149-
return ["lb27156"]
158+
return settings.PUBLIC_TAS_LIST
150159

151160
def get_proposals_for_user_from_django(self, user):
152161
# Get the list of proposals for the user
@@ -278,20 +287,27 @@ def get_proposals_for_user(self, user):
278287
"""Returns a list of proposals (public and private) that the user has access to."""
279288
assert user
280289

290+
proposals = []
281291
ispyb_user = os.environ.get("ISPYB_USER")
282292
logger.debug("ispyb_user=%s", ispyb_user)
283293
if ispyb_user:
284294
if user.is_authenticated:
285295
logger.info("Getting proposals from ISPyB...")
286-
return self.get_proposals_for_user_from_ispyb(user)
296+
proposals = self.get_proposals_for_user_from_ispyb(user)
287297
else:
288-
logger.info(
289-
"No proposals (user %s is not authenticated)", user.username
290-
)
291-
return []
298+
username = user.username or "UNKNOWN"
299+
logger.info("No proposals (user '%s' is not authenticated)", username)
292300
else:
293301
logger.info("Getting proposals from Django...")
294-
return self.get_proposals_for_user_from_django(user)
302+
proposals = self.get_proposals_for_user_from_django(user)
303+
304+
# Now add in Target Access Strings that everyone has access to
305+
# (unless we already have them)
306+
for open_proposal in self.get_open_proposals():
307+
if open_proposal not in proposals:
308+
proposals.append(open_proposal)
309+
310+
return proposals
295311

296312
def get_q_filter(self, proposal_list):
297313
"""Returns a Q expression representing a (potentially complex) table filter."""

api/utils.py

+4
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,7 @@ def pretty_request(request, *, tag='', print_body=False):
399399
f'{body}\n'
400400
'- REQUEST END'
401401
)
402+
403+
404+
def deployment_mode_is_production():
405+
return settings.DEPLOYMENT_MODE == "PRODUCTION"

docker-compose.yml

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ services:
8686
- .:/code/
8787
environment:
8888
AUTHENTICATE_UPLOAD: ${AUTHENTICATE_UPLOAD:-True}
89+
DEPLOYMENT_MODE: 'development'
8990
POSTGRESQL_USER: postgres
9091
# Celery tasks need to run synchronously
9192
CELERY_TASK_ALWAYS_EAGER: 'True'
@@ -99,6 +100,9 @@ services:
99100
OIDC_AS_CLIENT_ID: ${OIDC_AS_CLIENT_ID:-account-server-api}
100101
OIDC_DM_CLIENT_ID: ${OIDC_DM_CLIENT_ID:-data-manager-api}
101102
OIDC_RENEW_ID_TOKEN_EXPIRY_MINUTES: '210'
103+
# Public target access strings?
104+
# A comma-separated list of Project titles.
105+
PUBLIC_TAS: lb18145-1
102106
# Squonk configuration
103107
SQUONK2_VERIFY_CERTIFICATES: 'No'
104108
SQUONK2_UNIT_BILLING_DAY: 3

fragalysis/settings.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@
237237
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = int(TOKEN_EXPIRY_MINUTES) * 60
238238
# Keycloak mozilla_django_oidc - Settings - End
239239

240+
# The deployment mode.
241+
# Controls the behaviour of the application (it's strictness to errors etc).
242+
# Typically one of "DEVELOPMENT" or "PRODUCTION".
243+
# see api.utils for the 'deployment_mode_is_production()' function.
244+
DEPLOYMENT_MODE = os.environ.get("DEPLOYMENT_MODE", "production").upper()
245+
240246
ROOT_URLCONF = "fragalysis.urls"
241247

242248
STATIC_ROOT = os.path.join(PROJECT_ROOT, "static")
@@ -389,6 +395,10 @@
389395
"TAS_REGEX_ERROR_MSG",
390396
"Must begin 'lb' followed by 5 digits, optionally followed by a hyphen and a number.",
391397
)
398+
# Are any public target access strings defined?
399+
# If so they'll be in the PUBLIC_TAS variable as a comma separated list.
400+
PUBLIC_TAS = os.environ.get("PUBLIC_TAS", "")
401+
PUBLIC_TAS_LIST = PUBLIC_TAS.split(",") if PUBLIC_TAS else []
392402

393403
COMPUTED_SET_MEDIA_DIRECTORY = "computed_set_data"
394404
TARGET_LOADER_MEDIA_DIRECTORY = "target_loader_data"
@@ -447,9 +457,9 @@
447457
'api.security': {'level': 'INFO'},
448458
'asyncio': {'level': 'WARNING'},
449459
'celery': {'level': 'INFO'},
450-
'django': {'level': 'INFO'},
451-
'mozilla_django_oidc': {'level': 'INFO'},
452-
'urllib3': {'level': 'INFO'},
460+
'django': {'level': 'WARNING'},
461+
'mozilla_django_oidc': {'level': 'WARNING'},
462+
'urllib3': {'level': 'WARNING'},
453463
'paramiko': {'level': 'WARNING'},
454464
},
455465
'root': {

nginx.conf

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ http {
2727
sendfile on;
2828
tcp_nopush on;
2929
tcp_nodelay on;
30-
keepalive_timeout 650;
3130
types_hash_max_size 2048;
3231
# server_tokens off;
3332

@@ -51,7 +50,8 @@ http {
5150
access_log /var/log/nginx/access.log;
5251
error_log /var/log/nginx/error.log;
5352

54-
# Tuneout settings
53+
# Timeeout settings
54+
keepalive_timeout 1000s;
5555
proxy_connect_timeout 1000s;
5656
proxy_send_timeout 1000s;
5757
proxy_read_timeout 1000s;

0 commit comments

Comments
 (0)