Skip to content

Commit 526af44

Browse files
alanbchristiekaliifWaztomAlan Christiedependabot[bot]
authored
Prepare for first formal release (#537)
* Some changes to cset_upload.py to allow site observation short codes (#527) * stashing * fix: cset_upload.py updated to allow new-style site observation codes NB! this probably still won't work! I suspect the file I was given is broken and I cannot test it further * stashing * Short code prefix and tooltip to backend Target loader now reads short code prefix and tooltip from meta_aligner.yaml. Tooltip is saved to Experiment model. TODO: make tooltip available via API * Prefix tooltip now serverd by api/site_observation * Site observation groups for shortcodes now by experiment * New format to download zip (issue 1326) (#530) * stashing * stashing * feat: download structure fixed TODO: add all the yamls * All yaml files added to download * cset_upload.py: lhs_pdb renamed to ref_pdb * Renamed canon- and conf site tags * Adds support for key-based SSH connections (#534) * Centralised environment variables (#529) * refactor: Restructured settings.py * docs: Minor tweaks * refactor: Move security and infection config to settings * refactor: b/e & f/e/ tags now in settings (also fixed f/e tag value) * refactor: Move Neo4j config to settings * refactor: More variables into settings * refactor: Moved remaining config * docs: Adds configuration guide as comments * docs: Variable prefix now 'stack_' not 'stack_env_' --------- Co-authored-by: Alan Christie <alan.christie@matildapeak.com> * feat: Adds support for private keys on SSH tunnel * fix: Fixes key-based logic --------- Co-authored-by: Alan Christie <alan.christie@matildapeak.com> * build(deps): bump cryptography from 42.0.0 to 42.0.2 (#533) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](pyca/cryptography@42.0.0...42.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs: Updates documentation (#536) Co-authored-by: Alan Christie <alan.christie@matildapeak.com> * build(deps): bump django from 3.2.20 to 3.2.24 (#535) Bumps [django](https://github.com/django/django) from 3.2.20 to 3.2.24. - [Commits](django/django@3.2.20...3.2.24) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Kalev Takkis <ktakkis@informaticsmatters.com> Co-authored-by: Warren Thompson <waztom@gmail.com> Co-authored-by: Alan Christie <alan.christie@matildapeak.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent ee98c09 commit 526af44

20 files changed

+755
-492
lines changed

README.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,11 @@ installs/updates new packages to local venv. It's equivalent to running
6666
`poetry lock && poetry install`, so if you're not interested in local environment and
6767
just want to update the lockfile, you can run just `poetry lock`.
6868

69-
7069
## Building and running (local)
7170
The backend is a Docker container image and can be build and deployed locally using `docker-compose`: -
7271

7372
docker-compose build
7473

75-
7674
To run the application (which wil include deployment of the postgres and neo4j databases)
7775
run: -
7876

@@ -181,6 +179,18 @@ at `/code/logs`.
181179
> For local development using the `docker-compose.yml` file you'll find the logs
182180
at `./data/logs/backend.log`.
183181

182+
## Configuration (environment variables)
183+
The backend configuration is controlled by a number of environment variables.
184+
Variables are typically defined in the project's `fragalysis/settings.py`, where you
185+
will also find **ALL** the dynamically configured variables (those that can be changed
186+
using *environment variables* in the deployed Pod/Container).
187+
188+
- Not all variables are dynamic. For example `ALLOWED_HOSTS` is a static variable
189+
that is set in the `settings.py` file and is not intended to be changed at run-time.
190+
191+
Refer to the documentation in the `settings.py` file to understand the environment
192+
and the style guide for new variables that you need to add.
193+
184194
## Database migrations
185195
The best approach is to spin-up the development backend (locally) using
186196
`docker-compose` with the custom *migration* compose file and then shell into Django.

api/infections.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
# Infections are injected into the application via the environment variable
55
# 'INFECTIONS', a comma-separated list of infection names.
66

7-
import os
87
from typing import Dict, Set
98

9+
from django.conf import settings
10+
1011
from api.utils import deployment_mode_is_production
1112

1213
# The built-in set of infections.
@@ -20,9 +21,6 @@
2021
INFECTION_STRUCTURE_DOWNLOAD: 'An error in the DownloadStructures view'
2122
}
2223

23-
# What infection have been set?
24-
_INFECTIONS: str = os.environ.get('INFECTIONS', '').lower()
25-
2624

2725
def have_infection(name: str) -> bool:
2826
"""Returns True if we've been given the named infection.
@@ -31,9 +29,11 @@ def have_infection(name: str) -> bool:
3129

3230

3331
def _get_infections() -> Set[str]:
34-
if _INFECTIONS == '':
32+
if settings.INFECTIONS == '':
3533
return set()
3634
infections: set[str] = {
37-
infection for infection in _INFECTIONS.split(',') if infection in _CATALOGUE
35+
infection
36+
for infection in settings.INFECTIONS.split(',')
37+
if infection in _CATALOGUE
3838
}
3939
return infections

api/remote_ispyb_connector.py

+44-10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(
2828
remote=False,
2929
ssh_user=None,
3030
ssh_password=None,
31+
ssh_private_key_filename=None,
3132
ssh_host=None,
3233
conn_inactivity=360,
3334
):
@@ -45,6 +46,7 @@ def __init__(
4546
'ssh_host': ssh_host,
4647
'ssh_user': ssh_user,
4748
'ssh_pass': ssh_password,
49+
'ssh_pkey': ssh_private_key_filename,
4850
'db_host': host,
4951
'db_port': int(port),
5052
'db_user': user,
@@ -53,12 +55,11 @@ def __init__(
5355
}
5456
self.remote_connect(**creds)
5557
logger.debug(
56-
"Started host=%s username=%s local_bind_port=%s",
58+
"Started remote ssh_host=%s ssh_user=%s local_bind_port=%s",
5759
ssh_host,
5860
ssh_user,
5961
self.server.local_bind_port,
6062
)
61-
6263
else:
6364
self.connect(
6465
user=user,
@@ -68,29 +69,60 @@ def __init__(
6869
port=port,
6970
conn_inactivity=conn_inactivity,
7071
)
71-
logger.debug("Started host=%s user=%s port=%s", host, user, port)
72+
logger.debug("Started direct host=%s user=%s port=%s", host, user, port)
7273

7374
def remote_connect(
74-
self, ssh_host, ssh_user, ssh_pass, db_host, db_port, db_user, db_pass, db_name
75+
self,
76+
ssh_host,
77+
ssh_user,
78+
ssh_pass,
79+
ssh_pkey,
80+
db_host,
81+
db_port,
82+
db_user,
83+
db_pass,
84+
db_name,
7585
):
7686
sshtunnel.SSH_TIMEOUT = 10.0
7787
sshtunnel.TUNNEL_TIMEOUT = 10.0
7888
sshtunnel.DEFAULT_LOGLEVEL = logging.CRITICAL
7989
self.conn_inactivity = int(self.conn_inactivity)
8090

81-
self.server = sshtunnel.SSHTunnelForwarder(
82-
(ssh_host),
83-
ssh_username=ssh_user,
84-
ssh_password=ssh_pass,
85-
remote_bind_address=(db_host, db_port),
86-
)
91+
if ssh_pkey:
92+
logger.debug(
93+
'Creating SSHTunnelForwarder (with SSH Key) host=%s user=%s',
94+
ssh_host,
95+
ssh_user,
96+
)
97+
self.server = sshtunnel.SSHTunnelForwarder(
98+
(ssh_host),
99+
ssh_username=ssh_user,
100+
ssh_pkey=ssh_pkey,
101+
remote_bind_address=(db_host, db_port),
102+
)
103+
else:
104+
logger.debug(
105+
'Creating SSHTunnelForwarder (with password) host=%s user=%s',
106+
ssh_host,
107+
ssh_user,
108+
)
109+
self.server = sshtunnel.SSHTunnelForwarder(
110+
(ssh_host),
111+
ssh_username=ssh_user,
112+
ssh_password=ssh_pass,
113+
remote_bind_address=(db_host, db_port),
114+
)
115+
logger.debug('Created SSHTunnelForwarder')
87116

88117
# stops hanging connections in transport
89118
self.server.daemon_forward_servers = True
90119
self.server.daemon_transport = True
91120

121+
logger.debug('Starting SSH server...')
92122
self.server.start()
123+
logger.debug('Started SSH server')
93124

125+
logger.debug('Connecting to ISPyB (db_user=%s db_name=%s)...', db_user, db_name)
94126
self.conn = pymysql.connect(
95127
user=db_user,
96128
password=db_pass,
@@ -100,8 +132,10 @@ def remote_connect(
100132
)
101133

102134
if self.conn is not None:
135+
logger.debug('Connected')
103136
self.conn.autocommit = True
104137
else:
138+
logger.debug('Failed to connect')
105139
self.server.stop()
106140
raise ISPyBConnectionException
107141
self.last_activity_ts = time.time()

api/security.py

+20-19
Original file line numberDiff line numberDiff line change
@@ -48,51 +48,52 @@
4848

4949

5050
def get_remote_conn() -> Optional[SSHConnector]:
51-
ispyb_credentials: Dict[str, Any] = {
52-
"user": os.environ.get("ISPYB_USER"),
53-
"pw": os.environ.get("ISPYB_PASSWORD"),
54-
"host": os.environ.get("ISPYB_HOST"),
55-
"port": os.environ.get("ISPYB_PORT"),
51+
credentials: Dict[str, Any] = {
52+
"user": settings.ISPYB_USER,
53+
"pw": settings.ISPYB_PASSWORD,
54+
"host": settings.ISPYB_HOST,
55+
"port": settings.ISPYB_PORT,
5656
"db": "ispyb",
5757
"conn_inactivity": 360,
5858
}
5959

6060
ssh_credentials: Dict[str, Any] = {
61-
'ssh_host': os.environ.get("SSH_HOST"),
62-
'ssh_user': os.environ.get("SSH_USER"),
63-
'ssh_password': os.environ.get("SSH_PASSWORD"),
61+
'ssh_host': settings.SSH_HOST,
62+
'ssh_user': settings.SSH_USER,
63+
'ssh_password': settings.SSH_PASSWORD,
64+
"ssh_private_key_filename": settings.SSH_PRIVATE_KEY_FILENAME,
6465
'remote': True,
6566
}
6667

67-
ispyb_credentials.update(**ssh_credentials)
68+
credentials.update(**ssh_credentials)
6869

6970
# Caution: Credentials may not be set in the environment.
7071
# Assume the credentials are invalid if there is no host.
7172
# If a host is not defined other properties are useless.
72-
if not ispyb_credentials["host"]:
73+
if not credentials["host"]:
7374
logger.debug("No ISPyB host - cannot return a connector")
7475
return None
7576

7677
# Try to get an SSH connection (aware that it might fail)
7778
conn: Optional[SSHConnector] = None
7879
try:
79-
conn = SSHConnector(**ispyb_credentials)
80+
conn = SSHConnector(**credentials)
8081
except Exception:
8182
# Log the exception if DEBUG level or lower/finer?
82-
# The following wil not log if the level is set to INFO for example.
83+
# The following will not log if the level is set to INFO for example.
8384
if logging.DEBUG >= logger.level:
84-
logger.info("ispyb_credentials=%s", ispyb_credentials)
85+
logger.info("credentials=%s", credentials)
8586
logger.exception("Got the following exception creating SSHConnector...")
8687

8788
return conn
8889

8990

9091
def get_conn() -> Optional[Connector]:
9192
credentials: Dict[str, Any] = {
92-
"user": os.environ.get("ISPYB_USER"),
93-
"pw": os.environ.get("ISPYB_PASSWORD"),
94-
"host": os.environ.get("ISPYB_HOST"),
95-
"port": os.environ.get("ISPYB_PORT"),
93+
"user": settings.ISPYB_USER,
94+
"pw": settings.ISPYB_PASSWORD,
95+
"host": settings.ISPYB_HOST,
96+
"port": settings.ISPYB_PORT,
9697
"db": "ispyb",
9798
"conn_inactivity": 360,
9899
}
@@ -108,7 +109,7 @@ def get_conn() -> Optional[Connector]:
108109
conn = Connector(**credentials)
109110
except Exception:
110111
# Log the exception if DEBUG level or lower/finer?
111-
# The following wil not log if the level is set to INFO for example.
112+
# The following will not log if the level is set to INFO for example.
112113
if logging.DEBUG >= logger.level:
113114
logger.info("credentials=%s", credentials)
114115
logger.exception("Got the following exception creating Connector...")
@@ -349,7 +350,7 @@ def get_proposals_for_user(self, user, restrict_to_membership=False):
349350
assert user
350351

351352
proposals = set()
352-
ispyb_user = os.environ.get("ISPYB_USER")
353+
ispyb_user = settings.ISPYB_USER
353354
logger.debug(
354355
"ispyb_user=%s restrict_to_membership=%s",
355356
ispyb_user,

build-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pre-commit == 3.5.0
88
poetry == 1.7.1
99

1010
# Matching main requirements...
11-
Django==3.2.20
11+
Django==3.2.24
1212

1313
# Others
1414
httpie == 3.2.1

0 commit comments

Comments
 (0)