diff --git a/.castor/init.php b/.castor/init.php index c34a8e9..0bef743 100644 --- a/.castor/init.php +++ b/.castor/init.php @@ -2,6 +2,7 @@ use Castor\Attribute\AsTask; +use function Castor\finder; use function Castor\fs; use function Castor\io; use function Castor\variable; @@ -11,12 +12,15 @@ #[AsTask(description: 'Initialize the project')] function init(): void { - // The CI of docker-starter itself goes away, the production images workflow stays - $buildPushWorkflow = '.github/workflows/build-push.yml'; - $buildPushWorkflowContent = file_get_contents($buildPushWorkflow); + // The CI is kept for the project, without what only tests docker-starter itself + foreach (finder()->files()->in('.github')->name('*.yml') as $file) { + $content = preg_replace('{^[ \t]*# >>> docker-starter only.*?^[ \t]*# <<< docker-starter only\n}ms', '', $file->getContents()); + // The PHP versions matrix is gone + $content = str_replace(' with PHP ${{ matrix.php-version }}', '', (string) $content); + fs()->dumpFile($file->getPathname(), rtrim($content) . "\n"); + } fs()->remove([ - '.github/', '.castor/docker-push-test.php', 'README.md', 'CHANGELOG.md', @@ -26,12 +30,6 @@ function init(): void ]); fs()->rename('README.dist.md', 'README.md'); - if (false !== $buildPushWorkflowContent) { - // Drop the "disabled" notice (first paragraph), then uncomment the workflow - $buildPushWorkflowContent = explode("\n\n", $buildPushWorkflowContent, 2)[1] ?? ''; - fs()->dumpFile($buildPushWorkflow, (string) preg_replace('{^# ?}m', '', $buildPushWorkflowContent)); - } - $readMeContent = file_get_contents('README.md'); if (false === $readMeContent) { diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 36a328b..8c3872f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,39 @@ updates: - '*' cooldown: default-days: 7 + + - package-ecosystem: 'composer' + directories: + # Add '/application' once the application has a composer.json + - '/tools/php-cs-fixer' + - '/tools/phpstan' + - '/tools/twig-cs-fixer' + schedule: + interval: 'monthly' + groups: + composer: + patterns: + - '*' + composer-security: + applies-to: security-updates + patterns: + - '*' + cooldown: + default-days: 7 + + - package-ecosystem: 'docker' + directories: + - '/infrastructure/docker/services/php' + - '/infrastructure/docker/services/router' + schedule: + interval: 'monthly' + groups: + docker: + patterns: + - '*' + docker-security: + applies-to: security-updates + patterns: + - '*' + cooldown: + default-days: 7 diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 75460cf..e692fac 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -1,48 +1,48 @@ -# Disabled in docker-starter itself: there is nothing to deploy. `castor init` -# enables it in your project, by removing this paragraph and uncommenting the rest. - -# name: Build and push production images -# -# "on": -# push: -# branches: ["main"] -# tags: ["*"] -# -# permissions: -# contents: read -# packages: write -# -# env: -# # Fix for symfony/color detection. We know GitHub Actions can handle it -# ANSICON: 1 -# CASTOR_CONTEXT: prod -# REGISTRY: "ghcr.io/${{ github.repository }}" -# -# jobs: -# build-push: -# name: Build and push production images -# runs-on: ubuntu-latest -# steps: -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 -# -# - name: Log in to registry -# shell: bash -# run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin -# -# - name: setup-castor -# uses: castor-php/setup-castor@2a495b8c91f00be6768ad8a040ba8634c797d386 # v1.1.0 -# -# - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 -# with: -# persist-credentials: false -# -# # Images are tagged with the short commit sha (what a deployment should -# # reference), "latest" on main, and the git tag when there is one -# - name: "Build and push the production images" -# shell: bash -# run: | -# tags=(--tag="${GITHUB_SHA::7}") -# if [ "${GITHUB_REF_NAME}" = main ]; then tags+=(--tag=latest); fi -# if [ "${GITHUB_REF_TYPE}" = tag ]; then tags+=(--tag="${GITHUB_REF_NAME}"); fi -# castor docker:push "${tags[@]}" +name: Build and push production images + +"on": + push: + branches: ["main"] + tags: ["*"] + +permissions: + contents: read + packages: write + +env: + # Fix for symfony/color detection. We know GitHub Actions can handle it + ANSICON: 1 + CASTOR_CONTEXT: prod + REGISTRY: "ghcr.io/${{ github.repository }}" + +jobs: + build-push: + name: Build and push production images + # Set the PUSH_PRODUCTION_IMAGES repository variable to "true" + # (Settings > Secrets and variables > Actions > Variables) to enable it + if: vars.PUSH_PRODUCTION_IMAGES == 'true' + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Log in to registry + shell: bash + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: setup-castor + uses: castor-php/setup-castor@2a495b8c91f00be6768ad8a040ba8634c797d386 # v1.1.0 + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # Images are tagged with the short commit sha (what a deployment should + # reference), "latest" on main, and the git tag when there is one + - name: "Build and push the production images" + shell: bash + run: | + tags=(--tag="${GITHUB_SHA::7}") + if [ "${GITHUB_REF_NAME}" = main ]; then tags+=(--tag=latest); fi + if [ "${GITHUB_REF_TYPE}" = tag ]; then tags+=(--tag="${GITHUB_REF_NAME}"); fi + castor docker:push "${tags[@]}" diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index 8ea04b3..1a96394 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -1,37 +1,35 @@ name: Push docker image to registry "on": - push: - # Only run this job when pushing to the main branch - branches: ["main"] + push: + # Only run this job when pushing to the main branch + branches: ["main"] permissions: - contents: read - packages: write + contents: read + packages: write env: - REGISTRY: "ghcr.io/jolicode/docker-starter" - DS_PHP_VERSION: "8.5" + REGISTRY: "ghcr.io/${{ github.repository }}" jobs: - push-images: - name: Push image to registry - runs-on: ubuntu-latest - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - - name: Log in to registry - shell: bash - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: setup-castor - uses: castor-php/setup-castor@2a495b8c91f00be6768ad8a040ba8634c797d386 # v1.1.0 - - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - - name: "Build and start the infrastructure" - run: "castor docker:push" + push-images: + name: Push image to registry + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Log in to registry + shell: bash + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: setup-castor + uses: castor-php/setup-castor@2a495b8c91f00be6768ad8a040ba8634c797d386 # v1.1.0 + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: "Push the images cache to the registry" + run: "castor docker:push" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de5cdf1..9676237 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,12 @@ env: # Fix for symfony/color detection. We know GitHub Actions can handle it ANSICON: 1 CASTOR_CONTEXT: ci - REGISTRY: "ghcr.io/jolicode/docker-starter" + REGISTRY: "ghcr.io/${{ github.repository }}" +# >>> docker-starter only +# The "docker-starter only" blocks test docker-starter itself: `castor init` +# removes them, the rest is the CI of your project. +# <<< docker-starter only jobs: check-dockerfiles: name: Check Dockerfile @@ -60,27 +64,33 @@ jobs: - name: "Build and start the production images" run: "castor start" + # Add some checks of your application, e.g. the status code of the homepage - name: "Test HTTP server" run: | set -e set -o pipefail + # >>> docker-starter only curl --fail --silent http://127.0.0.1:8000 | grep "Hello world" curl --fail --silent http://127.0.0.1:8000 | grep "Environment: prod" + # <<< docker-starter only test "$(curl --silent -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/index.php)" = 404 - name: "Test the php image runs as a non-root user" run: "castor builder -- id -u | grep -x 1000" ci: - name: Test with PHP ${{ matrix.php-version }} + # >>> docker-starter only: every supported PHP version is tested (`castor + # init` also removes the PHP version from the job name) strategy: fail-fast: false matrix: php-version: ["8.3", "8.4", "8.5"] - runs-on: ubuntu-latest env: DS_PHP_VERSION: ${{ matrix.php-version }} + # <<< docker-starter only + name: Test with PHP ${{ matrix.php-version }} + runs-on: ubuntu-latest steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 @@ -114,6 +124,7 @@ jobs: - name: "Run PHPUnit" run: "castor qa:phpunit" + # >>> docker-starter only - name: "Test docker:push" run: "castor docker:test-push" @@ -186,3 +197,4 @@ jobs: sleep 3 curl --fail --insecure --silent -H "Host: app.test" https://127.0.0.1 | grep "database OK" + # <<< docker-starter only diff --git a/CHANGELOG.md b/CHANGELOG.md index 8887862..9bd7b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Migrate from Invoke to Castor * Add `castor symfony` to install a Symfony application * Add `castor init` command to initialize a new project + * `castor init` keeps the CI (GitHub Actions) for the project, without what only tests docker-starter * Add a `test` context * Add a `prod` context to build, run and push production images * `castor docker:push --tag=...` also pushes the images @@ -31,6 +32,7 @@ * Add support for caching image cache in a registry * Build (and push the cache of) each service separately, in parallel, so services sharing a stage all reuse their cache * Add production images (`php` and `nginx`): code baked in, non-root, php-fpm on a unix socket + * Push the production images from the CI only when the `PUSH_PRODUCTION_IMAGES` repository variable is `true` * Share php-fpm and nginx configuration between the dev `frontend` container and the production images * The `frontend` container now listens on port 8080 and php-fpm on a unix socket * Keep the port of the `Host` header in `HTTP_HOST`, dropped by Debian's nginx (absolute URLs on a git worktree stack) diff --git a/README.dist.md b/README.dist.md index e0c31a3..4b7c4cc 100644 --- a/README.dist.md +++ b/README.dist.md @@ -110,7 +110,9 @@ castor builder The application ships as two images, `php` and `nginx`, built from the "Production stages" of `infrastructure/docker/services/php/Dockerfile` and -pushed to the registry by `.github/workflows/build-push.yml`. To test them +pushed to the registry by `.github/workflows/build-push.yml`, when the +`PUSH_PRODUCTION_IMAGES` repository variable is `true` (Settings > Secrets and +variables > Actions > Variables, disabled by default). To test them locally, on a stack independent from the development one: ```bash @@ -118,6 +120,16 @@ castor start -c prod # -> http://127.0.0.1:8000 castor destroy -c prod ``` +### Continuous integration + +The GitHub Actions workflows live in `.github/workflows/`: + + * `ci.yml` checks the Dockerfile, runs the QA tools and the tests, and checks + the production images: add there some checks of your application + * `cache.yml` pushes the Docker build cache to the registry, on `main` + * `build-push.yml` builds and pushes the production images, on `main` and on + tags, when the `PUSH_PRODUCTION_IMAGES` repository variable is `true` + ### Other tasks Checkout `castor` to have the list of available tasks. diff --git a/README.md b/README.md index 8b92e07..d0ee8f9 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,10 @@ castor init > [!NOTE] > This command can be run only once +The GitHub Actions workflows of `.github/` are kept as the CI of your project: +`castor init` only removes their "docker-starter only" blocks, which test +docker-starter itself (PHP versions matrix, demo application...). + Also, in order to improve your usage of castor scripts, you can install console autocompletion script. @@ -1125,11 +1129,12 @@ container). `castor docker:push --tag=...` also pushes the images themselves (not only their build cache). On every push to `main` (and on every git tag), the -`.github/workflows/build-push.yml` workflow (commented in docker-starter, -enabled by `castor init`) pushes both images to +`.github/workflows/build-push.yml` workflow pushes both images to `ghcr.io//php` and `ghcr.io//nginx`, tagged with the -short commit sha, `latest` on `main`, and the tag name when there is one. To -push from your machine (you need to be logged in to the registry, and a buildx +short commit sha, `latest` on `main`, and the tag name when there is one. This +workflow is disabled by default (and in docker-starter itself): set the +`PUSH_PRODUCTION_IMAGES` repository variable to `true` to enable it (Settings > +Secrets and variables > Actions > Variables). To push from your machine (you need to be logged in to the registry, and a buildx builder able to export a registry cache, e.g. `docker buildx create --use`): ```bash