-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdeploy.py
More file actions
144 lines (130 loc) · 6.5 KB
/
deploy.py
File metadata and controls
144 lines (130 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python
"""Despliega tu aplicación web en tu contenedor Docker de la FDI
Requiere:
ejecutarse desde consola en la misma carpeta en la que esté tu pom.xml, volcado de BD, y ficheros de datos
que tu proyecto esté actualizado (y en particular, que tenga un application-container.properties)
credenciales en un fichero credentials.json (NO SUBAS CREDENCIALES A GITHUB)
puedes ver sus valores orientativos en credentials.json.template
Cómo ejecutar:
1. instala las dependencias, bien vía pip ó con un entorno virtual python
entorno virtual: créalo vía `python3 -m venv deploy`
actívalo vía `deploy/scripts/activate.ps1` (Windows) ó `source deploy/bin/activate` (Linux)
instala dependencias vía `pip install -r requirements.txt`
2. asegúrate de que tu base de datos está escribiéndose a disco, con JDBC URL jdbc:h2:file:./iwdb
3. asegúrate de que estás guardando ficheros de usuario a disco, a ./iwdata
4. ejecuta el script: python deploy.py
"""
# dependencias no-incluídas en Python estándar
import sshtunnel # instala via pip install sshtunnel (ó entorno virtual, ver más arriba)
import fabric # instala via pip install fabric
# dependencias incluídas en Python estándar
import sys
import subprocess
import glob
import json
import zipfile
from typing import Union
from pathlib import Path
import argparse
# see https://stackoverflow.com/a/68817065/15472
def zip_dir(dir: Union[Path, str], filename: Union[Path, str]):
"""Zip the provided directory without navigating to that directory using `pathlib` module"""
# Convert to Path object
dir = Path(dir)
with zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
for entry in dir.rglob("*"):
zip_file.write(entry, entry.relative_to(dir))
def main(credentials_file, db_file_root, data_file_root):
print(f"Checking for database files... (should be named {db_file_root}; ensure you have spring.datasource.url=jdbc:h2:file:./{db_file_root}) ")
dbfiles = glob.glob(f"{db_file_root}.*")
if len(dbfiles) > 0:
print(f"Found {len(dbfiles)} database files: {dbfiles}")
else:
print("No database files found, exiting.")
sys.exit(1)
print(f"Checking for data files... (should be in {data_file_root}; ensure you have es.ucm.fdi.base-path=./{data_file_root}) ")
datafiles = glob.glob(data_file_root, recursive=True)
if len(datafiles) > 0:
print(f"Found {len(datafiles)} data files, compressing... ")
zip_dir(data_file_root, "iwdata.zip")
else:
print("No data files found.")
sys.exit(1)
print("Building deployment jar file... ")
try:
# shell=True works in windows/fails in Linux; and vice-versa
is_win = sys.platform.startswith("win")
subprocess.run(["mvn",
"package",
"-P",
"linux,!windows,!macos",
"-DskipTests=true"], shell=is_win, check=True)
jar_path = glob.glob("target/*.jar")[0]
jar_name = Path(jar_path).name
print(f"Deployment jar file is ready: {jar_path} ({jar_name})")
except Exception as e:
print(f"Error: Could not build jar file. Exiting: {e}")
sys.exit(1)
print(f"Loading credentials from `{credentials_file}` ... ")
try:
credentials = json.load(open(credentials_file))
except:
print("Error: Could not load credentials file. Exiting.")
sys.exit(1)
print("Connecting to jumphost ... ")
with sshtunnel.open_tunnel(
(credentials['jumphost'], 22),
ssh_username=credentials['jumphost_user'],
ssh_password=credentials['jumphost_pass'],
remote_bind_address=(credentials['target'], 22),
local_bind_address=('0.0.0.0', 2222),
allow_agent=False
) as tunnel:
print(f"Tunnel to {credentials['jumphost']} over port 22 established, bind via localhost 2222 ...")
with fabric.connection.Connection(
host='127.0.0.1',
user=credentials['target_user'],
port=2222,
connect_kwargs={
"password": credentials['target_pass'],
"allow_agent": False
}
) as c:
print(f"Connected to target host {credentials['target']} as {credentials['target_user']}")
print("Uploading database files ... ")
for f in dbfiles:
c.put(f)
print("Uploading data files ... ")
c.put("iwdata.zip")
c.run(f"unzip iwdata.zip -d {data_file_root} && rm iwdata.zip")
print("Uploading jar file ... ")
c.put(jar_path)
print(f"All files uploaded. Killing previous servers ...")
c.run("tmux kill-server || true")
print(f"creating a launch script (`run.sh`) for the web application ...")
c.run(f"echo 'SPRING_PROFILES_ACTIVE=container java -jar {jar_name}' > run.sh && chmod +x run.sh")
print(f"... and deploying in new tmux session `iw`; connect via `tmux a -t iw` to see logs")
c.run(f"tmux new-session -d -s iw ./run.sh")
target_prefix = credentials['target'].split(".")[0]
target_suffix = credentials['jumphost']
print(f"Deployment is now being completed at the server:\n"
f" - Visit https://{target_prefix}.{target_suffix} (after a minute or so) to access your site\n"
f" - To see progress/check logs, visit https://guacamole.{credentials['jumphost']} as `{target_prefix}` with password `{credentials['target_pass']}`\n"
f" and open logs via `tmux a -t iw`; disconnecting with Ctrl+b + d\n"
f" - If you need to kill the server, run `tmux kill-server`")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=\
"Upload all components of a Spring Boot application to a remote server")
parser.add_argument("--credentials", "-c", type=str, default="credentials.json",
help="Path to credentials")
parser.add_argument("--db_file_root", "-d", type=str, default="iwdb",
help="Root name of database files (see application.properties' spring.datasource.url)")
parser.add_argument("--data_file_root", "-f", type=str, default="iwdata",
help="Folder with data files (see application.properties' es.ucm.fdi.base-path)")
args = parser.parse_args()
try:
main(args.credentials, args.db_file_root, args.data_file_root)
except Exception as e:
print(f"Aborting due to error: {e}")
sys.exit(1)
sys.exit(0)