twc-cli 2.6.0__py3-none-any.whl → 2.8.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.

Potentially problematic release.


This version of twc-cli might be problematic. Click here for more details.

CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
4
4
 
5
+ # Версия 2.7.0 (2024.11.02)
6
+
7
+ ## Добавлено
8
+
9
+ - Добавлена новая команда `twc whoami`, которая показывает логин аккаунта, в котором в текущий момент авторизован CLI.
10
+
11
+ ## Изменено
12
+
13
+ - Команда `twc account status` теперь также отображает логин аккаунта.
14
+
15
+ ## Исправлено
16
+
17
+ - Устранено падение программы при листинге образов (`twc image list`) и при создании сервера из образа.
18
+ - Исправлена ошибка при создании сервера с приватной сетью в зоне доступности SPB-3.
19
+ - Исправлены ошибки в командах `twc storage subdomain gencert` и `twc storage subdomain remove`, возникшие из-за нарушения обратной совместимости эндпоинтов API.
20
+ - Исправлена ошибка валидации приватного адреса при созаднии сервера — ошибочно запрещалось использовать 4-й по порядку адрес в подсети.
21
+ - Исправлена ошибка разбора JSON при выводе списка ресурсов в проекте.
22
+ - Исправлены ошибки добавления TXT записи для домена в команде `twc domain record add`.
23
+ - Исправлена ошибка парсинка субдомена в команде `twc domain record add`.
24
+ - Исправлены ошибки обновления DNS-записей на поддоменах.
25
+
5
26
  # Версия 2.6.0 (2024.08.14)
6
27
 
7
28
  ## Добавлено
twc/__main__.py CHANGED
@@ -21,6 +21,7 @@ from .commands import (
21
21
  vpc,
22
22
  firewall,
23
23
  floating_ip,
24
+ whoami,
24
25
  )
25
26
  from .commands.common import version_callback, version_option, verbose_option
26
27
 
@@ -46,6 +47,7 @@ cli.add_typer(domain, name="domain", aliases=["domains", "d"])
46
47
  cli.add_typer(vpc, name="vpc", aliases=["vpcs", "network", "networks"])
47
48
  cli.add_typer(firewall, name="firewall", aliases=["fw"])
48
49
  cli.add_typer(floating_ip, name="ip", aliases=["ips"])
50
+ cli.add_typer(whoami, name="whoami", aliases=[])
49
51
 
50
52
 
51
53
  @cli.command("version")
twc/__version__.py CHANGED
@@ -12,5 +12,5 @@
12
12
  import sys
13
13
 
14
14
 
15
- __version__ = "2.6.0"
15
+ __version__ = "2.8.0"
16
16
  __pyversion__ = sys.version.replace("\n", "")
twc/api/client.py CHANGED
@@ -469,14 +469,11 @@ class TimewebCloud(TimewebCloudBase):
469
469
  # -----------------------------------------------------------------------
470
470
  # Images
471
471
 
472
- def get_images(
473
- self, limit: int = 100, offset: int = 0, with_deleted: bool = False
474
- ):
472
+ def get_images(self, limit: int = 100, offset: int = 0):
475
473
  """Get list of images."""
476
474
  params = {
477
475
  "limit": limit,
478
476
  "offset": offset,
479
- "with_deleted": with_deleted,
480
477
  }
481
478
  return self._request("GET", f"{self.api_url}/images", params=params)
482
479
 
@@ -1413,17 +1410,31 @@ class TimewebCloud(TimewebCloudBase):
1413
1410
  self,
1414
1411
  fqdn: str,
1415
1412
  dns_record_type: DNSRecordType,
1416
- value: str,
1413
+ value: Optional[str] = None,
1417
1414
  subdomain: Optional[str] = None,
1418
1415
  priority: Optional[int] = None,
