diff --git a/.gitignore b/.gitignore index f4b5e7378db..2c53a248a72 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ _site .jekyll-cache /bootstrap /vendor/ -Gemfile.lock tmp/ node_modules /src/*/bootstrap diff --git a/src/current/.dockerignore b/src/current/.dockerignore new file mode 100644 index 00000000000..c279631d7fa --- /dev/null +++ b/src/current/.dockerignore @@ -0,0 +1,35 @@ +# Git +.git +.gitignore + +# Jekyll build outputs +_site +.jekyll-cache +.jekyll-metadata +.sass-cache + +# Ruby vendor dependencies (will be installed in image) +vendor + +# Node modules +node_modules + +# Logs +*.log +build_*.log + +# OS files +.DS_Store +Thumbs.db + +# IDE +.idea +.vscode +*.swp +*.swo + +# Algolia state (generated during indexing) +algolia_state + +# Temporary files +tmp diff --git a/src/current/Dockerfile b/src/current/Dockerfile new file mode 100644 index 00000000000..faffd6f8b48 --- /dev/null +++ b/src/current/Dockerfile @@ -0,0 +1,68 @@ +# Hermetic Jekyll Documentation Build Image +# +# This Dockerfile creates a consistent build environment for the CockroachDB +# documentation site. It pins all Ruby, Python, and Node.js dependencies to +# ensure reproducible builds across all developers and CI environments. +# +# For build and publish instructions, see ci/README.md + +FROM ruby:3.4-slim + +# Version labels +LABEL org.opencontainers.image.title="CockroachDB Docs Builder" +LABEL org.opencontainers.image.description="Hermetic build environment for CockroachDB documentation" +LABEL org.opencontainers.image.source="https://github.com/cockroachdb/docs" +LABEL ruby.version="3.4.0" +LABEL bundler.version="4.0.0" +LABEL jekyll.version="4.3.4" + +# Install build and runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + git \ + python3 \ + python3-pip \ + curl \ + libxml2-dev \ + libxslt1-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js 20 LTS for Jest tests +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /docs + +# Install specific bundler version (must match Gemfile.lock BUNDLED WITH) +RUN gem install bundler:4.0.0 --no-document + +# Copy Gemfile and local gem for dependency installation +# The jekyll-algolia-dev directory contains a local gem that must be present +COPY Gemfile Gemfile.lock ./ +COPY jekyll-algolia-dev ./jekyll-algolia-dev/ + +# Configure bundler and install gems to /usr/local/bundle (default location) +# Ensure the lockfile in the image supports common Linux platforms +RUN bundle lock --add-platform aarch64-linux --add-platform x86_64-linux \ + && bundle config set --local jobs 4 \ + && bundle config set --local without development:test \ + && bundle install + +# Install Python dependencies for Algolia indexing +RUN pip3 install --break-system-packages --no-cache-dir \ + pyyaml \ + "algoliasearch>=3.0,<4.0" \ + beautifulsoup4 \ + lxml \ + tqdm + +# Set environment variables +ENV JEKYLL_ENV=development +ENV BUNDLE_FROZEN=true + +# Expose Jekyll server port +EXPOSE 4000 + +# Default command - serve the documentation locally +CMD ["bundle", "exec", "jekyll", "serve", "--host", "0.0.0.0", "--port", "4000"] diff --git a/src/current/Gemfile b/src/current/Gemfile index 208f25f007c..68e9b8dbb99 100644 --- a/src/current/Gemfile +++ b/src/current/Gemfile @@ -5,6 +5,10 @@ source "https://rubygems.org" # If you add to this file, it is recommended to run `make vendor` # (`gem install bundler && bundle install`) before running your next local build. # It may fail without at least running `bundle install`. +# +# If you modify this file, you'll need to rebuild the docs-builder Docker +# image to ensure CI and local builds use the updated dependencies. +# See ci/README.md for build and publish instructions. gem "jekyll", "4.3.4" gem "liquid-c", "~> 4.0.0" gem "redcarpet", "~> 3.6" diff --git a/src/current/Gemfile.lock b/src/current/Gemfile.lock new file mode 100644 index 00000000000..4ec80a7e590 --- /dev/null +++ b/src/current/Gemfile.lock @@ -0,0 +1,180 @@ +GIT + remote: https://github.com/ianjevans/jekyll-remote-include.git + revision: 231b7ccbd097167e40d1f2184c50e0868ba412bb + tag: v1.1.7 + specs: + jekyll-remote-include (1.1.7) + +PATH + remote: jekyll-algolia-dev + specs: + jekyll-algolia (1.6.0) + algolia_html_extractor (~> 2.6) + algoliasearch (~> 1.26) + filesize (~> 0.1) + jekyll (>= 3.6, < 5.0) + json (~> 2.0) + nokogiri (~> 1.6) + progressbar (~> 1.9) + verbal_expressions (~> 0.1.5) + +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + algolia_html_extractor (2.6.4) + json (~> 2.0) + nokogiri (~> 1.10) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + base64 (0.3.0) + bigdecimal (4.0.0) + colorator (1.1.0) + concurrent-ruby (1.3.6) + cssminify2 (2.1.0) + csv (3.3.5) + deep_merge (1.2.2) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + execjs (2.10.0) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + filesize (0.2.0) + forwardable-extended (2.6.0) + htmlcompressor (0.4.0) + http_parser.rb (0.8.0) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-get-json (1.0.0) + deep_merge (~> 1.2) + jekyll (>= 3.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-last-modified-at (1.3.2) + jekyll (>= 3.7, < 5.0) + jekyll-minifier (0.2.2) + cssminify2 (~> 2.1.0) + htmlcompressor (~> 0.4) + jekyll (~> 4.0) + json-minify (~> 0.0.3) + terser (~> 1.2.3) + jekyll-sass-converter (2.2.0) + sassc (> 2.0.1, < 3.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + json (2.18.0) + json-minify (0.0.3) + json (> 0) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + liquid-c (4.0.1) + liquid (>= 3.0.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + mercenary (0.4.0) + mutex_m (0.3.0) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + progressbar (1.13.0) + public_suffix (7.0.0) + racc (1.8.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + redcarpet (3.6.1) + rexml (3.4.4) + rouge (4.6.1) + rss (0.3.1) + rexml + safe_yaml (1.0.5) + sassc (2.4.0) + ffi (~> 1.9) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + terser (1.2.6) + execjs (>= 0.3.0, < 3) + unicode-display_width (2.6.0) + verbal_expressions (0.1.5) + webrick (1.9.2) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + base64 + bigdecimal + csv + jekyll (= 4.3.4) + jekyll-algolia (~> 1.0)! + jekyll-get-json + jekyll-include-cache + jekyll-last-modified-at + jekyll-minifier + jekyll-remote-include! + jekyll-sass-converter (~> 2.0) + liquid-c (~> 4.0.0) + logger + redcarpet (~> 3.6) + rss + webrick + +BUNDLED WITH + 2.7.2 diff --git a/src/current/Makefile b/src/current/Makefile index 063711deaa8..f9ee8cddc49 100644 --- a/src/current/Makefile +++ b/src/current/Makefile @@ -104,3 +104,70 @@ clean-site: clean-cache: rm -rf .jekyll-cache + +# ============================================================================= +# Docker-based builds +# ============================================================================= +# These targets use the hermetic docs-builder Docker image for consistent builds +# across all environments. See ci/README.md for more details. + +DOCKER_REGISTRY := us-docker.pkg.dev/release-notes-automation-stag/docs-builder +DOCKER_IMAGE := docs-builder +DOCKER_TAG := latest +# Run as current user to avoid permission issues with mounted volumes +DOCKER_USER := $(shell id -u):$(shell id -g) + +# Build the Docker image locally +.PHONY: docker-build +docker-build: + docker build -t $(DOCKER_IMAGE):local . + +# Serve documentation using Docker (with live reload) +.PHONY: docker-serve +docker-serve: + docker run -it --rm \ + --dns 8.8.8.8 \ + --user $(DOCKER_USER) \ + -p 4000:4000 \ + -v "$(CURDIR)":/docs \ + -e JEKYLL_ENV=development \ + -e HOME=/tmp \ + $(DOCKER_IMAGE):local \ + bundle exec jekyll serve --host 0.0.0.0 --port 4000 --incremental --trace \ + --config _config_base.yml,_config_cockroachdb.yml,_config_cockroachdb_local.yml + +# Build documentation using Docker (no serve) +.PHONY: docker-build-site +docker-build-site: + docker run -it --rm \ + --dns 8.8.8.8 \ + --user $(DOCKER_USER) \ + -v "$(CURDIR)":/docs \ + -e JEKYLL_ENV=production \ + -e HOME=/tmp \ + $(DOCKER_IMAGE):local \ + bundle exec jekyll build --trace \ + --config _config_base.yml,_config_cockroachdb.yml + +# Interactive shell in the Docker container +.PHONY: docker-shell +docker-shell: + docker run -it --rm \ + --dns 8.8.8.8 \ + --user $(DOCKER_USER) \ + -v "$(CURDIR)":/docs \ + -e HOME=/tmp \ + $(DOCKER_IMAGE):local \ + /bin/bash + +# Pull the latest pre-built image from GCP Artifact Registry +.PHONY: docker-pull +docker-pull: + docker pull $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) + docker tag $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKER_IMAGE):local + +# Push image to GCP Artifact Registry (requires gcloud auth) +.PHONY: docker-push +docker-push: + docker tag $(DOCKER_IMAGE):local $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) + docker push $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):$(DOCKER_TAG) diff --git a/src/current/_plugins/versions/release_info.rb b/src/current/_plugins/versions/release_info.rb index c7a8c9e9d90..ce69cb26c71 100644 --- a/src/current/_plugins/versions/release_info.rb +++ b/src/current/_plugins/versions/release_info.rb @@ -11,9 +11,16 @@ def generate(site) parent_dir = File.expand_path('..', site.source) # Step 2: Construct the paths to versions.csv and releases.yml + # Try parent/current/_data first (standard layout), fall back to site.source/_data (Docker) versions_path = File.join(parent_dir, "current/_data/versions.csv") releases_path = File.join(parent_dir, "current/_data/releases.yml") + # Fall back to site.source if parent path doesn't exist (e.g., in Docker container) + unless File.exist?(versions_path) + versions_path = File.join(site.source, "_data/versions.csv") + releases_path = File.join(site.source, "_data/releases.yml") + end + # Load versions and releases data versions_data = CSV.read(versions_path, headers: true) releases_data = YAML.load_file(releases_path) diff --git a/src/current/ci/README.md b/src/current/ci/README.md new file mode 100644 index 00000000000..c90251e1577 --- /dev/null +++ b/src/current/ci/README.md @@ -0,0 +1,195 @@ +# Documentation Build Docker Image + +This directory contains configuration for building and publishing the hermetic Docker image used for CockroachDB documentation builds. + +## Quick Start (Local Development) + +```bash +# 1. Build the Docker image (first time only) +make docker-build + +# 2. Serve docs locally with live reload +make docker-serve + +# 3. Open http://localhost:4000/docs/ in your browser +``` + +That's it! No Ruby, Bundler, or gem installation required on your machine. + +**Other useful commands:** +- `make docker-build-site` - Build without serving +- `make docker-shell` - Interactive shell for debugging +- `make docker-pull` - Pull pre-built image from GCP (instead of building locally) + +## Overview + +The `docs-builder` Docker image provides a consistent build environment with pinned versions of: + +| Component | Version | +|-----------|---------| +| Base Image | ruby:3.4-slim (Debian Trixie) | +| Ruby | 3.4.0 | +| Bundler | 4.0.0 | +| Jekyll | 4.3.4 | +| Python | 3.13+ | +| Node.js | 20 LTS | + +## Prerequisites + +- Docker installed locally +- For publishing: `gcloud` CLI configured with appropriate permissions + +## Building the Image Locally + +```bash +# From the repository root +docker build -t docs-builder:local . + +# Or use the Makefile target +make docker-build +``` + +## Running Locally with Docker + +### Serve documentation with live reload + +```bash +make docker-serve +# Then open http://localhost:4000 +``` + +### Interactive shell in the container + +```bash +make docker-shell +``` + +### Build without serving + +```bash +docker run -it --rm \ + -v "$(pwd)":/docs \ + docs-builder:local \ + bundle exec jekyll build --trace \ + --config _config_base.yml,_config_cockroachdb.yml +``` + +## Publishing to GCP Artifact Registry + +### Manual Publishing + +1. Authenticate with GCP: + ```bash + gcloud auth login + gcloud auth configure-docker us-docker.pkg.dev + ``` + +2. Build and push the image: + ```bash + # Set the image tag (use date-based versioning) + export IMAGE_TAG=$(date +%Y-%m-%d) + export REGISTRY=us-docker.pkg.dev/release-notes-automation-stag/docs-builder + + # Build for multi-architecture + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --tag ${REGISTRY}/docs-builder:${IMAGE_TAG} \ + --tag ${REGISTRY}/docs-builder:latest \ + --push \ + . + ``` + +### Automated Publishing with Cloud Build + +Trigger a Cloud Build to build and publish the image: + +```bash +gcloud builds submit --config ci/cloudbuild.yaml . +``` + +## Image Tagging Convention + +| Tag | Description | +|-----|-------------| +| `latest` | Most recent build | +| `YYYY-MM-DD` | Date-based version (e.g., `2025-01-15`) | +| `ruby3.4-bundler4.0` | Version-based for explicit compatibility | + +## When to Rebuild the Image + +Rebuild and publish a new image when: + +1. `Gemfile` or `Gemfile.lock` changes +2. Python dependencies change in build scripts +3. Ruby or Node.js version needs updating +4. Security updates are required for base image + +## Environment Variables + +The following environment variables can be passed to the container: + +| Variable | Description | Default | +|----------|-------------|---------| +| `JEKYLL_ENV` | Build environment (`development`, `production`, `preview`) | `development` | +| `ALGOLIA_API_KEY` | Algolia write API key for indexing | - | +| `PROD_ALGOLIA_API_KEY` | Production Algolia API key | - | + +## Volume Mounts + +| Mount Point | Purpose | +|-------------|---------| +| `/docs` | Mount the documentation source directory here | + +## Exposed Ports + +| Port | Service | +|------|---------| +| 4000 | Jekyll development server | + +## Troubleshooting + +### Permission errors with mounted volumes + +If you encounter permission errors (e.g., with `.jekyll-cache`, `_site`, `.jekyll-metadata`), run the container as your current user: + +```bash +docker run -it --rm \ + -v "$(pwd)":/docs \ + -u "$(id -u):$(id -g)" \ + -e HOME=/tmp \ + docs-builder:local \ + bundle exec jekyll build +``` + +If files were created by root in a previous run, remove them first: + +```bash +sudo rm -rf .jekyll-cache _site .jekyll-metadata +``` + +### DNS resolution errors + +If you see errors like "Failed to open TCP connection" or "getaddrinfo: Temporary failure in name resolution", add explicit DNS: + +```bash +docker run -it --rm \ + --dns 8.8.8.8 \ + -v "$(pwd)":/docs \ + docs-builder:local \ + bundle exec jekyll build +``` + +### Native gem compilation issues + +The image uses `ruby:3.4-slim` which includes build tools for native extensions. If you encounter issues: + +1. Ensure you're using the multi-arch image matching your platform +2. Try pulling a fresh image: `docker pull ${REGISTRY}/docs-builder:latest` + +### Bundle path issues + +The image pre-installs gems to `/usr/local/bundle`. When mounting your local directory, ensure you don't have a conflicting local `vendor/bundle` directory. + +To work around this, you can either: +- Clear your local vendor directory: `rm -rf vendor` +- Or use a different bundle path in the container diff --git a/src/current/ci/cloudbuild.yaml b/src/current/ci/cloudbuild.yaml new file mode 100644 index 00000000000..8745fdbf1d3 --- /dev/null +++ b/src/current/ci/cloudbuild.yaml @@ -0,0 +1,75 @@ +# Cloud Build configuration for multi-arch docs-builder image +# +# This builds the documentation Docker image for both AMD64 and ARM64 +# architectures and pushes to GCP Artifact Registry. +# +# Usage: +# gcloud builds submit --config ci/cloudbuild.yaml . +# +# Or trigger manually: +# gcloud builds submit --config ci/cloudbuild.yaml --substitutions=_IMAGE_TAG=2025-01-15 . + +substitutions: + _REGISTRY: us-docker.pkg.dev/release-notes-automation-stag/docs-builder + _IMAGE_NAME: docs-builder + # Default tag is date-based + _IMAGE_TAG: ${_DATE} + +options: + # Use a larger machine for faster builds + machineType: 'E2_HIGHCPU_8' + # Enable Kaniko caching for faster builds + logging: CLOUD_LOGGING_ONLY + +steps: + # Step 1: Set up Docker buildx for multi-architecture builds + - name: 'gcr.io/cloud-builders/docker' + id: 'setup-buildx' + entrypoint: 'bash' + args: + - '-c' + - | + docker buildx create --name multiarch --driver docker-container --use + docker buildx inspect --bootstrap + + # Step 2: Build and push multi-arch image with date tag + - name: 'gcr.io/cloud-builders/docker' + id: 'build-push-dated' + entrypoint: 'bash' + args: + - '-c' + - | + DATE_TAG=$(date +%Y-%m-%d) + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --tag ${_REGISTRY}/${_IMAGE_NAME}:$${DATE_TAG} \ + --tag ${_REGISTRY}/${_IMAGE_NAME}:latest \ + --tag ${_REGISTRY}/${_IMAGE_NAME}:ruby3.4-bundler4.0 \ + --push \ + --cache-from type=registry,ref=${_REGISTRY}/${_IMAGE_NAME}:cache \ + --cache-to type=registry,ref=${_REGISTRY}/${_IMAGE_NAME}:cache,mode=max \ + . + waitFor: ['setup-buildx'] + + # Step 3: Verify the image was pushed successfully + - name: 'gcr.io/cloud-builders/docker' + id: 'verify' + entrypoint: 'bash' + args: + - '-c' + - | + echo "Verifying image availability..." + docker manifest inspect ${_REGISTRY}/${_IMAGE_NAME}:latest + echo "Image published successfully!" + echo "" + echo "Available tags:" + echo " - ${_REGISTRY}/${_IMAGE_NAME}:latest" + echo " - ${_REGISTRY}/${_IMAGE_NAME}:$(date +%Y-%m-%d)" + echo " - ${_REGISTRY}/${_IMAGE_NAME}:ruby3.4-bundler4.0" + waitFor: ['build-push-dated'] + +# Images to be available after build +images: + - '${_REGISTRY}/${_IMAGE_NAME}:latest' + +timeout: '1800s' # 30 minutes max