Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/wiremock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
docker pull "$IMAGE_NAME"
docker pull wiremock/wiremock
docker pull public.ecr.aws/lambda/python:3.12
pip install localstack terraform-local awscli-local[ver1]
pip install localstack terraform-local awscli-local

make install
make lint
Expand Down
6 changes: 3 additions & 3 deletions keycloak/localstack_keycloak/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from localstack_extensions.utils.docker import ProxiedDockerContainerExtension

from .utils import (
DEFAULT_AUDIENCE,
DEFAULT_CLIENT_SECRET,
DEFAULT_REALM,
DEFAULT_VERSION,
ENV_KEYCLOAK_DEFAULT_PASSWORD,
ENV_KEYCLOAK_DEFAULT_USER,
ENV_KEYCLOAK_FLAGS,
Expand All @@ -19,9 +22,6 @@
ENV_KEYCLOAK_VERSION,
KEYCLOAK_HTTP_PORT,
KEYCLOAK_MGMT_PORT,
DEFAULT_AUDIENCE,
DEFAULT_REALM,
DEFAULT_VERSION,
get_default_client_config,
get_default_realm_config,
get_env,
Expand Down
16 changes: 11 additions & 5 deletions keycloak/sample-app/cdk/stacks/api_stack.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import os

from aws_cdk import (
Stack,
Duration,
aws_lambda as lambda_,
RemovalPolicy,
Stack,
)
from aws_cdk import (
aws_apigateway as apigw,
)
from aws_cdk import (
aws_dynamodb as dynamodb,
aws_iam as iam,
RemovalPolicy,
)
from aws_cdk import (
aws_lambda as lambda_,
)
from constructs import Construct
import os


class KeycloakSampleApiStack(Stack):
Expand Down
2 changes: 0 additions & 2 deletions keycloak/sample-app/lambda/authorizer/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
"""

import base64
import hashlib
import hmac
import json
import os
import urllib.request
Expand Down
13 changes: 8 additions & 5 deletions keycloak/tests/test_extension.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import json
import base64
import json
import time

import boto3
import pytest
import requests
import boto3
from botocore.config import Config


LOCALSTACK_URL = "http://localhost:4566"
KEYCLOAK_URL = "http://keycloak.localhost.localstack.cloud:4566"
KEYCLOAK_DIRECT_URL = "http://localhost:8080" # Direct access to Keycloak HTTP port
Expand Down Expand Up @@ -189,7 +189,7 @@ class TestUserManagement:

def test_create_user_with_required_profile_fields(self, admin_token):
"""Verify user creation works with required Keycloak 26+ profile fields.

Keycloak 26+ requires email, firstName, lastName for users to be "fully set up".
Password must be set separately via reset-password endpoint.
"""
Expand Down Expand Up @@ -302,7 +302,10 @@ def test_incomplete_user_fails_password_grant(self, admin_token):
timeout=30,
)
assert token_response.status_code != 200
assert "not fully set up" in token_response.json().get("error_description", "").lower()
assert (
"not fully set up"
in token_response.json().get("error_description", "").lower()
)

# Cleanup
requests.delete(f"{users_url}/{user_id}", headers=headers, timeout=10)
Expand Down
2 changes: 1 addition & 1 deletion paradedb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ conn = psycopg2.connect(
port=5432,
database="mydatabase",
user="myuser",
password="mypassword"
password="mypassword",
)
```

Expand Down
4 changes: 2 additions & 2 deletions paradedb/localstack_paradedb/extension.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os
import socket

from localstack_extensions.utils.docker import ProxiedDockerContainerExtension
from localstack import config
from localstack_extensions.utils.docker import ProxiedDockerContainerExtension

# Environment variables for configuration
ENV_POSTGRES_USER = "PARADEDB_POSTGRES_USER"
Expand Down Expand Up @@ -88,7 +88,7 @@ def _check_tcp_port(self, host: str, port: int, timeout: float = 2.0) -> None:
try:
sock.connect((host, port))
sock.close()
except (socket.timeout, socket.error) as e:
except (TimeoutError, OSError) as e:
raise AssertionError(f"Port {port} not ready: {e}")

def get_connection_info(self) -> dict:
Expand Down
3 changes: 2 additions & 1 deletion paradedb/tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from localstack.utils.strings import short_uid
from localstack.utils.sync import retry


# Connection details for ParadeDB
# Connect through LocalStack gateway with TCP proxying
HOST = "paradedb.localhost.localstack.cloud"
Expand All @@ -15,6 +14,7 @@

def get_connection():
"""Create a connection to ParadeDB, retrying until the server is ready."""

def _connect():
return psycopg2.connect(
host=HOST,
Expand All @@ -23,6 +23,7 @@ def _connect():
password=PASSWORD,
database=DATABASE,
)

return retry(_connect, retries=15, sleep=2.0)


Expand Down
18 changes: 18 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[lint]
ignore = [
"B006", # TODO Do not use mutable data structures for argument defaults
"B007", # TODO Loop control variable x not used within loop body
"B017", # TODO `pytest.raises(Exception)` should be considered evil
"B019", # TODO Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
"B023", # TODO Function definition does not bind loop variable `server`
"B024", # TODO x is an abstract base class, but it has no abstract methods
"B026", # TODO Star-arg unpacking after a keyword argument is strongly discouraged
"B027", # TODO `Server.do_shutdown` is an empty method in an abstract base class, but has no abstract decorator
"B904", # TODO Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
"C901", # TODO function is too complex
"E402", # TODO Module level import not at top of file
"E501", # E501 Line too long - handled by black, see https://docs.astral.sh/ruff/faq/#is-ruff-compatible-with-black
"E741", # TODO Ambiguous variable name: `l`
"T201", # TODO `print` found
]
select = ["B", "C", "E", "F", "I", "W", "T", "B9", "G", "UP", "TID251"]
10 changes: 8 additions & 2 deletions typedb/tests/test_extension.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import requests
import httpx
import requests
from localstack.utils.strings import short_uid
from typedb.driver import TypeDB, Credentials, DriverOptions, TransactionType, DriverTlsConfig
from typedb.driver import (
Credentials,
DriverOptions,
DriverTlsConfig,
TransactionType,
TypeDB,
)


def test_connect_to_db_via_http_api():
Expand Down
8 changes: 4 additions & 4 deletions utils/localstack_extensions/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
ProxyResource,
)
from localstack_extensions.utils.h2_proxy import (
ProxyRequestMatcher,
TcpForwarder,
apply_http2_patches_for_grpc_support,
get_frames_from_http2_stream,
get_headers_from_data_stream,
get_headers_from_frames,
get_frames_from_http2_stream,
ProxyRequestMatcher,
)

__all__ = [
"ProxiedDockerContainerExtension",
"ProxyRequestMatcher",
"ProxyResource",
"TcpForwarder",
"apply_http2_patches_for_grpc_support",
"get_frames_from_http2_stream",
"get_headers_from_data_stream",
"get_headers_from_frames",
"get_frames_from_http2_stream",
"ProxyRequestMatcher",
]
31 changes: 16 additions & 15 deletions utils/localstack_extensions/utils/docker.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import re
import logging
import re
from collections.abc import Callable
from functools import cache
from typing import Callable
import requests

import requests
from localstack.config import is_env_true
from localstack_extensions.utils.h2_proxy import (
apply_http2_patches_for_grpc_support,
)
from localstack.utils.docker_utils import DOCKER_CLIENT
from localstack.extensions.api import Extension, http
from localstack.http import Request
from localstack.utils.container_utils.container_client import (
PortMappings,
SimpleVolumeBind,
)
from localstack.utils.docker_utils import DOCKER_CLIENT
from localstack.utils.net import get_addressable_container_host
from localstack.utils.sync import retry
from rolo import route
from rolo.proxy import Proxy
from rolo.routing import RuleAdapter, WithHost
from werkzeug.datastructures import Headers

from localstack_extensions.utils.h2_proxy import (
apply_http2_patches_for_grpc_support,
)

LOG = logging.getLogger(__name__)


Expand Down Expand Up @@ -104,7 +105,10 @@ def __init__(
tcp_ports: list[int] | None = None,
):
try:
from localstack.pro.core.utils.container.registry_strategies import CustomizableRegistryStrategy
from localstack.pro.core.utils.container.registry_strategies import (
CustomizableRegistryStrategy,
)

self.image_name = CustomizableRegistryStrategy().resolve(image_name)
except ImportError:
self.image_name = image_name
Expand Down Expand Up @@ -138,10 +142,7 @@ def update_gateway_routes(self, router: http.Router[http.RouteHandler]):
# Determine if HTTP proxy should be set up. Skip it when all container ports are
# TCP-only and no host restriction is set, since a catch-all HTTP proxy would
# intercept all requests and break other services.
uses_http = (
self.host
and set(self.container_ports) - set(self.tcp_ports or [])
)
uses_http = self.host and set(self.container_ports) - set(self.tcp_ports or [])

if uses_http:
# add resource for HTTP/1.1 requests
Expand Down Expand Up @@ -179,8 +180,8 @@ def _setup_tcp_protocol_routing(self):
matcher = getattr(self, "tcp_connection_matcher", None)
if not matcher:
LOG.warning(
f"Extension {self.name} has tcp_ports but no tcp_connection_matcher(). "
"TCP routing will not work without a matcher."
"Extension %s has tcp_ports but no tcp_connection_matcher(). "
"TCP routing will not work without a matcher.", self.name
)
return

Expand All @@ -199,7 +200,7 @@ def _setup_tcp_protocol_routing(self):
)

LOG.info(
f"Registered TCP extension {self.name} -> {self.container_host}:{target_port} on gateway"
"Registered TCP extension %s -> %s:%s on gateway", self.name, self.container_host, target_port
)

def http2_request_matcher(self, headers: Headers) -> bool:
Expand Down
16 changes: 7 additions & 9 deletions utils/localstack_extensions/utils/h2_proxy.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import logging
import socket
from collections.abc import Callable, Iterable
from enum import Enum
from typing import Iterable, Callable

from h2.frame_buffer import FrameBuffer
from hpack import Decoder
from hyperframe.frame import HeadersFrame, Frame
from twisted.internet import reactor

from hyperframe.frame import Frame, HeadersFrame
from localstack.utils.patch import patch
from twisted.internet import reactor
from twisted.web._http2 import H2Connection
from werkzeug.datastructures import Headers

Expand Down Expand Up @@ -50,7 +49,7 @@ def send(self, data):
def close(self):
if self._closed:
return
LOG.debug(f"Closing connection to upstream HTTP2 server on port {self.port}")
LOG.debug("Closing connection to upstream HTTP2 server on port %d", self.port)
self._closed = True
try:
self._socket.shutdown(socket.SHUT_RDWR)
Expand All @@ -70,7 +69,7 @@ def apply_http2_patches_for_grpc_support(
Apply some patches to proxy incoming gRPC requests and forward them to a target port.
Note: this is a very brute-force approach and needs to be fixed/enhanced over time!
"""
LOG.debug(f"Enabling proxying to backend {target_host}:{target_port}")
LOG.debug("Enabling proxying to backend %s:%s", target_host, target_port)
global patched_connection
assert not patched_connection, (
"It is not safe to patch H2Connection twice with this function"
Expand All @@ -96,7 +95,7 @@ class ForwardingBuffer:
def __init__(self, http_response_stream):
self.http_response_stream = http_response_stream
LOG.debug(
f"Starting TCP forwarder to port {target_port} for new HTTP2 connection"
"Starting TCP forwarder to port %s for new HTTP2 connection", target_port
)
self.backend = TcpForwarder(target_port, host=target_host)
self.buffer = []
Expand Down Expand Up @@ -175,7 +174,6 @@ def get_frames_from_http2_stream(data: bytes) -> Iterable[Frame]:
buffer.max_frame_size = 16384
try:
buffer.add_data(data)
for frame in buffer:
yield frame
yield from buffer
except Exception:
pass
12 changes: 6 additions & 6 deletions utils/localstack_extensions/utils/tcp_protocol_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
"""

import logging

from localstack import config
from localstack.utils.patch import patch
from twisted.internet import reactor
from twisted.protocols.portforward import ProxyClient, ProxyClientFactory
from twisted.web.http import HTTPChannel

from localstack.utils.patch import patch
from localstack import config

LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG if config.DEBUG else logging.INFO)

Expand Down Expand Up @@ -117,7 +117,7 @@ def _patched_dataReceived(fn, self, data):
reactor.connectTCP(backend_host, backend_port, client_factory)
return
except Exception as e:
LOG.debug(f"Error in matcher for {ext_name}: {e}")
LOG.debug("Error in matcher for %s: %s", ext_name, e)
continue

# No extension claimed the connection
Expand Down Expand Up @@ -164,7 +164,7 @@ def register_tcp_extension(
"""
_tcp_extensions.append((extension_name, matcher, backend_host, backend_port))
LOG.info(
f"Registered TCP extension {extension_name} -> {backend_host}:{backend_port}"
"Registered TCP extension %s -> %s:%s", extension_name, backend_host, backend_port
)


Expand All @@ -176,4 +176,4 @@ def unregister_tcp_extension(extension_name: str):
for name, matcher, host, port in _tcp_extensions
if name != extension_name
]
LOG.info(f"Unregistered TCP extension {extension_name}")
LOG.info("Unregistered TCP extension %s", extension_name)
Loading