devstack-cli 11.0.86__py3-none-any.whl → 11.0.88__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.
cli.py CHANGED
@@ -57,6 +57,30 @@ logger.addHandler(rich.logging.RichHandler())
57
57
  json_logger = logging.getLogger('cli-json')
58
58
  json_logger.addHandler(rich.logging.RichHandler(highlighter=rich.highlighter.JSONHighlighter()))
59
59
 
60
+ def is_valid_username(username: str) -> bool:
61
+ return re.match(r'^[a-zA-Z0-9]+[a-zA-Z0-9_.-]*$', username) is not None
62
+
63
+ def is_valid_hostname(hostname: str) -> bool:
64
+ return re.match(r'^([a-zA-Z0-9][a-zA-Z0-9-]*\.)*([a-zA-Z0-9][a-zA-Z0-9-]*)$', hostname) is not None
65
+
66
+ def is_valid_path(path: str) -> bool:
67
+ return re.match(r'^[a-zA-Z0-9/_.,\-+ ]+$', path) is not None
68
+
69
+ def ensure_valid_username(username: str) -> str:
70
+ if not is_valid_username(username):
71
+ raise ValueError(f'Invalid username: "{username}". Username must start with an alphanumeric character, and contain only alphanumeric characters, dots, underscores and hyphens.')
72
+ return username
73
+
74
+ def ensure_valid_hostname(hostname: str) -> str:
75
+ if not is_valid_hostname(hostname):
76
+ raise ValueError(f'Invalid hostname: "{hostname}". Hostname must start with an alphanumeric character, and contain only alphanumeric characters, dots, and hyphens.')
77
+ return hostname
78
+
79
+ def ensure_valid_path(path: str) -> str:
80
+ if not is_valid_path(path):
81
+ raise ValueError(f'Invalid path: "{path}". Path must start with an alphanumeric character, and contain only alphanumeric characters, slashes, dots, underscores, commas, pluses, hyphens, and spaces.')
82
+ return path
83
+
60
84
  class SubprocessError(Exception):
61
85
  """A subprocess call returned with non-zero."""
62
86
 
@@ -1223,12 +1247,8 @@ class Cli:
1223
1247
  self.port_forwarding_task = None
1224
1248
 
1225
1249
  async def _bg_port_forwarding(self: 'Cli') -> None:
1226
- remote_username = self.cde_type['value']['remote-username']
1227
- if re.match(r'[^a-zA-Z0-9]', remote_username):
1228
- raise Exception(f'Invalid remote username: "{remote_username}". Only alphanumeric characters are allowed.')
1229
- hostname = self.hostname
1230
- if re.match(r'[^a-zA-Z0-9]', hostname):
1231
- raise Exception(f'Invalid hostname: "{hostname}". Only alphanumeric characters are allowed.')
1250
+ remote_username = ensure_valid_username(self.cde_type['value']['remote-username'])
1251
+ hostname = ensure_valid_hostname(self.hostname)
1232
1252
  service_ports = self.cde_type['value'].get('service-ports')
1233
1253
  if service_ports is None:
1234
1254
  service_ports = [
@@ -1247,9 +1267,9 @@ class Cli:
1247
1267
  ]
1248
1268
  for port in service_ports:
1249
1269
  if port[0] < 1 or port[0] > 65535:
1250
- raise Exception(f'Invalid port: "{port[0]}". Only numbers between 1 and 65535 are allowed.')
1270
+ raise ValueError(f'Invalid port: "{port[0]}". Only numbers between 1 and 65535 are allowed.')
1251
1271
  if port[1] < 1 or port[1] > 65535:
1252
- raise Exception(f'Invalid port: "{port[1]}". Only numbers between 1 and 65535 are allowed.')
1272
+ raise ValueError(f'Invalid port: "{port[1]}". Only numbers between 1 and 65535 are allowed.')
1253
1273
  while True:
1254
1274
  logger.info('Starting port forwarding of %s', ', '.join(str(port[0]) for port in service_ports))
1255
1275
  try:
@@ -1439,15 +1459,9 @@ class Cli:
1439
1459
  return await self._process_remote_item_copy_file(file_info.filename)
1440
1460
 
1441
1461
  async def _process_remote_item_copy_dir(self: 'Cli', filename: str) -> str:
