cicada 0.8.3__tar.gz → 0.9.0__tar.gz

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.
Files changed (37) hide show
  1. {cicada-0.8.3 → cicada-0.9.0}/PKG-INFO +15 -6
  2. {cicada-0.8.3 → cicada-0.9.0}/README.md +2 -2
  3. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/exec_schedule.py +2 -4
  4. {cicada-0.8.3 → cicada-0.9.0}/cicada/lib/scheduler.py +9 -0
  5. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/PKG-INFO +15 -6
  6. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/requires.txt +2 -2
  7. {cicada-0.8.3 → cicada-0.9.0}/setup.py +3 -3
  8. cicada-0.9.0/tests/test_lib_scheduler.py +218 -0
  9. cicada-0.8.3/tests/test_lib_scheduler.py +0 -58
  10. {cicada-0.8.3 → cicada-0.9.0}/LICENSE +0 -0
  11. {cicada-0.8.3 → cicada-0.9.0}/cicada/__init__.py +0 -0
  12. {cicada-0.8.3 → cicada-0.9.0}/cicada/cli.py +0 -0
  13. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/__init__.py +0 -0
  14. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/archive_schedule_log.py +0 -0
  15. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/delete_schedule.py +0 -0
  16. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/exec_server_schedules.py +0 -0
  17. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/list_schedules.py +0 -0
  18. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/list_server_schedules.py +0 -0
  19. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/ping_slack.py +0 -0
  20. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/register_server.py +0 -0
  21. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/show_schedule.py +0 -0
  22. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/spread_schedules.py +0 -0
  23. {cicada-0.8.3 → cicada-0.9.0}/cicada/commands/upsert_schedule.py +0 -0
  24. {cicada-0.8.3 → cicada-0.9.0}/cicada/lib/__init__.py +0 -0
  25. {cicada-0.8.3 → cicada-0.9.0}/cicada/lib/postgres.py +0 -0
  26. {cicada-0.8.3 → cicada-0.9.0}/cicada/lib/utils.py +0 -0
  27. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/SOURCES.txt +0 -0
  28. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/dependency_links.txt +0 -0
  29. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/entry_points.txt +0 -0
  30. {cicada-0.8.3 → cicada-0.9.0}/cicada.egg-info/top_level.txt +0 -0
  31. {cicada-0.8.3 → cicada-0.9.0}/pyproject.toml +0 -0
  32. {cicada-0.8.3 → cicada-0.9.0}/setup.cfg +0 -0
  33. {cicada-0.8.3 → cicada-0.9.0}/tests/test_functional_archive_logs.py +0 -0
  34. {cicada-0.8.3 → cicada-0.9.0}/tests/test_functional_cli_entrypoint.py +0 -0
  35. {cicada-0.8.3 → cicada-0.9.0}/tests/test_functional_main.py +0 -0
  36. {cicada-0.8.3 → cicada-0.9.0}/tests/test_functional_spread_schedules.py +0 -0
  37. {cicada-0.8.3 → cicada-0.9.0}/tests/test_lib_postgres.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cicada
3
- Version: 0.8.3
3
+ Version: 0.9.0
4
4
  Summary: Lightweight, agent-based, distributed scheduler
5
5
  Home-page: https://github.com/transferwise/cicada
6
6
  Author: Wise
@@ -10,9 +10,9 @@ Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: psycopg2-binary==2.9.*
12
12
  Requires-Dist: pyyaml==6.0.*
13
- Requires-Dist: croniter==2.0.*
13
+ Requires-Dist: croniter<3.1,>=2.0
14
14
  Requires-Dist: tabulate==0.9.*
15
- Requires-Dist: slack-sdk==3.31.*
15
+ Requires-Dist: slack-sdk==3.37.*
16
16
  Requires-Dist: backoff==2.2.*
17
17
  Provides-Extra: dev
18
18
  Requires-Dist: pytest==8.2.*; extra == "dev"
@@ -21,6 +21,15 @@ Requires-Dist: pytest-mock==3.14.*; extra == "dev"
21
21
  Requires-Dist: black==24.4.*; extra == "dev"
22
22
  Requires-Dist: flake8==7.1.*; extra == "dev"
