playground-ls-cli 4.14.1.dev8__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.
- localstack_cli/__init__.py +0 -0
- localstack_cli/cli/__init__.py +10 -0
- localstack_cli/cli/console.py +11 -0
- localstack_cli/cli/core_plugin.py +12 -0
- localstack_cli/cli/exceptions.py +19 -0
- localstack_cli/cli/localstack.py +951 -0
- localstack_cli/cli/lpm.py +138 -0
- localstack_cli/cli/main.py +22 -0
- localstack_cli/cli/plugin.py +39 -0
- localstack_cli/cli/plugins.py +134 -0
- localstack_cli/cli/profiles.py +65 -0
- localstack_cli/config.py +1689 -0
- localstack_cli/constants.py +165 -0
- localstack_cli/logging/__init__.py +0 -0
- localstack_cli/logging/format.py +194 -0
- localstack_cli/logging/setup.py +142 -0
- localstack_cli/packages/__init__.py +25 -0
- localstack_cli/packages/api.py +418 -0
- localstack_cli/packages/core.py +416 -0
- localstack_cli/pro/__init__.py +0 -0
- localstack_cli/pro/core/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/__init__.py +1 -0
- localstack_cli/pro/core/bootstrap/auth.py +213 -0
- localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
- localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
- localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
- localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
- localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
- localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
- localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
- localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
- localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
- localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
- localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
- localstack_cli/pro/core/cli/__init__.py +0 -0
- localstack_cli/pro/core/cli/auth.py +226 -0
- localstack_cli/pro/core/cli/aws.py +16 -0
- localstack_cli/pro/core/cli/cli.py +99 -0
- localstack_cli/pro/core/cli/click_utils.py +21 -0
- localstack_cli/pro/core/cli/cloud_pods.py +465 -0
- localstack_cli/pro/core/cli/diff_view.py +41 -0
- localstack_cli/pro/core/cli/ephemeral.py +199 -0
- localstack_cli/pro/core/cli/extensions.py +492 -0
- localstack_cli/pro/core/cli/iam.py +180 -0
- localstack_cli/pro/core/cli/license.py +90 -0
- localstack_cli/pro/core/cli/localstack.py +118 -0
- localstack_cli/pro/core/cli/replicator.py +378 -0
- localstack_cli/pro/core/cli/state.py +183 -0
- localstack_cli/pro/core/cli/tree_view.py +235 -0
- localstack_cli/pro/core/config.py +556 -0
- localstack_cli/pro/core/constants.py +54 -0
- localstack_cli/pro/core/plugins.py +169 -0
- localstack_cli/runtime/__init__.py +6 -0
- localstack_cli/runtime/exceptions.py +7 -0
- localstack_cli/runtime/hooks.py +73 -0
- localstack_cli/testing/__init__.py +1 -0
- localstack_cli/testing/config.py +4 -0
- localstack_cli/utils/__init__.py +0 -0
- localstack_cli/utils/analytics/__init__.py +12 -0
- localstack_cli/utils/analytics/cli.py +67 -0
- localstack_cli/utils/analytics/client.py +111 -0
- localstack_cli/utils/analytics/events.py +30 -0
- localstack_cli/utils/analytics/logger.py +48 -0
- localstack_cli/utils/analytics/metadata.py +250 -0
- localstack_cli/utils/analytics/publisher.py +160 -0
- localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
- localstack_cli/utils/archives.py +271 -0
- localstack_cli/utils/batching.py +258 -0
- localstack_cli/utils/bootstrap.py +1418 -0
- localstack_cli/utils/checksum.py +313 -0
- localstack_cli/utils/collections.py +554 -0
- localstack_cli/utils/common.py +229 -0
- localstack_cli/utils/container_networking.py +142 -0
- localstack_cli/utils/container_utils/__init__.py +0 -0
- localstack_cli/utils/container_utils/container_client.py +1585 -0
- localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
- localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
- localstack_cli/utils/crypto.py +294 -0
- localstack_cli/utils/docker_utils.py +272 -0
- localstack_cli/utils/files.py +327 -0
- localstack_cli/utils/functions.py +92 -0
- localstack_cli/utils/http.py +326 -0
- localstack_cli/utils/json.py +219 -0
- localstack_cli/utils/net.py +516 -0
- localstack_cli/utils/no_exit_argument_parser.py +19 -0
- localstack_cli/utils/numbers.py +49 -0
- localstack_cli/utils/objects.py +235 -0
- localstack_cli/utils/patch.py +260 -0
- localstack_cli/utils/platform.py +77 -0
- localstack_cli/utils/run.py +514 -0
- localstack_cli/utils/server/__init__.py +0 -0
- localstack_cli/utils/server/tcp_proxy.py +108 -0
- localstack_cli/utils/serving.py +187 -0
- localstack_cli/utils/ssl.py +71 -0
- localstack_cli/utils/strings.py +245 -0
- localstack_cli/utils/sync.py +267 -0
- localstack_cli/utils/threads.py +163 -0
- localstack_cli/utils/time.py +81 -0
- localstack_cli/utils/urls.py +21 -0
- localstack_cli/utils/venv.py +100 -0
- localstack_cli/utils/xml.py +41 -0
- localstack_cli/version.py +34 -0
- playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
- playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
- playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
- playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
- playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MergeStrategy(str, Enum):
|
|
5
|
+
"""Enumerates the different strategies we can adopt when merging a state into LocalStack."""
|
|
6
|
+
|
|
7
|
+
OVERWRITE = "overwrite"
|
|
8
|
+
"""The runtime state is wiped out and the incoming one is loaded."""
|
|
9
|
+
ACCOUNT_REGION_MERGE = "account-region-merge"
|
|
10
|
+
"""Services sitting in different account-region pairs are merged. Technically, this level can also have conflicts
|
|
11
|
+
in the account/region-cross attributes."""
|
|
12
|
+
SERVICE_MERGE = "service-merge"
|
|
13
|
+
"""Services are merged down to the account-region level if there is no resource overlap. Ideologically, a resource
|
|
14
|
+
is everything that have an ARN in AWS. In the internal implementation of LocalStack, a resource is a key in the
|
|
15
|
+
attribute dictionary of a store or moto backend."""
|
|
16
|
+
# RESOURCE_MERGE = "resource-merge"
|
|
17
|
+
"""At this level, we merge everything down to the single AWS resource. This level is not yet implemented."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
NIL_PTR = "NIL"
|
|
2
|
+
|
|
3
|
+
# well-defined directory names
|
|
4
|
+
ASSETS_ROOT_DIR = "assets"
|
|
5
|
+
DEFAULT_POD_DIR = "cloudpods"
|
|
6
|
+
OBJ_STORE_DIR = "objects"
|
|
7
|
+
|
|
8
|
+
VERSION_FILE = "version.yaml"
|
|
9
|
+
|
|
10
|
+
# regex pattern for cloud pod names
|
|
11
|
+
POD_NAME_PATTERN = "^[a-zA-Z0-9_-]+$"
|
|
12
|
+
# regex pattern for cloud pod versions - currently only numeric versions supported
|
|
13
|
+
POD_VERSION_PATTERN = r"\d+"
|
|
14
|
+
|
|
15
|
+
# The name of the zip file containing cloud pod state (i.e., pickled stores and assets)
|
|
16
|
+
STATE_ZIP = "pod_state"
|
|
17
|
+
# The name of the zip file containing the full version history
|
|
18
|
+
VERSIONS_ARCHIVE = "version"
|
|
19
|
+
# Compression format for the cloud pod
|
|
20
|
+
COMPRESSION_FORMAT = "zip"
|
|
21
|
+
|
|
22
|
+
# well-defined file names
|
|
23
|
+
VERSION_SPACE_DIRS = [OBJ_STORE_DIR, VERSION_FILE]
|
|
24
|
+
|
|
25
|
+
# header name that indicates internal requests made to LocalStack
|
|
26
|
+
INTERNAL_REQUEST_PARAMS_HEADER = "x-localstack-data"
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from localstack_cli import config
|
|
6
|
+
from localstack_cli.pro.core.bootstrap.pods.constants import INTERNAL_REQUEST_PARAMS_HEADER
|
|
7
|
+
from localstack_cli.pro.core.constants import API_PATH_PODS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CloudPodsRemotesInterface(ABC):
|
|
11
|
+
"""Service interface for the remote CRUD operations."""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def create_remote(self, name: str, protocols: list[str], remote_url: str | None = None) -> None:
|
|
15
|
+
"""Creates a new remote with a given name.
|
|
16
|
+
:param name: The name of the remote.
|
|
17
|
+
:param protocols: The protocols supported by the remote.
|
|
18
|
+
:param remote_url: The URL of the remote.
|
|
19
|
+
TODO: think about security: we should check if the URL is either (1) localhost, or (2) using HTTPS.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def delete_remote(self, name: str) -> None:
|
|
24
|
+
"""Deletes a named remote."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def get_remote(self, name: str) -> dict[str, str]:
|
|
28
|
+
"""Returns a named remote."""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_remotes(self) -> list[dict[str, str]]:
|
|
32
|
+
"""Returns a list of all remotes."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CloudPodsRemotesClient(CloudPodsRemotesInterface):
|
|
36
|
+
@property
|
|
37
|
+
def endpoint(self):
|
|
38
|
+
return f"{config.external_service_url()}{API_PATH_PODS}/remotes"
|
|
39
|
+
|
|
40
|
+
def create_remote(self, name: str, protocols: list[str], remote_url: str | None = None) -> None:
|
|
41
|
+
params = {"name": name, "protocols": protocols, "remote_url": remote_url}
|
|
42
|
+
response = self._client.post(
|
|
43
|
+
url=f"{self.endpoint}/{name}",
|
|
44
|
+
data=json.dumps(params),
|
|
45
|
+
headers={"Content-Type": "application/json"},
|
|
46
|
+
)
|
|
47
|
+
if not response.ok:
|
|
48
|
+
raise Exception(f"Failed to create remote: {response.content}")
|
|
49
|
+
|
|
50
|
+
def delete_remote(self, name: str) -> None:
|
|
51
|
+
response = self._client.delete(url=f"{self.endpoint}/{name}")
|
|
52
|
+
if not response.ok:
|
|
53
|
+
raise Exception(f"Failed to delete remote: {response.content}")
|
|
54
|
+
|
|
55
|
+
def get_remotes(self) -> list[dict[str, str]]:
|
|
56
|
+
response = self._client.get(url=self.endpoint)
|
|
57
|
+
if not response.ok:
|
|
58
|
+
raise Exception(f"Failed to get list of remotes: {response.content}")
|
|
59
|
+
remotes = json.loads(response.content)
|
|
60
|
+
return remotes.get("remotes", [])
|
|
61
|
+
|
|
62
|
+
def get_remote(self, name: str) -> dict[str, str]:
|
|
63
|
+
response = self._client.get(url=f"{self.endpoint}/{name}")
|
|
64
|
+
if not response.ok:
|
|
65
|
+
raise Exception(f"Failed to get remote: {response.content}")
|
|
66
|
+
remote = json.loads(response.content)
|
|
67
|
+
return remote
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def _client(self) -> requests.Session:
|
|
71
|
+
"""Return an HTTP client session, adding default headers for all requests, as required"""
|
|
72
|
+
session = requests.Session()
|
|
73
|
+
# TODO: temporary fix to bypass Gateway 503 responses on LocalStack container shutdown
|
|
74
|
+
session.headers.update({INTERNAL_REQUEST_PARAMS_HEADER: "{}"})
|
|
75
|
+
return session
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass, field
|
|
2
|
+
from urllib.parse import quote, urlparse
|
|
3
|
+
|
|
4
|
+
DEFAULT_REMOTE_SCHEME = "platform"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class RemoteConfig:
|
|
9
|
+
"""
|
|
10
|
+
Defines the configurations of a specific remote storage backend, used to perform cloud pods remote operations.
|
|
11
|
+
|
|
12
|
+
Note: For now, the remote config only consists of a URL, which would encapsulate all the details required to
|
|
13
|
+
interact with the remote (incl. auth tokens, etc). Over time, we may think of introducing a way to add
|
|
14
|
+
more fine-grained configurations for remotes.
|
|
15
|
+
|
|
16
|
+
Note: Remote URLs may contain `{..}` placeholders like `proto://{user}:{secret}@host', which will then
|
|
17
|
+
get replaced with concrete user parameters specified via `RemoteConfigParams` at runtime.
|
|
18
|
+
|
|
19
|
+
For the special case of our own remote platform (which is the default for remote operations), specifying the URL
|
|
20
|
+
is not required, and the access credentials are retrieved from the token cache populated on "localstack login".
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
remote_url: str
|
|
24
|
+
"""The URL of the remote"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def scheme(self) -> str:
|
|
28
|
+
scheme = DEFAULT_REMOTE_SCHEME
|
|
29
|
+
if self.remote_url:
|
|
30
|
+
scheme = urlparse(self.remote_url).scheme
|
|
31
|
+
return scheme
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class RemoteConfigParams:
|
|
36
|
+
"""
|
|
37
|
+
Runtime parameters for cloud pod remotes, extracted from the user environment for remote operations.
|
|
38
|
+
|
|
39
|
+
The remote parameters typically contain credentials which are specified by the user and are then rendered
|
|
40
|
+
into the remote URL at runtime (to keep secret values ephemeral and avoid persisting them within URLs).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
remote_name: str
|
|
44
|
+
"""The name of the remote"""
|
|
45
|
+
remote_params: dict[str, str] = field(default_factory=dict)
|
|
46
|
+
"""The runtime parameters of the remote"""
|
|
47
|
+
|
|
48
|
+
def render_url(self, remote_url: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Render the given remote URL template (e.g., 's3://{key}:{secret}@bucket') with the remote params.
|
|
51
|
+
"""
|
|
52
|
+
if self.remote_params:
|
|
53
|
+
remote_params_escaped = {k: quote(v or "") for k, v in self.remote_params.items()}
|
|
54
|
+
remote_url = remote_url.format(**remote_params_escaped)
|
|
55
|
+
try:
|
|
56
|
+
# check if all placeholders have been replaced
|
|
57
|
+
remote_url.format()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise Exception(
|
|
60
|
+
f"Missing parameters for cloud pod remote URL template: {remote_url}"
|
|
61
|
+
) from e
|
|
62
|
+
return remote_url
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return asdict(self)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_dict(cls, params: dict) -> "RemoteConfigParams":
|
|
69
|
+
return cls(**params)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
from localstack_cli.pro.core.bootstrap.pods.remotes.configs import DEFAULT_REMOTE_SCHEME
|
|
7
|
+
|
|
8
|
+
LOG = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
PARAM_ACCESS_KEY_ID = "access_key_id"
|
|
11
|
+
PARAM_SECRET_ACCESS_KEY = "secret_access_key"
|
|
12
|
+
PARAM_SESSION_TOKEN = "session_token"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_aws_credentials_from_boto_session() -> dict[str, str] | None:
|
|
16
|
+
try:
|
|
17
|
+
import boto3
|
|
18
|
+
|
|
19
|
+
session = boto3.session.Session()
|
|
20
|
+
credentials = session.get_credentials()
|
|
21
|
+
return {
|
|
22
|
+
PARAM_ACCESS_KEY_ID: credentials.access_key,
|
|
23
|
+
PARAM_SECRET_ACCESS_KEY: credentials.secret_key,
|
|
24
|
+
PARAM_SESSION_TOKEN: credentials.token,
|
|
25
|
+
}
|
|
26
|
+
except Exception as e:
|
|
27
|
+
LOG.debug("Unable to extract remote parameters: %s", e)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_s3_remote_params() -> dict[str, str]:
|
|
31
|
+
"""
|
|
32
|
+
Returns the AWS credentials necessary to create the bucket storing the pods artifacts.
|
|
33
|
+
It first tries to fetch the credentials from a boto3 session. If this is not possible (e.g., boto3 not installed
|
|
34
|
+
on the host), it looks in the environment.
|
|
35
|
+
"""
|
|
36
|
+
if params := _get_aws_credentials_from_boto_session():
|
|
37
|
+
return params
|
|
38
|
+
|
|
39
|
+
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID")
|
|
40
|
+
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY")
|
|
41
|
+
aws_session_token = os.getenv("AWS_SESSION_TOKEN")
|
|
42
|
+
|
|
43
|
+
if not aws_access_key_id or not aws_secret_access_key:
|
|
44
|
+
raise Exception(
|
|
45
|
+
"Please export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the environment"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
params = {
|
|
49
|
+
PARAM_ACCESS_KEY_ID: aws_access_key_id,
|
|
50
|
+
PARAM_SECRET_ACCESS_KEY: aws_secret_access_key,
|
|
51
|
+
}
|
|
52
|
+
if aws_session_token:
|
|
53
|
+
params[PARAM_SESSION_TOKEN] = aws_session_token
|
|
54
|
+
return params
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_oras_remote_params() -> dict[str, str]:
|
|
58
|
+
# note: allow case-insensitive env. variables (all upper- or lower-case)
|
|
59
|
+
oras_username = os.getenv("ORAS_USERNAME") or os.getenv("oras_username")
|
|
60
|
+
oras_password = os.getenv("ORAS_PASSWORD") or os.getenv("oras_password")
|
|
61
|
+
if not oras_username or not oras_password:
|
|
62
|
+
raise Exception("Please specify ORAS_USERNAME and ORAS_PASSWORD in the environment")
|
|
63
|
+
return {"oras_username": oras_username, "oras_password": oras_password}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_platform_remote_params() -> dict[str, str]:
|
|
67
|
+
# note: allow case-insensitive env. variables (all upper- or lower-case)
|
|
68
|
+
auth_token = os.getenv("LOCALSTACK_AUTH_TOKEN")
|
|
69
|
+
bearer_token = os.getenv("LOCALSTACK_BEARER_TOKEN")
|
|
70
|
+
api_key = os.getenv("LOCALSTACK_API_KEY")
|
|
71
|
+
if not auth_token and not api_key and not bearer_token:
|
|
72
|
+
raise Exception("Please specify LOCALSTACK_AUTH_TOKEN in the environment")
|
|
73
|
+
return {"api_key": api_key, "auth_token": auth_token, "bearer_token": bearer_token}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
remotes_protocols: dict[str, Callable[[], dict]] = {
|
|
77
|
+
"s3": get_s3_remote_params,
|
|
78
|
+
"oras": get_oras_remote_params,
|
|
79
|
+
"platform": get_platform_remote_params,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_remote_params_callable(url: str) -> Callable[[], dict] | None:
|
|
84
|
+
"""Returns a Callable that retrieves the remote parameters for a given URL."""
|
|
85
|
+
protocol = urlparse(url).scheme or DEFAULT_REMOTE_SCHEME
|
|
86
|
+
return remotes_protocols.get(protocol, None)
|