├── .gitignore ├── README.adoc ├── composer.json ├── composer.lock ├── conf ├── deployer │ ├── deploy.php │ └── hosts.yml ├── docker │ ├── Dockerfile.cronjob │ ├── Dockerfile.phpcs │ ├── Dockerfile.phpunit │ ├── Dockerfile.web.local │ ├── Dockerfile.web.remote │ ├── Dockerfile.webinit │ ├── docker-compose.cli.local.yml │ └── files │ │ └── geolite2 │ │ └── GeoLite2-Country.mmdb ├── env │ ├── .env.dev.local │ ├── .env.master.local │ ├── .env.oat.remote │ └── .env.prod.remote ├── infra │ ├── msmtp.logrotate │ ├── msmtprc.tpl │ ├── php.ini.tpl │ ├── ssh_config │ └── virtual-host.conf.tpl ├── k8s │ ├── cronjob.local.tpl.yml │ ├── cronjob.remote.tpl.yml │ ├── deployment.local.tpl.yml │ ├── deployment.remote.tpl.yml │ └── service.tpl.yml └── phpcs │ └── ruleset.xml ├── docker-compose.yml ├── docs ├── architecture-images │ ├── architecture-local.png │ └── architecture-remote.png └── architecture.drawio └── sf ├── .env.dev ├── .env.master ├── .env.test ├── .gitignore ├── bin └── console ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── easy_log_handler.yaml │ │ ├── flysystem.yaml │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── flysystem.yaml │ ├── framework.yaml │ ├── mailer.yaml │ ├── messenger.yaml │ ├── oat │ │ ├── deprecations.yaml │ │ ├── doctrine.yaml │ │ ├── monolog.yaml │ │ └── routing.yaml │ ├── prod │ │ ├── deprecations.yaml │ │ ├── doctrine.yaml │ │ ├── monolog.yaml │ │ └── routing.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── sensio_framework_extra.yaml │ ├── test │ │ ├── cache.yaml │ │ ├── dama_doctrine_test_bundle.yaml │ │ ├── flysystem.yaml │ │ ├── framework.yaml │ │ ├── mailer.yaml │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ ├── security.yaml │ │ ├── twig.yaml │ │ ├── validator.yaml │ │ └── web_profiler.yaml │ ├── translation.yaml │ ├── twig.yaml │ └── validator.yaml ├── preload.php ├── routes.yaml ├── routes │ ├── annotations.yaml │ └── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml ├── services.yaml ├── services_craue.yml ├── services_dev.yaml └── services_test.yaml ├── migrations └── .gitignore ├── phpunit.xml.dist ├── public ├── css │ └── index.html ├── img │ └── index.html ├── index.php └── js │ └── index.html ├── src ├── Controller │ ├── .gitignore │ ├── MailController.php │ └── SecurityController.php ├── Entity │ └── .gitignore ├── EventListener │ └── AuthenticationListener.php ├── Kernel.php ├── Migrations │ └── .gitignore ├── Repository │ └── .gitignore ├── Security │ ├── AuthenticationFailureHandler.php │ └── AuthenticationSuccessHandler.php └── Twig │ └── JsonDecodeExtension.php ├── symfony.lock ├── tests └── bootstrap.php └── translations └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build/ 3 | /cache/ 4 | /log/ 5 | /var/ 6 | /vendor/ 7 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Symfony application on GKE 2 | :author: Your Name 3 | :email: your@email 4 | :revnumber: 0.3 5 | :revdate: 2021-10-15 6 | :revremark: 7 | :version-label!: 8 | :sectnums: 9 | :toc: 10 | :toclevels: 3 11 | :imagesdir: docs/architecture-images 12 | :source-highlighter: highlightjs 13 | :highlightjsdir: ../github/highlight 14 | // Unfortunately Github doesn't support include statements 15 | ifndef::env-github[] 16 | include::docs/README.config.adoc[] 17 | endif::[] 18 | ifdef::env-github[] 19 | // Copy the included section 20 | :GCP_PROJECT: myproject-123456 21 | :GCP_REGION: us-central1 22 | :GCP_ZONE: us-central1-c 23 | :PROJECT: myproject 24 | :GITHUB_REPO: myproject 25 | :GITHUB_ACCOUNT: myorganization 26 | :WEB_URL: myproject.com 27 | :GKE_CLUSTER: myproject 28 | endif::[] 29 | 30 | == Architecture 31 | 32 | === What is this for? 33 | 34 | This is a recipe for deploying a Symfony application on the Google Kubernetes 35 | Engine. It includes all the configuration files and build scripts. 36 | 37 | It is based on the implementation notes of a recent project. I tidied them up so that 38 | they could be shared. Hope this helps. 39 | 40 | How to use it: 41 | 42 | * Download the repo 43 | * Prepare your local and remote infrastructure as explained on this page 44 | * Install `composer.json` 45 | * Customize `sf/composer.json` if needed and `composer install` 46 | * Adapt the `conf/env` files to your environments 47 | * Adapt the `docs/README.config.adoc` files to your project configuration 48 | * Start developing your Symfony application 49 | 50 | === Principles 51 | 52 | Key principles underlying this design: 53 | 54 | . Adhere to the 12-factors principles 55 | . Identical application code deployed in local and remote environments 56 | . Near-identical infrastructure code deployed in local and remote environments 57 | . Ready to deploy on the GKE (Google Kubernetes Engine) 58 | 59 | === Assumptions 60 | 61 | . Code repository in a private Github repo (the code is easy to adapt if your code sits somewhere else) 62 | . A web reverse proxy has been deployed in the GKE cluster, behind a service of type LoadBalancer (see https://github.com/ericjacolin/apache-proxy-k8s[this recipe] for deploying an Apache+Letsencrypt web server 63 | 64 | === Architecture overview 65 | 66 | ==== Local architecture 67 | image::architecture-local.png[] 68 | 69 | Kubernetes: 70 | 71 | * Kubernetes cluster on Minikube, with Virtualbox VM back-end 72 | * Includes its own Docker environment 73 | * The data volumes on the host are mounted on the `/hosthome` VM location, where they 74 | are visible to the Kubernetes cluster. From there they can be mounted as persistent volumes 75 | onto the container 76 | * Connect to a MySQL database on the host, external to the cluster 77 | 78 | The application manages two types of content files: 79 | 80 | * Public files, served directly by the web proxy server 81 | * Private files, subject to access control, served by the Symfony application 82 | * File storage abstraction using Flysystem, in `local` mode 83 | 84 | 85 | ==== Remote architecture 86 | image::architecture-remote.png[] 87 | 88 | Kubernetes: 89 | 90 | * GKE (Google Kubernetes Engine) Autopilot cluster 91 | * GCP Docker image registry 92 | * Permanent storage on Google Cloud Storage buckets, public buckets for public files, 93 | private buckets for private files 94 | * Connect to Google Cloud SQL/ MySQL database 95 | 96 | Application content files: 97 | 98 | * Public files are served directly by the web proxy server, proxying to Cloud Storage 99 | buckets API (public buckets) 100 | * Private files are served by the web application from private buckets 101 | * File storage abstraction using Flysystem, in `gcloud` (Cloud Storage) mode 102 | 103 | We use Cloud Shell to build and deploy releases, with PHP Deployer scripts: 104 | 105 | * Pull project files from Github repo 106 | * Build Docker images, push images to the GCP Registry 107 | * An init container pulls Symfony files from Github and builds the Symfony application, calling `composer install` 108 | * Update GKE deployment manifest with new image tag 109 | 110 | ==== Identical code 111 | 112 | With this recipe, we have identical Symfony application code and Kubernetes services, 113 | deployments and cronjobs definitions across all environments, local as well as remote. 114 | 115 | All differences between environments are reflected in project-level `.env` configuration files. 116 | 117 | ==== Limitations 118 | 119 | The architecture is suited for a relatively simple web application, with relatively modest 120 | traffic and SLAs, or a MVP. 121 | 122 | The limitations, deliberate for a simple project, can easily be extended as needed as follows. 123 | 124 | Here we assume that an Apache server in reverse proxy mode is deployed on a free tier 125 | Compute Engine VM. For bigger sites one would typically use HTTPS Load Balancers 126 | which are expensive. 127 | 128 | [cols="3*", options="header"] 129 | |=== 130 | |Limitation 131 | |Rationale 132 | |How to extend 133 | 134 | |Sessions stored in the container 135 | |Single pod so no need to implement session affinity 136 | |Implement session affinity in the load balancer or reverse proxy + 137 | Alternatively store session information in Google Cloud Memorystore (managed Redis service) 138 | 139 | |Symfony logs stored locally 140 | |Monolog configured to send emails at a certain alert level 141 | |Send Symfony logs to Stackdriver 142 | 143 | |Partial CI/CD 144 | |Deployment by manual execution of a Deployer script in Cloud Shell + 145 | (Github web hooks cannot access Cloud Shell) 146 | |Deploy Jenkins on GCP + 147 | Use Cloud Source Repository instead of Github 148 | 149 | |No test automation in the deployment 150 | |Functional tests are executed locally prior to committing 151 | |Add test tasks to the Deployer script 152 | 153 | |Single pod used for web and batch 154 | |Load on web pod can accommodate batch jobs 155 | |Deploy a separate pod dedicated to batch jobs 156 | 157 | |Symfony Mailer does not yet supports multiple asynchronous transports 158 | |Limitation of the current Mailer version; expecting this to be fixed soon 159 | (https://github.com/symfony/symfony/issues/35750[Issue]) + 160 | For low volumes a single transport suffices 161 | |- 162 | |=== 163 | 164 | === Environments 165 | 166 | Environments are defined by two meta-parameters 167 | 168 | * HOST_ENV: 169 | ** `local` (developer's laptop) 170 | ** `remote` (GCP) 171 | * APP_ENV: 172 | ** any name: dev, master, prod, oat, etc. 173 | ** avoid reusing the same name in both a local and a remote environment, since Symfony will use override 174 | configuration files based on the APP_ENV name; those overrides are likely to be different in a local 175 | and a remote deployment 176 | 177 | Symfony configuration `.env` files are named using these two meta-parameters, and named `.env.{APP_ENV}.{HOST_ENV}`, such as `.env.oat.remote`. 178 | 179 | `.env` file contain all environment parameters needed by either Docker, PHP Deployer or Symfony. 180 | 181 | They contain all environment-specific parameters except secrets. 182 | 183 | Symfony looks at OS environment variables when it can't find an environment variable 184 | in the Symfony `.env` file. 185 | 186 | In the local environment, the Symfony working directory is mounted externally on 187 | the container, thus code changes are visible immediately. 188 | 189 | To switch between environments in the local hosting: 190 | 191 | * Copy the relevant `.env` file from `conf/env` to the Symfony root folder `sf` 192 | * Check out the master or dev branch (or feature branch as the case may be) 193 | * (the `.env` file is built by the build process, not committed to source control) 194 | 195 | In the remote (GCP) environments, the build process selects the relevant Symfony `.env` file 196 | and ADD's it to the Docker container. 197 | 198 | === Folder structure 199 | 200 | The project folder structure is as follows: 201 | 202 | [cols="1,2,2", options="header"] 203 | |=== 204 | |Folder 205 | |Contents 206 | |Comments 207 | 208 | |`` 209 | |Project root, git root 210 | | 211 | 212 | |`{vbar}-- assets` 213 | |Assets to build with Webpack Encore 214 | |css, js 215 | 216 | |`{vbar}-- build` 217 | |Deployment built artefacts (on local deployments) 218 | |Is emptied at the beginning of a build process. Gitignored 219 | 220 | |`{vbar}-- conf` 221 | |Project configuration files 222 | | 223 | 224 | |`{nbsp}{nbsp}{nbsp}{vbar}-- deployer` 225 | |PHP Deployer scripts, Deployer hosts configuration 226 | | 227 | 228 | |`{nbsp}{nbsp}{nbsp}{vbar}-- docker` 229 | |Docker image templates 230 | |Web and batch components 231 | 232 | |`{nbsp}{nbsp}{nbsp}{vbar}-- env` 233 | |Environment variables 234 | |Depend on and 235 | 236 | |`{nbsp}{nbsp}{nbsp}{vbar}-- infra` 237 | |Container configuration file templates 238 | |Apache, PHP, msmtp + 239 | Docker images include the `dockerize` script, which substitutes environment 240 | variables at container build time 241 | 242 | |`{nbsp}{nbsp}{nbsp}{vbar}-- k8s` 243 | |Kubernetes manifest templates: service, deployment, cronjob 244 | |Depend on 245 | 246 | |`{vbar}-- docs` 247 | |Project documentation 248 | | 249 | 250 | |`{vbar}-- sf` 251 | |Symfony project root folder 252 | |The Symfony `.env` file is built dynamically at build time based on dynamically selected `.env..` file 253 | 254 | |`{vbar}-- vendor` 255 | |PHP libraries used by Deployer 256 | |Managed by Composer, distinct from PHP libraries of the Symfony application which are 257 | managed under the `sf` folder 258 | |=== 259 | 260 | ==== Deployer 261 | 262 | https://deployer.org/[Deployer] is a simple deployment tool written in PHP. 263 | It is open source and free. It contains pre-defined recipes designed for traditional 264 | FTP deployments; those are not useful in a Kubernetes context, so we wrote new scripts 265 | from scratch. 266 | 267 | We use Deployer scripts to: 268 | 269 | . Generate service/ cronjob manifests (usually done only once) 270 | . Generate deployment manifests (usually done only once) 271 | . Deploy new container version (done at every release, only remotely) 272 | 273 | We run Deployer scripts on: 274 | 275 | * local laptop for local environment 276 | * Cloud Shell for GCP environments 277 | 278 | The scripts take the parameters: 279 | 280 | * `APP_ENV` 281 | * `TAG`: 282 | ** In remote environments, a git tag version is pulled from Github and deployed 283 | ** In local environment: 'current'. The container needs only rebuilding infrequently, 284 | as it mounts the Symfony working directory, obscuring the ADD directive 285 | in the Dockerfile, thus serves whatever is currently checked out 286 | in the working directory. Note that we use 'current' instead of 'latest' as 'latest' 287 | forces a rebuild of the container, which we don't want locally. 288 | 289 | Outline of the remote build process: 290 | 291 | . Execute an initialisation container which: 292 | .. Checks out the tagged version from Github (into a detached branch) 293 | .. Copies relevant Symfony application files from source 294 | .. Copies the relevant `.env..remote` file to both `build/.env` and to `sf/.env` 295 | .. Warms up the Symfony application cache, which is needed by the PHP Opcache directive and must exist 296 | at the time the web container starts 297 | . Build the application container: 298 | .. Passing the `build/.env` as environment parameters 299 | .. ADD the Symfony application and cache files from the init container 300 | .. COPY infrastructure configuration templates 301 | .. RUN `dockerize` on infrastructure configuration templates (see next section) 302 | .. docker push the new container to the GCP container registry 303 | . Build a deployment manifest to `build/deployment.yml`. This manifest contains the new container tag 304 | .. Apply the updated Kubernetes deployment manifest 305 | 306 | Notes: 307 | 308 | * In the web container, the Apache user (www-data) has user:group id 1000:33, whereas in the init container it has user:group id 33:33. This explains the chown commands in the initialisation container 309 | * Another approach would be to use the GCP native Cloud Build service (but this is less portable) 310 | * https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/[SSH key as secret] 311 | 312 | ==== Infrastructure configuration 313 | 314 | The following container infrastructure files are templated. Running `dockerize` interpolates 315 | placeholders in the templates with variables from the Docker build `.env` file. 316 | 317 | [cols="1,2,2", options="header"] 318 | |=== 319 | |Template 320 | |Target file in container 321 | |Contents 322 | 323 | |`msmtp.logrotate` 324 | |`/etc/logrotate.d/msmtp` 325 | |msmtp logrotate configuration 326 | 327 | |`msmtprc` 328 | |`/etc/msmtprc` 329 | |msmtp configuration. + 330 | Note that the SMTP password is not stored in clear but obtained from an OS environment 331 | variable. 332 | 333 | |`php.ini` 334 | |`/usr/local/etc/php/php.ini` + 335 | `/etc/php/7.3/apache2/php.ini` 336 | |php.ini for CLI and the Apache PHP module 337 | 338 | |`ssh_config` 339 | |`/.ssh/config` 340 | |Location of the SSH key to the Github private repo. Used by the webinit initialisation container to build 341 | the Symfony application files 342 | 343 | |`virtual-host.conf` 344 | |`/etc/apache2/sites-enabled/virtual-host.conf` 345 | |Single virtual host for the web application 346 | |=== 347 | 348 | Notes: 349 | 350 | * For more info on dockerize, https://github.com/powerman/dockerize[see]. 351 | * See also `conf/docker/Dockerfile.PHP.example` for a typical Docker RUN command with commonly 352 | used PHP libraries. Adapt as needed. 353 | 354 | ==== Secrets management 355 | 356 | We use two types of secrets: 357 | 358 | * Kubernetes secrets, mounted onto containers: 359 | ** `MAILER_PASSWORD`: SMTP account password 360 | ** `API_KEY`: API key used by the cron service 361 | * Symfony application secrets, packed into a single `SYMFONY_DECRYPTION_SECRET` Kubernetes secret: 362 | ** `APP_SECRET`: encryption key 363 | ** `DB_PASSWORD`: MySQL account password 364 | ** `MAILER_PASSWORD`: SMTP account password 365 | 366 | The `MAILER_PASSWORD` secret, although used by the Symfony application, is needed outside the Symfony environments, to send emails via the batch cron container. 367 | 368 | With this container build process, secrets only exist as container OS environment variables. 369 | 370 | See: https://symfony.com/doc/current/configuration/secrets.html[Managing Symfony secrets] 371 | 372 | == Symfony application 373 | 374 | === Proxies 375 | 376 | In order for the application to correctly reads the headers forwarded by the web reverse proxy. 377 | 378 | .sf/config/packages/framework.yaml 379 | ---- 380 | framework: 381 | ... 382 | trusted_proxies: '%env(TRUSTED_PROXIES)%' 383 | trusted_headers: ['x-forwarded-for', 'x-forwarded-host'] 384 | ---- 385 | 386 | https://symfony.com/doc/current/deployment/proxies.html[See] for reference. 387 | 388 | === Batch jobs 389 | 390 | A Symfony application is typically used in two modes: online requests and batch jobs. 391 | 392 | For this recipe we use a single container to serve both. 393 | 394 | We define batch jobs as Kubernetes cronjobs. Those jobs do the following: 395 | 396 | * Instantiate a simple Alpine/curl container in the cluster 397 | * The container command sends a curl GET request to the application pod inside the cluster 398 | * The request is handled by a normal Symfony controller 399 | * Complete job and log the job status depending on the HTTP response (200 or 500) 400 | 401 | Note that in a traditional, non-containerized Symfony application we would implement 402 | Console Commands in the controller, triggered by command line php calls, scheduled by a cron job. 403 | We can't do this with Kubernetes, since the Kubernetes cronjob cannot execute remote shell commands on the 404 | container and is limited to sending HTTP requests. 405 | 406 | A simpler alternative is to define cron jobs inside your web proxy container, calling application containers using the same API endpoints. 407 | 408 | === Sending emails 409 | 410 | To send emails, we use the following components: 411 | 412 | * The Symfony Mailer library to create emails 413 | * The Symfony Messenger to queue emails in the database 414 | * The msmtp MTA (message transfer agent) to send emails 415 | * Kubernetes cronjobs to process Messenger queues 416 | 417 | The new Mailer library replaces the deprecated SwiftMailer library and is now the recommended 418 | library for new projects. 419 | 420 | For transport the Symfony application does not establish a SMTP connection to the 421 | remote SMTP server, but instead sends messages to a local MTA running in the container. 422 | We use msmtp as MTA. msmtp is a popular successor to sendmail, easier to configure, with a 423 | sendmail-compatible API. 424 | The benefits of using a MTA are: 425 | 426 | * The messages are sent to the remote SMTP server by the MTA background process, not PHP scripts 427 | * The MTA handles exceptions well, such as SMTP server unavailable or returning errors. 428 | 429 | Here we configure two asynchronous transports: 430 | 431 | * `realtime_mailer` for high priority emails (e.g. confirmation after registration) 432 | * `batch_mailer` for low priority emails (e.g. batch newsletter) 433 | 434 | These transports are configured in `Sendmail` mode. 435 | 436 | The process of sending an email is the following: 437 | 438 | * A Symfony controller action (online or batch) generates an email, indicates the transport 439 | method (high or low priority) 440 | * The Messenger component puts these messages in either queue in the database 441 | * The Messenger component processes these two queues in batch mode and sends the emails 442 | to the msmtp MTA (which runs on the same container) 443 | ** The high priority batch job runs every 2mn with a time-out of 100s. If not all queued emails are 444 | processed, the next run starting 20s after will pick them up 445 | ** The low priority batch job runs every 20mn 446 | * The MTA sends the emails to the remote SMTP server roughly in the order it has 447 | received them. 448 | 449 | See previous section for how cronjobs are implemented in Kubernetes. 450 | 451 | In the messenger configuration file we define the queue where to put emails. 452 | Here we use the application database to persist the queues (other methods are available, 453 | notably Redis). We pass the `queue_name` argument to indicate the queue. 454 | 455 | We also define a dead-letter queue where failed messages will be logged. This will be 456 | rare as it is the MTA that is likely to fail while sending emails instead of the Symfony 457 | application. 458 | 459 | .config/packages/messenger.yaml 460 | [source,yaml] 461 | ---- 462 | framework: 463 | messenger: 464 | # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. 465 | failure_transport: failed 466 | 467 | transports: 468 | # https://symfony.com/doc/current/messenger.html#transport-configuration 469 | failed: 'doctrine://default?queue_name=failed' 470 | # sync: 'sync://' 471 | batch_mailer: 'doctrine://default?queue_name=batch_mailer' 472 | realtime_mailer: 'doctrine://default?queue_name=realtime_mailer' 473 | 474 | routing: 475 | # Route your messages to the transports 476 | 'Symfony\Component\Mailer\Messenger\SendEmailMessage': realtime_mailer 477 | ---- 478 | 479 | In the mailer configuration file we define the action to take when actually sending an email. 480 | Usually this is either a SMTP DSN string or Sendmail. Here we use Sendmail. The `native://default` option 481 | uses the `sendmail_path` setting of php.ini, itself defined as `/usr/bin/msmtp -t -v`. See infra configuration 482 | files. 483 | 484 | .config/packages/mailer.yaml 485 | [source,yaml] 486 | ---- 487 | framework: 488 | mailer: 489 | transports: 490 | #main: '%env(MAILER_DSN)%' 491 | realtime_mailer: 'native://default' 492 | ---- 493 | 494 | Mailer is a new library, and has currently some limitations that we expect to be remediated soon: 495 | 496 | * No support for multiple async transports (https://github.com/symfony/symfony/issues/35750[See]) 497 | 498 | The msmtp configuration is defined in the `conf/infra/msmtprc.tpl` template file. 499 | It contains: 500 | 501 | * SMTP account details 502 | * The password is not stored in clear but is defined by a shell command: `"echo $MAILER_PASSWORD"` 503 | * Location of the msmtp log file 504 | 505 | === File storage 506 | 507 | For application user file management we use the Flysystem library, which provides filesystem abstraction 508 | across a number of storage mechanisms. 509 | 510 | * The local environment uses the `local` adapter (local file system) 511 | * The GCP environment uses the `gcloud` adapter (Cloud Storage). 512 | 513 | The application code to read/ write files is identical in all environments. Only the environment configuration 514 | changes. The storage mechanics are abstracted from the application. The application uses `get` and `put` 515 | methods for file paths on a virtual file system. 516 | 517 | Typically the application needs to manage: 518 | 519 | * Public files, served directly by the web proxy without access control 520 | * Private files, subject to access control, and served by the Symfony application 521 | * Each for local and remote file storage 522 | 523 | Hence four adapter configuration: 524 | 525 | .config/packages/flysystem.yaml 526 | [source,yaml] 527 | ---- 528 | flysystem: 529 | storages: 530 | storage.private.local: 531 | adapter: 'local' 532 | options: 533 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PRIVATE_BUCKET)%' 534 | storage.public.local: 535 | adapter: 'local' 536 | options: 537 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PUBLIC_BUCKET)%' 538 | storage.private.gcloud: 539 | adapter: 'gcloud' 540 | options: 541 | client: 'Google\Cloud\Storage\StorageClient' # The service ID of the Google\Cloud\Storage\StorageClient instance 542 | bucket: '%env(GCS_PRIVATE_BUCKET)%' 543 | prefix: '' 544 | api_url: 'https://storage.googleapis.com' 545 | storage.public.gcloud: 546 | adapter: 'gcloud' 547 | options: 548 | client: 'Google\Cloud\Storage\StorageClient' # The service ID of the Google\Cloud\Storage\StorageClient instance 549 | bucket: '%env(GCS_PUBLIC_BUCKET)%' 550 | prefix: '' 551 | api_url: 'https://storage.googleapis.com' 552 | # Aliases based on environment variable 553 | storage.private: 554 | adapter: 'lazy' 555 | options: 556 | source: 'storage.private.%env(STORAGE_ADAPTER)%' 557 | storage.public: 558 | adapter: 'lazy' 559 | options: 560 | source: 'storage.public.%env(STORAGE_ADAPTER)%' 561 | ---- 562 | 563 | Thus Symfony creates four "real" services (`flysystem.storage.private.local`, etc) 564 | corresponding to these four adapters. 565 | 566 | We do not use these services but dynamic "alias" (lazy) services `storage.private` and `storage.private` 567 | which depend on the environment. 568 | 569 | To use these services in a controller: 570 | 571 | [source,php] 572 | ---- 573 | use League\Flysystem\FilesystemInterface; 574 | 575 | class MyController extends AbstractController 576 | { 577 | /** @var FilesystemInterface $storagePublic Public storage adapter */ 578 | private $storagePublic; 579 | 580 | /** @var FilesystemInterface $storagePrivate Private storage adapter */ 581 | private $storagePrivate; 582 | 583 | public function __construct( 584 | FilesystemInterface $storagePublic, 585 | FilesystemInterface $storagePrivate 586 | ) { 587 | $this->storagePublic = $storagePublic; 588 | $this->storagePrivate = $storagePrivate; 589 | } 590 | 591 | public function myAction( 592 | Request $request 593 | ) { 594 | $this->storagePrivate->put($file_path, $content); 595 | ---- 596 | 597 | On local hosting: 598 | 599 | * External persistent folders on the host are mounted on the Minikube cluster, and 600 | in turn mounted as persistent volumes on the container 601 | * The root of the virtual file system is the mount point of the persistent volume 602 | in the container 603 | * The application read/writes to these folders using the `local` adapter 604 | 605 | On GKE hosting: 606 | 607 | * We use Cloud Storage buckets for storage 608 | * The root of the virtual file system viewed from the application is the bucket 609 | * The application read/writes to these buckets using the `gcloud` adapter, which uses API calls 610 | to Cloud Storage 611 | * The buckets must be configured for ACL access as the Symfony application will use 612 | a GCP service account to access the buckets. 613 | 614 | The `CDN_URL` environment variable is used by the application to create URLs to public 615 | assets that are served directly by the web proxy, outside the application. 616 | 617 | == Local environment 618 | 619 | In the local environment we instantiate a Kubernetes engine using the Minikube 620 | package. 621 | 622 | === Minikube 623 | 624 | . Install kubectl 625 | . Install https://kubernetes.io/docs/setup/learning-environment/minikube[minikube] 626 | via direct download 627 | . Bind mount the host folders used to persist application user data host folder to `/home`, 628 | which is accessible from within the Minikube cluster as `/hosthome` 629 | 630 | Increase the default CPU allocated to the cluster VM (2CPU) to 4CPU: 631 | 632 | ---- 633 | minikube delete 634 | minikube config set cpus 4 635 | minikube start 636 | ---- 637 | 638 | .In the local shell: 639 | [source,bash,subs=attributes+] 640 | ---- 641 | sudo mount --bind /opt/data/storage-buckets /home/storage-buckets \ 642 | && sudo mount --bind /opt/data/projects/myproject /home/myproject \ 643 | \ 644 | && minikube start --driver=virtualbox --cpus 4 \ 645 | && minikube tunnel 646 | ---- 647 | 648 | Then optionally open the minikube dashboard in a browser: 649 | 650 | .In the local shell: 651 | [source,bash,subs=attributes+] 652 | ---- 653 | # Open the minikube dashboard in a browser 654 | minikube dashboard 655 | ---- 656 | 657 | The dashboard is very handy. 658 | 659 | On Linux, from within the minikube cluster, the host does not have a DNS name. This is available on MacOS 660 | hosts (name is `host.docker.internal`). There is a https://github.com/moby/moby/pull/40007[pull request] 661 | to this effect. 662 | 663 | You have to use the host IP address `10.0.2.2` instead. 664 | 665 | === Docker context 666 | 667 | Before building containers (`docker build`) ensure you are in the correct context. Minikube has its 668 | own Docker engine running inside its Virtualbox VM, distinct from that of the laptop host. 669 | 670 | .In the local shell: 671 | [source,bash] 672 | ---- 673 | # switch to the Minikube VM context (must be run in each new terminal session) 674 | eval $(minikube docker-env) 675 | # switch back to local Docker context 676 | eval $(minikube docker-env -u) 677 | ---- 678 | 679 | Notes: 680 | 681 | * I tried to use Minikube with `--vm-driver=none`, so that it would use the host Docker 682 | engine, but it didn't work and probably never will 683 | * The Minikube cluster node is visible in the host at `http://192.168.99.100:31645/` 684 | (the port is randomly assigned at minikube startup). 685 | 686 | === Kubernetes context 687 | 688 | You will be switching between the local and GKE Kubernetes contexts. Ensure you are in the correct 689 | context before firing `kubectl apply` commands. 690 | 691 | .In the local shell: 692 | [source,bash,subs=attributes+] 693 | ---- 694 | kubectl config get-contexts 695 | # output: 696 | CURRENT NAME CLUSTER 697 | * gke_{GCP_PROJECT}_{GCP_REGION}_{GKE_CLUSTER} gke_{GCP_PROJECT}_{GCP_REGION}_{GKE_CLUSTER} 698 | minikube minikube 699 | # To switch to another context: 700 | kubectl config use-context gke_{GCP_PROJECT}_{GCP_REGION}_{GKE_CLUSTER} 701 | # or 702 | kubectl config use-context minikube 703 | ---- 704 | 705 | == GCP environment 706 | 707 | === GCP project 708 | 709 | Set project defaults: 710 | 711 | .In the local shell: 712 | [source,bash,subs=attributes+] 713 | ---- 714 | gcloud config set project {GCP_PROJECT} 715 | gcloud config set compute/region {GCP_REGION} 716 | gcloud config set compute/zone {GCP_ZONE} 717 | ---- 718 | 719 | Generate SSH keys, store them locally in `~/.ssh/`. 720 | 721 | Copy the keys in your Cloud Shell `~/.ssh/` folder. 722 | 723 | === Cloud Shell 724 | 725 | We use Cloud Shell as a build and deployment environment 726 | 727 | .In the Cloud shell: 728 | [source,bash,subs=attributes+] 729 | ---- 730 | # set project 731 | gcloud config set project {GCP_PROJECT} 732 | # clone the project repo: 733 | git clone git@github.com:{GITHUB_ACCOUNT}/{GITHUB_REPO}.git 734 | # install the Deployer vendor libraries 735 | cd {PROJECT} && composer install 736 | ---- 737 | 738 | To avoid having to reinstall Deployer at every new session, add the following lines to your Cloud Shell 739 | customize_environment file: 740 | 741 | .~/customize_environment 742 | ---- 743 | #!/bin/sh 744 | curl -LO https://deployer.org/deployer.phar 745 | sudo mv deployer.phar /usr/local/bin/dep 746 | sudo chmod +x /usr/local/bin/dep 747 | ---- 748 | 749 | It is useful to SCP to Cloud Shell. Note that paths must be absolute. Use the `--recurse` flag 750 | for recursive copying. 751 | 752 | .In the local shell: 753 | [source,bash] 754 | ---- 755 | # To copy a remote directory to your local machine: 756 | gcloud alpha cloud-shell scp \ 757 | cloudshell:~/REMOTE-DIR \ 758 | localhost:~/LOCAL-DIR 759 | # Conversely: 760 | gcloud alpha cloud-shell scp \ 761 | localhost:~/LOCAL-DIR \ 762 | cloudshell:~/REMOTE-DIR 763 | ---- 764 | 765 | ==== Install PHP 7.3 766 | 767 | https://computingforgeeks.com/how-to-install-php-7-3-on-ubuntu-18-04-ubuntu-16-04-debian/[See] 768 | 769 | .In the Google Cloud shell: 770 | [source,bash] 771 | ---- 772 | sudo add-apt-repository ppa:ondrej/php 773 | sudo apt-get update \ 774 | sudo apt install php7.3 php7.3-cli php7.3-mbstring php7.3-curl php7.3-xml php7.3-zip php7.3-curl 775 | sudo update-alternatives --set php /usr/bin/php7.3 776 | ---- 777 | 778 | TO DO: replace the PHP CLI install by a Docker container... nicer and cleaner 779 | 780 | ==== Github SSH key 781 | 782 | https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent[See] 783 | 784 | Create a new SSH key labelled "{PROJECT}-vm" locally. Register it on Github. 785 | Also create a corresponding config file: 786 | 787 | .In the local ~/.ssh/config file, append: 788 | [source,bash,subs=attributes+] 789 | ---- 790 | Host github.com 791 | User git 792 | Hostname github.com 793 | PreferredAuthentications publickey 794 | IdentityFile ~/.ssh/{PROJECT}-vm_rsa 795 | ---- 796 | 797 | ==== Composer 798 | 799 | Composer is used to manage dependencies for PHP Deployer. 800 | 801 | .In the Google Cloud shell: 802 | [source,bash,subs=attributes+] 803 | ---- 804 | # PHP Deployer 805 | cd ~/{PROJECT} 806 | composer install 807 | # Symfony 808 | cd sf 809 | composer install 810 | ---- 811 | 812 | === Cloud storage 813 | 814 | It is a good practice to create bucket names that are domain names associated to your 815 | project, as it guarantees global unicity. 816 | 817 | This requires domain ownership verification: 818 | 819 | * In the Google Search Console, add Property: `{WEB_URL}` 820 | * In your domain name DNS manager, add a TXT record with the provided text. 821 | 822 | Create the following buckets: 823 | 824 | * `app.{WEB_URL}`: private data, live site 825 | * `app-test.{WEB_URL}`: private data, test site 826 | * `cdn.{WEB_URL}`: public data, live site 827 | * `cdn-test.{WEB_URL}`: public data, test site 828 | 829 | Set bucket access control policy to ACLs, since the PHP flysystem API will use a service 830 | account to access the Cloud Storage API. 831 | 832 | To make buckets public: 833 | 834 | .In the local shell: 835 | [source,bash,subs=attributes+] 836 | ---- 837 | gsutil iam ch allUsers:objectViewer gs://cdn-test.{WEB_URL} 838 | gsutil iam ch allUsers:objectViewer gs://cdn.{WEB_URL} 839 | ---- 840 | 841 | Using Chrome, upload folders/ files using the Cloud Storage console or the `gsutil cp` command. 842 | 843 | Example of commands to copy files at the command line from local and remote environments: 844 | 845 | .In the local shell: 846 | [source,bash,subs=attributes+] 847 | ---- 848 | # local to remote 849 | gsutil cp * gs://cdn.{WEB_URL}/dir 850 | # remote to local 851 | gsutil cp gs://cdn.{WEB_URL}/dir/* . 852 | # remote to remote 853 | gsutil cp gs://cdn.{WEB_URL}/dir/* gs://cdn-test.{WEB_URL}.org/dir 854 | ---- 855 | 856 | === Cloud database 857 | 858 | ==== Create the database 859 | 860 | On the Gcloud SQL Console, create DB instance called `{PROJECT}-db` (MySQL 5.7): 861 | 862 | Create Database: 863 | 864 | * Character set/ Collation => `utf8mb4/ utf8mb4_unicode_ci` 865 | * Connectivity: Private IP 866 | 867 | Don't use the recommended collation for MySQL 8.0 `utf8mb4_0900_ai_ci` as it is 868 | not supported on MySQL 5.7. 869 | 870 | Note down the DB instance external IP address (`10.1.2.3`). You will use it in your 871 | `.env` configuration files. 872 | 873 | ==== Test connectivity from VPC 874 | 875 | Instantiate temporarily a Compute Engine VM in the same VPC. 876 | 877 | Install the mysql CLI: `apt-get update && apt-get install -y mysql-client-5.7` 878 | 879 | .In the VM shell: 880 | [source,bash,subs=attributes+] 881 | ---- 882 | # root user - enter password at prompt 883 | mysql -u root -p -h 10.1.2.3 884 | # application user - enter password at prompt 885 | mysql -u {PROJECT}_dev -p -h 10.1.2.3 886 | ---- 887 | 888 | Note: 889 | 890 | * You can't connect from the Cloud Shell, as it is outside the project VPC. 891 | 892 | ==== Create DB accounts 893 | 894 | On the Gcloud SQL Console > Users > Create MySQL user accounts: 895 | 896 | * `{PROJECT}_dev` 897 | * `{PROJECT}_master` 898 | 899 | Allow from any host (%) 900 | 901 | Apply GRANT commands as required by your application. A typical one would be: 902 | 903 | .In the MySQL shell: 904 | [source,mysql] 905 | ---- 906 | GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, EXECUTE, 907 | CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER, LOCK TABLES 908 | ON ``.* TO ''@'%'; 909 | FLUSH PRIVILEGES; 910 | ---- 911 | 912 | ==== Application service account 913 | 914 | Create a `{PROJECT}-dev` Cloud IAM service account for the application. Add roles: 915 | 916 | * Storage > Storage Object Admin 917 | * Cloud SQL > Cloud SQL Client 918 | 919 | The service account is named `{PROJECT}-dev@{GCP_PROJECT}.iam.gserviceaccount.com` 920 | 921 | Create a JSON key associated to this account => `{GCP_PROJECT}-abcdef123456.json`. 922 | 923 | ==== Import a database 924 | 925 | To import a database, instantiate a temporary VM. 926 | 927 | All tables must be in the InnoDB format. 928 | 929 | . Export the DB in SQL format with Adminer or PhpMyAdmin 930 | . SCP the SQL file to the VM: 931 | 932 | .In the local shell: 933 | [source,bash,subs=attributes+] 934 | ---- 935 | gcloud compute scp \ 936 | ~/{PROJECT}_dev.sql \ 937 | temp-vm:/tmp 938 | ---- 939 | 940 | [start=3] 941 | . Import the DB: 942 | 943 | .In the temporary VM shell: 944 | [source,bash,subs=attributes+] 945 | ---- 946 | mysql -u root -p -h 10.1.2.3 {PROJECT}_dev < /tmp/{PROJECT}_dev.sql 947 | ---- 948 | 949 | ==== Artifact Registry 950 | 951 | The Container Registry is deprecated. Use the Artifact Registry instead. 952 | 953 | * In the Console > Artifact Registry, create a repo named `{GCP_PROJECT}` 954 | * In Cloud Shell run `gcloud auth configure-docker {GCP_REGION}-docker.pkg.dev` 955 | * Assign role "Artifact Registry Writer" to your service account 956 | 957 | === GKE 958 | 959 | ==== Create the cluster 960 | 961 | Create an Autopilot cluster. Workload identity is enabled by default. 962 | 963 | ==== Bind the service account 964 | 965 | Creating the cluster creates automatically: 966 | 967 | * A `default` namespace 968 | * A Kubernetes service account (`kubectl get serviceaccount --namespace default`). 969 | At this stage this service account controls access only within the cluster. 970 | 971 | The Kubernetes service account needs to access Google resources, so we bind it to the 972 | application Google service account previously created. 973 | 974 | .In the local shell: 975 | [source,bash,subs=attributes+] 976 | ---- 977 | gcloud iam service-accounts add-iam-policy-binding \ 978 | --role roles/iam.workloadIdentityUser \ 979 | --member "serviceAccount:{GCP_PROJECT}.svc.id.goog[default/default]" \ 980 | {GCP_PROJECT}-dev@{GCP_PROJECT}.iam.gserviceaccount.com 981 | ---- 982 | 983 | Add corresponding annotation to the Kubernetes service account: 984 | 985 | .In the local shell: 986 | [source,bash,subs=attributes+] 987 | ---- 988 | kubectl annotate serviceaccount \ 989 | --namespace default \ 990 | default \ 991 | iam.gke.io/gcp-service-account={GCP_PROJECT}-dev@{GCP_PROJECT}.iam.gserviceaccount.com 992 | ---- 993 | 994 | Verify that the service account is configured correctly by running a test container 995 | provided by Google: 996 | 997 | .In the local shell: 998 | [source,bash,subs=attributes+] 999 | ---- 1000 | kubectl run -it \ 1001 | --generator=run-pod/v1 \ 1002 | --image google/cloud-sdk \ 1003 | --serviceaccount default \ 1004 | --namespace default \ 1005 | workload-identity-test 1006 | ---- 1007 | 1008 | .In the container shell: 1009 | [source,bash] 1010 | ---- 1011 | gcloud auth list 1012 | ---- 1013 | This should display a single Google service account, the one bind'ed earlier. This is the service account 1014 | the pod will use to access GCP services. 1015 | 1016 | Once done, delete the `workload-identity-test` pod. 1017 | 1018 | == Deployment 1019 | 1020 | === Deploy locally 1021 | 1022 | ==== Secrets 1023 | 1024 | Create your secrets for each environment in a safe location outside the project directory. 1025 | 1026 | Naming convention: `secrets...yml` 1027 | Those are in the form: 1028 | 1029 | ./secrets/secrets.local.dev.yml 1030 | [source,yaml,subs=attributes+] 1031 | ---- 1032 | apiVersion: v1 1033 | kind: Secret 1034 | metadata: 1035 | name: {PROJECT}-{APP_ENV}-sf-secrets 1036 | type: Opaque 1037 | data: 1038 | # php -r 'echo base64_encode(base64_encode(require "config/secrets/dev/dev.decrypt.private.php"));' 1039 | SYMFONY_DECRYPTION_SECRET: YWJjZGVmZ2h1aWRVQUlQR0RBWkVQVUlJVUdQ 1040 | # echo -n 'secret1' | base64 1041 | API_KEY: c2VjcmV0MQ== 1042 | # echo -n 'secret2' | base64 1043 | MAILER_PASSWORD: c2VjcmV0Mg== 1044 | ---- 1045 | 1046 | Deploy Kubernetes secrets locally: 1047 | 1048 | .In the local shell: 1049 | [source,bash,subs=attributes+] 1050 | ---- 1051 | cd dir 1052 | kubectl config use-context minikube 1053 | kubectl apply -f secrets.local.dev.yml 1054 | kubectl apply -f secrets.local.master.yml 1055 | ---- 1056 | 1057 | ==== Cronjobs 1058 | 1059 | To execute cron jobs we instantiate a very simple Docker Alpine image with the cUrl 1060 | library. 1061 | 1062 | .In the local shell: 1063 | [source,bash,subs=attributes+] 1064 | ---- 1065 | cd {PROJECT} 1066 | # Set Docker and Kubernetes contexts to Minikube 1067 | eval $(minikube docker-env) 1068 | # Build the Docker cronjob image 1069 | docker build -f build/Dockerfile.cronjob -t k8s-cronjob:current . 1070 | ---- 1071 | 1072 | ==== Services 1073 | 1074 | We build services manifests with PHP Deployer and deploy them using kubectl. 1075 | This is usually done only once. 1076 | 1077 | .In the local shell: 1078 | [source,bash,subs=attributes+] 1079 | ---- 1080 | cd {PROJECT} 1081 | # Set Docker and Kubernetes contexts to Minikube 1082 | kubectl config use-context minikube 1083 | # Deploy services (dev environment) 1084 | php vendor/bin/dep --file=conf/deployer/deploy.php \ 1085 | --hosts=localhost gen-service -o APP_ENV=dev 1086 | kubectl apply -f build/service.yml 1087 | kubectl apply -f build/cronjob.local.yml 1088 | # Deploy services (master environment) 1089 | php vendor/bin/dep --file=conf/deployer/deploy.php \ 1090 | --hosts=localhost gen-service -o APP_ENV=master 1091 | kubectl apply -f build/service.yml 1092 | kubectl apply -f build/cronjob.local.yml 1093 | ---- 1094 | 1095 | ==== Releases 1096 | 1097 | The web application container mounts the Symfony working directory. There is no need 1098 | to rebuild the container, unless, say, you need to add a PHP library. Just switch git 1099 | branches as needed. 1100 | 1101 | The Minikube web application is visible on the host at: `192.168.99.100:32745` 1102 | (adapt the port number) 1103 | 1104 | === Deploy on GKE 1105 | 1106 | We use Cloud Shell to build and deploy on GKE. 1107 | 1108 | ==== Pull the repo 1109 | 1110 | .In the Cloud shell: 1111 | [source,bash,subs=attributes+] 1112 | ---- 1113 | git clone git@github.com:{GITHUB_ACCOUNT}/{GITHUB_REPO}.git 1114 | cd {PROJECT} 1115 | ---- 1116 | 1117 | ==== Secrets 1118 | 1119 | Deploy Kubernetes secrets. Here those are the same as for the local environment. 1120 | 1121 | .In the local shell: 1122 | [source,bash,subs=attributes+] 1123 | ---- 1124 | cd your-secrets-dir 1125 | # Switch to GKE context 1126 | kubectl config use-context gke_{GCP_PROJECT}_{GCP_REGION}_{GKE_CLUSTER} 1127 | kubectl apply -f secrets.oat.remote.yml 1128 | kubectl apply -f secrets.prod.remote.yml 1129 | ---- 1130 | 1131 | ==== Cronjobs 1132 | 1133 | Push the Alpine/cUrl image to GCR: 1134 | 1135 | .In the Cloud shell: 1136 | [source,bash,subs=attributes+] 1137 | ---- 1138 | cd {PROJECT} 1139 | docker build -f conf/docker/Dockerfile.cronjob -t k8s-cronjob:current . 1140 | docker tag k8s-cronjob:current gcr.io/{GCP_PROJECT}/k8s-cronjob:current 1141 | docker push gcr.io/{GCP_PROJECT}/k8s-cronjob:current 1142 | ---- 1143 | 1144 | ==== Services 1145 | 1146 | Deploy services manifests: 1147 | 1148 | .In the Cloud shell: 1149 | [source,bash,subs=attributes+] 1150 | ---- 1151 | cd {PROJECT} 1152 | # Switch to GKE context 1153 | kubectl config use-context gke_{GCP_PROJECT}_{GCP_REGION}_{GKE_CLUSTER} 1154 | # Deploy services (OAT on test.) 1155 | php vendor/bin/dep --file=conf/deployer/deploy.php \ 1156 | --hosts=remote gen-service -o APP_ENV=oat 1157 | kubectl apply -f build/service.yml 1158 | kubectl apply -f build/cronjob.remote.yml 1159 | # Deploy services (Prod on www.) 1160 | php vendor/bin/dep --file=conf/deployer/deploy.php \ 1161 | --hosts=remote gen-service -o APP_ENV=prod 1162 | kubectl apply -f build/service.yml 1163 | kubectl apply -f build/cronjob.remote.yml 1164 | ---- 1165 | 1166 | ==== Releases 1167 | 1168 | The release process is as follows: 1169 | 1170 | . Git tag a release version 1171 | . Push the tag to Github 1172 | . In the Cloud Shell git pull 1173 | . Execute the Deployer script; this: 1174 | .. Builds a new container with a Docker tag identical to the git tag 1175 | .. Applies an updated deployment manifest with the new Docker tag 1176 | 1177 | Commands: 1178 | 1179 | .In the local shell: 1180 | [source,bash,subs=attributes+] 1181 | ---- 1182 | # tag a commit locally 1183 | git tag 0.4 1184 | # push tags 1185 | git push origin --tags 1186 | ---- 1187 | 1188 | .In the Cloud shell: 1189 | [source,bash,subs=attributes+] 1190 | ---- 1191 | # Deploy new version - Test 1192 | cd {PROJECT} 1193 | php vendor/bin/dep --file=conf/deployer/deploy.php --hosts=remote deploy-remote \ 1194 | -o APP_ENV=dev -o TAG=0.4 1195 | # Deploy new version - Prod 1196 | cd {PROJECT} 1197 | php vendor/bin/dep --file=conf/deployer/deploy.php --hosts=remote deploy-remote \ 1198 | -o APP_ENV=master -o TAG=0.4 1199 | ---- 1200 | 1201 | == References 1202 | 1203 | Some random resources that I found useful. Not all apply to this recipe. 1204 | 1205 | === Docker 1206 | 1207 | http://docs.blowb.org/index.html[The Blowb Project - Deploy Integrated Apps Using Docker] 1208 | (Not used in this article but looks like it has many good ideas) 1209 | 1210 | === Kubernetes 1211 | 1212 | https://codeburst.io/getting-started-with-kubernetes-deploy-a-docker-container-with-kubernetes-in-5-minutes-eb4be0e96370 1213 | 1214 | https://www.mirantis.com/blog/introduction-to-yaml-creating-a-kubernetes-deployment/ 1215 | 1216 | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#container-v1-core 1217 | 1218 | https://vsupalov.com/yaml-kubernetes-examples-docs-spec/ 1219 | 1220 | https://dzone.com/articles/kubernetes-cron-jobs 1221 | 1222 | https://stackoverflow.com/questions/14155596/how-to-substitute-shell-variables-in-complex-text-files 1223 | 1224 | https://dzone.com/articles/how-i-switched-my-blog-from-ovh-to-google-containe 1225 | 1226 | https://gravitational.com/blog/troubleshooting-kubernetes-networking 1227 | 1228 | https://estl.tech/configuring-https-to-a-web-service-on-google-kubernetes-engine-2d71849520d 1229 | 1230 | https://blog.container-solutions.com/kubernetes-deployment-strategies 1231 | 1232 | https://medium.com/google-cloud/kubernetes-best-practices-8d5cd03446e2 1233 | 1234 | https://stackoverflow.com/questions/22944631/how-to-get-the-ip-address-of-the-docker-host-from-inside-a-docker-container 1235 | 1236 | === GKE 1237 | 1238 | https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity 1239 | 1240 | https://cloud.google.com/solutions/using-gcp-services-from-gke 1241 | 1242 | https://medium.com/redpoint/cost-effective-kubernetes-on-google-cloud-61067185ebe8 1243 | 1244 | https://rominirani.com/google-cloud-platform-factors-to-control-your-costs-5a256ed207f1?gi=e90cc7e943c8 1245 | 1246 | https://medium.com/google-cloud/kubernetes-day-one-30a80b5dcb29 1247 | 1248 | === Symfony 1249 | 1250 | https://medium.com/@galopintitouan/how-to-build-a-scalable-symfony-application-on-kubernetes-30f23bf304e 1251 | 1252 | https://titouangalopin.com/introducing-the-official-flysystem-bundle/ 1253 | 1254 | https://itnext.io/scaling-your-symfony-application-and-preparing-it-for-deployment-on-kubernetes-c102bf246a93 1255 | 1256 | https://medium.com/@joeymasip/how-to-create-an-api-with-symfony-4-and-jwt-b2334a8fbec2 1257 | 1258 | https://www.jakelitwicki.com/2015/05/26/a-standard-gitignore-for-symfony-applications/ 1259 | 1260 | === Mailer 1261 | 1262 | https://backbeat.tech/blog/sending-emails-with-symfony/ 1263 | 1264 | https://symfony.com/doc/4.4/mailer.html 1265 | 1266 | https://github.com/cmaessen/docker-php-sendmail/blob/master/Dockerfile 1267 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | }, 4 | "require": { 5 | "symfony/dotenv": "^4.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "f7a5b0c01b4bcbe9cbb0ae3b47394e5a", 8 | "packages": [ 9 | { 10 | "name": "symfony/dotenv", 11 | "version": "v4.4.18", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/symfony/dotenv.git", 15 | "reference": "2befc49ec50b4d6ffd100b332389260c9069ba1c" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/symfony/dotenv/zipball/2befc49ec50b4d6ffd100b332389260c9069ba1c", 20 | "reference": "2befc49ec50b4d6ffd100b332389260c9069ba1c", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.1.3" 25 | }, 26 | "require-dev": { 27 | "symfony/process": "^3.4.2|^4.0|^5.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-4": { 32 | "Symfony\\Component\\Dotenv\\": "" 33 | }, 34 | "exclude-from-classmap": [ 35 | "/Tests/" 36 | ] 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "MIT" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Fabien Potencier", 45 | "email": "fabien@symfony.com" 46 | }, 47 | { 48 | "name": "Symfony Community", 49 | "homepage": "https://symfony.com/contributors" 50 | } 51 | ], 52 | "description": "Registers environment variables from a .env file", 53 | "homepage": "https://symfony.com", 54 | "keywords": [ 55 | "dotenv", 56 | "env", 57 | "environment" 58 | ], 59 | "support": { 60 | "source": "https://github.com/symfony/dotenv/tree/v4.4.18" 61 | }, 62 | "funding": [ 63 | { 64 | "url": "https://symfony.com/sponsor", 65 | "type": "custom" 66 | }, 67 | { 68 | "url": "https://github.com/fabpot", 69 | "type": "github" 70 | }, 71 | { 72 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 73 | "type": "tidelift" 74 | } 75 | ], 76 | "time": "2020-12-08T16:59:59+00:00" 77 | } 78 | ], 79 | "packages-dev": [], 80 | "aliases": [], 81 | "minimum-stability": "stable", 82 | "stability-flags": [], 83 | "prefer-stable": false, 84 | "prefer-lowest": false, 85 | "platform": [], 86 | "platform-dev": [], 87 | "plugin-api-version": "2.0.0" 88 | } 89 | -------------------------------------------------------------------------------- /conf/deployer/deploy.php: -------------------------------------------------------------------------------- 1 | set('HOST_ENV', 'local') 60 | ->set('CACHE_FROM_WEB', function () { 61 | return ''; 62 | }) 63 | ->set('CACHE_FROM_WEBINIT', function () { 64 | return ''; 65 | }) 66 | ; 67 | 68 | /** 69 | * Set .env file, load into environment variables 70 | */ 71 | task( 72 | 'load-env', function () { 73 | set('ENV_FILE', parse("{{ ROOT_DIR }}/conf/env/.env.{{ APP_ENV }}.{{ HOST_ENV }}")); 74 | (new Dotenv())->load(get('ENV_FILE')); 75 | array_map( 76 | function ($var) { 77 | set($var, getenv($var)); 78 | }, 79 | explode(',', $_SERVER['SYMFONY_DOTENV_VARS']) 80 | ); 81 | } 82 | ); 83 | 84 | /** 85 | * Generate service/ cronjob manifests (usually done only once) 86 | */ 87 | task( 88 | 'gen-service', [ 89 | // Set .env file, load into environment variables 90 | 'load-env', 91 | // Build service manifests 92 | 'service-manifest', 93 | ] 94 | ); 95 | 96 | /** 97 | * Build Kubernetes service manifests 98 | */ 99 | task( 100 | 'service-manifest', function () { 101 | // Create build output folder 102 | runLocally("rm -rf build && mkdir build"); 103 | // Service manifests 104 | $conf_file = parse("{{ ROOT_DIR }}/conf/k8s/service.tpl.yml"); 105 | $build_file = parse("{{ ROOT_DIR }}/build/service.yml"); 106 | parse_into($conf_file, $build_file); 107 | // Cronjob manifests 108 | $conf_file = parse("{{ ROOT_DIR }}/conf/k8s/cronjob.{{ HOST_ENV }}.tpl.yml"); 109 | $build_file = parse("{{ ROOT_DIR }}/build/cronjob.{{ HOST_ENV }}.yml"); 110 | parse_into($conf_file, $build_file); 111 | } 112 | ); 113 | 114 | /** 115 | * Deploy containers on localhost 116 | */ 117 | task( 118 | 'deploy-local', [ 119 | // Set .env file, load into environment variables 120 | 'load-env', 121 | // Set local docker context to Minikube 122 | 'local:set-docker-context', 123 | // Build docker context 124 | 'docker-build-context', 125 | // Add Symfony local working directory to docker build context 126 | 'local:add-sf-dir', 127 | // Build docker web image 128 | 'docker-build-web-image', 129 | // Apply Kubernetes deployment 130 | 'k8s-deployment', 131 | // Set local docker back to local host 132 | 'local:unset-docker-context', 133 | ] 134 | ); 135 | 136 | /** 137 | * Build docker web image 138 | */ 139 | task( 140 | 'docker-build-web-image', function () { 141 | cd("{{ ROOT_DIR }}/build"); 142 | run( 143 | " 144 | docker build -f Dockerfile.web.{{ HOST_ENV }} \ 145 | -t {{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-web:{{ TAG }} {{ CACHE_FROM_WEB }} \ 146 | --build-arg APP_ENV={{ APP_ENV }} \ 147 | --build-arg ROUTER_REQUEST_CONTEXT_HOST={{ ROUTER_REQUEST_CONTEXT_HOST }} \ 148 | --build-arg PHP_DISPLAY_ERRORS={{ PHP_DISPLAY_ERRORS }} \ 149 | --build-arg PHP_ERROR_REPORTING=\"{{ PHP_ERROR_REPORTING }}\" \ 150 | --build-arg OPCACHE_ENABLE={{ OPCACHE_ENABLE }} \ 151 | --build-arg OPCACHE_PRELOAD={{ OPCACHE_PRELOAD }} \ 152 | --build-arg SMTP_ACCOUNT={{ SMTP_ACCOUNT }} \ 153 | . 154 | ", ['timeout' => null, 'tty' => true] 155 | ); 156 | } 157 | ); 158 | 159 | /** 160 | * Build docker web init image (remote) 161 | */ 162 | task( 163 | 'remote:docker-build-webinit-image', function () { 164 | cd("{{ ROOT_DIR }}/build"); 165 | run( 166 | " 167 | docker build -f Dockerfile.webinit \ 168 | -t {{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-webinit:{{ TAG }} {{ CACHE_FROM_WEBINIT }} \ 169 | --build-arg APP_ENV={{ APP_ENV }} \ 170 | --build-arg HOST_ENV={{ HOST_ENV }} \ 171 | --build-arg TAG={{ TAG }} \ 172 | --build-arg GITHUB_SSH_KEY=\"{{ GITHUB_SSH_KEY }}\" \ 173 | --build-arg GITHUB_ACCOUNT=\"{{ GITHUB_ACCOUNT }}\" \ 174 | --build-arg GITHUB_REPO=\"{{ GITHUB_REPO }}\" \ 175 | . 176 | ", ['timeout' => null, 'tty' => true] 177 | ); 178 | } 179 | ); 180 | 181 | /** 182 | * Set docker local context to Minikube 183 | */ 184 | task( 185 | 'local:set-docker-context', function () { 186 | runLocally( 187 | " 188 | eval $(minikube docker-env) 189 | ", ['timeout' => null, 'tty' => true] 190 | ); 191 | } 192 | ); 193 | 194 | /** 195 | * Set docker local context back to local host 196 | */ 197 | task( 198 | 'local:unset-docker-context', function () { 199 | runLocally( 200 | " 201 | eval $(minikube docker-env -u) 202 | ", ['timeout' => null, 'tty' => true] 203 | ); 204 | } 205 | ); 206 | 207 | /** 208 | * Add Symfony local working directory to docker build context 209 | */ 210 | task( 211 | 'local:add-sf-dir', function () { 212 | cd("{{ ROOT_DIR }}"); 213 | // Copy ddocker context files 214 | runLocally( 215 | " 216 | cp {{ ENV_FILE }} sf/.env \ 217 | && tar -czf build/sf.tar.gz -C . sf 218 | " 219 | ); 220 | } 221 | ); 222 | 223 | /** 224 | * Apply Kubernetes deployment 225 | */ 226 | task( 227 | 'k8s-deployment', function () { 228 | cd("{{ ROOT_DIR }}"); 229 | run( 230 | " 231 | kubectl apply -f build/deployment.yml 232 | ", ['timeout' => null, 'tty' => true] 233 | ); 234 | } 235 | ); 236 | 237 | 238 | /** 239 | * ------------------------------ 240 | * Build and deploy services on Google Cloud Shell 241 | * ------------------------------ 242 | */ 243 | host('remote') 244 | ->set('HOST_ENV', 'remote') 245 | ->set('GITHUB_SSH_KEY', file_get_contents('/home/'.get('CLOUD_SHELL_USER').'/.ssh/id_rsa')) 246 | ->set('CACHE_FROM_WEB', function () { 247 | if (get('LAST')) { 248 | // --cache-from {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-web:{{ TAG }} 249 | return sprintf( 250 | '--cache-from %s-docker.pkg.dev/%s/%s-%s-web:%s', 251 | get('GCP_REGION'), 252 | get('GCP_PROJECT_ID'), 253 | get('PROJECT'), 254 | get('COMPOSE_PROJECT_NAME'), 255 | get('APP_ENV'), 256 | get('LAST') 257 | ); 258 | } else { 259 | return ''; 260 | } 261 | }) 262 | ->set('CACHE_FROM_WEBINIT', function () { 263 | if (get('LAST')) { 264 | // --cache-from {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-webinit:{{ TAG }} 265 | return sprintf( 266 | '--cache-from %s-docker.pkg.dev/%s/%s-%s-webinit:%s', 267 | get('GCP_REGION'), 268 | get('GCP_PROJECT_ID'), 269 | get('PROJECT'), 270 | get('COMPOSE_PROJECT_NAME'), 271 | get('APP_ENV'), 272 | get('LAST') 273 | ); 274 | } else { 275 | return ''; 276 | } 277 | }) 278 | ; 279 | 280 | /** 281 | * Deploy services on GKE 282 | */ 283 | task( 284 | 'deploy-remote', [ 285 | // Set .env file, load into environment variables 286 | 'load-env', 287 | // Build docker resources locally 288 | 'docker-build-context', 289 | // Build docker web image 290 | 'docker-build-web-image', 291 | // Build docker web init image 292 | 'remote:docker-build-webinit-image', 293 | // Push docker image to repository 294 | 'remote:push-image', 295 | // Apply Kubernetes deployment 296 | 'k8s-deployment', 297 | ] 298 | ); 299 | 300 | /** 301 | * Tag local image for the Artifact Registry, 302 | * Push images to Artifact Registry 303 | */ 304 | task( 305 | 'remote:push-image', function () { 306 | run( 307 | " 308 | docker tag {{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-webinit:{{ TAG }} \ 309 | {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-webinit:{{ TAG }} 310 | ", ['timeout' => null, 'tty' => true] 311 | ); 312 | run( 313 | " 314 | docker push {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-webinit:{{ TAG }} 315 | ", ['timeout' => null, 'tty' => true] 316 | ); 317 | run( 318 | " 319 | docker tag {{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-web:{{ TAG }} \ 320 | {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-web:{{ TAG }} 321 | ", ['timeout' => null, 'tty' => true] 322 | ); 323 | run( 324 | " 325 | docker push {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ COMPOSE_PROJECT_NAME }}-{{ APP_ENV }}-web:{{ TAG }} 326 | ", ['timeout' => null, 'tty' => true] 327 | ); 328 | } 329 | ); 330 | 331 | /** 332 | * Create Docker build context 333 | */ 334 | task( 335 | 'docker-build-context', function () { 336 | cd("{{ ROOT_DIR }}"); 337 | // Copy ddocker context files 338 | runLocally( 339 | " 340 | rm -rf build \ 341 | && mkdir -p build/infra \ 342 | && cp -r conf/infra/* build/infra \ 343 | && cp -r conf/docker/files build \ 344 | && cp conf/docker/Dockerfile.* build \ 345 | && cp {{ ENV_FILE }} build/.env 346 | " 347 | ); 348 | // Copy the deployment manifest 349 | $conf_file = parse("{{ ROOT_DIR }}/conf/k8s/deployment.{{ HOST_ENV }}.tpl.yml"); 350 | $build_file = parse("{{ ROOT_DIR }}/build/deployment.yml"); 351 | parse_into($conf_file, $build_file); 352 | } 353 | ); 354 | 355 | /** 356 | * Helper function - Parse a source file into a destination file 357 | * 358 | * @param string $src_filepath Source filepath 359 | * @param string $dest_filepath Destination filepath 360 | * 361 | * @return void 362 | */ 363 | function parse_into($src_filepath, $dest_filepath): void 364 | { 365 | $content = file_get_contents($src_filepath); 366 | $content = parse($content); 367 | $fh = fopen($dest_filepath, 'w'); 368 | fwrite($fh, $content); 369 | fclose($fh); 370 | } 371 | -------------------------------------------------------------------------------- /conf/deployer/hosts.yml: -------------------------------------------------------------------------------- 1 | localhost: 2 | local: true 3 | 4 | remote: 5 | local: true 6 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.cronjob: -------------------------------------------------------------------------------- 1 | FROM alpine:3.12 2 | 3 | RUN set -x \ 4 | && apk update \ 5 | && apk upgrade \ 6 | && apk add --no-cache curl 7 | 8 | ENTRYPOINT [ "/bin/sh", "-c" ] 9 | 10 | CMD [ "/usr/bin/curl -vvv -X POST -d '' ${URL}" ] 11 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.phpcs: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli 2 | 3 | LABEL maintainer= 4 | 5 | ENV PHPCS_VERSION=3.5.8 6 | 7 | RUN apt-get update \ 8 | && apt-get install -y \ 9 | curl \ 10 | git \ 11 | # zip 12 | && apt-get install -y \ 13 | libzip-dev \ 14 | zip \ 15 | && docker-php-ext-install zip \ 16 | # Clean up 17 | && apt-get clean && \ 18 | rm -rf /var/lib/lists/* 19 | 20 | RUN curl -L https://github.com/squizlabs/PHP_CodeSniffer/releases/download/$PHPCS_VERSION/phpcs.phar > /usr/local/bin/phpcs \ 21 | && chmod +x /usr/local/bin/phpcs 22 | 23 | WORKDIR "/project" 24 | 25 | ENTRYPOINT ["phpcs"] 26 | CMD ["--version"] 27 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.phpunit: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli 2 | LABEL maintainer= 3 | 4 | # Install PHP modules 5 | RUN apt-get update \ 6 | # generic libraries 7 | && apt-get install -y \ 8 | curl \ 9 | libonig-dev \ 10 | libssl-dev \ 11 | libxml2-dev \ 12 | libyaml-dev \ 13 | nano \ 14 | # PHP modules 15 | && docker-php-ext-install \ 16 | exif \ 17 | gettext \ 18 | intl \ 19 | mbstring \ 20 | pdo_mysql \ 21 | # APCu 22 | && echo '' | pecl install apcu-5.1.18 \ 23 | && docker-php-ext-enable apcu \ 24 | && echo "extension=apcu.so" > /usr/local/etc/php/php.ini \ 25 | # gd 26 | && apt-get install -y \ 27 | libfreetype6-dev \ 28 | libjpeg62-turbo-dev \ 29 | libpng-dev \ 30 | && docker-php-ext-configure gd \ 31 | --with-freetype \ 32 | --with-jpeg \ 33 | && docker-php-ext-install -j$(nproc) gd \ 34 | # HTMLdoc 35 | && apt-get install -y htmldoc \ 36 | # imagemagick 37 | && apt-get install -y --force-yes \ 38 | libmagickwand-dev --no-install-recommends \ 39 | # Inkscape is needed if you want to read SVG files created by Inkscape 40 | inkscape \ 41 | && pecl install imagick-3.4.4 \ 42 | && docker-php-ext-enable imagick \ 43 | # imap 44 | && apt-get install -y libc-client-dev libkrb5-dev \ 45 | && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ 46 | # xslt 47 | && apt-get install -y libxslt-dev \ 48 | && docker-php-ext-install xsl \ 49 | # YAML extension 50 | && pecl install yaml-2.0.4 && echo "extension=yaml.so" > /usr/local/etc/php/conf.d/ext-yaml.ini \ 51 | # zip 52 | && apt-get install -y \ 53 | libzip-dev \ 54 | zip \ 55 | && docker-php-ext-install zip \ 56 | # Purge apt 57 | && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y \ 58 | && rm -rf /var/lib/apt/lists/* 59 | 60 | # Geolite2 data file 61 | COPY conf/docker/files/geolite2/GeoLite2-Country.mmdb /usr/local/share/geolite2/GeoLite2-Country.mmdb 62 | 63 | # Install Composer 64 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 65 | 66 | WORKDIR "/var/www/sf" 67 | ENTRYPOINT ["./bin/phpunit"] 68 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.web.local: -------------------------------------------------------------------------------- 1 | FROM php:7.4-apache 2 | LABEL maintainer= 3 | 4 | # Install PHP modules 5 | RUN apt-get update \ 6 | # generic libraries 7 | && apt-get install -y \ 8 | curl \ 9 | libonig-dev \ 10 | libssl-dev \ 11 | libxml2-dev \ 12 | libyaml-dev \ 13 | nano \ 14 | # PHP modules 15 | && docker-php-ext-configure pcntl --enable-pcntl \ 16 | && docker-php-ext-install \ 17 | exif \ 18 | gettext \ 19 | intl \ 20 | mbstring \ 21 | pcntl \ 22 | pdo_mysql \ 23 | # APCu 24 | && echo '' | pecl install apcu-5.1.18 \ 25 | && docker-php-ext-enable apcu \ 26 | && echo "extension=apcu.so" > /usr/local/etc/php/php.ini \ 27 | # gd 28 | && apt-get install -y \ 29 | libfreetype6-dev \ 30 | libjpeg62-turbo-dev \ 31 | libpng-dev \ 32 | && docker-php-ext-configure gd \ 33 | --with-freetype \ 34 | --with-jpeg \ 35 | && docker-php-ext-install -j$(nproc) gd \ 36 | # HTMLdoc 37 | && apt-get install -y htmldoc \ 38 | # imagemagick 39 | && apt-get install -y --force-yes \ 40 | libmagickwand-dev --no-install-recommends \ 41 | # Inkscape is needed if you want to read SVG files created by Inkscape 42 | inkscape \ 43 | && pecl install imagick-3.4.4 \ 44 | && docker-php-ext-enable imagick \ 45 | # imap 46 | && apt-get install -y libc-client-dev libkrb5-dev \ 47 | && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ 48 | # xslt 49 | && apt-get install -y libxslt-dev \ 50 | && docker-php-ext-install xsl \ 51 | # YAML extension 52 | && pecl install yaml-2.0.4 && echo "extension=yaml.so" > /usr/local/etc/php/conf.d/ext-yaml.ini \ 53 | # zip 54 | && apt-get install -y \ 55 | libzip-dev \ 56 | zip \ 57 | && docker-php-ext-install zip \ 58 | # Purge apt 59 | && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y \ 60 | && rm -rf /var/lib/apt/lists/* 61 | 62 | # Opcache 63 | RUN docker-php-ext-install opcache 64 | 65 | RUN ln -snf /usr/share/zoneinfo/NZ /etc/localtime 66 | 67 | # Dockerize 68 | RUN curl -sfL https://github.com/powerman/dockerize/releases/download/v0.10.0/dockerize-`uname -s`-`uname -m` \ 69 | | install /dev/stdin /usr/local/bin/dockerize 70 | 71 | # PHP CLI configuration 72 | COPY infra/php.ini.tpl /usr/local/etc/php/php.ini.tpl 73 | 74 | # Geolite2 data file 75 | COPY files/geolite2/GeoLite2-Country.mmdb /usr/local/share/geolite2/GeoLite2-Country.mmdb 76 | 77 | # Install Apache modules 78 | ENV APACHE_RUN_USER www-data 79 | ENV APACHE_RUN_GROUP www-data 80 | ENV APACHE_LOG_DIR /var/log/apache2 81 | ENV APACHE_PID_FILE /var/run/apache2/apache2.pid 82 | ENV APACHE_RUN_DIR /var/run/apache2 83 | ENV APACHE_LOCK_DIR /var/lock/apache2 84 | ENV APACHE_LOG_DIR /var/log/apache2 85 | RUN a2enmod rewrite \ 86 | && a2enmod ssl \ 87 | && a2dissite 000-default.conf \ 88 | && chown -R www-data:www-data /var/www \ 89 | && mkdir -p $APACHE_RUN_DIR \ 90 | && mkdir -p $APACHE_LOCK_DIR \ 91 | && mkdir -p $APACHE_LOG_DIR 92 | 93 | # Apache configuration (project-dependent) 94 | COPY infra/php.ini.tpl /etc/php/7.4/apache2/php.ini.tpl 95 | COPY infra/virtual-host.conf.tpl /etc/apache2/sites-enabled/virtual-host.conf.tpl 96 | 97 | # Sendmail installation 98 | RUN apt-get update \ 99 | && apt-get install -y \ 100 | msmtp msmtp-mta \ 101 | # Purge apt 102 | && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y \ 103 | && rm -rf /var/lib/apt/lists/* 104 | 105 | # MSMTP configuration (project-dependent) 106 | COPY infra/msmtprc.tpl /etc/msmtprc.tpl 107 | COPY infra/msmtp.logrotate /etc/logrotate.d/msmtp 108 | 109 | # Copy Symfony application files from local context 110 | ADD sf.tar.gz /var/www/ 111 | 112 | # Arguments to build configuration files with dockerize 113 | ARG ROUTER_REQUEST_CONTEXT_HOST 114 | ENV ROUTER_REQUEST_CONTEXT_HOST $ROUTER_REQUEST_CONTEXT_HOST 115 | ARG PHP_DISPLAY_ERRORS 116 | ENV PHP_DISPLAY_ERRORS $PHP_DISPLAY_ERRORS 117 | ARG PHP_ERROR_REPORTING 118 | ENV PHP_ERROR_REPORTING $PHP_ERROR_REPORTING 119 | ARG OPCACHE_ENABLE 120 | ENV OPCACHE_ENABLE $OPCACHE_ENABLE 121 | ARG OPCACHE_PRELOAD 122 | ENV OPCACHE_PRELOAD $OPCACHE_PRELOAD 123 | ARG SMTP_ACCOUNT 124 | ENV SMTP_ACCOUNT $SMTP_ACCOUNT 125 | ARG APP_ENV 126 | ENV APP_ENV $APP_ENV 127 | 128 | # To allow SF to write to var/cache and log on mounted volumes 129 | RUN usermod -u 1000 www-data 130 | 131 | # Preload Symfony cache for PHP opcache 132 | RUN mkdir -p /var/www/cache \ 133 | && chown -R www-data:www-data /var/www/cache \ 134 | && mkdir -p /var/www/log \ 135 | && chown -R www-data:www-data /var/www/log \ 136 | && env DB_PASSWORD="dummy" \ 137 | API_KEY="dummy" \ 138 | APP_SECRET="dummy" \ 139 | MAILER_PASSWORD="dummy" \ 140 | /var/www/sf/bin/console cache:warmup --env=${APP_ENV} 141 | 142 | # Add Tini 143 | ENV TINI_VERSION v0.19.0 144 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 145 | RUN chmod +x /tini 146 | ENTRYPOINT ["/tini", "--", "docker-php-entrypoint"] 147 | 148 | #CMD ["apachectl", "-D", "FOREGROUND"] 149 | CMD ["dockerize", \ 150 | "-template", "/usr/local/etc/php/php.ini.tpl:/usr/local/etc/php/php.ini", \ 151 | "-template", "/etc/php/7.4/apache2/php.ini.tpl:/etc/php/7.4/apache2/php.ini", \ 152 | "-template", "/etc/apache2/sites-enabled/virtual-host.conf.tpl:/etc/apache2/sites-enabled/virtual-host.conf", \ 153 | "-template", "/etc/msmtprc.tpl:/etc/msmtprc", \ 154 | "apachectl", "-D", "FOREGROUND"] 155 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.web.remote: -------------------------------------------------------------------------------- 1 | FROM php:7.4-apache 2 | LABEL maintainer= 3 | 4 | # Install PHP modules 5 | RUN apt-get update \ 6 | # generic libraries 7 | && apt-get install -y \ 8 | curl \ 9 | libonig-dev \ 10 | libssl-dev \ 11 | libxml2-dev \ 12 | libyaml-dev \ 13 | nano \ 14 | # PHP modules 15 | && docker-php-ext-configure pcntl --enable-pcntl \ 16 | && docker-php-ext-install \ 17 | exif \ 18 | gettext \ 19 | intl \ 20 | mbstring \ 21 | pcntl \ 22 | pdo_mysql \ 23 | # APCu 24 | && echo '' | pecl install apcu-5.1.18 \ 25 | && docker-php-ext-enable apcu \ 26 | && echo "extension=apcu.so" > /usr/local/etc/php/php.ini \ 27 | # gd 28 | && apt-get install -y \ 29 | libfreetype6-dev \ 30 | libjpeg62-turbo-dev \ 31 | libpng-dev \ 32 | && docker-php-ext-configure gd \ 33 | --with-freetype \ 34 | --with-jpeg \ 35 | && docker-php-ext-install -j$(nproc) gd \ 36 | # HTMLdoc 37 | && apt-get install -y htmldoc \ 38 | # imagemagick 39 | && apt-get install -y --force-yes \ 40 | libmagickwand-dev --no-install-recommends \ 41 | # Inkscape is needed if you want to read SVG files created by Inkscape 42 | inkscape \ 43 | && pecl install imagick-3.4.4 \ 44 | && docker-php-ext-enable imagick \ 45 | # imap 46 | && apt-get install -y libc-client-dev libkrb5-dev \ 47 | && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ 48 | # xslt 49 | && apt-get install -y libxslt-dev \ 50 | && docker-php-ext-install xsl \ 51 | # YAML extension 52 | && pecl install yaml-2.0.4 && echo "extension=yaml.so" > /usr/local/etc/php/conf.d/ext-yaml.ini \ 53 | # zip 54 | && apt-get install -y \ 55 | libzip-dev \ 56 | zip \ 57 | && docker-php-ext-install zip \ 58 | # Purge apt 59 | && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y \ 60 | && rm -rf /var/lib/apt/lists/* 61 | 62 | # Opcache 63 | RUN docker-php-ext-install opcache 64 | 65 | RUN ln -snf /usr/share/zoneinfo/NZ /etc/localtime 66 | 67 | # Dockerize 68 | RUN curl -sfL https://github.com/powerman/dockerize/releases/download/v0.10.0/dockerize-`uname -s`-`uname -m` \ 69 | | install /dev/stdin /usr/local/bin/dockerize 70 | 71 | # PHP CLI configuration 72 | COPY infra/php.ini.tpl /usr/local/etc/php/php.ini.tpl 73 | 74 | # Geolite2 data file 75 | COPY files/geolite2/GeoLite2-Country.mmdb /usr/local/share/geolite2/GeoLite2-Country.mmdb 76 | 77 | # Install Apache modules 78 | ENV APACHE_RUN_USER www-data 79 | ENV APACHE_RUN_GROUP www-data 80 | ENV APACHE_LOG_DIR /var/log/apache2 81 | ENV APACHE_PID_FILE /var/run/apache2/apache2.pid 82 | ENV APACHE_RUN_DIR /var/run/apache2 83 | ENV APACHE_LOCK_DIR /var/lock/apache2 84 | ENV APACHE_LOG_DIR /var/log/apache2 85 | RUN a2enmod rewrite \ 86 | && a2enmod ssl \ 87 | && a2dissite 000-default.conf \ 88 | && chown -R www-data:www-data /var/www \ 89 | && mkdir -p $APACHE_RUN_DIR \ 90 | && mkdir -p $APACHE_LOCK_DIR \ 91 | && mkdir -p $APACHE_LOG_DIR 92 | 93 | # Apache configuration (project-dependent) 94 | COPY infra/php.ini.tpl /etc/php/7.4/apache2/php.ini.tpl 95 | COPY infra/virtual-host.conf.tpl /etc/apache2/sites-enabled/virtual-host.conf.tpl 96 | 97 | # Sendmail installation 98 | RUN apt-get update \ 99 | && apt-get install -y \ 100 | msmtp msmtp-mta \ 101 | # Purge apt 102 | && apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y \ 103 | && rm -rf /var/lib/apt/lists/* 104 | 105 | # MSMTP configuration (project-dependent) 106 | COPY infra/msmtprc.tpl /etc/msmtprc.tpl 107 | COPY infra/msmtp.logrotate /etc/logrotate.d/msmtp 108 | 109 | # Arguments to build configuration files with dockerize 110 | ARG ROUTER_REQUEST_CONTEXT_HOST 111 | ENV ROUTER_REQUEST_CONTEXT_HOST $ROUTER_REQUEST_CONTEXT_HOST 112 | ARG PHP_DISPLAY_ERRORS 113 | ENV PHP_DISPLAY_ERRORS $PHP_DISPLAY_ERRORS 114 | ARG PHP_ERROR_REPORTING 115 | ENV PHP_ERROR_REPORTING $PHP_ERROR_REPORTING 116 | ARG OPCACHE_ENABLE 117 | ENV OPCACHE_ENABLE $OPCACHE_ENABLE 118 | ARG OPCACHE_PRELOAD 119 | ENV OPCACHE_PRELOAD $OPCACHE_PRELOAD 120 | ARG SMTP_ACCOUNT 121 | ENV SMTP_ACCOUNT $SMTP_ACCOUNT 122 | ARG APP_ENV 123 | ENV APP_ENV $APP_ENV 124 | 125 | # To allow SF to write to var/cache and log on mounted volumes 126 | RUN usermod -u 1000 www-data 127 | 128 | # Mount points for Symfony application files from build stage 129 | RUN mkdir -p /var/www/cache \ 130 | && chown -R www-data:www-data /var/www/cache \ 131 | && mkdir -p /var/www/log \ 132 | && chown -R www-data:www-data /var/www/log \ 133 | && mkdir -p /var/www/sf \ 134 | && chown -R www-data:www-data /var/www/sf 135 | 136 | # Add Tini 137 | ENV TINI_VERSION v0.19.0 138 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 139 | RUN chmod +x /tini 140 | ENTRYPOINT ["/tini", "--", "docker-php-entrypoint"] 141 | 142 | #CMD ["apachectl", "-D", "FOREGROUND"] 143 | CMD ["dockerize", \ 144 | "-template", "/usr/local/etc/php/php.ini.tpl:/usr/local/etc/php/php.ini", \ 145 | "-template", "/etc/php/7.4/apache2/php.ini.tpl:/etc/php/7.4/apache2/php.ini", \ 146 | "-template", "/etc/apache2/sites-enabled/virtual-host.conf.tpl:/etc/apache2/sites-enabled/virtual-host.conf", \ 147 | "-template", "/etc/msmtprc.tpl:/etc/msmtprc", \ 148 | "apachectl", "-D", "FOREGROUND"] 149 | -------------------------------------------------------------------------------- /conf/docker/Dockerfile.webinit: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | # To avoid interactive install for git 4 | ARG DEBIAN_FRONTEND=noninteractive 5 | # Git credentials 6 | ARG GITHUB_SSH_KEY 7 | ENV GITHUB_SSH_KEY $GITHUB_SSH_KEY 8 | ARG GITHUB_ACCOUNT 9 | ENV GITHUB_ACCOUNT $GITHUB_ACCOUNT 10 | ARG GITHUB_REPO 11 | ENV GITHUB_REPO $GITHUB_REPO 12 | # Application build parameters 13 | ARG TAG 14 | ENV TAG $TAG 15 | ARG APP_ENV 16 | ENV APP_ENV $APP_ENV 17 | ARG HOST_ENV 18 | ENV HOST_ENV $HOST_ENV 19 | 20 | # Build tools 21 | RUN apt-get update \ 22 | && apt-get install -y \ 23 | curl \ 24 | git \ 25 | libssl-dev \ 26 | libxml2-dev \ 27 | nano \ 28 | php-apcu \ 29 | php-cli \ 30 | php-curl \ 31 | php-mbstring \ 32 | php-xml \ 33 | php-zip \ 34 | unzip \ 35 | wget 36 | 37 | WORKDIR /root 38 | 39 | # Clone the Github repo 40 | COPY infra/ssh_config .ssh/config 41 | RUN chmod 600 .ssh/config 42 | RUN echo "${GITHUB_SSH_KEY}" > .ssh/id_rsa 43 | RUN chmod 400 .ssh/id_rsa 44 | RUN git clone git@github.com:${GITHUB_ACCOUNT}/${GITHUB_REPO}.git 45 | 46 | # Fetch the version tag to build 47 | WORKDIR /root/${GITHUB_REPO} 48 | RUN git fetch --all --tags --prune \ 49 | && git checkout tags/${TAG} 50 | 51 | # Copy Symfony application files 52 | RUN mkdir -p /var/www/sf 53 | WORKDIR /root/${GITHUB_REPO}/sf 54 | RUN cp -r -t /var/www/sf \ 55 | bin \ 56 | config \ 57 | public \ 58 | src \ 59 | templates \ 60 | translations \ 61 | composer.json \ 62 | composer.lock \ 63 | symfony.lock 64 | COPY .env /var/www/sf/.env 65 | 66 | # Campaign email template workaround - TECHNICAL DEBT 67 | RUN mkdir -p /var/www/sf/templates/email/campaign 68 | 69 | # Composer update 70 | WORKDIR /root 71 | RUN wget -O composer-setup.php https://getcomposer.org/installer \ 72 | && php composer-setup.php --install-dir=/usr/local/bin --filename=composer 73 | WORKDIR /var/www/sf 74 | RUN composer update --no-scripts 75 | 76 | RUN mkdir -p /var/www/cache \ 77 | && env DB_PASSWORD="dummy" \ 78 | API_KEY="dummy" \ 79 | APP_SECRET="dummy" \ 80 | MAILER_PASSWORD="dummy" \ 81 | EMAIL_FROM="dummy@dummy.com" \ 82 | /var/www/sf/bin/console cache:warmup --env=${APP_ENV} 83 | -------------------------------------------------------------------------------- /conf/docker/docker-compose.cli.local.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | phpunit: 6 | build: 7 | context: . 8 | dockerfile: conf/docker/Dockerfile.phpunit 9 | restart: "no" 10 | volumes: 11 | - "/projects/myproject/sf:/var/www/sf" 12 | - "/storage/myproject:/var/www/local-storage" 13 | network_mode: "host" 14 | 15 | composer: 16 | build: 17 | context: . 18 | dockerfile: conf/docker/Dockerfile.composer 19 | restart: "no" 20 | volumes: 21 | - "/projects/myproject/sf:/var/www/sf" 22 | network_mode: "host" 23 | 24 | phpcs: 25 | build: 26 | context: . 27 | dockerfile: conf/docker/Dockerfile.phpcs 28 | restart: "no" 29 | volumes: 30 | - "/projects/myproject:/project" 31 | network_mode: "host" 32 | -------------------------------------------------------------------------------- /conf/docker/files/geolite2/GeoLite2-Country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/conf/docker/files/geolite2/GeoLite2-Country.mmdb -------------------------------------------------------------------------------- /conf/env/.env.dev.local: -------------------------------------------------------------------------------- 1 | PROJECT= 2 | 3 | ### Symfony environment 4 | APP_ENV=dev 5 | APP_DEBUG=1 6 | #APP_SECRET=SYMFONY_DECRYPTION_SECRET 7 | #API_KEY=K8S_SECRET 8 | 9 | ### Bind mounts (local only) 10 | HOST_SF_DIR=/hosthome/myproject/sf 11 | HOST_DATA_LOCAL_STORAGE=/hosthome/storage-buckets 12 | 13 | ### K8s resources 14 | WEB_POD_URL_INTERNAL=http://myproject-dev 15 | 16 | ### Application host names and protocol 17 | ROUTER_REQUEST_CONTEXT_HOST=dev.myproject 18 | ROUTER_REQUEST_CONTEXT_SCHEME=http 19 | CDN_URL=http://cdn-dev.myproject 20 | CDN_URL_FROM_CLUSTER=http://cdn-dev.myproject 21 | TRUSTED_PROXIES=172.0.0.0/8 22 | OPCACHE_ENABLE=0 23 | OPCACHE_PRELOAD= 24 | 25 | ### Docker build arguments 26 | PHP_DISPLAY_ERRORS=On 27 | PHP_ERROR_REPORTING=E_ALL 28 | 29 | ### Storage abstraction 30 | STORAGE_ADAPTER=local 31 | ### GCS adapter 32 | GCS_PRIVATE_BUCKET=app-dev.myproject.info 33 | GCS_PUBLIC_BUCKET=cdn-dev.myproject.info 34 | 35 | ### MySQL 36 | DB_HOST=10.0.2.2 37 | DB_PORT=3306 38 | DB_NAME=myproject_dev 39 | DB_USER=myproject_dev 40 | #DB_PASSWORD=SYMFONY_DECRYPTION_SECRET 41 | 42 | ### SMTP 43 | SMTP_ACCOUNT=mailcatcher 44 | MAILER_TRANSPORT=smtp 45 | MAILER_HOST=ssl0.ovh.net 46 | MAILER_PORT=465 47 | MAILER_ENCRYPTION=ssl 48 | MAILER_USER=info@myproject.info 49 | #MAILER_PASSWORD=SYMFONY_DECRYPTION_SECRET 50 | EMAIL_FROM=dummy@myproject.info 51 | -------------------------------------------------------------------------------- /conf/env/.env.master.local: -------------------------------------------------------------------------------- 1 | PROJECT=myproject 2 | 3 | ### Symfony environment 4 | APP_ENV=master 5 | APP_DEBUG=1 6 | #APP_SECRET=SYMFONY_DECRYPTION_SECRET 7 | #API_KEY=K8S_SECRET 8 | 9 | ### Bind mounts (local only) 10 | HOST_SF_DIR=/hosthome/myproject/sf 11 | HOST_DATA_LOCAL_STORAGE=/hosthome/storage-buckets 12 | 13 | ### K8s resources 14 | WEB_POD_URL_INTERNAL=http://myproject-master 15 | 16 | ### Application host names and protocol 17 | ROUTER_REQUEST_CONTEXT_HOST=master.myproject 18 | ROUTER_REQUEST_CONTEXT_SCHEME=http 19 | CDN_URL=http://cdn.myproject:10080 20 | CDN_URL_FROM_CLUSTER=http://10.0.2.2:10082 21 | TRUSTED_PROXIES=172.0.0.0/8 22 | OPCACHE_ENABLE=1 23 | OPCACHE_PRELOAD=/var/www/cache/master/App_KernelMasterDebugContainer.preload.php 24 | 25 | ### Docker build arguments 26 | PHP_DISPLAY_ERRORS=Off 27 | PHP_ERROR_REPORTING=E_ALL 28 | 29 | ### Storage abstraction 30 | STORAGE_ADAPTER=local 31 | ### GCS adapter 32 | GCS_PRIVATE_BUCKET=app-master.myproject.info 33 | GCS_PUBLIC_BUCKET=cdn-master.myproject.info 34 | 35 | ### MySQL 36 | DB_HOST=10.0.2.2 37 | DB_PORT=3306 38 | DB_NAME=myproject_master 39 | DB_USER=myproject_master 40 | #DB_PASSWORD=SYMFONY_DECRYPTION_SECRET 41 | 42 | ### SMTP 43 | SMTP_ACCOUNT=ovh 44 | MAILER_TRANSPORT=smtp 45 | MAILER_HOST=ssl0.ovh.net 46 | MAILER_PORT=465 47 | MAILER_ENCRYPTION=ssl 48 | MAILER_USER=info@myproject.info 49 | #MAILER_PASSWORD=SYMFONY_DECRYPTION_SECRET 50 | EMAIL_FROM=dummy@myproject.info 51 | -------------------------------------------------------------------------------- /conf/env/.env.oat.remote: -------------------------------------------------------------------------------- 1 | PROJECT=myproject 2 | 3 | ### Symfony environment 4 | APP_ENV=oat 5 | APP_DEBUG=0 6 | #APP_SECRET=SYMFONY_DECRYPTION_SECRET 7 | #API_KEY=K8S_SECRET 8 | 9 | ### Bind mounts 10 | HOST_SF_DIR= 11 | HOST_DATA_LOCAL_STORAGE= 12 | 13 | ### K8s resources 14 | WEB_POD_URL_INTERNAL=http://myproject-oat 15 | 16 | ### Application host names and protocol 17 | ROUTER_REQUEST_CONTEXT_HOST=test.myproject.info 18 | ROUTER_REQUEST_CONTEXT_SCHEME=https 19 | CDN_URL=https://cdn-test.myproject.info 20 | CDN_URL_FROM_CLUSTER=https://cdn-test.myproject.info 21 | TRUSTED_PROXIES=172.0.0.0/8 22 | OPCACHE_ENABLE=0 23 | OPCACHE_PRELOAD=/var/www/cache/oat/App_KernelOatContainer.preload.php 24 | 25 | ### Docker build arguments 26 | PHP_DISPLAY_ERRORS=Off 27 | PHP_ERROR_REPORTING=E_ALL 28 | 29 | ### Storage abstraction 30 | STORAGE_ADAPTER=gcloud 31 | ### GCS adapter 32 | GCS_PRIVATE_BUCKET=app-test.myproject.info 33 | GCS_PUBLIC_BUCKET=cdn-test.myproject.info 34 | 35 | ### MySQL 36 | DB_HOST=10.1.2.3 37 | DB_PORT=3306 38 | DB_NAME=myproject_dev 39 | DB_USER=myproject_dev 40 | #DB_PASSWORD=SYMFONY_DECRYPTION_SECRET 41 | 42 | ### SMTP 43 | SMTP_ACCOUNT=ovh 44 | MAILER_TRANSPORT=smtp 45 | MAILER_HOST=ssl0.ovh.net 46 | MAILER_PORT=465 47 | MAILER_ENCRYPTION=ssl 48 | MAILER_USER=info@myproject.info 49 | #MAILER_PASSWORD=SYMFONY_DECRYPTION_SECRET 50 | EMAIL_FROM=info@myproject.info 51 | -------------------------------------------------------------------------------- /conf/env/.env.prod.remote: -------------------------------------------------------------------------------- 1 | PROJECT=myproject 2 | 3 | ### Symfony environment 4 | APP_ENV=prod 5 | APP_DEBUG=0 6 | #APP_SECRET=SYMFONY_DECRYPTION_SECRET 7 | #API_KEY=K8S_SECRET 8 | 9 | ### Bind mounts 10 | HOST_SF_DIR= 11 | HOST_DATA_LOCAL_STORAGE= 12 | 13 | ### K8s resources 14 | WEB_POD_URL_INTERNAL=http://myproject-prod 15 | 16 | ### Application host names and protocol 17 | ROUTER_REQUEST_CONTEXT_HOST=www.myproject.info 18 | ROUTER_REQUEST_CONTEXT_SCHEME=https 19 | CDN_URL=https://cdn.myproject.info 20 | CDN_URL_FROM_CLUSTER=https://cdn.myproject.info 21 | TRUSTED_PROXIES=172.0.0.0/8 22 | OPCACHE_ENABLE=0 23 | OPCACHE_PRELOAD=/var/www/cache/prod/App_KernelProdContainer.preload.php 24 | 25 | ### Docker build arguments 26 | PHP_DISPLAY_ERRORS=Off 27 | PHP_ERROR_REPORTING=E_ERROR|E_WARNING|E_PARSE 28 | 29 | ### Storage abstraction 30 | STORAGE_ADAPTER=gcloud 31 | ### GCS adapter 32 | GCS_PRIVATE_BUCKET=app.myproject.info 33 | GCS_PUBLIC_BUCKET=cdn.myproject.info 34 | 35 | ### MySQL 36 | DB_HOST=10.1.2.3 37 | DB_PORT=3306 38 | DB_NAME=myproject_master 39 | DB_USER=myproject_master 40 | #DB_PASSWORD=SYMFONY_DECRYPTION_SECRET 41 | 42 | ### SMTP 43 | SMTP_ACCOUNT=ovh 44 | MAILER_TRANSPORT=smtp 45 | MAILER_HOST=ssl0.ovh.net 46 | MAILER_PORT=465 47 | MAILER_ENCRYPTION=ssl 48 | MAILER_USER=info@myproject.info 49 | #MAILER_PASSWORD=SYMFONY_DECRYPTION_SECRET 50 | EMAIL_FROM=info@myproject.info 51 | -------------------------------------------------------------------------------- /conf/infra/msmtp.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/msmtp/*.log { 2 | rotate 5 3 | daily 4 | compress 5 | missingok 6 | notifempty 7 | } 8 | -------------------------------------------------------------------------------- /conf/infra/msmtprc.tpl: -------------------------------------------------------------------------------- 1 | defaults 2 | 3 | logfile /var/log/msmtp/msmtp.log 4 | 5 | # OVH 6 | account ovh 7 | host ssl0.ovh.net 8 | auth on 9 | port 465 10 | from info@myproject.info 11 | user eric@myproject.info 12 | passwordeval "echo $MAILER_PASSWORD" 13 | auth on 14 | tls on 15 | tls_trust_file /etc/ssl/certs/ca-certificates.crt 16 | # https://wiki.archlinux.org/index.php/msmtp#Server_sent_empty_reply 17 | tls_starttls off 18 | 19 | # mailcatcher (local) 20 | account mailcatcher 21 | host 10.0.2.2 22 | port 1025 23 | from dummy@myproject.info 24 | auth off 25 | tls off 26 | 27 | # Set a default account 28 | account default : {{ .Env.SMTP_ACCOUNT }} 29 | -------------------------------------------------------------------------------- /conf/infra/php.ini.tpl: -------------------------------------------------------------------------------- 1 | ; Errors 2 | display_errors = {{ .Env.PHP_DISPLAY_ERRORS }} 3 | display_startup_errors = On 4 | error_reporting = {{ .Env.PHP_ERROR_REPORTING }} 5 | log_errors = On 6 | error_log = /var/www/var/log/php_error.log 7 | 8 | ; Session 9 | session.gc_maxlifetime = 7200 10 | 11 | ; File uploads 12 | upload_max_filesize = 16M 13 | post_max_size = 16M 14 | 15 | ; mail function 16 | sendmail_path = "/usr/bin/msmtp -t -v" 17 | 18 | ; opcache 19 | opcache.enable={{ .Env.OPCACHE_ENABLE }} 20 | opcache.preload_user=www-data 21 | opcache.memory_consumption=256 22 | opcache.max_accelerated_files=20000 23 | opcache.preload={{ .Env.OPCACHE_PRELOAD }} 24 | -------------------------------------------------------------------------------- /conf/infra/ssh_config: -------------------------------------------------------------------------------- 1 | Host github.com 2 | HostName github.com 3 | IdentityFile ~/.ssh/id_rsa 4 | StrictHostKeyChecking no 5 | -------------------------------------------------------------------------------- /conf/infra/virtual-host.conf.tpl: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/sf/public 3 | ServerName {{ .Env.ROUTER_REQUEST_CONTEXT_HOST }} 4 | ServerAdmin info@myproject.info 5 | 6 | AllowOverride AuthConfig 7 | Require all granted 8 | RewriteEngine On 9 | RewriteCond %{REQUEST_FILENAME} !-f 10 | RewriteRule ^(.*)$ index.php [QSA,L] 11 | 12 | 13 | -------------------------------------------------------------------------------- /conf/k8s/cronjob.local.tpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob 3 | metadata: 4 | name: {{ PROJECT }}-{{ }}-mailer-realtime 5 | labels: 6 | cron-job: {{ PROJECT }}-{{ APP_ENV }}-mailer-realtime 7 | spec: 8 | schedule: "*/2 * * * *" 9 | successfulJobsHistoryLimit: 1 10 | failedJobsHistoryLimit: 1 11 | concurrencyPolicy: Forbid 12 | startingDeadlineSeconds: 60 13 | jobTemplate: 14 | spec: 15 | template: 16 | spec: 17 | restartPolicy: OnFailure 18 | containers: 19 | - name: k8s-cronjob 20 | image: k8s-cronjob:current 21 | env: 22 | - name: API_KEY 23 | valueFrom: 24 | secretKeyRef: 25 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 26 | key: API_KEY 27 | - name: URL 28 | value: {{ WEB_POD_URL_INTERNAL }}/api/email_send_realtime/$(API_KEY) 29 | --- 30 | # apiVersion: batch/v1beta1 31 | # kind: CronJob 32 | # metadata: 33 | # name: {{ PROJECT }}-{{ APP_ENV }}-mailer-batch 34 | # labels: 35 | # cron-job: {{ PROJECT }}-{{ APP_ENV }}-mailer-batch 36 | # spec: 37 | # schedule: "*/10 * * * *" 38 | # successfulJobsHistoryLimit: 1 39 | # failedJobsHistoryLimit: 1 40 | # concurrencyPolicy: Forbid 41 | # startingDeadlineSeconds: 60 42 | # jobTemplate: 43 | # spec: 44 | # template: 45 | # spec: 46 | # restartPolicy: OnFailure 47 | # containers: 48 | # - name: k8s-cronjob 49 | # image: k8s-cronjob:current 50 | # env: 51 | # env: 52 | # - name: API_KEY 53 | # valueFrom: 54 | # secretKeyRef: 55 | # name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 56 | # key: API_KEY 57 | # - name: URL 58 | # value: {{ WEB_POD_URL_INTERNAL }}/api/email_send_batch/$(API_KEY) 59 | --- 60 | -------------------------------------------------------------------------------- /conf/k8s/cronjob.remote.tpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob 3 | metadata: 4 | name: {{ PROJECT }}-{{ APP_ENV }}-mailer-realtime 5 | labels: 6 | cron-job: {{ PROJECT }}-{{ APP_ENV }}-mailer-realtime 7 | spec: 8 | schedule: "*/2 * * * *" 9 | successfulJobsHistoryLimit: 0 10 | failedJobsHistoryLimit: 0 11 | concurrencyPolicy: Forbid 12 | startingDeadlineSeconds: 60 13 | jobTemplate: 14 | spec: 15 | template: 16 | spec: 17 | restartPolicy: OnFailure 18 | containers: 19 | - name: k8s-cronjob 20 | image: us-central1-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/k8s-cronjob:current 21 | env: 22 | - name: API_KEY 23 | valueFrom: 24 | secretKeyRef: 25 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 26 | key: API_KEY 27 | - name: URL 28 | value: {{ WEB_POD_URL_INTERNAL }}/api/email_send_realtime/$(API_KEY) 29 | resources: 30 | requests: 31 | memory: "512Mi" 32 | cpu: "250m" 33 | limits: 34 | memory: "512Mi" 35 | cpu: "250m" 36 | --- 37 | # apiVersion: batch/v1beta1 38 | # kind: CronJob 39 | # metadata: 40 | # name: {{ PROJECT }}-{{ APP_ENV }}-mailer-batch 41 | # labels: 42 | # cron-job: {{ PROJECT }}-{{ APP_ENV }}-mailer-batch 43 | # spec: 44 | # schedule: "*/10 * * * *" 45 | # successfulJobsHistoryLimit: 0 46 | # failedJobsHistoryLimit: 0 47 | # concurrencyPolicy: Forbid 48 | # startingDeadlineSeconds: 60 49 | # jobTemplate: 50 | # spec: 51 | # template: 52 | # spec: 53 | # restartPolicy: OnFailure 54 | # containers: 55 | # - name: k8s-cronjob 56 | # image: us-central1-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/k8s-cronjob:current 57 | # env: 58 | # - name: API_KEY 59 | # valueFrom: 60 | # secretKeyRef: 61 | # name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 62 | # key: API_KEY 63 | # - name: URL 64 | # value: {{ WEB_POD_URL_INTERNAL }}/api/email_send_batch/$(API_KEY) 65 | # resources: 66 | # requests: 67 | # memory: "512Mi" 68 | # cpu: "250m" 69 | # limits: 70 | # memory: "512Mi" 71 | # cpu: "250m" 72 | -------------------------------------------------------------------------------- /conf/k8s/deployment.local.tpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ PROJECT }}-{{ APP_ENV }} 5 | labels: 6 | app: {{ PROJECT }}-{{ APP_ENV }} 7 | spec: 8 | replicas: 1 9 | strategy: 10 | type: Recreate 11 | selector: 12 | matchLabels: 13 | app: {{ PROJECT }}-{{ APP_ENV }} 14 | 15 | template: 16 | metadata: 17 | labels: 18 | app: {{ PROJECT }}-{{ APP_ENV }} 19 | APP_ENV: {{ APP_ENV }} 20 | tag: {{ TAG }} 21 | spec: 22 | 23 | containers: 24 | - name: {{ PROJECT }}-{{ APP_ENV }}-web 25 | image: {{ PROJECT }}-{{ APP_ENV }}-web:{{ TAG }} 26 | env: 27 | - name: SYMFONY_DECRYPTION_SECRET 28 | valueFrom: 29 | secretKeyRef: 30 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 31 | key: SYMFONY_DECRYPTION_SECRET 32 | - name: API_KEY 33 | valueFrom: 34 | secretKeyRef: 35 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 36 | key: API_KEY 37 | - name: MAILER_PASSWORD 38 | valueFrom: 39 | secretKeyRef: 40 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 41 | key: MAILER_PASSWORD 42 | ports: 43 | - name: {{ PROJECT }}-{{ APP_ENV }} 44 | containerPort: 80 45 | volumeMounts: 46 | - name: {{ PROJECT }}-{{ APP_ENV }}-log 47 | mountPath: /var/www/log 48 | # Only for local development 49 | - name: {{ PROJECT }}-{{ APP_ENV }}-local-storage 50 | mountPath: /var/www/local-storage 51 | - name: {{ PROJECT }}-{{ APP_ENV }}-sf 52 | mountPath: /var/www/sf 53 | 54 | volumes: 55 | - name: {{ PROJECT }}-{{ APP_ENV }}-log 56 | emptyDir: {} 57 | - name: {{ PROJECT }}-{{ APP_ENV }}-local-storage 58 | hostPath: 59 | path: {{ HOST_DATA_LOCAL_STORAGE }} 60 | type: Directory 61 | - name: {{ PROJECT }}-{{ APP_ENV }}-sf 62 | hostPath: 63 | path: {{ HOST_SF_DIR }} 64 | type: Directory 65 | -------------------------------------------------------------------------------- /conf/k8s/deployment.remote.tpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ PROJECT }}-{{ APP_ENV }} 5 | labels: 6 | app: {{ PROJECT }}-{{ APP_ENV }} 7 | spec: 8 | replicas: 1 9 | strategy: 10 | type: Recreate 11 | selector: 12 | matchLabels: 13 | app: {{ PROJECT }}-{{ APP_ENV }} 14 | 15 | template: 16 | metadata: 17 | labels: 18 | app: {{ PROJECT }}-{{ APP_ENV }} 19 | APP_ENV: {{ APP_ENV }} 20 | tag: "{{ TAG }}" 21 | spec: 22 | 23 | initContainers: 24 | - name: {{ PROJECT }}-{{ APP_ENV }}-webinit 25 | image: {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ PROJECT }}-{{ APP_ENV }}-webinit:{{ TAG }} 26 | command: 27 | - "/bin/sh" 28 | - "-c" 29 | - | 30 | cp -r /var/www/sf/* /sf \ 31 | && cp -r /var/www/sf/.env /sf/.env \ 32 | && cp -r /var/www/cache/* /cache \ 33 | && chown -R 1000:33 /cache \ 34 | && chown -R 1000:33 /sf 35 | # && chmod 0777 /sf/templates/email/campaign \ 36 | volumeMounts: 37 | - name: sf 38 | mountPath: /sf 39 | - name: cache 40 | mountPath: /cache 41 | 42 | containers: 43 | - name: {{ PROJECT }}-{{ APP_ENV }}-web 44 | image: {{ GCP_REGION }}-docker.pkg.dev/{{ GCP_PROJECT_ID }}/{{ PROJECT }}/{{ PROJECT }}-{{ APP_ENV }}-web:{{ TAG }} 45 | env: 46 | - name: SYMFONY_DECRYPTION_SECRET 47 | valueFrom: 48 | secretKeyRef: 49 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 50 | key: SYMFONY_DECRYPTION_SECRET 51 | - name: API_KEY 52 | valueFrom: 53 | secretKeyRef: 54 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 55 | key: API_KEY 56 | - name: MAILER_PASSWORD 57 | valueFrom: 58 | secretKeyRef: 59 | name: {{ PROJECT }}-{{ APP_ENV }}-sf-secrets 60 | key: MAILER_PASSWORD 61 | ports: 62 | - name: {{ PROJECT }}-{{ APP_ENV }} 63 | containerPort: 80 64 | resources: 65 | requests: 66 | memory: "512Mi" 67 | cpu: "250m" 68 | limits: 69 | memory: "512Mi" 70 | cpu: "250m" 71 | volumeMounts: 72 | - name: sf 73 | mountPath: /var/www/sf 74 | - name: cache 75 | mountPath: /var/www/cache 76 | 77 | volumes: 78 | - name: sf 79 | emptyDir: {} 80 | - name: cache 81 | emptyDir: {} 82 | -------------------------------------------------------------------------------- /conf/k8s/service.tpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ PROJECT }}-{{ APP_ENV }} 5 | labels: 6 | app: {{ PROJECT }}-{{ APP_ENV }} 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - port: 80 11 | selector: 12 | app: {{ PROJECT }}-{{ APP_ENV }} 13 | -------------------------------------------------------------------------------- /conf/phpcs/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eric's custom coding standard. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | phpunit: 6 | build: 7 | context: . 8 | dockerfile: conf/docker/Dockerfile.phpunit 9 | restart: "no" 10 | volumes: 11 | - "/projects/myproject/sf:/var/www/sf" 12 | - "/storage-buckets:/var/www/local-storage" 13 | network_mode: "host" 14 | 15 | composer: 16 | build: 17 | context: . 18 | dockerfile: conf/docker/Dockerfile.composer 19 | restart: "no" 20 | volumes: 21 | - "/projects/myproject/sf:/var/www/sf" 22 | network_mode: "host" 23 | 24 | phpcs: 25 | build: 26 | context: . 27 | dockerfile: conf/docker/Dockerfile.phpcs 28 | restart: "no" 29 | volumes: 30 | - "/projects/myproject:/project" 31 | network_mode: "host" 32 | -------------------------------------------------------------------------------- /docs/architecture-images/architecture-local.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/docs/architecture-images/architecture-local.png -------------------------------------------------------------------------------- /docs/architecture-images/architecture-remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/docs/architecture-images/architecture-remote.png -------------------------------------------------------------------------------- /docs/architecture.drawio: -------------------------------------------------------------------------------- 1 | 7V1bc5s4FP41mdndGTOIq3lMnKZpm3TTJttt96WDjWzTYERBTuz++pW4GSQZEww4aezJA0ggy+ccfecq5UQdLVZvQzuYXyMHeieK7KxO1PMTRQGaopzQP9lZpy2GApKWWeg6adum4db9BdNGOW1dug6MSg9ihDzsBuXGCfJ9OMGlNjsM0WP5sSnyyt8a2DPINdxObI9v/dd18DxtBYa16biE7myefvVQMZOOhZ09nP6SaG476LHQpL45UUchQji5WqxG0KPUy+iSvHexpTefWAh9XOeFn+8s09Kds+/uvXujXt0vPnwMB8NklAfbW6Y/+MoOMArSKeN1RocQLX0H0qHkE/Xsce5ieBvYE9r7SFhP2uZ44ZE7QC4jHKJ7OEIeCuO31eRDeqau5xXap/EnfyOjMB3jAYbYJXw49dyZT9ropMj7yMepkACN3CMyBxdT4dLpvOz0YQ9OCU3OeBKlVKODw1WhKSXZW4gWEIdr8kjaq6XcS+V3oMppw+NGGszsoXlBEICcPWmnIjjLB99wiVykjHoC0yyOadeu794vxzCeF5mWYnj0949DcjWjV1/cEC9tb4xWdDGGLiFAVyzmWckw/YJ8RiMBM8Usr2ZxRHv92VV8dw70TdMdffvcakcMcrZncgCGvBxohkAOhkYLYnB/+vPTx/d/f3LG/kh/fPfO+b7+OlA4MSBo4v9A4+hgrM3Xcz3WtsAY3SozxlB4vgBFwBetq9XJs2U/dmzkX25JlhmS5fe7SNaGJAtJph9J9lSSGbtJtoNIdhQkBtPUXVHC8utejj9d0BMwcApMnp5DEZh2RU6zZQnskZZGmZQCvdSvZGaWe4GWpwExKOVbGD64hF6vSDkZ+k4xB6bOMwd0ZjsCsIU7N8h5TZwxGUDXRJwRm/Vdccbk6A8d4oumtyjEczRDvu292bSelTm0eeYKxdYvpfUPiPE6Jaq9xKjMNbhy8Vf6uqSnd98KPeerdOT4JgMtQvZwXXiJ3n4r9m1ei++y94rcVar4GKFlOIFVtErhGdvhDOKqB9PFRSlZKRch9GxM3KLSPNq3DXl/G8iSLCmSUvTZCjJg/FzSAEFMu0EUE++UPACUYBVTMOvPHD3SraqycVhRUoqyJNeTJVCSpI1gdS9Lek1ZyiI7z0SWsjVQkCW8DlL5GHnLCMPw3Q0fCggIa8kTmZ1wALh/Y2wJAHj2GHpn9uR+Fk9F9Eob2M8Y83ms8WAuI+AdoEc4Pomjmth2fRqsYdn4F2H0kYe5/s6sqJ081LuCdt6wury7u2kA6xymL9ZBiH4QZ23gwIcdHG+A2yyUik2CahjfA36Ng6ny9NUb5Pp4I1pDtSxammqUh0h+UvoWIzX5NPYAA961n4TIr0aDP2AwhwsY2t6fR0hI74EhmWpNVFClTLu3D+58uD7jXxTYvhAWJgmJKCSEs/EfZMbkj3y9XLj6k15SyskxiEztheutk3fIQPYi8ZRUlXJgDr0HSD0prqc8CItEpb5klrTTR+HC9srdjyk5ab+WzDPu9Aj2wHCQBuWF7xMG40Ea16fdEyIEVMiL3S4RYT8dXi5MLe7Eoe1HUzJoNrwP8wceUeiUv734+jiXygFDc0XXc1oz1xvKO24UeHZKddf33MIXTz1k4+KEWFgvqgciWYkwpFZ8tdY4ikfX4sG5VccVe2iWPIMVyxhiW5buATzvFgIy2lbLrzoYVMvy+zC+HE4vf/0IvPd3hkp80E+3wSAr59hp+Ck1Db/UEBjIkqZndv7TjMHTMLTXhQcCauRFRRNgYy5mz6DpNILdWIJ8tOiMVrTsm7vfbo9dxJ/9zMB8jB21Gm2YfEzuYyDMfojCuN1lf1WOZ+/8CNs+dm3M5z4OCA7gCeDQqVtYVdywGxyMfrxCgxE1Q+/XK1T4DOX1+vbTFWk653XO64QDwPCodi60OzTgfb9LFFGxuiakxq+pUEdVyswRZtxEOf+8gLJ17mSBnpJnfjEno9NxuRjLxPGl3AKUJmjxWiMtbPWGevB1pvLB1ypO2kFw5KSIk5poUfbLST6rFXMywkdu7oRYrQY3RbXK3XFTP4jFm1mqRTs1t1q7y0PHLrTvnNJtB+R+4tlR5E7u5q6fdFy4XjbFWoaxmKRqTcs40279p6uFBj3vF8UmEI3ouL7DiQmNEpW5mhV+p7Em3lRZuI6TCBCM3F/2OB6K8ij14sm4+tmJfl65HoWGkbjAoFLiuVWbb1FJJ3ZS3AUiWs2yBHQTlBZ0o5gG58qA8qADJiPaXTxD5WVgF7Yfba4t2C60ufrF9oPUqzXE26bw3kSV7IHtdaMemb3bddQjL43JAEhnpGlL1IMfSGdME8AMlPzkNsInQu3DF9j8ntonixLtrX0GRP2YZIWVw6ut6J88oN+DxuEj6CNCWRhPJymximC492a43yeCxvqDB3cHrUMomUIlq2GVkN+yjErs59VTHnxXnpSao6PewNAlZKRws2+R1ZBXLMKcnNqPXmH3UwFWHdTWK0xUkd0g1J5aEdJV4yOIc4yDOId8wRi0Tav/SiawsALwxWqrLDrehrYy29JNUnnPDlsX1p2y0gDH2RdgUDcBNsEugRaKDCotgAOES7ZAn96WSW3uGKhr7OO9+S/zOLnVykYWxw0JAKDUhvgd4E7bZo41Ms5Vc1hWofvhX36uAZPW7hDu+Ej/hbeO1hGGCz4QRKTFQxNaglZpqL+gaHFPu5bUA5l0ilWvQGLnQJrMzKXjUAHgKy1ultG8UjfH5YIvbDdmTUXfSGSFhBVoYjEDWi/p2StxwYcQbtcLQhU6g0cU3tMqVVpXuk1dvZIANRs60AUHJXUWOxAyTrTvojrbkLtZtKZ3+lo5yaYahJzsKtUgRgR+n+ML8IyeS6qhanHsBONhP9YDm2gwGgeE2ESD2W+iQXC+xkvPNFQuyVeYaRDT4+jN9OLNKP3gkT5knRBTYuok6iKSxvozPac+AR+nGYk2Eu8Rq1kkCNeZLD8lVPg8hblKB79kWe5bu/J749+6eL4c7xC+p9rqlmGqtlHHVofA0aEpsBY7K6RnHS2zzyTt/fzLRL3+8s/N++j649X0Qb5fowFv8nDs4A4bjG0VGL55gInJErMhOyGZssyxo3nOv628SgMvsZ1zgyIXu0hoO10xD4wRxmixlU2cDcZwXZbBcHieTJkeuXO+WM3oAdiS/WsZQmkZJS8xkNJ+vl4dWky6aiA6mlIzeWlgswWtSQNvCNGTiSeYN3YO4cM1CbAJUvGN01+11ErVKttpIvWkVYBezj8MrIYeG7tra9CdTkHwzH4cf1tZ01vZvfz14VafjwTodXtND2ZopTLoRWmWIbtFq899PULe8FiS8KZ/INks+P3MygyAwEl9/6xdU7RqEfS+i1ZtCBrsQHp3oPEfBNNv7ufP3lcFTsbTqfn3f58zenV1DFdZVZmGVpAWIMmyUikv3dSOdS4bFrMvQVHNhsLBjgTYw447lg4etlqvCnvxFWFVy+oZFYQBWTLKsUmliwoJ5+zHGA7cs59D1bKV8T/2e6uWX9WzCa2UTd+GhV9Ds6T8jB3RyaZotpcVXRv1SofOWK3I3NBg8atpjdhQA5IlW5tPaVxgmZJZ6LSYqNAWlOzoCBz5++3odKZ8lW/Wd7PxPbzAChAsgXM0uY+PmgrhzI0yI/OVnKHAJLd6/WcnVWc4Ffjzb7yrIoTkV0X0aC2iplb0JwSv63x5I68j7uWsfyFzeDOkvOXleKRw5ZHCPZ4oXLUXo8baeo3/WYOt098Y+YdbcBrHsc2J7FfIdogVbvsT0dI7Hso+ZNmp1tRtbQSyhdzkz65oMaNpqoxLX20Es0zx0fslPRU07ox8O7hDiVFWK2RtakxyVJbVXdY7vWtkhldh2879aH3FpSyThROlcWjKYr6+vWCDkER8SvQYbGDFsmp9P6dgg8YFG2SJ9cpacbaEBBm2D3jbg5pNSjo6Sba9CHySVclkAIqNeNfFJ92yJJkdrLuaJCHZ+NLs47bZ2riVbUh4Prg1UICkMNtmKXDJhY/aF4oJisW7g7FSILOFLF6VH7+7OLKvTA0wWHYPmkYoBWP1jEWCiu0SGE2yA0PahyXhgVYvFZZarQhvazc/U1euSMAsghJoYXM/uQ0R5fHmcVoKdo0cSJ/4Hw==7V1rd6K6Gv41rrX3WUsXd/Bja2/Tac/uTLtnT8+XWSgp0gFCAVvtrz8JBIQkIiqg7bYzHySBEN/Lk/eW2JNH3vwyNIPpLbSA25MEa96Tz3qSJCqS1MP/BWtBWjRJTFvs0LFI27Lh3nkHpFEgrTPHAlHpxhhCN3aCcuME+j6YxKU2MwzhW/m2J+iW3xqYNmAa7iemy7b+41jxlLSK2nDZcQUce0pebUh62uGZ2c3km0RT04JvhSb5vCePQgjj9JM3HwEXUy+jy7U9+utE0qdfHl+u/v7uPS8uv/j9dLCLTR7Jv0II/HjroS8X8MeDefpDNP5+flW/xgt3JJBHhFfTnRF6ke+KuklDFIfwNxhBF4ZJg2wlfz35NCeIiC+S2zIS45YQznwLWOTKHEfQncXgJJwQIUla8ytEztMnx3ULL3pK/lC7HZqWg7571udDH+DboR9nY+HHYWBOnBgLqirgsV3H9tGFC54Q1U5rEpEQ+xWEMZgXRIgQ9RJAD8ThAt1CeocaISLREFkn129LcZME0jYtSJqYqYhJRNzOx16yEX0gnNyAqyLD1ZELZ1ZP0hBVRUyb+283DKMtM5om7MI3EHU0x0kvbpnGnku4RvFJFQ3lQs5lgOISkpIAj+HNbQwxA3sSSIMpmH9Br0DdQZh+OJvgKf6KXlzcaIYxeZdrjoF7ByMndpLbwpSAp5hBDlLzG6rfcywLz5lmf3b/CWnO74uw1Pj2TXLbmUrkqvD1hskfR95YiarUsfUSRSRIGKiMBCkKK0AygbAQuGbsvJYBjydU5I130EETzgVYLYlvX1XLA8CnpwjEjEzm064lpq7xVfj2aHjv6sWLP38RTl8ezL6s/nvQJwSTWRghJn0HUfoQ0bLYdHwQkuu2MEoTyhjVFyUOSCniIBOGEk4J4nB3oJp/mz7b76Nb6+ut/x1ev7xb5s++wQjASRCghjtoMZKwZCim1NvUicE9IjnufUPAUkYoRmzSP66krOJ7kcEKB0FiGLTJMkUalDVTNlieibo+4LKsgaWFq7OK/u/R2dYsBnVYYqzYpcHA5yqriIzFME3GOSybIXARAEyRS4AR9MNaDdWK9lHMBko2WzQbhkcIahqC+BgkdopBrC86ys2jJQ59B7YTZe87ICjKTblfYTbDT4ZIud4dEYmijHREpJ0RSZaoMIrAsXYNnlVktIVIMgeRaKsohiEOQB4aGKWxFDK5T4ZD0gfDoWEbOMR1p+X1MLSRF71EgjaDE4pa1nxF5mi+xNF8rQHF59JROdKxEToOGbIBywb35BKG8RTa0Dfd82XraZmwy3tuIA63JOR8BnG8IMBhzmJYJjaYO/FP/PhAJVePhZ6zORk5uchYgjgRLgoP4cvHYt/yseQqe249glWxNoKzcAIq6CdmCTMztEFcdSMRUEzeSlGpjWw78T2bTkGB3sC4lyT5coPexavIGH+y8af/9OSTbCXfQ9TvXLu4GI04Ub9kvTw1J7/tZCq8R9pSaE2nFFpklzGuQitqW4xlV5irh4e7IjcLzNNeZjg3mpC0n0a9T9ANohTMl50Z/71FEMJnZCb1oRmvEYMtUIHWVD5KCJUo0bR2K3vTbr7dMhTokLOia+VB0i9FnmvegMkEfF8rhr7VkmGoRnHR6AsDQdQ3WDmUrPcOhA4i4ZbpoPUCp7MCd/k4v/5xZb4IhjeePNzdXn+z3cz+aVvedM3AFC8Zy7Qtks61PYHTGECbxjEuTTnB78L/d4c2BGiDHN4GE+gxQo4WhLgsk5m3NUGsT4LbK/2tECcUC75lgGmVUE897alnlUsYN9fFN2Qq9ZVZ0/J6HjKxXrFkhrfWIaUh0rmjUIn6wJBKMiUiDZXLw7TngolsfuwUFzFhk2cX02Y1Dy+Sv92sn3wMGpaoWFBrlo5Grzx9iZOhFldkqJW2HBiRDezdLpLaGeHs9MjQivyCQZmuPG5qPNO1LVZKrOlq82E+BW0zCNaA9r/ZNZGVGvxVOuUvu5JX8ndi+Uf+1uavrO2bvzIblLtwF9EiioHHchcxHnG/cS8y9xSKPmPuQa5xFMRSZGkZaNpTbElinYFKxWrbG6DDl7Jcz/dcO5CiUHNp2afglDXezTi1M0XPluR3Wgp+Voc1VgU/xTUCCnzrBBfo93C+yYwiZ/Iwdfy048JxV0Vetpfj/8LZ041n/bQexpdTRf91M9bv+2pNMVY6CqKIAlUPLgm1xLgp6eOUURxxshOclLsRMI02vZS8BHRTpFRppKRLJlpGykwnC7J66cTT2bhhW2yo6bKp1bHFgGipQOfYYt0W/4oCXbA9rJnLa83+Ulj7upsVretVZjcUyCIG7S8zAg0Drekud91lK4bvZtF0j0ZOOxnegzVyuPfpXQkfHd6ha1zqrkDMSBo9UstizAb41trqbWWhtpO0pWBvZjZtJqGHInlGZmVlxjVd/FdX8Ohdm5LerdyxaYIzOPmNswSC45k2WGeUH6PLRebxolNtRZerALoII1d3qOEMBC5cHJM/m3CzS2ZyC0xrbIo3oyA9rODJmWMupllXEJ6/gjT5mvghWWE3pl6pInils0JMJLpIl8kC01W6YxjH0FvppzDZZEoEBEE0jLMeW2Bsvs9CMJhF6UPVheJtyYdBObx9mbNZJdsuVRQQ2pttTEBERh46sA+YipHcCs4v05VfWxNnQRd0ocm21vH2Zm6V4q13sjqyNvRl5Xielpa3NDh0iU1x03upmrM5+IXzDK7d31/tW5TLxXiG3qAoi7JSEuaBIBhrBLrhMqy6Ul65A7R1KZcpfBXplbW2UU3tKtSNTuWbzfB/NF+O6IFYUoKaYXPK/9ObdQDXRsg4AbKdHEUiUujrq2IzlWEGJefStkGLbX1HJBLmonAbqdRbOeEhtSVOU4XqeUn8L7hUs3QG1NONFsXdXIQW+N+j4r6MtPe/3kfD2YPLMajvb3HVuIDsytddnaNPGO1XedU0bUX7uRxjT5BKObYPKN0uvlUPW/VqcG0YI3lZhCqFaT2aNqTkTt46lUg7aXpN23ZTTDToyLMyrJ4Xdb+qVGDiFnin9Mc/zPj6qzf/fhufjn9ePp4/ZOzb1y4Gaau0iLT1zjelBU05tK0KzNYYNMUtzQdt2OW2B66AsvC+etdD3XLJD7CHoUpZD2gLQ1/SBqpeEhAaTRux1bjkYJ2o37MxosJe/KiVyS4R+fIy5dyv23K1Vdp4VYThQGIEWa1hXXcKyaiiKs0cc2DojcUNtHLcgE7qrUDDllwY7jrDIuY/yY7mECDgiADmfQjnmNLB8exCTZHpFY57dmFbJxdyOchGP9M96ZkPetyQvp6v+nL/VbYwdbknnctXdsFapZn3iNPOhD2s56id/f2rJ7u9I17gVCTeVnsDTQtZk6Y/4SlqgAwLdNNRV6ktlRqtq/mJaOt0tYnEKZfJ7Pb+Jjfvy0WzDhmI1bEdmlc+vJ55AemMfDN4gKl5USvXpCtU2XrBPu0m11QFjmvdaLGmGbmrG62zrq9IS1vtjKou0RJe03BsyijkHx5wPDuAFuEqLDggv1tUBppSkidJGAzbOA6fSxC9eXBcXTPSRYzwc2OZZlCW8LbhQEMYyBSOtVeMyqUYW8vfOI59ivhhldoeEI71JcZNS4BMKPx1diCKvN9kSAHl1lVScI9k6rQoripucTD7tLUh41gwXmFd7JOZbF63eRA2JlQCPnKA7sCG0HaBGThRilwr8iNgbnqBCz4qulWq7+7whqwHavOG2BDa0YfWUb9W0+IvEbAOwNfZGIQ+cuiiwinR577t+Id3SHR+Yj1I5/dRzommfzXJg6+YbMmMyOw3w+CGf0ip6mjpDeSaygB2tWCzO5PS32Q6Rk9J9JQqsss4u/ac+iZCp5UHiRZYNgmRUlaet/sHCKbAA6Hp/rk3lh5q3FQXRAFX7Cf/StxWZRXHGRmGy+JAHQr5P84PpyjyIBur+bOvGP6PEP+f4XjHXYSfQFt1HIldxU1N4uhuDVa2psisI/7Fj2LTjx0zZpG30+rLzc4GOsDjluv6URma1i2poE0CUd14qdcleSBrmqITIS2byZperyazKQMgO3Wem4obubMIuStf7o55uO0OcZd0Dua0VdvNDSjwtremvEQmuc8N8U1SgmEBCO3xH2gE9B+9XSh8+hN/xHQUkoDgk+k57iJ9Bg1keinMy8merClwXwFeBpie8iB0VLHUl84Sd/ow9Ey33P1GSIn7lXSeSaeL8A6EfeJ9cJ/HvnqfuDG4m3jopW4HCbRPhhcKU0s649D0oyc0aDY8dv7IDW8wtMpvLz4+zmW0T9Fcwr/LS2hNfV5S3nKiwDUJ1R3fdQovfnLxwfqFCdEh2uJJ/jj4kQgDOSasOgJ8FI+2xYPm1VFj986SA9BY6jczVqhu91bjbpsDNqtlJbbRZkHE1btOVlcbyGJ5HW+wSHXty5jfuNvaBESXIcRCtLwdBwlvoYVjZ+f/Bw== -------------------------------------------------------------------------------- /sf/.env.dev: -------------------------------------------------------------------------------- 1 | # Local override of production secrets 2 | 3 | MAILER_PASSWORD=dummy 4 | 5 | # To create data in the Test environment using the Dev UI: 6 | 7 | # 1. Uncomment the following: 8 | #APP_ENV=test 9 | #DB_NAME=dbname_test 10 | #DB_USER=dbuser_test 11 | #CDN_URL=http://cdn-test.myproject:10080 12 | 13 | # 2. Comment out: 14 | # In sf/config/packages/test/framework.yaml: 15 | # framework.session.storage_id 16 | # framework.test.true 17 | 18 | # 3. Clear the test cache; 19 | # docker-compose run --rm --entrypoint php composer bin/console cache:clear --no-warmup --env=test 20 | -------------------------------------------------------------------------------- /sf/.env.master: -------------------------------------------------------------------------------- 1 | MAILER_PASSWORD=dummy 2 | -------------------------------------------------------------------------------- /sf/.env.test: -------------------------------------------------------------------------------- 1 | API_KEY=dummy.test 2 | APP_SECRET=dummy.test 3 | DB_PASSWORD=dbpwd_test 4 | MAILER_PASSWORD=dummy 5 | 6 | GCS_PRIVATE_BUCKET=app-test.myproject.info 7 | GCS_PUBLIC_BUCKET=cdn-test.myproject.info 8 | -------------------------------------------------------------------------------- /sf/.gitignore: -------------------------------------------------------------------------------- 1 | ###> symfony/framework-bundle ### 2 | /.env 3 | /.env.local 4 | /.env.*.local 5 | /bin/* 6 | !/bin/console 7 | /config/secrets/*/*.decrypt.private.php 8 | /public/bundles/ 9 | /var/ 10 | /vendor/ 11 | ###< symfony/framework-bundle ### 12 | 13 | ###> symfony/phpunit-bridge ### 14 | /.phpunit 15 | /.phpunit.result.cache 16 | /phpunit.xml 17 | ###< symfony/phpunit-bridge ### 18 | -------------------------------------------------------------------------------- /sf/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 24 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 25 | } 26 | 27 | if ($input->hasParameterOption('--no-debug', true)) { 28 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 29 | } 30 | 31 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 32 | 33 | if ($_SERVER['APP_DEBUG']) { 34 | umask(0000); 35 | 36 | if (class_exists(Debug::class)) { 37 | Debug::enable(); 38 | } 39 | } 40 | 41 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 42 | $application = new Application($kernel); 43 | $application->run($input); 44 | -------------------------------------------------------------------------------- /sf/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "license": "proprietary", 4 | "require": { 5 | "php": "^7.4", 6 | "ext-ctype": "*", 7 | "ext-iconv": "*", 8 | "craue/formflow-bundle": "~3.2", 9 | "geoip2/geoip2": "~2.0", 10 | "laminas/laminas-code": "^3.4", 11 | "laminas/laminas-escaper": "^2.6", 12 | "laminas/laminas-eventmanager": "^3.2", 13 | "league/flysystem-bundle": "^1.5", 14 | "mossadal/math-parser": "^1.3", 15 | "phpoffice/phpword": "^0.17.0", 16 | "ramsey/uuid-doctrine": "^1.6", 17 | "sensio/framework-extra-bundle": "^5.1", 18 | "superbalist/flysystem-google-storage": "^7.2", 19 | "symfony/asset": "5.2.*", 20 | "symfony/console": "5.2.*", 21 | "symfony/dotenv": "5.2.*", 22 | "symfony/expression-language": "5.2.*", 23 | "symfony/flex": "^1.1", 24 | "symfony/form": "5.2.*", 25 | "symfony/framework-bundle": "5.2.*", 26 | "symfony/intl": "5.2.*", 27 | "symfony/mailer": "5.2.*", 28 | "symfony/messenger": "5.2.*", 29 | "symfony/monolog-bundle": "^3.1", 30 | "symfony/orm-pack": "^2.1", 31 | "symfony/process": "5.2.*", 32 | "symfony/security-bundle": "5.2.*", 33 | "symfony/serializer-pack": "^1.0", 34 | "symfony/translation": "5.2.*", 35 | "symfony/twig-bundle": "5.2.*", 36 | "symfony/uid": "5.2.*", 37 | "symfony/validator": "5.2.*", 38 | "symfony/web-link": "5.2.*", 39 | "symfony/yaml": "5.2.*", 40 | "twig/cssinliner-extra": "^3.0", 41 | "twig/extra-bundle": "^3.0", 42 | "ua-parser/uap-php": "^3.9" 43 | }, 44 | "require-dev": { 45 | "dama/doctrine-test-bundle": "^6.5", 46 | "symfony/debug-pack": "*", 47 | "symfony/maker-bundle": "^1.0", 48 | "symfony/phpunit-bridge": "^5.2", 49 | "symfony/profiler-pack": "*", 50 | "symfony/test-pack": "*" 51 | }, 52 | "config": { 53 | "preferred-install": { 54 | "*": "dist" 55 | }, 56 | "sort-packages": true, 57 | "platform": { 58 | "php": "7.4" 59 | } 60 | }, 61 | "autoload": { 62 | "psr-4": { 63 | "App\\": "src/" 64 | } 65 | }, 66 | "autoload-dev": { 67 | "psr-4": { 68 | "App\\Tests\\": "tests/" 69 | } 70 | }, 71 | "scripts": { 72 | "auto-scripts": { 73 | "cache:clear": "symfony-cmd", 74 | "assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd", 75 | "assets:install %PUBLIC_DIR%": "symfony-cmd" 76 | }, 77 | "post-install-cmd": [ 78 | "@auto-scripts" 79 | ], 80 | "post-update-cmd": [ 81 | "@auto-scripts" 82 | ] 83 | }, 84 | "conflict": { 85 | "symfony/symfony": "*" 86 | }, 87 | "extra": { 88 | "symfony": { 89 | "id": "01C0DG6SEZ39ABT6Y5YM8T4HNA", 90 | "allow-contrib": false, 91 | "require": "5.2.*" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sf/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | =1.2) 13 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) { 14 | (new Dotenv(false))->populate($env); 15 | } else { 16 | // load all the .env files 17 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 18 | } 19 | 20 | $_SERVER += $_ENV; 21 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 22 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 23 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 24 | -------------------------------------------------------------------------------- /sf/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 6 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 7 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 8 | Craue\FormFlowBundle\CraueFormFlowBundle::class => ['all' => true], 9 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 10 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 11 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 12 | League\FlysystemBundle\FlysystemBundle::class => ['all' => true], 13 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 14 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 15 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 16 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 17 | DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], 18 | ]; 19 | -------------------------------------------------------------------------------- /sf/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /sf/config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /sf/config/packages/dev/easy_log_handler.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | EasyCorp\EasyLog\EasyLogHandler: 3 | public: false 4 | arguments: ['%kernel.logs_dir%/%kernel.environment%.log'] 5 | 6 | #// FIXME: How to add this configuration automatically without messing up with the monolog configuration? 7 | #monolog: 8 | # handlers: 9 | # buffered: 10 | # type: buffer 11 | # handler: easylog 12 | # channels: ['!event'] 13 | # level: debug 14 | # easylog: 15 | # type: service 16 | # id: EasyCorp\EasyLog\EasyLogHandler 17 | -------------------------------------------------------------------------------- /sf/config/packages/dev/flysystem.yaml: -------------------------------------------------------------------------------- 1 | flysystem: 2 | storages: 3 | storage.private.local: 4 | adapter: 'local' 5 | options: 6 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PRIVATE_BUCKET)%' 7 | storage.public.local: 8 | adapter: 'local' 9 | options: 10 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PUBLIC_BUCKET)%' 11 | -------------------------------------------------------------------------------- /sf/config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /sf/config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /sf/config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /sf/config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | # Adds a fallback DATABASE_URL if the env var is not set. 3 | # This allows you to run cache:warmup even if your 4 | # environment variables are not available yet. 5 | # You should not need to change this value. 6 | env(DATABASE_URL): '' 7 | 8 | doctrine: 9 | dbal: 10 | # configure these for your database server 11 | driver: 'pdo_mysql' 12 | server_version: '5.7' 13 | charset: utf8mb4 14 | default_table_options: 15 | charset: utf8mb4 16 | collate: utf8mb4_unicode_ci 17 | 18 | dbname: '%env(DB_NAME)%' 19 | user: '%env(DB_USER)%' 20 | password: '%env(DB_PASSWORD)%' 21 | host: '%env(DB_HOST)%' 22 | port: '%env(DB_PORT)%' 23 | # the following needed as Doctrine doesn't natively support ENUM 24 | mapping_types: 25 | enum: string 26 | # UUID custom type 27 | types: 28 | uuid: Ramsey\Uuid\Doctrine\UuidType 29 | 30 | orm: 31 | auto_generate_proxy_classes: true 32 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 33 | auto_mapping: true 34 | mappings: 35 | App: 36 | is_bundle: false 37 | type: annotation 38 | dir: '%kernel.project_dir%/src/Entity' 39 | prefix: 'App\Entity' 40 | alias: App 41 | -------------------------------------------------------------------------------- /sf/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | -------------------------------------------------------------------------------- /sf/config/packages/flysystem.yaml: -------------------------------------------------------------------------------- 1 | flysystem: 2 | storages: 3 | storage.private.local: 4 | adapter: 'local' 5 | options: 6 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PRIVATE_BUCKET)%' 7 | storage.public.local: 8 | adapter: 'local' 9 | options: 10 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PUBLIC_BUCKET)%' 11 | storage.private.gcloud: 12 | adapter: 'gcloud' 13 | options: 14 | client: 'Google\Cloud\Storage\StorageClient' # The service ID of the Google\Cloud\Storage\StorageClient instance 15 | bucket: '%env(GCS_PRIVATE_BUCKET)%' 16 | prefix: '' 17 | api_url: 'https://storage.googleapis.com' 18 | storage.public.gcloud: 19 | adapter: 'gcloud' 20 | options: 21 | client: 'Google\Cloud\Storage\StorageClient' # The service ID of the Google\Cloud\Storage\StorageClient instance 22 | bucket: '%env(GCS_PUBLIC_BUCKET)%' 23 | prefix: '' 24 | api_url: 'https://storage.googleapis.com' 25 | # Aliases based on environment variable 26 | storage.private: 27 | adapter: 'lazy' 28 | options: 29 | source: 'storage.private.%env(STORAGE_ADAPTER)%' 30 | storage.public: 31 | adapter: 'lazy' 32 | options: 33 | source: 'storage.public.%env(STORAGE_ADAPTER)%' 34 | -------------------------------------------------------------------------------- /sf/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | default_locale: '%locale%' 5 | csrf_protection: false 6 | http_method_override: true 7 | 8 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 9 | # Remove or comment this section to explicitly disable session support. 10 | session: 11 | handler_id: null 12 | cookie_secure: auto 13 | cookie_samesite: lax 14 | 15 | #esi: true 16 | #fragments: true 17 | php_errors: 18 | log: true 19 | 20 | trusted_proxies: '%env(TRUSTED_PROXIES)%' 21 | trusted_headers: ['x-forwarded-for', 'x-forwarded-host'] 22 | -------------------------------------------------------------------------------- /sf/config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | transports: 4 | #main: '%env(MAILER_DSN)%' 5 | #realtime_mailer: 'sendmail://localhost' 6 | # Uses the sendmail binary and options configured in the sendmail_path setting of php.ini 7 | realtime_mailer: 'native://default' 8 | -------------------------------------------------------------------------------- /sf/config/packages/messenger.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | messenger: 3 | # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. 4 | failure_transport: failed 5 | 6 | transports: 7 | # https://symfony.com/doc/current/messenger.html#transport-configuration 8 | failed: 'doctrine://default?queue_name=failed' 9 | # async: '%env(MESSENGER_TRANSPORT_DSN)%' 10 | # sync: 'sync://' 11 | batch_mailer: 'doctrine://default?queue_name=batch_mailer' 12 | realtime_mailer: 'doctrine://default?queue_name=realtime_mailer' 13 | 14 | routing: 15 | # Route your messages to the transports 16 | 'Symfony\Component\Mailer\Messenger\SendEmailMessage': realtime_mailer 17 | # 'App\Message\YourMessage': async 18 | -------------------------------------------------------------------------------- /sf/config/packages/oat/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" 9 | -------------------------------------------------------------------------------- /sf/config/packages/oat/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /sf/config/packages/oat/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: grouped 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | grouped: 10 | type: group 11 | members: [streamed, deduplicated] 12 | streamed: 13 | type: rotating_file 14 | path: "%kernel.logs_dir%/%kernel.environment%.log" 15 | level: debug 16 | max_files: 10 17 | deduplicated: 18 | type: deduplication 19 | handler: symfony_mailer 20 | symfony_mailer: 21 | type: symfony_mailer 22 | from_email: "%email_from%" 23 | to_email: "%email_from%" 24 | subject: 'An Error Occurred! %%message%%' 25 | level: debug 26 | formatter: monolog.formatter.html 27 | content_type: text/html 28 | -------------------------------------------------------------------------------- /sf/config/packages/oat/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /sf/config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" 9 | -------------------------------------------------------------------------------- /sf/config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /sf/config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: grouped 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | grouped: 10 | type: group 11 | members: [streamed, deduplicated] 12 | streamed: 13 | type: rotating_file 14 | path: "%kernel.logs_dir%/%kernel.environment%.log" 15 | level: debug 16 | max_files: 10 17 | deduplicated: 18 | type: deduplication 19 | handler: symfony_mailer 20 | symfony_mailer: 21 | type: symfony_mailer 22 | from_email: "%email_from%" 23 | to_email: "%email_from%" 24 | subject: 'An Error Occurred! %%message%%' 25 | level: debug 26 | formatter: monolog.formatter.html 27 | content_type: text/html 28 | -------------------------------------------------------------------------------- /sf/config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /sf/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | -------------------------------------------------------------------------------- /sf/config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | providers: 3 | db: 4 | entity: 5 | class: App\Entity\User 6 | property: email 7 | firewalls: 8 | # for the time being, completely open API resources 9 | api: 10 | pattern: ^/api 11 | stateless: true 12 | anonymous: true 13 | security: false 14 | 15 | # disables authentication for assets and the profiler, adapt it according to your needs 16 | dev: 17 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 18 | security: false 19 | 20 | main: 21 | anonymous: true 22 | lazy: true 23 | provider: db 24 | form_login: 25 | login_path: / 26 | check_path: /login 27 | username_parameter: _email 28 | password_parameter: _password 29 | success_handler: App\Security\AuthenticationSuccessHandler 30 | failure_handler: App\Security\AuthenticationFailureHandler 31 | logout: 32 | path: /logout 33 | invalidate_session: true 34 | remember_me: 35 | secret: '%env(APP_SECRET)%' 36 | path: / 37 | always_remember_me: true 38 | switch_user: { role: ROLE_ADMIN, parameter: _su } 39 | user_checker: App\Security\UserChecker 40 | # guard: 41 | # authenticators: 42 | # - App\Security\PasswordResetAuthenticator 43 | #provider: users_in_memory 44 | 45 | # Order is critical: first matched URL applies, so order by more specific first 46 | access_control: 47 | # Admin section 48 | - { path: '^/admin/', roles: [ROLE_ADMIN] } 49 | - { path: '^/admin', roles: [IS_AUTHENTICATED_ANONYMOUSLY] } 50 | # User section 51 | - { path: '^/user/', roles: [ROLE_CLIENTUSER] } 52 | - { path: '^/user', roles: [IS_AUTHENTICATED_ANONYMOUSLY] } 53 | # Home page 54 | - { path: '^/', roles: [IS_AUTHENTICATED_ANONYMOUSLY] } 55 | # https://symfony.com/doc/current/security.html#firewalls-authentication 56 | role_hierarchy: 57 | ROLE_CLIENTADMIN: [ROLE_CLIENTUSER] 58 | # https://symfony.com/doc/current/security/impersonating_user.html 59 | # switch_user: true 60 | encoders: 61 | App\Entity\User: 62 | algorithm: auto 63 | -------------------------------------------------------------------------------- /sf/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /sf/config/packages/test/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | #app: cache.adapter.filesystem 8 | # The data in this cache should persist between deploys. 9 | # Other options include: 10 | 11 | # Redis 12 | #app: cache.adapter.redis 13 | #default_redis_provider: redis://localhost 14 | 15 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 16 | #app: cache.adapter.apcu 17 | 18 | # Namespaced pools use the above "app" backend by default 19 | #pools: 20 | #my.dedicated.cache: null 21 | -------------------------------------------------------------------------------- /sf/config/packages/test/dama_doctrine_test_bundle.yaml: -------------------------------------------------------------------------------- 1 | dama_doctrine_test: 2 | enable_static_connection: true 3 | enable_static_meta_data_cache: true 4 | enable_static_query_cache: true 5 | -------------------------------------------------------------------------------- /sf/config/packages/test/flysystem.yaml: -------------------------------------------------------------------------------- 1 | flysystem: 2 | storages: 3 | storage.private.local: 4 | adapter: 'local' 5 | options: 6 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PRIVATE_BUCKET)%' 7 | storage.public.local: 8 | adapter: 'local' 9 | options: 10 | directory: '%kernel.project_dir%/../local-storage/%env(GCS_PUBLIC_BUCKET)%' 11 | -------------------------------------------------------------------------------- /sf/config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | secrets: ~ 4 | session: 5 | storage_id: session.storage.mock_file 6 | -------------------------------------------------------------------------------- /sf/config/packages/test/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | transports: 4 | #main: '%env(MAILER_DSN)%' 5 | realtime_mailer: 'null://default' 6 | -------------------------------------------------------------------------------- /sf/config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /sf/config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /sf/config/packages/test/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | encoders: 3 | App\Entity\User: 4 | # Faster login for tests 5 | cost: 4 6 | algorithm: auto 7 | -------------------------------------------------------------------------------- /sf/config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /sf/config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /sf/config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /sf/config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | -------------------------------------------------------------------------------- /sf/config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | debug: '%kernel.debug%' 4 | strict_variables: '%kernel.debug%' 5 | 6 | # Twig helper services 7 | globals: 8 | # Services 9 | some_utils: '@App\Utils\SomeUtils' 10 | 11 | # Parameters 12 | cdn_url: "%cdn_url%" 13 | cdn_url_from_cluster: "%cdn_url_from_cluster%" 14 | site_url: "%router.request_context.scheme%://%router.request_context.host%" 15 | 16 | # Asset cache busting 17 | asset_v: "%asset_v%" 18 | 19 | # Global form theming 20 | form_themes: 21 | - 'form/form_fields.html.twig' 22 | 23 | # Deprecation - see https://github.com/symfony/symfony/blob/master/UPGRADE-4.4.md 24 | # exception_controller: null 25 | 26 | paths: 27 | '%kernel.project_dir%/templates/email': email 28 | -------------------------------------------------------------------------------- /sf/config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /sf/config/preload.php: -------------------------------------------------------------------------------- 1 | get() won't work. 36 | # The best practice is to be explicit about your dependencies anyway. 37 | bind: 38 | $projectDir: '%kernel.project_dir%' 39 | $api_key: '%api_key%' 40 | $email_from: '%email_from%' 41 | # $mailer_batch: '@mailer.messenger.batch' 42 | # $mailer_realtime: '@mailer.messenger.realtime' 43 | 44 | # makes classes in src/ available to be used as services 45 | # this creates a service per class whose id is the fully-qualified class name 46 | App\: 47 | resource: '../src/*' 48 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' 49 | 50 | # controllers are imported separately to make sure services can be injected 51 | # as action arguments even if you don't extend any base controller class 52 | App\Controller\: 53 | resource: '../src/Controller' 54 | tags: ['controller.service_arguments'] 55 | 56 | # add more service definitions when explicit configuration is needed 57 | # please note that last definitions always *replace* previous ones 58 | 59 | # Success handler after authentication - sets session variables user, user_name, company, company_name, log audit event 60 | App\Security\AuthenticationSuccessHandler: ~ 61 | 62 | # Failure handler after authentication - log audit event 63 | App\Security\AuthenticationFailureHandler: ~ 64 | 65 | # Logout event listener 66 | App\EventListener\LogoutListener: 67 | tags: 68 | - name: 'kernel.event_listener' 69 | event: 'Symfony\Component\Security\Http\Event\LogoutEvent' 70 | dispatcher: security.event_dispatcher.main 71 | 72 | # Authentication event listener 73 | App\EventListener\AuthenticationListener: 74 | tags: 75 | - { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure } 76 | - { name: kernel.event_listener, event: security.interactive_login, method: onAuthenticationSuccess } 77 | 78 | # Switch user event listener 79 | App\EventListener\SwitchUserListener: 80 | tags: 81 | - { name: kernel.event_listener, event: security.switch_user, method: onSwitchUser } 82 | 83 | # To manually create a RememberMe cookie after programmatic login 84 | Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices: 85 | public: true 86 | arguments: 87 | - ['@security.authentication_utils'] 88 | - '%env(APP_SECRET)%' 89 | - 'db' 90 | - { name: 'REMEMBERME', lifetime: 31536000, path: '/', domain: ~, always_remember_me: true, remember_me_parameter: '_remember_me' } 91 | # 'name' => 'REMEMBERME', 92 | # 'lifetime' => 31536000, 93 | # 'path' => '/', 94 | # 'domain' => null, 95 | # 'secure' => false, 96 | # 'httponly' => true, 97 | # 'always_remember_me' => false, 98 | # 'remember_me_parameter' => '_remember_me', 99 | 100 | # user_update.listener: 101 | App\EventListener\UserUpdateListener: 102 | tags: 103 | - { name: doctrine.event_listener, event: preUpdate } 104 | 105 | # Google Cloud storage (Flysystem) 106 | Google\Cloud\Storage\StorageClient: ~ 107 | -------------------------------------------------------------------------------- /sf/config/services_craue.yml: -------------------------------------------------------------------------------- 1 | # Separate file to avoid _defaults.bind error when calling a parent class as _defaults apply at file level 2 | services: 3 | # Craue multi-page form 4 | App\Form\SomeEntity\SomeFlow: 5 | autowire: true 6 | autoconfigure: false 7 | public: true 8 | parent: craue.form.flow 9 | tags: 10 | - { name: form.type } 11 | -------------------------------------------------------------------------------- /sf/config/services_dev.yaml: -------------------------------------------------------------------------------- 1 | # parameters: 2 | -------------------------------------------------------------------------------- /sf/config/services_test.yaml: -------------------------------------------------------------------------------- 1 | # parameters: 2 | -------------------------------------------------------------------------------- /sf/migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/migrations/.gitignore -------------------------------------------------------------------------------- /sf/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | tests 56 | 57 | 58 | 59 | 60 | 61 | src 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /sf/public/css/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/public/css/index.html -------------------------------------------------------------------------------- /sf/public/img/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/public/img/index.html -------------------------------------------------------------------------------- /sf/public/index.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 11 | 12 | if ($_SERVER['APP_DEBUG']) { 13 | umask(0000); 14 | 15 | Debug::enable(); 16 | } 17 | 18 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 19 | $request = Request::createFromGlobals(); 20 | $response = $kernel->handle($request); 21 | $response->send(); 22 | $kernel->terminate($request, $response); 23 | -------------------------------------------------------------------------------- /sf/public/js/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/public/js/index.html -------------------------------------------------------------------------------- /sf/src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/src/Controller/.gitignore -------------------------------------------------------------------------------- /sf/src/Controller/MailController.php: -------------------------------------------------------------------------------- 1 | vu = $vu; 29 | } 30 | 31 | /** 32 | * Send spooled emails (real-time) 33 | * 34 | * @Route("/api/email_send_realtime/{api_key}", name="email_send_realtime", 35 | * requirements={"api_key": "[\w]{12}"}) 36 | */ 37 | public function emailSendRealtimeAction(KernelInterface $kernel, string $api_key) 38 | { 39 | // Validate API key 40 | $this->vu->validateApiKey($api_key); 41 | 42 | $application = new Application($kernel); 43 | $application->setAutoExit(false); 44 | 45 | $input = new ArrayInput([ 46 | 'command' => 'messenger:consume', 47 | 'receivers' => ['realtime_mailer'], 48 | '--limit' => 20, 49 | '--time-limit' => 100, 50 | ]); 51 | $output = new NullOutput(); 52 | $application->run($input, $output); 53 | return new Response(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /sf/src/Controller/SecurityController.php: -------------------------------------------------------------------------------- 1 | auth Authentication Utilities */ 24 | private $auth; 25 | 26 | public function __construct( 27 | AuthenticationUtils $auth, 28 | RouterInterface $router 29 | ) { 30 | $this->auth = $auth; 31 | $this->router = $router; 32 | } 33 | 34 | 35 | /** 36 | * Display login form 37 | */ 38 | public function loginEmbeddedShowAction( 39 | Request $request, 40 | $site_domain, 41 | $token = null 42 | ) { 43 | // Evaluate the Target paths - your code here 44 | // $target_path = ... 45 | // $failure_path = ... 46 | 47 | $form = $this->createForm(LoginForm::class, [ 48 | 'site_domain' => $site_domain, 49 | // Target paths 50 | '_target_path' => $target_path, 51 | '_failure_path' => $failure_path, 52 | ]); 53 | 54 | return $this->render('security/login.html.twig', [ 55 | 'form' => $form->createView(), 56 | ]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sf/src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/src/Entity/.gitignore -------------------------------------------------------------------------------- /sf/src/EventListener/AuthenticationListener.php: -------------------------------------------------------------------------------- 1 | getRequest(); 30 | $user = $event->getAuthenticationToken()->getUser(); 31 | // Initiate session 32 | // your code here 33 | } 34 | 35 | /** 36 | * onAuthenticationFailure 37 | * 38 | * @param AuthenticationFailureEvent $event 39 | */ 40 | public function onAuthenticationFailure( 41 | AuthenticationFailureEvent $event 42 | ) { 43 | $token = $event->getAuthenticationToken(); 44 | $username = $token->getUsername(); 45 | 46 | // your code here 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sf/src/Kernel.php: -------------------------------------------------------------------------------- 1 | import('../config/{packages}/*.yaml'); 17 | $container->import('../config/{packages}/'.$this->environment.'/*.yaml'); 18 | 19 | if (is_file(\dirname(__DIR__).'/config/services.yaml')) { 20 | $container->import('../config/services.yaml'); 21 | $container->import('../config/{services}_'.$this->environment.'.yaml'); 22 | } elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) { 23 | (require $path)($container->withPath($path), $this); 24 | } 25 | } 26 | 27 | protected function configureRoutes(RoutingConfigurator $routes): void 28 | { 29 | $routes->import('../config/{routes}/'.$this->environment.'/*.yaml'); 30 | $routes->import('../config/{routes}/*.yaml'); 31 | 32 | if (is_file(\dirname(__DIR__).'/config/routes.yaml')) { 33 | $routes->import('../config/routes.yaml'); 34 | } elseif (is_file($path = \dirname(__DIR__).'/config/routes.php')) { 35 | (require $path)($routes->withPath($path), $this); 36 | } 37 | } 38 | 39 | public function getCacheDir() 40 | { 41 | // return dirname(__DIR__).'/var/'.$this->environment.'/cache'; 42 | return dirname(__DIR__).'/../cache/'.$this->environment; 43 | } 44 | 45 | public function getLogDir() 46 | { 47 | // return dirname(__DIR__).'/var/'.$this->environment.'/log'; 48 | return dirname(__DIR__).'/../log/'.$this->environment; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /sf/src/Migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/src/Migrations/.gitignore -------------------------------------------------------------------------------- /sf/src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/src/Repository/.gitignore -------------------------------------------------------------------------------- /sf/src/Security/AuthenticationFailureHandler.php: -------------------------------------------------------------------------------- 1 | router = $router; 26 | } 27 | 28 | /** 29 | * This is called when an interactive authentication attempt succeeds. This 30 | * is called by authentication listeners inheriting from AbstractAuthenticationListener. 31 | * @param Request $request 32 | * @param TokenInterface $token 33 | * @return Response The response to return 34 | */ 35 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception) 36 | { 37 | // Flash message 38 | $request->getSession()->getFlashBag()->add('error', 'Incorrect email or password'); 39 | // The failure redirection path 40 | $path = !is_null($request->get('_failure_path')) ? $request->get('_failure_path') : '/'; 41 | return new RedirectResponse($path); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sf/src/Security/AuthenticationSuccessHandler.php: -------------------------------------------------------------------------------- 1 | router = $router; 33 | $this->session = $session; 34 | } 35 | 36 | /** 37 | * This is called when an interactive authentication attempt succeeds. This 38 | * is called by authentication listeners inheriting from AbstractAuthenticationListener. 39 | * @param Request $request 40 | * @param TokenInterface $token 41 | * @return Response The response to return 42 | */ 43 | public function onAuthenticationSuccess(Request $request, TokenInterface $token) 44 | { 45 | // Session variables are set by the AuthenticationListener 46 | 47 | // The failure redirection path 48 | $path = !is_null($request->get('_target_path')) ? $request->get('_target_path') : '/'; 49 | return new RedirectResponse($path); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sf/src/Twig/JsonDecodeExtension.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 11 | } 12 | -------------------------------------------------------------------------------- /sf/translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericjacolin/symfony-gke/1abfc128e9ed77b55bf5013ed3999973b0dbda2e/sf/translations/.gitignore --------------------------------------------------------------------------------