second first commit

This commit is contained in:
Zakaria
2026-09-22 08:57:00 -04:00
parent e88d7e2ff3
commit 0aef7fb2d8
4766 changed files with 1236986 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
# Stage 1: Build
FROM eclipse-temurin:25-jdk-noble AS build
WORKDIR /build
RUN apt-get update && apt-get install -y maven && rm -rf /var/lib/apt/lists/*
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src/ src/
RUN mvn clean package -DskipTests -q
# Stage 2: Runtime
FROM eclipse-temurin:25-jre-noble
ARG VERSION=latest
ENV FLOCI_VERSION=${VERSION}
ENV FLOCI_STORAGE_PERSISTENT_PATH=/app/data
ENV AWS_DEFAULT_REGION=us-east-1
ENV AWS_ACCESS_KEY_ID=test
ENV AWS_SECRET_ACCESS_KEY=test
ENV AWS_CONFIG_FILE=/etc/floci/aws/config
# Privilege drop happens in the entrypoint through coreutils chroot, so no gosu
# download; wget stays for the HEALTHCHECK below.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates passwd python3 python3-pip wget; \
rm -rf /var/lib/apt/lists/*; \
pip3 install --no-cache-dir --break-system-packages awscli boto3; \
aws_bin="$(command -v aws)"; \
mv "$aws_bin" /usr/bin/aws.real; \
printf '#!/bin/sh\nexec /usr/bin/aws.real --endpoint-url=http://localhost:4566 "$@"\n' > /usr/bin/aws; \
chmod +x /usr/bin/aws; \
useradd -r -u 1001 -g root -d /app -s /sbin/nologin floci
WORKDIR /app
# cli_follow_urlparam: the bundled CLI is v1 (pip ships no v2), where a parameter value that looks
# like a URL is *fetched* and its body used as the value. An init hook putting an SSM parameter such
# as MY_API_URL=https://example.com then fails with "Unable to retrieve https://…", and a hook that
# backgrounds its calls loses that silently. AWS CLI v2 dropped the behaviour, so this only shows up
# in the image, never when the same script is run from a host with v2.
RUN mkdir -p /app/data /etc/floci/aws \
&& printf '[default]\nendpoint_url = http://localhost:4566\ncli_follow_urlparam = false\n' > /etc/floci/aws/config \
&& chown 1001:root /app \
&& chmod "g+rwX" /app \
&& chown 1001:root /app/data
COPY --from=build /build/target/quarkus-app/ quarkus-app/
RUN chown -R 1001:root /app/quarkus-app
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY --chmod=755 docker/localstack-parity.sh /usr/local/bin/localstack-parity.sh
VOLUME /app/data
EXPOSE 4566
# Need to expose separately due to https://github.com/floci-io/floci/issues/2095.
# This preseres compatibility with https://github.com/docker-java/docker-java/blob/002b6eb7aa2b892be729c7c2a5e95f782924db38/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPort.java#L119
EXPOSE 6379
EXPOSE 6380
EXPOSE 6381
EXPOSE 6382
EXPOSE 6383
EXPOSE 6384
EXPOSE 6385
EXPOSE 6386
EXPOSE 6387
EXPOSE 6388
EXPOSE 6389
EXPOSE 6390
EXPOSE 6391
EXPOSE 6392
EXPOSE 6393
EXPOSE 6394
EXPOSE 6395
EXPOSE 6396
EXPOSE 6397
EXPOSE 6398
EXPOSE 6399
HEALTHCHECK --interval=5s --timeout=3s --retries=5 \
CMD wget -q --spider http://localhost:4566/_floci/health || exit 1
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# A published port needs 0.0.0.0 inside the container, which Floci only accepts with explicit consent.
CMD ["java", "--enable-native-access=ALL-UNNAMED", "-Dquarkus.http.host=0.0.0.0", "-Dfloci.security.allow-unsafe-network-exposure=true", "-jar", "/app/quarkus-app/quarkus-run.jar"]
+121
View File
@@ -0,0 +1,121 @@
ARG VERSION=latest
# BASE_IMAGE is the image produced by Dockerfile.native-package. Release and nightly
# tags use VERSION; reusable builds override BASE_IMAGE with the digest-qualified
# native image they just published.
#
# That image is ubi9-micro and has no package manager, so this image cannot extend it.
ARG BASE_IMAGE=floci/floci:${VERSION}
FROM ${BASE_IMAGE} AS native
# Everything that does not depend on the Floci version. CI builds this stage alone
# (target: tools) to test the aws CLI shim and to warm the layer cache.
FROM registry.access.redhat.com/ubi9-minimal:9.7 AS tools
LABEL org.opencontainers.image.title="Floci (compat)" \
org.opencontainers.image.description="Floci compatibility image with AWS CLI and boto3 pre-installed" \
org.opencontainers.image.url="https://floci.io" \
org.opencontainers.image.source="https://github.com/floci-io/floci" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Floci" \
description="Floci compatibility image with AWS CLI and boto3 pre-installed" \
io.k8s.description="Floci compatibility image with AWS CLI and boto3 pre-installed" \
io.k8s.display-name="Floci (compat)" \
io.openshift.expose-services="4566:tcp" \
io.openshift.tags="floci aws-emulator" \
maintainer="Floci <https://github.com/floci-io/floci>" \
name="floci/floci" \
summary="Floci compatibility image with AWS CLI and boto3 pre-installed" \
url="https://floci.io" \
vendor="Floci"
ENV AWS_DEFAULT_REGION=us-east-1
ENV AWS_ACCESS_KEY_ID=test
ENV AWS_SECRET_ACCESS_KEY=test
ENV AWS_ENDPOINT_URL="http://localhost:4566"
ENV AWS_CONFIG_FILE=/etc/floci/aws/config
ENV AWS_PAGER=""
# AWS CLI v2. v1 (the only version pip3 install awscli can give you) resolves
# a fixed set of services (SQS, RDS, DocDB, Neptune, EMR, Health) through
# awscli/customizations/overridesslcommonname.py, which forces the real AWS
# endpoint whenever no --endpoint-url flag is passed on the command line —
# no environment variable, including AWS_ENDPOINT_URL and the per-service
# AWS_ENDPOINT_URL_<SERVICE> overrides, can redirect it (aws/aws-cli#8737).
# v2 has no equivalent override and resolves every service through the
# normal endpoint chain, so AWS_ENDPOINT_URL alone is sufficient.
#
# v1 also entered maintenance mode 2026-07-15 (EOL 2027-07-15), so this is
# needed regardless of the endpoint-resolution bug.
#
# cli_binary_format has no env var in v2 (its provider chain is config-file,
# then a base64 constant), so blob params default to base64 here, same as
# a v2 install against real AWS. A hook passing raw bytes (e.g.
# aws lambda invoke --payload) needs --cli-binary-format raw-in-base64-out.
ARG AWSCLI_VERSION=2.36.24
RUN useradd -r -u 1001 -g root -d /app -s /sbin/nologin floci \
&& microdnf install -y --nodocs python3 python3-pip unzip \
&& pip3 install --no-cache-dir boto3 \
&& case "$(uname -m)" in \
x86_64) arch=x86_64 ;; \
aarch64) arch=aarch64 ;; \
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;; \
esac \
&& curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-${arch}-${AWSCLI_VERSION}.zip" -o /tmp/awscliv2.zip \
&& unzip -q /tmp/awscliv2.zip -d /tmp \
&& /tmp/aws/install --bin-dir /usr/bin --install-dir /usr/local/aws-cli \
&& rm -rf /tmp/awscliv2.zip /tmp/aws \
# The CLI's `aws <cmd> help` example pages are 26 MB of text nobody reads inside a container;
# the command reference itself (dist/awscli/data) stays. unzip exists only to unpack the CLI.
# pip stays: the image documents it and init hooks use it to install packages.
&& rm -rf /usr/local/aws-cli/v2/*/dist/awscli/examples \
# Tab completion has no user inside a container: aws_completer and its 21 MB ac.index go.
# The system Python keeps pip, setuptools and what boto3 imports; the optimised .pyc
# variants, lib2to3 and ensurepip (pip is already installed) are build-time extras.
&& rm -rf /usr/local/aws-cli/v2/*/dist/awscli/data/ac.index /usr/local/aws-cli/v2/*/dist/aws_completer /usr/bin/aws_completer \
&& rm -rf /usr/lib64/python3.9/lib2to3 /usr/lib64/python3.9/ensurepip \
&& python3 -c 'import pathlib; [p.unlink() for d in ("/usr/lib64/python3.9","/usr/lib/python3.9") for p in pathlib.Path(d).rglob("*.opt-[12].pyc")]' \
&& microdnf remove -y unzip \
&& microdnf clean all \
&& rm -rf /root/.cache/pip
COPY --chmod=755 bin/awslocal /usr/local/bin/awslocal
# Pre-create the init-hook directories so archive-based copies (Testcontainers
# withCopyFileToContainer / `docker cp`) can drop hooks straight into them.
# Docker's archive extract unpacks into an existing dir and never mkdir -p's the
# parents, so without these the copy fails. Mirrors LocalStack, which ships
# /etc/localstack/init. Owned by the runtime user (uid 1001, group root).
RUN mkdir -p /etc/localstack/init/boot.d /etc/localstack/init/start.d /etc/localstack/init/ready.d \
/etc/floci/init/boot.d /etc/floci/init/start.d /etc/floci/init/ready.d \
&& chown -R 1001:root /etc/localstack /etc/floci
WORKDIR /app
# A chmod after the copy below would rewrite the 200 MB binary into a second layer.
RUN chown 1001:root /app && chmod g+rwX /app
FROM tools
ARG VERSION
ENV FLOCI_VERSION=${VERSION}
ENV FLOCI_STORAGE_PERSISTENT_PATH=/app/data
# The compatibility image can be built on top of an older native image during
# CI. An environment variable is ignored by older binaries and enables the
# network guard in images that support it.
ENV FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE=true
COPY --from=native --chown=1001:root /app/ /app/
VOLUME /app/data
COPY --from=native /usr/local/bin/docker-entrypoint.sh /usr/local/bin/localstack-parity.sh /usr/local/bin/
# From the build context, not the native image: a published native image may predate the script.
COPY --chmod=755 docker/healthcheck.sh /usr/local/bin/
EXPOSE 4566
HEALTHCHECK --interval=5s --timeout=3s --retries=5 CMD ["/usr/local/bin/healthcheck.sh"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# A published port needs 0.0.0.0 inside the container, which Floci only accepts with explicit consent.
CMD ["/app/application", "-Dquarkus.http.host=0.0.0.0"]
USER 1001
+89
View File
@@ -0,0 +1,89 @@
# JVM image built from an artifact that already exists on the host:
# ./mvnw package -DskipTests && docker build -f docker/Dockerfile.jvm-package -t floci/floci:dev .
#
# The counterpart to Dockerfile.native-package, for the JVM runtime: same idea, same reason. It
# exists for the local loop: docker/Dockerfile runs Maven inside the build, which re-resolves every
# dependency and wants a couple of gigabytes, so iterating on a change through it is slow and can
# fall over on a machine with little headroom. Consuming target/quarkus-app takes seconds.
#
# Everything below the FROM is a copy of the runtime stage of docker/Dockerfile, which is the
# canonical definition. Exactly two lines differ, both by design: the default VERSION, and where
# the application directory is copied from. DockerfileRuntimeParityTest enforces that, so a change
# to the canonical runtime stage fails the build here until it is mirrored.
FROM eclipse-temurin:25-jre-noble
ARG VERSION=dev
ENV FLOCI_VERSION=${VERSION}
ENV FLOCI_STORAGE_PERSISTENT_PATH=/app/data
ENV AWS_DEFAULT_REGION=us-east-1
ENV AWS_ACCESS_KEY_ID=test
ENV AWS_SECRET_ACCESS_KEY=test
ENV AWS_CONFIG_FILE=/etc/floci/aws/config
# Privilege drop happens in the entrypoint through coreutils chroot, so no gosu
# download; wget stays for the HEALTHCHECK below.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates passwd python3 python3-pip wget; \
rm -rf /var/lib/apt/lists/*; \
pip3 install --no-cache-dir --break-system-packages awscli boto3; \
aws_bin="$(command -v aws)"; \
mv "$aws_bin" /usr/bin/aws.real; \
printf '#!/bin/sh\nexec /usr/bin/aws.real --endpoint-url=http://localhost:4566 "$@"\n' > /usr/bin/aws; \
chmod +x /usr/bin/aws; \
useradd -r -u 1001 -g root -d /app -s /sbin/nologin floci
WORKDIR /app
# cli_follow_urlparam: the bundled CLI is v1 (pip ships no v2), where a parameter value that looks
# like a URL is *fetched* and its body used as the value. An init hook putting an SSM parameter such
# as MY_API_URL=https://example.com then fails with "Unable to retrieve https://…", and a hook that
# backgrounds its calls loses that silently. AWS CLI v2 dropped the behaviour, so this only shows up
# in the image, never when the same script is run from a host with v2.
RUN mkdir -p /app/data /etc/floci/aws \
&& printf '[default]\nendpoint_url = http://localhost:4566\ncli_follow_urlparam = false\n' > /etc/floci/aws/config \
&& chown 1001:root /app \
&& chmod "g+rwX" /app \
&& chown 1001:root /app/data
COPY target/quarkus-app/ quarkus-app/
RUN chown -R 1001:root /app/quarkus-app
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY --chmod=755 docker/localstack-parity.sh /usr/local/bin/localstack-parity.sh
VOLUME /app/data
EXPOSE 4566
# Need to expose separately due to https://github.com/floci-io/floci/issues/2095.
# This preseres compatibility with https://github.com/docker-java/docker-java/blob/002b6eb7aa2b892be729c7c2a5e95f782924db38/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPort.java#L119
EXPOSE 6379
EXPOSE 6380
EXPOSE 6381
EXPOSE 6382
EXPOSE 6383
EXPOSE 6384
EXPOSE 6385
EXPOSE 6386
EXPOSE 6387
EXPOSE 6388
EXPOSE 6389
EXPOSE 6390
EXPOSE 6391
EXPOSE 6392
EXPOSE 6393
EXPOSE 6394
EXPOSE 6395
EXPOSE 6396
EXPOSE 6397
EXPOSE 6398
EXPOSE 6399
HEALTHCHECK --interval=5s --timeout=3s --retries=5 \
CMD wget -q --spider http://localhost:4566/_floci/health || exit 1
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# A published port needs 0.0.0.0 inside the container, which Floci only accepts with explicit consent.
CMD ["java", "--enable-native-access=ALL-UNNAMED", "-Dquarkus.http.host=0.0.0.0", "-Dfloci.security.allow-unsafe-network-exposure=true", "-jar", "/app/quarkus-app/quarkus-run.jar"]
+50
View File
@@ -0,0 +1,50 @@
# Stage 1: Build native executable
FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-25 AS build
USER root
WORKDIR /app
# Install Maven 3.9.x (system Maven is too old for Quarkus 3.31)
RUN curl -fsSL https://archive.apache.org/dist/maven/maven-3/3.9.12/binaries/apache-maven-3.9.12-bin.tar.gz | tar xz -C /opt \
&& ln -sf /opt/apache-maven-3.9.12/bin/mvn /usr/local/bin/mvn
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
# Quarkus 3.39 switches on -H:+AOTSingleCallsiteInline for Mandrel 25.0.4+. The generated
# REST invokers for AwsQueryController.dispatch and AwsJson11Controller.handle are each the
# single call site of a very large method; inlined, the method outgrows the aarch64
# conditional-branch range and the build dies with BranchTargetOutOfBoundsException.
# The flag only exists on Mandrel, so it is passed here rather than in application.yml,
# the same way the workflow native builds pass it.
RUN mvn clean package -Dnative -DskipTests -B \
-Dquarkus.native.additional-build-args-append=-H:-AOTSingleCallsiteInline
# Stage 2: Minimal runtime. Same base and user setup as Dockerfile.native-package.
FROM registry.access.redhat.com/ubi9-micro:9.7
WORKDIR /app
RUN echo 'floci:x:1001:0:Floci:/app:/sbin/nologin' >> /etc/passwd \
&& mkdir -p /app/data \
&& chown -R 1001:root /app \
&& chmod -R g+rwX /app
VOLUME /app/data
EXPOSE 4566
HEALTHCHECK --interval=5s --timeout=3s --retries=5 CMD ["/usr/local/bin/healthcheck.sh"]
ARG VERSION=latest
ENV FLOCI_VERSION=${VERSION}
ENV FLOCI_STORAGE_PERSISTENT_PATH=/app/data
# The builder image has no zlib-static, so native-image links zlib dynamically and the
# runner needs libz.so.1, which ubi9-micro does not ship. Take it from the builder image.
COPY --from=build /usr/lib64/libz.so.1 /usr/lib64/libz.so.1
COPY --chmod=755 --from=build /app/target/*-runner /app/application
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY --chmod=755 docker/localstack-parity.sh docker/healthcheck.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# A published port needs 0.0.0.0 inside the container, which Floci only accepts with explicit consent.
CMD ["/app/application", "-Dquarkus.http.host=0.0.0.0", "-Dfloci.security.allow-unsafe-network-exposure=true"]
+47
View File
@@ -0,0 +1,47 @@
FROM registry.access.redhat.com/ubi9-micro:9.7
# Automatically set by Docker BuildKit to "amd64" or "arm64"
ARG TARGETARCH
WORKDIR /app
# ubi9-micro has no shadow-utils, so the runtime user is one /etc/passwd line.
RUN echo 'floci:x:1001:0:Floci:/app:/sbin/nologin' >> /etc/passwd \
&& mkdir -p /app/data \
&& chown -R 1001:root /app \
&& chmod -R g+rwX /app
VOLUME /app/data
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY --chmod=755 docker/localstack-parity.sh docker/healthcheck.sh /usr/local/bin/
ARG VERSION=latest
ENV FLOCI_VERSION=${VERSION}
ENV FLOCI_STORAGE_PERSISTENT_PATH=/app/data
COPY --chmod=755 --chown=1001:root native/${TARGETARCH}/ /app/
LABEL org.opencontainers.image.title="Floci" \
org.opencontainers.image.description="Local AWS emulator — open-source alternative to LocalStack Community" \
org.opencontainers.image.url="https://floci.io" \
org.opencontainers.image.source="https://github.com/floci-io/floci" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Floci" \
description="Local AWS emulator — open-source alternative to LocalStack Community" \
io.k8s.description="Local AWS emulator — open-source alternative to LocalStack Community" \
io.k8s.display-name="Floci" \
io.openshift.expose-services="4566:tcp" \
io.openshift.tags="floci aws-emulator" \
maintainer="Floci <https://github.com/floci-io/floci>" \
name="floci/floci" \
summary="Local AWS emulator — open-source alternative to LocalStack Community" \
url="https://floci.io" \
vendor="Floci"
EXPOSE 4566
HEALTHCHECK --interval=5s --timeout=3s --retries=5 CMD ["/usr/local/bin/healthcheck.sh"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# A published port needs 0.0.0.0 inside the container, which Floci only accepts with explicit consent.
CMD ["/app/application", "-Dquarkus.http.host=0.0.0.0", "-Dfloci.security.allow-unsafe-network-exposure=true"]
@@ -0,0 +1,30 @@
releaseId: ubuntu-24.04
images:
- id: ubuntu-24.04-arm64
catalogImageId: ami-ubuntu2404-cloud-arm64
catalogAliases:
- ami-ubuntu2404-cloud
family: ubuntu
version: "24.04"
architecture: arm64
aws:
region: us-east-1
ownerId: "099720109477"
imageId: ami-ubuntu2404-arm64
name: ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-arm64-server-20260515
creationDate: "2026-05-15T00:00:00.000Z"
virtualizationType: hvm
rootDeviceType: ebs
canonical:
baseUrl: https://cloud-images.ubuntu.com/releases/noble/release
rootfs: ubuntu-24.04-server-cloudimg-arm64-root.tar.xz
rootfsSha256: 15188696da114a3ffd3d3554f5858a0c3ac257933656e85feb4e0e83ad542b4a
manifest: ubuntu-24.04-server-cloudimg-arm64.manifest
docker:
image: floci/ami-ubuntu:24.04-arm64
guest:
runtime: systemd
cloudInit: true
smokePackages:
- systemd
- cloud-init
+86
View File
@@ -0,0 +1,86 @@
#!/bin/sh
# Starts as root, reads the bind-mounted Docker socket's group so the
# unprivileged `floci` user can reach it on any host, then re-executes this
# script as `floci` via coreutils `chroot --userspec`, which also grants that
# group as a supplementary group. The second invocation falls through to exec
# the user's command. No gosu, no usermod: both would cost a package layer the
# base image does not otherwise need.
#
# Why: on native Linux Docker, /var/run/docker.sock is owned by
# root:docker with mode 660 and the docker GID varies by distro. On
# Docker Desktop (macOS/Windows) the socket is typically root:root (GID
# 0). Without the group fix-up below, floci (uid 1001, group 0) can open
# the socket on Docker Desktop but not on native Linux — which breaks
# ECR, Lambda, and RDS service emulation there. Discovering the GID at
# runtime handles every host transparently.
set -eu
if [ "$(id -u)" = '0' ]; then
groups='0'
if [ -S /var/run/docker.sock ]; then
sock_gid="$(stat -c '%g' /var/run/docker.sock)"
if [ "$sock_gid" != '0' ]; then
groups="0,$sock_gid"
fi
fi
# Re-own state dir for the case where a host bind-mount arrives with
# ownership the floci user cannot write to. Ignore errors (read-only
# mounts, unusual filesystems) so the container still starts.
if [ -d /app/data ]; then
chown -R floci:root /app/data 2>/dev/null || true
fi
# `chroot /` changes nothing but the identity: uid 1001, primary gid 0, plus the socket's
# group. Supplementary groups are set by number, so the group needs no /etc/group entry.
# --skip-chdir keeps the working directory (/app, where relative data paths resolve); GNU
# chroot would otherwise chdir to the new root.
if [ "${FLOCI_RUN_AS_ROOT:-false}" != 'true' ]; then
exec chroot --userspec=1001:0 --groups="$groups" --skip-chdir / "$0" "$@"
fi
fi
if [ "${LOCALSTACK_PARITY:-true}" != "false" ]; then
. /usr/local/bin/localstack-parity.sh
fi
# Probe the state dir as the unprivileged user and warn loudly when it is not
# writable (read-only or root-owned bind mounts the chown above could not fix).
# The container still starts: with the default in-memory storage the dir is
# unused, and non-memory modes fail fast at boot with an actionable error.
data_dir="${FLOCI_STORAGE_PERSISTENT_PATH:-/app/data}"
if [ -d "$data_dir" ]; then
probe="$data_dir/.floci-write-probe.$$"
if ! ( touch "$probe" && rm -f "$probe" ) 2>/dev/null; then
cat >&2 <<EOF
**************************************************************************
WARNING: $data_dir is not writable by $(id -un) (uid $(id -u)).
Persisted state cannot be saved there. If FLOCI_STORAGE_MODE (or any
per-service storage mode) is not 'memory', Floci will refuse to start.
Fix the volume mount permissions (it may be read-only or root-owned) or
set FLOCI_STORAGE_PERSISTENT_PATH to a writable directory.
**************************************************************************
EOF
fi
fi
# Fall back to the image's default command when invoked with no arguments.
# Some tooling re-executes this entrypoint directly with an empty argv —
# Testcontainers' LocalStackContainer does exactly that via its starter
# script — and `exec "$@"` with no argv would make the script reach EOF
# and exit 0 without ever starting the emulator. LocalStack's entrypoint
# ignores its argv entirely, so this keeps drop-in parity.
# The default matches the CMD of the image variant: native images ship
# /app/application, JVM images ship /app/quarkus-app/quarkus-run.jar.
# Both listen on 0.0.0.0 so a published port reaches Floci, which it only
# accepts with explicit consent. The JVM reads -D options only before -jar.
if [ $# -eq 0 ]; then
if [ -x /app/application ]; then
set -- /app/application -Dquarkus.http.host=0.0.0.0 -Dfloci.security.allow-unsafe-network-exposure=true
else
set -- java -Dquarkus.http.host=0.0.0.0 -Dfloci.security.allow-unsafe-network-exposure=true -jar /app/quarkus-app/quarkus-run.jar
fi
fi
exec "$@"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
# Container health probe. Bash only, because ubi9-micro has no curl.
exec 3<>/dev/tcp/127.0.0.1/4566 || exit 1
printf 'GET /_floci/health HTTP/1.0\r\nHost: localhost\r\n\r\n' >&3
read -r _proto status _rest <&3
[ "$status" = 200 ]
+82
View File
@@ -0,0 +1,82 @@
#!/bin/sh
# Maps LocalStack Community environment variables to their Floci equivalents.
# Sourced by entrypoint.sh when LOCALSTACK_PARITY=true.
# Floci vars always win: every mapping uses ${FLOCI_VAR:-<derived>} so an
# explicitly-set Floci var is never overwritten.
# Storage mode — PERSISTENCE=1 / PERSIST_STATE=1 → persistent storage
if [ -n "${PERSISTENCE:-}" ] || [ -n "${PERSIST_STATE:-}" ]; then
_ls_persist="${PERSISTENCE:-${PERSIST_STATE:-}}"
if [ "${_ls_persist}" = "1" ] || [ "${_ls_persist}" = "true" ]; then
export FLOCI_STORAGE_MODE="${FLOCI_STORAGE_MODE:-persistent}"
fi
fi
# Bind port — EDGE_PORT → FLOCI_PORT
[ -n "${EDGE_PORT:-}" ] && export FLOCI_PORT="${FLOCI_PORT:-${EDGE_PORT}}"
# Hostname returned in response URLs — LOCALSTACK_HOST / LOCALSTACK_HOSTNAME → FLOCI_HOSTNAME
_ls_host="${LOCALSTACK_HOST:-${LOCALSTACK_HOSTNAME:-}}"
[ -n "${_ls_host}" ] && export FLOCI_HOSTNAME="${FLOCI_HOSTNAME:-${_ls_host}}"
# Bind address — GATEWAY_LISTEN → QUARKUS_HTTP_HOST. LocalStack listens there with no further
# opt-in, so GATEWAY_LISTEN also gives the consent Floci requires for a non-loopback address.
if [ -n "${GATEWAY_LISTEN:-}" ]; then
export QUARKUS_HTTP_HOST="${QUARKUS_HTTP_HOST:-${GATEWAY_LISTEN}}"
export FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE="${FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE:-true}"
fi
# Log level — LS_LOG / DEBUG=1 → QUARKUS_LOG_LEVEL
if [ -n "${LS_LOG:-}" ]; then
export QUARKUS_LOG_LEVEL="${QUARKUS_LOG_LEVEL:-${LS_LOG}}"
elif [ "${DEBUG:-}" = "1" ]; then
export QUARKUS_LOG_LEVEL="${QUARKUS_LOG_LEVEL:-DEBUG}"
fi
# Lambda — LAMBDA_EXECUTOR is intentionally ignored; Floci always runs Lambda in Docker containers.
# Lambda Docker network
[ -n "${LAMBDA_DOCKER_NETWORK:-}" ] && \
export FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK="${FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK:-${LAMBDA_DOCKER_NETWORK}}"
# Lambda ephemeral containers
if [ "${LAMBDA_REMOVE_CONTAINERS:-}" = "1" ] || [ "${LAMBDA_REMOVE_CONTAINERS:-}" = "true" ]; then
export FLOCI_SERVICES_LAMBDA_EPHEMERAL="${FLOCI_SERVICES_LAMBDA_EPHEMERAL:-true}"
fi
# LAMBDA_REMOTE_DOCKER — not fully supported.
# Floci's hot-reload is per-function opt-in (S3Bucket=hot-reload), not a global bind-mount mode.
if [ -n "${LAMBDA_REMOTE_DOCKER:-}" ]; then
echo "[floci-parity] WARNING: LAMBDA_REMOTE_DOCKER is not fully supported by Floci." >&2
echo "[floci-parity] Use S3Bucket=hot-reload per function instead. See https://floci.io/docs/services/lambda" >&2
fi
# Docker host
[ -n "${DOCKER_HOST:-}" ] && export FLOCI_DOCKER_DOCKER_HOST="${FLOCI_DOCKER_DOCKER_HOST:-${DOCKER_HOST}}"
# Docker network — shared across all container-based services (Lambda, RDS, ElastiCache, MSK, OpenSearch, EKS).
# Per-service overrides (e.g. FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK) take precedence when set.
[ -n "${DOCKER_NETWORK:-}" ] && export FLOCI_SERVICES_DOCKER_NETWORK="${FLOCI_SERVICES_DOCKER_NETWORK:-${DOCKER_NETWORK}}"
# DNS suffixes — register LocalStack and Floci hostname suffixes so that container-to-container
# hostname routing (Function URLs, presigned S3, SQS QueueUrl, etc.) works without manual config.
_parity_suffixes="localhost.localstack.cloud,localhost.floci.io"
if [ -n "${FLOCI_DNS_EXTRA_SUFFIXES:-}" ]; then
export FLOCI_DNS_EXTRA_SUFFIXES="${FLOCI_DNS_EXTRA_SUFFIXES},${_parity_suffixes}"
else
export FLOCI_DNS_EXTRA_SUFFIXES="${_parity_suffixes}"
fi
# TLS — USE_SSL=1 → FLOCI_TLS_ENABLED=true
if [ "${USE_SSL:-0}" = "1" ]; then
export FLOCI_TLS_ENABLED="${FLOCI_TLS_ENABLED:-true}"
fi
# TLS — CUSTOM_SSL_CERT_PATH → FLOCI_TLS_CERT_PATH + FLOCI_TLS_KEY_PATH
# LocalStack uses a single combined PEM (cert+key). Floci accepts it in both fields.
if [ -n "${CUSTOM_SSL_CERT_PATH:-}" ]; then
export FLOCI_TLS_CERT_PATH="${FLOCI_TLS_CERT_PATH:-${CUSTOM_SSL_CERT_PATH}}"
export FLOCI_TLS_KEY_PATH="${FLOCI_TLS_KEY_PATH:-${CUSTOM_SSL_CERT_PATH}}"
fi
# SERVICES: intentionally ignored; Floci starts every service in ~24ms.
+96
View File
@@ -0,0 +1,96 @@
#!/bin/bash
set -e
# 1. Start Floci
echo "=== Starting Floci with docker-compose ==="
docker compose up -d --build
# Wait for healthy
echo "Waiting for Floci to be healthy..."
# Portable wait without 'timeout' command
MAX_RETRIES=60
COUNT=0
until curl -sf http://localhost:4566/_floci/health >/dev/null 2>&1; do
if [ $COUNT -ge $MAX_RETRIES ]; then
echo "Floci failed to become healthy in time"
exit 1
fi
sleep 1
COUNT=$((COUNT + 1))
echo -n "."
done
echo " Floci is up!"
# 2. Network setup (Floci uses floci_default from compose)
NETWORK="floci_default"
DOCKER_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock)
# Floci's embedded DNS server resolves *.floci → Floci's IP.
# Passing --dns <floci-ip> to test containers lets the S3 virtual-host client
# send to <bucket>.floci:4566 which Floci DNS resolves correctly. Without this,
# Docker's built-in DNS only resolves the exact service name "floci", not
# wildcard subdomains like my-bucket.floci.
FLOCI_CONTAINER=$(docker compose ps -q floci 2>/dev/null | head -1)
FLOCI_IP=$(docker inspect -f "{{.NetworkSettings.Networks.${NETWORK}.IPAddress}}" "$FLOCI_CONTAINER" 2>/dev/null || true)
# 3. Test suites
SUITES=(
"sdk-test-python"
"sdk-test-node"
"sdk-test-java"
"sdk-test-go"
"sdk-test-awscli"
"compat-cdk"
"compat-terraform"
"compat-opentofu"
)
# results dir
mkdir -p test-results
FAILED=0
for suite in "${SUITES[@]}"; do
echo "=== Running $suite in Docker ==="
IMAGE_NAME="compat-$suite"
# Build
if ! docker build -q -t "$IMAGE_NAME" "compatibility-tests/$suite"; then
echo "Test suite $suite failed to build"
FAILED=1
continue
fi
# Build DNS args: if we resolved Floci's IP, inject it as the DNS server so
# wildcard subdomains like <bucket>.floci resolve inside test containers.
DNS_ARGS=()
if [ -n "$FLOCI_IP" ]; then
DNS_ARGS=(--dns "$FLOCI_IP")
fi
# Per-suite extra args, mirroring .github/workflows/compatibility.yml.
# sdk-test-java's Lambda hot-reload test needs a host directory that the Docker
# daemon can bind-mount into the hot-reload Lambda container.
EXTRA_ARGS=()
if [ "$suite" = "sdk-test-java" ]; then
mkdir -p /tmp/floci-hot-reload
EXTRA_ARGS=(-v /tmp/floci-hot-reload:/tmp/floci-hot-reload -e HOT_RELOAD_BASE_DIR=/tmp/floci-hot-reload)
fi
# Run
if ! docker run --rm --network "$NETWORK" \
"${DNS_ARGS[@]}" \
-e FLOCI_ENDPOINT=http://floci:4566 \
-e FLOCI_S3_VHOST_ENDPOINT=http://floci:4566 \
-v "$(pwd)/test-results:/results" \
-v /var/run/docker.sock:/var/run/docker.sock \
--group-add "$DOCKER_GID" \
"${EXTRA_ARGS[@]}" \
"$IMAGE_NAME"; then
echo "Test suite $suite failed"
FAILED=1
fi
done
echo "=== All Docker tests completed ==="
exit "$FAILED"
+151
View File
@@ -0,0 +1,151 @@
#!/bin/sh
# Unit tests for entrypoint.sh argument handling.
# Run directly: sh docker/test-entrypoint.sh
# Exit 0 on success, non-zero on first failure summary.
#
# These tests run as an unprivileged user with LOCALSTACK_PARITY=false.
# Stub id and chroot to check the root path without changing privileges.
set -eu
SCRIPT="$(cd "$(dirname "$0")" && pwd)/entrypoint.sh"
PASS=0
FAIL=0
assert_eq() {
desc="$1"; expected="$2"; actual="$3"
if [ "${actual}" = "${expected}" ]; then
printf '[PASS] %s\n' "${desc}"
PASS=$((PASS + 1))
else
printf '[FAIL] %s\n expected: %s\n actual: %s\n' "${desc}" "${expected}" "${actual}"
FAIL=$((FAIL + 1))
fi
}
if [ "$(id -u)" = '0' ]; then
echo "These tests must run as an unprivileged user (the root path re-execs via chroot)." >&2
exit 1
fi
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT INT TERM
# Stub java on PATH that prints the argv it was exec'd with.
mkdir -p "${WORK}/bin"
cat > "${WORK}/bin/java" <<'EOF'
#!/bin/sh
printf '%s\n' "java $*"
EOF
chmod +x "${WORK}/bin/java"
mkdir -p "${WORK}/root-bin"
cat > "${WORK}/root-bin/id" <<'EOF'
#!/bin/sh
if [ "$1" = '-u' ]; then
printf '0\n'
else
/usr/bin/id "$@"
fi
EOF
cat > "${WORK}/root-bin/chroot" <<'EOF'
#!/bin/sh
printf 'dropped privileges\n'
EOF
chmod +x "${WORK}/root-bin/id" "${WORK}/root-bin/chroot"
assert_eq "root drops privileges by default" \
"dropped privileges" \
"$(PATH="${WORK}/root-bin:${PATH}" FLOCI_STORAGE_PERSISTENT_PATH="${WORK}/absent" LOCALSTACK_PARITY=false sh "${SCRIPT}" echo preserved)"
assert_eq "false keeps the unprivileged default" \
"dropped privileges" \
"$(PATH="${WORK}/root-bin:${PATH}" FLOCI_RUN_AS_ROOT=false FLOCI_STORAGE_PERSISTENT_PATH="${WORK}/absent" LOCALSTACK_PARITY=false sh "${SCRIPT}" echo preserved)"
assert_eq "other values keep the unprivileged default" \
"dropped privileges" \
"$(PATH="${WORK}/root-bin:${PATH}" FLOCI_RUN_AS_ROOT=TRUE FLOCI_STORAGE_PERSISTENT_PATH="${WORK}/absent" LOCALSTACK_PARITY=false sh "${SCRIPT}" echo preserved)"
assert_eq "explicit root option preserves command arguments" \
"one two|three" \
"$(PATH="${WORK}/root-bin:${PATH}" FLOCI_RUN_AS_ROOT=true FLOCI_STORAGE_PERSISTENT_PATH="${WORK}/absent" LOCALSTACK_PARITY=false sh "${SCRIPT}" sh -c 'printf "%s|%s\n" "$1" "$2"' _ "one two" three)"
assert_eq "non-root process cannot elevate with root option" \
"one two" \
"$(FLOCI_RUN_AS_ROOT=true LOCALSTACK_PARITY=false sh "${SCRIPT}" echo one two)"
# --- explicit arguments are exec'd unchanged ---
assert_eq "explicit command is exec'd unchanged" \
"one two" \
"$(LOCALSTACK_PARITY=false sh "${SCRIPT}" echo one two)"
assert_eq "explicit java command bypasses the fallback" \
"java -jar /custom/app.jar" \
"$(PATH="${WORK}/bin:${PATH}" LOCALSTACK_PARITY=false sh "${SCRIPT}" java -jar /custom/app.jar)"
# --- empty argv falls back to the image default command ---
# Without /app/application (JVM image layout), the fallback must exec the
# Quarkus runner jar with the same arguments as the published image CMD.
if [ ! -e /app/application ]; then
assert_eq "empty argv falls back to the JVM default command" \
"java -Dquarkus.http.host=0.0.0.0 -Dfloci.security.allow-unsafe-network-exposure=true -jar /app/quarkus-app/quarkus-run.jar" \
"$(PATH="${WORK}/bin:${PATH}" LOCALSTACK_PARITY=false sh "${SCRIPT}")"
else
printf '[SKIP] empty argv falls back to the JVM default command (/app/application exists on this host)\n'
fi
# With an executable /app/application (native image layout), the fallback
# must prefer the native binary. Only runs where /app is writable or the
# binary already exists (always true inside the published images).
NATIVE_TESTABLE=false
if [ -x /app/application ]; then
NATIVE_TESTABLE=true
elif mkdir -p /app 2>/dev/null && [ -w /app ]; then
cat > /app/application <<'EOF'
#!/bin/sh
printf '%s\n' "/app/application $*"
EOF
chmod +x /app/application
trap 'rm -f /app/application; rm -rf "${WORK}"' EXIT INT TERM
NATIVE_TESTABLE=true
fi
if [ "${NATIVE_TESTABLE}" = 'true' ]; then
assert_eq "empty argv prefers the native binary when present" \
"/app/application -Dquarkus.http.host=0.0.0.0 -Dfloci.security.allow-unsafe-network-exposure=true" \
"$(LOCALSTACK_PARITY=false sh "${SCRIPT}")"
else
printf '[SKIP] empty argv prefers the native binary when present (/app not writable)\n'
fi
# --- unwritable state dir prints a warning but still execs the command ---
RO_DIR="${WORK}/ro-data"
mkdir -p "${RO_DIR}"
chmod 555 "${RO_DIR}"
RO_ERR="${WORK}/ro-err.txt"
assert_eq "unwritable state dir still execs the command" \
"ok" \
"$(FLOCI_STORAGE_PERSISTENT_PATH="${RO_DIR}" LOCALSTACK_PARITY=false sh "${SCRIPT}" echo ok 2>"${RO_ERR}")"
if grep -q 'not writable' "${RO_ERR}"; then
printf '[PASS] unwritable state dir prints a loud warning\n'
PASS=$((PASS + 1))
else
printf '[FAIL] unwritable state dir prints a loud warning\n stderr was:\n%s\n' "$(cat "${RO_ERR}")"
FAIL=$((FAIL + 1))
fi
chmod 755 "${RO_DIR}"
# --- writable state dir stays quiet and leaves no probe behind ---
RW_DIR="${WORK}/rw-data"
mkdir -p "${RW_DIR}"
RW_ERR="${WORK}/rw-err.txt"
FLOCI_STORAGE_PERSISTENT_PATH="${RW_DIR}" LOCALSTACK_PARITY=false sh "${SCRIPT}" echo ok >/dev/null 2>"${RW_ERR}"
if ! grep -q 'not writable' "${RW_ERR}" && [ -z "$(ls -A "${RW_DIR}")" ]; then
printf '[PASS] writable state dir stays quiet and leaves no probe file\n'
PASS=$((PASS + 1))
else
printf '[FAIL] writable state dir stays quiet and leaves no probe file\n'
FAIL=$((FAIL + 1))
fi
printf '\n%d passed, %d failed\n' "${PASS}" "${FAIL}"
[ "${FAIL}" -eq 0 ]
+153
View File
@@ -0,0 +1,153 @@
#!/bin/sh
# Unit tests for localstack-parity.sh.
# Run directly: sh docker/test-localstack-parity.sh
# Exit 0 on success, non-zero on first failure.
set -eu
SCRIPT="$(dirname "$0")/localstack-parity.sh"
PASS=0
FAIL=0
# Run the parity script in a subshell with a given environment and print the
# value of a single variable. Arguments: VAR_NAME [ENV_KEY=VALUE ...]
_run() {
var="$1"; shift
env -i "$@" sh -c ". '${SCRIPT}'; printf '%s' \"\${${var}:-}\""
}
# Assert that _run produces an expected value.
assert_eq() {
desc="$1"; expected="$2"; actual="$3"
if [ "${actual}" = "${expected}" ]; then
printf '[PASS] %s\n' "${desc}"
PASS=$((PASS + 1))
else
printf '[FAIL] %s\n expected: %s\n actual: %s\n' "${desc}" "${expected}" "${actual}"
FAIL=$((FAIL + 1))
fi
}
# --- PERSISTENCE ---
assert_eq "PERSISTENCE=1 sets FLOCI_STORAGE_MODE=persistent" \
"persistent" \
"$(_run FLOCI_STORAGE_MODE PERSISTENCE=1)"
assert_eq "PERSISTENCE=true sets FLOCI_STORAGE_MODE=persistent" \
"persistent" \
"$(_run FLOCI_STORAGE_MODE PERSISTENCE=true)"
assert_eq "PERSIST_STATE=1 sets FLOCI_STORAGE_MODE=persistent" \
"persistent" \
"$(_run FLOCI_STORAGE_MODE PERSIST_STATE=1)"
assert_eq "FLOCI_STORAGE_MODE wins over PERSISTENCE" \
"hybrid" \
"$(_run FLOCI_STORAGE_MODE PERSISTENCE=1 FLOCI_STORAGE_MODE=hybrid)"
# --- EDGE_PORT ---
assert_eq "EDGE_PORT sets FLOCI_PORT" \
"4567" \
"$(_run FLOCI_PORT EDGE_PORT=4567)"
assert_eq "FLOCI_PORT wins over EDGE_PORT" \
"4568" \
"$(_run FLOCI_PORT EDGE_PORT=4567 FLOCI_PORT=4568)"
# --- LOCALSTACK_HOST / LOCALSTACK_HOSTNAME ---
assert_eq "LOCALSTACK_HOST sets FLOCI_HOSTNAME" \
"myhost" \
"$(_run FLOCI_HOSTNAME LOCALSTACK_HOST=myhost)"
assert_eq "LOCALSTACK_HOSTNAME sets FLOCI_HOSTNAME when LOCALSTACK_HOST unset" \
"myhost2" \
"$(_run FLOCI_HOSTNAME LOCALSTACK_HOSTNAME=myhost2)"
assert_eq "LOCALSTACK_HOST takes priority over LOCALSTACK_HOSTNAME" \
"primary" \
"$(_run FLOCI_HOSTNAME LOCALSTACK_HOST=primary LOCALSTACK_HOSTNAME=secondary)"
assert_eq "FLOCI_HOSTNAME wins over LOCALSTACK_HOST" \
"explicit" \
"$(_run FLOCI_HOSTNAME LOCALSTACK_HOST=myhost FLOCI_HOSTNAME=explicit)"
# --- GATEWAY_LISTEN ---
assert_eq "GATEWAY_LISTEN sets QUARKUS_HTTP_HOST" \
"0.0.0.0" \
"$(_run QUARKUS_HTTP_HOST GATEWAY_LISTEN=0.0.0.0)"
assert_eq "GATEWAY_LISTEN allows a non-loopback address" \
"true" \
"$(_run FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE GATEWAY_LISTEN=0.0.0.0)"
assert_eq "FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE wins over GATEWAY_LISTEN" \
"false" \
"$(_run FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE GATEWAY_LISTEN=0.0.0.0 FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE=false)"
assert_eq "no GATEWAY_LISTEN leaves network exposure unallowed" \
"" \
"$(_run FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE)"
# --- LOG LEVEL ---
assert_eq "LS_LOG sets QUARKUS_LOG_LEVEL" \
"WARN" \
"$(_run QUARKUS_LOG_LEVEL LS_LOG=WARN)"
assert_eq "DEBUG=1 sets QUARKUS_LOG_LEVEL=DEBUG" \
"DEBUG" \
"$(_run QUARKUS_LOG_LEVEL DEBUG=1)"
assert_eq "LS_LOG takes priority over DEBUG=1" \
"TRACE" \
"$(_run QUARKUS_LOG_LEVEL LS_LOG=TRACE DEBUG=1)"
assert_eq "QUARKUS_LOG_LEVEL wins over LS_LOG" \
"INFO" \
"$(_run QUARKUS_LOG_LEVEL LS_LOG=DEBUG QUARKUS_LOG_LEVEL=INFO)"
# --- LAMBDA ---
assert_eq "LAMBDA_DOCKER_NETWORK sets FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK" \
"mynet" \
"$(_run FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK LAMBDA_DOCKER_NETWORK=mynet)"
assert_eq "FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK wins over LAMBDA_DOCKER_NETWORK" \
"floci-net" \
"$(_run FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK LAMBDA_DOCKER_NETWORK=mynet FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK=floci-net)"
assert_eq "LAMBDA_REMOVE_CONTAINERS=1 sets FLOCI_SERVICES_LAMBDA_EPHEMERAL=true" \
"true" \
"$(_run FLOCI_SERVICES_LAMBDA_EPHEMERAL LAMBDA_REMOVE_CONTAINERS=1)"
assert_eq "LAMBDA_REMOVE_CONTAINERS=true sets FLOCI_SERVICES_LAMBDA_EPHEMERAL=true" \
"true" \
"$(_run FLOCI_SERVICES_LAMBDA_EPHEMERAL LAMBDA_REMOVE_CONTAINERS=true)"
assert_eq "FLOCI_SERVICES_LAMBDA_EPHEMERAL wins over LAMBDA_REMOVE_CONTAINERS" \
"false" \
"$(_run FLOCI_SERVICES_LAMBDA_EPHEMERAL LAMBDA_REMOVE_CONTAINERS=1 FLOCI_SERVICES_LAMBDA_EPHEMERAL=false)"
# --- DOCKER HOST / NETWORK ---
assert_eq "DOCKER_HOST sets FLOCI_DOCKER_DOCKER_HOST" \
"unix:///var/run/docker.sock" \
"$(_run FLOCI_DOCKER_DOCKER_HOST DOCKER_HOST=unix:///var/run/docker.sock)"
assert_eq "DOCKER_NETWORK sets FLOCI_SERVICES_DOCKER_NETWORK" \
"shared" \
"$(_run FLOCI_SERVICES_DOCKER_NETWORK DOCKER_NETWORK=shared)"
assert_eq "FLOCI_SERVICES_DOCKER_NETWORK wins over DOCKER_NETWORK" \
"override" \
"$(_run FLOCI_SERVICES_DOCKER_NETWORK DOCKER_NETWORK=shared FLOCI_SERVICES_DOCKER_NETWORK=override)"
# --- DNS SUFFIXES ---
assert_eq "DNS suffixes set when FLOCI_DNS_EXTRA_SUFFIXES unset" \
"localhost.localstack.cloud,localhost.floci.io" \
"$(_run FLOCI_DNS_EXTRA_SUFFIXES)"
assert_eq "DNS suffixes appended to existing FLOCI_DNS_EXTRA_SUFFIXES" \
"custom.internal,localhost.localstack.cloud,localhost.floci.io" \
"$(_run FLOCI_DNS_EXTRA_SUFFIXES FLOCI_DNS_EXTRA_SUFFIXES=custom.internal)"
# --- Summary ---
printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}"
[ "${FAIL}" -eq 0 ]
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
LOG_FILE=$(mktemp)
trap 'rm -f "$LOG_FILE"' EXIT
export DOCKER_TEST_LOG="$LOG_FILE"
export FAIL_RUN_SUITE=sdk-test-node
export FAIL_BUILD_SUITE=
docker() {
if [[ "$1" == compose && "$2" == up ]]; then
return 0
fi
if [[ "$1" == compose && "$2" == ps ]]; then
printf 'floci-container\n'
return 0
fi
if [[ "$1" == inspect ]]; then
printf '172.20.0.2\n'
return 0
fi
if [[ "$1" == build ]]; then
suite="${!#}"
suite="${suite#compatibility-tests/}"
printf 'build:%s\n' "$suite" >> "$DOCKER_TEST_LOG"
[[ "$suite" != "$FAIL_BUILD_SUITE" ]]
return
fi
if [[ "$1" == run ]]; then
suite="${!#}"
suite="${suite#compat-}"
printf 'run:%s\n' "$suite" >> "$DOCKER_TEST_LOG"
[[ "$suite" != "$FAIL_RUN_SUITE" ]]
return
fi
printf 'unexpected docker command: %s\n' "$*" >&2
return 1
}
curl() {
return 0
}
export -f docker curl
if bash "$REPO_ROOT/docker/run-docker-tests.sh"; then
status=0
else
status=$?
fi
if [[ "$status" -eq 0 ]]; then
echo "expected a failed suite to produce a non-zero exit status" >&2
exit 1
fi
expected_suites=(
sdk-test-python
sdk-test-node
sdk-test-java
sdk-test-go
sdk-test-awscli
compat-cdk
compat-terraform
compat-opentofu
)
for suite in "${expected_suites[@]}"; do
grep -qx "run:$suite" "$LOG_FILE" || {
echo "expected suite to run after failure: $suite" >&2
exit 1
}
done
: > "$LOG_FILE"
export FAIL_RUN_SUITE=
bash "$REPO_ROOT/docker/run-docker-tests.sh" || {
echo "expected all successful suites to produce a zero exit status" >&2
exit 1
}
: > "$LOG_FILE"
export FAIL_BUILD_SUITE=compat-cdk
if bash "$REPO_ROOT/docker/run-docker-tests.sh"; then
status=0
else
status=$?
fi
if [[ "$status" -eq 0 ]]; then
echo "expected a failed image build to produce a non-zero exit status" >&2
exit 1
fi
for suite in "${expected_suites[@]}"; do
if [[ "$suite" == "compat-cdk" ]]; then
grep -q "run:$suite" "$LOG_FILE" && {
echo "failed image build should not run suite: $suite" >&2
exit 1
}
continue
fi
grep -q "run:$suite" "$LOG_FILE" || {
echo "expected suite to run after failed image build: $suite" >&2
exit 1
}
done