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
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
* Version updates (build dependencies)
* TBD

## 5.2.1

* Features and fixes
* fix: Restore the ability to persist data to a mounted Docker volume. Since the migration to the Cloud Native Buildpacks OCI image (which runs as the non-root `cnb` user), a mounted named volume was no longer writable. The image now pre-creates a `cnb`-owned `/s3mockroot` directory, so a Docker named volume mounted at `/s3mockroot` (with `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=/s3mockroot`) is writable without running the container as root. S3Mock now also fails fast with a clear message when the configured store root is not writable, creates nested store-root paths (not just a single missing directory), and `S3MockContainer.withNamedVolume(...)` plus a container restart persistence test were added. The image also restores `/` (rather than the Buildpacks default `/workspace`) as its working directory, so a *relative* `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` resolves exactly as it did before 5.2 instead of silently resolving under `/workspace` and missing the mounted volume; the resolved absolute root folder is now always logged at startup alongside the configured value. ([#3139](https://github.com/adobe/S3Mock/issues/3139))
* Version updates (deliverable dependencies)
* N/A
* Version updates (build dependencies)
* Bump actions/setup-java from 5.7.0 to 6.0.0
* Bump github/codeql-action from 4.37.7 to 4.37.9
* Bump docker/setup-qemu-action from 4.2.0 to 4.3.0
* Bump step-security/harden-runner from 2.21.0 to 2.21.1
Comment thread
afranken marked this conversation as resolved.

## 5.2.0

* Features and fixes
Expand Down Expand Up @@ -216,7 +228,7 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
* Bump docker/setup-qemu-action from 4.1.0 to 4.2.0
* Bump github/codeql-action from 4.36.2 to 4.37.7
* Bump ossf/scorecard-action from 2.4.3 to 2.4.4
* Bump step-security/harden-runner from 2.19.4 to 2.21.1
* Bump step-security/harden-runner from 2.19.4 to 2.21.0
Comment thread
afranken marked this conversation as resolved.

## 5.1.0

Expand Down
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,20 +302,46 @@ services:
- 9191:9191
```

**With persistent storage:**
**With persistent storage (named volume):**
```yaml
services:
s3mock:
image: adobe/s3mock:latest
environment:
- COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=containers3root
# /s3mockroot is pre-created in the image, owned by the non-root user the container runs as,
# so a mounted named volume is writable without running the container as root.
- COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=/s3mockroot
- COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT=true
ports:
- 9090:9090
volumes:
- ./locals3root:/containers3root
- s3mockdata:/s3mockroot

volumes:
s3mockdata:
```

> The store root **must** be `/s3mockroot` for a Docker named volume: the image runs as the
> non-root `cnb` user, and Docker only makes a named volume writable by that user when it is
> mounted onto a directory that already exists in the image with that ownership. A bind mount
> (`./host/path:/s3mockroot`) keeps its host ownership instead, so it must be writable by that
> user (uid 1000).
>
> A Docker named volume (`s3mockdata:/s3mockroot` above) is **not** a host directory you can
> browse directly — Docker manages its storage location internally. Use
> `docker volume inspect <project>_s3mockdata` to find its `Mountpoint`, or
> `docker run --rm -v <project>_s3mockdata:/v alpine ls /v` to list its contents. If you need a
> predictable, browsable host path instead, use a bind mount
> (`./locals3root:/s3mockroot`), keeping in mind the ownership requirement above.
>
> A **relative** `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` (e.g. `data` instead of `/data`) resolves
> against the container's working directory, which the image sets to `/` — so `data` resolves
> to `/data`, matching a volume mounted at `/data`. This is unrelated to the `/s3mockroot`
> named-volume requirement above: prefer an absolute path in general so the resolved location
> doesn't depend on the image's working directory. The resolved absolute root folder is always
> logged at startup, alongside the configured value, to make a mismatch between the two easy to
> spot.

### Testcontainers

The [`S3MockContainer`](testsupport/testcontainers/src/main/kotlin/com/adobe/testing/s3mock/testcontainers/S3MockContainer.kt) provides a ready-to-use Testcontainers implementation.
Expand Down Expand Up @@ -443,7 +469,7 @@ Configure S3Mock using environment variables:

| Variable | Default | Description |
|-------------------------------------------------------|---------------------|---------------------------------------------------------------|
| `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` | Java temp directory | Base directory for file storage |
| `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` | Java temp directory | Base directory for file storage (use `/s3mockroot` to persist to a mounted named volume) |
| `COM_ADOBE_TESTING_S3MOCK_STORE_REGION` | `us-east-1` | AWS region to mock |
| `COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS` | none | Comma-separated list of buckets to create on startup |
| `COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT` | `false` | Keep files after shutdown |
Expand Down
14 changes: 10 additions & 4 deletions docs/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ S3Mock is configured via environment variables (Docker / Testcontainers) or Spri

| Environment variable | Spring property | Default | Description |
|---|---|---|---|
| `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` | `com.adobe.testing.s3mock.store.root` | temp dir | Storage root directory |
| `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` | `com.adobe.testing.s3mock.store.root` | temp dir | Storage root directory (use `/s3mockroot` to persist to a mounted Docker named volume) |
| `COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT` | `com.adobe.testing.s3mock.store.retainFilesOnExit` | `false` | Keep files on shutdown |
| `COM_ADOBE_TESTING_S3MOCK_STORE_REGION` | `com.adobe.testing.s3mock.store.region` | `us-east-1` | AWS region |
| `COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS` | `com.adobe.testing.s3mock.store.initialBuckets` | _(none)_ | Comma-separated bucket names to create on startup |
Expand All @@ -56,16 +56,20 @@ Or with Docker directly:
docker run -p 9090:9090 -p 9191:9191 adobe/s3mock
```

With persistent storage:
With persistent storage (named volume):
```bash
docker run \
-p 9090:9090 -p 9191:9191 \
-e COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=/data \
-e COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=/s3mockroot \
-e COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT=true \
-v /local/path:/data \
-v s3mockdata:/s3mockroot \
adobe/s3mock
```

`/s3mockroot` is pre-created in the image owned by the non-root `cnb` user the container runs as,
so a Docker named volume mounted there is writable without running as root. A bind mount
(`-v /local/path:/s3mockroot`) keeps its host ownership and must be writable by uid 1000 instead.

## Running Tests

```bash
Expand Down Expand Up @@ -119,3 +123,5 @@ MANAGEMENT_ENDPOINTS_ACCESS_DEFAULT=unrestricted docker run -p 9090:9090 -p 9191
| `NoClassDefFoundError` in in-process test | Spring Boot version mismatch | Ensure your project is Spring Boot 4.x compatible |
| HTTPS connection refused | Client not trusting self-signed cert | Configure trust-all-certs on your AWS SDK client |
| Empty bucket after restart | `retainFilesOnExit` defaults to false | Set `COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT=true` and use a fixed `STORE_ROOT` |
| HTTP 500 / `not writable` at startup when mounting a volume | Docker named volume or bind mount not writable by the non-root `cnb` user | Mount a **named volume** at `/s3mockroot` and set `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=/s3mockroot`; for a bind mount, make the host directory writable by uid 1000 |
| Container starts fine, `2xx` responses, but a mounted volume stays empty | A **relative** `STORE_ROOT` (e.g. `s3root`) resolved against the wrong working directory, missing the mounted volume | Use an **absolute** `STORE_ROOT` (e.g. `/s3root`); check the startup log line showing the resolved absolute root folder against your mount path |
35 changes: 25 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
<checkstyle.version>14.0.0</checkstyle.version>

<docker.image.name>adobe/s3mock</docker.image.name>
<!--
Local-only Cloud Native Buildpacks run image (see server/src/main/docker/run-image/Dockerfile).
It is built into the daemon and baked into the published app image, so it is never pushed to a
registry itself.
-->
<docker.run.image.name>adobe/s3mock-run</docker.run.image.name>
<dokka-maven-plugin.version>2.2.0</dokka-maven-plugin.version>
<exec-maven-plugin.version>3.6.3</exec-maven-plugin.version>

Expand Down Expand Up @@ -231,7 +237,7 @@
year bumps scoped to files actually touched (see INVARIANTS.md). -->
<ratchetFrom>origin/main</ratchetFrom>
<kotlin>
<ktlint />
<ktlint/>
<licenseHeader>
<file>${maven.multiModuleProjectDirectory}/etc/license-header-spotless.txt</file>
</licenseHeader>
Expand Down Expand Up @@ -401,8 +407,8 @@
<requireJavaVersion>
<version>${java.version}</version>
</requireJavaVersion>
<dependencyConvergence />
<requireUpperBoundDeps />
<dependencyConvergence/>
<requireUpperBoundDeps/>
</rules>
</configuration>
</plugin>
Expand Down Expand Up @@ -470,15 +476,24 @@
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<goals>deploy -DskipTests -Prelease -P!build-docker-image -Ppush-docker-image</goals>
<!-- Skip Spotless during the release. The source already passed spotless:check in
CI and every PRB. The maven-release-plugin rewrites every pom to set the
release/next-dev version, and its JDOM writer normalizes empty elements to
`<foo />` (with a space), which Spotless sortPom would then reject (`<foo/>`) -
a violation the plugin introduces itself, not present in the committed source.
`arguments` is passed to both the prepare (preparationGoals) and perform
<!-- The maven-release-plugin rewrites every pom to set the release/next-dev
version, and its JDOM writer normalizes empty elements to `<foo />` (with a
space), which Spotless sortPom would then reject (`<foo/>`) - a violation the
plugin introduces itself, not present in the committed source. Both
preparationGoals (after the release-version rewrite) and completionGoals
(after the next-dev-version rewrite) run `spotless:apply` *before* the
rewritten poms are committed, so the poms committed to main are always
correctly formatted - not just skipped-and-left-broken, which previously left
misformatted poms on main that failed spotless:check (ratchetFrom=origin/main)
on the next PR touching any pom.xml. `spotless:apply` runs first so the
plugin's own `<foo />` violations are fixed before `install`'s bound
spotless:check phase would otherwise fail the build; `-Dspotless.check.skip`
is kept as a safety net for the perform (goals) forked build. `arguments` is
passed to both the prepare (preparationGoals/completionGoals) and perform
(goals) forked Maven builds. -->
<arguments>-Dspotless.check.skip=true</arguments>
<preparationGoals>clean install</preparationGoals>
<preparationGoals>spotless:apply clean install</preparationGoals>
<completionGoals>spotless:apply</completionGoals>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
Expand Down
93 changes: 93 additions & 0 deletions server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@
CVEs reported by users' image scanners. Multi-arch (linux/amd64 + linux/arm64).
-->
<builder>bellsoft/buildpacks.builder:musl</builder>
<!--
Use the locally built custom run image (server/src/main/docker/run-image/Dockerfile)
which pre-creates a `cnb`-owned /s3mockroot so a mounted named volume is writable by
the non-root user (see https://github.com/adobe/S3Mock/issues/3139). The concrete
runImage tag is set per build-image execution below (it is architecture specific).
IF_NOT_PRESENT keeps the buildpack from trying to pull our local-only run image from a
registry, while still pulling the builder on a clean machine when it is absent.
-->
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
<env>
<BP_JVM_VERSION>${java.version}</BP_JVM_VERSION>
<!-- Run jdeps + jlink to produce a minimal custom JRE (only required modules). -->
Expand Down Expand Up @@ -272,6 +281,33 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<!--
Build the custom run image (host architecture) before build-image runs, so the
buildpack can resolve it locally via the IF_NOT_PRESENT pull policy. It pre-creates
a `cnb`-owned /s3mockroot so named volumes are writable by the non-root user.
-->
<execution>
<id>build-run-image</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>docker</executable>
<arguments>
<argument>build</argument>
<argument>--tag</argument>
<argument>${docker.run.image.name}:musl</argument>
<argument>${project.basedir}/src/main/docker/run-image</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
Comment thread
afranken marked this conversation as resolved.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand All @@ -285,6 +321,7 @@
<configuration>
<image>
<name>${docker.image.name}:${project.version}</name>
<runImage>${docker.run.image.name}:musl</runImage>
<!-- Version tag names: major, minor, latest (patch is the image name above). -->
<tags>
<tag>${docker.image.name}:${parsedVersion.majorVersion}</tag>
Expand All @@ -303,6 +340,12 @@
Release-only profile: builds both linux/amd64 and linux/arm64 images (arm64 via QEMU
emulation on an amd64 runner), publishes each to a temporary per-architecture tag on
Docker Hub, then merges them into a single multi-architecture manifest for the final tags.

Duplicates the build-helper-maven-plugin/exec-maven-plugin declarations from
build-docker-image above rather than sharing them: the two profiles are mutually
exclusive at release time (the root pom's release goals pass
"-P!build-docker-image -Ppush-docker-image"), and this profile's executions build/push
both architectures instead of just the host one.
-->
<profile>
<id>push-docker-image</id>
Expand All @@ -328,6 +371,54 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<!--
Comment thread
afranken marked this conversation as resolved.
Build the custom run image for each architecture before build-image runs, so the
buildpack resolves it locally (IF_NOT_PRESENT) instead of pulling from a registry.
A local Docker image tag holds a single architecture, so amd64 and arm64 get
distinct tags referenced by the matching build-image execution below. arm64 is
emulated via QEMU (buildx) on the amd64 release runner. The run image only adds a
`cnb`-owned /s3mockroot so mounted named volumes are writable by the non-root user.
-->
<execution>
<id>build-run-image-amd64</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>docker</executable>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>--platform</argument>
<argument>linux/amd64</argument>
<argument>--load</argument>
Comment thread
Copilot marked this conversation as resolved.
<argument>--tag</argument>
<argument>${docker.run.image.name}:musl-amd64</argument>
<argument>${project.basedir}/src/main/docker/run-image</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>build-run-image-arm64</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>docker</executable>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>--platform</argument>
<argument>linux/arm64</argument>
<argument>--load</argument>
<argument>--tag</argument>
<argument>${docker.run.image.name}:musl-arm64</argument>
<argument>${project.basedir}/src/main/docker/run-image</argument>
</arguments>
</configuration>
</execution>
<!--
Merge the two per-architecture images into a single multi-arch manifest for the
final tags. Runs after both images have been published (package phase).
Expand Down Expand Up @@ -382,6 +473,7 @@
<configuration>
<image>
<name>${docker.image.name}:${project.version}-amd64</name>
<runImage>${docker.run.image.name}:musl-amd64</runImage>
<imagePlatform>linux/amd64</imagePlatform>
<publish>true</publish>
</image>
Expand All @@ -396,6 +488,7 @@
<configuration>
<image>
<name>${docker.image.name}:${project.version}-arm64</name>
<runImage>${docker.run.image.name}:musl-arm64</runImage>
<imagePlatform>linux/arm64</imagePlatform>
<publish>true</publish>
</image>
Expand Down
Loading
Loading