23
23
  Requires-Dist: freezegun==1.5.*; extra == "dev"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: provides-extra
31
+ Dynamic: requires-dist
32
+ Dynamic: summary
24
33
 
25
34
  # Cicada scheduler
26
35
 
@@ -58,7 +67,7 @@ Requires-Dist: freezegun==1.5.*; extra == "dev"
58
67
 
59
68
  ## Setup Central Database
60
69
 
61
- Verified on **PostgreSQL** versions *9.6* to *12.9*
70
+ Verified on **PostgreSQL** versions *12.9* to *15.14*
62
71
 
63
72
  1. Execute as **postgres** user [setup/db_and_user.sql](setup/db_and_user.sql)
64
73
  2. Change **cicada** user password
@@ -69,7 +78,7 @@ Verified on **PostgreSQL** versions *9.6* to *12.9*
69
78
 
70
79
  ## Setup Node
71
80
 
72
- Verified on *Ubuntu 18.04 and 20.04 LTS*
81
+ Verified on *Ubuntu 18.04, 20.04 LTS and 22.04 LTS*
73
82
 
74
83
  Prerequisites
75
84
 
@@ -34,7 +34,7 @@
34
34
 
35
35
  ## Setup Central Database
36
36
 
37
- Verified on **PostgreSQL** versions *9.6* to *12.9*
37
+ Verified on **PostgreSQL** versions *12.9* to *15.14*
38
38
 
39
39
  1. Execute as **postgres** user [setup/db_and_user.sql](setup/db_and_user.sql)
40
40
  2. Change **cicada** user password
@@ -45,7 +45,7 @@ Verified on **PostgreSQL** versions *9.6* to *12.9*
45
45
 
46
46
  ## Setup Node
47
47
 
48
- Verified on *Ubuntu 18.04 and 20.04 LTS*
48
+ Verified on *Ubuntu 18.04, 20.04 LTS and 22.04 LTS*
49
49
 
50
50
  Prerequisites
51
51
 
@@ -155,11 +155,9 @@ def main(schedule_id, dbname=None):
155
155
  command = str(row[0])
156
156
  parameters = str(row[1])
157
157
 
158
- full_command = []
159
- full_command.extend(command.split())
160
- full_command.extend(parameters.split())
158
+ full_command = scheduler.get_full_command(command, parameters)
161
159
 
162
- human_full_command = str(" ".join(full_command))
160
+ human_full_command = str(command + " " + parameters)
163
161
 
164
162
  # Check to see that schedule is not already running
165
163
  if get_is_running(db_cur, schedule_id) == 0:
@@ -11,6 +11,7 @@ import sys
11
11
  import os
12
12
  import datetime
13
13
  import socket
14
+ import shlex
14
15
  from croniter import croniter
15
16
 
16
17
  from cicada.lib import postgres
@@ -252,6 +253,14 @@ def get_schedule_executable(db_cur, schedule_id):
252
253
  return obj_schedule_executable
253
254
 
254
255
 
256
+ def get_full_command(command, parameters):
257
+ """Generate Full Command"""
258
+ full_command = []
259
+ full_command.extend(shlex.split(command))
260
+ full_command.extend(shlex.split(parameters))
261
+ return full_command
262
+
263
+
255
264
  def get_all_schedules(db_cur, server_id, is_async):
