From 0c5c0c2b014d75e3e1cfe10832ec372e5b4d4dc0 Mon Sep 17 00:00:00 2001 From: Waldemar Hummer Date: Tue, 2 Jul 2024 00:55:56 +0200 Subject: [PATCH 1/8] small refactorings to fix CLI import errors for replicator (#68) --- .../aws_replicator/client/auth_proxy.py | 3 +- aws-replicator/aws_replicator/client/cli.py | 9 +++-- .../aws_replicator/client/service_states.py | 38 ++++++++++++++++++- .../aws_replicator/shared/models.py | 38 +------------------ 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/aws-replicator/aws_replicator/client/auth_proxy.py b/aws-replicator/aws_replicator/client/auth_proxy.py index 966abe2..2e75dee 100644 --- a/aws-replicator/aws_replicator/client/auth_proxy.py +++ b/aws-replicator/aws_replicator/client/auth_proxy.py @@ -14,7 +14,6 @@ from botocore.model import OperationModel from localstack import config from localstack import config as localstack_config -from localstack.aws.protocol.parser import create_parser from localstack.aws.spec import load_service from localstack.config import external_service_url from localstack.constants import AWS_REGION_US_EAST_1, DOCKER_IMAGE_NAME_PRO @@ -158,6 +157,8 @@ def register_in_instance(self): def _parse_aws_request( self, request: Request, service_name: str, region_name: str, client ) -> Tuple[OperationModel, AWSPreparedRequest, Dict]: + from localstack.aws.protocol.parser import create_parser + parser = create_parser(load_service(service_name)) operation_model, parsed_request = parser.parse(request) request_context = { diff --git a/aws-replicator/aws_replicator/client/cli.py b/aws-replicator/aws_replicator/client/cli.py index 8c90918..422e6be 100644 --- a/aws-replicator/aws_replicator/client/cli.py +++ b/aws-replicator/aws_replicator/client/cli.py @@ -67,10 +67,7 @@ def _is_logged_in() -> bool: required=False, ) def cmd_aws_proxy(services: str, config: str, container: bool, port: int, host: str): - from aws_replicator.client.auth_proxy import ( - start_aws_auth_proxy, - start_aws_auth_proxy_in_container, - ) + from aws_replicator.client.auth_proxy import start_aws_auth_proxy_in_container config_json: ProxyConfig = {"services": {}} if config: @@ -84,6 +81,10 @@ def cmd_aws_proxy(services: str, config: str, container: bool, port: int, host: try: if container: return start_aws_auth_proxy_in_container(config_json) + + # note: deferring the import here, to avoid import errors in CLI context + from aws_replicator.client.auth_proxy import start_aws_auth_proxy + proxy = start_aws_auth_proxy(config_json, port=port) proxy.join() except Exception as e: diff --git a/aws-replicator/aws_replicator/client/service_states.py b/aws-replicator/aws_replicator/client/service_states.py index 1cad6d4..148339b 100644 --- a/aws-replicator/aws_replicator/client/service_states.py +++ b/aws-replicator/aws_replicator/client/service_states.py @@ -1,14 +1,17 @@ import logging -from typing import Dict, Type +from typing import Dict, Optional, Type import boto3 from botocore.client import BaseClient from localstack.services.cloudformation.models.s3 import S3Bucket +from localstack.services.cloudformation.service_models import GenericBaseModel from localstack.utils.aws import aws_stack +from localstack.utils.objects import get_all_subclasses from localstack.utils.threads import parallelize from aws_replicator.client.utils import post_request_to_instance -from aws_replicator.shared.models import ExtendedResourceStateReplicator, ReplicateStateRequest +from aws_replicator.shared.models import ReplicateStateRequest +from aws_replicator.shared.utils import get_resource_type LOG = logging.getLogger(__name__) @@ -23,6 +26,37 @@ def wrapper(wrapping_clazz): return wrapper +# TODO: remove / adjust to use latest upstream CFn models! +class ExtendedResourceStateReplicator(GenericBaseModel): + """Extended resource models, used to replicate (inject) additional state into a resource instance""" + + def add_extended_state_external(self, remote_client: BaseClient = None): + """Called in the context of external CLI execution to fetch/replicate resource details from a remote account""" + + def add_extended_state_internal(self, state: Dict): + """Called in the context of the internal LocalStack instance to inject the state into a resource""" + + @classmethod + def get_resource_instance(cls, resource: Dict) -> Optional["ExtendedResourceStateReplicator"]: + resource_type = get_resource_type(resource) + resource_class = cls.find_resource_classes().get(resource_type) + if resource_class: + return resource_class(resource) + + @classmethod + def get_resource_class( + cls, resource_type: str + ) -> Optional[Type["ExtendedResourceStateReplicator"]]: + return cls.find_resource_classes().get(resource_type) + + @classmethod + def find_resource_classes(cls) -> Dict[str, "ExtendedResourceStateReplicator"]: + return { + inst.cloudformation_type(): inst + for inst in get_all_subclasses(ExtendedResourceStateReplicator) + } + + # resource-specific replications diff --git a/aws-replicator/aws_replicator/shared/models.py b/aws-replicator/aws_replicator/shared/models.py index 2d64a6d..5644212 100644 --- a/aws-replicator/aws_replicator/shared/models.py +++ b/aws-replicator/aws_replicator/shared/models.py @@ -1,46 +1,10 @@ import logging from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional, Type, TypedDict, Union - -from botocore.client import BaseClient -from localstack.services.cloudformation.service_models import GenericBaseModel -from localstack.utils.objects import get_all_subclasses - -from aws_replicator.shared.utils import get_resource_type +from typing import Any, Dict, List, Optional, TypedDict, Union LOG = logging.getLogger(__name__) -class ExtendedResourceStateReplicator(GenericBaseModel): - """Extended resource models, used to replicate (inject) additional state into a resource instance""" - - def add_extended_state_external(self, remote_client: BaseClient = None): - """Called in the context of external CLI execution to fetch/replicate resource details from a remote account""" - - def add_extended_state_internal(self, state: Dict): - """Called in the context of the internal LocalStack instance to inject the state into a resource""" - - @classmethod - def get_resource_instance(cls, resource: Dict) -> Optional["ExtendedResourceStateReplicator"]: - resource_type = get_resource_type(resource) - resource_class = cls.find_resource_classes().get(resource_type) - if resource_class: - return resource_class(resource) - - @classmethod - def get_resource_class( - cls, resource_type: str - ) -> Optional[Type["ExtendedResourceStateReplicator"]]: - return cls.find_resource_classes().get(resource_type) - - @classmethod - def find_resource_classes(cls) -> Dict[str, "ExtendedResourceStateReplicator"]: - return { - inst.cloudformation_type(): inst - for inst in get_all_subclasses(ExtendedResourceStateReplicator) - } - - class ReplicateStateRequest(TypedDict): """ Represents a request sent from the CLI to the extension request From ecbffaa45a569834bf8a100a5abbc3dbc4475776 Mon Sep 17 00:00:00 2001 From: Thomas Rausch Date: Wed, 10 Jul 2024 02:19:06 +0200 Subject: [PATCH 2/8] add terraform-init extension (#71) --- README.md | 1 + terraform-init/Makefile | 36 ++++++ terraform-init/README.md | 110 ++++++++++++++++++ .../localstack_terraform_init/__init__.py | 1 + .../localstack_terraform_init/extension.py | 79 +++++++++++++ terraform-init/setup.cfg | 23 ++++ terraform-init/setup.py | 4 + 7 files changed, 254 insertions(+) create mode 100644 terraform-init/Makefile create mode 100644 terraform-init/README.md create mode 100644 terraform-init/localstack_terraform_init/__init__.py create mode 100644 terraform-init/localstack_terraform_init/extension.py create mode 100644 terraform-init/setup.cfg create mode 100644 terraform-init/setup.py diff --git a/README.md b/README.md index f70523d..bc8eafc 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ You can install the respective extension by calling `localstack install [!WARNING] +> This extension is experimental and subject to change. + +> [!NOTE] +> The extension is designed for simple self-contained terraform files, not complex projects or modules. +> If you have larger projects, then we recommend running them from the host. + +## Usage + +* Start localstack with `EXTENSION_AUTO_INSTALL="localstack-extension-terraform-init"` +* Mount a `main.tf` file into `/etc/localstack/init/ready.d` + +When LocalStack starts up, it will install the extension, which in turn install `terraform` and `tflocal` into the container. +If one of the init stage directories contain a `main.tf`, the extension will run `tflocal init` and `tflocal apply` on that directory. + +> [!NOTE] +> Terraform state files will be created in your host directory if you mounted an entire folder into `/etc/localstack/init/ready.d`. +> These files are created from within the container using the container user, so you may need `sudo` to remove the files from your host. +> If you only mount the `main.tf` file, not an entire directory, localstack will have to download the AWS terraform provider every time during `tflocal init`. +> +### Example + +Example `main.tf`: +```hcl +resource "aws_s3_bucket" "example" { + bucket = "my-tf-test-bucket" + + tags = { + Name = "My bucket" + Environment = "Dev" + } +} +``` + +Start LocalStack Pro with mounted `main.tf`: + +```console +localstack start \ + -e EXTENSION_AUTO_INSTALL="localstack-extension-terraform-init" \ + -v ./main.tf:/etc/localstack/init/ready.d/main.tf +``` + +Or, if you use a docker-compose file: + +```yaml +services: + localstack: + container_name: "localstack-main" + image: localstack/localstack-pro # required for Pro + ports: + - "127.0.0.1:4566:4566" # LocalStack Gateway + environment: + # Activate LocalStack Pro: https://docs.localstack.cloud/getting-started/auth-token/ + - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:?} + - EXTENSION_AUTO_LOAD=localstack-extension-terraform-init" + volumes: + # you could also place your main.tf in `./ready.d` and set "./ready.d:/etc/localstack/init/ready.d" + - "./main.tf:/etc/localstack/init/ready.d/main.tf" + - "./volume:/var/lib/localstack" + - "/var/run/docker.sock:/var/run/docker.sock" +``` + +In a new terminal window, you can wait for localstack to complete and then print the created s3 buckets. + +```console +localstack wait && awslocal s3 ls +``` + +The logs should show something like: + +``` +2024-06-26T20:36:19.946 INFO --- [ady_monitor)] l.extension : Applying terraform project from file /etc/localstack/init/ready.d/main.tf +2024-06-26T20:36:19.946 DEBUG --- [ady_monitor)] localstack.utils.run : Executing command: ['tflocal', '-chdir=/etc/localstack/init/ready.d', 'init', '-input=false'] +2024-06-26T20:36:26.864 DEBUG --- [ady_monitor)] localstack.utils.run : Executing command: ['tflocal', '-chdir=/etc/localstack/init/ready.d', 'apply', '-auto-approve'] +``` + +## Install local development version + +To install the extension into localstack in developer mode, you will need Python 3.10, and create a virtual environment in the extensions project. + +In the newly generated project, simply run + +```bash +make install +``` + +Then, to enable the extension for LocalStack, run + +```bash +localstack extensions dev enable . +``` + +You can then start LocalStack with `EXTENSION_DEV_MODE=1` to load all enabled extensions: + +```bash +EXTENSION_DEV_MODE=1 localstack start +``` + +## Install from GitHub repository + +To distribute your extension, simply upload it to your github account. Your extension can then be installed via: + +```bash +localstack extensions install "git+https://github.com/localstack/localstack-extensions/#egg=localstack-extension-terraform-init&subdirectory=terraform-init" +``` diff --git a/terraform-init/localstack_terraform_init/__init__.py b/terraform-init/localstack_terraform_init/__init__.py new file mode 100644 index 0000000..c311b1a --- /dev/null +++ b/terraform-init/localstack_terraform_init/__init__.py @@ -0,0 +1 @@ +name = "localstack_terraform_init" diff --git a/terraform-init/localstack_terraform_init/extension.py b/terraform-init/localstack_terraform_init/extension.py new file mode 100644 index 0000000..b9ef81e --- /dev/null +++ b/terraform-init/localstack_terraform_init/extension.py @@ -0,0 +1,79 @@ +import logging +import os +from typing import List + +from localstack import config +from localstack.extensions.api import Extension +from localstack.packages import InstallTarget, Package, PackageInstaller +from localstack.packages.core import PythonPackageInstaller +from localstack.packages.terraform import terraform_package +from localstack.runtime.init import ScriptRunner +from localstack.utils.run import run + +LOG = logging.getLogger(__name__) + + +class TflocalInitExtension(Extension): + # the extension itself is just used for discoverability + name = "localstack-terraform-init" + + def on_extension_load(self): + logging.getLogger("localstack_terraform_init").setLevel( + logging.DEBUG if config.DEBUG else logging.INFO + ) + + +class TflocalPackage(Package): + def __init__(self, default_version: str = "0.18.2"): + super().__init__(name="terraform_local", default_version=default_version) + + def _get_installer(self, version: str) -> PackageInstaller: + return TflocalPackageInstaller(version) + + def get_versions(self) -> List[str]: + return [self.default_version] + + +class TflocalPackageInstaller(PythonPackageInstaller): + def __init__(self, version: str): + super().__init__("terraform_local", version) + + +tflocal_package = TflocalPackage() + + +class TflocalScriptRunner(ScriptRunner): + name = "tflocal" + + def load(self, *args, **kwargs): + terraform_package.install() + tflocal_package.install() + + def should_run(self, script_file: str) -> bool: + if os.path.basename(script_file) == "main.tf": + return True + return False + + def run(self, path: str) -> None: + # create path to find ``terraform`` and ``tflocal`` binaries + # TODO: better way to define path + tf_path = terraform_package.get_installed_dir() + install_dir = tflocal_package.get_installer()._get_install_dir( + InstallTarget.VAR_LIBS + ) + tflocal_path = f"{install_dir}/bin" + env_path = f"{tflocal_path}:{tf_path}:{os.getenv('PATH')}" + + LOG.info("Applying terraform project from file %s", path) + # run tflocal + workdir = os.path.dirname(path) + LOG.debug("Initializing terraform provider in %s", workdir) + run( + ["tflocal", f"-chdir={workdir}", "init", "-input=false"], + env_vars={"PATH": env_path}, + ) + LOG.debug("Applying terraform file %s", path) + run( + ["tflocal", f"-chdir={workdir}", "apply", "-auto-approve"], + env_vars={"PATH": env_path}, + ) diff --git a/terraform-init/setup.cfg b/terraform-init/setup.cfg new file mode 100644 index 0000000..b17d97f --- /dev/null +++ b/terraform-init/setup.cfg @@ -0,0 +1,23 @@ +[metadata] +name = localstack-extension-terraform-init +version = 0.2.0 +summary = LocalStack Extension: LocalStack Terraform Init +url = https://github.com/localstack/localstack-extensions/tree/main/terraform-init +author = Thomas Rausch +author_email = thomas@localstack.cloud +description = LocalStack Extension for using Terraform files in init hooks +long_description = file: README.md +long_description_content_type = text/markdown; charset=UTF-8 + +[options] +zip_safe = False +packages = find: +install_requires = + localstack-core>=3.4 + plux + +[options.entry_points] +localstack.extensions = + localstack-terraform-init = localstack_terraform_init.extension:TflocalInitExtension +localstack.init.runner= + tflocal = localstack_terraform_init.extension:TflocalScriptRunner diff --git a/terraform-init/setup.py b/terraform-init/setup.py new file mode 100644 index 0000000..c823345 --- /dev/null +++ b/terraform-init/setup.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +from setuptools import setup + +setup() From 97ad22af15bc8b6c4ebea1e7e2dfea1e61cd50a9 Mon Sep 17 00:00:00 2001 From: Alexander Rashed <2796604+alexrashed@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:24:24 +0200 Subject: [PATCH 3/8] move localstack dependency to dev extra (#73) --- aws-replicator/setup.cfg | 4 ++-- diagnosis-viewer/Makefile | 3 +-- diagnosis-viewer/setup.cfg | 5 ++++- hello-world/Makefile | 3 +-- hello-world/setup.cfg | 6 ++++-- httpbin/Makefile | 3 +-- httpbin/setup.cfg | 5 ++++- mailhog/Makefile | 3 +-- mailhog/setup.cfg | 6 ++++-- miniflare/Makefile | 3 +-- miniflare/setup.cfg | 6 ++++-- openai/Makefile | 3 +-- openai/setup.cfg | 2 +- stripe/Makefile | 2 +- stripe/setup.cfg | 2 +- template/{{cookiecutter.project_slug}}/Makefile | 3 +-- template/{{cookiecutter.project_slug}}/setup.cfg | 6 ++++-- terraform-init/Makefile | 3 +-- terraform-init/setup.cfg | 5 ++++- 19 files changed, 41 insertions(+), 32 deletions(-) diff --git a/aws-replicator/setup.cfg b/aws-replicator/setup.cfg index f9a75d8..c738c19 100644 --- a/aws-replicator/setup.cfg +++ b/aws-replicator/setup.cfg @@ -18,9 +18,7 @@ install_requires = # TODO: currently requires a version pin, see note in auth_proxy.py botocore>=1.29.151 flask - localstack localstack-client - localstack-ext xmltodict # TODO: refactor the use of http2_server hypercorn @@ -37,6 +35,8 @@ install_requires = [options.extras_require] test = apispec + localstack-core + localstack-ext openapi-spec-validator pyproject-flake8 pytest diff --git a/diagnosis-viewer/Makefile b/diagnosis-viewer/Makefile index c86b76f..30feecb 100644 --- a/diagnosis-viewer/Makefile +++ b/diagnosis-viewer/Makefile @@ -8,7 +8,6 @@ venv: $(VENV_ACTIVATE) $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -18,7 +17,7 @@ clean: rm -rf *.egg-info/ install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/diagnosis-viewer/setup.cfg b/diagnosis-viewer/setup.cfg index 55a2437..b27ee38 100644 --- a/diagnosis-viewer/setup.cfg +++ b/diagnosis-viewer/setup.cfg @@ -13,9 +13,12 @@ long_description_content_type = text/markdown; charset=UTF-8 zip_safe = False packages = find: install_requires = - localstack>=1.4 diapretty +[options.extras_require] +dev = + localstack-core>=1.4 + [options.entry_points] localstack.extensions = diagnosis-viewer = diagnosis_viewer.extension:DiagnosisViewerExtension diff --git a/hello-world/Makefile b/hello-world/Makefile index 084fa9b..1cc5f9a 100644 --- a/hello-world/Makefile +++ b/hello-world/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel $(VENV_RUN); pip install --upgrade black isort pyproject-flake8 flake8-black flake8-isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -25,7 +24,7 @@ format: ## Run black and isort code formatter $(VENV_RUN); python -m isort helloworld; python -m black helloworld install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/hello-world/setup.cfg b/hello-world/setup.cfg index 2da1782..3e91181 100644 --- a/hello-world/setup.cfg +++ b/hello-world/setup.cfg @@ -12,8 +12,10 @@ author_email = thomas@localstack.cloud [options] zip_safe = False packages = find: -install_requires = - localstack>=1.0 + +[options.extras_require] +dev = + localstack-core>=1.0 [options.entry_points] localstack.extensions = diff --git a/httpbin/Makefile b/httpbin/Makefile index 480979a..ac6aefe 100644 --- a/httpbin/Makefile +++ b/httpbin/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel $(VENV_RUN); pip install --upgrade black isort pyproject-flake8 flake8-black flake8-isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -25,7 +24,7 @@ format: venv $(VENV_RUN); python -m isort .; python -m black . install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/httpbin/setup.cfg b/httpbin/setup.cfg index f79e303..ba63975 100644 --- a/httpbin/setup.cfg +++ b/httpbin/setup.cfg @@ -13,7 +13,6 @@ long_description_content_type = text/markdown; charset=UTF-8 zip_safe = False packages = find: install_requires = - localstack>=2.2 # requirements for vendored httpbin Flask MarkupSafe @@ -24,6 +23,10 @@ install_requires = gevent flasgger +[options.extras_require] +dev = + localstack-core>=2.2 + [options.entry_points] localstack.extensions = httpbin = localstack_httpbin.extension:HttpbinExtension diff --git a/mailhog/Makefile b/mailhog/Makefile index 5c13051..e636a5f 100644 --- a/mailhog/Makefile +++ b/mailhog/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel $(VENV_RUN); pip install --upgrade black isort pyproject-flake8 flake8-black flake8-isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -25,7 +24,7 @@ format: ## Run black and isort code formatter $(VENV_RUN); python -m isort mailhog; python -m black mailhog install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/mailhog/setup.cfg b/mailhog/setup.cfg index 0a5ee07..0446993 100644 --- a/mailhog/setup.cfg +++ b/mailhog/setup.cfg @@ -12,8 +12,10 @@ long_description_content_type = text/markdown; charset=UTF-8 [options] zip_safe = False packages = find: -install_requires = - localstack>=2.2 + +[options.extras_require] +dev = + localstack-core>=2.2 [options.entry_points] localstack.extensions = diff --git a/miniflare/Makefile b/miniflare/Makefile index 8490197..218eb31 100644 --- a/miniflare/Makefile +++ b/miniflare/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel $(VENV_RUN); pip install --upgrade black isort pyproject-flake8 flake8-black flake8-isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -25,7 +24,7 @@ format: ## Run black and isort code formatter $(VENV_RUN); python -m isort .; python -m black . install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/miniflare/setup.cfg b/miniflare/setup.cfg index c8aaaed..55761e0 100644 --- a/miniflare/setup.cfg +++ b/miniflare/setup.cfg @@ -12,8 +12,10 @@ author_email = waldemar@localstack.cloud [options] zip_safe = False packages = find: -install_requires = - localstack>=1.0.0 + +[options.extras_require] +dev = + localstack-core>=1.0.0 [options.entry_points] localstack.extensions = diff --git a/openai/Makefile b/openai/Makefile index d63477e..e299dc6 100644 --- a/openai/Makefile +++ b/openai/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux wheel $(VENV_RUN); pip install --upgrade black isort pyproject-flake8 flake8-black flake8-isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -25,7 +24,7 @@ format: ## Run black and isort code formatter $(VENV_RUN); python -m isort .; python -m black . install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/openai/setup.cfg b/openai/setup.cfg index 643dc48..681e4e6 100644 --- a/openai/setup.cfg +++ b/openai/setup.cfg @@ -24,7 +24,6 @@ zip_safe = False packages = find: install_requires = faker>=8.12.1 - localstack>=3.1 plux>=1.3 rolo>=0.3 test_requires = @@ -33,6 +32,7 @@ test_requires = [options.extras_require] dev = + localstack-core>=3.1 openai>=0.10.2,<1.0 pytest>=6.2.4 black==22.3.0 diff --git a/stripe/Makefile b/stripe/Makefile index 9b7963c..75b92e4 100644 --- a/stripe/Makefile +++ b/stripe/Makefile @@ -32,7 +32,7 @@ dist: venv $(VENV_ACTIVATE); python setup.py sdist bdist_wheel install: venv - $(VENV_ACTIVATE); python setup.py install + $(VENV_ACTIVATE); python -m pip install -e .[dev] upload: venv dist $(VENV_ACTIVATE); pip install --upgrade twine; twine upload dist/* diff --git a/stripe/setup.cfg b/stripe/setup.cfg index 1480f97..4de70aa 100644 --- a/stripe/setup.cfg +++ b/stripe/setup.cfg @@ -29,13 +29,13 @@ setup_requires = install_requires = stevedore>=3.4 plux>=1.3 - localstack>=1.0 localstack-localstripe>=1.13.8 test_requires = pytest>=6.2.4 [options.extras_require] dev = + localstack-core>=1.0 pytest>=6.2.4 black==22.3.0 isort==5.10.1 diff --git a/template/{{cookiecutter.project_slug}}/Makefile b/template/{{cookiecutter.project_slug}}/Makefile index 0f12f48..c3d6a3e 100644 --- a/template/{{cookiecutter.project_slug}}/Makefile +++ b/template/{{cookiecutter.project_slug}}/Makefile @@ -8,7 +8,6 @@ venv: $(VENV_ACTIVATE) $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -18,7 +17,7 @@ clean: rm -rf *.egg-info/ install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] dist: venv $(VENV_RUN); python setup.py sdist bdist_wheel diff --git a/template/{{cookiecutter.project_slug}}/setup.cfg b/template/{{cookiecutter.project_slug}}/setup.cfg index 2bb3f02..17145a0 100644 --- a/template/{{cookiecutter.project_slug}}/setup.cfg +++ b/template/{{cookiecutter.project_slug}}/setup.cfg @@ -12,8 +12,10 @@ long_description_content_type = text/markdown; charset=UTF-8 [options] zip_safe = False packages = find: -install_requires = - localstack>=1.0 + +[options.extras_require] +dev = + localstack-core>=1.0 [options.entry_points] localstack.extensions = diff --git a/terraform-init/Makefile b/terraform-init/Makefile index 42739d8..4ade53f 100644 --- a/terraform-init/Makefile +++ b/terraform-init/Makefile @@ -9,7 +9,6 @@ $(VENV_ACTIVATE): setup.py setup.cfg test -d .venv || $(VENV_BIN) .venv $(VENV_RUN); pip install --upgrade pip setuptools plux build $(VENV_RUN); pip install --upgrade black isort - $(VENV_RUN); pip install -e . touch $(VENV_DIR)/bin/activate clean: @@ -19,7 +18,7 @@ clean: rm -rf *.egg-info/ install: venv - $(VENV_RUN); python setup.py develop + $(VENV_RUN); python -m pip install -e .[dev] format: venv $(VENV_RUN); python -m isort .; python -m black . diff --git a/terraform-init/setup.cfg b/terraform-init/setup.cfg index b17d97f..ccaec5d 100644 --- a/terraform-init/setup.cfg +++ b/terraform-init/setup.cfg @@ -13,9 +13,12 @@ long_description_content_type = text/markdown; charset=UTF-8 zip_safe = False packages = find: install_requires = - localstack-core>=3.4 plux +[options.extras_require] +dev = + localstack-core>=3.4 + [options.entry_points] localstack.extensions = localstack-terraform-init = localstack_terraform_init.extension:TflocalInitExtension From 794cf6a6dc9d726bc5616c2a0fca43a5fd9018a1 Mon Sep 17 00:00:00 2001 From: Alexander Rashed <2796604+alexrashed@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:49:16 +0200 Subject: [PATCH 4/8] fix missed version bumps for httpbin, miniflare, terraform-init (#74) --- httpbin/setup.cfg | 2 +- miniflare/setup.cfg | 2 +- terraform-init/setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/httpbin/setup.cfg b/httpbin/setup.cfg index ba63975..79c0647 100644 --- a/httpbin/setup.cfg +++ b/httpbin/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-httpbin -version = 0.1.1 +version = 0.1.2 url = https://github.com/localstack/localstack-extensions/tree/main/httpbin author = LocalStack author_email = info@localstack.cloud diff --git a/miniflare/setup.cfg b/miniflare/setup.cfg index 55761e0..d668b3e 100644 --- a/miniflare/setup.cfg +++ b/miniflare/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-miniflare -version = 0.1.1 +version = 0.1.2 summary = LocalStack Extension: Miniflare description = This extension makes Miniflare (dev environment for Cloudflare workers) available directly in LocalStack long_description = file: README.md diff --git a/terraform-init/setup.cfg b/terraform-init/setup.cfg index ccaec5d..10bb9e3 100644 --- a/terraform-init/setup.cfg +++ b/terraform-init/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-terraform-init -version = 0.2.0 +version = 0.2.1 summary = LocalStack Extension: LocalStack Terraform Init url = https://github.com/localstack/localstack-extensions/tree/main/terraform-init author = Thomas Rausch From ea21a5296b16fe1f6a3938c0fa6c3e50bc7fffac Mon Sep 17 00:00:00 2001 From: Alexander Rashed <2796604+alexrashed@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:17:30 +0200 Subject: [PATCH 5/8] migrate imports from localstack_ext to localstack.pro.core (#72) --- aws-replicator/README.md | 2 ++ .../aws_replicator/client/auth_proxy.py | 13 ++++++++++++- aws-replicator/aws_replicator/client/cli.py | 13 ++++++++++--- .../aws_replicator/server/resource_replicator.py | 12 +++++++++--- aws-replicator/setup.cfg | 2 +- mailhog/mailhog/extension.py | 15 ++++++++++----- mailhog/mailhog/package.py | 1 + mailhog/mailhog/server.py | 1 + mailhog/setup.cfg | 2 +- 9 files changed, 47 insertions(+), 14 deletions(-) diff --git a/aws-replicator/README.md b/aws-replicator/README.md index 377e04a..b8bec51 100644 --- a/aws-replicator/README.md +++ b/aws-replicator/README.md @@ -152,6 +152,8 @@ localstack extensions install "git+https://github.com/localstack/localstack-exte ## Change Log +* `0.1.16`: Update imports for localstack >=3.6 compatibility +* `0.1.15`: Move localstack dependency installation to extra since it's provided at runtime * `0.1.14`: Install missing dependencies into proxy container for localstack >=3.4 compatibility * `0.1.13`: Add compatibility with localstack >=3.4; add http2-server; migrate to localstack auth login * `0.1.12`: Modify aws credentials text field type to password diff --git a/aws-replicator/aws_replicator/client/auth_proxy.py b/aws-replicator/aws_replicator/client/auth_proxy.py index 2e75dee..651b962 100644 --- a/aws-replicator/aws_replicator/client/auth_proxy.py +++ b/aws-replicator/aws_replicator/client/auth_proxy.py @@ -28,7 +28,6 @@ from localstack.utils.net import get_docker_host_from_container, get_free_tcp_port from localstack.utils.serving import Server from localstack.utils.strings import short_uid, to_bytes, to_str, truncate -from localstack_ext.bootstrap.licensingv2 import ENV_LOCALSTACK_API_KEY, ENV_LOCALSTACK_AUTH_TOKEN from requests import Response from aws_replicator import config as repl_config @@ -38,6 +37,18 @@ from .http2_server import run_server +try: + from localstack.pro.core.bootstrap.licensingv2 import ( + ENV_LOCALSTACK_API_KEY, + ENV_LOCALSTACK_AUTH_TOKEN, + ) +except ImportError: + # TODO remove once we don't need compatibility with <3.6 anymore + from localstack_ext.bootstrap.licensingv2 import ( + ENV_LOCALSTACK_API_KEY, + ENV_LOCALSTACK_AUTH_TOKEN, + ) + LOG = logging.getLogger(__name__) LOG.setLevel(logging.INFO) if config.DEBUG: diff --git a/aws-replicator/aws_replicator/client/cli.py b/aws-replicator/aws_replicator/client/cli.py index 422e6be..d199095 100644 --- a/aws-replicator/aws_replicator/client/cli.py +++ b/aws-replicator/aws_replicator/client/cli.py @@ -6,12 +6,19 @@ from localstack.cli import LocalstackCli, LocalstackCliPlugin, console from localstack.logging.setup import setup_logging from localstack.utils.files import load_file -from localstack_ext.bootstrap.auth import get_auth_headers -from localstack_ext.cli.aws import aws -from localstack_ext.config import is_api_key_configured from aws_replicator.shared.models import ProxyConfig, ProxyServiceConfig +try: + from localstack.pro.core.bootstrap.auth import get_auth_headers + from localstack.pro.core.cli.aws import aws + from localstack.pro.core.config import is_api_key_configured +except ImportError: + # TODO remove once we don't need compatibility with <3.6 anymore + from localstack_ext.bootstrap.auth import get_auth_headers + from localstack_ext.cli.aws import aws + from localstack_ext.config import is_api_key_configured + class AwsReplicatorPlugin(LocalstackCliPlugin): name = "aws-replicator" diff --git a/aws-replicator/aws_replicator/server/resource_replicator.py b/aws-replicator/aws_replicator/server/resource_replicator.py index 7c7c2ba..63687cb 100644 --- a/aws-replicator/aws_replicator/server/resource_replicator.py +++ b/aws-replicator/aws_replicator/server/resource_replicator.py @@ -66,9 +66,15 @@ def _get_cf_model_class(self, resource: Dict) -> Optional[Type]: def _load_resource_models(self): if not hasattr(template_deployer, "_ls_patch_applied"): - from localstack_ext.services.cloudformation.cloudformation_extended import ( - patch_cloudformation, - ) + try: + from localstack.pro.core.services.cloudformation.cloudformation_extended import ( + patch_cloudformation, + ) + except ImportError: + # TODO remove once we don't need compatibility with <3.6 anymore + from localstack_ext.services.cloudformation.cloudformation_extended import ( + patch_cloudformation, + ) patch_cloudformation() template_deployer._ls_patch_applied = True diff --git a/aws-replicator/setup.cfg b/aws-replicator/setup.cfg index c738c19..6f025f9 100644 --- a/aws-replicator/setup.cfg +++ b/aws-replicator/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-aws-replicator -version = 0.1.14 +version = 0.1.16 summary = LocalStack Extension: AWS replicator description = Replicate AWS resources into your LocalStack instance long_description = file: README.md diff --git a/mailhog/mailhog/extension.py b/mailhog/mailhog/extension.py index 696833c..2347086 100644 --- a/mailhog/mailhog/extension.py +++ b/mailhog/mailhog/extension.py @@ -4,7 +4,12 @@ from localstack import config, constants from localstack.extensions.api import Extension, http -from localstack_ext import config as config_ext + +try: + from localstack.pro.core import config as config_pro +except ImportError: + # TODO remove once we don't need compatibility with <3.6 anymore + from localstack_ext import config as config_pro if TYPE_CHECKING: # conditional import for type checking during development. the actual import is deferred to plugin loading @@ -57,10 +62,10 @@ def on_platform_start(self): LOG.info("starting mailhog server") self.server.start() - if not config_ext.SMTP_HOST: - config_ext.SMTP_HOST = f"localhost:{self.server.smtp_port}" - os.environ["SMTP_HOST"] = config_ext.SMTP_HOST - LOG.info("configuring SMTP host to internal mailhog smtp: %s", config_ext.SMTP_HOST) + if not config_pro.SMTP_HOST: + config_pro.SMTP_HOST = f"localhost:{self.server.smtp_port}" + os.environ["SMTP_HOST"] = config_pro.SMTP_HOST + LOG.info("configuring SMTP host to internal mailhog smtp: %s", config_pro.SMTP_HOST) def on_platform_ready(self): # FIXME: reconcile with LOCALSTACK_HOST. the URL should be reachable from the host (the idea is diff --git a/mailhog/mailhog/package.py b/mailhog/mailhog/package.py index 960a1f2..2da60c7 100644 --- a/mailhog/mailhog/package.py +++ b/mailhog/mailhog/package.py @@ -1,6 +1,7 @@ """ Package for mailhog that downloads the mailhog binary from https://github.com/mailhog/MailHog. """ + import os from functools import lru_cache diff --git a/mailhog/mailhog/server.py b/mailhog/mailhog/server.py index c81d423..b1f799b 100644 --- a/mailhog/mailhog/server.py +++ b/mailhog/mailhog/server.py @@ -1,6 +1,7 @@ """ Tools to run the mailhog service. """ + import logging import os diff --git a/mailhog/setup.cfg b/mailhog/setup.cfg index 0446993..369f45d 100644 --- a/mailhog/setup.cfg +++ b/mailhog/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-mailhog -version = 0.1.0 +version = 0.1.2 url = https://github.com/localstack/localstack-extensions/tree/main/mailhog author = LocalStack author_email = info@localstack.cloud From 2f0ac6f5cf75c47eac7d702bda3004476c953b8b Mon Sep 17 00:00:00 2001 From: robertlcx <152975295+robertlcx@users.noreply.github.com> Date: Wed, 17 Jul 2024 18:11:11 +0300 Subject: [PATCH 6/8] AWS Replicator - regex match `secretsmanager` secrets based on their ARNs (#70) Co-authored-by: Robert Lucian --- aws-replicator/README.md | 1 + .../aws_replicator/server/aws_request_forwarder.py | 8 +++++++- aws-replicator/setup.cfg | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/aws-replicator/README.md b/aws-replicator/README.md index b8bec51..8f8fe69 100644 --- a/aws-replicator/README.md +++ b/aws-replicator/README.md @@ -152,6 +152,7 @@ localstack extensions install "git+https://github.com/localstack/localstack-exte ## Change Log +* `0.1.17`: Add basic support for ARN-based pattern-matching for `secretsmanager` resources * `0.1.16`: Update imports for localstack >=3.6 compatibility * `0.1.15`: Move localstack dependency installation to extra since it's provided at runtime * `0.1.14`: Install missing dependencies into proxy container for localstack >=3.4 compatibility diff --git a/aws-replicator/aws_replicator/server/aws_request_forwarder.py b/aws-replicator/aws_replicator/server/aws_request_forwarder.py index c2e6ba1..a9d1021 100644 --- a/aws-replicator/aws_replicator/server/aws_request_forwarder.py +++ b/aws-replicator/aws_replicator/server/aws_request_forwarder.py @@ -9,7 +9,7 @@ from localstack.constants import APPLICATION_JSON, LOCALHOST, LOCALHOST_HOSTNAME from localstack.http import Response from localstack.utils.aws import arns -from localstack.utils.aws.arns import sqs_queue_arn +from localstack.utils.aws.arns import secretsmanager_secret_arn, sqs_queue_arn from localstack.utils.aws.aws_stack import get_valid_regions from localstack.utils.aws.request_context import mock_aws_request_headers from localstack.utils.collections import ensure_list @@ -118,6 +118,12 @@ def _request_matches_resource( if re.match(resource_name_pattern, candidate): return True return False + if service_name == "secretsmanager": + secret_id = context.service_request.get("SecretId") or "" + secret_arn = secretsmanager_secret_arn( + secret_id, account_id=context.account_id, region_name=context.region + ) + return bool(re.match(resource_name_pattern, secret_arn)) # TODO: add more resource patterns return True diff --git a/aws-replicator/setup.cfg b/aws-replicator/setup.cfg index 6f025f9..0de75ab 100644 --- a/aws-replicator/setup.cfg +++ b/aws-replicator/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-aws-replicator -version = 0.1.16 +version = 0.1.17 summary = LocalStack Extension: AWS replicator description = Replicate AWS resources into your LocalStack instance long_description = file: README.md From 1e7ec821ba4645054a3293677a6268eeac062485 Mon Sep 17 00:00:00 2001 From: Waldemar Hummer Date: Thu, 25 Jul 2024 10:13:01 +0200 Subject: [PATCH 7/8] fix CLI command to enable starting the proxy from within Docker (#77) --- .github/workflows/aws-replicator.yml | 12 +++++++++--- aws-replicator/Makefile | 9 ++++++--- aws-replicator/README.md | 1 + aws-replicator/aws_replicator/client/auth_proxy.py | 9 ++++++--- aws-replicator/setup.cfg | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/aws-replicator.yml b/.github/workflows/aws-replicator.yml index 49bd888..b3aaefb 100644 --- a/.github/workflows/aws-replicator.yml +++ b/.github/workflows/aws-replicator.yml @@ -20,10 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Set up Terraform CLI uses: hashicorp/setup-terraform@v2 @@ -32,8 +32,10 @@ jobs: env: LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} run: | + set -e docker pull localstack/localstack-pro & docker pull public.ecr.aws/lambda/python:3.8 & + # install latest CLI packages (dev releases) pip install --upgrade --pre localstack localstack-ext @@ -48,7 +50,11 @@ jobs: localstack extensions init ( cd aws-replicator - make install && make build && make enable + make install + . .venv/bin/activate + pip install --upgrade --pre localstack localstack-ext + make build + make enable ) # install awslocal/tflocal command lines diff --git a/aws-replicator/Makefile b/aws-replicator/Makefile index 0b79fc2..e0b920c 100644 --- a/aws-replicator/Makefile +++ b/aws-replicator/Makefile @@ -5,6 +5,9 @@ VENV_RUN = . $(VENV_ACTIVATE) TEST_PATH ?= tests PIP_CMD ?= pip +usage: ## Show this help + @grep -Fh "##" $(MAKEFILE_LIST) | grep -Fv fgrep | sed -e 's/:.*##\s*/##/g' | awk -F'##' '{ printf "%-25s %s\n", $$1, $$2 }' + venv: $(VENV_ACTIVATE) $(VENV_ACTIVATE): setup.py setup.cfg @@ -40,13 +43,13 @@ build: ## Build the extension cp -r setup.py setup.cfg README.md aws_replicator build/ (cd build && python setup.py sdist) -enable: $(wildcard ./build/dist/localstack-extension-aws-replicator-*.tar.gz) ## Enable the extension in LocalStack +enable: $(wildcard ./build/dist/localstack_extension_aws_replicator-*.tar.gz) ## Enable the extension in LocalStack $(VENV_RUN); \ pip uninstall --yes localstack-extension-aws-replicator; \ - localstack extensions -v install file://./$? + localstack extensions -v install file://$? publish: clean-dist venv dist - $(VENV_RUN); pip install --upgrade twine; twine upload dist/* + $(VENV_RUN); cd build; pip install --upgrade twine; twine upload dist/* clean-dist: clean rm -rf dist/ diff --git a/aws-replicator/README.md b/aws-replicator/README.md index 8f8fe69..2ce6107 100644 --- a/aws-replicator/README.md +++ b/aws-replicator/README.md @@ -152,6 +152,7 @@ localstack extensions install "git+https://github.com/localstack/localstack-exte ## Change Log +* `0.1.18`: Update environment check to use SDK Docker client and enable starting the proxy from within Docker (e.g., from the LS main container as part of an init script) * `0.1.17`: Add basic support for ARN-based pattern-matching for `secretsmanager` resources * `0.1.16`: Update imports for localstack >=3.6 compatibility * `0.1.15`: Move localstack dependency installation to extra since it's provided at runtime diff --git a/aws-replicator/aws_replicator/client/auth_proxy.py b/aws-replicator/aws_replicator/client/auth_proxy.py index 651b962..e79fa2f 100644 --- a/aws-replicator/aws_replicator/client/auth_proxy.py +++ b/aws-replicator/aws_replicator/client/auth_proxy.py @@ -12,7 +12,6 @@ import requests from botocore.awsrequest import AWSPreparedRequest from botocore.model import OperationModel -from localstack import config from localstack import config as localstack_config from localstack.aws.spec import load_service from localstack.config import external_service_url @@ -51,7 +50,7 @@ LOG = logging.getLogger(__name__) LOG.setLevel(logging.INFO) -if config.DEBUG: +if localstack_config.DEBUG: LOG.setLevel(logging.DEBUG) # TODO make configurable @@ -371,10 +370,14 @@ def start_aws_auth_proxy_in_container( target_host = get_docker_host_from_container() env_vars["LOCALSTACK_HOST"] = target_host + # Use the Docker SDK command either if quiet mode is enabled, or if we're executing + # in Docker itself (e.g., within the LocalStack main container, as part of an init script) + use_docker_sdk_command = quiet or localstack_config.is_in_docker + try: print("Proxy container is ready.") command = f"{venv_activate}; localstack aws proxy -c {CONTAINER_CONFIG_FILE} -p {port} --host 0.0.0.0 > {CONTAINER_LOG_FILE} 2>&1" - if quiet: + if use_docker_sdk_command: DOCKER_CLIENT.exec_in_container( container_name, command=["bash", "-c", command], env_vars=env_vars, interactive=True ) diff --git a/aws-replicator/setup.cfg b/aws-replicator/setup.cfg index 0de75ab..fd4d9ee 100644 --- a/aws-replicator/setup.cfg +++ b/aws-replicator/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = localstack-extension-aws-replicator -version = 0.1.17 +version = 0.1.18 summary = LocalStack Extension: AWS replicator description = Replicate AWS resources into your LocalStack instance long_description = file: README.md From 3ff9fab7729609dea95281de9a23b191a0fa6aea Mon Sep 17 00:00:00 2001 From: Thomas Rausch Date: Fri, 16 Aug 2024 19:35:54 +0200 Subject: [PATCH 8/8] introduce templates/ directory and add basic template (#78) Co-authored-by: Luca Pivetta <36865043+Pive01@users.noreply.github.com> --- template/README.md | 3 ++ templates/basic/README.md | 10 ++++++ templates/basic/cookiecutter.json | 11 ++++++ .../{{cookiecutter.project_slug}}/.gitignore | 5 +++ .../{{cookiecutter.project_slug}}/Makefile | 32 +++++++++++++++++ .../{{cookiecutter.project_slug}}/README.md | 34 +++++++++++++++++++ .../pyproject.toml | 29 ++++++++++++++++ .../{{cookiecutter.module_name}}/__init__.py | 1 + .../{{cookiecutter.module_name}}/extension.py | 22 ++++++++++++ 9 files changed, 147 insertions(+) create mode 100644 templates/basic/README.md create mode 100644 templates/basic/cookiecutter.json create mode 100644 templates/basic/{{cookiecutter.project_slug}}/.gitignore create mode 100644 templates/basic/{{cookiecutter.project_slug}}/Makefile create mode 100644 templates/basic/{{cookiecutter.project_slug}}/README.md create mode 100644 templates/basic/{{cookiecutter.project_slug}}/pyproject.toml create mode 100644 templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/__init__.py create mode 100644 templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/extension.py diff --git a/template/README.md b/template/README.md index 0213a57..3ed9bbb 100644 --- a/template/README.md +++ b/template/README.md @@ -1,6 +1,9 @@ Extension Template ================== +> [!NOTE] +> This template is used for localstack CLI versions <= 3.6.0. For later version see https://github.com/localstack/localstack-extensions/tree/main/templates + This is a [cookiecutter](https://github.com/cookiecutter/cookiecutter) template that is used when you invoke. ```console diff --git a/templates/basic/README.md b/templates/basic/README.md new file mode 100644 index 0000000..0213a57 --- /dev/null +++ b/templates/basic/README.md @@ -0,0 +1,10 @@ +Extension Template +================== + +This is a [cookiecutter](https://github.com/cookiecutter/cookiecutter) template that is used when you invoke. + +```console +localstack extensions dev new +``` + +It contains a simple python distribution config, and some boilerplate extension code. diff --git a/templates/basic/cookiecutter.json b/templates/basic/cookiecutter.json new file mode 100644 index 0000000..8b4ead9 --- /dev/null +++ b/templates/basic/cookiecutter.json @@ -0,0 +1,11 @@ +{ + "project_name": "My LocalStack Extension", + "project_short_description": "All the boilerplate you need to create a LocalStack extension.", + "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}", + "module_name": "{{ cookiecutter.project_slug.replace('-', '_') }}", + "class_name": "{{ cookiecutter.project_name.replace('-', ' ').replace('_', ' ').title().replace(' ', '') }}", + "full_name": "Jane Doe", + "email": "jane@example.com", + "github_username": "janedoe", + "version": "0.1.0" +} \ No newline at end of file diff --git a/templates/basic/{{cookiecutter.project_slug}}/.gitignore b/templates/basic/{{cookiecutter.project_slug}}/.gitignore new file mode 100644 index 0000000..77be714 --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/.gitignore @@ -0,0 +1,5 @@ +.venv +dist +build +**/*.egg-info +.eggs \ No newline at end of file diff --git a/templates/basic/{{cookiecutter.project_slug}}/Makefile b/templates/basic/{{cookiecutter.project_slug}}/Makefile new file mode 100644 index 0000000..7059d14 --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/Makefile @@ -0,0 +1,32 @@ +VENV_BIN = python3 -m venv +VENV_DIR ?= .venv +VENV_ACTIVATE = $(VENV_DIR)/bin/activate +VENV_RUN = . $(VENV_ACTIVATE) + +venv: $(VENV_ACTIVATE) + +$(VENV_ACTIVATE): pyproject.toml + test -d .venv || $(VENV_BIN) .venv + $(VENV_RUN); pip install --upgrade pip setuptools plux + $(VENV_RUN); pip install -e .[dev] + touch $(VENV_DIR)/bin/activate + +clean: + rm -rf .venv/ + rm -rf build/ + rm -rf .eggs/ + rm -rf *.egg-info/ + +install: venv + $(VENV_RUN); python -m plux entrypoints + +dist: venv + $(VENV_RUN); python -m build + +publish: clean-dist venv dist + $(VENV_RUN); pip install --upgrade twine; twine upload dist/* + +clean-dist: clean + rm -rf dist/ + +.PHONY: clean clean-dist dist install publish diff --git a/templates/basic/{{cookiecutter.project_slug}}/README.md b/templates/basic/{{cookiecutter.project_slug}}/README.md new file mode 100644 index 0000000..7240972 --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/README.md @@ -0,0 +1,34 @@ +{{ cookiecutter.project_name }} +=============================== + +{{ cookiecutter.project_short_description }} + +## Install local development version + +To install the extension into localstack in developer mode, you will need Python 3.10, and create a virtual environment in the extensions project. + +In the newly generated project, simply run + +```bash +make install +``` + +Then, to enable the extension for LocalStack, run + +```bash +localstack extensions dev enable . +``` + +You can then start LocalStack with `EXTENSION_DEV_MODE=1` to load all enabled extensions: + +```bash +EXTENSION_DEV_MODE=1 localstack start +``` + +## Install from GitHub repository + +To distribute your extension, simply upload it to your github account. Your extension can then be installed via: + +```bash +localstack extensions install "git+https://github.com/{{cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/#egg={{ cookiecutter.project_slug }}" +``` diff --git a/templates/basic/{{cookiecutter.project_slug}}/pyproject.toml b/templates/basic/{{cookiecutter.project_slug}}/pyproject.toml new file mode 100644 index 0000000..af0a134 --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools", 'wheel', 'plux>=1.3.1'] +build-backend = "setuptools.build_meta" + +[project] +name = "{{ cookiecutter.project_slug }}" +version = "{{ cookiecutter.version }}" +description = "LocalStack Extension: {{ cookiecutter.project_name }}" +readme = {file = "README.md", content-type = "text/markdown; charset=UTF-8"} +requires-python = ">=3.8" +license = {text = "UNLICENSED"} +authors = [ + { name = "{{ cookiecutter.full_name }}", email = "{{ cookiecutter.email }}" } +] +keywords = ["localstack", "localstack-extension", "extension"] +classifiers = [] +dependencies = [ +] + +[project.urls] +Homepage = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}" + +[project.optional-dependencies] +dev = [ + "localstack>=0.0.0.dev" +] + +[project.entry-points."localstack.extensions"] +{{ cookiecutter.module_name }} = "{{ cookiecutter.module_name }}.extension:{{ cookiecutter.class_name }}" diff --git a/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/__init__.py b/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/__init__.py new file mode 100644 index 0000000..4b62a2f --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/__init__.py @@ -0,0 +1 @@ +name = "{{ cookiecutter.module_name }}" diff --git a/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/extension.py b/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/extension.py new file mode 100644 index 0000000..3c3671a --- /dev/null +++ b/templates/basic/{{cookiecutter.project_slug}}/{{cookiecutter.module_name}}/extension.py @@ -0,0 +1,22 @@ +from localstack.extensions.api import Extension, http, aws + +class {{ cookiecutter.class_name }}(Extension): + name = "{{ cookiecutter.project_slug }}" + + def on_extension_load(self): + print("MyExtension: extension is loaded") + + def on_platform_start(self): + print("MyExtension: localstack is starting") + + def on_platform_ready(self): + print("MyExtension: localstack is running") + + def update_gateway_routes(self, router: http.Router[http.RouteHandler]): + pass + + def update_request_handlers(self, handlers: aws.CompositeHandler): + pass + + def update_response_handlers(self, handlers: aws.CompositeResponseHandler): + pass