1442
- remote_username = self.cde_type['value']['remote-username']
1443
- if re.match(r'[^a-zA-Z0-9]', remote_username):
1444
- raise Exception(f'Invalid remote username: "{remote_username}". Only alphanumeric characters are allowed.')
1445
- hostname = self.hostname
1446
- if re.match(r'[^a-zA-Z0-9]', hostname):
1447
- raise Exception(f'Invalid hostname: "{hostname}". Only alphanumeric characters are allowed.')
1448
- remote_source_directory = self.cde_type['value']['remote-source-directory']
1449
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_source_directory):
1450
- raise Exception(f'Invalid remote source directory: "{remote_source_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1462
+ remote_username = ensure_valid_username(self.cde_type['value']['remote-username'])
1463
+ hostname = ensure_valid_hostname(self.hostname)
1464
+ remote_source_directory = ensure_valid_path(self.cde_type['value']['remote-source-directory'])
1451
1465
  await run_subprocess(
1452
1466
  'rsync',
1453
1467
  [
@@ -1462,9 +1476,7 @@ class Cli:
1462
1476
  return f'Copied directory "{filename}"'
1463
1477
 
1464
1478
  async def _process_remote_item_copy_file(self: 'Cli', filename: str) -> str:
1465
- remote_source_directory = self.cde_type['value']['remote-source-directory']
1466
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_source_directory):
1467
- raise Exception(f'Invalid remote source directory: "{remote_source_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1479
+ remote_source_directory = ensure_valid_path(self.cde_type['value']['remote-source-directory'])
1468
1480
  await self.loop.run_in_executor(
1469
1481
  executor=None,
1470
1482
  func=functools.partial(
@@ -1476,15 +1488,9 @@ class Cli:
1476
1488
  return f'Copied file "{filename}"'
1477
1489
 
1478
1490
  async def _process_remote_item_clone(self: 'Cli', filename: str) -> str:
1479
- remote_username = self.cde_type['value']['remote-username']
1480
- if re.match(r'[^a-zA-Z0-9]', remote_username):
1481
- raise Exception(f'Invalid remote username: "{remote_username}". Only alphanumeric characters are allowed.')
1482
- hostname = self.hostname
1483
- if re.match(r'[^a-zA-Z0-9]', hostname):
1484
- raise Exception(f'Invalid hostname: "{hostname}". Only alphanumeric characters are allowed.')
1485
- remote_source_directory = self.cde_type['value']['remote-source-directory']
1486
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_source_directory):
1487
- raise Exception(f'Invalid remote source directory: "{remote_source_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1491
+ remote_username = ensure_valid_username(self.cde_type['value']['remote-username'])
1492
+ hostname = ensure_valid_hostname(self.hostname)
1493
+ remote_source_directory = ensure_valid_path(self.cde_type['value']['remote-source-directory'])
1488
1494
  await run_subprocess(
1489
1495
  'git',
1490
1496
  [
@@ -1530,15 +1536,9 @@ class Cli:
1530
1536
  return f'Cloned repository "{filename}"'
1531
1537
 
1532
1538
  async def _background_sync(self: 'Cli') -> None:
1533
- remote_username = self.cde_type['value']['remote-username']
1534
- if re.match(r'[^a-zA-Z0-9]', remote_username):
1535
- raise Exception(f'Invalid remote username: "{remote_username}". Only alphanumeric characters are allowed.')
1536
- hostname = self.hostname
1537
- if re.match(r'[^a-zA-Z0-9]', hostname):
1538
- raise Exception(f'Invalid hostname: "{hostname}". Only alphanumeric characters are allowed.')
1539
- remote_source_directory = self.cde_type['value']['remote-source-directory']
1540
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_source_directory):
1541
- raise Exception(f'Invalid remote source directory: "{remote_source_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1539
+ remote_username = ensure_valid_username(self.cde_type['value']['remote-username'])
1540
+ hostname = ensure_valid_hostname(self.hostname)
1541
+ remote_source_directory = ensure_valid_path(self.cde_type['value']['remote-source-directory'])
1542
1542
  logger.debug('Starting background sync')
1543
1543
  self.local_source_directory.mkdir(parents=True, exist_ok=True)
1544
1544
  with contextlib.suppress(OSError):
@@ -1606,19 +1606,10 @@ class Cli:
1606
1606
  logger.info('Background sync done')
1607
1607
 
1608
1608
  async def _reverse_background_sync(self: 'Cli') -> None:
1609
- remote_username = self.cde_type['value']['remote-username']
1610
- if re.match(r'[^a-zA-Z0-9]', remote_username):
1611
- raise Exception(f'Invalid remote username: "{remote_username}". Only alphanumeric characters are allowed.')
1612
- hostname = self.hostname
1613
- if re.match(r'[^a-zA-Z0-9]', hostname):
1614
- raise Exception(f'Invalid hostname: "{hostname}". Only alphanumeric characters are allowed.')
1615
- remote_source_directory = self.cde_type['value']['remote-source-directory']
1616
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_source_directory):
1617
- raise Exception(f'Invalid remote source directory: "{remote_source_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1609
+ remote_username = ensure_valid_username(self.cde_type['value']['remote-username'])
1610
+ hostname = ensure_valid_hostname(self.hostname)
1611
+ remote_output_directory = ensure_valid_path(self.cde_type['value']['remote-output-directory'])
1618
1612
  logger.debug('Starting reverse background sync')
1619
- remote_output_directory = self.cde_type['value']['remote-output-directory']
1620
- if re.match(r'[^a-zA-Z0-9/_.-]', remote_output_directory):
1621
- raise Exception(f'Invalid remote output directory: "{remote_output_directory}". Only alphanumeric characters, slashes, underscores, dots and hyphens are allowed.')
1622
1613
  with contextlib.suppress(OSError):
1623
1614
  self.sftp_client.mkdir(remote_output_directory)
1624
1615
  self.local_output_directory.mkdir(parents=True, exist_ok=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: devstack-cli
3
- Version: 11.0.86
3
+ Version: 11.0.88
4
4
  Summary: Command-line access to Cloud Development Environments (CDEs) created by Cloudomation DevStack
5
5
  Author-email: Stefan Mückstein <stefan@cloudomation.com>
6
6
  Project-URL: Homepage, https://cloudomation.com/
@@ -0,0 +1,9 @@
1
+ __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ cli.py,sha256=-KSbyW0gGMp18u3EkQ2J6H4tiVyyW__0zUDSiC1J75o,81433
3
+ version.py,sha256=qza2HnAKjrjiL9HnU7qLhuqYTAtgntUo6i3QbWeRuDU,180
4
+ devstack_cli-11.0.88.dist-info/LICENSE,sha256=OBXZbEUMtIHIzyISkJ9fJlf_imds3rcKqeQu9yiyUJI,1055
5
+ devstack_cli-11.0.88.dist-info/METADATA,sha256=temVYDs588G-T9yMi7_P4q3tMObOMATNstdm6reeeFw,4287
6
+ devstack_cli-11.0.88.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
7
+ devstack_cli-11.0.88.dist-info/entry_points.txt,sha256=f0xb4DIk0a7E5kyZ7YpoLhtjoagQj5VQpeBbW9a8A9Y,42
8
+ devstack_cli-11.0.88.dist-info/top_level.txt,sha256=lP8zvU46Am_G0MPcNmCI6f0sMfwpDUWpTROaPs-IEPk,21
9
+ devstack_cli-11.0.88.dist-info/RECORD,,
version.py CHANGED
@@ -4,6 +4,6 @@ constants, set by build
4
4
 
5
5
  MAJOR = '11'
6
6
  BRANCH_NAME = 'release-11'
7
- BUILD_DATE = '2025-02-11-041730'
8
- SHORT_SHA = 'f9e1188'
9
- VERSION = '11+release-11.2025-02-11-041730.f9e1188'
7
+ BUILD_DATE = '2025-02-14-040012'
8
+ SHORT_SHA = '0dab1e2'
9
+ VERSION = '11+release-11.2025-02-14-040012.0dab1e2'
@@ -1,9 +0,0 @@
1
- __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- cli.py,sha256=MB5BGbhMwEs1icEsDpheBQDrNk1ck9QeDV2vTzYU82s,83037
3
- version.py,sha256=sJpyKrc9U7z_DqGEbXgd2Wk7mEkpNR377tdh5lUi-hc,180
4
- devstack_cli-11.0.86.dist-info/LICENSE,sha256=OBXZbEUMtIHIzyISkJ9fJlf_imds3rcKqeQu9yiyUJI,1055
5
- devstack_cli-11.0.86.dist-info/METADATA,sha256=ms4yDvaRUZ9etdQhZfQ4bfF1H4fGoMRBQrwmfaCCUEw,4287
6
- devstack_cli-11.0.86.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
7
- devstack_cli-11.0.86.dist-info/entry_points.txt,sha256=f0xb4DIk0a7E5kyZ7YpoLhtjoagQj5VQpeBbW9a8A9Y,42
8
- devstack_cli-11.0.86.dist-info/top_level.txt,sha256=lP8zvU46Am_G0MPcNmCI6f0sMfwpDUWpTROaPs-IEPk,21
9
- devstack_cli-11.0.86.dist-info/RECORD,,