OpenOrchestrator 1.0.2__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OpenOrchestrator/__init__.py +7 -0
- OpenOrchestrator/__main__.py +2 -0
- OpenOrchestrator/common/connection_frame.py +35 -49
- OpenOrchestrator/common/crypto_util.py +4 -4
- OpenOrchestrator/common/datetime_util.py +20 -0
- OpenOrchestrator/database/constants.py +25 -19
- OpenOrchestrator/database/db_util.py +77 -30
- OpenOrchestrator/database/logs.py +13 -0
- OpenOrchestrator/database/queues.py +17 -0
- OpenOrchestrator/database/triggers.py +25 -56
- OpenOrchestrator/orchestrator/application.py +87 -34
- OpenOrchestrator/orchestrator/datetime_input.py +75 -0
- OpenOrchestrator/orchestrator/popups/constant_popup.py +87 -69
- OpenOrchestrator/orchestrator/popups/credential_popup.py +92 -82
- OpenOrchestrator/orchestrator/popups/generic_popups.py +27 -0
- OpenOrchestrator/orchestrator/popups/trigger_popup.py +216 -0
- OpenOrchestrator/orchestrator/tabs/constants_tab.py +52 -0
- OpenOrchestrator/orchestrator/tabs/logging_tab.py +70 -0
- OpenOrchestrator/orchestrator/tabs/queue_tab.py +116 -0
- OpenOrchestrator/orchestrator/tabs/settings_tab.py +22 -0
- OpenOrchestrator/orchestrator/tabs/trigger_tab.py +87 -0
- OpenOrchestrator/scheduler/application.py +3 -3
- OpenOrchestrator/scheduler/connection_frame.py +96 -0
- OpenOrchestrator/scheduler/run_tab.py +87 -80
- OpenOrchestrator/scheduler/runner.py +33 -25
- OpenOrchestrator/scheduler/settings_tab.py +2 -1
- {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/METADATA +2 -2
- OpenOrchestrator-1.2.0.dist-info/RECORD +38 -0
- OpenOrchestrator/orchestrator/constants_tab.py +0 -169
- OpenOrchestrator/orchestrator/logging_tab.py +0 -221
- OpenOrchestrator/orchestrator/popups/queue_trigger_popup.py +0 -129
- OpenOrchestrator/orchestrator/popups/scheduled_trigger_popup.py +0 -129
- OpenOrchestrator/orchestrator/popups/single_trigger_popup.py +0 -134
- OpenOrchestrator/orchestrator/settings_tab.py +0 -31
- OpenOrchestrator/orchestrator/table_util.py +0 -76
- OpenOrchestrator/orchestrator/trigger_tab.py +0 -231
- OpenOrchestrator-1.0.2.dist-info/RECORD +0 -36
- {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/LICENSE +0 -0
- {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/WHEEL +0 -0
- {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""This module contains a single class: ConnectionFrame."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tkinter
|
|
5
|
+
from tkinter import ttk, messagebox
|
|
6
|
+
|
|
7
|
+
from OpenOrchestrator.common import crypto_util
|
|
8
|
+
from OpenOrchestrator.database import db_util
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# pylint: disable-next=too-many-ancestors
|
|
12
|
+
class ConnectionFrame(ttk.Frame):
|
|
13
|
+
"""A ttk.Frame object that contains two ttk.Entry and
|
|
14
|
+
two ttk.Button used to enter a connection string and
|
|
15
|
+
encryption key and to connect to the database.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, parent: tkinter.Widget):
|
|
18
|
+
super().__init__(parent)
|
|
19
|
+
|
|
20
|
+
frame = ttk.Frame(self)
|
|
21
|
+
frame.columnconfigure(1, weight=1)
|
|
22
|
+
frame.pack(fill='both')
|
|
23
|
+
|
|
24
|
+
ttk.Label(frame, text="Connection string:").grid(row=0, column=0, sticky='w')
|
|
25
|
+
|
|
26
|
+
self.conn_entry = ttk.Entry(frame)
|
|
27
|
+
self.conn_entry.grid(row=0, column=1, sticky='ew')
|
|
28
|
+
|
|
29
|
+
self.conn_button = ttk.Button(frame, text="Connect", command=self._connect)
|
|
30
|
+
self.conn_button.grid(row=0, column=2, sticky='e')
|
|
31
|
+
|
|
32
|
+
ttk.Label(frame, text="Encryption key:").grid(row=1, column=0, sticky='w')
|
|
33
|
+
|
|
34
|
+
self.key_entry = ttk.Entry(frame)
|
|
35
|
+
self.key_entry.grid(row=1, column=1, sticky='ew')
|
|
36
|
+
|
|
37
|
+
self.disconn_button = ttk.Button(frame, text="Disconnect", command=self._disconnect, state='disabled')
|
|
38
|
+
self.disconn_button.grid(row=1, column=2, sticky='e')
|
|
39
|
+
|
|
40
|
+
self._initial_connect()
|
|
41
|
+
|
|
42
|
+
def _connect(self) -> None:
|
|
43
|
+
"""Validate the connection string and encryption key
|
|
44
|
+
and connect to the database.
|
|
45
|
+
"""
|
|
46
|
+
conn_string = self.conn_entry.get()
|
|
47
|
+
crypto_key = self.key_entry.get()
|
|
48
|
+
|
|
49
|
+
if not crypto_util.validate_key(crypto_key):
|
|
50
|
+
messagebox.showerror("Invalid encryption key", "The entered encryption key is not a valid AES key.")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if db_util.connect(conn_string):
|
|
54
|
+
crypto_util.set_key(crypto_key)
|
|
55
|
+
self._set_state(True)
|
|
56
|
+
|
|
57
|
+
def _disconnect(self) -> None:
|
|
58
|
+
db_util.disconnect()
|
|
59
|
+
crypto_util.set_key(None)
|
|
60
|
+
self._set_state(False)
|
|
61
|
+
|
|
62
|
+
def _set_state(self, connected: bool) -> None:
|
|
63
|
+
if connected:
|
|
64
|
+
self.conn_entry.configure(state='disabled')
|
|
65
|
+
self.key_entry.configure(state='disabled')
|
|
66
|
+
self.conn_button.configure(state='disabled')
|
|
67
|
+
self.disconn_button.configure(state='normal')
|
|
68
|
+
else:
|
|
69
|
+
self.conn_entry.configure(state='normal')
|
|
70
|
+
self.key_entry.configure(state='normal')
|
|
71
|
+
self.conn_button.configure(state='normal')
|
|
72
|
+
self.disconn_button.configure(state='disabled')
|
|
73
|
+
|
|
74
|
+
def _initial_connect(self) -> None:
|
|
75
|
+
"""Check the environment for a connection string
|
|
76
|
+
and encryption key and connect to the database if both
|
|
77
|
+
are found.
|
|
78
|
+
"""
|
|
79
|
+
conn_string = os.environ.get('OpenOrchestratorConnString', None)
|
|
80
|
+
if conn_string:
|
|
81
|
+
self.conn_entry.insert(0, conn_string)
|
|
82
|
+
|
|
83
|
+
crypto_key = os.environ.get('OpenOrchestratorKey', None)
|
|
84
|
+
if crypto_key:
|
|
85
|
+
self.key_entry.insert(0, crypto_key)
|
|
86
|
+
|
|
87
|
+
if conn_string and crypto_key:
|
|
88
|
+
self._connect()
|
|
89
|
+
|
|
90
|
+
def new_key(self):
|
|
91
|
+
"""Creates a new encryption key and inserts it
|
|
92
|
+
into the key entry.
|
|
93
|
+
"""
|
|
94
|
+
key = crypto_util.generate_key()
|
|
95
|
+
self.key_entry.delete(0, 'end')
|
|
96
|
+
self.key_entry.insert(0, key)
|
|
@@ -1,115 +1,123 @@
|
|
|
1
1
|
"""This module is responsible for the layout and functionality of the run tab
|
|
2
2
|
in Scheduler."""
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
4
7
|
import tkinter
|
|
5
8
|
from tkinter import ttk
|
|
6
9
|
import sys
|
|
7
10
|
|
|
11
|
+
from sqlalchemy import exc as alc_exc
|
|
12
|
+
|
|
8
13
|
from OpenOrchestrator.common import crypto_util
|
|
9
14
|
from OpenOrchestrator.database import db_util
|
|
10
15
|
from OpenOrchestrator.scheduler import runner
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from OpenOrchestrator.scheduler.application import Application
|
|
14
19
|
|
|
15
|
-
Args:
|
|
16
|
-
parent: The ttk.Notebook object that this tab is a child of.
|
|
17
|
-
app: The Scheduler application object.
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
ttk.Frame: The created tab object as a ttk.Frame.
|
|
21
|
-
"""
|
|
22
|
-
tab = ttk.Frame(parent)
|
|
23
|
-
tab.pack(fill='both', expand=True)
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
# pylint: disable-next=too-many-ancestors
|
|
22
|
+
class RunTab(ttk.Frame):
|
|
23
|
+
"""A ttk.frame object containing the functionality of the run tab in Scheduler."""
|
|
24
|
+
def __init__(self, parent: ttk.Notebook, app: Application):
|
|
25
|
+
super().__init__(parent)
|
|
26
|
+
self.pack(fill='both', expand=True)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
ttk.Button(tab, text="Pause", command=lambda: pause(app, status_label)).pack()
|
|
28
|
+
self.app = app
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
s = ttk.Style()
|
|
31
|
+
s.configure('my.TButton', font=('Helvetica Bold', 24))
|
|
32
|
+
self.button = ttk.Button(self, text="Run", command=self.button_click, style='my.TButton')
|
|
33
|
+
self.button.pack()
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# Text area
|
|
36
|
+
text_frame = tkinter.Frame(self)
|
|
37
|
+
text_frame.pack()
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
text_yscroll.pack(side='right', fill='y')
|
|
40
|
-
text_area.configure(yscrollcommand=text_yscroll.set)
|
|
39
|
+
self.text_area = tkinter.Text(text_frame, state='disabled', wrap='none')
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
text_area.configure(xscrollcommand=text_xscroll.set)
|
|
41
|
+
# Redirect stdout to the text area instead of console
|
|
42
|
+
sys.stdout.write = self.print_text
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
# Add scroll bars to text area
|
|
45
|
+
text_yscroll = ttk.Scrollbar(text_frame, orient='vertical', command=self.text_area.yview)
|
|
46
|
+
text_yscroll.pack(side='right', fill='y')
|
|
47
|
+
self.text_area.configure(yscrollcommand=text_yscroll.set)
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
text_xscroll = ttk.Scrollbar(text_frame, orient='horizontal', command=self.text_area.xview)
|
|
50
|
+
text_xscroll.pack(side='bottom', fill='x')
|
|
51
|
+
self.text_area.configure(xscrollcommand=text_xscroll.set)
|
|
49
52
|
|
|
53
|
+
self.text_area.pack()
|
|
50
54
|
|
|
51
|
-
def
|
|
52
|
-
|
|
55
|
+
def button_click(self):
|
|
56
|
+
"""Callback for when the run/pause button is clicked."""
|
|
57
|
+
if self.app.running:
|
|
58
|
+
self.pause()
|
|
59
|
+
else:
|
|
60
|
+
self.run()
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
def pause(self):
|
|
63
|
+
"""Stops the Scheduler and sets the app's status to 'paused'."""
|
|
64
|
+
self.button.configure(text="Run")
|
|
65
|
+
print('Paused... Please wait for all processes to stop before closing the application\n')
|
|
66
|
+
self.app.running = False
|
|
67
|
+
|
|
68
|
+
def run(self):
|
|
69
|
+
"""Starts the Scheduler and sets the app's status to 'running'."""
|
|
70
|
+
if db_util.get_conn_string() is None:
|
|
71
|
+
print("Can't start without a valid connection string. Go to the settings tab to configure the connection string")
|
|
72
|
+
return
|
|
73
|
+
if crypto_util.get_key() is None:
|
|
74
|
+
print("Can't start without a valid encryption key. Go to the settings tab to configure the encryption key")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
self.button.configure(text="Pause")
|
|
67
78
|
print('Running...\n')
|
|
68
|
-
app.running = True
|
|
69
|
-
|
|
70
|
-
# Only start loop if it's not already running
|
|
71
|
-
if app.tk.call('after', 'info') == '':
|
|
72
|
-
app.after(0, loop, app)
|
|
79
|
+
self.app.running = True
|
|
73
80
|
|
|
81
|
+
# Only start a new loop if it's not already running
|
|
82
|
+
if self.app.tk.call('after', 'info') == '':
|
|
83
|
+
self.app.after(0, loop, self.app)
|
|
74
84
|
|
|
75
|
-
def
|
|
76
|
-
|
|
85
|
+
def print_text(self, text: str) -> None:
|
|
86
|
+
"""Appends text to the text area.
|
|
87
|
+
Is used to replace the functionality of sys.stdout.write (print).
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
print('Paused... Please wait for all processes to stop before closing the application\n')
|
|
85
|
-
app.running = False
|
|
89
|
+
Args:
|
|
90
|
+
string: The string to append.
|
|
91
|
+
"""
|
|
92
|
+
# Insert text at the end
|
|
93
|
+
self.text_area.configure(state='normal')
|
|
94
|
+
self.text_area.insert('end', text)
|
|
86
95
|
|
|
96
|
+
# If the number of lines are above 1000 delete 10 lines from the top
|
|
97
|
+
num_lines = int(self.text_area.index('end').split('.', maxsplit=1)[0])
|
|
98
|
+
if num_lines > 1000:
|
|
99
|
+
self.text_area.delete("1.0", "10.0")
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Args:
|
|
93
|
-
print_text: The text area object.
|
|
94
|
-
string: The string to append.
|
|
95
|
-
"""
|
|
96
|
-
text_widget.configure(state='normal')
|
|
97
|
-
text_widget.insert('end', string)
|
|
98
|
-
text_widget.see('end')
|
|
99
|
-
text_widget.configure(state='disabled')
|
|
101
|
+
# Scroll to end
|
|
102
|
+
self.text_area.see('end')
|
|
103
|
+
self.text_area.configure(state='disabled')
|
|
100
104
|
|
|
101
105
|
|
|
102
|
-
def loop(app) -> None:
|
|
106
|
+
def loop(app: Application) -> None:
|
|
103
107
|
"""The main loop function of the Scheduler.
|
|
104
108
|
Checks heartbeats, check triggers, and schedules the next loop.
|
|
105
109
|
|
|
106
110
|
Args:
|
|
107
111
|
app: The Scheduler Application object.
|
|
108
112
|
"""
|
|
109
|
-
|
|
113
|
+
try:
|
|
114
|
+
check_heartbeats(app)
|
|
115
|
+
|
|
116
|
+
if app.running:
|
|
117
|
+
check_triggers(app)
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
except alc_exc.OperationalError:
|
|
120
|
+
print("Couldn't connect to database.")
|
|
113
121
|
|
|
114
122
|
if len(app.running_jobs) == 0:
|
|
115
123
|
print("Doing cleanup...")
|
|
@@ -123,7 +131,7 @@ def loop(app) -> None:
|
|
|
123
131
|
print("Scheduler is paused and no more processes are running.")
|
|
124
132
|
|
|
125
133
|
|
|
126
|
-
def check_heartbeats(app) -> None:
|
|
134
|
+
def check_heartbeats(app: Application) -> None:
|
|
127
135
|
"""Check if any running jobs are still running, failed or done.
|
|
128
136
|
|
|
129
137
|
Args:
|
|
@@ -132,27 +140,26 @@ def check_heartbeats(app) -> None:
|
|
|
132
140
|
print('Checking heartbeats...')
|
|
133
141
|
for job in app.running_jobs:
|
|
134
142
|
if job.process.poll() is not None:
|
|
135
|
-
app.running_jobs.remove(job)
|
|
136
|
-
|
|
137
143
|
if job.process.returncode == 0:
|
|
138
144
|
print(f"Process '{job.trigger.process_name}' is done")
|
|
139
145
|
runner.end_job(job)
|
|
140
146
|
else:
|
|
141
|
-
print(f"Process '{job.trigger.process_name}' failed")
|
|
147
|
+
print(f"Process '{job.trigger.process_name}' failed. Check process log for more info.")
|
|
142
148
|
runner.fail_job(job)
|
|
143
149
|
|
|
150
|
+
app.running_jobs.remove(job)
|
|
144
151
|
else:
|
|
145
152
|
print(f"Process '{job.trigger.process_name}' is still running")
|
|
146
153
|
|
|
147
154
|
|
|
148
|
-
def check_triggers(app) -> None:
|
|
155
|
+
def check_triggers(app: Application) -> None:
|
|
149
156
|
"""Checks any process is blocking
|
|
150
157
|
and if not checks if any trigger should be run.
|
|
151
158
|
|
|
152
159
|
Args:
|
|
153
160
|
app: The Scheduler Application object.
|
|
154
161
|
"""
|
|
155
|
-
#Check if process is blocking
|
|
162
|
+
# Check if process is blocking
|
|
156
163
|
blocking = False
|
|
157
164
|
for job in app.running_jobs:
|
|
158
165
|
if job.trigger.is_blocking:
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
"""This module is responsible for checking triggers and running processes."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from datetime import datetime
|
|
5
4
|
import subprocess
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
import uuid
|
|
8
7
|
|
|
9
|
-
from croniter import croniter
|
|
10
|
-
|
|
11
8
|
from OpenOrchestrator.common import crypto_util
|
|
12
9
|
from OpenOrchestrator.database import db_util
|
|
13
10
|
from OpenOrchestrator.database.triggers import Trigger, SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerStatus
|
|
14
11
|
from OpenOrchestrator.database.logs import LogLevel
|
|
15
12
|
|
|
13
|
+
|
|
16
14
|
@dataclass
|
|
17
15
|
class Job():
|
|
18
16
|
"""An object that holds information about a running job."""
|
|
19
17
|
process: subprocess.Popen
|
|
20
18
|
trigger: Trigger
|
|
19
|
+
process_folder: str
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
def poll_triggers(app) -> Job | None:
|
|
@@ -68,10 +67,7 @@ def run_single_trigger(trigger: SingleTrigger) -> Job | None:
|
|
|
68
67
|
print('Running trigger: ', trigger.trigger_name)
|
|
69
68
|
|
|
70
69
|
if db_util.begin_single_trigger(trigger.id):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if process is not None:
|
|
74
|
-
return Job(process, trigger)
|
|
70
|
+
return run_process(trigger)
|
|
75
71
|
|
|
76
72
|
return None
|
|
77
73
|
|
|
@@ -89,13 +85,8 @@ def run_scheduled_trigger(trigger: ScheduledTrigger) -> Job | None:
|
|
|
89
85
|
"""
|
|
90
86
|
print('Running trigger: ', trigger.trigger_name)
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if db_util.begin_scheduled_trigger(trigger.id, next_run):
|
|
95
|
-
process = run_process(trigger)
|
|
96
|
-
|
|
97
|
-
if process is not None:
|
|
98
|
-
return Job(process, trigger)
|
|
88
|
+
if db_util.begin_scheduled_trigger(trigger.id):
|
|
89
|
+
return run_process(trigger)
|
|
99
90
|
|
|
100
91
|
return None
|
|
101
92
|
|
|
@@ -113,10 +104,7 @@ def run_queue_trigger(trigger: QueueTrigger) -> Job | None:
|
|
|
113
104
|
print('Running trigger: ', trigger.trigger_name)
|
|
114
105
|
|
|
115
106
|
if db_util.begin_queue_trigger(trigger.id):
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if process is not None:
|
|
119
|
-
return Job(process, trigger)
|
|
107
|
+
return run_process(trigger)
|
|
120
108
|
|
|
121
109
|
return None
|
|
122
110
|
|
|
@@ -146,8 +134,7 @@ def clone_git_repo(repo_url: str) -> str:
|
|
|
146
134
|
def clear_repo_folder() -> None:
|
|
147
135
|
"""Completely remove the repos folder."""
|
|
148
136
|
repo_folder = get_repo_folder_path()
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
clear_folder(repo_folder)
|
|
151
138
|
|
|
152
139
|
|
|
153
140
|
def get_repo_folder_path() -> str:
|
|
@@ -161,6 +148,15 @@ def get_repo_folder_path() -> str:
|
|
|
161
148
|
return repo_path
|
|
162
149
|
|
|
163
150
|
|
|
151
|
+
def clear_folder(folder_path: str) -> None:
|
|
152
|
+
"""Clear a folder on the system.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
folder_path: The folder to remove.
|
|
156
|
+
"""
|
|
157
|
+
subprocess.run(['rmdir', '/s', '/q', folder_path], check=False, shell=True, capture_output=True)
|
|
158
|
+
|
|
159
|
+
|
|
164
160
|
def find_main_file(folder_path: str) -> str:
|
|
165
161
|
"""Finds the file in the given folder with the name 'main.py'.
|
|
166
162
|
The search checks subfolders recursively.
|
|
@@ -198,6 +194,9 @@ def end_job(job: Job) -> None:
|
|
|
198
194
|
elif isinstance(job.trigger, QueueTrigger):
|
|
199
195
|
db_util.set_trigger_status(job.trigger.id, TriggerStatus.IDLE)
|
|
200
196
|
|
|
197
|
+
if job.process_folder:
|
|
198
|
+
clear_folder(job.process_folder)
|
|
199
|
+
|
|
201
200
|
|
|
202
201
|
def fail_job(job: Job) -> None:
|
|
203
202
|
"""Mark a job as failed in the triggers table in the database.
|
|
@@ -206,9 +205,15 @@ def fail_job(job: Job) -> None:
|
|
|
206
205
|
job: The job whose trigger to mark as failed.
|
|
207
206
|
"""
|
|
208
207
|
db_util.set_trigger_status(job.trigger.id, TriggerStatus.FAILED)
|
|
208
|
+
_, error = job.process.communicate()
|
|
209
|
+
error_msg = f"An uncaught error ocurred during the process:\n{error}"
|
|
210
|
+
db_util.create_log(job.trigger.process_name, LogLevel.ERROR, error_msg)
|
|
211
|
+
|
|
212
|
+
if job.process_folder:
|
|
213
|
+
clear_folder(job.process_folder)
|
|
209
214
|
|
|
210
215
|
|
|
211
|
-
def run_process(trigger: Trigger) ->
|
|
216
|
+
def run_process(trigger: Trigger) -> Job | None:
|
|
212
217
|
"""Runs the process of the given trigger with the necessary inputs:
|
|
213
218
|
Process name
|
|
214
219
|
Connection string
|
|
@@ -226,14 +231,15 @@ def run_process(trigger: Trigger) -> subprocess.Popen | None:
|
|
|
226
231
|
trigger: The trigger whose process to run.
|
|
227
232
|
|
|
228
233
|
Returns:
|
|
229
|
-
|
|
234
|
+
Job: A Job object referencing the process if succesful.
|
|
230
235
|
"""
|
|
231
236
|
process_path = trigger.process_path
|
|
237
|
+
folder_path = None
|
|
232
238
|
|
|
233
239
|
try:
|
|
234
240
|
if trigger.is_git_repo:
|
|
235
|
-
|
|
236
|
-
process_path = find_main_file(
|
|
241
|
+
folder_path = clone_git_repo(process_path)
|
|
242
|
+
process_path = find_main_file(folder_path)
|
|
237
243
|
|
|
238
244
|
if not os.path.isfile(process_path):
|
|
239
245
|
raise ValueError(f"The process path didn't point to a file on the system. Path: '{process_path}'")
|
|
@@ -246,7 +252,9 @@ def run_process(trigger: Trigger) -> subprocess.Popen | None:
|
|
|
246
252
|
|
|
247
253
|
command_args = ['python', process_path, trigger.process_name, conn_string, crypto_key, trigger.process_args]
|
|
248
254
|
|
|
249
|
-
|
|
255
|
+
process = subprocess.Popen(command_args, stderr=subprocess.PIPE, text=True) # pylint: disable=consider-using-with
|
|
256
|
+
|
|
257
|
+
return Job(process, trigger, folder_path)
|
|
250
258
|
|
|
251
259
|
# We actually want to catch any exception here
|
|
252
260
|
# pylint: disable=broad-exception-caught
|
|
@@ -3,7 +3,8 @@ in Scheduler."""
|
|
|
3
3
|
|
|
4
4
|
from tkinter import ttk
|
|
5
5
|
|
|
6
|
-
from OpenOrchestrator.
|
|
6
|
+
from OpenOrchestrator.scheduler.connection_frame import ConnectionFrame
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
def create_tab(parent: ttk.Notebook) -> ttk.Frame:
|
|
9
10
|
"""Creates a new Settings tab object.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: OpenOrchestrator
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A package containing OpenOrchestrator and OpenOrchestrator Scheduler
|
|
5
5
|
Author-email: ITK Development <itk-rpa@mkb.aarhus.dk>
|
|
6
6
|
Project-URL: Homepage, https://github.com/itk-dev-rpa/OpenOrchestrator
|
|
@@ -11,11 +11,11 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: tkcalendar >=1.6.1
|
|
15
14
|
Requires-Dist: cryptography >=41.0.3
|
|
16
15
|
Requires-Dist: croniter >=1.4.1
|
|
17
16
|
Requires-Dist: SQLAlchemy >=2.0.22
|
|
18
17
|
Requires-Dist: pyodbc >=4.0.39
|
|
18
|
+
Requires-Dist: nicegui ==1.4.12
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pylint ; extra == 'dev'
|
|
21
21
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
OpenOrchestrator/__init__.py,sha256=i4Ir68mBu7rrqhlEE6Qh_uyble599jaEWCEMf8g58tI,179
|
|
2
|
+
OpenOrchestrator/__main__.py,sha256=Oe3SbWqw3zUhpQpspUaS6mMddXxVvvV-pJbuBU_1GgA,518
|
|
3
|
+
OpenOrchestrator/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
OpenOrchestrator/common/connection_frame.py,sha256=NuRwbEZviOExszhspbETvHcMi7pS4cgOnwQBvAqAZc4,2838
|
|
5
|
+
OpenOrchestrator/common/crypto_util.py,sha256=Yo3WvIJnWtBJvlbvkDRu-HaReUUdaW747PcamCJUHzw,2029
|
|
6
|
+
OpenOrchestrator/common/datetime_util.py,sha256=xNf_LORb67ohR3KfWE_owY15RqAOLJ8DfDPQlMBfsGc,527
|
|
7
|
+
OpenOrchestrator/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
OpenOrchestrator/database/constants.py,sha256=ik6lY6IR8EOyKEg-N70X-0U654i3nFb3u-XWhqbUbJU,2404
|
|
9
|
+
OpenOrchestrator/database/db_util.py,sha256=VViN_V7o2zhKJcxsv-jMQmwecvXRavWgLsBKQZYm61o,27568
|
|
10
|
+
OpenOrchestrator/database/logs.py,sha256=h2BztjmDRhj8TACYN3LK5c9hIqn5xCs9m_LDUdYYHEk,1623
|
|
11
|
+
OpenOrchestrator/database/queues.py,sha256=BFCbUUbo_OOCPNCzRRxDcgoxos_LI__Ypg7t9e3qdUk,2264
|
|
12
|
+
OpenOrchestrator/database/triggers.py,sha256=Qq1lsSNrOYBKXnKhaD5s71YVmviaTxiD8j8kMBc76sg,4004
|
|
13
|
+
OpenOrchestrator/orchestrator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
OpenOrchestrator/orchestrator/application.py,sha256=Sb0rMrlFsvwkXuTLvBLqTUNqFSYjvdrAz3keiQYiUvY,3224
|
|
15
|
+
OpenOrchestrator/orchestrator/datetime_input.py,sha256=1EEkmKxYOQSIE6e-anbjACgbo7SL4wyuhqerdiBIg5w,2571
|
|
16
|
+
OpenOrchestrator/orchestrator/popups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
OpenOrchestrator/orchestrator/popups/constant_popup.py,sha256=B6-xfEYRNNkf4dwbPgjGRuDqfKDL5pa6wUoZJ-A2l4g,3367
|
|
18
|
+
OpenOrchestrator/orchestrator/popups/credential_popup.py,sha256=dIDro77ogBCZEZ8jUXMkenpSBANIfYX-PqVgKvVVbvU,3839
|
|
19
|
+
OpenOrchestrator/orchestrator/popups/generic_popups.py,sha256=wV5isJrwFGUBpDhDpuOzzURwU4NTp4l5mzMDg-KebKw,1061
|
|
20
|
+
OpenOrchestrator/orchestrator/popups/trigger_popup.py,sha256=Ir8Hx-DHtvCu3qm5BkG0uSRTObM9G4fpk-VOfeajelA,9684
|
|
21
|
+
OpenOrchestrator/orchestrator/tabs/constants_tab.py,sha256=JcRIyntrzZBh5Ugkb0QrkBrjI-g9xl2VpbeKhdT6bFM,2478
|
|
22
|
+
OpenOrchestrator/orchestrator/tabs/logging_tab.py,sha256=7HdXAnAMDHGC6Au8FaQ58M8i88oalBBtBAvGvdiRD_0,3527
|
|
23
|
+
OpenOrchestrator/orchestrator/tabs/queue_tab.py,sha256=3JwrQUFr796uDmASDESjal1py18OT-Vq5K_VrnPlHCk,5169
|
|
24
|
+
OpenOrchestrator/orchestrator/tabs/settings_tab.py,sha256=xBMBdgrTxehmbuAk-H3QfEP_zdILzJ3sIgMCii7qlQg,845
|
|
25
|
+
OpenOrchestrator/orchestrator/tabs/trigger_tab.py,sha256=gRwLINuf4wQcFieNZL1grlc5qmPOqGRAI1-TS1za_5Q,4047
|
|
26
|
+
OpenOrchestrator/orchestrator_connection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
OpenOrchestrator/orchestrator_connection/connection.py,sha256=DU552EigOu2xzM3SjY4766BXrDEnDC2jC4Jjy0-9OrU,8706
|
|
28
|
+
OpenOrchestrator/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
OpenOrchestrator/scheduler/application.py,sha256=vjDzW8x0MwS9giFI6UOlhtFqUPHKbsRnrb6-HY8jWOI,1620
|
|
30
|
+
OpenOrchestrator/scheduler/connection_frame.py,sha256=I1Qooso_iqkNhvztQvFgjlC89Lvfqx0s5B6olATqa98,3369
|
|
31
|
+
OpenOrchestrator/scheduler/run_tab.py,sha256=QP-hoZEJAI5A-T7kV1lFCftpTm_e_R3T8I2Fg9BHxnA,5779
|
|
32
|
+
OpenOrchestrator/scheduler/runner.py,sha256=0lI-Xpv4Co617ZvTrRm9pCAZP1-aNg0P-Vy1c1oy7cM,8170
|
|
33
|
+
OpenOrchestrator/scheduler/settings_tab.py,sha256=AQxt5HxPn4sLfj-6GwyAQ8ffJ35D0PHLBVoi8W3tu2A,613
|
|
34
|
+
OpenOrchestrator-1.2.0.dist-info/LICENSE,sha256=4-Kjm-gkbiOLCBYMzsVJZEepdsm2vk8QesNOASvi9mg,1068
|
|
35
|
+
OpenOrchestrator-1.2.0.dist-info/METADATA,sha256=Y0hvXWs-UDhtwmJ4X9YTSGeZwqmxykiulkZleWTBwXw,1938
|
|
36
|
+
OpenOrchestrator-1.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
37
|
+
OpenOrchestrator-1.2.0.dist-info/top_level.txt,sha256=2btKMQESHuRC_ICbCjHTHH_-us2G7CyeskeaSTTL07Y,17
|
|
38
|
+
OpenOrchestrator-1.2.0.dist-info/RECORD,,
|