diff --git a/_twig/docker-compose.yml/service/chrome.yml.twig b/_twig/docker-compose.yml/service/chrome.yml.twig new file mode 100644 index 00000000..c8a1c813 --- /dev/null +++ b/_twig/docker-compose.yml/service/chrome.yml.twig @@ -0,0 +1,7 @@ + chrome: + image: yukinying/chrome-headless-browser:latest + command: ["--no-sandbox", "--disable-gpu", "--headless", "--disable-dev-shm-usage", "--remote-debugging-address=0.0.0.0", "--remote-debugging-port=9222", "--user-data-dir=/data"] + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/cron.yml.twig b/_twig/docker-compose.yml/service/cron.yml.twig new file mode 100644 index 00000000..49e8964e --- /dev/null +++ b/_twig/docker-compose.yml/service/cron.yml.twig @@ -0,0 +1,22 @@ + cron: + build: + context: ./ + dockerfile: .my127ws/docker/image/cron/Dockerfile +{% if @('app.build') == 'dynamic' %} + volumes: + - {{ (syncvolume) ? @('workspace.name') ~ '-sync:/app:nocopy' : ('./:/app' ~ @('docker.compose.host_volume_options')) }} + - ./.my127ws/application:/home/build/application +{% else %} + image: {{ @('docker.repository') ~ ':' ~ @('app.version') ~ '-cron' }} +{% endif %} + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-base.environment'), + @('services.cron.environment'), + @('services.php-base.environment_secrets'), + @('services.cron.environment_secrets') + ]), 2, 6) | raw }} +{% include blocks ~ 'environment.yml.twig' %} + networks: + - private + labels: + - traefik.enable=false diff --git a/_twig/docker-compose.yml/service/elasticsearch.yml.twig b/_twig/docker-compose.yml/service/elasticsearch.yml.twig new file mode 100644 index 00000000..b3eee557 --- /dev/null +++ b/_twig/docker-compose.yml/service/elasticsearch.yml.twig @@ -0,0 +1,12 @@ + elasticsearch: + image: {{ @('elasticsearch.image') }}:{{ @('elasticsearch.tag') }} + labels: + - traefik.enable=false + environment: + ES_JAVA_OPTS: -Xms512m -Xmx512m + discovery.type: single-node + networks: + - private + ports: + - 9200 + - 9300 diff --git a/_twig/docker-compose.yml/service/memcached.yml.twig b/_twig/docker-compose.yml/service/memcached.yml.twig new file mode 100644 index 00000000..f4116b00 --- /dev/null +++ b/_twig/docker-compose.yml/service/memcached.yml.twig @@ -0,0 +1,6 @@ + memcached: + image: memcached:1-alpine + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/mysql.yml.twig b/_twig/docker-compose.yml/service/mysql.yml.twig new file mode 100644 index 00000000..842071f9 --- /dev/null +++ b/_twig/docker-compose.yml/service/mysql.yml.twig @@ -0,0 +1,19 @@ +{% set command = [] %} +{% for var,value in @('database.var') -%} + {% set command = command|merge(['--' ~ var ~ '=' ~ value]) %} +{% endfor %} + mysql: + image: {{ @('mysql.image') }}:{{ @('mysql.tag') }} + labels: + - traefik.enable=false +{% if command|length %} + command: {{ command|join(' ') }} +{% endif %} + environment: {{ to_nice_yaml(deep_merge([ + @('services.mysql.environment'), + @('services.mysql.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private + ports: + - {% if @('database.port_forward') %}{{ @('database.port_forward') }}:{% endif %}3306 diff --git a/_twig/docker-compose.yml/service/php-fpm-exporter.yml.twig b/_twig/docker-compose.yml/service/php-fpm-exporter.yml.twig new file mode 100644 index 00000000..07885237 --- /dev/null +++ b/_twig/docker-compose.yml/service/php-fpm-exporter.yml.twig @@ -0,0 +1,12 @@ + php-fpm-exporter: + image: hipages/php-fpm_exporter + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-fpm-exporter.environment'), + @('services.php-fpm-exporter.environment_secrets') + ]), 2, 6) | raw }} + labels: + - traefik.enable=false + depends_on: + - php-fpm + networks: + - private diff --git a/_twig/docker-compose.yml/service/postgres.yml.twig b/_twig/docker-compose.yml/service/postgres.yml.twig new file mode 100644 index 00000000..3327e480 --- /dev/null +++ b/_twig/docker-compose.yml/service/postgres.yml.twig @@ -0,0 +1,12 @@ + postgres: + image: postgres:9.6 + labels: + - traefik.enable=false + environment: {{ to_nice_yaml(deep_merge([ + @('services.postgres.environment'), + @('services.postgres.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private + ports: + - 5432 diff --git a/_twig/docker-compose.yml/service/rabbitmq.yml.twig b/_twig/docker-compose.yml/service/rabbitmq.yml.twig new file mode 100644 index 00000000..700b11e2 --- /dev/null +++ b/_twig/docker-compose.yml/service/rabbitmq.yml.twig @@ -0,0 +1,16 @@ + rabbitmq: + image: {{ @('rabbitmq.image') }}:{{ @('rabbitmq.tag') }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.rabbitmq.environment'), + @('services.rabbitmq.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private + - shared + labels: + - traefik.backend={{ @('rabbitmq.host') }}-{{ @('workspace.name') }} + - traefik.frontend.rule=Host:{{ @('rabbitmq.external_host') }} + - traefik.docker.network=my127ws + - traefik.port={{ @('rabbitmq.api_port') }} + - co.elastic.logs/module=rabbitmq + - co.elastic.metrics/module=rabbitmq diff --git a/_twig/docker-compose.yml/service/redis-session.yml.twig b/_twig/docker-compose.yml/service/redis-session.yml.twig new file mode 100644 index 00000000..44c60720 --- /dev/null +++ b/_twig/docker-compose.yml/service/redis-session.yml.twig @@ -0,0 +1,8 @@ + redis-session: + image: redis:4-alpine + # 1GB; evict key that would expire soonest + command: redis-server --maxmemory 1073742000 --maxmemory-policy volatile-ttl --save 3600 1 --save 300 100 --save 60 10000 + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/redis.yml.twig b/_twig/docker-compose.yml/service/redis.yml.twig new file mode 100644 index 00000000..978a77f3 --- /dev/null +++ b/_twig/docker-compose.yml/service/redis.yml.twig @@ -0,0 +1,8 @@ + redis: + image: redis:4-alpine + # 1GB; evict any least recently used key even if they don't have a TTL + command: redis-server --maxmemory 1073742000 --maxmemory-policy allkeys-lru --save 3600 1 --save 300 100 --save 60 10000 + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/varnish.yml.twig b/_twig/docker-compose.yml/service/varnish.yml.twig new file mode 100644 index 00000000..df1a27ba --- /dev/null +++ b/_twig/docker-compose.yml/service/varnish.yml.twig @@ -0,0 +1,49 @@ +{% if 'varnish' in @('app.services') %} +{% set hostnames = [@('hostname')] %} +{% set hostnames = hostnames|merge(@('hostname_aliases')|map(alias => "#{alias}." ~ @('domain'))) %} + varnish: + image: varnish:6 + labels: + - traefik.backend={{ @('workspace.name') }} + - traefik.frontend.rule=Host:{{ hostnames|join(',') }} + - traefik.docker.network=my127ws + - traefik.port=80 + environment: {{ to_nice_yaml(deep_merge([ + @('services.varnish.environment'), + @('services.varnish.environment_secrets') + ]), 2, 6) | raw }} + links: + - nginx:nginx + volumes: + - .my127ws/docker/image/varnish/root/etc/varnish/default.vcl:/etc/varnish/default.vcl:ro + - type: tmpfs + target: /var/lib/varnish:exec + tmpfs: + size: "100000" + networks: + private: + aliases: + - varnish-0.varnish-headless +{% if @('replicas.varnish') > 1 %} +{% for instanceNumber in 1..(@('replicas.varnish')-1) %} + - varnish-{{ instanceNumber }}.varnish-headless +{% endfor %} +{% endif %} + shared: {} + + # Provide TLS offloading for integration tests via varnish + tls-offload: + build: + context: .my127ws/docker/image/tls-offload/ + labels: + - traefik.enable=false + links: + - varnish:varnish + networks: + private: + aliases: +{% for alias in hostnames %} + - {{ alias }} +{% endfor %} + shared: {} +{% endif %} diff --git a/application/overlay/.dockerignore.twig b/application/overlay/.dockerignore.twig new file mode 100644 index 00000000..96c90adc --- /dev/null +++ b/application/overlay/.dockerignore.twig @@ -0,0 +1,8 @@ +{% set build = @('app.build') %} +{% set blocks = 'application/overlay/_twig/.dockerignore/' %} + +{% if build == 'dynamic' %} +{% include blocks ~ 'dynamic.twig' %} +{% else %} +{% include blocks ~ 'static.twig' %} +{% endif %} diff --git a/application/overlay/Jenkinsfile.twig b/application/overlay/Jenkinsfile.twig new file mode 100644 index 00000000..fbc9dec0 --- /dev/null +++ b/application/overlay/Jenkinsfile.twig @@ -0,0 +1,69 @@ +pipeline { + agent { label "my127ws" } + environment { + MY127WS_KEY = credentials('{{ @('jenkins.credentials.my127ws_key') }}') + MY127WS_ENV = "pipeline" + } + triggers { cron(env.BRANCH_NAME == '{{ @('git.main_branch') }}' ? 'H H(0-6) * * *' : '') } + stages { + stage('Build') { + steps { + sh 'ws install' + milestone(10) + } + } + stage('Checks without development dependencies') { + steps { + sh 'ws exec composer test-production-quality' + sh 'ws exec app composer:development_dependencies' + milestone(20) + } + } + stage('Test') { + parallel { + stage('quality') { steps { sh 'ws exec composer test-quality' } } + stage('unit') { steps { sh 'ws exec composer test-unit' } } + stage('acceptance') { steps { sh 'ws exec composer test-acceptance' } } + stage('helm kubeval qa') { steps { sh 'ws helm kubeval qa' } } + } + } +{% if @('pipeline.publish.enabled') == 'yes' %} + stage('Publish') { + when { not { triggeredBy 'TimerTrigger' } } + steps { + milestone(50) + sh 'ws app publish' +{% if @('pipeline.publish.chart.enabled') %} + sh 'ws app publish chart "${GIT_BRANCH}" "{{ @('workspace.name') }} build artifact ${GIT_COMMIT}"' +{% endif %} + } + } +{% endif %} +{% if @('pipeline.qa.enabled') == 'yes' %} + stage('Deploy (QA)') { + environment { +{% for key, value in @('pipeline.qa.environment') %} + {{ key }} = {{ value|raw }} +{% endfor %} + } + when { + not { triggeredBy 'TimerTrigger' } + branch '{{ @('pipeline.qa.branch') }}' + } + steps { + milestone(100) + lock(resource: '{{ @('workspace.name') }}-qa-deploy', inversePrecedence: true) { + milestone(101) + sh 'ws app deploy qa' + } + } + } +{% endif %} + } + post { + always { + sh 'ws destroy' + cleanWs() + } + } +} diff --git a/application/overlay/_twig/.dockerignore/dynamic.twig b/application/overlay/_twig/.dockerignore/dynamic.twig new file mode 100644 index 00000000..4ebb7d2c --- /dev/null +++ b/application/overlay/_twig/.dockerignore/dynamic.twig @@ -0,0 +1,2 @@ +* +!.my127ws diff --git a/application/overlay/_twig/.dockerignore/static.twig b/application/overlay/_twig/.dockerignore/static.twig new file mode 100644 index 00000000..c007eac9 --- /dev/null +++ b/application/overlay/_twig/.dockerignore/static.twig @@ -0,0 +1 @@ +# no need for exclusions for now diff --git a/application/overlay/auth.json.twig b/application/overlay/auth.json.twig new file mode 100644 index 00000000..0731d871 --- /dev/null +++ b/application/overlay/auth.json.twig @@ -0,0 +1,15 @@ +{ + "http-basic": { + {% for repo in @('composer.auth.basic') %} + "{{ repo.path }}": { + "username": "{{ repo.username }}", + "password": "{{ repo.password }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + }, + "github-oauth": { + {% if @('composer.auth.github') %} + "github.com": "{{ @('composer.auth.github') }}" + {% endif %} + } +} diff --git a/application/skeleton/README.md.twig b/application/skeleton/README.md.twig new file mode 100644 index 00000000..7d563b33 --- /dev/null +++ b/application/skeleton/README.md.twig @@ -0,0 +1,206 @@ +{% set blocks = 'application/skeleton/_twig/README.md/' %} +# {{ @('workspace.name') }} + +Please follow the below steps to get started, if you encounter any issues installing the dependencies or provisioning the development environment, please check the [Common Issues](#common-issues) section first. + +## Development Environment + +### Getting Started + +#### Prerequisites + +##### General + +- Access to LastPass folders + - `Shared-{{ @('workspace.name') }}-Servers` and `Shared-{{ @('workspace.name') }}-Accounts` + +##### Docker + +- A working Docker setup + - On MacOS, use [Docker for Mac](https://docs.docker.com/docker-for-mac/install/). + - On Linux, add the official Docker repository and install the "docker-ce" package. + You will also need to have a recent [docker-compose](https://docs.docker.com/compose/install/) version - at least `1.24.0`. + +#### Setup + +1. Install [workspace](https://github.com/my127/workspace) +2. Copy the LastPass entry "{{ @('workspace.name') }}: Development Environment Key" to a new blank file named `workspace.override.yml` in the project root. +3. Run `ws install` + +Once installed, the site should be available at [https://{{ @('hostname') }}](https://{{ @('hostname') }}). + +### Development environment cleanup + +To stop the development environment, run `ws disable`. + +To start the development environment again, run `ws enable`. + +To remove the development environment, run `ws destroy`. + +### Frontend + +The frontend build should be automatically done as part of bringing up the environment. + +To trigger a rebuild, run `ws frontend build`. + +To watch for changes, run `ws frontend watch`. + +To gain access to the `console` container where the builds happen: `ws frontend console`. + +### Harness Version Updates + +If you have been notified that a harness version upgrade is available by your team, do the following. + +If you have an existing environment running: +```bash +ws harness update existing +``` + +or if you don't have one running right now or would like to set up from fresh: +```bash +ws harness update fresh +``` + +{% for readme_block in @('framework.readme_blocks') %} +{% include blocks ~ readme_block ~ '.md.twig' %} +{% endfor %} + +{% if "mysql" in @('app.services') -%} +### MySQL Access + +MySQL can be used either via command line tools via `ws db console` or via GUI tools. + +In your GUI tool, set up a new connection to localhost with the port being the returned port number from: +```bash +ws port mysql +``` + +{% if @('database.port_forward') == "" -%} +This port will change each time the project environment is started, as docker will allocate a random unused port +on your host machine. + +To set a consistent port for this project, choose a port number that you think will be unique across all projects +that your developers will encounter (e.g. not 3306!). Acceptable range of ports is 1-65535. + +Once you have a port number, you can define it in the workspace.yml with: +```yaml +attribute('database.port_forward'): portNumberHere +``` +{%- endif %} + +The connection username and password is listed under the `mysql` service environment section in `docker-compose.yml` in +the project root. +{% endif %} + +### Xdebug + +Xdebug is turned off by default as it drastically slows down requests for all developers. + +To enable, run `ws feature xdebug on`. To turn off again, `ws feature xdebug off`. + +To enable on CLI in `ws console`, run `ws feature xdebug cli on`. To turn off again, `ws feature xdebug cli off`. + +Xdebug is set up to listen to your computer's 9000 port once enabled, so all you would need to do in your IDE is: +1. Create a mapping from the project root to `/app` for name `workspace`, hostname `localhost` and port 80. +2. Depending on the IDE, you may have to configure the settings for the IDE to have idekey being `workspace`. + Some IDEs also need to restart after changing this. Not required for PhpStorm. +3. Listen for connections. +4. If trying to debug a website, configure the IDE key of browser Xdebug extension such as one listed on + [Browser Debugging Extensions](https://www.jetbrains.com/help/phpstorm/browser-debugging-extensions.html) + to be `workspace`, toggle debug on, then refresh the page in the browser +5. If trying to debug a CLI application, re-run the CLI command. Some CLI programs like phpstan explicitly turn + off Xdebug deliberately, but provide a `--xdebug` flag to allow running with Xdebug. + +[Here's a good guide for PhpStorm](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging.html). + +If you have trouble with triggered requests not starting connections to your IDE, check that +`sudo lsof -i :9000 | grep LISTEN` on your host shows process from your IDE. +If it doesn't, stop the indicated process (e.g. `php-fpm`) and toggle the Xdebug listen button off and on again in PhpStorm. + +### Performance on macOS + +Page load times with Docker for Mac can vary considerably due to the sharing of files from the macOS disk to the small +virtual machine that docker is running inside. +This is especially so when there is a large quantity of small files, such as with a large composer node_modules or +vendor folder. + +[Mutagen](https://mutagen.io/documentation/transports/docker) and +[docker-sync](https://github.com/EugenMayer/docker-sync/) are tools to synchronise files between host machine and +docker containers. +They both enable production-like performance at the cost of having to synchronise files with an intermediate +"data" container. + +{% if @('mutagen') == 'yes' %} +Mutagen is enabled on the development environment. +You can switch to docker-sync if you wish with `ws switch docker-sync`. +{% elseif @('docker-sync') == 'yes' %} +docker-sync is enabled on the development environment. +You can switch to mutagen if you wish with `ws switch mutagen`. +{% else %} +If it takes over 2 seconds to load a page, you should consider enabling mutagen or docker-sync by adding the following +to `workspace.override.yml` in the project root, or after testing it and the whole team would like to use it, +`workspace.yml` in the project root: + +```yaml +# for mutagen: +attribute('docker-sync'): 'no' +attribute('mutagen'): 'yes' +# or for docker-sync: +attribute('docker-sync'): 'yes' +attribute('mutagen'): 'no' +``` + +Then running `ws harness prepare && ws disable && ws enable`. +The initial sync can take between 5 to 15 minutes, depending on the size of the project directory. + +If committing the attribute changes to `workspace.yml`, ensure the `Performance on macOS` section from +`.my127ws/application/skeleton/README.md` is copied to the project's README.md too! +{% endif %} + +The following are some useful commands regarding Mutagen: +```bash +# To check the Mutagen sync status (sync is ready when status is "Watching for changes") +mutagen monitor +# To debug a sync error +mutagen list +``` +{% if @('docker-sync') == 'yes' %} + +Useful commands for docker-sync: +```bash +# Check the logs +docker-sync logs -f +``` +{% endif %} + +### Common Issues + +As setup issues are encountered please detail with step by step fix instructions, and where possible update the project or the upstream workspace harness itself to provide a more permanent fix. + +* If you get a error that the TLS certificate has expired for the development website in your browser: + * Restart the my127 global traefik proxy with `ws global service proxy restart`. + This will fetch new TLS certificates for `*.my127.site`. +* If you use docker-sync and notice that your changes aren't synchronising to the environment: + * Check if your file exists with your changes in `ws console` + * If your file in `ws console` has changed okay, check your project for any caching that + would prevent changes to source code appearing immediately. + * If the file changes do not appear in `ws console` reasonably quickly, it might be that + Docker for Mac is overwhelmed with file change events. + * Restarting Docker for Mac using the system tray icon's menu and then running `ws enable` again + will fix it. + +# License + +Copyright 2020, Inviqa + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/docker-compose.yml.twig b/docker-compose.yml.twig new file mode 100644 index 00000000..f1839068 --- /dev/null +++ b/docker-compose.yml.twig @@ -0,0 +1,31 @@ +{% set blocks = '_twig/docker-compose.yml/' %} +{% set syncvolume = false %} +{% if @('host.os') == 'darwin' and @('docker-sync') == 'yes' %} +{% set syncvolume = true %} +{% elseif @('host.os') == 'darwin' and @('mutagen') == 'yes' %} +{% set syncvolume = true %} +{% endif %} +version: '{{ @('docker.compose.file_version') }}' +services: +{% include blocks ~ 'application.yml.twig' %} +{% for service in @('app.services') %} +{% include blocks ~ 'service/' ~ service ~ '.yml.twig' %} +{% endfor %} +networks: + private: + external: false + shared: + external: + name: my127ws +{% if syncvolume %} +volumes: +{% if @('mutagen') == 'yes' %} +{% for volumeName in get_mutagen_volume_names() %} + {{ volumeName }}: + external: true +{% endfor %} +{% else %} + {{ @('workspace.name') }}-sync: + external: true +{% endif %} +{% endif %} diff --git a/docker-sync.yml.twig b/docker-sync.yml.twig new file mode 100644 index 00000000..9510ee5a --- /dev/null +++ b/docker-sync.yml.twig @@ -0,0 +1,16 @@ +version: '2' +options: + compose-file-path: + - docker-compose.yml + project_root: config_path +syncs: + {{ @('workspace.name') }}-sync: + src: ./ + sync_userid: '1000' + sync_excludes_type: BelowPath + sync_excludes: &IGNORE + - .docker-sync + - .idea + - .git + - var + watch_excludes: *IGNORE diff --git a/docker/image/console/.dockerignore b/docker/image/console/.dockerignore new file mode 100644 index 00000000..cf49d54d --- /dev/null +++ b/docker/image/console/.dockerignore @@ -0,0 +1 @@ +**/*.twig diff --git a/docker/image/console/Dockerfile.twig b/docker/image/console/Dockerfile.twig new file mode 100644 index 00000000..b95487d3 --- /dev/null +++ b/docker/image/console/Dockerfile.twig @@ -0,0 +1,35 @@ +FROM {{ @('docker.image.console') }} + +COPY .my127ws/docker/image/console/root / +RUN chown -R build:build /home/build \ + && curl -L -o /usr/local/bin/mhsendmail -sS -f https://github.com/mailhog/mhsendmail/releases/download/v0.2.0/mhsendmail_linux_amd64 \ + && chmod +x /usr/local/bin/mhsendmail + +ENV APP_MODE={{ @('app.mode') }} \ + APP_BUILD={{ @('app.build') }} \ + ASSETS_DIR={{ @('assets.local') }} + +{% if @('node.version') is not null %} +USER build +RUN . /home/build/.nvm/nvm.sh \ + && nvm install {{ @('node.version') }} \ + && nvm use {{ @('node.version') }} \ + && nvm alias default {{ @('node.version') }} \ + && npm install -g yarn +USER root +{% endif %} + +{% if @('app.build') == 'static' %} +RUN chown build:build /app +COPY --chown=build:build .my127ws/application /home/build/application +COPY --chown=build:build ./ /app +USER build +RUN app build +USER root +{% else %} +VOLUME /app +VOLUME /home/build/application +{% endif %} + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["sleep", "infinity"] diff --git a/docker/image/console/root/app/.gitkeep b/docker/image/console/root/app/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docker/image/console/root/bin/app b/docker/image/console/root/bin/app new file mode 100755 index 00000000..73a86614 --- /dev/null +++ b/docker/image/console/root/bin/app @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +main() +{ + task "$@" +} + +bootstrap() +{ + export NVM_DIR="$HOME/.nvm" + # shellcheck source=/home/build/nvm.sh + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + . /lib/sidekick.sh + . /lib/functions.sh +} + +bootstrap +main "$@" diff --git a/docker/image/console/root/entrypoint.dynamic.sh b/docker/image/console/root/entrypoint.dynamic.sh new file mode 100755 index 00000000..940b229d --- /dev/null +++ b/docker/image/console/root/entrypoint.dynamic.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +setup_app_volume_permissions() +{ + case "$STRATEGY" in + "host-linux-normal") + usermod -u "$(stat -c '%u' /app)" build + groupmod -g "$(stat -c '%g' /app)" build + ;; + "host-osx-normal") + usermod -u 1000 build + groupmod -g 1000 build + ;; + "host-osx-dockersync") + usermod -u 1000 build + groupmod -g 1000 build + ;; + *) + exit 1 + esac + + chown build:build /app +} + +resolve_volume_mount_strategy() +{ + if [ "${HOST_OS_FAMILY}" = "linux" ]; then + STRATEGY="host-linux-normal" + elif [ "${HOST_OS_FAMILY}" = "darwin" ]; then + if (mount | grep "/app type fuse.osxfs") > /dev/null 2>&1; then + STRATEGY="host-osx-normal" + elif (mount | grep "/app type ext4") > /dev/null 2>&1; then + STRATEGY="host-osx-dockersync" + elif (mount | grep "/app type btrfs") > /dev/null 2>&1; then + STRATEGY="host-linux-normal" + else + exit 1 + fi + else + exit 1 + fi +} + +bootstrap() +{ + resolve_volume_mount_strategy + setup_app_volume_permissions +} + +bootstrap + +source /entrypoint.sh "$@" diff --git a/docker/image/console/root/entrypoint.sh b/docker/image/console/root/entrypoint.sh new file mode 100755 index 00000000..d8e05681 --- /dev/null +++ b/docker/image/console/root/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +setup_app_networking() +{ + # make linux consistent with docker-for-mac + if [ "${HOST_OS_FAMILY}" = "linux" ]; then + DOCKER_INTERNAL_HOST="host.docker.internal" + if ! grep $DOCKER_INTERNAL_HOST /etc/hosts > /dev/null ; then + DOCKER_INTERNAL_IP=$(/sbin/ip route|awk '/default/ { print $3 }') + echo -e "$DOCKER_INTERNAL_IP $DOCKER_INTERNAL_HOST" | tee -a /etc/hosts > /dev/null + fi + fi +} + +bootstrap() +{ + setup_app_networking +} + +bootstrap + +if [ "${1:-}" == "sleep" ]; then + "$@" +else + exec "$@" +fi diff --git a/docker/image/console/root/home/build/.my.cnf.twig b/docker/image/console/root/home/build/.my.cnf.twig new file mode 100644 index 00000000..ef83bda0 --- /dev/null +++ b/docker/image/console/root/home/build/.my.cnf.twig @@ -0,0 +1,4 @@ +[client] +host=mysql +user=root +password={{ @('database.root_pass') }} diff --git a/docker/image/console/root/lib/functions.sh b/docker/image/console/root/lib/functions.sh new file mode 100644 index 00000000..4b7b4018 --- /dev/null +++ b/docker/image/console/root/lib/functions.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +function db_hasSchema() +{ + if [ "${DB_PLATFORM}" == "mysql" ]; then + SQL="SELECT IF (COUNT(*) = 0, 'no', 'yes') FROM information_schema.tables WHERE table_schema = '$DB_NAME';" + IS_DATABASE_APPLIED="$(mysql -ss -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" -e "$SQL")" + elif [ "${DB_PLATFORM}" == "postgres" ]; then + SQL="SELECT CASE WHEN COUNT(*) = 0 THEN 'no' ELSE 'yes' END FROM information_schema.tables WHERE table_catalog = '$DB_NAME' and table_schema='public';" + IS_DATABASE_APPLIED="$(PGPASSWORD="$DB_PASS" psql -qtAX -h "$DB_HOST" -U "$DB_USER" -c "$SQL")" + elif [ -n "${DB_PLATFORM}" ]; then + (>&2 echo "invalid database type") + exit 1 + fi + + if [ -n "${DB_PLATFORM}" ] && [ "$IS_DATABASE_APPLIED" = "no" ]; then + return 1 + fi + + return 0 +} diff --git a/docker/image/console/root/lib/sidekick.sh b/docker/image/console/root/lib/sidekick.sh new file mode 100644 index 00000000..9b364371 --- /dev/null +++ b/docker/image/console/root/lib/sidekick.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +VERBOSE="no" + +RUN_CWD="" + +INDICATOR_RUNNING="34m" +INDICATOR_SUCCESS="32m" +INDICATOR_ERROR="31m" +INDICATOR_PASSTHRU="37m" + +TASKS="/lib/task" + +task() +{ + local TASK_FILE="${TASKS}/${1//:/\/}.sh" + local TASK_NAME="task_${1//:/_}" + + # shellcheck source=/dev/null + declare -F "${TASK_NAME}" &>/dev/null || source "${TASK_FILE}" + + shift + + "${TASK_NAME}" "$@" +} + +prompt() +{ + if [ "${RUN_CWD}" != "$(pwd)" ]; then + RUN_CWD="$(pwd)" + echo -e "\\033[1m[\\033[0mdocker(console):$(pwd)\\033[1m]:\\033[0m" >&2 + fi +} + +run() +{ + local -r COMMAND_DEPRECATED="$*" + local COMMAND=("$@") + local DEPRECATED_MODE=no + + if [[ "${COMMAND[0]}" = *" "* ]]; then + # echo "deprecated: support for passing multiple arguments in the following line will be removed in a future version" >&2 + # echo "run '${COMMAND_DEPRECATED[*]}'" >&2 + # echo "a future major version will only support:" >&2 + # echo "run ${COMMAND_DEPRECATED[*]}" >&2 + # echo >&2 + DEPRECATED_MODE=yes + fi + + if [ "$VERBOSE" = "no" ]; then + + prompt + if [ "${DEPRECATED_MODE}" = "yes" ]; then + echo " > ${COMMAND_DEPRECATED[*]}" >&2 + COMMAND=(bash -e -c "${COMMAND_DEPRECATED[@]}") + else + echo " >$(printf ' %q' "${COMMAND[@]}")" >&2 + fi + + setCommandIndicator "${INDICATOR_RUNNING}" + + if "${COMMAND[@]}" > /tmp/my127ws-stdout.txt 2> /tmp/my127ws-stderr.txt; then + setCommandIndicator "${INDICATOR_SUCCESS}" + else + setCommandIndicator "${INDICATOR_ERROR}" + if [ "${APP_BUILD}" = "static" ]; then + echo "Command failed. stdout:" + cat /tmp/my127ws-stdout.txt + echo + echo "stderr:" + cat /tmp/my127ws-stderr.txt + echo + else + echo "Command failed. stderr:" + cat /tmp/my127ws-stderr.txt + echo "----------------------------------" + echo "Full logs are accessible in the console container at path :-" + echo " stdout: /tmp/my127ws-stdout.txt" + echo " stderr: /tmp/my127ws-stderr.txt" + fi + + return 1 + fi + elif [ "${DEPRECATED_MODE}" = "yes" ]; then + passthru "${COMMAND_DEPRECATED[@]}" + else + passthru "${COMMAND[@]}" + fi +} + +passthru() +{ + local -r COMMAND_DEPRECATED="$*" + local -r COMMAND=("$@") + local DEPRECATED_MODE=no + + if [[ "${COMMAND[0]}" = *" "* ]]; then + # echo "deprecated: support for passing multiple arguments in the following line will be removed in a future version" >&2 + # echo "passthru '${COMMAND_DEPRECATED[*]}'" >&2 + # echo "a future major version will only support:" >&2 + # echo "passthru ${COMMAND_DEPRECATED[*]}" >&2 + # echo >&2 + DEPRECATED_MODE=yes + fi + + prompt + + if [ "${DEPRECATED_MODE}" = "yes" ]; then + echo -e "\\033[${INDICATOR_PASSTHRU}■\\033[0m > $*" >&2 + bash -e -c "${COMMAND_DEPRECATED[@]}" + else + echo -e "\\033[${INDICATOR_PASSTHRU}■\\033[0m >$(printf ' %q' "${COMMAND[@]}")" >&2 + "${COMMAND[@]}" + fi +} + +setCommandIndicator() +{ + echo -ne "\\033[1A" >&2 + echo -ne "\\033[$1" >&2 + echo -n "■" >&2 + echo -ne "\\033[0m" >&2 + echo -ne "\\033[1E" >&2 +} diff --git a/docker/image/console/root/lib/task/assets/apply.sh b/docker/image/console/root/lib/task/assets/apply.sh new file mode 100644 index 00000000..d22e65b1 --- /dev/null +++ b/docker/image/console/root/lib/task/assets/apply.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +function task_assets_apply() +{ + local ASSETS_DIR="${ASSETS_DIR:-tools/assets/development}" + local IMPORT_COMMAND="" + + if [ "${DB_PLATFORM}" == "mysql" ]; then + IMPORT_COMMAND="mysql -h $DB_HOST -u ${DB_ADMIN_USER:-$DB_USER} -p${DB_ROOT_PASS:-${DB_ADMIN_PASS:-$DB_PASS}} $DB_NAME" + elif [ "${DB_PLATFORM}" == "postgres" ]; then + IMPORT_COMMAND="PGPASSWORD=$DB_PASS psql -h $DB_HOST -U $DB_USER $DB_NAME" + elif [ -n "${DB_PLATFORM}" ]; then + (>&2 echo "invalid database type") + exit 1 + fi + + if ! db_hasSchema; then + + local DATABASE_FILE="/app/${ASSETS_DIR}/${DB_NAME}.sql.gz" + + if [ ! -f "$DATABASE_FILE" ]; then + DATABASE_FILE="$(find "/app/${ASSETS_DIR}/" -maxdepth 1 -name "${DB_NAME}*.sql.gz" -print | head -n1)" + fi + + if [ -f "$DATABASE_FILE" ]; then + passthru "pv --force $DATABASE_FILE | zcat - | $IMPORT_COMMAND" + else + task install + fi + fi + + for file in "/app/${ASSETS_DIR}/"*.files.{tgz,tar.gz}; do + [ -f "$file" ] || continue + run tar -xvf "${file}" -C /app + done +} diff --git a/docker/image/console/root/lib/task/assets/dump.sh b/docker/image/console/root/lib/task/assets/dump.sh new file mode 100644 index 00000000..0bb835b3 --- /dev/null +++ b/docker/image/console/root/lib/task/assets/dump.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +function task_assets_dump() +{ + local ASSETS_DIR="${ASSETS_DIR:-tools/assets/development}" + + if [ ! -d "/app/${ASSETS_DIR}" ]; then + run mkdir -p "/app/${ASSETS_DIR}" + fi + + if [ "${DB_PLATFORM}" == "mysql" ]; then + run "mysqldump -h '${DB_HOST}' -u '${DB_USER}' '-p${DB_PASS}' '${DB_NAME}' | gzip > '/app/${ASSETS_DIR}/${DB_NAME}.sql.gz'" + elif [ "${DB_PLATFORM}" == "postgres" ]; then + PGPASSWORD="$DB_PASS" run "pg_dump -h '${DB_HOST}' -U '${DB_USER}' '${DB_NAME}' | gzip > '/app/${ASSETS_DIR}/${DB_NAME}.sql.gz'" + elif [ -n "${DB_PLATFORM}" ]; then + (>&2 echo "invalid database type") + exit 1 + fi +} diff --git a/docker/image/console/root/lib/task/build.sh b/docker/image/console/root/lib/task/build.sh new file mode 100644 index 00000000..c49bf57e --- /dev/null +++ b/docker/image/console/root/lib/task/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +function task_build() +{ + if [ ! -f /app/composer.json ]; then + task skeleton:apply + fi + + task overlay:apply + + task build:backend + task build:frontend +} diff --git a/docker/image/console/root/lib/task/build/backend.sh.twig b/docker/image/console/root/lib/task/build/backend.sh.twig new file mode 100644 index 00000000..418fa623 --- /dev/null +++ b/docker/image/console/root/lib/task/build/backend.sh.twig @@ -0,0 +1,15 @@ +#!/bin/bash + +function task_build_backend() +{( + cd {{ @('backend.path') }} + + if [ ! {{ @('backend.build.when')|raw }} ]; then + return 0; + fi + + {% for step in @('backend.build.steps') -%} + {{ step|raw }} + {% endfor %} + +)} diff --git a/docker/image/console/root/lib/task/build/frontend.sh.twig b/docker/image/console/root/lib/task/build/frontend.sh.twig new file mode 100644 index 00000000..deff49a4 --- /dev/null +++ b/docker/image/console/root/lib/task/build/frontend.sh.twig @@ -0,0 +1,15 @@ +#!/bin/bash + +function task_build_frontend() +{( + cd {{ @('frontend.path') }} + + if [ ! {{ @('frontend.build.when')|raw }} ]; then + return 0; + fi + + {% for step in @('frontend.build.steps') -%} + {{ step|raw }} + {% endfor %} + +)} diff --git a/docker/image/console/root/lib/task/composer/install.sh.twig b/docker/image/console/root/lib/task/composer/install.sh.twig new file mode 100644 index 00000000..f321a9d5 --- /dev/null +++ b/docker/image/console/root/lib/task/composer/install.sh.twig @@ -0,0 +1,16 @@ +#!/bin/bash + +function task_composer_install() +{ + {% if @('app.mode') == 'development' and @('app.build') == 'static' %} + passthru composer install --no-interaction --optimize-autoloader + {% elseif @('app.mode') == 'development' %} + passthru composer install --no-interaction + {% else %} + passthru composer install --no-interaction --no-dev --optimize-autoloader + {% endif %} + + {% if @('app.build') == 'static' %} + run "composer clear-cache" + {% endif %} +} diff --git a/docker/image/console/root/lib/task/database/available.sh b/docker/image/console/root/lib/task/database/available.sh new file mode 100644 index 00000000..c39b848c --- /dev/null +++ b/docker/image/console/root/lib/task/database/available.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +function task_database_available() +{ + local command="" + + if [ "${DB_PLATFORM}" == "mysql" ]; then + command="mysqladmin -h $DB_HOST -u ${DB_ADMIN_USER:-$DB_USER} -p${DB_ROOT_PASS:-${DB_ADMIN_PASS:-$DB_PASS}} ping --connect_timeout=10" + elif [ "${DB_PLATFORM}" == "postgres" ]; then + command="pg_isready -h $DB_HOST" + elif [ "${DB_PLATFORM}" == "" ]; then + # no database is used + return + else + (>&2 echo "invalid database type") + exit 1 + fi + + local counter=0 + + while ! $command &> /dev/null; do + + if (( counter > 300 )); then + (>&2 echo "timeout while waiting on ${DB_PLATFORM} to become available") + exit 1 + fi + + sleep 2 + ((++counter)) + done +} diff --git a/docker/image/console/root/lib/task/database/import.sh.twig b/docker/image/console/root/lib/task/database/import.sh.twig new file mode 100644 index 00000000..d0742b9a --- /dev/null +++ b/docker/image/console/root/lib/task/database/import.sh.twig @@ -0,0 +1,10 @@ +#!/bin/bash + +function task_database_import() +{ + + {% for step in @('database.import.steps') -%} + {{ step|raw }} + {% endfor %} + +} diff --git a/docker/image/console/root/lib/task/http/wait.sh b/docker/image/console/root/lib/task/http/wait.sh new file mode 100644 index 00000000..5b6c05a9 --- /dev/null +++ b/docker/image/console/root/lib/task/http/wait.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# wait for http service +function task_http_wait() { + echo -e "Waiting for http service $1 to be available" + + local counter=0 + + while ! curl -s -k "$1" -o /dev/null -L --fail; do + + if (( counter > 60 )); then + (>&2 echo "timeout while waiting on $1 to become available") + exit 1 + fi + + sleep 1 + ((++counter)) + done +} \ No newline at end of file diff --git a/docker/image/console/root/lib/task/init.sh.twig b/docker/image/console/root/lib/task/init.sh.twig new file mode 100644 index 00000000..58e04a56 --- /dev/null +++ b/docker/image/console/root/lib/task/init.sh.twig @@ -0,0 +1,18 @@ +#!/bin/bash + +function task_init() +{ + task database:available + + if ! db_hasSchema; then + + task assets:apply + + {% for step in @('backend.init.steps') -%} + {{ step|raw }} + {% endfor %} + + task welcome + + fi +} diff --git a/docker/image/console/root/lib/task/install.sh.twig b/docker/image/console/root/lib/task/install.sh.twig new file mode 100644 index 00000000..07717612 --- /dev/null +++ b/docker/image/console/root/lib/task/install.sh.twig @@ -0,0 +1,11 @@ +#!/bin/bash + +function task_install() +{ + task database:available + + {% for step in @('backend.install.steps') -%} + {{ step|raw }} + {% endfor %} + +} diff --git a/docker/image/console/root/lib/task/migrate.sh.twig b/docker/image/console/root/lib/task/migrate.sh.twig new file mode 100644 index 00000000..863a1ad4 --- /dev/null +++ b/docker/image/console/root/lib/task/migrate.sh.twig @@ -0,0 +1,10 @@ +#!/bin/bash + +function task_migrate() +{ + task database:available + + {% for step in @('backend.migrate.steps') -%} + {{ step|raw }} + {% endfor %} +} diff --git a/docker/image/console/root/lib/task/overlay/apply.sh b/docker/image/console/root/lib/task/overlay/apply.sh new file mode 100644 index 00000000..33fec2d1 --- /dev/null +++ b/docker/image/console/root/lib/task/overlay/apply.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +function task_overlay_apply() +{ + run rsync --exclude='*.twig' --exclude='_twig' -a /home/build/application/overlay/ /app/ +} diff --git a/docker/image/console/root/lib/task/phpstan.sh b/docker/image/console/root/lib/task/phpstan.sh new file mode 100644 index 00000000..bf7159f5 --- /dev/null +++ b/docker/image/console/root/lib/task/phpstan.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +function task_phpstan() +{ + local phpstan_version="" + phpstan_version="$(find_phpstan_version)" + + echo "Using phpstan v$phpstan_version" + composer global require "phpstan/phpstan:$phpstan_version" + composer global exec -v -- phpstan analyse -c /app/phpstan.neon +} + +function find_phpstan_version() +( + local phpstan_version="0.12.37" + local project_phpstan_version="" + set +e + if [ -f /app/composer.lock ]; then + project_phpstan_version="$(jq -r '."packages" + ."packages-dev" | map(select( .name == "phpstan/phpstan" )) | .[].version' /app/composer.lock)" + if [ -n "$project_phpstan_version" ]; then + phpstan_version="$project_phpstan_version" + fi + fi + echo "$phpstan_version" +) diff --git a/docker/image/console/root/lib/task/rabbitmq/vhosts.sh.twig b/docker/image/console/root/lib/task/rabbitmq/vhosts.sh.twig new file mode 100644 index 00000000..94b9fa4f --- /dev/null +++ b/docker/image/console/root/lib/task/rabbitmq/vhosts.sh.twig @@ -0,0 +1,15 @@ +#!/bin/bash + +# create rabbitmq virtual hosts +function task_rabbitmq_vhosts() { +{% if 'rabbitmq' not in @('app.services') %} + : +{% else %} + local rabbitmq_api_url="http://$RABBITMQ_HOST:$RABBITMQ_API_PORT/api" + task http:wait "$rabbitmq_api_url/index.html" + +{% for vhost in @('rabbitmq.vhosts') %} + curl -f -I -s -u "$RABBITMQ_USER:$RABBITMQ_PASSWORD" -X PUT "$rabbitmq_api_url/vhosts/{{ vhost }}" +{% endfor %} +{% endif %} +} diff --git a/docker/image/console/root/lib/task/skeleton/apply.sh b/docker/image/console/root/lib/task/skeleton/apply.sh new file mode 100644 index 00000000..700ab064 --- /dev/null +++ b/docker/image/console/root/lib/task/skeleton/apply.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +function task_skeleton_apply() +{ + run rsync --exclude='*.twig' --exclude='_twig' -a /home/build/application/skeleton/ /app/ +} diff --git a/docker/image/console/root/lib/task/state.sh b/docker/image/console/root/lib/task/state.sh new file mode 100644 index 00000000..c0e00419 --- /dev/null +++ b/docker/image/console/root/lib/task/state.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +function task_state() +{ + task database:available + + echo "Ready!" +} diff --git a/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig new file mode 100644 index 00000000..858a33ad --- /dev/null +++ b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig @@ -0,0 +1,8 @@ +{% set xdebug = @('php.ext-xdebug') %} + +{% if xdebug.cli.enable == 'yes' %} + zend_extension=xdebug.so + {% for key, value in xdebug.config -%} + xdebug.{{ key }}={{ value }} + {% endfor %} +{% endif %} diff --git a/docker/image/console/root/usr/local/etc/php/php.ini.twig b/docker/image/console/root/usr/local/etc/php/php.ini.twig new file mode 100644 index 00000000..b76369c9 --- /dev/null +++ b/docker/image/console/root/usr/local/etc/php/php.ini.twig @@ -0,0 +1,10 @@ +{% for name, value in @('php.ini') %} +{{ name }} = {{ value }} +{% endfor %} +{% for name, value in @('php.cli.ini') %} +{{ name }} = {{ value }} +{% endfor %} + +; UTC for consistent logging that doesn't vary for Daylight Savings +; If you need to change it, configure your application to display dates offset from UTC. +date.timezone = UTC diff --git a/docker/image/cron/Dockerfile.twig b/docker/image/cron/Dockerfile.twig new file mode 100644 index 00000000..1055fb43 --- /dev/null +++ b/docker/image/cron/Dockerfile.twig @@ -0,0 +1,35 @@ +{% if @('app.build') == 'static' %} +FROM {{ @('docker.repository') ~ ':' ~ @('app.version') }}-php-fpm +{% else %} +FROM {{ @('workspace.name') ~ '-php-fpm:dev' }} +{% endif %} + +# Install cron +RUN apt-get update -qq \ + && DEBIAN_FRONTEND=noninteractive apt-get -qq -y --no-install-recommends install \ + cron \ + sudo \ + # clean \ + && apt-get auto-remove -qq -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY .my127ws/docker/image/cron/root / +RUN chmod +x /cron-run-with-env.sh + +{% if @('app.build') == 'static' %} +RUN bash /fix_app_permissions.sh +{% else %} +VOLUME /app +{% endif %} +ENV APP_MODE {{ @('app.mode') }} + +{% if @('app.build') == 'static' %} +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] +CMD ["sleep", "infinity"] +{% else %} +ENTRYPOINT ["/entrypoint.dynamic.sh"] +CMD ["sleep", "infinity"] +{% endif %} diff --git a/docker/image/cron/root/cron-run-with-env.sh b/docker/image/cron/root/cron-run-with-env.sh new file mode 100755 index 00000000..caa047d4 --- /dev/null +++ b/docker/image/cron/root/cron-run-with-env.sh @@ -0,0 +1,5 @@ +#!/bin/bash +script_args="$*" +env_vars=() +readarray -t env_vars < /app/env.sh +/usr/bin/env - "${env_vars[@]}" su -s /bin/bash -p -c "$script_args" www-data > /proc/1/fd/1 2> /proc/1/fd/2 diff --git a/docker/image/cron/root/crontab.twig b/docker/image/cron/root/crontab.twig new file mode 100644 index 00000000..71a84dc3 --- /dev/null +++ b/docker/image/cron/root/crontab.twig @@ -0,0 +1,3 @@ +{% for cronjob in @('backend.cron.jobs') -%} +{{ cronjob|raw }} +{% endfor %} \ No newline at end of file diff --git a/docker/image/cron/root/entrypoint.sh b/docker/image/cron/root/entrypoint.sh new file mode 100644 index 00000000..82a76307 --- /dev/null +++ b/docker/image/cron/root/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# this is used to load env vars in crontab commands +env > /app/env.sh + +# run +crontab /crontab +exec cron -f -L 15 \ No newline at end of file diff --git a/docker/image/nginx/Dockerfile.twig b/docker/image/nginx/Dockerfile.twig new file mode 100644 index 00000000..d4072acf --- /dev/null +++ b/docker/image/nginx/Dockerfile.twig @@ -0,0 +1,17 @@ +{% if @('app.build') == 'static' %} +FROM {{ @('docker.repository') ~ ':' ~ @('app.version') }}-console as console +{% endif %} + +FROM nginx:1.17-alpine +COPY root / + +{% if @('app.build') == 'static' %} +{% for copy_directory in @('nginx.copy_directories')|filter(v => v is not empty) %} +COPY --from=console {{ copy_directory }} {{ copy_directory }} +{% endfor %} +{% else %} +VOLUME /app +{% endif %} + +ENTRYPOINT ["sh", "/docker-entrypoint.d/config_render.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/image/nginx/root/etc/nginx/conf.d/0-nginx.conf.twig b/docker/image/nginx/root/etc/nginx/conf.d/0-nginx.conf.twig new file mode 100644 index 00000000..c9efff8a --- /dev/null +++ b/docker/image/nginx/root/etc/nginx/conf.d/0-nginx.conf.twig @@ -0,0 +1,3 @@ +{% for name, value in @('nginx.global.conf') %} + {{ name }} {{ value }}; +{% endfor %} diff --git a/docker/image/nginx/root/etc/nginx/conf.d/default.conf.template.twig b/docker/image/nginx/root/etc/nginx/conf.d/default.conf.template.twig new file mode 100644 index 00000000..f1554754 --- /dev/null +++ b/docker/image/nginx/root/etc/nginx/conf.d/default.conf.template.twig @@ -0,0 +1,63 @@ + +server { + + listen 80 default_server; + listen 443 ssl http2 default_server; + + server_name _; + + include snippets/certificate.conf; + include snippets/ssl-params.conf; + include snippets/top-*.conf; + + {% for name, value in @('nginx.site.conf') %} + {{ name }} {{ value }}; + {% endfor %} + + set $custom_https $https; + set $custom_scheme $scheme; + + if ($http_x_forwarded_proto) { + set $custom_scheme $http_x_forwarded_proto; + } + + if ($http_x_forwarded_proto = https) { + set $custom_https on; + } + + root {{ @('app.web_directory') }}; + + index index.php; + + location / { + try_files $uri @rewriteapp; + } + + location @rewriteapp { + rewrite ^(.*)$ /index.php/$1 last; + } + + location ~ \.php(/|$) { + + fastcgi_pass ${FPM_HOST}:9000; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + + fastcgi_read_timeout {{ @('php.fpm.ini.max_execution_time') + 1 }}s; + + fastcgi_param HTTPS $custom_https; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + + fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_PROXY ""; + } + + include snippets/bottom-*.conf; + + # Banned locations (only reached if the earlier PHP entry point regexes don't match) + location ~* (\.php$|\.htaccess$|\.git) { + deny all; + } + +} diff --git a/docker/image/nginx/root/etc/nginx/snippets/certificate.conf b/docker/image/nginx/root/etc/nginx/snippets/certificate.conf new file mode 100644 index 00000000..80e7be3f --- /dev/null +++ b/docker/image/nginx/root/etc/nginx/snippets/certificate.conf @@ -0,0 +1,2 @@ +ssl_certificate /etc/ssl/certs/app.crt; +ssl_certificate_key /etc/ssl/private/app.key; diff --git a/docker/image/nginx/root/etc/nginx/snippets/ssl-params.conf b/docker/image/nginx/root/etc/nginx/snippets/ssl-params.conf new file mode 100644 index 00000000..9b37b604 --- /dev/null +++ b/docker/image/nginx/root/etc/nginx/snippets/ssl-params.conf @@ -0,0 +1,17 @@ +# from https://cipherli.st/ +# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + +ssl_protocols TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +ssl_ecdh_curve secp384r1; +ssl_session_cache shared:SSL:10m; +ssl_session_tickets off; +ssl_stapling on; +ssl_stapling_verify on; + +resolver 1.1.1.1 8.8.8.8 valid=300s; +resolver_timeout 5s; + +add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; +add_header X-Content-Type-Options nosniff; diff --git a/docker/image/nginx/root/etc/ssl/certs/app.crt.twig b/docker/image/nginx/root/etc/ssl/certs/app.crt.twig new file mode 100644 index 00000000..0d945815 --- /dev/null +++ b/docker/image/nginx/root/etc/ssl/certs/app.crt.twig @@ -0,0 +1 @@ +{{ @('tls.crt') }} \ No newline at end of file diff --git a/docker/image/nginx/root/etc/ssl/private/app.key.twig b/docker/image/nginx/root/etc/ssl/private/app.key.twig new file mode 100644 index 00000000..6bb8bcbf --- /dev/null +++ b/docker/image/nginx/root/etc/ssl/private/app.key.twig @@ -0,0 +1 @@ +{{ @('tls.key') }} \ No newline at end of file diff --git a/docker/image/php-fpm/.dockerignore b/docker/image/php-fpm/.dockerignore new file mode 100644 index 00000000..cf49d54d --- /dev/null +++ b/docker/image/php-fpm/.dockerignore @@ -0,0 +1 @@ +**/*.twig diff --git a/docker/image/php-fpm/Dockerfile.twig b/docker/image/php-fpm/Dockerfile.twig new file mode 100644 index 00000000..827d3966 --- /dev/null +++ b/docker/image/php-fpm/Dockerfile.twig @@ -0,0 +1,29 @@ +{% if @('app.build') == 'static' %} +FROM {{ @('docker.repository') ~ ':' ~ @('app.version') }}-console as console +RUN if [ -d /app/tools/assets/ ]; then rm -rf /app/tools/assets/; fi +RUN if [ -d {{ @('frontend.path') }}/node_modules/ ]; then rm -rf {{ @('frontend.path') }}/node_modules/; fi +{% endif %} + +FROM {{ @('docker.image.php-fpm') }} +WORKDIR /app +COPY root / + +RUN curl -L -o /usr/local/bin/mhsendmail -sS -f https://github.com/mailhog/mhsendmail/releases/download/v0.2.0/mhsendmail_linux_amd64 \ + && chmod +x /usr/local/bin/mhsendmail + +{% if @('app.build') == 'static' %} +COPY --from=console --chown=root:root /app /app +RUN bash /fix_app_permissions.sh +{% else %} +VOLUME /app +{% endif %} +ENV APP_MODE {{ @('app.mode') }} + +{% if @('app.build') == 'static' %} +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] +CMD ["sleep", "infinity"] +{% else %} +ENTRYPOINT ["/entrypoint.dynamic.sh"] +CMD ["sleep", "infinity"] +{% endif %} diff --git a/docker/image/php-fpm/root/app/.gitkeep b/docker/image/php-fpm/root/app/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docker/image/php-fpm/root/entrypoint.dynamic.sh b/docker/image/php-fpm/root/entrypoint.dynamic.sh new file mode 100755 index 00000000..06d8b0dd --- /dev/null +++ b/docker/image/php-fpm/root/entrypoint.dynamic.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +main() +{ + source /entrypoint.sh +} + +setup_app_networking() +{ + # make linux consistent with docker-for-mac + if [ "${HOST_OS_FAMILY}" = "linux" ]; then + DOCKER_INTERNAL_HOST="host.docker.internal" + if ! grep $DOCKER_INTERNAL_HOST /etc/hosts > /dev/null ; then + DOCKER_INTERNAL_IP=$(/sbin/ip route|awk '/default/ { print $3 }') + echo -e "$DOCKER_INTERNAL_IP $DOCKER_INTERNAL_HOST" | tee -a /etc/hosts > /dev/null + fi + fi +} + +setup_app_volume_permissions() +{ + case "$STRATEGY" in + "host-linux-normal") + usermod -u "$(stat -c '%u' /app)" www-data + groupmod -g "$(stat -c '%g' /app)" www-data + ;; + "host-osx-normal") + usermod -u 1000 www-data + groupmod -g 1000 www-data + ;; + "host-osx-dockersync") + usermod -u 1000 www-data + groupmod -g 1000 www-data + ;; + *) + exit 1 + esac +} + +resolve_volume_mount_strategy() +{ + if [ "${HOST_OS_FAMILY}" = "linux" ]; then + STRATEGY="host-linux-normal" + elif [ "${HOST_OS_FAMILY}" = "darwin" ]; then + if (mount | grep "/app type fuse.osxfs") > /dev/null 2>&1; then + STRATEGY="host-osx-normal" + elif (mount | grep "/app type ext4") > /dev/null 2>&1; then + STRATEGY="host-osx-dockersync" + elif (mount | grep "/app type btrfs") > /dev/null 2>&1; then + STRATEGY="host-linux-normal" + else + exit 1 + fi + else + exit 1 + fi +} + +bootstrap() +{ + resolve_volume_mount_strategy + setup_app_volume_permissions + setup_app_networking +} + +bootstrap +main diff --git a/docker/image/php-fpm/root/entrypoint.sh.twig b/docker/image/php-fpm/root/entrypoint.sh.twig new file mode 100755 index 00000000..3100a0e0 --- /dev/null +++ b/docker/image/php-fpm/root/entrypoint.sh.twig @@ -0,0 +1,18 @@ +#!/bin/bash + +run_steps() +{ + {% for poolName, pool in @('php-fpm.pools') -%} + FPM_NAME="{{ poolName }}" FPM_PORT="{{ pool.port }}" envsubst < /usr/local/etc/php-fpm.d/pool.conf.template > /usr/local/etc/php-fpm.d/{{ poolName }}.conf; + {% endfor %} + + # run any command required to be executed at docker startup + {% for step in @('php-fpm.entrypoint.steps') -%} + {{ step|raw }} + {% endfor %} +} + +run_steps + +# run +exec supervisord -c /etc/supervisor/supervisord.conf -n diff --git a/docker/image/php-fpm/root/etc/supervisor/conf.d/container-cmd.conf b/docker/image/php-fpm/root/etc/supervisor/conf.d/container-cmd.conf new file mode 100644 index 00000000..a96f50ea --- /dev/null +++ b/docker/image/php-fpm/root/etc/supervisor/conf.d/container-cmd.conf @@ -0,0 +1,9 @@ +[program:php-fpm] +command=docker-php-entrypoint php-fpm +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +user = root +autostart = %(ENV_AUTOSTART_PHP_FPM)s +autorestart = true diff --git a/docker/image/php-fpm/root/etc/supervisor/supervisord.conf b/docker/image/php-fpm/root/etc/supervisor/supervisord.conf new file mode 100644 index 00000000..6cfe17a9 --- /dev/null +++ b/docker/image/php-fpm/root/etc/supervisor/supervisord.conf @@ -0,0 +1,18 @@ +[supervisord] +nodaemon = true +logfile=/dev/stdout +logfile_maxbytes=0 +pidfile = /var/run/supervisord.pid + +[include] +files = /etc/supervisor/conf.d/*.conf + +[supervisorctl] +serverurl = unix:///var/run/supervisor.sock + +[unix_http_server] +file = /var/run/supervisor.sock +chmod = 0700 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface diff --git a/docker/image/php-fpm/root/fix_app_permissions.sh.twig b/docker/image/php-fpm/root/fix_app_permissions.sh.twig new file mode 100755 index 00000000..705b8ab9 --- /dev/null +++ b/docker/image/php-fpm/root/fix_app_permissions.sh.twig @@ -0,0 +1,55 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +main() +{ + app_permissions_fix +} + + +function app_permissions_fix() +{ + local APP_OWNER="{{ @('app.web_owner') }}" + local APP_GROUP="{{ @('app.web_group') }}" + DIRS=("{{ @('app.web_writable_dirs') | join('" "') | raw }}") + FILES=("{{ @('app.web_writable_files') | join('" "') | raw }}") + + for DIR in "${DIRS[@]}" + do + if [ -n "${DIR}" ]; then + if [ ! -d "${DIR}" ]; then + echo "${DIR} does not exist. Creating ${DIR}..." + mkdir -p "${DIR}" + fi + echo -n "Fixing permissions for ${DIR}..." + find "${DIR}" \( ! -user "${APP_OWNER}" -or ! -group "${APP_GROUP}" \) -exec chown "${APP_OWNER}":"${APP_GROUP}" {} + + find "${DIR}" -type d ! -perm ug+rwx,o+rx,o-w -exec chmod ug+rwx,o+rx,o-w {} + + find "${DIR}" -type f ! -perm ug+rw,o+r,o-w -exec chmod ug+rw,o+r,o-w {} + + echo "Done" + else + echo "No directory was specified for permissions fixing." + fi + done + + for FILE in "${FILES[@]}" + do + if [ -n "${FILE}" ]; then + if [ ! -f "${FILE}" ]; then + echo "${FILE} does not exist. Creating ${FILE}..." + touch "${FILE}" + fi + echo -n "Fixing permissions for ${FILE}..." + chown "${APP_OWNER}":"${APP_GROUP}" "${FILE}" + chmod ug+rw,o+r,o-w "${FILE}" + echo "Done" + else + echo "No file was specified for permissions fixing." + fi + done +} + +main diff --git a/docker/image/php-fpm/root/usr/local/etc/php-fpm.d/pool.conf.template.twig b/docker/image/php-fpm/root/usr/local/etc/php-fpm.d/pool.conf.template.twig new file mode 100644 index 00000000..14a055d8 --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/etc/php-fpm.d/pool.conf.template.twig @@ -0,0 +1,24 @@ +[${FPM_NAME}] +user = www-data +group = www-data + +listen = ${FPM_PORT} + +pm = dynamic + +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 + +pm.status_path = /status + +; if we send this to /proc/self/fd/1, it never appears +access.log = /proc/self/fd/2 + +clear_env = no + +catch_workers_output = yes +{% if version_compare(@('php.version'), '7.3.0', '>=') %} +decorate_workers_output = no +{% endif %} diff --git a/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig new file mode 100644 index 00000000..1c319dad --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.twig @@ -0,0 +1,8 @@ +{% set xdebug = @('php.ext-xdebug') %} + +{% if xdebug.enable == 'yes' %} + zend_extension=xdebug.so + {% for key, value in xdebug.config -%} + xdebug.{{ key }}={{ value }} + {% endfor %} +{% endif %} diff --git a/docker/image/php-fpm/root/usr/local/etc/php/php.ini.twig b/docker/image/php-fpm/root/usr/local/etc/php/php.ini.twig new file mode 100644 index 00000000..90f4ad60 --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/etc/php/php.ini.twig @@ -0,0 +1,10 @@ +{% for name, value in @('php.ini') %} +{{ name }} = {{ value }} +{% endfor %} +{% for name, value in @('php.fpm.ini') %} +{{ name }} = {{ value }} +{% endfor %} + +; UTC for consistent logging that doesn't vary for Daylight Savings +; If you need to change it, configure your application to display dates offset from UTC. +date.timezone = UTC diff --git a/docker/image/relay/Dockerfile b/docker/image/relay/Dockerfile new file mode 100644 index 00000000..b0c24329 --- /dev/null +++ b/docker/image/relay/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.17-alpine +COPY root / diff --git a/docker/image/relay/root/etc/nginx/nginx.conf b/docker/image/relay/root/etc/nginx/nginx.conf new file mode 100644 index 00000000..8f51cf3c --- /dev/null +++ b/docker/image/relay/root/etc/nginx/nginx.conf @@ -0,0 +1,24 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + +stream { + server { + listen 1025; + + proxy_pass mailhog:1025; + } + + server { + listen 8025; + + proxy_pass mailhog:8025; + } +} diff --git a/docker/image/tls-offload/Dockerfile b/docker/image/tls-offload/Dockerfile new file mode 100644 index 00000000..b0c24329 --- /dev/null +++ b/docker/image/tls-offload/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.17-alpine +COPY root / diff --git a/docker/image/tls-offload/root/etc/nginx/conf.d/0-nginx.conf.twig b/docker/image/tls-offload/root/etc/nginx/conf.d/0-nginx.conf.twig new file mode 100644 index 00000000..c9efff8a --- /dev/null +++ b/docker/image/tls-offload/root/etc/nginx/conf.d/0-nginx.conf.twig @@ -0,0 +1,3 @@ +{% for name, value in @('nginx.global.conf') %} + {{ name }} {{ value }}; +{% endfor %} diff --git a/docker/image/tls-offload/root/etc/nginx/conf.d/default.conf.twig b/docker/image/tls-offload/root/etc/nginx/conf.d/default.conf.twig new file mode 100644 index 00000000..359e4835 --- /dev/null +++ b/docker/image/tls-offload/root/etc/nginx/conf.d/default.conf.twig @@ -0,0 +1,44 @@ + +server { + + listen 80 default_server; + listen 443 ssl http2 default_server; + + server_name _; + + include snippets/certificate.conf; + include snippets/ssl-params.conf; + include snippets/top-*.conf; + + {% for name, value in @('nginx.site.conf') %} + {{ name }} {{ value }}; + {% endfor %} + + set $custom_https $https; + set $custom_scheme $scheme; + + if ($http_x_forwarded_proto) { + set $custom_scheme $http_x_forwarded_proto; + } + + if ($http_x_forwarded_proto = https) { + set $custom_https on; + } + + root {{ @('app.web_directory') }}; + + index index.php; + + location / { + proxy_pass http://varnish; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $custom_scheme; + proxy_read_timeout {{ @('php.fpm.ini.max_execution_time') + 3 }}s; + proxy_send_timeout {{ @('php.fpm.ini.max_execution_time') + 3 }}s; + proxy_connect_timeout {{ @('php.fpm.ini.max_execution_time') + 3 }}s; + send_timeout {{ @('php.fpm.ini.max_execution_time') + 3 }}s; + } + + include snippets/bottom-*.conf; +} diff --git a/docker/image/tls-offload/root/etc/nginx/snippets/certificate.conf b/docker/image/tls-offload/root/etc/nginx/snippets/certificate.conf new file mode 100644 index 00000000..80e7be3f --- /dev/null +++ b/docker/image/tls-offload/root/etc/nginx/snippets/certificate.conf @@ -0,0 +1,2 @@ +ssl_certificate /etc/ssl/certs/app.crt; +ssl_certificate_key /etc/ssl/private/app.key; diff --git a/docker/image/tls-offload/root/etc/nginx/snippets/ssl-params.conf b/docker/image/tls-offload/root/etc/nginx/snippets/ssl-params.conf new file mode 100644 index 00000000..9b37b604 --- /dev/null +++ b/docker/image/tls-offload/root/etc/nginx/snippets/ssl-params.conf @@ -0,0 +1,17 @@ +# from https://cipherli.st/ +# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + +ssl_protocols TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +ssl_ecdh_curve secp384r1; +ssl_session_cache shared:SSL:10m; +ssl_session_tickets off; +ssl_stapling on; +ssl_stapling_verify on; + +resolver 1.1.1.1 8.8.8.8 valid=300s; +resolver_timeout 5s; + +add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; +add_header X-Content-Type-Options nosniff; diff --git a/docker/image/tls-offload/root/etc/ssl/certs/app.crt.twig b/docker/image/tls-offload/root/etc/ssl/certs/app.crt.twig new file mode 100644 index 00000000..0d945815 --- /dev/null +++ b/docker/image/tls-offload/root/etc/ssl/certs/app.crt.twig @@ -0,0 +1 @@ +{{ @('tls.crt') }} \ No newline at end of file diff --git a/docker/image/tls-offload/root/etc/ssl/private/app.key.twig b/docker/image/tls-offload/root/etc/ssl/private/app.key.twig new file mode 100644 index 00000000..6bb8bcbf --- /dev/null +++ b/docker/image/tls-offload/root/etc/ssl/private/app.key.twig @@ -0,0 +1 @@ +{{ @('tls.key') }} \ No newline at end of file diff --git a/docker/image/varnish/root/etc/varnish/default.vcl.twig b/docker/image/varnish/root/etc/varnish/default.vcl.twig new file mode 100644 index 00000000..971517ba --- /dev/null +++ b/docker/image/varnish/root/etc/varnish/default.vcl.twig @@ -0,0 +1,41 @@ +# +# This is an example VCL file for Varnish. +# +# It does not do anything by default, delegating control to the +# builtin VCL. The builtin VCL is called when there is no explicit +# return statement. +# +# See the VCL chapters in the Users Guide for a comprehensive documentation +# at https://www.varnish-cache.org/docs/. + +# Marker to tell the VCL compiler that this VCL has been written with the +# 4.0 or 4.1 syntax. +vcl 4.1; + +# Default backend definition. Set this to point to your content server. +backend default { + .host = "{% if varnish.target_service is defined %}{{ varnish.target_service }}{% else %}{{ @('varnish.target_service') }}{% endif %}"; + .port = "80"; + .first_byte_timeout = {{ @('php.fpm.ini.max_execution_time') + 2 }}s; +} + +sub vcl_recv { + # Happens before we check if we have this in cache already. + # + # Typically you clean up the request here, removing cookies you don't need, + # rewriting the request, etc. +} + +sub vcl_backend_response { + # Happens after we have read the response headers from the backend. + # + # Here you clean the response headers, removing silly Set-Cookie headers + # and other mistakes your backend does. +} + +sub vcl_deliver { + # Happens when we have all the pieces we need, and are about to send the + # response to the client. + # + # You can do accounting or modifying the final object here. +} diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/harness/attributes/common.yml b/harness/attributes/common.yml new file mode 100644 index 00000000..0cdd31b1 --- /dev/null +++ b/harness/attributes/common.yml @@ -0,0 +1,380 @@ + + +attributes.default: + + app: + # build - static|dynamic + # dynamic - volumes are mounted and the build step is run once the containers have started + # static - app is copied into console, built, then the resulting build is copied into the web image + build: dynamic + + # mode - development|production + # development - additional tooling is made available and application is run in development mode + # production - leaner images with less tooling and application is run in production mode + mode: development + + services: [mysql] + web_owner: www-data + web_group: www-data + web_writable_dirs: [] + web_writable_files: [] + + web_directory: /app/public + vendor_directory: /app/vendor + + delegated-volumes: no + docker-sync: no + mutagen: yes + + jenkins: + credentials: + my127ws_key: = @('workspace.name') ~ '-my127ws-key' + + pipeline: + base: + persistence: = @('persistence') + resourcePrefix: ~ + hostname: = @('pipeline.preview.hostname') + publish: + enabled: no + services: = publishable_services(@('services')) + # when enabled the application helm chart will be published + # to the given git repository. + chart: + enabled: no + git: + key: ~ # deprecated, use ssh_private_key + # private key with write access to the repository + ssh_private_key: = @('pipeline.publish.chart.git.key') + # eg. git@github.com:organisation/project.git + repository: ~ + # path within the repository to place the chart, no leading or trailing slashes + # note: an additional directory with the branch name will be created + path: = 'build-artifacts/' ~ @('workspace.name') + # sets the git user.email before pushing the commit + email: name@example.com + preview: + enabled: no + environment: ~ + cluster: + name: null + namespace: = @('workspace.name') ~ '-' ~ slugify(branch()) + hostname: = @('pipeline.preview.namespace') ~ '.example.com' + rabbitmq: + external_host: = 'rabbitmq-' ~ @('pipeline.preview.hostname') + persistence: + redis_session: + enabled: false + qa: + enabled: no + environment: ~ + branch: develop + cluster: + name: null + namespace: = @('workspace.name') ~ '-' ~ 'qa' + hostname: = @('pipeline.qa.namespace') ~ '.example.com' + resourcePrefix: ~ + persistence: ~ + rabbitmq: + external_host: = 'rabbitmq-' ~ @('pipeline.qa.hostname') + + docker: + registry: + url: = get_docker_registry(@('docker.repository')) + username: = @('docker.username') # for backwards compatibility + password: = @('docker.password') # for backwards compatibility + repository: = @("workspace.name") + compose: + file_version: '3.7' + host_volume_options: "= ':' ~ (@('delegated-volumes') ? 'delegated' : 'cached')" + config: null # deprecated + # If using gitops helm chart repositories, it's recommended to put this configuration + # in the values.yml in there now instead of project application repositories + image_pull_config: = @('docker.config') + image: + console: = 'my127/php:' ~ @('php.version') ~ '-fpm-stretch-console' + php-fpm: = 'my127/php:' ~ @('php.version') ~ '-fpm-stretch' + + composer: + auth: + basic: ~ + github: ~ + + backend: + path: /app + build: + when: -f "composer.json" + steps: + - task composer:install + install: + steps: [] + init: + steps: [] + migrate: + steps: [] + cron: + jobs: [] + + framework: + readme_blocks: [] + + frontend: + path: /app + watch: npm run watch + build: + when: -f "package.json" + steps: + - | + if [ "$APP_BUILD" == "static" ] && [ -f package-lock.json ]; then + run npm clean-install + else + run npm install + fi + - | + if [ "$(jq ".scripts.build != null" < package.json)" != "false" ]; then + run npm run build + fi + - | + if [ "$APP_BUILD" == "static" ]; then + run rm -rf node_modules/ + run npm cache clean --force + fi + + git: + main_branch: develop + + nginx: + # used to set site specific configurations under server directive + site: + conf: [] + # used to set nginx global configurations under http directive + global: + conf: [] + # used to limit what is copied into an Nginx static-built image + copy_directories: + - = @('app.web_directory') + + node: + # only set this attribute if you wish to override the supplied node version, by default + # the supplied version will be the current LTS. + version: null + + php: + version: 7.3 + cli: + ini: + max_execution_time: 0 + memory_limit: -1 + fpm: + ini: + disable_functions: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals + error_reporting: "E_ALL" + expose_php: Off + log_errors: On + max_execution_time: 30 + max_input_time: 60 + memory_limit: 1024M + output_buffering: 4096 + register_argc_argv: Off + request_order: GP + variables_order: GPCS + ini: + enable_dl: Off + opcache.enable_cli: On + realpath_cache_ttl: 600 + sendmail_path: = '\'/usr/local/bin/mhsendmail --smtp-addr="' ~ @('smtp.host') ~ ':' ~ @('smtp.port') ~ '"\'' + short_open_tag: Off + ext-xdebug: + enable: no + cli: + enable: no + config: + remote_enable: 1 + remote_autostart: 1 + remote_port: 9000 + remote_host: host.docker.internal + idekey: workspace + + assets: + remote: ="s3://"~@("aws.bucket")~"/development" + local: tools/assets/development + + database: + # possible platforms are mysql, postgres or ~ for none + platform: mysql + host: mysql + user: app + pass: app + name: app + root_pass: DV6RdNY3QcFsBk7V + port_forward: ~ + var: + max_allowed_packet: 4M + import: + steps: [] + + elasticsearch: + host: elasticsearch + image: elasticsearch + port: 9200 + tag: 7.1.1 + + domain: my127.site + hostname: = @('namespace') ~ '.' ~ @('domain') + hostname_aliases: [] + + helm: + additional_schema_locations: https://inviqa.github.io/kubernetes-json-schema/schema + feature: + # Note: be very careful considering disabling this, as in most cases + # it causes the secrets in it to be stored plaintext on filesystem + # or in helm chart repositories + # requires sealed-secrets k8s operator + sealed_secrets: false + timeout: 300 + + sealed_secrets: + controller_name: sealed-secrets + controller_namespace: sealed-secrets + + php-fpm: + pools: + www: + port: 9000 + entrypoint: + steps: [] + + mysql: + image: mysql + tag: 5.7 + + rabbitmq: + image: rabbitmq + tag: 3.8-management-alpine + api_port: 15672 + erlang_cookie: TeTTiwT9y548yIcfw4peaOrqgtLItD6B + external_host: = 'rabbitmq-' ~ @('hostname') + host: rabbitmq + port: 5672 + user: rabbitmq + password: rabbitmq + vhosts: + default: '/' + + redis: + host: redis + port: 6379 + + redis_session: + host: redis-session + port: 6379 + + smtp: + host: 'mailhog-relay' + port: 1025 + + varnish: + target_service: nginx + + resources: + cpu: + requests: [] + limits: [] + memory: + app_init: "1024Mi" + app_migrate: "1024Mi" + console: "2048Mi" + cron: "1024Mi" + elasticsearch: "1024Mi" + mysql: "512Mi" + nginx: "100Mi" + php_fpm: "1024Mi" + php_fpm_exporter: "100Mi" + postgres: "512Mi" + rabbitmq: "1024Mi" + redis: "256Mi" + redis_session: "1024Mi" + varnish: "1124Mi" + + replicas: + varnish: 1 + + persistence: + enabled: false + mountVolumesOnConsole: true # possible to disable, but may lead to unexpected concequences with app init/migrate + + elasticsearch: + enabled: true + accessMode: ReadWriteOnce + size: 2Gi + mysql: + enabled: true + accessMode: ReadWriteOnce + size: 4Gi + rabbitmq: + enabled: true + accessMode: ReadWriteOnce + size: 2Gi + postgres: + enabled: true + accessMode: ReadWriteOnce + size: 4Gi + redis: + enabled: false + accessMode: ReadWriteOnce + size: 2Gi + redis_session: + enabled: true + accessMode: ReadWriteOnce + size: 2Gi + + tls: + key: | + -----BEGIN PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC9q3EMGDtDNKAt + CHyM1MfWUaIGsv5Zka7pfjIOKL/KvrSIVpZyyJ3lSALeRyyDDRZeYwkbkNdDJgir + FmD0vJkYifYWfGdIJmbjdm62LGyzDf79Ve8aQF0lLcedMhoCfu7+qciIYnjHdvkx + Y6Fo9CTZP7m+oSNmUjHdJD3scX4l3vodBK67Hpk+W2eEhwAxG5aFsspDnPi4L9ha + KlMps+eSn1hLLGYrmthtP2assckvdyL10hS7+OOZFmj5vxGNbQn2F4whiu06JmXL + oO8T9yd8XNN383FjREYKjrQcyJo3izvawEgrRi9mNKM9DhWuBdbYNe/cSgGfwBK6 + Kc9e8zCLAgMBAAECggEAVL6fOgoxoGuJDdX24G3KBCZhQKEFKDwBbO4nq0/lsc7X + lvspKYwdkG5Gac5fQwa78dxKG3jx1VzPDrJnC7KgrOgnfhCDjScrXYJzIQ5kWvRr + 9AFLXe1YMN5ti/zwxiC05DA0G0v0LxsnaDvdyKkdNbxVX6lbycH76ZTh3h0vgfeD + Wc8MMlRTa+ORWKoIZsm+qnNLhWQhhbmoA8LNkZqtJIOBZuyAu8vyXzrEFIhKqVzU + Jdz0S+xtgYUkoXCTgdYiqoFjlKhaIIV2Fc0vbc68ziIiu3ZtaVch7wNyftoXyjBr + hjI1GT1dr7NsQpPZ6xtqjGhIvUuVSeCSBZeeXkymAQKBgQDsxFb916gIxl3QQYPV + NiKPi3rZD5iuMTjy0937dhHWGGYlWW2GEE0XjWrmLUnGyLuxwA5kFgS0eMubbhTg + aeewHamHeN/pcJIF6HndJbqXLB148E8ux6grpQrHKpTb2RLOPkd6bAgn1SjPllCL + a/vVanevaJs8U27sKEfGND0aCwKBgQDNE7IzvD1JAUrzqzG9Lxg+NhaHQTQfW45W + cuHvESiyhc93kdufvcCTrrRRXSZOqDN26+64Ni62O/ulduq4qZmhG/lxqSz5mnpr + 2oV2Kg1C1EUC4V2B9WQoMOhvOa0esTVSm9hC4oX0kYIzl6HbVAUGe8JMXOoKTJkV + 3sAz0FbTgQKBgQC1CX+2wvIiG4NaHO4v1h/hAHajiDBnaQ2xZtzCTMpgmPFpt5Ju + QwKfcqt9ar2RuKUDyeV3E/ru/7o3k5l06qWUXWnmQz96oG+XAuZDeXjN5JZ4hc8V + 5uYo0R6HoYCHBdlCSA6hhf9Kbcuxxq65nIzH54uyXNrt6qHTAw22ePULdwKBgQCK + vp+a1ukToldGQfV1zA330Pou6dNMv9Gt9S2cY5yII3W4rKrNCUDn6ZO/VGkdYDjp + ZTft02KJEk3vpWOqKbxxvo5l8pImEPhwTbhruImeRCSojTaJPS9U7bnjvj68/CFa + UWvf3IfKbkOLijQMQmzf9Q0AQwBolWgg3sJki7iigQKBgQCf82eve3W/s/pZzKGi + WWACcZLmDTHeH2AU3ZDfFaEAKObe/cdHMgk8MGewf1IF6QDQXtaxHlM5/FKQ1ohu + uoe7Xo1R+KlrVRxKAlNQ5lfzhAAgDNaOhpgkH4cehfPrIIh9rwNMSkHDRS0DELvY + DPyRumPy7zTg5YPzzl7tM1/OPA== + -----END PRIVATE KEY----- + + crt: | + -----BEGIN CERTIFICATE----- + MIIC6zCCAdOgAwIBAgIJAI4syJyPEWAMMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNV + BAMMASowHhcNMTgwNzI0MTE1MjQ0WhcNMTkwNzI0MTE1MjQ0WjAMMQowCAYDVQQD + DAEqMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvatxDBg7QzSgLQh8 + jNTH1lGiBrL+WZGu6X4yDii/yr60iFaWcsid5UgC3kcsgw0WXmMJG5DXQyYIqxZg + 9LyZGIn2FnxnSCZm43Zutixssw3+/VXvGkBdJS3HnTIaAn7u/qnIiGJ4x3b5MWOh + aPQk2T+5vqEjZlIx3SQ97HF+Jd76HQSuux6ZPltnhIcAMRuWhbLKQ5z4uC/YWipT + KbPnkp9YSyxmK5rYbT9mrLHJL3ci9dIUu/jjmRZo+b8RjW0J9heMIYrtOiZly6Dv + E/cnfFzTd/NxY0RGCo60HMiaN4s72sBIK0YvZjSjPQ4VrgXW2DXv3EoBn8ASuinP + XvMwiwIDAQABo1AwTjAdBgNVHQ4EFgQUK4SXDSeyVYbCWr31rz2K6GLRFoYwHwYD + VR0jBBgwFoAUK4SXDSeyVYbCWr31rz2K6GLRFoYwDAYDVR0TBAUwAwEB/zANBgkq + hkiG9w0BAQUFAAOCAQEAjz2PUbdi2S+h4UB3P2UBLdgDClkiSPF+gAqfw4D82faf + M8hpMuCcd3148dejU4tFPUdLx1MASK8ucCk7rcwVtafWPYU4nMDlmZ9Zj9F2Y8KY + dVfHfTIOblTSYc90g+nTFsTchkEFOH0nRZAKhCT3HphXNTZFNIQWoqe63SJZ8LTs + 8RBO/zcoh5E31+Rm0WxKlYH4QElLp9dXAtKueWGTafh2E8Re96IS+Uig+yC3RIYJ + MWLcATwR3lSnqN2ifByic5VGWbRKkGNsh3wAWlejL2FGv8mLU1q8nLK36UcU/HzZ + ziLtpidJOJHDpyDSAxDSxcP9fJ6gssMQln92DJ/SUQ== + -----END CERTIFICATE----- diff --git a/harness/attributes/docker-base.yml b/harness/attributes/docker-base.yml new file mode 100644 index 00000000..4b52e20e --- /dev/null +++ b/harness/attributes/docker-base.yml @@ -0,0 +1,134 @@ +attributes: + services: + php-base: + environment: + HOST_OS_FAMILY: = @('host.os') + APP_NAME: = @('workspace.name') + APP_HOST: = @('hostname') + DB_PLATFORM: = @('database.platform') + DB_HOST: = @('database.host') + DB_USER: = @('database.user') + DB_NAME: = @('database.name') + ELASTICSEARCH_HOST: = @('elasticsearch.host') + ELASTICSEARCH_PORT: = @('elasticsearch.port') + RABBITMQ_API_PORT: = @('rabbitmq.api_port') + RABBITMQ_EXTERNAL_HOST: = @('rabbitmq.external_host') + RABBITMQ_HOST: = @('rabbitmq.host') + RABBITMQ_PORT: = @('rabbitmq.port') + RABBITMQ_USER: = @('rabbitmq.user') + RABBITMQ_VHOST: = @('rabbitmq.vhosts.default') + REDIS_HOST: = @('redis.host') + REDIS_PORT: = @('redis.port') + REDIS_SESSION_HOST: = @('redis_session.host') + REDIS_SESSION_PORT: = @('redis_session.port') + PHP_IDE_CONFIG: "serverName=workspace" + VARNISH_HOSTNAME_TEMPLATE: "varnish-%d.varnish-headless" + environment_secrets: + DB_PASS: = @('database.pass') + RABBITMQ_PASSWORD: = @('rabbitmq.password') + nginx: + publish: true + environment: + FPM_HOST: php-fpm + console: + enabled: true + publish: true + environment: + DB_ADMIN_USER: root + HAS_VARNISH: "= ('varnish' in @('app.services') ? 'true' : 'false')" + environment_secrets: + DB_ADMIN_PASS: = @('database.root_pass') + php-fpm: + publish: true + environment: + AUTOSTART_PHP_FPM: "true" + php-fpm-exporter: + environment: + PHP_FPM_SCRAPE_URI: = php_fpm_exporter_scrape_url('php-fpm', @('php-fpm.pools')) + mysql: + environment: + MYSQL_DATABASE: = @('database.name') + MYSQL_USER: = @('database.user') + environment_secrets: + MYSQL_PASSWORD: = @('database.pass') + MYSQL_ROOT_PASSWORD: = @('database.root_pass') + rabbitmq: + environment: + RABBITMQ_DEFAULT_USER: = @('rabbitmq.user') + RABBITMQ_DEFAULT_VHOST: = @('rabbitmq.vhosts.default') + environment_secrets: + RABBITMQ_DEFAULT_PASS: = @('rabbitmq.password') + RABBITMQ_ERLANG_COOKIE: = @('rabbitmq.erlang_cookie') + postgres: + environment: + POSTGRES_DB: = @('database.name') + POSTGRES_USER: = @('database.user') + PGDATA: /var/lib/postgresql/data/pgdata + environment_secrets: + POSTGRES_PASSWORD: = @('database.pass') + cron: + publish: "= 'cron' in @('app.services')" + environment: [] + varnish: + environment: + VARNISH_SIZE: "1024M" + pipeline: + base: + prometheus: + podMonitoring: false + services: + php-base: + environment: + APP_HOST: = @('pipeline.base.hostname') + DB_HOST: = '{{ .Values.resourcePrefix }}' ~ @('database.host') + ELASTICSEARCH_HOST: '{{ if .Values.service.elasticsearch }}{{ .Values.resourcePrefix }}elasticsearch{{ end }}' + ES_HOST: '{{ if .Values.service.elasticsearch }}{{ .Values.resourcePrefix }}elasticsearch:9200{{ end }}' + RABBITMQ_HOST: '{{ if .Values.service.rabbitmq }}{{ .Values.resourcePrefix }}rabbitmq{{ end }}' + RABBITMQ_EXTERNAL_HOST: = @('pipeline.preview.rabbitmq.external_host') + REDIS_HOST: '{{ if .Values.service.redis }}{{ .Values.resourcePrefix }}redis{{ end }}' + REDIS_SESSION_HOST: '{{ if .Values.service.redis_session }}{{ .Values.resourcePrefix }}redis-session{{ end }}' + PHP_IDE_CONFIG: = '' + VARNISH_HOSTNAME_TEMPLATE: "{{ .Values.resourcePrefix }}varnish-%d.{{ .Values.resourcePrefix }}varnish-headless" + nginx: + environment: + FPM_HOST: localhost + ingress: + annotations: {} + metricsEnabled: false + metricsEndpoints: + - port: http + php-fpm-exporter: + environment: + PHP_FPM_SCRAPE_URI: = php_fpm_exporter_scrape_url('127.0.0.1', @('php-fpm.pools')) + metricsEnabled: true + metricsEndpoints: + - port: php-fpm-metrics + istio: + gateways: + - "istio-system/{{ .Release.Namespace }}-gateway" + additionalGateways: [] + production: {} + qa: + services: + php-base: + environment: + APP_HOST: = @('pipeline.qa.hostname') + RABBITMQ_EXTERNAL_HOST: = @('pipeline.qa.rabbitmq.external_host') + preview: + resources: + memory: + app_init: "1024Mi" + app_migrate: "1024Mi" + console: "1024Mi" + cron: "1024Mi" + elasticsearch: "1024Mi" + mysql: "512Mi" + nginx: "64Mi" + php_fpm: "1024Mi" + php_fpm_exporter: "32Mi" + postgres: "512Mi" + redis: "64Mi" + redis_session: "64Mi" + services: + console: + enabled: false diff --git a/harness/config/commands.yml b/harness/config/commands.yml new file mode 100644 index 00000000..06b0cc9f --- /dev/null +++ b/harness/config/commands.yml @@ -0,0 +1,250 @@ + +command('enable'): + env: + USE_DOCKER_SYNC: = @('host.os') == 'darwin' and @('docker-sync') == 'yes' ? 'yes':'no' + USE_MUTAGEN: = @('host.os') == 'darwin' and @('mutagen') == 'yes' ? 'yes':'no' + APP_BUILD: = @('app.build') + APP_MODE: = @('app.mode') + NAMESPACE: = @('namespace') + HAS_ASSETS: = @('aws.bucket') !== null ? 'yes':'no' + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/enable.sh + +command('disable'): + env: + USE_DOCKER_SYNC: = (@('host.os') == 'darwin' and @('docker-sync') == 'yes') ? 'yes':'no' + USE_MUTAGEN: = @('host.os') == 'darwin' and @('mutagen') == 'yes' ? 'yes':'no' + NAMESPACE: = @('namespace') + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/disable.sh + +command('destroy'): + env: + NAMESPACE: = @('namespace') + APP_BUILD: = @('app.build') + APP_VERSION: = @('app.version') + DOCKER_REPOSITORY: = @('docker.repository') + USE_DOCKER_SYNC: = (@('host.os') == 'darwin' and @('docker-sync') == 'yes') ? 'yes':'no' + USE_MUTAGEN: = @('host.os') == 'darwin' and @('mutagen') == 'yes' ? 'yes':'no' + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + source .my127ws/harness/scripts/destroy.sh + +command('networks external'): + env: + NETWORKS: = get_docker_external_networks() + exec: | + #!bash(workspace:/) + for NETWORK in ${NETWORKS}; do + if ! docker network inspect "${NETWORK}" >/dev/null 2>&1; then + passthru docker network create "${NETWORK}" + fi + done + +command('exec %'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|= + if [ -t 0 ] && [ -t 1 ] ; then + docker-compose exec -u build console ={ input.argument('%') } + else + docker-compose exec -T -u build console ={ input.argument('%') } + fi + +command('logs %'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(harness:/)|= + docker-compose logs ={input.argument('%')} + +command('ps'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + docker-compose ps + +command('console'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + passthru docker-compose exec -u build console bash + +command('composer %'): + exec: | + #!bash(workspace:/)|= + passthru ws exec composer ={ input.argument('%') } + +command('db-console'): | + #!bash + ws db console + +command('db console'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + passthru "docker-compose exec console bash -c 'mysql --host \"\$DB_HOST\" --user \"\$DB_USER\" -p\"\$DB_PASS\" \"\$DB_NAME\"'" + +command('assets download'): + env: + AWS_ID: =@('aws.id') + AWS_KEY: =@('aws.key') + exec: | + #!bash(workspace:/)|@ + passthru ws.aws s3 sync '@('assets.remote')' '@('assets.local')' + +command('assets upload'): + env: + AWS_ID: =@('aws.id') + AWS_KEY: =@('aws.key') + exec: | + #!bash(workspace:/)|@ + passthru ws.aws s3 sync '@('assets.local')' '@('assets.remote')' + +command('feature docker-sync (on|off)'): + env: + ATTR_KEY: 'docker-sync' + ATTR_VAL: = input.command(3) == 'on' ? 'yes':'no' + exec: | + #!bash(workspace:/)|= + ws set $ATTR_KEY $ATTR_VAL + echo 'Updating docker-compose.yml' + run ws install --step=prepare + echo 'Bringing up the environment with the new setting' + if [[ "$ATTR_VAL" = "yes" ]]; then + passthru ws docker-sync start + fi + passthru ws enable + if [[ "$ATTR_VAL" = "no" ]]; then + passthru ws docker-sync stop + passthru ws docker-sync clean + fi + echo 'Done' + +command('frontend build'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + passthru docker-compose exec -u build console app build:frontend + +command('frontend watch'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + # Use `bash -i` to load up /home/build/.bashrc, which sets up node version manager (nvm) paths + passthru docker-compose exec -u build --workdir '@('frontend.path')' console bash -i -c '@('frontend.watch')' + +command('frontend console'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + # Use `bash -i` to load up /home/build/.bashrc, which sets up node version manager (nvm) paths + passthru docker-compose exec -u build --workdir '@('frontend.path')' console bash -i + +command('port '): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|= + passthru docker port "$(docker-compose ps -q ={input.argument('service')})" + +command('service php-fpm restart'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + passthru ws install --step=prepare + passthru docker-compose exec console bash -c 'cp -r /.my127ws/docker/image/console/root/usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/' + passthru docker-compose exec php-fpm bash -c 'cp -r /.my127ws/docker/image/php-fpm/root/usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/' + passthru docker-compose exec php-fpm supervisorctl restart php-fpm + +command('set '): + env: + ATTR_KEY: = input.argument('attribute') + ATTR_VAL: = input.argument('value') + exec: | + #!bash(workspace:/)|= + if [ ! -f workspace.override.yml ]; then + touch workspace.override.yml + fi + if grep -q "attribute('${ATTR_KEY}'):" workspace.override.yml; then + echo "Removing old '${ATTR_KEY}' setting from workspace.override.yml" + sed "/^attribute('${ATTR_KEY}'): .*$/d" workspace.override.yml > workspace.override.yml.tmp && mv workspace.override.yml.tmp workspace.override.yml + fi + if grep -q "attribute('${ATTR_KEY}'):" workspace.override.yml; then + echo 'Could not remove line from workspace.override.yml, failing' + exit 1 + fi + echo "Setting '${ATTR_KEY}' setting to '${ATTR_VAL}' in workspace.override.yml" + echo "attribute('${ATTR_KEY}'): ${ATTR_VAL}" >> workspace.override.yml + +command('feature xdebug (on|off)'): + env: + ATTR_KEY: 'php.ext-xdebug.enable' + ATTR_VAL: = input.command(3) == 'on' ? 'yes':'no' + exec: | + #!bash(workspace:/)|= + ws set $ATTR_KEY $ATTR_VAL + echo 'Updating templates in .my127ws/' + run ws install --step=prepare + echo 'Bringing up php-fpm with the new setting' + run ws service php-fpm restart + echo 'Done' + +command('feature xdebug cli (on|off)'): + env: + ATTR_KEY: 'php.ext-xdebug.cli.enable' + ATTR_VAL: = input.command(4) == 'on' ? 'yes':'no' + exec: | + #!bash(workspace:/)|= + ws set $ATTR_KEY $ATTR_VAL + echo 'Updating templates in .my127ws/' + run ws install --step=prepare + echo 'Bringing up console with the new setting' + run ws service php-fpm restart + echo 'Done' + +command('db import '): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + DATABASE_FILE: = input.argument('database_file') + exec: | + #!bash(workspace:/)|= + passthru docker-compose exec -u build console app database:import "$DATABASE_FILE" + +command('harness update existing'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + ws disable + rm -rf .my127ws + ws install --step=download + ws harness prepare + touch .my127ws/.flag-built + ws refresh + ws exec app overlay:apply + ws exec composer install + ws exec app migrate + ws exec app welcome + +command('harness update fresh'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + ws disable || true + rm -rf .my127ws + ws install diff --git a/harness/config/docker-sync.yml b/harness/config/docker-sync.yml new file mode 100644 index 00000000..accbfab1 --- /dev/null +++ b/harness/config/docker-sync.yml @@ -0,0 +1,8 @@ +command('docker-sync (start|stop|clean)'): + env: + NAMESPACE: = @('namespace') + COMMAND: = input.command(2) + PATH: "$PATH:./.my127ws/" + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/docker_sync.sh "$COMMAND" diff --git a/harness/config/events.yml b/harness/config/events.yml new file mode 100644 index 00000000..3b9ee0bc --- /dev/null +++ b/harness/config/events.yml @@ -0,0 +1,15 @@ + +after('harness.install'): | + #!bash + ws enable + +after('harness.refresh'): + env: + COMPOSE_PROJECT: = @('namespace') + exec: | + #!bash(workspace:/)|@ + run docker-compose stop + passthru "docker-compose config --services | grep -v php-fpm | xargs docker-compose pull" + passthru "docker-compose config --services | grep -v cron | grep -v job-queue-consumer | grep -v jenkins-runner | xargs docker-compose build --pull" + passthru "docker-compose config --services | grep -v cron | grep -v job-queue-consumer | grep -v jenkins-runner | xargs docker-compose up -d" + run docker-compose up -d --build diff --git a/harness/config/external-images.yml b/harness/config/external-images.yml new file mode 100644 index 00000000..2dadbc7c --- /dev/null +++ b/harness/config/external-images.yml @@ -0,0 +1,38 @@ +function('external_images', [services]): | + #!php + $upstreamImages = $producedImages = []; + $excludeImages = ['scratch']; + + foreach ($services as $service) { + if (count($service['upstream']) > 0) { + $upstreamImages = array_merge($upstreamImages, $service['upstream']); + if ($service['image']) { + $producedImages[] = $service['image']; + } + } else if ($service['image']) { + $upstreamImages[] = $service['image']; + } + } + $externalImages = array_diff($upstreamImages, $producedImages, $excludeImages); + + # workspace commands don't allow non-string types + = join(' ', $externalImages); + + +command('external-images config'): + env: + IMAGES: = external_images(docker_service_images()) + exec: | + #!php + $compose = ['version' => '3', 'services' => []]; + foreach (explode(' ', $env['IMAGES']) as $image) { + $compose['services'][str_replace(['/', ':'], '_', $image)] = ['image' => $image]; + } + echo \Symfony\Component\Yaml\Yaml::dump($compose, 100, 2); + +command('external-images pull'): + env: + BASE_IMAGES: = external_images(docker_service_images()) + exec: | + #!bash(workspace:/)|@ + passthru 'ws external-images config | docker-compose -f - pull' diff --git a/harness/config/functions.yml b/harness/config/functions.yml new file mode 100644 index 00000000..508d097b --- /dev/null +++ b/harness/config/functions.yml @@ -0,0 +1,207 @@ +function('to_yaml', [data]): | + #!php + $yaml = \Symfony\Component\Yaml\Yaml::dump($data, 100, 2)); + if (is_array($data) && count($data) > 0) { + $yaml = "\n" . rtrim(preg_replace('/^/m', ' ', $yaml), "\n"); + } + = $yaml; + +function('to_nice_yaml', [data, indentation, nesting]): | + #!php + $yaml = \Symfony\Component\Yaml\Yaml::dump($data, 100, $indentation ?: 2); + if (is_array($data) && count($data) > 0) { + $yaml = "\n" . rtrim(preg_replace('/^/m', str_repeat(' ', $nesting ?: 2), $yaml), "\n"); + } + = $yaml; + +function('indent', [text, indentation]): | + #!php + = preg_replace('/^/m', str_repeat(' ', $indentation ?: 2), $text); + +function('deep_merge', [arrays]): | + #!php + // source https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drupal_array_merge_deep_array/7.x + $deepMerge = function ($arrays) use (&$deepMerge) { + $result = array(); + foreach ($arrays as $array) { + if ($array === null) { continue; } + foreach ($array as $key => $value) { + // Renumber integer keys as array_merge_recursive() does. Note that PHP + // automatically converts array keys that are integer strings (e.g., '1') + // to integers. + if (is_integer($key)) { + $result[] = $value; + } + elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { + $result[$key] = $deepMerge(array( + $result[$key], + $value, + )); + } + else { + $result[$key] = $value; + } + } + } + return $result; + }; + = $deepMerge($arrays); + +function('deep_merge_to_yaml', [arrays, indentation, nesting]): | + #!php + trigger_error('deep_merge_to_yaml is deprecated, please use separate deep_merge and to_nice_yaml functions', E_USER_DEPRECATED); + // source https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drupal_array_merge_deep_array/7.x + $deepMerge = function ($arrays) use (&$deepMerge) { + $result = array(); + foreach ($arrays as $array) { + if ($array === null) { continue; } + foreach ($array as $key => $value) { + // Renumber integer keys as array_merge_recursive() does. Note that PHP + // automatically converts array keys that are integer strings (e.g., '1') + // to integers. + if (is_integer($key)) { + $result[] = $value; + } + elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { + $result[$key] = $deepMerge(array( + $result[$key], + $value, + )); + } + else { + $result[$key] = $value; + } + } + } + return $result; + }; + $text = $deepMerge($arrays); + = preg_replace('/^/m', str_repeat(' ', $nesting ?: 2), \Symfony\Component\Yaml\Yaml::dump($text, 100, $indentation ?: 2)); + +function('filter_local_services', [services]): | + #!php + $filteredServices = []; + foreach ($services as $serviceName => $service) { + $filteredService = []; + foreach ($service as $key => $value) { + switch ($key) { + case 'environment': + case 'enabled': + $filteredService[$key] = $value; + } + } + if (count($filteredService) > 0) { + $filteredServices[$serviceName] = $filteredService; + } + } + = $filteredServices; + +function('get_docker_external_networks'): | + #!php + $configRaw = shell_exec('docker-compose config'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + $externalNetworks = []; + if (isset($config['networks'])) { + foreach ($config['networks'] as $network) { + if (isset($network['external'])) { + if (is_array($network['external'])) { + $externalNetworks[] = $network['external']['name']; + } else if ($network['external'] === true) { + $externalNetworks[] = $network['name']; + } + } + } + } + = join(" ", $externalNetworks); + +function('docker_service_images'): | + #!php + $configRaw = shell_exec('docker-compose config'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + $images = []; + + foreach ($config['services'] as $serviceName => $service) { + $image = [ + 'image' => $service['image'] ?? null, + 'upstream' => [], + ]; + + if (isset($service['build'])) { + $context = rtrim($service['build']['context'], '/'); + $dockerfile = $service['build']['dockerfile'] ?? 'Dockerfile'; + + if (preg_match_all('/^FROM\s+([^\s]*)/m', file_get_contents($context.'/'.$dockerfile), $matches) === false) { + continue; + } + $image['upstream'] = $matches[1]; + } + $images[$serviceName] = $image; + } + + = $images; + +function('get_docker_registry', [dockerRepository]): | + #!php + $dockerRepoParts = explode('/', $dockerRepository); + if (strpos($dockerRepoParts[0], '.') !== false) { + $registry = $dockerRepoParts[0]; + } + = $registry ?? 'https://index.docker.io/v1/'; + +function('docker_config', [registryConfig]): | + #!php + $config = [ + 'auths' => [ + $registryConfig['url'] => [ + 'auth' => base64_encode($registryConfig['username'].':'.$registryConfig['password']) + ] + ] + ]; + + = json_encode($config); + +function('branch'): | + #!bash(workspace:/) + =$(git branch | grep \* | cut -d ' ' -f2) + +function('slugify', [text]): | + #!php + $text = preg_replace('~[^\pL\d]+~u', '-', $text); + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + $text = preg_replace('~[^-\w]+~', '', $text); + $text = trim($text, '-'); + $text = preg_replace('~-+~', '-', $text); + $text = strtolower($text); + = $text; + +function('php_fpm_exporter_scrape_url', [hostname, pools]): | + #!php + $text = join( + ',', + array_map( + function ($pool) use ($hostname) { + return 'tcp://' . $hostname .':' . $pool['port'] . '/status'; + }, + $pools + ) + ); + = $text; + +function('publishable_services', [services]): | + #!php + $pushServices = array_map( + function ($serviceName, $service) { + return ($service['publish'] ?? false) ? $serviceName : null; + }, + array_keys($services), + $services + ); + = join(' ', array_filter($pushServices)); + +function('version_compare', [version1, version2, operator]): | + #!php + $count1 = substr_count($version1, '.'); + $count2 = substr_count($version2, '.'); + $version1 .= str_repeat('.0', max(0, $count2 - $count1)); + $version2 .= str_repeat('.0', max(0, $count1 - $count2)); + = version_compare($version1, $version2, $operator); diff --git a/harness/config/mutagen.yml b/harness/config/mutagen.yml new file mode 100644 index 00000000..a9188296 --- /dev/null +++ b/harness/config/mutagen.yml @@ -0,0 +1,103 @@ +function('get_mutagen_volume_names'): | + #!php + $volumeNames = []; + + if (file_exists('mutagen.yml')) { + $configRaw = file_get_contents('mutagen.yml'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + + if (isset($config['sync'])) { + foreach (array_keys($config['sync']) as $syncName) { + $volumeNames[] = $syncName . '-sync'; + } + } + } + = $volumeNames; + +function('get_mutagen_volume_containers'): | + #!php + $volumeContainers = []; + + if (file_exists('mutagen.yml')) { + $configRaw = file_get_contents('mutagen.yml'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + + if (isset($config['sync'])) { + foreach ($config['sync'] as $syncConfig) { + $parts = parse_url($syncConfig['beta']); + $volumeContainers[] = $parts['host']; + } + } + } + = join("\n", array_unique($volumeContainers)); + +function('get_mutagen_volume_mappings'): | + #!php + $volumeMappings = []; + + if (file_exists('mutagen.yml')) { + $configRaw = file_get_contents('mutagen.yml'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + + if (isset($config['sync'])) { + foreach ($config['sync'] as $syncName => $syncConfig) { + $parts = parse_url($syncConfig['beta']); + $volumeMappings[] = $parts['host'] . ':' . $syncName . '-sync:' . $parts['path']; + } + } + } + = join("\n", $volumeMappings); + +command('mutagen (start|stop|pause|resume)'): + env: + COMMAND: = input.command(2) + CONTAINER_NAMES: = get_mutagen_volume_containers() + VOLUME_MAPPINGS: = get_mutagen_volume_mappings() + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/mutagen.sh "$COMMAND" + +command('mutagen rm'): + env: + CONTAINER_NAMES: = get_mutagen_volume_containers() + exec: | + #!bash(workspace:/) + CONTAINER_NAMES=($CONTAINER_NAMES) + passthru docker volume rm "${CONTAINER_NAMES[@]}" + +command('switch (cached-volumes|delegated-volumes|mutagen|docker-sync)'): + env: + SYNC: = input.command(2) + CONTAINER_NAMES: = get_mutagen_volume_containers() + exec: | + #!bash(workspace:/)|= + run ws disable + if [[ "$SYNC = "delegated-volumes" ]]; then + ws set delegated-volumes yes + ws set mutagen no + ws set docker-sync no + elif [[ "$SYNC" = "mutagen" ]]; then + ws set delegated-volumes no + ws set mutagen yes + ws set docker-sync no + elif [[ "$SYNC" = "docker-sync" ]]; then + ws set delegated-volumes no + ws set mutagen no + ws set docker-sync yes + else + ws set delegated-volumes no + ws set mutagen no + ws set docker-sync no + fi + run ws harness prepare + echo 'Bringing up the environment with the new setting' + if [[ "$SYNC" = "mutagen" ]]; then + if command -v docker-sync >/dev/null 2>&1 && [ -f docker-sync.yml ]; then + passthru ws docker-sync clean + fi + passthru ws mutagen start + else + passthru ws mutagen stop + fi + passthru ws enable + echo 'Done' diff --git a/harness/config/pipeline.yml b/harness/config/pipeline.yml new file mode 100644 index 00000000..adb57099 --- /dev/null +++ b/harness/config/pipeline.yml @@ -0,0 +1,112 @@ + +command('app build'): + env: + HAS_CRON: "= ('cron' in @('app.services') ? 'true' : 'false')" + exec: | + #!bash(workspace:/)|@ + ws external-images pull + + # dependency ordered build + passthru docker-compose build console + passthru docker-compose build php-fpm nginx + if [[ "${HAS_CRON}" == "true" ]]; then + passthru docker-compose build cron + fi + +command('app build '): + env: + SERVICE: = input.argument('service') + exec: | + #!bash(workspace:/)|@ + passthru docker-compose build "${SERVICE}" + +command('app publish'): | + #!bash(workspace:/)|@ + echo '@('docker.registry.password')' | run docker login --username='@('docker.registry.username')' --password-stdin '@('docker.registry.url')' + passthru docker-compose push @('pipeline.publish.services') + run docker logout '@('docker.registry.url')' + +command('app publish chart '): + env: + SSH_PRIVATE_KEY: = @('pipeline.publish.chart.git.ssh_private_key') + REPOSITORY: = @('pipeline.publish.chart.git.repository') + ARTIFACTS_PATH: = "./build-artifacts-repository/" ~ @('pipeline.publish.chart.git.path') ~ "/" ~ input.argument('release') + MESSAGE: = input.argument('message') + USER_EMAIL: = @('pipeline.publish.chart.git.email') + exec: | + #!bash(workspace:/)|@ + + run rm -rf build-artifacts-repository + echo "${SSH_PRIVATE_KEY}" | base64 -d > id_rsa + chmod 0600 id_rsa + + export GIT_SSH_COMMAND='ssh -i ./id_rsa -o "IdentitiesOnly yes" -F /dev/null -o StrictHostKeyChecking=no' + + run git clone "$REPOSITORY" ./build-artifacts-repository + run git -C ./build-artifacts-repository config user.email "${USER_EMAIL}" + + run mkdir -p $ARTIFACTS_PATH + run rsync --exclude='*.twig' --exclude='_twig' --delete -a .my127ws/helm/app/ "${ARTIFACTS_PATH}/" + + export GIT_SSH_COMMAND='ssh -i ../id_rsa -o "IdentitiesOnly yes" -F /dev/null -o StrictHostKeyChecking=no' + run git -C ./build-artifacts-repository add . + run "git -C ./build-artifacts-repository commit --allow-empty -m '${MESSAGE}'" + run git -C ./build-artifacts-repository push origin master + +command('app deploy '): + env: + ENVIRONMENT: = input.argument('environment') + NAMESPACE: = @('pipeline.' ~ input.argument('environment') ~ '.namespace') + CLUSTER: = @('pipeline.' ~ input.argument('environment') ~ '.cluster.name') + TIMEOUT: = @('helm.timeout') + exec: | + #!bash(harness:/helm)|= + set -o pipefail + cd "${ENVIRONMENT}" + doctl -t "$DO_ACCESS_TOKEN" kubernetes cluster kubeconfig show "$CLUSTER" > kubectl.config.yaml + if helm version --short --client | grep '^Client: v2' >/dev/null 2>&1; then + passthru helm init --client-only + fi + passthru helm dependency build + passthru helm --kubeconfig="${PWD}/kubectl.config.yaml" upgrade --wait --atomic --install --timeout "${TIMEOUT}" --namespace "${NAMESPACE}" "${NAMESPACE}" ./ + +command('helm template '): + env: + CHART_PATH: = input.argument('chart-path') + NAMESPACE: = @('pipeline.' ~ input.argument('environment') ~ '.namespace') + exec: | + #!bash(harness:/helm)|= + set -o pipefail + cd "${CHART_PATH}" + if helm version --short --client | grep '^Client: v2' >/dev/null 2>&1; then + passthru helm init --client-only + fi + passthru helm dependency build + passthru helm template . + +command('helm kubeval '): + env: + CHART_PATH: = input.argument('chart-path') + NAMESPACE: = @('pipeline.' ~ input.argument('environment') ~ '.namespace') + ADDITIONAL_SCHEMA_LOCATIONS: = @('helm.additional_schema_locations') + exec: | + #!bash(harness:/helm)|= + set -o pipefail + cd "${CHART_PATH}" + if helm version --short --client | grep '^Client: v2' >/dev/null 2>&1; then + passthru helm init --client-only + fi + passthru helm dependency build + + if readlink "${HELM_HOME:-$HOME}/.helm/plugins/helm-kubeval" | grep '/https-github.com-instrumenta-helm-kubeval$' >/dev/null; then + passthru helm plugin remove kubeval + fi + + passthru helm plugin install https://github.com/inviqa/helm-kubeval || true + KUBEVAL_OPTS=() + + if [ -n "${ADDITIONAL_SCHEMA_LOCATIONS:-}" ]; then + KUBEVAL_OPTS+=(--additional-schema-locations "${ADDITIONAL_SCHEMA_LOCATIONS}") + fi + + passthru helm kubeval "${KUBEVAL_OPTS[@]}" . diff --git a/harness/config/secrets.yml b/harness/config/secrets.yml new file mode 100644 index 00000000..d2b2cae5 --- /dev/null +++ b/harness/config/secrets.yml @@ -0,0 +1,99 @@ +command('secret image-pull-config'): + env: + SEALED_SECRETS: "= @('helm.feature.sealed_secrets') ? 'yes' : 'no'" + DEFAULT_CONFIG: = docker_config(@('docker.registry')) + SEALED_SECRETS_CONTROLLER_NAME: = @('helm.sealed_secrets.controller_name') + SEALED_SECRETS_CONTROLLER_NAMESPACE: = @('helm.sealed_secrets.controller_namespace') + exec: | + #!bash + if [ "$SEALED_SECRETS" == 'yes' ] && ! command -v kubeseal >/dev/null; then + echo 'kubeseal is needed in order to use this command' >&2 + exit 1 + fi + + if [ -t 1 ] ; then + # Use an editor with a temp file to allow longer terminal input + TMPFILE="$(mktemp -t tmp.XXXXXXXXX)" + "${EDITOR:-vi}" "${TMPFILE}" + DOCKER_CONFIG="$(cat "${TMPFILE}")" + rm -f "${TMPFILE}" + else + # read from stdin until EOF + DOCKER_CONFIG="$(cat)" + fi + + DOCKER_CONFIG="${DOCKER_CONFIG:-${DEFAULT_CONFIG}}" + + if [ "$SEALED_SECRETS" == 'yes' ]; then + echo 'Encrypting as a sealed-secret value with certificate from current kubectl context' >&2 + KUBESEAL_OPTS=( + --name "image-pull-config" + --scope cluster-wide + ) + if [ -n "${SEALED_SECRETS_CONTROLLER_NAME:-}" ]; then + KUBESEAL_OPTS+=( + --controller-name "${SEALED_SECRETS_CONTROLLER_NAME}" + ) + fi + if [ -n "${SEALED_SECRETS_CONTROLLER_NAMESPACE:-}" ]; then + KUBESEAL_OPTS+=( + --controller-namespace "${SEALED_SECRETS_CONTROLLER_NAMESPACE}" + ) + fi + + echo -n "${DOCKER_CONFIG}" | kubeseal --raw "${KUBESEAL_OPTS[@]}" + else + echo 'Note: this has unencrypted credentials in, do not save directly to file' >&2 + echo "If storing within workspace attributes, use `ws secret encrypt` first" >&2 + echo "${DOCKER_CONFIG}" | base64 + fi + +command('sealed-secret encrypt (string|blob) '): + env: + INPUT_TYPE: = input.command(3) + SEALED_SECRETS_CONTROLLER_NAME: = @('helm.sealed_secrets.controller_name') + SEALED_SECRETS_CONTROLLER_NAMESPACE: = @('helm.sealed_secrets.controller_namespace') + SECRET_NAME: = input.argument('secret-name') + exec: | + #!bash + if ! command -v kubeseal >/dev/null; then + echo 'kubeseal is needed in order to use this command' >&2 + exit 1 + fi + + echo "Enter the secret ${INPUT_TYPE} to encrypt" >&2 + case "${INPUT_TYPE}" in + string) + read DATA # read a single line + ;; + blob) + if [ -t 1 ] ; then + # Use an editor with a temp file to allow longer terminal input + TMPFILE="$(mktemp -t tmp.XXXXXXXXX)" + "${EDITOR:-vi}" "${TMPFILE}" + DATA="$(cat "${TMPFILE}")" + rm -f "${TMPFILE}" + else + # read from stdin until EOF + DATA="$(cat)" + fi + ;; + esac + + echo 'Encrypting as a sealed-secret value with certificate from current kubectl context' >&2 + KUBESEAL_OPTS=( + --name "${SECRET_NAME}" + --scope cluster-wide + ) + if [ -n "${SEALED_SECRETS_CONTROLLER_NAME:-}" ]; then + KUBESEAL_OPTS+=( + --controller-name "${SEALED_SECRETS_CONTROLLER_NAME}" + ) + fi + if [ -n "${SEALED_SECRETS_CONTROLLER_NAMESPACE:-}" ]; then + KUBESEAL_OPTS+=( + --controller-namespace "${SEALED_SECRETS_CONTROLLER_NAMESPACE}" + ) + fi + + echo -n "${DATA}" | kubeseal --raw "${KUBESEAL_OPTS[@]}" diff --git a/harness/scripts/destroy.sh b/harness/scripts/destroy.sh new file mode 100755 index 00000000..a95f28a3 --- /dev/null +++ b/harness/scripts/destroy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +run docker-compose down --rmi local --volumes --remove-orphans + +if [[ "$USE_DOCKER_SYNC" = "yes" ]] && [ -f docker-sync.yml ]; then + run ws docker-sync clean + run docker volume rm "${NAMESPACE}-sync" +elif [[ "$USE_MUTAGEN" = "yes" ]]; then + run ws mutagen stop + passthru ws mutagen rm +fi + +if [[ "$APP_BUILD" = "static" ]]; then + run "docker images --filter=since='${DOCKER_REPOSITORY}:${APP_VERSION}-console' -q | xargs --no-run-if-empty docker image rm --force" + run "docker images --filter=reference='${DOCKER_REPOSITORY}:${APP_VERSION}-*' -q | xargs --no-run-if-empty docker image rm --force" + run "docker images --filter=reference='${NAMESPACE}-*:dev' -q | xargs --no-run-if-empty docker image rm --force" +fi + +run rm -f .my127ws/.flag-built diff --git a/harness/scripts/disable.sh b/harness/scripts/disable.sh new file mode 100755 index 00000000..632d0298 --- /dev/null +++ b/harness/scripts/disable.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [[ "$USE_DOCKER_SYNC" = "yes" ]] && [ -f docker-sync.yml ]; then + run ws docker-sync stop +elif [[ "$USE_MUTAGEN" = "yes" ]]; then + run ws mutagen pause +fi + +run docker-compose stop diff --git a/harness/scripts/docker_sync.sh b/harness/scripts/docker_sync.sh new file mode 100644 index 00000000..2a0cb135 --- /dev/null +++ b/harness/scripts/docker_sync.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +if [ "$#" -ne 1 ]; then + echo "This script supports only one parameter" + exit 1 +fi + +COMMAND="$1" +PATH="$PATH:./.my127ws/utilities/rbenv/bin/" + +install_rbenv() +( + if command -v rbenv > /dev/null 2>&1; then + return 0 + fi + + if command -v sw_vers > /dev/null 2>&1 && sw_vers | grep -q Mac && command -v brew > /dev/null 2>&1; then + passthru brew install rbenv + else + mkdir -p .my127ws/utilities/ + if [ ! -d .my127ws/utilities/rbenv ]; then + git clone https://github.com/rbenv/rbenv.git .my127ws/utilities/rbenv + fi + mkdir -p "$(rbenv root)"/plugins + git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build + fi + + passthru rbenv install --skip-existing 2.7.0 + run rbenv local 2.7.0 + init_rbenv +) + +init_rbenv() +{ + eval "$(rbenv init -)" +} + +install_docker_sync() +( + if command -v docker-sync > /dev/null 2>&1; then + return 0 + fi + + install_rbenv + passthru gem install docker-sync --no-document +) + +start() +{ + install_docker_sync + init_rbenv + passthru docker-sync start +} + +stop() +{ + init_rbenv + passthru docker-sync stop +} + +clean() +{ + init_rbenv + run docker-sync stop + run docker rm "${NAMESPACE}-sync" +} + +case "$COMMAND" in + start) + start + ;; + stop) + stop + ;; + clean) + clean + ;; + *) + echo "Command not supported" + exit 1 + ;; +esac diff --git a/harness/scripts/latest-mutagen-release.php b/harness/scripts/latest-mutagen-release.php new file mode 100644 index 00000000..b5ace9cc --- /dev/null +++ b/harness/scripts/latest-mutagen-release.php @@ -0,0 +1,33 @@ + [ + 'header' => "User-Agent: inviqa/harness-base-php\r\n", + ], +]); +$releases = file_get_contents('https://api.github.com/repos/mutagen-io/mutagen/releases', false, $fetchOptions); +if (!$releases) { + throw new Exception('Could not fetch releases. Response from GitHub: ' . $releases); +} + +$jsonReleases = json_decode($releases, true); +if (!$jsonReleases) { + throw new Exception('Could not decode releases. Response from GitHub: ' . $releases); +} + +$stableReleases = array_filter($jsonReleases, function ($release) { + return !$release['prerelease'] && !$release['draft']; +}); +$latestStableRelease = reset($stableReleases); +if (!$latestStableRelease) { + throw new Exception('Could not find latest stable release.'); +} + +$osFamily = strtolower(PHP_OS_FAMILY); +$releaseAssets = array_filter($latestStableRelease['assets'], function ($asset) use ($osFamily) { + return preg_match("/^mutagen_.*${osFamily}.*amd64.*/", $asset['name']) > 0; +}); +$releaseAsset = reset($releaseAssets); +if (!$releaseAsset) { + throw new Exception('Could not find latest stable release asset for your OS Platform: ' . $osFamily); +} +echo $releaseAsset['browser_download_url']; diff --git a/harness/scripts/mutagen.sh b/harness/scripts/mutagen.sh new file mode 100644 index 00000000..607b9e43 --- /dev/null +++ b/harness/scripts/mutagen.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if [ "$#" -ne 1 ]; then + echo "This script supports only one parameter" + exit 1 +fi + +COMMAND="$1" +PATH="$PATH:./.my127ws/utilities/mutagen/" + +# shellcheck disable=SC2206 +CONTAINER_NAMES=($CONTAINER_NAMES) + +install_mutagen() +{ + if command -v mutagen > /dev/null 2>&1; then + return 0 + fi + + if command -v sw_vers > /dev/null 2>&1 && sw_vers | grep -q Mac && command -v brew > /dev/null 2>&1; then + passthru brew install mutagen-io/mutagen/mutagen + return "$?" + fi + mkdir -p .my127ws/utilities/mutagen/ + local download_url="" + download_url="$(php .my127ws/harness/scripts/latest-mutagen-release.php)" + if [ -z "$download_url" ]; then + echo "Failed to get mutagen download link. Please install mutagen globally." + return 1 + fi + + run curl -L -q -sS -f "$download_url" -o .my127ws/utilities/mutagen/mutagen.tar.gz + run "cd .my127ws/utilities/mutagen/ && tar -xf mutagen.tar.gz" +} + +setup_sync_container() +{ + local CONTAINER_NAME + local CONTAINER_VOLUME_MAPPINGS + local line + for CONTAINER_NAME in "${CONTAINER_NAMES[@]}"; do + if [[ "$(docker ps -a -f "name=${CONTAINER_NAME}" --format '{{.Names}}')" == "${CONTAINER_NAME}" ]]; then + passthru docker rm -f "${CONTAINER_NAME}" + fi + + CONTAINER_VOLUME_MAPPINGS=() + while IFS= read -r line; do + CONTAINER_VOLUME_MAPPINGS+=("$line") + done < <(echo "${VOLUME_MAPPINGS}" | grep "^${CONTAINER_NAME}" | cut -d ':' -f2- ) + + # shellcheck disable=SC2046 + passthru docker run -d --name "${CONTAINER_NAME}" $(printf -- '-v %q ' "${CONTAINER_VOLUME_MAPPINGS[@]}") alpine:latest tail -f /dev/null + done +} + +start_mutagen_daemon() +{ + passthru mutagen daemon start +} + +start() +{ + install_mutagen + setup_sync_container + start_mutagen_daemon + + mutagen project list > /dev/null 2>&1 && passthru mutagen project terminate + passthru mutagen project start + passthru mutagen project flush +} + +stop() +{ + passthru mutagen project terminate + passthru docker rm -f "${CONTAINER_NAMES[@]}" +} + +pause() +{ + passthru mutagen project pause + passthru docker stop "${CONTAINER_NAMES[@]}" +} + +resume() +{ + start_mutagen_daemon + passthru docker start "${CONTAINER_NAMES[@]}" + passthru mutagen project resume +} + +case "$COMMAND" in + start) + start + ;; + stop) + stop + ;; + pause) + pause + ;; + resume) + resume + ;; + *) + echo "Command not supported" + exit 1 + ;; +esac diff --git a/helm/app/Chart.yaml.twig b/helm/app/Chart.yaml.twig new file mode 100644 index 00000000..ee356e32 --- /dev/null +++ b/helm/app/Chart.yaml.twig @@ -0,0 +1,5 @@ +name: {{ @('workspace.name') }} +description: Base helm chart for {{ @('workspace.name') }} +version: 0.0.1 +sources: +home: diff --git a/helm/app/_twig/templates/service/varnish/configmap.yaml.twig b/helm/app/_twig/templates/service/varnish/configmap.yaml.twig new file mode 100644 index 00000000..7ac0fa15 --- /dev/null +++ b/helm/app/_twig/templates/service/varnish/configmap.yaml.twig @@ -0,0 +1,17 @@ +{% set blocks = 'docker/image/varnish/root/etc/varnish/' %} +{% verbatim %} +{{ if .Values.service.varnish }} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}varnish + name: {{ .Values.resourcePrefix }}varnish-configuration +data: + default.vcl: | +{% endverbatim %} +{%- set config = include(blocks ~ 'default.vcl.twig', {varnish: {target_service: "{{ .Values.resourcePrefix }}webapp"}}) %} +{{ indent(config, 4) }} +{%- verbatim %} +{{ end }} +{% endverbatim %} diff --git a/helm/app/templates/_base_helper.tpl b/helm/app/templates/_base_helper.tpl new file mode 100644 index 00000000..b7943ea4 --- /dev/null +++ b/helm/app/templates/_base_helper.tpl @@ -0,0 +1,44 @@ +{{- define "application.volumes.backend" }}{{ end }} +{{- define "application.volumeMounts.backend" }}{{ end }} + +{{- define "application.volumes.all" }}{{ end }} +{{- define "application.volumeMounts.all" }}{{ end }} + +{{- define "application.volumes.wwwDataPaths" }}{{ end }} + +{{- define "application.volumes.console" -}} +{{- if .Values.persistence.mountVolumesOnConsole -}} +{{- template "application.volumes.backend" . -}} +{{- template "application.volumes.all" . -}} +{{- end -}} +{{- end }} +{{- define "application.volumeMounts.console" -}} +{{- if .Values.persistence.mountVolumesOnConsole -}} +{{- template "application.volumeMounts.backend" . -}} +{{- template "application.volumeMounts.all" . -}} +{{- end -}} +{{- end }} + +{{- define "service.environment.secret" }} +{{ if .service.environment_secrets }} +{{ if .Values.feature.sealed_secrets }} +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +{{ else }} +apiVersion: v1 +kind: Secret +{{ end }} +metadata: + name: {{ .Values.resourcePrefix }}{{ .service_name }} +{{ if .Values.feature.sealed_secrets }} + annotations: + sealedsecrets.bitnami.com/cluster-wide: "true" +spec: + encryptedData: +{{ index .service.environment_secrets | toYaml | nindent 4 -}} +{{ else }} +stringData: +{{ index .service.environment_secrets | toYaml | nindent 2 -}} +{{ end }} +{{ end }} +{{- end }} diff --git a/helm/app/templates/application/app-init.yaml b/helm/app/templates/application/app-init.yaml new file mode 100644 index 00000000..0d75ef28 --- /dev/null +++ b/helm/app/templates/application/app-init.yaml @@ -0,0 +1,62 @@ +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "console") (dict "environment" .Values.environment) -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Values.resourcePrefix }}app-init + annotations: + helm.sh/hook: "post-install" + helm.sh/hook-delete-policy: "before-hook-creation" + argocd.argoproj.io/hook: "Sync" + argocd.argoproj.io/hook-delete-policy: "BeforeHookCreation" + argocd.argoproj.io/sync-wave: "5" +spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - env: + {{- range $key, $value := $service.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if $service.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}console + {{- end }} + image: {{ .Values.docker.image.console }} + imagePullPolicy: Always + name: app-init +{{ if eq .Values.ingress.type "istio" }} + command: ["/bin/sh"] + args: ["-c", "app init ; code=$? ; curl -vv -XPOST http://127.0.0.1:15020/quitquitquit ; exit $code"] +{{ else }} + command: ["app", "init"] +{{ end }} + resources: + limits: + memory: {{ .Values.resources.memory.app_init }} + requests: + memory: {{ .Values.resources.memory.app_init }} + volumeMounts: + {{- if not (eq "" (include "application.volumeMounts.console" .)) }} + {{- include "application.volumeMounts.console" . | indent 8 }} + {{- else }} [] + {{- end }} +{{- if .Values.docker.image_pull_config }} + imagePullSecrets: + - name: {{ .Values.resourcePrefix }}image-pull-config +{{- end }} + restartPolicy: Never + enableServiceLinks: false + volumes: + {{- if not (eq "" (include "application.volumes.console" .)) }} + {{- include "application.volumes.console" . | indent 6 }} + {{- else }} [] + {{- end }} diff --git a/helm/app/templates/application/app-migrate.yaml b/helm/app/templates/application/app-migrate.yaml new file mode 100644 index 00000000..530e9e96 --- /dev/null +++ b/helm/app/templates/application/app-migrate.yaml @@ -0,0 +1,60 @@ +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "console") (dict "environment" .Values.environment) -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Values.resourcePrefix }}app-migrate + annotations: + helm.sh/hook: "pre-upgrade" + helm.sh/hook-delete-policy: "before-hook-creation" + argocd.argoproj.io/hook: "Sync" + argocd.argoproj.io/hook-delete-policy: "BeforeHookCreation" + argocd.argoproj.io/sync-wave: "10" +spec: + template: + spec: + containers: + - env: + {{- range $key, $value := $service.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if $service.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}console + {{- end }} + image: {{ .Values.docker.image.console }} + imagePullPolicy: Always + name: app-migrate +{{ if eq .Values.ingress.type "istio" }} + command: ["/bin/sh"] + args: ["-c", "app migrate ; code=$? ; curl -vv -XPOST http://127.0.0.1:15020/quitquitquit ; exit $code"] +{{ else }} + command: ["app", "migrate"] +{{ end }} + resources: + limits: + memory: {{ .Values.resources.memory.app_migrate }} + requests: + memory: {{ .Values.resources.memory.app_migrate }} + volumeMounts: + {{- if not (eq "" (include "application.volumeMounts.console" .)) }} + {{- include "application.volumeMounts.console" . | indent 8 }} + {{- else }} [] + {{- end }} +{{- if .Values.docker.image_pull_config }} + imagePullSecrets: + - name: {{ .Values.resourcePrefix }}image-pull-config +{{- end }} + restartPolicy: Never + enableServiceLinks: false + volumes: + {{- if not (eq "" (include "application.volumes.console" .)) }} + {{- include "application.volumes.console" . | indent 6 }} + {{- else }} [] + {{- end }} diff --git a/helm/app/templates/application/console/deployment.yaml b/helm/app/templates/application/console/deployment.yaml new file mode 100644 index 00000000..326ef1e7 --- /dev/null +++ b/helm/app/templates/application/console/deployment.yaml @@ -0,0 +1,70 @@ +{{- if .Values.docker.services.console.enabled -}} +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "console") (dict "environment" .Values.environment) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}console + labels: + app.service: {{ .Values.resourcePrefix }}console + annotations: + argocd.argoproj.io/sync-wave: "15" +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}console + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}console + spec: + containers: + - env: + {{- range $key, $value := $service.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if $service.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}console + {{- end }} + image: {{ .Values.docker.image.console }} + imagePullPolicy: Always + name: console + resources: + limits: + memory: {{ .Values.resources.memory.console }} + requests: + memory: {{ .Values.resources.memory.console }} + readinessProbe: + exec: + command: + - app + - state + initialDelaySeconds: 20 + periodSeconds: 10 + volumeMounts: + {{- if not (eq "" (include "application.volumeMounts.console" .)) }} + {{- include "application.volumeMounts.console" . | indent 8 }} + {{- else }} [] + {{- end }} +{{- if .Values.docker.image_pull_config }} + imagePullSecrets: + - name: {{ .Values.resourcePrefix }}image-pull-config +{{- end }} + restartPolicy: Always + enableServiceLinks: false + volumes: + {{- if not (eq "" (include "application.volumes.console" .)) }} + {{- include "application.volumes.console" . | indent 6 }} + {{- else }} [] + {{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/application/console/secret.yaml b/helm/app/templates/application/console/secret.yaml new file mode 100644 index 00000000..db1aea56 --- /dev/null +++ b/helm/app/templates/application/console/secret.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "console") -}} +{{ template "service.environment.secret" (dict "service_name" "console" "service" $service "Values" .Values) }} diff --git a/helm/app/templates/application/cron/deployment.yaml b/helm/app/templates/application/cron/deployment.yaml new file mode 100644 index 00000000..06dde31a --- /dev/null +++ b/helm/app/templates/application/cron/deployment.yaml @@ -0,0 +1,81 @@ +{{- if .Values.service.cron -}} +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "cron") (dict "environment" .Values.environment) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}cron + labels: + app.service: {{ .Values.resourcePrefix }}cron + annotations: + argocd.argoproj.io/sync-wave: "15" +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}cron + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}cron + spec: + {{- if not (eq "" (include "application.volumes.wwwDataPaths" .)) }} + initContainers: + - name: cron-volume-permissions + image: busybox + command: + - "/bin/chown" + - "-R" + - "www-data" + {{- include "application.volumes.wwwDataPaths" . | indent 8 }} + volumeMounts: + {{- if not (and (eq "" (include "application.volumeMounts.backend" .)) (eq "" (include "application.volumeMounts.all" .)) ) }} + {{- include "application.volumeMounts.backend" . | indent 8 }} + {{- include "application.volumeMounts.all" . | indent 8 }} + {{- else }} [] + {{- end }} + {{- end }} + containers: + - name: cron + env: + {{- range $key, $value := $service.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if $service.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}cron + {{- end }} + image: {{ .Values.docker.image.cron }} + imagePullPolicy: Always + resources: + limits: + memory: {{ .Values.resources.memory.cron }} + requests: + memory: {{ .Values.resources.memory.cron }} + volumeMounts: + {{- if not (and (eq "" (include "application.volumeMounts.backend" .)) (eq "" (include "application.volumeMounts.all" .)) ) }} + {{- include "application.volumeMounts.backend" . | indent 8 }} + {{- include "application.volumeMounts.all" . | indent 8 }} + {{- else }} [] + {{- end }} +{{- if .Values.docker.image_pull_config }} + imagePullSecrets: + - name: {{ .Values.resourcePrefix }}image-pull-config +{{- end }} + restartPolicy: Always + enableServiceLinks: false + volumes: + {{- if not (and (eq "" (include "application.volumes.backend" .)) (eq "" (include "application.volumes.all" .)) ) }} + {{- include "application.volumes.backend" . | indent 6 }} + {{- include "application.volumes.all" . | indent 6 }} + {{- else }} [] + {{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/application/cron/secret.yaml b/helm/app/templates/application/cron/secret.yaml new file mode 100644 index 00000000..84b39ae7 --- /dev/null +++ b/helm/app/templates/application/cron/secret.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "cron") -}} +{{ template "service.environment.secret" (dict "service_name" "cron" "service" $service "Values" .Values) }} diff --git a/helm/app/templates/application/image-pull-config.yaml b/helm/app/templates/application/image-pull-config.yaml new file mode 100644 index 00000000..c3cce246 --- /dev/null +++ b/helm/app/templates/application/image-pull-config.yaml @@ -0,0 +1,24 @@ +{{ if .Values.docker.image_pull_config }} +{{ if .Values.feature.sealed_secrets }} +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +{{ else }} +apiVersion: v1 +kind: Secret +{{ end }} +metadata: + name: {{ .Values.resourcePrefix }}image-pull-config + annotations: + sealedsecrets.bitnami.com/cluster-wide: "true" +{{ if .Values.feature.sealed_secrets }} +spec: + template: + type: kubernetes.io/dockerconfigjson + encryptedData: + .dockerconfigjson: {{ .Values.docker.image_pull_config }} +{{ else }} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ .Values.docker.image_pull_config }} +{{ end }} +{{ end }} diff --git a/helm/app/templates/application/webapp/deployment.yaml b/helm/app/templates/application/webapp/deployment.yaml new file mode 100644 index 00000000..4f479936 --- /dev/null +++ b/helm/app/templates/application/webapp/deployment.yaml @@ -0,0 +1,152 @@ +{{- $service_php_fpm := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "php-fpm") (dict "environment" .Values.environment) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}webapp + labels: + app.service: {{ .Values.resourcePrefix }}webapp + annotations: + argocd.argoproj.io/sync-wave: "15" +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}webapp + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}webapp + spec: + {{- if not (eq "" (include "application.volumes.wwwDataPaths" .)) }} + initContainers: + - name: webapp-volume-permissions + image: busybox + command: + - "/bin/chown" + - "-R" + - "www-data" + {{- include "application.volumes.wwwDataPaths" . | indent 8 }} + volumeMounts: + {{- if not (and (eq "" (include "application.volumeMounts.backend" .)) (eq "" (include "application.volumeMounts.all" .)) ) }} + {{- include "application.volumeMounts.backend" . | indent 8 }} + {{- include "application.volumeMounts.all" . | indent 8 }} + {{- else }} [] + {{- end }} + {{- end }} + containers: + - name: nginx + env: + {{- range $key, $value := .Values.docker.services.nginx.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.docker.services.nginx.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}nginx + {{- end }} + image: {{ .Values.docker.image.nginx }} + imagePullPolicy: Always + ports: + - name: http + containerPort: 80 + resources: + limits: + memory: {{ .Values.resources.memory.nginx }} + requests: + memory: {{ .Values.resources.memory.nginx }} + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + {{- if not (eq "" (include "application.volumeMounts.all" .)) }} + {{- include "application.volumeMounts.all" . | indent 8 }} + {{- else }} [] + {{- end }} + + - name: php-fpm + env: + {{- range $key, $value := $service_php_fpm.environment }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if $service_php_fpm.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}php-fpm + {{- end }} + image: {{ .Values.docker.image.fpm }} + imagePullPolicy: Always + ports: + - containerPort: 9000 + resources: + limits: + memory: {{ .Values.resources.memory.php_fpm }} + requests: + memory: {{ .Values.resources.memory.php_fpm }} + readinessProbe: + tcpSocket: + port: 9000 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + {{- if not (and (eq "" (include "application.volumeMounts.backend" .)) (eq "" (include "application.volumeMounts.all" .)) ) }} + {{- include "application.volumeMounts.backend" . | indent 8 }} + {{- include "application.volumeMounts.all" . | indent 8 }} + {{- else }} [] + {{- end }} + + {{ if .Values.service.php_fpm_exporter }} + - name: php-fpm-exporter + env: + {{- range $key, $value := index .Values.docker.services "php-fpm-exporter" "environment" }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + image: hipages/php-fpm_exporter + imagePullPolicy: Always + ports: + - name: php-fpm-metrics + containerPort: 9253 + resources: + limits: + memory: {{ .Values.resources.memory.php_fpm_exporter }} + requests: + memory: {{ .Values.resources.memory.php_fpm_exporter }} + readinessProbe: + tcpSocket: + port: 9253 + initialDelaySeconds: 5 + periodSeconds: 10 + {{ end }} +{{- if .Values.docker.image_pull_config }} + imagePullSecrets: + - name: {{ .Values.resourcePrefix }}image-pull-config +{{- end }} + restartPolicy: Always + enableServiceLinks: false + volumes: + {{- if not (and (eq "" (include "application.volumes.backend" .)) (eq "" (include "application.volumes.all" .)) ) }} + {{- include "application.volumes.backend" . | indent 6 }} + {{- include "application.volumes.all" . | indent 6 }} + {{- else }} [] + {{- end }} +status: {} diff --git a/helm/app/templates/application/webapp/podmonitor.yaml b/helm/app/templates/application/webapp/podmonitor.yaml new file mode 100644 index 00000000..6d650e69 --- /dev/null +++ b/helm/app/templates/application/webapp/podmonitor.yaml @@ -0,0 +1,19 @@ +{{- if .Values.prometheus.podMonitoring -}} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ .Values.resourcePrefix }}webapp + labels: + app.service: {{ .Values.resourcePrefix }}webapp +spec: + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}webapp + podMetricsEndpoints: +{{- if .Values.docker.services.nginx.metricsEnabled -}} +{{ .Values.docker.services.nginx.metricsEndpoints | toYaml | nindent 6 -}} +{{- end -}} +{{- if and .Values.service.php_fpm_exporter (index .Values.docker.services "php-fpm-exporter" "metricsEnabled") -}} +{{ index .Values.docker.services "php-fpm-exporter" "metricsEndpoints" | toYaml | nindent 6 -}} +{{- end -}} +{{- end -}} diff --git a/helm/app/templates/application/webapp/secret-nginx.yaml b/helm/app/templates/application/webapp/secret-nginx.yaml new file mode 100644 index 00000000..d084b784 --- /dev/null +++ b/helm/app/templates/application/webapp/secret-nginx.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "nginx" "service" .Values.docker.services.nginx "Values" .Values) }} diff --git a/helm/app/templates/application/webapp/secret-php-fpm.yaml b/helm/app/templates/application/webapp/secret-php-fpm.yaml new file mode 100644 index 00000000..6d8df564 --- /dev/null +++ b/helm/app/templates/application/webapp/secret-php-fpm.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.docker.services "php-base") (index .Values.docker.services "php-fpm") -}} +{{ template "service.environment.secret" (dict "service_name" "php-fpm" "service" $service "Values" .Values) }} diff --git a/helm/app/templates/application/webapp/service.yaml b/helm/app/templates/application/webapp/service.yaml new file mode 100644 index 00000000..5760135b --- /dev/null +++ b/helm/app/templates/application/webapp/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}webapp + name: {{ .Values.resourcePrefix }}webapp +spec: + ports: + - name: http-80 + port: 80 + targetPort: 80 + selector: + app.service: {{ .Values.resourcePrefix }}webapp +status: + loadBalancer: {} diff --git a/helm/app/templates/service/elasticsearch/deployment.yaml b/helm/app/templates/service/elasticsearch/deployment.yaml new file mode 100644 index 00000000..da0a6943 --- /dev/null +++ b/helm/app/templates/service/elasticsearch/deployment.yaml @@ -0,0 +1,52 @@ +{{ if .Values.service.elasticsearch }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}elasticsearch + labels: + app.service: {{ .Values.resourcePrefix }}elasticsearch +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}elasticsearch + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}elasticsearch + spec: + containers: + - env: + - name: ES_JAVA_OPTS + value: -Xmx512m -Xms512m + - name: discovery.type + value: single-node + image: {{ .Values.docker.image.elasticsearch }} + imagePullPolicy: Always + name: elasticsearch + ports: + - containerPort: 9200 + resources: + limits: + memory: {{ .Values.resources.memory.elasticsearch }} + requests: + memory: {{ .Values.resources.memory.elasticsearch }} + readinessProbe: + tcpSocket: + port: 9200 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}elasticsearch-persistent-storage + mountPath: /usr/share/elasticsearch/data + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}elasticsearch-persistent-storage +{{- if .Values.persistence.elasticsearch.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}elasticsearch-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/elasticsearch/pvc.yaml b/helm/app/templates/service/elasticsearch/pvc.yaml new file mode 100644 index 00000000..823f0f2f --- /dev/null +++ b/helm/app/templates/service/elasticsearch/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.elasticsearch .Values.persistence.elasticsearch.enabled -}} + +{{- with .Values.persistence.elasticsearch -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}elasticsearch-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}elasticsearch +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/elasticsearch/service.yaml b/helm/app/templates/service/elasticsearch/service.yaml new file mode 100644 index 00000000..6f550dde --- /dev/null +++ b/helm/app/templates/service/elasticsearch/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.elasticsearch }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}elasticsearch + name: {{ .Values.resourcePrefix }}elasticsearch +spec: + ports: + - name: "9200" + port: 9200 + targetPort: 9200 + selector: + app.service: {{ .Values.resourcePrefix }}elasticsearch +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/mysql/deployment.yaml b/helm/app/templates/service/mysql/deployment.yaml new file mode 100644 index 00000000..3388cc6d --- /dev/null +++ b/helm/app/templates/service/mysql/deployment.yaml @@ -0,0 +1,60 @@ +{{ if .Values.service.mysql }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}mysql + labels: + app.service: {{ .Values.resourcePrefix }}mysql +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}mysql + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}mysql + spec: + containers: + - env: + {{- range $key, $value := .Values.docker.services.mysql.environment }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.docker.services.mysql.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}mysql + {{- end }} + image: {{ .Values.docker.image.mysql }} + imagePullPolicy: Always + name: mysql + args: + - --ignore-db-dir + - lost+found + ports: + - containerPort: 3306 + resources: + limits: + memory: {{ .Values.resources.memory.mysql }} + requests: + memory: {{ .Values.resources.memory.mysql }} + readinessProbe: + tcpSocket: + port: 3306 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}mysql-persistent-storage + mountPath: /var/lib/mysql + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}mysql-persistent-storage +{{- if .Values.persistence.mysql.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}mysql-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/mysql/pvc.yaml b/helm/app/templates/service/mysql/pvc.yaml new file mode 100644 index 00000000..155c55a1 --- /dev/null +++ b/helm/app/templates/service/mysql/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.mysql .Values.persistence.mysql.enabled -}} + +{{- with .Values.persistence.mysql -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}mysql-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}mysql +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/mysql/secret.yaml b/helm/app/templates/service/mysql/secret.yaml new file mode 100644 index 00000000..88b0d814 --- /dev/null +++ b/helm/app/templates/service/mysql/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "mysql" "service" .Values.docker.services.mysql "Values" .Values) }} diff --git a/helm/app/templates/service/mysql/service.yaml b/helm/app/templates/service/mysql/service.yaml new file mode 100644 index 00000000..26e8f093 --- /dev/null +++ b/helm/app/templates/service/mysql/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.mysql }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}mysql + name: {{ .Values.resourcePrefix }}mysql +spec: + ports: + - name: "3306" + port: 3306 + targetPort: 3306 + selector: + app.service: {{ .Values.resourcePrefix }}mysql +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/postgres/deployment.yaml b/helm/app/templates/service/postgres/deployment.yaml new file mode 100644 index 00000000..720da028 --- /dev/null +++ b/helm/app/templates/service/postgres/deployment.yaml @@ -0,0 +1,57 @@ +{{ if .Values.service.postgres }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}postgres + labels: + app.service: {{ .Values.resourcePrefix }}postgres +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}postgres + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}postgres + spec: + containers: + - env: + {{- range $key, $value := .Values.docker.services.postgres.environment }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.docker.services.postgres.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}postgres + {{- end }} + image: postgres:9.6 + imagePullPolicy: Always + name: postgres + ports: + - containerPort: 5432 + resources: + limits: + memory: {{ .Values.resources.memory.postgres }} + requests: + memory: {{ .Values.resources.memory.postgres }} + readinessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}postgres-persistent-storage + mountPath: /var/lib/postgresql/data + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}postgres-persistent-storage +{{- if .Values.persistence.postgres.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}postgres-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/postgres/pvc.yaml b/helm/app/templates/service/postgres/pvc.yaml new file mode 100644 index 00000000..6e69a506 --- /dev/null +++ b/helm/app/templates/service/postgres/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.postgres .Values.persistence.postgres.enabled -}} + +{{- with .Values.persistence.postgres -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}postgres-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}postgres +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/postgres/secret.yaml b/helm/app/templates/service/postgres/secret.yaml new file mode 100644 index 00000000..58ffaf90 --- /dev/null +++ b/helm/app/templates/service/postgres/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "postgres" "service" .Values.docker.services.postgres "Values" .Values) }} diff --git a/helm/app/templates/service/postgres/service.yaml b/helm/app/templates/service/postgres/service.yaml new file mode 100644 index 00000000..ee08424a --- /dev/null +++ b/helm/app/templates/service/postgres/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.postgres }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}postgres + name: {{ .Values.resourcePrefix }}postgres +spec: + ports: + - name: "5432" + port: 5432 + targetPort: 5432 + selector: + app.service: {{ .Values.resourcePrefix }}postgres +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/rabbitmq/deployment.yaml b/helm/app/templates/service/rabbitmq/deployment.yaml new file mode 100644 index 00000000..0fb92af5 --- /dev/null +++ b/helm/app/templates/service/rabbitmq/deployment.yaml @@ -0,0 +1,60 @@ +{{ if .Values.service.rabbitmq }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}rabbitmq + labels: + app.service: {{ .Values.resourcePrefix }}rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}rabbitmq + strategy: + type: Recreate + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}rabbitmq + spec: + containers: + - env: + {{- range $key, $value := .Values.docker.services.rabbitmq.environment }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.docker.services.rabbitmq.environment_secrets }} + envFrom: + - secretRef: + name: {{ .Values.resourcePrefix }}rabbitmq + {{- end }} + image: {{ .Values.docker.image.rabbitmq }} + imagePullPolicy: Always + name: rabbitmq + ports: + - containerPort: 5672 + - containerPort: 15672 + resources: + limits: + memory: {{ .Values.resources.memory.rabbitmq }} + requests: + memory: {{ .Values.resources.memory.rabbitmq }} + readinessProbe: + tcpSocket: + port: 5672 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}rabbitmq-persistent-storage + mountPath: /var/lib/rabbitmq + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}rabbitmq-persistent-storage +{{- if .Values.persistence.rabbitmq.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}rabbitmq-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/rabbitmq/pvc.yaml b/helm/app/templates/service/rabbitmq/pvc.yaml new file mode 100644 index 00000000..c3760cad --- /dev/null +++ b/helm/app/templates/service/rabbitmq/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.rabbitmq .Values.persistence.rabbitmq.enabled -}} + +{{- with .Values.persistence.rabbitmq -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}rabbitmq-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}rabbitmq +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/rabbitmq/secret.yaml b/helm/app/templates/service/rabbitmq/secret.yaml new file mode 100644 index 00000000..5104e43a --- /dev/null +++ b/helm/app/templates/service/rabbitmq/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "rabbitmq" "service" .Values.docker.services.rabbitmq "Values" .Values) }} diff --git a/helm/app/templates/service/rabbitmq/service.yaml b/helm/app/templates/service/rabbitmq/service.yaml new file mode 100644 index 00000000..095489d1 --- /dev/null +++ b/helm/app/templates/service/rabbitmq/service.yaml @@ -0,0 +1,20 @@ +{{ if .Values.service.rabbitmq }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}rabbitmq + name: {{ .Values.resourcePrefix }}rabbitmq +spec: + ports: + - name: "5672" + port: 5672 + targetPort: 5672 + - name: "15672" + port: 15672 + targetPort: 15672 + selector: + app.service: {{ .Values.resourcePrefix }}rabbitmq +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/redis-session/deployment.yaml b/helm/app/templates/service/redis-session/deployment.yaml new file mode 100644 index 00000000..3b49158c --- /dev/null +++ b/helm/app/templates/service/redis-session/deployment.yaml @@ -0,0 +1,62 @@ +{{ if .Values.service.redis_session }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}redis-session + labels: + app.service: {{ .Values.resourcePrefix }}redis-session +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}redis-session + template: + metadata: + labels: + app.service: {{ .Values.resourcePrefix }}redis-session + spec: + containers: + - args: + - redis-server + - --maxmemory + - "1073742000" + - --maxmemory-policy + - volatile-ttl + - --save + - "3600" + - "1" + - --save + - "300" + - "100" + - --save + - "60" + - "10000" + image: redis:4-alpine + imagePullPolicy: Always + name: redis-session + ports: + - containerPort: 6379 + resources: + limits: + memory: {{ .Values.resources.memory.redis_session }} + requests: + memory: {{ .Values.resources.memory.redis_session }} + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}redis-session-persistent-storage + mountPath: /data + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}redis-session-persistent-storage +{{- if .Values.persistence.redis_session.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}redis-session-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/redis-session/pvc.yaml b/helm/app/templates/service/redis-session/pvc.yaml new file mode 100644 index 00000000..248ceac3 --- /dev/null +++ b/helm/app/templates/service/redis-session/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.redis_session .Values.persistence.redis_session.enabled -}} + +{{- with .Values.persistence.redis_session -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}redis-session-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}redis-session +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/redis-session/service.yaml b/helm/app/templates/service/redis-session/service.yaml new file mode 100644 index 00000000..2c4256d1 --- /dev/null +++ b/helm/app/templates/service/redis-session/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.redis_session }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}redis-session + name: {{ .Values.resourcePrefix }}redis-session +spec: + ports: + - name: "6379" + port: 6379 + targetPort: 6379 + selector: + app.service: {{ .Values.resourcePrefix }}redis-session +status: + loadBalancer: {} +{{ end }} \ No newline at end of file diff --git a/helm/app/templates/service/redis/deployment.yaml b/helm/app/templates/service/redis/deployment.yaml new file mode 100644 index 00000000..fba48c57 --- /dev/null +++ b/helm/app/templates/service/redis/deployment.yaml @@ -0,0 +1,63 @@ +{{ if .Values.service.redis }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}redis + labels: + app.service: {{ .Values.resourcePrefix }}redis +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}redis + template: + metadata: + creationTimestamp: null + labels: + app.service: {{ .Values.resourcePrefix }}redis + spec: + containers: + - args: + - redis-server + - --maxmemory + - "1073742000" + - --maxmemory-policy + - allkeys-lru + - --save + - "3600" + - "1" + - --save + - "300" + - "100" + - --save + - "60" + - "10000" + image: redis:4-alpine + imagePullPolicy: Always + name: redis + ports: + - containerPort: 6379 + resources: + limits: + memory: {{ .Values.resources.memory.redis }} + requests: + memory: {{ .Values.resources.memory.redis }} + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}redis-persistent-storage + mountPath: /data + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}redis-persistent-storage +{{- if .Values.persistence.redis.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.resourcePrefix }}redis-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{ end }} diff --git a/helm/app/templates/service/redis/pvc.yaml b/helm/app/templates/service/redis/pvc.yaml new file mode 100644 index 00000000..fbf7b119 --- /dev/null +++ b/helm/app/templates/service/redis/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.service.redis .Values.persistence.redis.enabled -}} + +{{- with .Values.persistence.redis -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}redis-pv-claim + labels: + app.service: {{ $.Values.resourcePrefix }}redis +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- if .selector }} + selector: + {{- .selector | toYaml | nindent 4 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/redis/service.yaml b/helm/app/templates/service/redis/service.yaml new file mode 100644 index 00000000..722bc2b7 --- /dev/null +++ b/helm/app/templates/service/redis/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.redis }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}redis + name: {{ .Values.resourcePrefix }}redis +spec: + ports: + - name: "6379" + port: 6379 + targetPort: 6379 + selector: + app.service: {{ .Values.resourcePrefix }}redis +status: + loadBalancer: {} +{{ end }} \ No newline at end of file diff --git a/helm/app/templates/service/varnish/headless-service.yaml b/helm/app/templates/service/varnish/headless-service.yaml new file mode 100644 index 00000000..08fe597a --- /dev/null +++ b/helm/app/templates/service/varnish/headless-service.yaml @@ -0,0 +1,18 @@ +{{ if .Values.service.varnish }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}varnish + name: {{ .Values.resourcePrefix }}varnish-headless +spec: + clusterIP: None + ports: + - name: "80" + port: 80 + targetPort: 80 + selector: + app.service: {{ .Values.resourcePrefix }}varnish +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/varnish/service.yaml b/helm/app/templates/service/varnish/service.yaml new file mode 100644 index 00000000..df2216f3 --- /dev/null +++ b/helm/app/templates/service/varnish/service.yaml @@ -0,0 +1,17 @@ +{{ if .Values.service.varnish }} +apiVersion: v1 +kind: Service +metadata: + labels: + app.service: {{ .Values.resourcePrefix }}varnish + name: {{ .Values.resourcePrefix }}varnish +spec: + ports: + - name: "80" + port: 80 + targetPort: 80 + selector: + app.service: {{ .Values.resourcePrefix }}varnish +status: + loadBalancer: {} +{{ end }} diff --git a/helm/app/templates/service/varnish/statefulset.yaml b/helm/app/templates/service/varnish/statefulset.yaml new file mode 100644 index 00000000..fd71570f --- /dev/null +++ b/helm/app/templates/service/varnish/statefulset.yaml @@ -0,0 +1,50 @@ +{{ if .Values.service.varnish }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Values.resourcePrefix }}varnish + labels: + app.service: {{ .Values.resourcePrefix }}varnish +spec: + replicas: {{ .Values.replicas.varnish }} + podManagementPolicy: Parallel + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}varnish + serviceName: {{ .Values.resourcePrefix }}varnish-headless + template: + metadata: + creationTimestamp: null + labels: + app.service: {{ .Values.resourcePrefix }}varnish + spec: + containers: + - name: varnish + image: varnish:6 + imagePullPolicy: Always + ports: + - containerPort: 80 + resources: + limits: + memory: {{ .Values.resources.memory.varnish }} + requests: + memory: {{ .Values.resources.memory.varnish }} + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: {{ .Values.resourcePrefix }}varnish-configuration + mountPath: /etc/varnish/ + - name: {{ .Values.resourcePrefix }}varnish-cache + mountPath: /var/lib/varnish + restartPolicy: Always + volumes: + - name: {{ .Values.resourcePrefix }}varnish-configuration + configMap: + name: {{ .Values.resourcePrefix }}varnish-configuration + - name: {{ .Values.resourcePrefix }}varnish-cache + emptyDir: + medium: Memory +{{ end }} diff --git a/helm/app/values-preview.yaml.twig b/helm/app/values-preview.yaml.twig new file mode 100644 index 00000000..85a24145 --- /dev/null +++ b/helm/app/values-preview.yaml.twig @@ -0,0 +1,11 @@ +{% if @('pipeline.preview.services') %} +docker: + services: {{ to_nice_yaml(@('pipeline.preview.services'), 2, 4) | raw }} +{% endif %} +{% if @('pipeline.preview.persistence') %} +persistence: {{ to_nice_yaml(@('pipeline.preview.persistence'), 2, 2) | raw }} +{% endif %} + +{% if @('pipeline.preview.resources') %} +resources: {{ to_nice_yaml(@('pipeline.preview.resources'), 2, 2) | raw }} +{% endif %} diff --git a/helm/app/values-production.yaml.twig b/helm/app/values-production.yaml.twig new file mode 100644 index 00000000..d9a33032 --- /dev/null +++ b/helm/app/values-production.yaml.twig @@ -0,0 +1,23 @@ + +# assumption is that in a production style environment these would be +# managed services outside of the applications control + +service: + elasticsearch: false + memcached: false + mysql: false + postgres: false + redis: false + redis_session: false + +{% if @('pipeline.production.services') %} +docker: + services: {{ to_nice_yaml(@('pipeline.production.services'), 2, 4) | raw }} +{% endif %} +{% if @('pipeline.production.persistence') %} +persistence: {{ to_nice_yaml(@('pipeline.production.persistence'), 2, 2) | raw }} +{% endif %} + + {% if @('pipeline.production.resources') %} +resources: {{ to_nice_yaml(@('pipeline.production.resources'), 2, 2) | raw }} +{% endif %} diff --git a/helm/qa/Chart.yaml.twig b/helm/qa/Chart.yaml.twig new file mode 100644 index 00000000..2afdc865 --- /dev/null +++ b/helm/qa/Chart.yaml.twig @@ -0,0 +1,5 @@ +name: {{ @('workspace.name') }}-qa +description: Base helm chart for the {{ @('workspace.name') }}-qa environment +version: 0.0.1 +sources: +home: diff --git a/helm/qa/requirements.yaml.twig b/helm/qa/requirements.yaml.twig new file mode 100644 index 00000000..16b0aa8c --- /dev/null +++ b/helm/qa/requirements.yaml.twig @@ -0,0 +1,4 @@ +dependencies: +- name: {{ @('workspace.name') }} + version: 0.0.1 + repository: "file://../app" diff --git a/mutagen.yml.twig b/mutagen.yml.twig new file mode 100644 index 00000000..d09a48d6 --- /dev/null +++ b/mutagen.yml.twig @@ -0,0 +1,30 @@ +forward: + {{ @('workspace.name') }}: + source: "tcp:localhost:6060" + destination: "docker://{{ @('workspace.name') }}-sync:tcp:localhost:6060" + +sync: + {{ @('workspace.name') }}: + alpha: "." + beta: "docker://{{ @('workspace.name') }}-sync/app" + mode: "two-way-resolved" + # Configuration for host file system as configured above for "alpha" key + configurationAlpha: + permissions: + defaultFileMode: 0644 + defaultDirectoryMode: 0755 + # Configuration for docker file system as configured above for "beta" key + configurationBeta: + permissions: + defaultOwner: "id:1000" + defaultGroup: "id:1000" + defaultFileMode: 0644 + defaultDirectoryMode: 0755 + symlink: + mode: posix-raw + ignore: + paths: + - /.docker-sync + - /.idea + - /.git + - /var