1416
+ ttl: Optional[int] = None,
1417
+ protocol: Optional[str] = None,
1418
+ service: Optional[str] = None,
1419
+ host: Optional[str] = None,
1420
+ port: Optional[int] = None,
1421
+ *,
1422
+ null_subdomain: bool = False,
1419
1423
  ):
1420
1424
  """Add DNS record to domain."""
1421
1425
  payload = {
1422
1426
  "type": dns_record_type,
1423
- "value": value,
1427
+ **({"value": value} if value else {}),
1424
1428
  **({"subdomain": subdomain} if subdomain else {}),
1425
- **({"priority": priority} if priority else {}),
1429
+ **({"priority": priority} if priority is not None else {}),
1430
+ **({"ttl": ttl} if ttl else {}),
1431
+ **({"protocol": protocol} if protocol else {}),
1432
+ **({"service": service} if service else {}),
1433
+ **({"host": host} if host else {}),
1434
+ **({"port": port} if port else {}),
1426
1435
  }
1436
+ if null_subdomain:
1437
+ payload["subdomain"] = None
1427
1438
  return self._request(
1428
1439
  "POST",
1429
1440
  f"{self.api_url}/domains/{fqdn}/dns-records",
twc/apiwrap.py CHANGED
@@ -4,7 +4,7 @@ import os
4
4
  import sys
5
5
  import textwrap
6
6
  from pathlib import Path
7
- from logging import debug
7
+ from logging import debug, warning
8
8
 
9
9
  from .api import TimewebCloud
10
10
  from .api import exceptions as exc
@@ -70,6 +70,11 @@ def create_client(config: Path, profile: str, **kwargs) -> TimewebCloud:
70
70
  """
71
71
  token = os.getenv("TWC_TOKEN")
72
72
  log_settings = os.getenv("TWC_LOG")
73
+ api_endpoint = os.getenv("TWC_ENDPOINT")
74
+
75
+ if api_endpoint:
76
+ warning("Using API URL from environment: %s", api_endpoint)
77
+ kwargs["api_base_url"] = api_endpoint
73
78
 
74
79
  if log_settings:
75
80
  for param in log_settings.split(","):
twc/commands/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Commands."""
2
2
 
3
- from .account import account
3
+ from .account import account, whoami
4
4
  from .config import config
5
5
  from .server import server
6
6
  from .ssh_key import ssh_key
twc/commands/account.py CHANGED
@@ -18,6 +18,35 @@ from .common import (
18
18
  )
19
19
 
20
20
 
21
+ whoami = TyperAlias(help="Display current login.", no_args_is_help=False)
22
+
23
+
24
+ # ------------------------------------------------------------- #
25
+ # $ twc whoami #
26
+ # ------------------------------------------------------------- #
27
+
28
+
29
+ @whoami.callback(invoke_without_command=True)
30
+ def whoami_callback(
31
+ verbose: Optional[bool] = verbose_option,
32
+ config: Optional[Path] = config_option,
33
+ profile: Optional[str] = profile_option,
34
+ output_format: Optional[str] = output_format_option,
35
+ ):
36
+ """Display current login."""
37
+ client = create_client(config, profile)
38
+ response = client.get_account_status()
39
+ login = response.json()["status"]["login"]
40
+ fmt.printer(
41
+ {"login": login, "profile": profile},
42
+ output_format=output_format,
43
+ func=lambda data: print(data["login"]),
44
+ )
45
+
46
+
47
+ # ------------------------------------------------------------- #
48
+
49
+
21
50
  account = TyperAlias(help=__doc__)
22
51
  account_access = TyperAlias(help="Manage account access restrictions.")
23
52
  account.add_typer(account_access, name="access")
@@ -33,6 +62,7 @@ def print_account_status(response: Response):
33
62
  status = response.json()["status"]
34
63
  output = dedent(
35
64
  f"""
65
+ Login: {status["login"]}
36
66
  Provider: {status["company_info"]["name"]}
37
67
  Yandex.Metrika ID: {status["ym_client_id"]}
38
68
  Blocked: {status["is_blocked"]}
twc/commands/domain.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import re
4
4
  import sys
5
+ from enum import Enum
5
6
  from typing import Optional, List
6
7
  from pathlib import Path
7
8
 
@@ -195,7 +196,7 @@ def domain_delete(
195
196
  # ------------------------------------------------------------- #
196
197
 
197
198
 
198
- @domain.command("add")
199
+ @domain.command("add", "create")
199
200
  def domain_add(
200
201
  domain_name: str,
201
202
  verbose: Optional[bool] = verbose_option,
@@ -316,7 +317,15 @@ def domain_remove_dns_record(
316
317
  # ------------------------------------------------------------- #
317
318
 
318
319
 
319
- @domain_record.command("add")
320
+ class SRVProtocol(str, Enum):
321
+ """Supported protocols for SRV records."""
322
+
323
+ TCP = "TCP"
324
+ UPD = "UDP"
325
+ TLS = "TLS"
326
+
327
+
328
+ @domain_record.command("add", "create")
320
329
  def domain_add_dns_record(
321
330
  domain_name: str,
322
331
  verbose: Optional[bool] = verbose_option,
@@ -331,12 +340,40 @@ def domain_add_dns_record(
331
340
  metavar="TYPE",
332
341
  help=f"[{'|'.join([k.value for k in DNSRecordType])}]",
333
342
  ),
334
- value: Optional[str] = typer.Option(...),
343
+ value: Optional[str] = typer.Option(
344
+ None,
345
+ help="Record value. Skip it for SRV records.",
346
+ ),
335
347
  priority: Optional[int] = typer.Option(
336
348
  None,
337
349
  "--prio",
338
350
  help="Record priority. Supported for MX, SRV records.",
339
351
  ),
352
+ service: Optional[str] = typer.Option(
353
+ None,
354
+ "--service",
355
+ help="Service for SRV record e.g '_matrix'.",
356
+ ),
357
+ proto: Optional[SRVProtocol] = typer.Option(
358
+ None,
359
+ "--proto",
360
+ help="Protocol for SRV record.",
361
+ ),
362
+ host: Optional[str] = typer.Option(
363
+ None,
364
+ "--host",
365
+ help="Host for SRV record.",
366
+ ),
367
+ port: Optional[int] = typer.Option(
368
+ None,
369
+ "--port",
370
+ help="Port for SRV record.",
371
+ min=1,
372
+ max=65535,
373
+ ),
374
+ ttl: Optional[int] = typer.Option(
375
+ None, "--ttl", help="Time-To-Live for DNS record."
376
+ ),
340
377
  second_ld: Optional[bool] = typer.Option(
341
378
  False,
342
379
  "--2ld",
@@ -346,30 +383,50 @@ def domain_add_dns_record(
346
383
  """Add dns record for domain or subdomain."""
347
384
  client = create_client(config, profile)
348
385
 
386
+ if record_type != "SRV" and not value:
387
+ sys.exit("Error: --value is expected for non-SRV DNS records")
388
+
389
+ null_subdomain = False
390
+
349
391
  if second_ld:
350
392
  offset = 3
351
393
  else:
352
394
  offset = 2
353
395
 
354
396
  subdomain = domain_name
397
+ original_domain_name = domain_name
355
398
  domain_name = ".".join(domain_name.split(".")[-offset:])
356
399
 
357
400
  if subdomain == domain_name:
358
401
  subdomain = None
359
402
 
360
- # API issue: see text below
361
- # API can add TXT record (only TXT, why?) with non-existent subdomain,
362
- # but 'subdomain' option must not be passed as FQDN!
363
- # API issue: You cannot create subdomains with underscore. Why?
364
- # Use previous described bug for this! Pass your subdomain with
365
- # underscores to this function.
366
403
  if record_type.lower() == "txt":
367
- # 'ftp.example.org' --> 'ftp'
368
- subdomain = ".".join(subdomain.split(".")[:-offset])
369
-
370
- response = client.add_domain_dns_record(
371
- domain_name, record_type, value, subdomain, priority
372
- )
404
+ if subdomain is None:
405
+ null_subdomain = True
406
+ else:
407
+ # 'ftp.example.org' --> 'ftp'
408
+ subdomain = ".".join(subdomain.split(".")[:-offset])
409
+ else:
410
+ subdomain = ".".join(original_domain_name.split(".")[:-offset])
411
+ if subdomain != "":
412
+ domain_name = original_domain_name
413
+ subdomain = None
414
+
415
+ payload = {
416
+ "fqdn": domain_name,
417
+ "dns_record_type": record_type,
418
+ "value": value,
419
+ "subdomain": subdomain,
420
+ "priority": priority,
421
+ "ttl": ttl,
422
+ "protocol": "_" + proto if proto else None,
423
+ "service": service,
424
+ "host": host,
425
+ "port": port,
426
+ "null_subdomain": null_subdomain,
427
+ }
428
+
429
+ response = client.add_domain_dns_record(**payload)
373
430
  fmt.printer(
374
431
  response,
375
432
  output_format=output_format,
@@ -424,6 +481,12 @@ def domain_update_dns_records(
424
481
  if subdomain == domain_name:
425
482
  subdomain = None
426
483
 
484
+ if record_type.lower() == "txt" and subdomain is not None:
485
+ subdomain = ".".join(subdomain.split(".")[:-offset])
486
+ elif subdomain is not None:
487
+ domain_name = subdomain
488
+ subdomain = None
489
+
427
490
  response = client.update_domain_dns_record(
428
491
  domain_name, record_id, record_type, value, subdomain, priority
429
492
  )
@@ -439,7 +502,7 @@ def domain_update_dns_records(
439
502
  # ------------------------------------------------------------- #
440
503
 
441
504
 
442
- @domain_subdomain.command("add")
505
+ @domain_subdomain.command("add", "create")
443
506
  def domain_add_subdomain(
444
507
  subdomain: str = typer.Argument(..., metavar="FQDN"),
445
508
  verbose: Optional[bool] = verbose_option,
twc/commands/project.py CHANGED
@@ -188,17 +188,11 @@ def print_resources(response: Response):
188
188
  for key in resource_keys:
189
189
  if resources[key]:
190
190
  for resource in resources[key]:
191
- try:
192
- location = resource["location"]
193
- except KeyError:
194
- # Balancers, clusters, and databases has no 'location' field.
195
- # These services is available only in 'ru-1' location.
196
- location = "ru-1"
197
191
  table.row(
198
192
  [
199
193
  resource["id"],
200
- resource["name"],
201
- location,
194
+ resource.get("name", resource.get("fqdn")),
195
+ resource.get("location", "ru-1"),
202
196
  key[:-1], # this is resource name e.g. 'server'
203
197
  ]
204
198
  )
twc/commands/server.py CHANGED
@@ -584,9 +584,7 @@ def server_create(
584
584
  net = IPv4Network(
585
585
  client.get_vpc(network).json()["vpc"]["subnet_v4"]
586
586
  )
587
- if IPv4Address(private_ip) > IPv4Address(
588
- int(net.network_address) + 4
589
- ):
587
+ if IPv4Address(private_ip) >= net.network_address + 4:
590
588
  payload["network"]["ip"] = private_ip
591
589
  else:
592
590
  # First 3 addresses is reserved for networks OVN based networks
twc/commands/storage.py CHANGED
@@ -500,7 +500,6 @@ def storage_subdomain_remove(
500
500
  verbose: Optional[bool] = verbose_option,
501
501
  config: Optional[Path] = config_option,
502
502
  profile: Optional[str] = profile_option,
503
- output_format: Optional[str] = output_format_option,
504
503
  yes: Optional[bool] = yes_option,
505
504
  ):
506
505
  """Remove subdomains."""
@@ -509,11 +508,11 @@ def storage_subdomain_remove(
509
508
  client = create_client(config, profile)
510
509
  bucket_id = resolve_bucket_id(client, bucket)
511
510
  response = client.delete_bucket_subdomains(bucket_id, subdomains)
512
- fmt.printer(
513
- response,
514
- output_format=output_format,
515
- func=print_subdomains_state,
516
- )
511
+ if response.status_code == 204:
512
+ for subdomain in subdomains:
513
+ print(subdomain)
514
+ else:
515
+ sys.exit(fmt.printer(response))
517
516
 
518
517
 
519
518
  # ------------------------------------------------------------- #
@@ -532,7 +531,7 @@ def storage_subdomain_gencert(
532
531
  client = create_client(config, profile)
533
532
  for subdomain in subdomains:
534
533
  response = client.gen_cert_for_bucket_subdomain(subdomain)
535
- if response.status_code == 201:
534
+ if response.status_code == 204:
536
535
  print(subdomain)
537
536
  else:
538
537
  sys.exit(fmt.printer(response))
twc/fmt.py CHANGED
@@ -67,6 +67,9 @@ class Printer:
67
67
 
68
68
  def raw(self):
69
69
  """Print raw API response text (mostly raw JSON)."""
70
+ if isinstance(self._data, dict):
71
+ typer.echo(json.dumps(self._data))
72
+ return
70
73
  typer.echo(self._data.text)
71
74
 
72
75
  def colorize(self, data: str, lexer: object = JsonLexer()):
@@ -77,9 +80,12 @@ class Printer:
77
80
  """Print colorized JSON output. Fallback to non-color output if
78
81
  Pygments not installed and fallback to raw output on JSONDecodeError.
79
82
  """
83
+ data = self._data
84
+ if not isinstance(self._data, dict):
85
+ data = self._data.json()
80
86
  try:
81
87
  json_data = json.dumps(
82
- self._data.json(), indent=4, sort_keys=True, ensure_ascii=False
88
+ data, indent=4, sort_keys=True, ensure_ascii=False
83
89
  )
84
90
  self.colorize(json_data, lexer=JsonLexer())
85
91
  except json.JSONDecodeError:
@@ -89,10 +95,11 @@ class Printer:
89
95
  """Print colorized YAML output. Fallback to non-color output if
90
96
  Pygments not installed and fallback to raw output on YAMLError.
91
97
  """
98
+ data = self._data
99
+ if not isinstance(self._data, dict):
100
+ data = self._data.json()
92
101
  try:
93
- yaml_data = yaml.dump(
94
- self._data.json(), sort_keys=True, allow_unicode=True
95
- )
102
+ yaml_data = yaml.dump(data, sort_keys=True, allow_unicode=True)
96
103
  self.colorize(yaml_data, lexer=YamlLexer())
97
104
  except yaml.YAMLError:
98
105
  self.raw()
twc/vars.py CHANGED
@@ -11,4 +11,4 @@ CONTROL_PANEL_URL = "https://timeweb.cloud/my"
11
11
  REGIONS_WITH_IPV6 = ["ru-1", "pl-1"]
12
12
  REGIONS_WITH_IMAGES = ["ru-1", "ru-3", "kz-1", "pl-1", "nl-1"]
13
13
  REGIONS_WITH_LAN = ["ru-1", "ru-3", "nl-1", "pl-1"]
14
- ZONES_WITH_LAN = ["spb-1", "spb-4", "msk-1", "ams-1", "gdn-1"]
14
+ ZONES_WITH_LAN = ["spb-1", "spb-3", "spb-4", "msk-1", "ams-1", "gdn-1"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twc-cli
3
- Version: 2.6.0
3
+ Version: 2.8.0
4
4
  Summary: Timeweb Cloud Command Line Interface.
5
5
  Home-page: https://github.com/timeweb-cloud/twc
6
6
  License: MIT
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
16
17
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
17
18
  Requires-Dist: pygments (>=2.18.0,<3.0.0)
18
19
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
@@ -1,36 +1,36 @@
1
- CHANGELOG.md,sha256=4VPeBGreHTzwFwgjCyNh1132Sx3glB2yBHBypJnl_fU,24717
1
+ CHANGELOG.md,sha256=AnIkFEP7S8yrjLrgp1Vbsp47c79n6RQxz6-6OEwwEmk,26436
2
2
  COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
3
3
  twc/__init__.py,sha256=NwPAMNw3NuHdWGQvWS9_lromVF6VM194oVOipojfJns,113
4
- twc/__main__.py,sha256=YBqi1OMSDaZnu2q8OyNa6N-dkVI90n6DZrJbUvYbHJA,2407
5
- twc/__version__.py,sha256=Pqtq12hKiQFbdtW9mzEN58Z8FqFqqIp-iWJljwSSYUU,442
4
+ twc/__main__.py,sha256=ADHceaQUzgLmvhYHvb5O8urdJWj5IcEHLpTQkSExiD8,2468
5
+ twc/__version__.py,sha256=OHC5aleI7fbFRKonYAojGfgjUXrryXI8Eh5UyrmuNM4,442
6
6
  twc/api/__init__.py,sha256=SXew0Fe51M7nRBNQaaLRH4NjnRHkQUn7J26OCkQsftA,128
7
7
  twc/api/base.py,sha256=QRefnIgmlbz8n37GLBKeAK1AtzkcNo1IFjZgHDDECJ4,7912
8
- twc/api/client.py,sha256=LSe8pH684fh1u2jxj-IY8O40NvFsqwVctHrshfctRIE,58482
8
+ twc/api/client.py,sha256=ROuj2ZklQOtgrcXYjoiN2mimwxfNFpy4MZvm0qTfomY,59010
9
9
  twc/api/exceptions.py,sha256=UzK3pKRffcXlhnkPy6MDjP_DygVoV17DuZ_mdNbOzts,2369
10
10
  twc/api/types.py,sha256=HCxdTi-o8nVq4ShPthd2fUvlYufEoXafx_6qrNHFH04,5406
11
- twc/apiwrap.py,sha256=0SmFZUH013jKHlpAQmsIySMtFuNtGvkMLQv0zTVJijg,2780
12
- twc/commands/__init__.py,sha256=hvTpQByt7wfl9hVo7xRPCdwAQpctoFiBVdaMLHDqKbs,422
13
- twc/commands/account.py,sha256=6T7J3McTXJKzT7Gi_AgRcKpWdeXcmBTcpwFF0GjzADo,4998
11
+ twc/apiwrap.py,sha256=hKrg_o6rLfY32SEnWMc1BSXHnSAh7TGar1JQ90YnG5M,2970
12
+ twc/commands/__init__.py,sha256=a-6fHQQwOj--Z7uBZGZL3z1rvJiOGUMQMRET1UknIYo,430
13
+ twc/commands/account.py,sha256=6q9ri02oFbUUZuqNVXO-uHOX45B4ELJlPjyfVaEL5Qw,5960
14
14
  twc/commands/balancer.py,sha256=QAouc74ZT5go11gB1vjjfYtd1luTmWrfpACPwokZ5sU,20278
15
15
  twc/commands/common.py,sha256=Wph8cVogUNNvc456SQrASb7mv7G88I8ETwHgISVjLQQ,8282
16
16
  twc/commands/config.py,sha256=hoRtxn2VRxIsuy9vgO6yd0Cu15Rbl-uYMZeU0Ix7dG0,8797
17
17
  twc/commands/database.py,sha256=2NZ-TyRBkFgfYJyUdZUcfdqSaX7QVdWDU4k_yQNtUvo,16052
18
- twc/commands/domain.py,sha256=SHOERsEvmlkdKQV5kaiEgMOLZme5ykW8LCUd1IF1Zzo,15624
18
+ twc/commands/domain.py,sha256=BIg5k0TDQ-iWnhjuAHaWlZBB0bfaZgqZ2EWZGk3BICA,17154
19
19
  twc/commands/firewall.py,sha256=KNolqbi2rsppOZwbs_j3yoZQt-0wKbj1JPGiZdfGxDE,27439
20
20
  twc/commands/floating_ip.py,sha256=G9nD5BbHCZcuytbzeneDJWQDhd8c8WRtq9pAfwI9m7E,8747
21
21
  twc/commands/image.py,sha256=OviQwegXK55H3TBlroCASVcgj2QUVCTo0ZhF5ug9eT8,8165
22
22
  twc/commands/kubernetes.py,sha256=-Cgas1vFVMcrWGinjstuUz3sqX0ZNXv_4mwPwuwKeLE,20870
23
- twc/commands/project.py,sha256=0z0MmkW8CVFb8emftE5OshoAwdLBZq_otQjNpeuuklU,10750
24
- twc/commands/server.py,sha256=nySxygbA1ZwLCE53JjhabLAI7uZjnFE_RdJ_8JzZvOU,70341
23
+ twc/commands/project.py,sha256=xnL3kLIumKzrI9EZ6r6m-PGOl3mZ9IhLQua7WZ3Rghg,10499
24
+ twc/commands/server.py,sha256=Cw8VxOcWEVPtcKZ53h9erhV3VOj7io9E8xQwVN0S53Y,70294
25
25
  twc/commands/ssh_key.py,sha256=NHgTPhAQpDzt-iPHHVo4XqUJvujNqf019N6N9qYZ9Us,7941
26
- twc/commands/storage.py,sha256=qt7_4rvnM2gnN4a0K4KEDoNZ_-2buvMNnCSJIXxQszs,19424
26
+ twc/commands/storage.py,sha256=Pztk5iUBp9RtkdOwsfHaZFCnD8GuH6zOPtluawkRmiI,19404
27
27
  twc/commands/vpc.py,sha256=SAht6UD17mU0d_AZY6W34VEYs7CqUsS2iDakPFxAFQU,8876
28
- twc/fmt.py,sha256=f8submLhHnm8OXBl_4Oe4tuhhMKvgTK1PP1y_25TmyU,6904
28
+ twc/fmt.py,sha256=nbuYZ8nVabYDwCmZqnL3-c6Tmri4B-R_sTCkG6sdfeI,7171
29
29
  twc/typerx.py,sha256=AZ6BgTQvlrZYfKVYd9YqRNQnAR2XuyqImz4rf6di6f4,6737
30
30
  twc/utils.py,sha256=uWizyUC4dHLwtk50q4Sub3zOvnVESfHKBbXYwk5t71w,651
31
- twc/vars.py,sha256=n6RzpTJAu44pp4f8KgXcLnorsQBhIcVIMs6DA8-4MCM,534
32
- twc_cli-2.6.0.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
33
- twc_cli-2.6.0.dist-info/METADATA,sha256=gkmWcgAscC5aSj83A1_RgHYfH9MW_5sd_0_7vK-cxkw,2601
34
- twc_cli-2.6.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
35
- twc_cli-2.6.0.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
36
- twc_cli-2.6.0.dist-info/RECORD,,
31
+ twc/vars.py,sha256=fva3O2leMGtExb1aWiAk6sOV0O8et9_kEyRpYYIZ7CM,543
32
+ twc_cli-2.8.0.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
33
+ twc_cli-2.8.0.dist-info/METADATA,sha256=LEAHMaB0puxa1JSU4C1ok6O1S1p99CObd5VpDuB1jEY,2652
34
+ twc_cli-2.8.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
35
+ twc_cli-2.8.0.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
36
+ twc_cli-2.8.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any