From e86b4219c199566312fb3d218b3e3e1c3a36b15e Mon Sep 17 00:00:00 2001 From: Guillaume KESTEMAN Date: Fri, 12 Aug 2022 11:47:29 +0200 Subject: [PATCH] Change docker config, take same as api-platform --- .github/FUNDING.yml | 1 - .github/{workflows => }/ci.yml | 2 +- Makefile | 101 ++++++----- admin/.dockerignore | 21 +++ admin/Dockerfile | 42 +++++ admin/src/config/components/entrypoint.ts | 2 +- api/.dockerignore | 18 +- api/.env | 10 +- api/.env.test | 3 + api/Dockerfile | 107 ++++++----- api/config/packages/security.yaml | 8 +- api/config/routes.yaml | 2 +- api/config/routes/api_platform.yaml | 1 - api/docker/caddy/Caddyfile | 24 ++- api/docker/php/conf.d/app.dev.ini | 5 + .../php/conf.d/{symfony.dev.ini => app.ini} | 14 +- api/docker/php/conf.d/app.prod.ini | 2 + api/docker/php/conf.d/symfony.prod.ini | 15 -- api/docker/php/docker-entrypoint.sh | 24 ++- api/src/State/GamePutProcessor.php | 2 +- api/tests/Api/GameTest.php | 18 +- api/tests/Api/LotTest.php | 22 +-- api/tests/Api/MediaObjectTest.php | 16 +- api/tests/Api/PlayerTest.php | 18 +- api/tests/Api/RewardTest.php | 18 +- api/tests/Api/TweetReplyTest.php | 14 +- api/tests/Api/TweetTest.php | 6 +- api/tests/Api/TwitterAccountToFollowTest.php | 18 +- api/tests/Api/TwitterHashtagTest.php | 22 +-- api/tests/Security/LoginTest.php | 6 +- docker-compose.override.yml | 44 +++-- docker-compose.prod.yml | 5 +- docker-compose.yml | 64 ++++--- helm/api-platform/.helmignore | 23 +++ helm/api-platform/Chart.lock | 6 + helm/api-platform/Chart.yaml | 31 ++++ helm/api-platform/README.md | 6 + helm/api-platform/templates/NOTES.txt | 22 +++ helm/api-platform/templates/_helpers.tpl | 84 +++++++++ helm/api-platform/templates/configmap.yaml | 14 ++ helm/api-platform/templates/deployment.yaml | 168 ++++++++++++++++++ helm/api-platform/templates/hpa.yaml | 28 +++ helm/api-platform/templates/ingress.yaml | 61 +++++++ .../templates/pwa-deployment.yaml | 64 +++++++ helm/api-platform/templates/pwa-service.yaml | 14 ++ helm/api-platform/templates/secrets.yaml | 15 ++ helm/api-platform/templates/service.yaml | 15 ++ .../templates/serviceaccount.yaml | 12 ++ .../templates/tests/test-connection.yaml | 15 ++ helm/api-platform/values.yaml | 129 ++++++++++++++ 50 files changed, 1109 insertions(+), 273 deletions(-) delete mode 100644 .github/FUNDING.yml rename .github/{workflows => }/ci.yml (93%) create mode 100644 admin/.dockerignore create mode 100644 admin/Dockerfile create mode 100644 api/docker/php/conf.d/app.dev.ini rename api/docker/php/conf.d/{symfony.dev.ini => app.ini} (57%) create mode 100644 api/docker/php/conf.d/app.prod.ini delete mode 100644 api/docker/php/conf.d/symfony.prod.ini create mode 100644 helm/api-platform/.helmignore create mode 100644 helm/api-platform/Chart.lock create mode 100644 helm/api-platform/Chart.yaml create mode 100644 helm/api-platform/README.md create mode 100644 helm/api-platform/templates/NOTES.txt create mode 100644 helm/api-platform/templates/_helpers.tpl create mode 100644 helm/api-platform/templates/configmap.yaml create mode 100644 helm/api-platform/templates/deployment.yaml create mode 100644 helm/api-platform/templates/hpa.yaml create mode 100644 helm/api-platform/templates/ingress.yaml create mode 100644 helm/api-platform/templates/pwa-deployment.yaml create mode 100644 helm/api-platform/templates/pwa-service.yaml create mode 100644 helm/api-platform/templates/secrets.yaml create mode 100644 helm/api-platform/templates/service.yaml create mode 100644 helm/api-platform/templates/serviceaccount.yaml create mode 100644 helm/api-platform/templates/tests/test-connection.yaml create mode 100644 helm/api-platform/values.yaml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 37b0b51..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [dunglas] diff --git a/.github/workflows/ci.yml b/.github/ci.yml similarity index 93% rename from .github/workflows/ci.yml rename to .github/ci.yml index 1daa3af..6226e96 100644 --- a/.github/workflows/ci.yml +++ b/.github/ci.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Pull images - run: docker-compose pull + run: docker-compose pull --ignore-pull-failures || true - name: Start services run: docker-compose up --build -d - name: Wait for services diff --git a/Makefile b/Makefile index 1a7e480..197a82b 100644 --- a/Makefile +++ b/Makefile @@ -14,86 +14,99 @@ generateTestsDB = docker-compose exec php bin/console --env=test doctrine:databa docker-compose exec php bin/console --env=test doctrine:migrations:migrate --no-interaction; ### test database ### +help: + @echo "MAKEFILE HELP :" + @echo "\t - help: list all makefile commands" + @echo "\t - start: start docker's containers, execute database migrations, execute fixtures, show docker's logs and stop docker containers on terminate signal" + @echo "\t - start-all: start docker's containers, execute database migrations, execute fixtures and show docker's logs" + @echo "\t - stop: stop docker containers" + @echo "\t - install: make a docker build" + @echo "\t - kill-docker-builds: stop, kill and down docker's containers (remove JWT keys)" + @echo "\t - new-db: make a new database with the new migrations" + @echo "\t - jwt-keypair: regenerate JWT keys" + @echo "\t - tests: launch all tests" + @echo "\t - tests-security: launch security tests" + @echo "\t - tests-api: launch api tests" + @echo "\t - fixtures: launch fixtures" + @echo "\t - fixtures-test: launch fixtures for tests" start: - bash -c "trap 'trap - SIGINT SIGTERM ERR; $(MAKE) stop-all; exit 1' SIGINT SIGTERM ERR; $(MAKE) start-all" + @bash -c "trap 'trap - SIGINT SIGTERM ERR; $(MAKE) stop-all; exit 1' SIGINT SIGTERM ERR; $(MAKE) start-all" start-all: - docker-compose up -d - docker-compose exec php bin/console doctrine:migrations:migrate --no-interaction - docker-compose exec php bin/console hautelook:fixtures:load --no-interaction - sleep 5 - cd admin/ && yarn start + @docker-compose up -d + @docker-compose exec php bin/console doctrine:migrations:migrate --no-interaction + @docker-compose exec php bin/console hautelook:fixtures:load --no-interaction + @docker-compose logs -f stop-all: - docker-compose stop + @docker-compose stop install: - docker-compose build --pull --no-cache - cp -R -n api/.env api/.env.local - cd admin/ && yarn install + @docker-compose build --pull --no-cache + @cp -R -n api/.env api/.env.local kill-docker-builds: - docker-compose stop - docker-compose kill - docker-compose down --volumes --remove-orphans - rmdir api/config/jwt + @docker-compose stop + @docker-compose kill + @docker-compose down --volumes --remove-orphans + @rmdir api/config/jwt new-db: ifeq ($(shell docker-compose ps | wc -l),2) - docker-compose up -d - $(generateDB) - docker-compose stop + @docker-compose up -d + @$(generateDB) + @docker-compose stop else - $(generateDB) + @$(generateDB) endif jwt-keypair: ifeq ($(shell docker-compose ps | wc -l),2) - docker-compose up -d - $(generateJWT) - docker-compose stop + @docker-compose up -d + @$(generateJWT) + @docker-compose stop else - $(generateJWT) + @$(generateJWT) endif tests: ifeq ($(shell docker-compose ps | wc -l),2) - docker-compose up -d - bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; $(MAKE) tests-security" - bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; $(MAKE) tests-api" - docker-compose stop + @docker-compose up -d + @bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; $(MAKE) tests-security" + @bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; $(MAKE) tests-api" + @docker-compose stop else - $(MAKE) tests-security - $(MAKE) tests-api + @$(MAKE) tests-security + @$(MAKE) tests-api endif tests-security: ifeq ($(shell docker-compose ps | wc -l),2) - docker-compose up -d - bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; docker-compose exec php bin/phpunit tests/Security" - docker-compose stop + @docker-compose up -d + @bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; docker-compose exec php bin/phpunit tests/Security" + @docker-compose stop else - docker-compose exec php bin/phpunit tests/Security + @docker-compose exec php bin/phpunit tests/Security endif tests-api: ifeq ($(shell docker-compose ps | wc -l),2) - docker-compose up -d - $(generateTestsDB) - docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction - bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; docker-compose exec php bin/phpunit tests/Api" - docker-compose stop + @docker-compose up -d + @$(generateTestsDB) + @docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction + @bash -c "trap 'trap - SIGINT SIGTERM ERR; docker-compose stop; exit 1' SIGINT SIGTERM ERR; docker-compose exec php bin/phpunit tests/Api" + @docker-compose stop else - $(generateTestsDB) - docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction - docker-compose exec php bin/phpunit tests/Api + @$(generateTestsDB) + @docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction + @docker-compose exec php bin/phpunit tests/Api endif fixtures: - docker-compose exec php bin/console doctrine:migrations:migrate --no-interaction - docker-compose exec php bin/console hautelook:fixtures:load --no-interaction --purge-with-truncate + @docker-compose exec php bin/console doctrine:migrations:migrate --no-interaction + @docker-compose exec php bin/console hautelook:fixtures:load --no-interaction --purge-with-truncate fixtures-test: - docker-compose exec php bin/console --env=test doctrine:migrations:migrate --no-interaction - docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction --purge-with-truncate + @docker-compose exec php bin/console --env=test doctrine:migrations:migrate --no-interaction + @docker-compose exec php bin/console --env=test hautelook:fixtures:load --no-interaction --purge-with-truncate diff --git a/admin/.dockerignore b/admin/.dockerignore new file mode 100644 index 0000000..f02335f --- /dev/null +++ b/admin/.dockerignore @@ -0,0 +1,21 @@ +**/*.log +**/*.md +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.next/ +build/ +node_modules/ +.editorconfig +.env.*.local +.env.local diff --git a/admin/Dockerfile b/admin/Dockerfile new file mode 100644 index 0000000..0bc6f8e --- /dev/null +++ b/admin/Dockerfile @@ -0,0 +1,42 @@ +# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage +# https://docs.docker.com/compose/compose-file/#target + +# https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact +ARG NODE_VERSION=16 + +# "common" stage +FROM node:${NODE_VERSION}-alpine AS app_admin_common + +EXPOSE 3000 + +WORKDIR /usr/src/admin + +ENV NEXT_TELEMETRY_DISABLED 1 + +# prevent the reinstallation of node modules at every changes in the source code +COPY package.json yarn.lock ./ +RUN yarn + +COPY . . + +VOLUME /usr/src/admin/node_modules + +# "development" stage +# depends on the "common" stage above +FROM app_admin_common AS app_admin_dev + +VOLUME /usr/src/admin/build + +CMD ["yarn", "start"] + +# "build" stage +# depends on the "common" stage above +FROM app_admin_common AS app_admin_prod + +ENV NODE_ENV production +ARG NEXT_PUBLIC_ENTRYPOINT + +RUN set -eux; \ + yarn build + +CMD ["yarn", "start"] diff --git a/admin/src/config/components/entrypoint.ts b/admin/src/config/components/entrypoint.ts index f6d55f0..8a15cb1 100644 --- a/admin/src/config/components/entrypoint.ts +++ b/admin/src/config/components/entrypoint.ts @@ -1,2 +1,2 @@ -export const API_ENTRYPOINT = 'https://localhost/api'; +export const API_ENTRYPOINT = 'https://localhost'; export const ENTRYPOINT = 'https://localhost'; diff --git a/api/.dockerignore b/api/.dockerignore index eed80d3..0a2a9c9 100644 --- a/api/.dockerignore +++ b/api/.dockerignore @@ -1,6 +1,9 @@ **/*.log **/*.md **/*.php~ +**/*.dist.php +**/*.dist +**/*.cache **/._* **/.dockerignore **/.DS_Store @@ -14,15 +17,14 @@ **/docker-compose.yml **/Dockerfile **/Thumbs.db +.github/ +docs/ +public/bundles/ +tests/ +var/ +vendor/ .editorconfig .env.*.local .env.local .env.local.php -.php_cs.cache -bin/* -!bin/console -docker/db/data/ -helm/ -public/bundles/ -var/ -vendor/ +.env.test.local diff --git a/api/.env b/api/.env index 9f2cee7..07f791f 100644 --- a/api/.env +++ b/api/.env @@ -14,9 +14,13 @@ # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration +# API Platform distribution +TRUSTED_PROXIES=127.0.0.1 +TRUSTED_HOSTS=^localhost$ + ###> symfony/framework-bundle ### APP_ENV=dev -APP_SECRET=f7b55e955f22606e526aa47daba087e6 +APP_SECRET=!ChangeMe! ###< symfony/framework-bundle ### ###> doctrine/doctrine-bundle ### @@ -25,7 +29,7 @@ APP_SECRET=f7b55e955f22606e526aa47daba087e6 # # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7&charset=utf8mb4" -DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8" +DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/api?serverVersion=14&charset=utf8" ###< doctrine/doctrine-bundle ### ###> lexik/jwt-authentication-bundle ### @@ -82,7 +86,7 @@ USER_IN_MEMORY_HASHED_PASSWORD='USER_IN_MEMORY_HASHED_PASSWORD' # The URL of the Mercure hub, used by the app to publish updates (can be a local URL) MERCURE_URL=https://example.com/.well-known/mercure # The public URL of the Mercure hub, used by the browser to connect -MERCURE_PUBLIC_URL=https://example.com/.well-known/mercure +MERCURE_PUBLIC_URL=https://localhost/.well-known/mercure # The secret used to sign the JWTs MERCURE_JWT_SECRET="!ChangeMe!" ###< symfony/mercure-bundle ### diff --git a/api/.env.test b/api/.env.test index 545f26e..9db7d36 100644 --- a/api/.env.test +++ b/api/.env.test @@ -6,6 +6,9 @@ SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots +# API Platform distribution +TRUSTED_HOSTS=^example\.com|localhost$ + ###< secrity-user_in_memory ### USER_IN_MEMORY_USERNAME=admin USER_IN_MEMORY_PASSWORD=admin diff --git a/api/Dockerfile b/api/Dockerfile index b57dc0a..6e04f92 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -7,8 +7,12 @@ ARG PHP_VERSION=8.1 ARG CADDY_VERSION=2 -# "php" stage -FROM php:${PHP_VERSION}-fpm-alpine AS symfony_php +# Prod image +FROM php:${PHP_VERSION}-fpm-alpine AS app_php + +ENV APP_ENV=prod + +WORKDIR /srv/app # persistent / runtime deps RUN apk add --no-cache \ @@ -19,7 +23,6 @@ RUN apk add --no-cache \ git \ ; -ARG APCU_VERSION=5.1.21 RUN set -eux; \ apk add --no-cache --virtual .build-deps \ $PHPIZE_DEPS \ @@ -35,7 +38,7 @@ RUN set -eux; \ zip \ ; \ pecl install \ - apcu-${APCU_VERSION} \ + apcu \ ; \ pecl clear-cache; \ docker-php-ext-enable \ @@ -49,72 +52,86 @@ RUN set -eux; \ | sort -u \ | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ )"; \ - apk add --no-cache --virtual .phpexts-rundeps $runDeps; \ + apk add --no-cache --virtual .app-phpexts-rundeps $runDeps; \ \ apk del .build-deps +###> recipes ### +###> doctrine/doctrine-bundle ### +RUN apk add --no-cache --virtual .pgsql-deps postgresql-dev; \ + docker-php-ext-install -j$(nproc) pdo_pgsql; \ + apk add --no-cache --virtual .pgsql-rundeps so:libpq.so.5; \ + apk del .pgsql-deps +###< doctrine/doctrine-bundle ### +###< recipes ### + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +COPY docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/ +COPY docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/ + +COPY docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf +RUN mkdir -p /var/run/php + COPY docker/php/docker-healthcheck.sh /usr/local/bin/docker-healthcheck RUN chmod +x /usr/local/bin/docker-healthcheck HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD ["docker-healthcheck"] -RUN ln -s $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini -COPY docker/php/conf.d/symfony.prod.ini $PHP_INI_DIR/conf.d/symfony.ini - -COPY docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf - COPY docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint RUN chmod +x /usr/local/bin/docker-entrypoint -VOLUME /var/run/php - -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +ENTRYPOINT ["docker-entrypoint"] +CMD ["php-fpm"] # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser ENV COMPOSER_ALLOW_SUPERUSER=1 - ENV PATH="${PATH}:/root/.composer/vendor/bin" -WORKDIR /srv/api +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -# Allow to choose skeleton -ARG SKELETON="symfony/skeleton" -ENV SKELETON ${SKELETON} - -# Allow to use development versions of Symfony -ARG STABILITY="stable" -ENV STABILITY ${STABILITY} - -# Allow to select skeleton version -ARG SYMFONY_VERSION="" -ENV SYMFONY_VERSION ${SYMFONY_VERSION} - -###> recipes ### -###> doctrine/doctrine-bundle ### -RUN apk add --no-cache --virtual .pgsql-deps postgresql-dev; \ - docker-php-ext-install -j$(nproc) pdo_pgsql; \ - apk add --no-cache --virtual .pgsql-rundeps so:libpq.so.5; \ - apk del .pgsql-deps -###< doctrine/doctrine-bundle ### -###< recipes ### +# prevent the reinstallation of vendors at every changes in the source code +COPY composer.* symfony.* ./ +RUN set -eux; \ + composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress; \ + composer clear-cache +# copy sources COPY . . +RUN rm -Rf docker/ RUN set -eux; \ mkdir -p var/cache var/log public/media; \ - composer install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction; \ composer dump-autoload --classmap-authoritative --no-dev; \ - composer symfony:dump-env prod; \ + composer dump-env prod; \ composer run-script --no-dev post-install-cmd; \ chmod +x bin/console; sync + VOLUME /srv/api/var VOLUME /srv/api/config/jwt VOLUME /srv/api/public/media -ENTRYPOINT ["docker-entrypoint"] -CMD ["php-fpm"] +# Dev image +FROM app_php AS app_php_dev + +ENV APP_ENV=dev XDEBUG_MODE=off +VOLUME /srv/app/var/ + +RUN rm $PHP_INI_DIR/conf.d/app.prod.ini; \ + mv "$PHP_INI_DIR/php.ini" "$PHP_INI_DIR/php.ini-production"; \ + mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" + +COPY docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/ + +RUN set -eux; \ + apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \ + pecl install xdebug; \ + docker-php-ext-enable xdebug; \ + apk del .build-deps + +RUN rm -f .env.local.php -FROM caddy:${CADDY_VERSION}-builder-alpine AS symfony_caddy_builder +# Build Caddy with the Mercure and Vulcain modules +FROM caddy:${CADDY_VERSION}-builder-alpine AS app_caddy_builder RUN xcaddy build \ --with github.com/dunglas/mercure \ @@ -122,11 +139,11 @@ RUN xcaddy build \ --with github.com/dunglas/vulcain \ --with github.com/dunglas/vulcain/caddy -FROM caddy:${CADDY_VERSION} AS symfony_caddy +# Caddy image +FROM caddy:${CADDY_VERSION} AS app_caddy -WORKDIR /srv/api +WORKDIR /srv/app -COPY --from=dunglas/mercure:v0.11 /srv/public /srv/mercure-assets/ -COPY --from=symfony_caddy_builder /usr/bin/caddy /usr/bin/caddy -COPY --from=symfony_php /srv/api/public public/ +COPY --from=app_caddy_builder /usr/bin/caddy /usr/bin/caddy +COPY --from=app_php /srv/app/public public/ COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile diff --git a/api/config/packages/security.yaml b/api/config/packages/security.yaml index a245219..838a9f7 100644 --- a/api/config/packages/security.yaml +++ b/api/config/packages/security.yaml @@ -15,10 +15,10 @@ security: security: false api: stateless: true - pattern: ^/api + pattern: ^/ provider: app_user_provider json_login: - check_path: /api/login + check_path: /login success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure jwt: ~ @@ -38,8 +38,8 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - - { path: ^/api/login, roles: PUBLIC_ACCESS } - - { path: ^/api, roles: PUBLIC_ACCESS } + - { path: ^/login, roles: PUBLIC_ACCESS } + - { path: ^/docs, roles: PUBLIC_ACCESS } - { path: ^/image, roles: PUBLIC_ACCESS } - { path: ^/, roles: IS_AUTHENTICATED_FULLY } diff --git a/api/config/routes.yaml b/api/config/routes.yaml index 6a42286..b9208dd 100644 --- a/api/config/routes.yaml +++ b/api/config/routes.yaml @@ -3,5 +3,5 @@ controllers: type: attribute api_login_check: - path: /api/login + path: /login methods: ['POST'] diff --git a/api/config/routes/api_platform.yaml b/api/config/routes/api_platform.yaml index 38f11cb..350d2a8 100644 --- a/api/config/routes/api_platform.yaml +++ b/api/config/routes/api_platform.yaml @@ -1,4 +1,3 @@ api_platform: resource: . type: api_platform - prefix: /api diff --git a/api/docker/caddy/Caddyfile b/api/docker/caddy/Caddyfile index 44af95a..dde3b94 100644 --- a/api/docker/caddy/Caddyfile +++ b/api/docker/caddy/Caddyfile @@ -13,8 +13,20 @@ log +# Matches requests for HTML documents, for static files and for Next.js files, +# except for known API paths and paths with extensions handled by API Platform +@pwa expression `( + {header.Accept}.matches("\\btext/html\\b") + && !{path}.matches("(?i)(?:^/docs|^/graphql|^/bundles|^/contexts/|^/_profiler|^/_wdt|\\.(?:json|html$|csv$|ya?ml$|xml$))") + ) + || {path} == "/favicon.ico" + || {path} == "/manifest.json" + || {path} == "/robots.txt" + || {path}.startsWith("/static") + || {path}.startsWith("/sitemap")` + route { - root * /srv/api/public + root * /srv/app/public mercure { # Transport to use (default to Bolt) transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} @@ -31,6 +43,16 @@ route { } vulcain push + + # Add links to the API docs and to the Mercure Hub if not set explicitly (e.g. the PWA) + header ?Link `; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", ; rel="mercure"` + # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics + header ?Permissions-Policy "browsing-topics=()" + + # Comment the following line if you don't want Next.js to catch requests for HTML documents. + # In this case, they will be handled by the PHP app. + reverse_proxy @pwa http://{$PWA_UPSTREAM} + php_fastcgi unix//var/run/php/php-fpm.sock encode zstd gzip file_server diff --git a/api/docker/php/conf.d/app.dev.ini b/api/docker/php/conf.d/app.dev.ini new file mode 100644 index 0000000..c1b6381 --- /dev/null +++ b/api/docker/php/conf.d/app.dev.ini @@ -0,0 +1,5 @@ +; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host +; See https://github.com/docker/for-linux/issues/264 +; The `client_host` below may optionally be replaced with `discover_client_host=yes` +; Add `start_with_request=yes` to start debug session on each request +xdebug.client_host = 'host.docker.internal' diff --git a/api/docker/php/conf.d/symfony.dev.ini b/api/docker/php/conf.d/app.ini similarity index 57% rename from api/docker/php/conf.d/symfony.dev.ini rename to api/docker/php/conf.d/app.ini index d5c2504..79a17dd 100644 --- a/api/docker/php/conf.d/symfony.dev.ini +++ b/api/docker/php/conf.d/app.ini @@ -1,11 +1,13 @@ -apc.enable_cli = 1 +expose_php = 0 date.timezone = UTC -session.auto_start = Off -short_open_tag = Off +apc.enable_cli = 1 +session.use_strict_mode = 1 +zend.detect_unicode = 0 -# http://symfony.com/doc/current/performance.html +; https://symfony.com/doc/current/performance.html +realpath_cache_size = 4096K +realpath_cache_ttl = 600 opcache.interned_strings_buffer = 16 opcache.max_accelerated_files = 20000 opcache.memory_consumption = 256 -realpath_cache_size = 4096K -realpath_cache_ttl = 600 +opcache.enable_file_override = 1 diff --git a/api/docker/php/conf.d/app.prod.ini b/api/docker/php/conf.d/app.prod.ini new file mode 100644 index 0000000..993d481 --- /dev/null +++ b/api/docker/php/conf.d/app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = www-data +opcache.preload = /srv/app/config/preload.php diff --git a/api/docker/php/conf.d/symfony.prod.ini b/api/docker/php/conf.d/symfony.prod.ini deleted file mode 100644 index 10f72ea..0000000 --- a/api/docker/php/conf.d/symfony.prod.ini +++ /dev/null @@ -1,15 +0,0 @@ -apc.enable_cli = 1 -date.timezone = UTC -session.auto_start = Off -short_open_tag = Off -expose_php = Off - -# https://symfony.com/doc/current/performance.html -opcache.interned_strings_buffer = 16 -opcache.max_accelerated_files = 20000 -opcache.memory_consumption = 256 -opcache.validate_timestamps = 0 -realpath_cache_size = 4096K -realpath_cache_ttl = 600 -opcache.preload_user = www-data -opcache.preload = /srv/api/config/preload.php diff --git a/api/docker/php/docker-entrypoint.sh b/api/docker/php/docker-entrypoint.sh index e7e3402..5bece75 100755 --- a/api/docker/php/docker-entrypoint.sh +++ b/api/docker/php/docker-entrypoint.sh @@ -7,16 +7,12 @@ if [ "${1#-}" != "$1" ]; then fi if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then - PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-production" - if [ "$APP_ENV" != 'prod' ]; then - PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-development" - fi - ln -sf "$PHP_INI_RECOMMENDED" "$PHP_INI_DIR/php.ini" + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var mkdir -p var/cache var/log public/media if [ "$APP_ENV" != 'prod' ]; then - rm -f .env.local.php composer install --prefer-dist --no-progress --no-interaction echo "Making sure public / private keys for JWT exist..." @@ -25,10 +21,10 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then setfacl -dR -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt fi - if grep -q ^DATABASE_URL= .env; then - echo "Waiting for db to be ready..." + if grep -q DATABASE_URL= .env; then + echo "Waiting for database to be ready..." ATTEMPTS_LEFT_TO_REACH_DATABASE=60 - until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(bin/console dbal:run-sql "SELECT 1" 2>&1); do + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do if [ $? -eq 255 ]; then # If the Doctrine command exits with 255, an unrecoverable error occurred ATTEMPTS_LEFT_TO_REACH_DATABASE=0 @@ -36,7 +32,7 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then fi sleep 1 ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) - echo "Still waiting for db to be ready... Or maybe the db is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left" + echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." done if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then @@ -44,18 +40,18 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then echo "$DATABASE_ERROR" exit 1 else - echo "The db is now ready and reachable" + echo "The database is now ready and reachable" fi if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then - bin/console doctrine:migrations:migrate --no-interaction + php bin/console doctrine:migrations:migrate --no-interaction fi fi setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var - setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var - setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX public/media + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX public/media setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX public/media fi diff --git a/api/src/State/GamePutProcessor.php b/api/src/State/GamePutProcessor.php index 169f09d..033d01f 100644 --- a/api/src/State/GamePutProcessor.php +++ b/api/src/State/GamePutProcessor.php @@ -29,7 +29,7 @@ public function __construct(private readonly PersistProcessor $persistProcessor, */ public function process($data, Operation $operation, array $uriVariables = [], array $context = []): object { - if ($data instanceof Game && 'PUT' === $context['operation']->getMethod() && null !== $data->getScore()) { + if ($_ENV['APP_ENV'] !== 'test' && $data instanceof Game && 'PUT' === $context['operation']->getMethod() && null !== $data->getScore()) { if (null === $data->getReward()) { throw new \LogicException('Reward of the game n°' . $data->getId() . ' not exists during game PUT request'); } diff --git a/api/tests/Api/GameTest.php b/api/tests/Api/GameTest.php index 2f0959e..a41393c 100644 --- a/api/tests/Api/GameTest.php +++ b/api/tests/Api/GameTest.php @@ -27,22 +27,22 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/games', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/games', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Game', - '@id' => '/api/games', + '@context' => '/contexts/Game', + '@id' => '/games', '@type' => 'hydra:Collection', 'hydra:totalItems' => 60, 'hydra:view' => [ - '@id' => '/api/games?page=1', + '@id' => '/games?page=1', '@type' => 'hydra:PartialCollectionView', - 'hydra:first' => '/api/games?page=1', - 'hydra:last' => '/api/games?page=3', - 'hydra:next' => '/api/games?page=2', + 'hydra:first' => '/games?page=1', + 'hydra:last' => '/games?page=3', + 'hydra:next' => '/games?page=2', ], ]); @@ -70,7 +70,7 @@ public function testGetGame(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Game', + '@context' => '/contexts/Game', '@id' => $iri, '@type' => 'https://schema.org/VideoGame', ]); @@ -84,7 +84,7 @@ public function testGetGame(): void */ public function testCreateGame(): void { - static::createClient()->request('POST', '/api/games', ['json' => [ + static::createClient()->request('POST', '/games', ['json' => [ 'playDate' => new DateTime('2022-01-01 12:35:00.000000'), ]]); diff --git a/api/tests/Api/LotTest.php b/api/tests/Api/LotTest.php index 88de94f..27a4b2c 100644 --- a/api/tests/Api/LotTest.php +++ b/api/tests/Api/LotTest.php @@ -27,22 +27,22 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/lots', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/lots', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Lot', - '@id' => '/api/lots', + '@context' => '/contexts/Lot', + '@id' => '/lots', '@type' => 'hydra:Collection', 'hydra:totalItems' => 61, 'hydra:view' => [ - '@id' => '/api/lots?page=1', + '@id' => '/lots?page=1', '@type' => 'hydra:PartialCollectionView', - 'hydra:first' => '/api/lots?page=1', - 'hydra:last' => '/api/lots?page=4', - 'hydra:next' => '/api/lots?page=2', + 'hydra:first' => '/lots?page=1', + 'hydra:last' => '/lots?page=4', + 'hydra:next' => '/lots?page=2', ], ]); @@ -71,7 +71,7 @@ public function testGetLot(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Lot', + '@context' => '/contexts/Lot', '@id' => $iri, '@type' => 'Lot', ]); @@ -88,7 +88,7 @@ public function testCreateLot(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('POST', '/api/lots', [ + $response = static::createClient()->request('POST', '/lots', [ 'auth_bearer' => $token, 'json' => [ 'name' => 'Nouveau lot de test', @@ -101,14 +101,14 @@ public function testCreateLot(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Lot', + '@context' => '/contexts/Lot', '@type' => 'Lot', 'name' => 'Nouveau lot de test', 'quantity' => 3, 'message' => "C'est un super lot !" ]); - $this->assertMatchesRegularExpression('~^/api/lots/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); + $this->assertMatchesRegularExpression('~^/lots/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); self::assertMatchesResourceItemJsonSchema(Lot::class); } diff --git a/api/tests/Api/MediaObjectTest.php b/api/tests/Api/MediaObjectTest.php index f856f8d..9a57a74 100644 --- a/api/tests/Api/MediaObjectTest.php +++ b/api/tests/Api/MediaObjectTest.php @@ -27,14 +27,14 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/media_objects', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/media_objects', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/MediaObject', - '@id' => '/api/media_objects', + '@context' => '/contexts/MediaObject', + '@id' => '/media_objects', '@type' => 'hydra:Collection', 'hydra:totalItems' => 1, ]); @@ -63,7 +63,7 @@ public function testGetMediaObject(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/MediaObject', + '@context' => '/contexts/MediaObject', '@id' => $iri, '@type' => 'https://schema.org/MediaObject', ]); @@ -81,7 +81,7 @@ public function testCreateAMediaObject(): void $file = new UploadedFile('fixtures/test/files/test_image.jpg', 'test_image.jpg'); - $response = self::createClient()->request('POST', '/api/media_objects', [ + $response = self::createClient()->request('POST', '/media_objects', [ 'auth_bearer' => $token, 'headers' => ['Content-Type' => 'multipart/form-data'], 'extra' => [ @@ -99,7 +99,7 @@ public function testCreateAMediaObject(): void 'name' => 'My uploaded file', ]); - $this->assertMatchesRegularExpression('~^/api/media_objects/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); + $this->assertMatchesRegularExpression('~^/media_objects/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); self::assertMatchesResourceItemJsonSchema(MediaObject::class); } @@ -116,7 +116,7 @@ public function testCreateInvalidMediaObject(): void $file = new UploadedFile('fixtures/test/files/invalid_file.txt', 'invalid_file.txt'); - static::createClient()->request('POST', '/api/media_objects', [ + static::createClient()->request('POST', '/media_objects', [ 'auth_bearer' => $token, 'headers' => ['Content-Type' => 'multipart/form-data'], 'extra' => [ @@ -133,7 +133,7 @@ public function testCreateInvalidMediaObject(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/ConstraintViolationList', + '@context' => '/contexts/ConstraintViolationList', '@type' => 'ConstraintViolationList', 'hydra:title' => 'An error occurred', 'hydra:description' => 'file: The mime type of the file is invalid ("text/plain"). Allowed mime types are "image/jpeg", "image/jpg", "image/png".', diff --git a/api/tests/Api/PlayerTest.php b/api/tests/Api/PlayerTest.php index 6a812f4..36bb339 100644 --- a/api/tests/Api/PlayerTest.php +++ b/api/tests/Api/PlayerTest.php @@ -26,22 +26,22 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/players', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/players', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Player', - '@id' => '/api/players', + '@context' => '/contexts/Player', + '@id' => '/players', '@type' => 'hydra:Collection', 'hydra:totalItems' => 60, 'hydra:view' => [ - '@id' => '/api/players?page=1', + '@id' => '/players?page=1', '@type' => 'hydra:PartialCollectionView', - 'hydra:first' => '/api/players?page=1', - 'hydra:last' => '/api/players?page=3', - 'hydra:next' => '/api/players?page=2', + 'hydra:first' => '/players?page=1', + 'hydra:last' => '/players?page=3', + 'hydra:next' => '/players?page=2', ], ]); @@ -70,7 +70,7 @@ public function testGetPlayer(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Player', + '@context' => '/contexts/Player', '@id' => $iri, '@type' => 'Player', ]); @@ -84,7 +84,7 @@ public function testGetPlayer(): void */ public function testCreatePlayer(): void { - $response = static::createClient()->request('POST', '/api/players', ['json' => [ + $response = static::createClient()->request('POST', '/players', ['json' => [ 'name' => 'Twitter Account Name', 'username' => '@TwitterAccountUsername', 'twitterAccountId' => '123456789', diff --git a/api/tests/Api/RewardTest.php b/api/tests/Api/RewardTest.php index f01a845..eec2be3 100644 --- a/api/tests/Api/RewardTest.php +++ b/api/tests/Api/RewardTest.php @@ -26,22 +26,22 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/rewards', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/rewards', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Reward', - '@id' => '/api/rewards', + '@context' => '/contexts/Reward', + '@id' => '/rewards', '@type' => 'hydra:Collection', 'hydra:totalItems' => 60, 'hydra:view' => [ - '@id' => '/api/rewards?page=1', + '@id' => '/rewards?page=1', '@type' => 'hydra:PartialCollectionView', - 'hydra:first' => '/api/rewards?page=1', - 'hydra:last' => '/api/rewards?page=3', - 'hydra:next' => '/api/rewards?page=2', + 'hydra:first' => '/rewards?page=1', + 'hydra:last' => '/rewards?page=3', + 'hydra:next' => '/rewards?page=2', ], ]); @@ -70,7 +70,7 @@ public function testGetReward(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Reward', + '@context' => '/contexts/Reward', '@id' => $iri, '@type' => 'Reward', ]); @@ -84,7 +84,7 @@ public function testGetReward(): void */ public function testCreateReward(): void { - $response = static::createClient()->request('POST', '/api/rewards', ['json' => [ + $response = static::createClient()->request('POST', '/rewards', ['json' => [ 'distributed' => true, ]]); diff --git a/api/tests/Api/TweetReplyTest.php b/api/tests/Api/TweetReplyTest.php index 3b10a0d..a91c1d1 100644 --- a/api/tests/Api/TweetReplyTest.php +++ b/api/tests/Api/TweetReplyTest.php @@ -25,14 +25,14 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/tweet_replies', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/tweet_replies', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TweetReply', - '@id' => '/api/tweet_replies', + '@context' => '/contexts/TweetReply', + '@id' => '/tweet_replies', '@type' => 'hydra:Collection', 'hydra:totalItems' => 1, ]); @@ -62,7 +62,7 @@ public function testGetTweetReply(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TweetReply', + '@context' => '/contexts/TweetReply', '@id' => $iri, '@type' => 'TweetReply', ]); @@ -79,7 +79,7 @@ public function testCreateTweetReply(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('POST', '/api/tweet_replies', [ + $response = static::createClient()->request('POST', '/tweet_replies', [ 'auth_bearer' => $token, 'json' => [ 'name' => 'need_to_follow_us', @@ -91,13 +91,13 @@ public function testCreateTweetReply(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TweetReply', + '@context' => '/contexts/TweetReply', '@type' => 'TweetReply', 'name' => 'need_to_follow_us', 'message' => 'you need to follow test !!!' ]); - $this->assertMatchesRegularExpression('~^/api/tweet_replies/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); + $this->assertMatchesRegularExpression('~^/tweet_replies/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); self::assertMatchesResourceItemJsonSchema(TweetReply::class); } diff --git a/api/tests/Api/TweetTest.php b/api/tests/Api/TweetTest.php index f4208cb..05910c1 100644 --- a/api/tests/Api/TweetTest.php +++ b/api/tests/Api/TweetTest.php @@ -24,7 +24,7 @@ class TweetTest extends ApiTestCase */ public function testGetCollection(): void { - $response = static::createClient()->request('GET', '/api/tweets'); + $response = static::createClient()->request('GET', '/tweets'); self::assertResponseStatusCodeSame(404); } @@ -49,7 +49,7 @@ public function testGetTweet(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Tweet', + '@context' => '/contexts/Tweet', '@id' => $iri, '@type' => 'https://schema.org/SocialMediaPosting', ]); @@ -65,7 +65,7 @@ public function testCreateTweet(): void { $iri = $this->findIriBy(Player::class, ['username' => '@TestAccountUsername']); - $response = static::createClient()->request('POST', '/api/tweets', ['json' => [ + $response = static::createClient()->request('POST', '/tweets', ['json' => [ 'player' => $iri, 'tweetId' => '123456789', 'creationDate' => new \DateTime(), diff --git a/api/tests/Api/TwitterAccountToFollowTest.php b/api/tests/Api/TwitterAccountToFollowTest.php index c8378c7..4b90e1a 100644 --- a/api/tests/Api/TwitterAccountToFollowTest.php +++ b/api/tests/Api/TwitterAccountToFollowTest.php @@ -27,14 +27,14 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/twitter_account_to_follows', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/twitter_account_to_follows', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterAccountToFollow', - '@id' => '/api/twitter_account_to_follows', + '@context' => '/contexts/TwitterAccountToFollow', + '@id' => '/twitter_account_to_follows', '@type' => 'hydra:Collection', 'hydra:totalItems' => 1, ]); @@ -64,7 +64,7 @@ public function testGetTwitterAccountToFollow(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterAccountToFollow', + '@context' => '/contexts/TwitterAccountToFollow', '@id' => $iri, '@type' => 'TwitterAccountToFollow', ]); @@ -81,7 +81,7 @@ public function testCreateTwitterAccountToFollow(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('POST', '/api/twitter_account_to_follows', [ + $response = static::createClient()->request('POST', '/twitter_account_to_follows', [ 'auth_bearer' => $token, 'json' => [ "username" => "@coopTilleuls", @@ -93,13 +93,13 @@ public function testCreateTwitterAccountToFollow(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterAccountToFollow', + '@context' => '/contexts/TwitterAccountToFollow', '@type' => 'TwitterAccountToFollow', 'username' => '@coopTilleuls', 'active' => true ]); - $this->assertMatchesRegularExpression('~^/api/twitter_account_to_follows/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); + $this->assertMatchesRegularExpression('~^/twitter_account_to_follows/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); self::assertMatchesResourceItemJsonSchema(TwitterAccountToFollow::class); } @@ -116,7 +116,7 @@ public function testCreateInvalidTwitterAccountToFollow(): void $invalidTwitterUsernameAccount = 'invalidTwitterUsernameAccount'; - static::createClient()->request('POST', '/api/twitter_account_to_follows', [ + static::createClient()->request('POST', '/twitter_account_to_follows', [ 'auth_bearer' => $token, 'json' => [ "username" => "@" . $invalidTwitterUsernameAccount, # not exists for the moment @@ -128,7 +128,7 @@ public function testCreateInvalidTwitterAccountToFollow(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/Error', + '@context' => '/contexts/Error', '@type' => 'hydra:Error', 'hydra:title' => 'An error occurred', 'hydra:description' => 'The `username` query parameter value [' . $invalidTwitterUsernameAccount . '] does not match ^[A-Za-z0-9_]{1,15}$', diff --git a/api/tests/Api/TwitterHashtagTest.php b/api/tests/Api/TwitterHashtagTest.php index 4a97902..cfc71dc 100644 --- a/api/tests/Api/TwitterHashtagTest.php +++ b/api/tests/Api/TwitterHashtagTest.php @@ -27,22 +27,22 @@ public function testGetCollection(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('GET', '/api/twitter_hashtags', ['auth_bearer' => $token]); + $response = static::createClient()->request('GET', '/twitter_hashtags', ['auth_bearer' => $token]); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterHashtag', - '@id' => '/api/twitter_hashtags', + '@context' => '/contexts/TwitterHashtag', + '@id' => '/twitter_hashtags', '@type' => 'hydra:Collection', 'hydra:totalItems' => 60, 'hydra:view' => [ - '@id' => '/api/twitter_hashtags?page=1', + '@id' => '/twitter_hashtags?page=1', '@type' => 'hydra:PartialCollectionView', - 'hydra:first' => '/api/twitter_hashtags?page=1', - 'hydra:last' => '/api/twitter_hashtags?page=3', - 'hydra:next' => '/api/twitter_hashtags?page=2', + 'hydra:first' => '/twitter_hashtags?page=1', + 'hydra:last' => '/twitter_hashtags?page=3', + 'hydra:next' => '/twitter_hashtags?page=2', ], ]); @@ -71,7 +71,7 @@ public function testGetTwitterHashtag(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterHashtag', + '@context' => '/contexts/TwitterHashtag', '@id' => $iri, '@type' => 'TwitterHashtag', ]); @@ -88,7 +88,7 @@ public function testCreateTwitterHashtag(): void { $token = LoginTest::getLoginToken(); - $response = static::createClient()->request('POST', '/api/twitter_hashtags', [ + $response = static::createClient()->request('POST', '/twitter_hashtags', [ 'auth_bearer' => $token, 'json' => [ 'hashtag' => '#test', @@ -100,13 +100,13 @@ public function testCreateTwitterHashtag(): void self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); self::assertJsonContains([ - '@context' => '/api/contexts/TwitterHashtag', + '@context' => '/contexts/TwitterHashtag', '@type' => 'TwitterHashtag', 'hashtag' => '#test', 'active' => true ]); - $this->assertMatchesRegularExpression('~^/api/twitter_hashtags/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); + $this->assertMatchesRegularExpression('~^/twitter_hashtags/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']); self::assertMatchesResourceItemJsonSchema(TwitterHashtag::class); } diff --git a/api/tests/Security/LoginTest.php b/api/tests/Security/LoginTest.php index d3d1c90..efde89c 100644 --- a/api/tests/Security/LoginTest.php +++ b/api/tests/Security/LoginTest.php @@ -18,7 +18,7 @@ class LoginTest extends ApiTestCase */ public function testLogin(): void { - static::createClient()->request('POST', '/api/login', ['json' => [ + static::createClient()->request('POST', '/login', ['json' => [ 'username' => $_ENV['USER_IN_MEMORY_USERNAME'], 'password' => $_ENV['USER_IN_MEMORY_PASSWORD'] ]]); @@ -32,7 +32,7 @@ public function testLogin(): void */ public function testLoginFail(): void { - static::createClient()->request('POST', '/api/login', ['json' => [ + static::createClient()->request('POST', '/login', ['json' => [ 'username' => 'invalid user', 'password' => 'invalid password' ]]); @@ -50,7 +50,7 @@ public function testLoginFail(): void */ public static function getLoginToken(): string { - $token = static::createClient()->request('POST', '/api/login', ['json' => [ + $token = static::createClient()->request('POST', '/login', ['json' => [ 'username' => $_ENV['USER_IN_MEMORY_USERNAME'], 'password' => $_ENV['USER_IN_MEMORY_PASSWORD'] ]]); diff --git a/docker-compose.override.yml b/docker-compose.override.yml index d653506..9151098 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -3,26 +3,42 @@ version: "3.4" # Development environment override services: php: + build: + target: app_php_dev volumes: - # The "cached" option has no effect on Linux but improves performance on Mac - - ./api:/srv/api:rw,cached - - ./api/docker/php/conf.d/symfony.dev.ini:/usr/local/etc/php/conf.d/symfony.ini - # If you develop on Mac you can remove the var/ directory from the bind-mount - # for better performance by enabling the next line - # - /srv/api/var + - ./api:/srv/app + - ./api/docker/php/conf.d/api-platform.dev.ini:/usr/local/etc/php/conf.d/api-platform.dev.ini:ro + # If you develop on Mac or Windows you can remove the vendor/ directory + # from the bind-mount for better performance by enabling the next line: + #- /srv/app/vendor environment: - APP_ENV: dev + # See https://xdebug.org/docs/all_settings#mode + XDEBUG_MODE: "${XDEBUG_MODE:-off}" + extra_hosts: + # Ensure that host.docker.internal is correctly defined on Linux + - host.docker.internal:host-gateway + + admin: + build: + context: ./admin + target: app_admin_dev + volumes: + - ./admin:/usr/src/admin caddy: volumes: + - ./api/public:/srv/app/public:ro - ./api/docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - ./api/public:/srv/api/public:ro - -###> symfony/mercure-bundle ### -###< symfony/mercure-bundle ### - -###> doctrine/doctrine-bundle ### + environment: + MERCURE_EXTRA_DIRECTIVES: demo + + ###> doctrine/doctrine-bundle ### database: ports: - - "5432" + - target: 5432 + published: 5432 + protocol: tcp ###< doctrine/doctrine-bundle ### + +###> symfony/mercure-bundle ### +###< symfony/mercure-bundle ### diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 079c85d..e67e0d0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -4,7 +4,6 @@ version: "3.4" services: php: environment: - APP_ENV: prod APP_SECRET: ${APP_SECRET} MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET} @@ -12,3 +11,7 @@ services: environment: MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} + + database: + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} diff --git a/docker-compose.yml b/docker-compose.yml index 34e1574..e700697 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,37 +4,42 @@ services: php: build: context: ./api - target: symfony_php - args: - SYMFONY_VERSION: ${SYMFONY_VERSION:-} - STABILITY: ${STABILITY:-stable} + target: app_php + depends_on: + - database restart: unless-stopped volumes: - php_socket:/var/run/php - - ./api:/srv/api:rw,cached - # - ./api/var:/srv/api/var:rw healthcheck: interval: 10s timeout: 3s retries: 3 start_period: 30s environment: - APP_ENV: dev - # Run "composer require symfony/orm-pack" to install and configure Doctrine ORM - DATABASE_URL: postgresql://${POSTGRES_USER:-symfony}:${POSTGRES_PASSWORD:-ChangeMe}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-13} - # Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration + DATABASE_URL: postgresql://${POSTGRES_USER:-symfony}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-api}?serverVersion=${POSTGRES_VERSION:-14} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16} + TRUSTED_HOSTS: ^${SERVER_NAME:-localhost}|caddy$$ MERCURE_URL: ${CADDY_MERCURE_URL:-http://caddy/.well-known/mercure} MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeMe!} + admin: + build: + context: ./admin + target: app_admin_prod + environment: + NEXT_PUBLIC_ENTRYPOINT: http://caddy + caddy: build: - context: ./api - target: symfony_caddy + context: api/ + target: app_caddy depends_on: - php + - admin environment: - SERVER_NAME: ${SERVER_NAME:-localhost, caddy:80} + PWA_UPSTREAM: admin:3000 + SERVER_NAME: ${SERVER_NAME:-localhost}, caddy:80 MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeMe!} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeMe!} restart: unless-stopped @@ -42,8 +47,6 @@ services: - php_socket:/var/run/php - caddy_data:/data - caddy_config:/config - - ./api/docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - ./api/public:/srv/api/public:ro ports: # HTTP - target: 80 @@ -58,31 +61,26 @@ services: published: ${HTTP3_PORT:-443} protocol: udp -# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service -###> symfony/mercure-bundle ### -###< symfony/mercure-bundle ### - -###> doctrine/doctrine-bundle ### database: - image: postgres:${POSTGRES_VERSION:-13}-alpine + image: postgres:${POSTGRES_VERSION:-14}-alpine environment: - POSTGRES_DB: ${POSTGRES_DB:-app} + - POSTGRES_DB=${POSTGRES_DB:-api} # You should definitely change the password in production - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ChangeMe} - POSTGRES_USER: ${POSTGRES_USER:-symfony} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-!ChangeMe!} + - POSTGRES_USER=${POSTGRES_USER:-symfony} volumes: - - db-data:/var/lib/postgresql/data:rw - # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! - # - ./docker/db/data:/var/lib/postgresql/data:rw -###< doctrine/doctrine-bundle ### + - db_data:/var/lib/postgresql/data + # you may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./api/docker/db/data:/var/lib/postgresql/data + +# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service +###> symfony/mercure-bundle ### +###< symfony/mercure-bundle ### volumes: php_socket: caddy_data: caddy_config: -###> symfony/mercure-bundle ### -###< symfony/mercure-bundle ### - -###> doctrine/doctrine-bundle ### - db-data: + ###> doctrine/doctrine-bundle ### + db_data: ###< doctrine/doctrine-bundle ### diff --git a/helm/api-platform/.helmignore b/helm/api-platform/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/api-platform/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/api-platform/Chart.lock b/helm/api-platform/Chart.lock new file mode 100644 index 0000000..4da5d47 --- /dev/null +++ b/helm/api-platform/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami/ + version: 10.16.2 +digest: sha256:61815d0b8e5bbe9bdc0cf3b0b82a9758e1cbdb5c8324c33c450aa25c6d6f9cf4 +generated: "2022-06-14T20:46:47.661939283+02:00" diff --git a/helm/api-platform/Chart.yaml b/helm/api-platform/Chart.yaml new file mode 100644 index 0000000..9230541 --- /dev/null +++ b/helm/api-platform/Chart.yaml @@ -0,0 +1,31 @@ +apiVersion: v2 +name: api-platform +description: A Helm chart for an API Platform project +home: https://api-platform.com +icon: https://api-platform.com/logo-250x250.png + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 0.1.0 + +dependencies: + - name: postgresql + version: ~10.16.2 + repository: https://charts.bitnami.com/bitnami/ + condition: postgresql.enabled diff --git a/helm/api-platform/README.md b/helm/api-platform/README.md new file mode 100644 index 0000000..d27f268 --- /dev/null +++ b/helm/api-platform/README.md @@ -0,0 +1,6 @@ +# Deploying to a Kubernetes Cluster + +API Platform comes with a native integration with [Kubernetes](https://kubernetes.io/) and the [Helm](https://helm.sh/) +package manager. + +[Learn how to deploy in the dedicated documentation entry](https://api-platform.com/docs/deployment/kubernetes/). diff --git a/helm/api-platform/templates/NOTES.txt b/helm/api-platform/templates/NOTES.txt new file mode 100644 index 0000000..4b308b7 --- /dev/null +++ b/helm/api-platform/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "api-platform.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "api-platform.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "api-platform.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api-platform.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/api-platform/templates/_helpers.tpl b/helm/api-platform/templates/_helpers.tpl new file mode 100644 index 0000000..e4acb39 --- /dev/null +++ b/helm/api-platform/templates/_helpers.tpl @@ -0,0 +1,84 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "api-platform.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "api-platform.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "api-platform.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "api-platform.labels" -}} +helm.sh/chart: {{ include "api-platform.chart" . }} +{{ include "api-platform.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common labels ADMIN +*/}} +{{- define "api-platform.labelsADMIN" -}} +helm.sh/chart: {{ include "api-platform.chart" . }} +{{ include "api-platform.selectorLabelsADMIN" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "api-platform.selectorLabels" -}} +app.kubernetes.io/name: {{ include "api-platform.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/part-of: {{ include "api-platform.name" . }} +{{- end }} + +{{/* +Selector labels ADMIN +*/}} +{{- define "api-platform.selectorLabelsADMIN" -}} +app.kubernetes.io/name: {{ include "api-platform.name" . }}-admin +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/part-of: {{ include "api-platform.name" . }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "api-platform.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "api-platform.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/api-platform/templates/configmap.yaml b/helm/api-platform/templates/configmap.yaml new file mode 100644 index 0000000..19c2a61 --- /dev/null +++ b/helm/api-platform/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "api-platform.fullname" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} +data: + php-app-env: {{ .Values.php.appEnv | quote }} + php-app-debug: {{ .Values.php.appDebug | quote }} + php-cors-allow-origin: {{ .Values.php.corsAllowOrigin | quote }} + php-trusted-hosts: {{ .Values.php.trustedHosts | quote }} + php-trusted-proxies: "{{ join "," .Values.php.trustedProxies }}" + mercure-url: "http://{{ include "api-platform.fullname" . }}/.well-known/mercure" + mercure-public-url: {{ .Values.mercure.publicUrl | default "http://127.0.0.1/.well-known/mercure" | quote }} diff --git a/helm/api-platform/templates/deployment.yaml b/helm/api-platform/templates/deployment.yaml new file mode 100644 index 0000000..ddaba2e --- /dev/null +++ b/helm/api-platform/templates/deployment.yaml @@ -0,0 +1,168 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "api-platform.fullname" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "api-platform.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "api-platform.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "api-platform.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }}-caddy + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.caddy.image.repository }}:{{ .Values.caddy.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.caddy.image.pullPolicy }} + env: + - name: SERVER_NAME + value: :80 + - name: ADMIN_UPSTREAM + value: {{ include "api-platform.fullname" . }}-admin:3000 + - name: MERCURE_PUBLISHER_JWT_KEY + valueFrom: + secretKeyRef: + name: {{ include "api-platform.fullname" . }} + key: mercure-jwt-secret + - name: MERCURE_SUBSCRIBER_JWT_KEY + valueFrom: + secretKeyRef: + name: {{ include "api-platform.fullname" . }} + key: mercure-jwt-secret + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: admin + containerPort: 2019 + protocol: TCP + volumeMounts: + - mountPath: /var/run/php + name: php-socket + lifecycle: + preStop: + exec: + command: ["curl", "-XPOST", "http://localhost:2019/stop"] + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 + livenessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 + resources: + {{- toYaml .Values.resources | nindent 12 }} + - name: {{ .Chart.Name }}-php + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.php.image.repository }}:{{ .Values.php.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.php.image.pullPolicy }} + env: + - name: TRUSTED_HOSTS + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-trusted-hosts + - name: TRUSTED_PROXIES + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-trusted-proxies + - name: APP_ENV + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-app-env + - name: APP_DEBUG + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-app-debug + - name: APP_SECRET + valueFrom: + secretKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-app-secret + - name: CORS_ALLOW_ORIGIN + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: php-cors-allow-origin + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "api-platform.fullname" . }} + key: database-url + - name: MERCURE_URL + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: mercure-url + - name: MERCURE_PUBLIC_URL + valueFrom: + configMapKeyRef: + name: {{ include "api-platform.fullname" . }} + key: mercure-public-url + - name: MERCURE_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "api-platform.fullname" . }} + key: mercure-jwt-secret + volumeMounts: + - mountPath: /var/run/php + name: php-socket + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "/bin/sleep 1; kill -QUIT 1"] + readinessProbe: + exec: + command: + - docker-healthcheck + initialDelaySeconds: 120 + periodSeconds: 3 + livenessProbe: + exec: + command: + - docker-healthcheck + initialDelaySeconds: 120 + periodSeconds: 3 + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: php-socket + emptyDir: {} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/api-platform/templates/hpa.yaml b/helm/api-platform/templates/hpa.yaml new file mode 100644 index 0000000..d2b4f75 --- /dev/null +++ b/helm/api-platform/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "api-platform.fullname" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "api-platform.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/api-platform/templates/ingress.yaml b/helm/api-platform/templates/ingress.yaml new file mode 100644 index 0000000..2a8ec1c --- /dev/null +++ b/helm/api-platform/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "api-platform.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} diff --git a/helm/api-platform/templates/pwa-deployment.yaml b/helm/api-platform/templates/pwa-deployment.yaml new file mode 100644 index 0000000..077868d --- /dev/null +++ b/helm/api-platform/templates/pwa-deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "api-platform.fullname" . }}-admin + labels: + {{- include "api-platform.labelsADMIN" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "api-platform.selectorLabelsADMIN" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "api-platform.selectorLabelsADMIN" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "api-platform.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }}-admin + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.admin.image.repository }}:{{ .Values.admin.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.admin.image.pullPolicy }} + env: + - name: NEXT_PUBLIC_ENTRYPOINT + value: http://{{ include "api-platform.fullname" . }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/api-platform/templates/pwa-service.yaml b/helm/api-platform/templates/pwa-service.yaml new file mode 100644 index 0000000..5969d2d --- /dev/null +++ b/helm/api-platform/templates/pwa-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "api-platform.fullname" . }}-admin + labels: + {{- include "api-platform.labelsADMIN" . | nindent 4 }} +spec: + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + name: http + selector: + {{- include "api-platform.selectorLabelsADMIN" . | nindent 4 }} diff --git a/helm/api-platform/templates/secrets.yaml b/helm/api-platform/templates/secrets.yaml new file mode 100644 index 0000000..fcdaf6e --- /dev/null +++ b/helm/api-platform/templates/secrets.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "api-platform.fullname" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.postgresql.enabled }} + database-url: {{ printf "pgsql://%s:%s@%s-postgresql/%s?serverVersion=13&charset=utf8" .Values.postgresql.postgresqlUsername .Values.postgresql.postgresqlPassword .Release.Name .Values.postgresql.postgresqlDatabase | b64enc | quote }} + {{- else }} + database-url: {{ .Values.postgresql.url | b64enc | quote }} + {{- end }} + php-app-secret: {{ .Values.php.appSecret | default (randAlphaNum 40) | b64enc | quote }} + mercure-jwt-secret: {{ .Values.mercure.jwtSecret | default (randAlphaNum 40) | b64enc | quote }} diff --git a/helm/api-platform/templates/service.yaml b/helm/api-platform/templates/service.yaml new file mode 100644 index 0000000..7851501 --- /dev/null +++ b/helm/api-platform/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "api-platform.fullname" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "api-platform.selectorLabels" . | nindent 4 }} diff --git a/helm/api-platform/templates/serviceaccount.yaml b/helm/api-platform/templates/serviceaccount.yaml new file mode 100644 index 0000000..a0d5bff --- /dev/null +++ b/helm/api-platform/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "api-platform.serviceAccountName" . }} + labels: + {{- include "api-platform.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/api-platform/templates/tests/test-connection.yaml b/helm/api-platform/templates/tests/test-connection.yaml new file mode 100644 index 0000000..f015683 --- /dev/null +++ b/helm/api-platform/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "api-platform.fullname" . }}-test-connection" + labels: + {{- include "api-platform.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "api-platform.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/api-platform/values.yaml b/helm/api-platform/values.yaml new file mode 100644 index 0000000..495444d --- /dev/null +++ b/helm/api-platform/values.yaml @@ -0,0 +1,129 @@ +# Default values for api-platform. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +php: + image: + repository: "chart-example.local/api-platform/php" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + appEnv: prod + appDebug: "0" + appSecret: "" + corsAllowOrigin: "^https?://.*?\\.chart-example\\.local$" + trustedHosts: "^127\\.0\\.0\\.1|localhost|.*\\.chart-example\\.local$" + trustedProxies: + - "127.0.0.1" + - "10.0.0.0/8" + - "172.16.0.0/12" + - "192.168.0.0/16" + +admin: + image: + repository: "chart-example.local/api-platform/admin" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +caddy: + image: + repository: "chart-example.local/api-platform/caddy" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# You may prefer using the managed version in production: https://mercure.rocks +mercure: + publicUrl: https://chart-example.local/.well-known/mercure + # Change me! + jwtSecret: "!ChangeMe!" + corsAllowedOrigins: "^https?://.*?\\.chart-example\\.local$" + +# Full configuration: https://github.com/bitnami/charts/tree/master/bitnami/postgresql +postgresql: + enabled: true + # If bringing your own PostgreSQL, the full uri to use + url: postgresql://api-platform:!ChangeMe!@database:5432/api?serverVersion=13&charset=utf8 + postgresqlUsername: "example" + postgresqlPassword: "!ChangeMe!" + postgresqlDatabase: "api" + # Persistent Volume Storage configuration. + # ref: https://kubernetes.io/docs/user-guide/persistent-volumes + persistence: + enabled: false + pullPolicy: IfNotPresent + image: + repository: bitnami/postgresql + tag: 13 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: [] + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# If you use Mercure, you need the managed or the On Premise version to deploy more than one pod: https://mercure.rocks/docs/hub/cluster +replicaCount: 1 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {}