diff --git a/.github/workflows/ci-rocker.yml b/.github/workflows/ci-rocker.yml new file mode 100644 index 0000000..13f3f97 --- /dev/null +++ b/.github/workflows/ci-rocker.yml @@ -0,0 +1,61 @@ +name: CI - Dockerfile.rocker + +on: + pull_request: + paths: + - "Dockerfile.rocker" + - "install_r_packages.R" + - "docker-compose.ci.yml" + - ".github/workflows/ci-rocker.yml" + push: + branches: [develop] + paths: + - "Dockerfile.rocker" + - "install_r_packages.R" + - "docker-compose.ci.yml" + - ".github/workflows/ci-rocker.yml" + +jobs: + build-rocker: + name: Build Dockerfile.rocker + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Dockerfile.rocker + run: | + docker build -t bsyncr-server-rocker \ + --build-arg BASE_IMAGE_TAG=4.4.3 \ + -f Dockerfile.rocker . + + - name: Run smoke test + run: | + docker run -d -p 8080:5000 \ + --name bsyncr-rocker \ + bsyncr-server-rocker + echo "Waiting for server to start..." + for i in $(seq 1 60); do + if curl -sf http://localhost:8080/health > /dev/null 2>&1; then + echo "Server ready (took ${i}s)" + break + fi + if [ "$i" -eq 60 ]; then + echo "FAIL: Server did not start within 60s" + docker logs bsyncr-rocker | tail -30 + exit 1 + fi + sleep 1 + done + curl -f http://localhost:8080/health + echo "" + echo "Health check passed" + + - name: Cleanup + if: always() + run: | + docker stop bsyncr-rocker 2>/dev/null || true + docker rm bsyncr-rocker 2>/dev/null || true diff --git a/Dockerfile.rocker b/Dockerfile.rocker index a902925..6b08ab5 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,14 +1,18 @@ ARG BASE_IMAGE_TAG +ARG ROCKER_REGISTRY +ARG NODE_REGISTRY -FROM node:22-alpine3.19 +FROM ${NODE_REGISTRY:+${NODE_REGISTRY}/}node:22-alpine3.19 AS node # Install necessary dependencies (curl and ca-certificates) -RUN apk add --no-cache curl ca-certificates && \ - curl -fsSLk -o /usr/local/share/ca-certificates/nrel_root.crt https://raw.github.nrel.gov/TADA/nrel-certs/v20180329/certs/nrel_root.pem && \ - curl -fsSLk -o /usr/local/share/ca-certificates/nrel_xca1.crt https://raw.github.nrel.gov/TADA/nrel-certs/v20180329/certs/nrel_xca1.pem && \ - update-ca-certificates +# NLR certs are only reachable from internal networks (CodeBuild VPC); +# on public CI runners this fails gracefully and the build continues without them. +RUN (apk add --no-cache curl ca-certificates || true) && \ + (curl -fsSLk --connect-timeout 5 -o /usr/local/share/ca-certificates/nrel_root.crt https://raw.github.nrel.gov/TADA/nrel-certs/v20180329/certs/nrel_root.pem || true) && \ + (curl -fsSLk --connect-timeout 5 -o /usr/local/share/ca-certificates/nrel_xca1.crt https://raw.github.nrel.gov/TADA/nrel-certs/v20180329/certs/nrel_xca1.pem || true) && \ + update-ca-certificates || true -FROM rocker/r-ver:${BASE_IMAGE_TAG:-4.4.3} +FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}rocker/r-ver:${BASE_IMAGE_TAG:-4.4.3} RUN mkdir /usr/src/app @@ -17,6 +21,11 @@ COPY --from=node /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificat COPY --from=node /usr/share/ca-certificates /usr/share/ca-certificates COPY --from=node /usr/local/share/ca-certificates /usr/local/share/ca-certificates +# Rebuild the CA trust store so R/libcurl trusts the NLR proxy certs +RUN update-ca-certificates +ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + # Install system dependencies and required tools RUN apt-get update && \ apt-get upgrade -y && \ diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 0000000..03dfb42 --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,48 @@ +version: "3.8" +# CI smoke test for Dockerfile.rocker (appfleet ECS build) +# Usage: docker compose -f docker-compose.ci.yml up --build --abort-on-container-exit + +services: + bsyncr-server: + build: + context: . + dockerfile: Dockerfile.rocker + args: + BASE_IMAGE_TAG: "4.4.3" + ports: + - "8080:5000" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 30s + + smoke-test: + image: curlimages/curl:latest + depends_on: + bsyncr-server: + condition: service_healthy + volumes: + - ./tests:/tests + entrypoint: ["/bin/sh", "-c"] + command: + - | + echo "=== Health check ===" + curl -f http://bsyncr-server:5000/health || exit 1 + echo "" + echo "=== POST BuildingSync file ===" + curl -X POST "http://bsyncr-server:5000/?model_type=SLR" \ + -F "file=@/tests/data/ex_bsync.xml" \ + --max-time 120 \ + --output /tmp/slr_results.zip + if [ ! -f /tmp/slr_results.zip ]; then + echo "FAIL: slr_results.zip not created" + exit 1 + fi + SIZE=$(stat -c%s /tmp/slr_results.zip 2>/dev/null || stat -f%z /tmp/slr_results.zip) + if [ "$SIZE" -le 10240 ]; then + echo "FAIL: slr_results.zip is less than 10kb (got $SIZE bytes)" + exit 1 + fi + echo "PASS: slr_results.zip is $SIZE bytes" diff --git a/install_r_packages.R b/install_r_packages.R index 1754fc6..d2f217c 100644 --- a/install_r_packages.R +++ b/install_r_packages.R @@ -1,7 +1,10 @@ -# BuildingSync®, Copyright (c) Alliance for Sustainable Energy, LLC, and other contributors. +# BuildingSync, Copyright (c) Alliance for Sustainable Energy, LLC, and other contributors. # See also https://github.com/BuildingSync/bsyncr-server/blob/main/LICENSE.txt +# Use HTTPS CRAN mirror +options(repos = c(CRAN = "https://cloud.r-project.org")) + # Install required packages if not already installed required_packages <- c( "remotes", "crayon", "dplyr", "tidyr", "crul", "xml2", "testthat", "anytime", "lubridate", "segmented", "xts", "zoo", "ggplot2", "scales", "XML", "rappdirs", "gridExtra", "isdparser", "geonames", "hoardr", "data.table" @@ -10,10 +13,16 @@ required_packages <- c( cat("Checking and installing required packages...\n") for (pkg in required_packages) { if (!requireNamespace(pkg, quietly = TRUE)) { - install.packages(pkg, repos = "http://cran.us.r-project.org") + cat(paste0("Installing: ", pkg, "\n")) + install.packages(pkg) } } +# Verify remotes installed successfully before proceeding +if (!requireNamespace("remotes", quietly = TRUE)) { + stop("Failed to install 'remotes' package. Check network connectivity and CRAN mirror.") +} + library("remotes") # RNOAA for weather data remotes::install_github("ropensci/rnoaa@v1.4.0", upgrade = "never")