256
265
  """Extract all candidate schedules for a server
257
266
  +--------- minute (0 - 59)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cicada
3
- Version: 0.8.3
3
+ Version: 0.9.0
4
4
  Summary: Lightweight, agent-based, distributed scheduler
5
5
  Home-page: https://github.com/transferwise/cicada
6
6
  Author: Wise
@@ -10,9 +10,9 @@ Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: psycopg2-binary==2.9.*
12
12
  Requires-Dist: pyyaml==6.0.*
13
- Requires-Dist: croniter==2.0.*
13
+ Requires-Dist: croniter<3.1,>=2.0
14
14
  Requires-Dist: tabulate==0.9.*
15
- Requires-Dist: slack-sdk==3.31.*
15
+ Requires-Dist: slack-sdk==3.37.*
16
16
  Requires-Dist: backoff==2.2.*
17
17
  Provides-Extra: dev
18
18
  Requires-Dist: pytest==8.2.*; extra == "dev"
@@ -21,6 +21,15 @@ Requires-Dist: pytest-mock==3.14.*; extra == "dev"
21
21
  Requires-Dist: black==24.4.*; extra == "dev"
22
22
  Requires-Dist: flake8==7.1.*; extra == "dev"
23
23
  Requires-Dist: freezegun==1.5.*; extra == "dev"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: provides-extra
31
+ Dynamic: requires-dist
32
+ Dynamic: summary
24
33
 
25
34
  # Cicada scheduler
26
35
 
@@ -58,7 +67,7 @@ Requires-Dist: freezegun==1.5.*; extra == "dev"
58
67
 
59
68
  ## Setup Central Database
60
69
 
61
- Verified on **PostgreSQL** versions *9.6* to *12.9*
70
+ Verified on **PostgreSQL** versions *12.9* to *15.14*
62
71
 
63
72
  1. Execute as **postgres** user [setup/db_and_user.sql](setup/db_and_user.sql)
64
73
  2. Change **cicada** user password
@@ -69,7 +78,7 @@ Verified on **PostgreSQL** versions *9.6* to *12.9*
69
78
 
70
79
  ## Setup Node
71
80
 
72
- Verified on *Ubuntu 18.04 and 20.04 LTS*
81
+ Verified on *Ubuntu 18.04, 20.04 LTS and 22.04 LTS*
73
82
 
74
83
  Prerequisites
75
84
 
@@ -1,8 +1,8 @@
1
1
  psycopg2-binary==2.9.*
2
2
  pyyaml==6.0.*
3
- croniter==2.0.*
3
+ croniter<3.1,>=2.0
4
4
  tabulate==0.9.*
5
- slack-sdk==3.31.*
5
+ slack-sdk==3.37.*
6
6
  backoff==2.2.*
7
7
 
8
8
  [dev]
@@ -7,7 +7,7 @@ with open("README.md") as f:
7
7
 
8
8
  setup(
9
9
  name="cicada",
10
- version="0.8.3",
10
+ version="0.9.0",
11
11
  description="Lightweight, agent-based, distributed scheduler",
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",
@@ -20,9 +20,9 @@ setup(
20
20
  install_requires=[
21
21
  "psycopg2-binary==2.9.*",
22
22
  "pyyaml==6.0.*",
23
- "croniter==2.0.*",
23
+ "croniter>=2.0,<3.1",
24
24
  "tabulate==0.9.*",
25
- "slack-sdk==3.31.*",
25
+ "slack-sdk==3.37.*",
26
26
  "backoff==2.2.*",
27
27
  ],
28
28
  extras_require={
@@ -0,0 +1,218 @@
1
+ """test_lib_scheduler.py"""
2
+
3
+ import socket
4
+
5
+ from cicada.lib import scheduler
6
+
7
+
8
+ def test_get_host_details():
9
+ """test_get_host_details"""
10
+ hostname = socket.gethostname()
11
+ if hostname.find(".") != -1:
12
+ hostname = hostname[: hostname.find(".")]
13
+
14
+ fqdn = socket.getfqdn()
15
+
16
+ ip4_address = socket.gethostbyname(fqdn)
17
+
18
+ host_details = scheduler.get_host_details()
19
+
20
+ assert (
21
+ hostname == host_details["hostname"]
22
+ and fqdn == host_details["fqdn"]
23
+ and ip4_address == host_details["ip4_address"]
24
+ )
25
+
26
+
27
+ def test_generate_exec_schedule_command():
28
+ """test_generate_exec_schedule_command"""
29
+ full_command = scheduler.generate_exec_schedule_command("test_schedule_id")
30
+
31
+ assert full_command == [
32
+ "/opt/cicada/venv/bin/cicada",
33
+ "exec_schedule",
34
+ "--schedule_id=test_schedule_id",
35
+ ]
36
+
37
+
38
+ def test_generate_exec_schedule_command_no_dbname():
39
+ """test_generate_exec_schedule_command_no_dbname"""
40
+ full_command = scheduler.generate_exec_schedule_command("test_schedule_id", None)
41
+
42
+ assert full_command == [
43
+ "/opt/cicada/venv/bin/cicada",
44
+ "exec_schedule",
45
+ "--schedule_id=test_schedule_id",
46
+ ]
47
+
48
+
49
+ def test_generate_exec_schedule_command_with_dbname():
50
+ """test_generate_exec_schedule_command_with_dbname"""
51
+ full_command = scheduler.generate_exec_schedule_command("test_schedule_id", "test_db")
52
+
53
+ assert full_command == [
54
+ "/opt/cicada/venv/bin/cicada",
55
+ "exec_schedule",
56
+ "--schedule_id=test_schedule_id",
57
+ "test_db",
58
+ ]
59
+
60
+
61
+ def test_get_full_command_simple():
62
+ """test_get_full_command with simple command and parameters"""
63
+ command = "sleep"
64
+ parameters = "0.5"
65
+
66
+ result = scheduler.get_full_command(command, parameters)
67
+
68
+ assert result == ["sleep", "0.5"]
69
+
70
+
71
+ def test_get_full_command_no_parameters():
72
+ """test_get_full_command with command only, no parameters"""
73
+ command = "echo"
74
+ parameters = ""
75
+
76
+ result = scheduler.get_full_command(command, parameters)
77
+
78
+ assert result == ["echo"]
79
+
80
+
81
+ def test_get_full_command_multiple_parameters():
82
+ """test_get_full_command with multiple space-separated parameters"""
83
+ command = "echo"
84
+ parameters = "hello world test"
85
+
86
+ result = scheduler.get_full_command(command, parameters)
87
+
88
+ assert result == ["echo", "hello", "world", "test"]
89
+
90
+
91
+ def test_get_full_command_quoted_parameters():
92
+ """test_get_full_command with quoted parameters containing spaces"""
93
+ command = "echo"
94
+ parameters = '"hello world" "foo bar"'
95
+
96
+ result = scheduler.get_full_command(command, parameters)
97
+
98
+ assert result == ["echo", "hello world", "foo bar"]
99
+
100
+
101
+ def test_get_full_command_single_quotes():
102
+ """test_get_full_command with single-quoted parameters"""
103
+ command = "echo"
104
+ parameters = "'hello world' 'test value'"
105
+
106
+ result = scheduler.get_full_command(command, parameters)
107
+
108
+ assert result == ["echo", "hello world", "test value"]
109
+
110
+
111
+ def test_get_full_command_mixed_quotes():
112
+ """test_get_full_command with mixed single and double quotes"""
113
+ command = "echo"
114
+ parameters = "\"double quoted\" 'single quoted' unquoted"
115
+
116
+ result = scheduler.get_full_command(command, parameters)
117
+
118
+ assert result == ["echo", "double quoted", "single quoted", "unquoted"]
119
+
120
+
121
+ def test_get_full_command_with_special_characters():
122
+ """test_get_full_command with special shell characters in quoted strings"""
123
+ command = "echo"
124
+ parameters = '"test$var" "path/to/file" "value&more"'
125
+
126
+ result = scheduler.get_full_command(command, parameters)
127
+
128
+ assert result == ["echo", "test$var", "path/to/file", "value&more"]
129
+
130
+
131
+ def test_get_full_command_with_escaped_quotes():
132
+ """test_get_full_command with escaped quotes inside quoted strings"""
133
+ command = "echo"
134
+ parameters = r'"she said \"hello\""'
135
+
136
+ result = scheduler.get_full_command(command, parameters)
137
+
138
+ assert result == ["echo", 'she said "hello"']
139
+
140
+
141
+ def test_get_full_command_with_equals_signs():
142
+ """test_get_full_command with equals signs in parameters (common in env vars)"""
143
+ command = "env"
144
+ parameters = "VAR1=value1 VAR2=value2"
145
+
146
+ result = scheduler.get_full_command(command, parameters)
147
+
148
+ assert result == ["env", "VAR1=value1", "VAR2=value2"]
149
+
150
+
151
+ def test_get_full_command_with_flags():
152
+ """test_get_full_command with command flags and parameters"""
153
+ command = "python3"
154
+ parameters = "-m pytest --verbose"
155
+
156
+ result = scheduler.get_full_command(command, parameters)
157
+
158
+ assert result == ["python3", "-m", "pytest", "--verbose"]
159
+
160
+
161
+ def test_get_full_command_with_path_spaces():
162
+ """test_get_full_command with file paths containing spaces"""
163
+ command = "cat"
164
+ parameters = '"/path/with spaces/file.txt"'
165
+
166
+ result = scheduler.get_full_command(command, parameters)
167
+
168
+ assert result == ["cat", "/path/with spaces/file.txt"]
169
+
170
+
171
+ def test_get_full_command_command_with_path():
172
+ """test_get_full_command with full path in command"""
173
+ command = "/usr/bin/python3"
174
+ parameters = "script.py arg1 arg2"
175
+
176
+ result = scheduler.get_full_command(command, parameters)
177
+
178
+ assert result == ["/usr/bin/python3", "script.py", "arg1", "arg2"]
179
+
180
+
181
+ def test_get_full_command_whitespace_handling():
182
+ """test_get_full_command properly handles extra whitespace"""
183
+ command = "echo"
184
+ parameters = " hello world "
185
+
186
+ result = scheduler.get_full_command(command, parameters)
187
+
188
+ assert result == ["echo", "hello", "world"]
189
+
190
+
191
+ def test_get_full_command_semicolon_in_quotes():
192
+ """test_get_full_command with semicolon in quotes (should not execute as separate command)"""
193
+ command = "echo"
194
+ parameters = '"command1; command2"'
195
+
196
+ result = scheduler.get_full_command(command, parameters)
197
+
198
+ assert result == ["echo", "command1; command2"]
199
+
200
+
201
+ def test_get_full_command_pipe_in_quotes():
202
+ """test_get_full_command with pipe character in quotes (should not pipe)"""
203
+ command = "echo"
204
+ parameters = '"value1 | value2"'
205
+
206
+ result = scheduler.get_full_command(command, parameters)
207
+
208
+ assert result == ["echo", "value1 | value2"]
209
+
210
+
211
+ def test_get_full_command_ampersand_in_quotes():
212
+ """test_get_full_command with ampersand in quotes (should not background)"""
213
+ command = "echo"
214
+ parameters = '"command & background"'
215
+
216
+ result = scheduler.get_full_command(command, parameters)
217
+
218
+ assert result == ["echo", "command & background"]
@@ -1,58 +0,0 @@
1
- """test_lib_scheduler.py"""
2
-
3
- import socket
4
-
5
- from cicada.lib import scheduler
6
-
7
-
8
- def test_get_host_details():
9
- """test_get_host_details"""
10
- hostname = socket.gethostname()
11
- if hostname.find(".") != -1:
12
- hostname = hostname[: hostname.find(".")]
13
-
14
- fqdn = socket.getfqdn()
15
-
16
- ip4_address = socket.gethostbyname(fqdn)
17
-
18
- host_details = scheduler.get_host_details()
19
-
20
- assert (
21
- hostname == host_details["hostname"]
22
- and fqdn == host_details["fqdn"]
23
- and ip4_address == host_details["ip4_address"]
24
- )
25
-
26
-
27
- def test_generate_exec_schedule_command():
28
- """test_generate_exec_schedule_command"""
29
- full_command = scheduler.generate_exec_schedule_command("test_schedule_id")
30
-
31
- assert full_command == [
32
- "/opt/cicada/venv/bin/cicada",
33
- "exec_schedule",
34
- "--schedule_id=test_schedule_id",
35
- ]
36
-
37
-
38
- def test_generate_exec_schedule_command_no_dbname():
39
- """test_generate_exec_schedule_command_no_dbname"""
40
- full_command = scheduler.generate_exec_schedule_command("test_schedule_id", None)
41
-
42
- assert full_command == [
43
- "/opt/cicada/venv/bin/cicada",
44
- "exec_schedule",
45
- "--schedule_id=test_schedule_id",
46
- ]
47
-
48
-
49
- def test_generate_exec_schedule_command_with_dbname():
50
- """test_generate_exec_schedule_command_with_dbname"""
51
- full_command = scheduler.generate_exec_schedule_command("test_schedule_id", "test_db")
52
-
53
- assert full_command == [
54
- "/opt/cicada/venv/bin/cicada",
55
- "exec_schedule",
56
- "--schedule_id=test_schedule_id",
57
- "test_db",
58
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes