From d014202b6e122092f3b3837d265058bde6cb6b51 Mon Sep 17 00:00:00 2001 From: "Charles Graham, SWT" Date: Wed, 12 Aug 2026 09:04:59 -0500 Subject: [PATCH] Add reproducible development container Signed-off-by: Charles Graham, SWT --- .devcontainer/.gitattributes | 1 + .devcontainer/devcontainer-lock.json | 24 ++++++++++++ .devcontainer/devcontainer.json | 58 ++++++++++++++++++++++++++++ .devcontainer/post-create.sh | 15 +++++++ README.md | 24 ++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 .devcontainer/.gitattributes create mode 100644 .devcontainer/devcontainer-lock.json create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/post-create.sh diff --git a/.devcontainer/.gitattributes b/.devcontainer/.gitattributes new file mode 100644 index 000000000..dfdb8b771 --- /dev/null +++ b/.devcontainer/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 000000000..1dc78928f --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,24 @@ +{ + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "1.10.0", + "resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e", + "integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e" + }, + "ghcr.io/devcontainers/features/java:1": { + "version": "1.8.1", + "resolved": "ghcr.io/devcontainers/features/java@sha256:8157bab2d8d71e40b2f3128c162fab763e2b11038fdd33784549290a5d386b48", + "integrity": "sha256:8157bab2d8d71e40b2f3128c162fab763e2b11038fdd33784549290a5d386b48" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "1.7.1", + "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6", + "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "1.8.0", + "resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511", + "integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..9b7a89c3f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +{ + "name": "CWMS Data API", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers/features/java:1": { + "version": "11", + "jdkDistro": "ms" + }, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "latest", + "moby": true, + "dockerDashComposeVersion": "v2" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "22" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.13", + "installTools": false + } + }, + "containerEnv": { + "TESTCONTAINERS_HOST_OVERRIDE": "host.docker.internal" + }, + "forwardPorts": [7000, 8081, 5173, 8000], + "portsAttributes": { + "7000": { + "label": "CDA Gradle server" + }, + "8081": { + "label": "CDA Docker Compose stack" + }, + "5173": { + "label": "CDA GUI development server" + }, + "8000": { + "label": "Sphinx documentation server" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "vscjava.vscode-java-pack", + "vscjava.vscode-gradle", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-python.python" + ] + } + }, + "hostRequirements": { + "cpus": 4, + "memory": "8gb", + "storage": "32gb" + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "remoteUser": "vscode" +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 000000000..d1c04012f --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +git config --global --add safe.directory "$PWD" + +# Docker Desktop bind mounts preserve Windows line endings and expose every file as executable. +# Match Git for Windows so the same checkout stays clean inside the container. +if grep -qi microsoft /proc/version; then + git config --global core.autocrlf true +fi + +# npm 10 strips platform metadata from the committed lockfile during the Gradle GUI build. +npm install --global npm@11 + +./gradlew --version diff --git a/README.md b/README.md index 9bdd87e38..eb45d409f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,30 @@ End user documentation available here: [📕 Read the Docs](https://cwms-data-ap Development and runtime currently requires java 11. JDKs and JREs greater than 11 should work, please report if they don't. +### Dev Container + +The repository includes a [Dev Container](https://containers.dev/) with Java 11, Node.js 22, +Python 3.13, and Docker Compose. It supports the Gradle build, CDA GUI and client development, +documentation builds, the local Docker Compose stack, and Docker-backed integration tests. + +To use it locally: + +1. Install Docker and the Visual Studio Code Dev Containers extension. +2. Open the cloned repository in Visual Studio Code. +3. Run **Dev Containers: Reopen in Container** from the command palette. + +GitHub Codespaces also detects the same configuration automatically. The first container creation +downloads the development image and initializes the Gradle wrapper. After it completes, run the +normal project commands from the container terminal, for example: + +```bash +./gradlew build +docker compose up -d --force-recreate +``` + +The integration tests use the host Docker engine through the forwarded Docker socket. Their database +setup has the same resource and startup-time requirements described in [Testing](#testing). + To build the war: