From 9f3f3234df2b972e96dfb0119af39f5ca4c639df Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 15 Jul 2026 17:13:20 -0600 Subject: [PATCH 1/8] fix: use HTTPS CRAN mirror and fix CA cert trust for R/libcurl - install_r_packages.R: use https://cloud.r-project.org (was HTTP) - install_r_packages.R: add verification that remotes installed before calling library('remotes') - gives clear error message on failure - Dockerfile.rocker: run update-ca-certificates after copying NLR certs - Dockerfile.rocker: set CURL_CA_BUNDLE and SSL_CERT_FILE env vars so R's libcurl uses the system cert store - Add docker-compose.ci.yml for smoke testing Dockerfile.rocker - Add .github/workflows/ci-rocker.yml to test Dockerfile.rocker on PR --- .github/workflows/ci-rocker.yml | 61 +++++++++++++++++++++++++++++++++ Dockerfile.rocker | 5 +++ docker-compose.ci.yml | 48 ++++++++++++++++++++++++++ install_r_packages.R | 13 +++++-- 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-rocker.yml create mode 100644 docker-compose.ci.yml diff --git a/.github/workflows/ci-rocker.yml b/.github/workflows/ci-rocker.yml new file mode 100644 index 0000000..a7e0967 --- /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..787dd7e 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -17,6 +17,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") From 899c04572cd914b85cd0582cf6e541f3a688bce6 Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 15 Jul 2026 17:46:45 -0600 Subject: [PATCH 2/8] style: format ci-rocker.yml with prettier --- .github/workflows/ci-rocker.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-rocker.yml b/.github/workflows/ci-rocker.yml index a7e0967..13f3f97 100644 --- a/.github/workflows/ci-rocker.yml +++ b/.github/workflows/ci-rocker.yml @@ -3,17 +3,17 @@ name: CI - Dockerfile.rocker on: pull_request: paths: - - 'Dockerfile.rocker' - - 'install_r_packages.R' - - 'docker-compose.ci.yml' - - '.github/workflows/ci-rocker.yml' + - "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' + - "Dockerfile.rocker" + - "install_r_packages.R" + - "docker-compose.ci.yml" + - ".github/workflows/ci-rocker.yml" jobs: build-rocker: From da726ff5b831e74da434c27703b2782505f7cbe6 Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 22 Jul 2026 14:19:56 -0600 Subject: [PATCH 3/8] fix: use ECR pull-through cache for Docker Hub base images When ROCKER_REGISTRY is set (e.g. to the ECR pull-through cache prefix), Docker Hub images are pulled via the cache, avoiding rate limits and transient Docker Hub outages. When ROCKER_REGISTRY is unset (local dev), falls back to pulling directly from Docker Hub. --- Dockerfile.rocker | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index 787dd7e..4140fbb 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,6 +1,7 @@ ARG BASE_IMAGE_TAG +ARG ROCKER_REGISTRY -FROM node:22-alpine3.19 +FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}node:22-alpine3.19 AS node # Install necessary dependencies (curl and ca-certificates) RUN apk add --no-cache curl ca-certificates && \ @@ -8,7 +9,7 @@ RUN apk add --no-cache curl ca-certificates && \ 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 -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 From 2ba6791ffc21fcc6da75f3191274a4116e30e2ed Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 22 Jul 2026 14:53:57 -0600 Subject: [PATCH 4/8] fix: make NLR cert download non-fatal for public CI runners The node stage downloads NLR CA certs from raw.github.nrel.gov which is only reachable from internal networks (CodeBuild VPC). On public GitHub Actions runners this fails with DNS resolution error. Add --connect-timeout 5 and || true so the build continues without the certs when running outside the internal network. The certs are only needed for production deployments (handled by CodeBuild). --- Dockerfile.rocker | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index 4140fbb..b83a4f3 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -4,9 +4,11 @@ ARG ROCKER_REGISTRY FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}node:22-alpine3.19 AS node # Install necessary dependencies (curl and 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 && \ - 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 && \ + (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 FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}rocker/r-ver:${BASE_IMAGE_TAG:-4.4.3} From cd047a4fc92b23c3dfb388ba01f378a0aae9a14a Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 22 Jul 2026 15:35:32 -0600 Subject: [PATCH 5/8] fix: only use pull-through cache for rocker/r-ver, not node stage The node stage is a lightweight cert-fetching helper. Official Docker Hub images (node, alpine, etc.) require the library/ namespace prefix in ECR pull-through cache (docker-hub/library/node), but third-party images like rocker/r-ver work directly (docker-hub/rocker/r-ver). Keep node pulling from Docker Hub directly - it's small, rarely rate-limited, and the cert download is already non-fatal. --- Dockerfile.rocker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index b83a4f3..6f2e4ae 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,7 +1,7 @@ ARG BASE_IMAGE_TAG ARG ROCKER_REGISTRY -FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}node:22-alpine3.19 AS node +FROM node:22-alpine3.19 AS node # Install necessary dependencies (curl and ca-certificates) # NLR certs are only reachable from internal networks (CodeBuild VPC); From ee7b311c4a0e2f9f85a54edc42cf80d334fc67f7 Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Wed, 22 Jul 2026 17:06:41 -0600 Subject: [PATCH 6/8] Revert "fix: only use pull-through cache for rocker/r-ver, not node stage" This reverts commit cd047a4fc92b23c3dfb388ba01f378a0aae9a14a. --- Dockerfile.rocker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index 6f2e4ae..b83a4f3 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,7 +1,7 @@ ARG BASE_IMAGE_TAG ARG ROCKER_REGISTRY -FROM node:22-alpine3.19 AS node +FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}node:22-alpine3.19 AS node # Install necessary dependencies (curl and ca-certificates) # NLR certs are only reachable from internal networks (CodeBuild VPC); From e8d361e486722bca189c88c0a7a62076df5c5488 Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Thu, 23 Jul 2026 11:05:59 -0600 Subject: [PATCH 7/8] Reapply "fix: only use pull-through cache for rocker/r-ver, not node stage" This reverts commit ee7b311c4a0e2f9f85a54edc42cf80d334fc67f7. --- Dockerfile.rocker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index b83a4f3..6f2e4ae 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,7 +1,7 @@ ARG BASE_IMAGE_TAG ARG ROCKER_REGISTRY -FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}node:22-alpine3.19 AS node +FROM node:22-alpine3.19 AS node # Install necessary dependencies (curl and ca-certificates) # NLR certs are only reachable from internal networks (CodeBuild VPC); From e716ebf1c8e351b40ae06e612b5f12aec5c967e2 Mon Sep 17 00:00:00 2001 From: Damon Haley Date: Tue, 11 Aug 2026 09:58:55 -0600 Subject: [PATCH 8/8] fix: add NODE_REGISTRY build arg for ECR pull-through cache - Add NODE_REGISTRY ARG with conditional prefix pattern (same as ROCKER_REGISTRY) - falls back to Docker Hub when unset - Make apk add and update-ca-certificates non-fatal so transient Alpine CDN failures don't break the build - NODE_REGISTRY should be set to the ECR pull-through cache path including library/ prefix for official images, e.g.: 991404956194.dkr.ecr.us-west-2.amazonaws.com/docker-hub/library --- Dockerfile.rocker | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile.rocker b/Dockerfile.rocker index 6f2e4ae..6b08ab5 100644 --- a/Dockerfile.rocker +++ b/Dockerfile.rocker @@ -1,15 +1,16 @@ ARG BASE_IMAGE_TAG ARG ROCKER_REGISTRY +ARG NODE_REGISTRY -FROM node:22-alpine3.19 AS node +FROM ${NODE_REGISTRY:+${NODE_REGISTRY}/}node:22-alpine3.19 AS node # Install necessary dependencies (curl and 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 && \ +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 + update-ca-certificates || true FROM ${ROCKER_REGISTRY:+${ROCKER_REGISTRY}/}rocker/r-ver:${BASE_IMAGE_TAG:-4.4.3}