Skip to content

Commit 2e075b0

Browse files
authored
merge for later work (#208)
* fix: worker-app-runner * fix: doc * fix: todos * fix: fixed default params * chore: comments * fix: small mods
1 parent b011cda commit 2e075b0

3 files changed

Lines changed: 171 additions & 12 deletions

File tree

extensions/business/container_apps/container_app_runner.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,24 @@ def on_command(self, data, **kwargs):
235235
else:
236236
self.P(f"Unknown plugin command: {data}")
237237
return
238+
239+
def on_post_container_start(self):
240+
"""
241+
Lifecycle hook called after the container is started.
242+
Runs commands in the container if specified in the config.
243+
244+
- after the container first start
245+
- after the container is restarted
246+
"""
247+
self.P("Container started, running post-start commands...")
248+
return
249+
238250

239251
def get_setup_commands(self):
252+
"""
253+
TODO: fix the attack vector here, we should not allow arbitrary commands to be run
254+
255+
"""
240256
cfg_setup_commands = self.cfg_setup_commands
241257
setup_commands = []
242258
if isinstance(cfg_setup_commands, str):
@@ -258,7 +274,12 @@ def get_setup_commands(self):
258274

259275
return setup_commands
260276

277+
261278
def get_start_commands(self):
279+
"""
280+
TODO: fix the attack vector here, we should not allow arbitrary commands to be run
281+
282+
"""
262283
cfg_start_commands = self.cfg_start_commands
263284
start_commands = []
264285
if isinstance(cfg_start_commands, str):

extensions/business/container_apps/container_utils.py

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,33 @@ def _container_pull_image(self):
7676
# end if result
7777
self.Pd(f"Image {full_ref} pulled successfully: {result.decode('utf-8', errors='ignore')}", score=30)
7878
return pulled
79+
80+
81+
def _get_default_env_vars(self):
82+
"""
83+
Get the default environment variables for the container.
84+
85+
WARNING: This is a critical method that should be thoroughly reviewed for attack vectors.
86+
87+
Returns:
88+
dict: Default environment variables.
89+
"""
90+
localhost_ip = self.log.get_localhost_ip()
91+
chainstore_peers = getattr(self, 'cfg_chainstore_peers', [])
92+
str_chainstore_peers = self.json_dumps(chainstore_peers)
93+
dct_env = {
94+
"CONTAINER_NAME": self.container_name,
95+
"EE_CONTAINER_NAME": self.container_name,
96+
"EE_HOST_IP": localhost_ip,
97+
"EE_HOST_ID": self.ee_id,
98+
"EE_HOST_ADDR": self.ee_addr,
99+
"EE_HOST_ETH_ADDR": self.bc.eth_address,
100+
"EE_CHAINSTORE_API_URL": f"http://{localhost_ip}:31234",
101+
"EE_R1FS_API_URL": f"http://{localhost_ip}:31235",
102+
"EE_CHAINSTORE_PEERS": str_chainstore_peers,
103+
}
104+
105+
return dct_env
79106

80107

81108
def _get_container_run_command(self):
@@ -110,18 +137,10 @@ def _get_container_run_command(self):
110137

111138
for key, val in self.dynamic_env.items():
112139
cmd += ["-e", f"{key}={val}"]
113-
114-
cmd += ["-e", f"CONTAINER_NAME={self.container_name}"]
115-
116-
# TODO: check if this is a potential security issue (host is a container itself but we need to make sure)
117-
host_ip = self._setup_dynamic_env_var_host_ip()
118-
cmd += ["-e", f"EE_HOST_IP={host_ip}"]
119-
cmd += ["-e", f"EE_CHAINSTORE_API_URL=http://{self._setup_dynamic_env_var_host_ip()}:31234"]
120-
cmd += ["-e", f"EE_R1FS_API_URL=http://{self._setup_dynamic_env_var_host_ip()}:31235"]
121-
122-
chainstore_peers = getattr(self, 'cfg_chainstore_peers', [])
123-
cmd += ["-e", f"EE_CHAINSTORE_PEERS='{self.json_dumps(chainstore_peers)}'"]
124140

141+
# now add the default env vars
142+
for key, val in self._get_default_env_vars().items():
143+
cmd += ["-e", f"{key}={val}"]
125144

126145
# Volume mounts
127146
if len(self.volumes) > 0:
@@ -243,15 +262,21 @@ def _restart_container(self):
243262
self._reload_server()
244263
self.container_id = None
245264
self.container_start_time = self.time() # Reset the start time after restart
265+
return
246266

247267
def _maybe_set_container_id_and_show_app_info(self):
248268
if self.container_id is None:
269+
# this is the first time we are starting the container, so we need to get its ID
249270
container_id = self._get_container_id()
250271
if container_id:
251272
self.container_id = container_id
252273
self.P(f"Container ID set to: {self.container_id}")
274+
self.on_post_container_start() # Call the lifecycle hoo
253275
self._maybe_send_plugin_start_confirmation()
254276
self._show_container_app_info()
277+
#endif
278+
#endif
279+
return
255280

256281
def _maybe_send_plugin_start_confirmation(self):
257282
"""
@@ -352,4 +377,24 @@ def _show_container_app_info(self):
352377
msg += f" CLI Tool: {self.cli_tool}\n"
353378
self.P(msg)
354379
return
355-
## END CONTAINER MIXIN ###
380+
381+
382+
def _run_command_in_container(self, command):
383+
"""
384+
Run a command inside the container.
385+
386+
Args:
387+
command (str): The command to run inside the container.
388+
"""
389+
if not self.container_id:
390+
self.P("Container ID is not set. Cannot run command.")
391+
return
392+
393+
cmd = [self.cli_tool, "exec", "-i", self.container_id] + command.split()
394+
try:
395+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
396+
self.P(f"Command output: {result.stdout}")
397+
except subprocess.CalledProcessError as e:
398+
self.P(f"Error running command in container: {e.stderr}", color='r')
399+
400+
## END CONTAINER MIXIN ###
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
DRAFT worker_app_runner.py
3+
4+
5+
TODO:
6+
- add git clone command after each container is started
7+
- OBS: cannot use the parent already existing git helper functions as they deploy in the Edge Node host not in the container.
8+
- use `remote_commit = self.git_get_last_commit_hash(repo_url=url, user=username, token=token)` to check if the repo is at the latest version
9+
- save latest commit hash in plugin state
10+
- if last version restart the container and re-run the clone & build-and-run commands
11+
12+
- (fix running the build and run commands)
13+
14+
"""
15+
16+
from .container_app_runner import ContainerAppRunnerPlugin as BasePlugin
17+
18+
__VER__ = "0.0.1"
19+
20+
_CONFIG = {
21+
**BasePlugin.CONFIG,
22+
23+
"IMAGE": "node:lts-alpine", # default image to run
24+
25+
"GIT_URL" : None, # clone mandatory url of the git repository
26+
27+
"BUILD_AND_RUN_COMMANDS" : [],
28+
29+
"CR_DATA": { # dict of container registry data
30+
"SERVER": 'docker.io', # Optional container registry URL
31+
"USERNAME": None, # Optional registry username
32+
"PASSWORD": None, # Optional registry password or token
33+
},
34+
35+
'VALIDATION_RULES': {
36+
**BasePlugin.CONFIG['VALIDATION_RULES'],
37+
},
38+
}
39+
40+
class WorkerAppRunnerPlugin(
41+
BasePlugin
42+
):
43+
"""
44+
A Ratio1 plugin to run a single Docker/Podman container.
45+
46+
This plugin:
47+
- Does the same job, as ContainerAppRunner, except:
48+
- Mapping the volumes is done directly, you can map any path to your container path
49+
- Runs only on oracles.
50+
- Can't be deployed via Deeploy
51+
"""
52+
53+
CONFIG = _CONFIG
54+
55+
56+
def on_init(self):
57+
super(WorkerAppRunnerPlugin, self).on_init()
58+
59+
return
60+
61+
def __run_commands_in_container(self):
62+
for container_command in self.cfg_build_and_run_commands:
63+
if container_command:
64+
self.log.info(f"Running command in container: {container_command}")
65+
self._run_command_in_container(container_command) # this is from container utils.
66+
else:
67+
self.log.warning("Empty command found in build and run commands, skipping.")
68+
return
69+
70+
71+
def on_post_container_start(self):
72+
"""
73+
Lifecycle hook called after the container has started.
74+
Runs any build and run commands specified in the configuration.
75+
"""
76+
super(WorkerAppRunnerPlugin, self).on_post_container_start()
77+
78+
self.__run_commands_in_container()
79+
80+
return
81+
82+
def on_close(self):
83+
"""
84+
Lifecycle hook called when plugin is stopping.
85+
Ensures container is shut down and logs are saved.
86+
Ensures the log process is killed.
87+
Stops tunnel if started.
88+
"""
89+
super(WorkerAppRunnerPlugin, self).on_close()
90+
91+
def process(self):
92+
super(WorkerAppRunnerPlugin, self).process()
93+
return

0 commit comments

Comments
 (0)