diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7aa45c2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +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/_twig/docker-compose.yml/service/blackfire.yml.twig b/_twig/docker-compose.yml/service/blackfire.yml.twig new file mode 100644 index 0000000..9dc0eba --- /dev/null +++ b/_twig/docker-compose.yml/service/blackfire.yml.twig @@ -0,0 +1,11 @@ + blackfire: + image: {{ @('services.blackfire.image') }} + labels: + - traefik.enable=false + environment: {{ to_nice_yaml(deep_merge([ + @('services.blackfire.environment'), + @('services.blackfire.environment_dynamic'), + @('services.blackfire.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private 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 0000000..67c7b43 --- /dev/null +++ b/_twig/docker-compose.yml/service/chrome.yml.twig @@ -0,0 +1,11 @@ + chrome: +{% if host_architecture() == 'amd64' %} + 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"] +{% else %} + image: quay.io/inviqa_images/chromium:latest +{% endif %} + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/console.yml.twig b/_twig/docker-compose.yml/service/console.yml.twig new file mode 100644 index 0000000..0427d82 --- /dev/null +++ b/_twig/docker-compose.yml/service/console.yml.twig @@ -0,0 +1,32 @@ +{% set syncvolume = false %} +{% if @('host.os') == 'darwin' and bool(@('mutagen')) %} +{% set syncvolume = true %} +{% endif %} + + console: + build: + context: ./ + dockerfile: .my127ws/docker/image/console/Dockerfile +{% if @('app.build') == 'dynamic' %} + entrypoint: [/entrypoint.dynamic.sh] + command: [sleep, infinity] + volumes: + - {{ (syncvolume) ? @('workspace.name') ~ '-sync:/app:nocopy' : ('./:/app' ~ @('docker.compose.host_volume_options')) }} + - ./.my127ws/application:/home/build/application + - ./.my127ws/docker/image/console/root/lib/task:/lib/task + - ./.my127ws:/.my127ws +{% else %} + image: {{ @('services.console.image') }} +{% endif %} + labels: + - traefik.enable=false + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-base.environment'), + @('services.php-base.environment_dynamic'), + @('services.console.environment'), + @('services.console.environment_dynamic'), + @('services.php-base.environment_secrets'), + @('services.console.environment_secrets') + ]), 2, 6) | raw }} + 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 0000000..f1e204f --- /dev/null +++ b/_twig/docker-compose.yml/service/cron.yml.twig @@ -0,0 +1,23 @@ + 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: {{ @('services.cron.image') }} +{% endif %} + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-base.environment'), + @('services.php-base.environment_dynamic'), + @('services.cron.environment'), + @('services.cron.environment_dynamic'), + @('services.php-base.environment_secrets'), + @('services.cron.environment_secrets') + ]), 2, 6) | raw }} + 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 0000000..486384f --- /dev/null +++ b/_twig/docker-compose.yml/service/elasticsearch.yml.twig @@ -0,0 +1,14 @@ + elasticsearch: + image: {{ @('services.elasticsearch.image') }} + labels: + - traefik.enable=false + environment: + ES_JAVA_OPTS: -Xms512m -Xmx512m + discovery.type: single-node + networks: + - private +{% if @('app.build') != 'static' and @('docker.port_forward.enabled') %} + ports: + - "127.0.0.1:0:9200" + - "127.0.0.1:0:9300" +{% endif %} diff --git a/_twig/docker-compose.yml/service/lighthouse.yml.twig b/_twig/docker-compose.yml/service/lighthouse.yml.twig new file mode 100644 index 0000000..2cf6f0d --- /dev/null +++ b/_twig/docker-compose.yml/service/lighthouse.yml.twig @@ -0,0 +1,12 @@ + lighthouse: + build: .my127ws/docker/image/lighthouse + entrypoint: "/usr/bin/dumb-init --" + command: "/bin/true" + environment: + TARGET_URL: "{{ @('lighthouse.target.url') | raw }}" +{% if @('app.build') == 'dynamic' %} + volumes: + - .my127ws/docker/image/lighthouse/root/app:/app +{% endif %} + networks: + - private 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 0000000..f5aaa50 --- /dev/null +++ b/_twig/docker-compose.yml/service/memcached.yml.twig @@ -0,0 +1,6 @@ + memcached: + image: {{ @('services.memcached.image') }} + labels: + - traefik.enable=false + networks: + - private diff --git a/_twig/docker-compose.yml/service/mongodb.yml.twig b/_twig/docker-compose.yml/service/mongodb.yml.twig new file mode 100644 index 0000000..83efc43 --- /dev/null +++ b/_twig/docker-compose.yml/service/mongodb.yml.twig @@ -0,0 +1,15 @@ + mongodb: + image: {{ @('services.mongodb.image') }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.mongodb.environment'), + @('services.mongodb.environment_dynamic'), + @('services.mongodb.environment_secrets') + ]), 2, 6) | raw }} + labels: + - traefik.enable=false + networks: + - private + expose: + - 27017 + volumes: + - /data/db 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 0000000..8efdc7b --- /dev/null +++ b/_twig/docker-compose.yml/service/mysql.yml.twig @@ -0,0 +1,20 @@ +{% set command = @('services.mysql.options') + | filter(v => v is not empty) + | map((value, var) => '--' ~ var ~ '=' ~ value) + | reduce((carry, v) => carry|merge([v]), []) %} + mysql: + image: {{ @('services.mysql.image') }} + labels: + - traefik.enable=false + command: {{ to_nice_yaml(command, 2, 6) }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.mysql.environment'), + @('services.mysql.environment_dynamic'), + @('services.mysql.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private +{% if @('app.build') != 'static' and @('docker.port_forward.enabled') %} + ports: + - "127.0.0.1:{{ @('database.port_forward') ? @('database.port_forward') : '0' }}:3306" +{% endif %} diff --git a/_twig/docker-compose.yml/service/nginx.yml.twig b/_twig/docker-compose.yml/service/nginx.yml.twig new file mode 100644 index 0000000..9814d29 --- /dev/null +++ b/_twig/docker-compose.yml/service/nginx.yml.twig @@ -0,0 +1,42 @@ +{% set syncvolume = false %} +{% if @('host.os') == 'darwin' and bool(@('mutagen')) %} +{% set syncvolume = true %} +{% endif %} +{% set hostnames = [@('hostname')] %} +{% set hostnames = hostnames|merge(@('hostname_aliases')|map(alias => "#{alias}." ~ @('domain'))) %} + + nginx: + build: .my127ws/docker/image/nginx +{% if @('app.build') == 'dynamic' %} + volumes: + - {{ (syncvolume) ? @('workspace.name') ~ '-sync:/app:nocopy' : ('./:/app' ~ @('docker.compose.host_volume_options')) }} +{% else %} + image: {{ @('services.nginx.image') }} +{% endif %} + labels: +{% if @('services.varnish.enabled') %} + - traefik.enable=false +{% else %} + - traefik.backend={{ @('workspace.name') }} + - traefik.frontend.rule=Host:{{ hostnames|join(',') }} + - traefik.docker.network=my127ws + - traefik.port=80 +{% endif %} + environment: {{ to_nice_yaml(deep_merge([ + @('services.nginx.environment'), + @('services.nginx.environment_dynamic'), + @('services.nginx.environment_secrets') + ]), 2, 6) | raw }} + links: + - php-fpm:php-fpm + networks: +{% if @('services.varnish.enabled') %} + private: {} +{% else %} + private: + aliases: +{% for alias in hostnames %} + - {{ alias }} +{% endfor %} +{% endif %} + shared: {} 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 0000000..e89c70f --- /dev/null +++ b/_twig/docker-compose.yml/service/php-fpm-exporter.yml.twig @@ -0,0 +1,13 @@ + php-fpm-exporter: + image: {{ @('services.php-fpm-exporter.image') }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-fpm-exporter.environment'), + @('services.php-fpm-exporter.environment_dynamic'), + @('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/php-fpm.yml.twig b/_twig/docker-compose.yml/service/php-fpm.yml.twig new file mode 100644 index 0000000..010034e --- /dev/null +++ b/_twig/docker-compose.yml/service/php-fpm.yml.twig @@ -0,0 +1,33 @@ +{% set syncvolume = false %} +{% if @('host.os') == 'darwin' and bool(@('mutagen')) %} +{% set syncvolume = true %} +{% endif %} + + php-fpm: + build: .my127ws/docker/image/php-fpm +{% if @('app.build') == 'dynamic' %} +{% if @('services.cron.enabled') %} + image: {{ @('workspace.name') ~ '-php-fpm:dev' }} +{% endif %} + volumes: + - {{ (syncvolume) ? @('workspace.name') ~ '-sync:/app:nocopy' : ('./:/app' ~ @('docker.compose.host_volume_options')) }} + - ./.my127ws:/.my127ws +{% else %} + image: {{ @('services.php-fpm.image') }} +{% endif %} + labels: + - traefik.enable=false + networks: + - private + environment: {{ to_nice_yaml(deep_merge([ + @('services.php-base.environment'), + @('services.php-base.environment_dynamic'), + @('services.php-fpm.environment'), + @('services.php-fpm.environment_dynamic'), + @('services.php-base.environment_secrets'), + @('services.php-fpm.environment_secrets') + ]), 2, 6) | raw }} + expose: +{% for pool in @('php-fpm.pools') %} + - {{ pool.port }} +{% endfor %} 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 0000000..a01fb70 --- /dev/null +++ b/_twig/docker-compose.yml/service/postgres.yml.twig @@ -0,0 +1,15 @@ + postgres: + image: {{ @('services.postgres.image') }} + labels: + - traefik.enable=false + environment: {{ to_nice_yaml(deep_merge([ + @('services.postgres.environment'), + @('services.postgres.environment_dynamic'), + @('services.postgres.environment_secrets') + ]), 2, 6) | raw }} + networks: + - private +{% if @('app.build') != 'static' and @('docker.port_forward.enabled') %} + ports: + - "127.0.0.1:{{ @('database.port_forward') ? @('database.port_forward') : '0' }}:5432" +{% endif %} 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 0000000..19ff8c2 --- /dev/null +++ b/_twig/docker-compose.yml/service/rabbitmq.yml.twig @@ -0,0 +1,17 @@ + rabbitmq: + image: {{ @('services.rabbitmq.image') }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.rabbitmq.environment'), + @('services.rabbitmq.environment_dynamic'), + @('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 0000000..03b6b74 --- /dev/null +++ b/_twig/docker-compose.yml/service/redis-session.yml.twig @@ -0,0 +1,8 @@ + redis-session: + image: {{ @('services.redis-session.image') }} + # 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 0000000..8f4c981 --- /dev/null +++ b/_twig/docker-compose.yml/service/redis.yml.twig @@ -0,0 +1,8 @@ + redis: + image: {{ @('services.redis.image') }} + # 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/relay.yml.twig b/_twig/docker-compose.yml/service/relay.yml.twig new file mode 100644 index 0000000..1fa566b --- /dev/null +++ b/_twig/docker-compose.yml/service/relay.yml.twig @@ -0,0 +1,10 @@ + relay: + build: .my127ws/docker/image/relay + labels: + - traefik.enable=false + networks: + private: + aliases: + - jaeger-relay + - mailhog-relay + shared: {} diff --git a/_twig/docker-compose.yml/service/solr.yml.twig b/_twig/docker-compose.yml/service/solr.yml.twig new file mode 100644 index 0000000..e43e50d --- /dev/null +++ b/_twig/docker-compose.yml/service/solr.yml.twig @@ -0,0 +1,34 @@ + solr: + image: {{ @('services.solr.image') }} + environment: {{ to_nice_yaml(deep_merge([ + @('services.solr.environment'), + @('services.solr.environment_dynamic'), + @('services.solr.environment_secrets') + ]), 2, 6) | raw }} + labels: + - traefik.backend=solr-{{ @('workspace.name') }} + - traefik.frontend.rule=Host:solr-{{ @('hostname') }} + - traefik.docker.network=my127ws + - traefik.port=8983 + command: + - solr-precreate + - {{ @('services.solr.environment.SOLR_CORE_NAME') }} +{% if @('services.solr.config_path') %} + - /opt/solr/server/solr/configsets/{{ @('services.solr.environment.SOLR_CORE_NAME') }} +{% elseif @('services.solr.major_version') == 4 %} + - /opt/solr/example/example-schemaless/solr/{{ @('services.solr.environment.SOLR_CORE_NAME') }} +{% endif %} + volumes: +{% if @('services.solr.config_path') %} + - {{ @('services.solr.config_path')}}:/opt/solr/server/solr/configsets/{{ @('services.solr.environment.SOLR_CORE_NAME') }}/conf +{% endif %} +{% if @('services.solr.major_version') == 4 %} + - solr_data:/opt/solr/example/solr +{% elseif @('services.solr.major_version') < 8 %} + - solr_data:/opt/solr/server/solr/mycores +{% else %} + - solr_data:/var/solr +{% endif %} + networks: + - private + - shared diff --git a/_twig/docker-compose.yml/service/tideways.yml.twig b/_twig/docker-compose.yml/service/tideways.yml.twig new file mode 100644 index 0000000..c6fbe66 --- /dev/null +++ b/_twig/docker-compose.yml/service/tideways.yml.twig @@ -0,0 +1,11 @@ + tideways: + image: {{ @('services.tideways.image') }} + labels: + - traefik.enable=false + environment: {{ to_nice_yaml(deep_merge([ + @('services.tideways.environment'), + @('services.tideways.environment_dynamic'), + @('services.tideways.environment_secrets') + ]), 2, 6) | raw }} + 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 0000000..32338e3 --- /dev/null +++ b/_twig/docker-compose.yml/service/varnish.yml.twig @@ -0,0 +1,50 @@ +{% if @('services.varnish.enabled') %} +{% set hostnames = [@('hostname')] %} +{% set hostnames = hostnames|merge(@('hostname_aliases')|map(alias => "#{alias}." ~ @('domain'))) %} + varnish: + image: {{ @('services.varnish.image') }} + 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_dynamic'), + @('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/_twig/docker-compose.yml/service/webapp.yml.twig b/_twig/docker-compose.yml/service/webapp.yml.twig new file mode 100644 index 0000000..c88f944 --- /dev/null +++ b/_twig/docker-compose.yml/service/webapp.yml.twig @@ -0,0 +1,3 @@ +{% set blocks = '_twig/docker-compose.yml/' %} +{% include blocks ~ 'service/nginx.yml.twig' %} +{% include blocks ~ 'service/php-fpm.yml.twig' %} diff --git a/application/overlay/.dockerignore.twig b/application/overlay/.dockerignore.twig new file mode 100644 index 0000000..96c90ad --- /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 0000000..72c1c2c --- /dev/null +++ b/application/overlay/Jenkinsfile.twig @@ -0,0 +1,133 @@ +pipeline { + agent { label "my127ws" } + environment { + COMPOSE_DOCKER_CLI_BUILD = {{ @('jenkins.docker.buildkit.enabled') ? '1' : '0' }} + DOCKER_BUILDKIT = {{ @('jenkins.docker.buildkit.enabled') ? '1' : '0' }} + MY127WS_KEY = credentials('{{ @('jenkins.credentials.my127ws_key') }}') + MY127WS_ENV = "pipeline" + } + triggers { cron(env.BRANCH_NAME == '{{ @('git.default_branch') }}' ? 'H H(0-6) * * *' : '') } + stages { +{% if @('jenkins.tests.isolated') %} + stage('Setup') { + steps { + sh 'ws harness download' + sh 'ws harness prepare' + sh 'ws enable console' + milestone(10) + } + } +{% else %} + stage('Build') { + steps { + sh 'ws install' + milestone(10) + } + } +{% endif %} + 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' } } +{% if not @('jenkins.tests.isolated') %} + stage('acceptance') { steps { sh 'ws exec composer test-acceptance' } } +{% if @('services.lighthouse.enabled') %} + stage('lighthouse') { steps { sh 'ws lighthouse' } } +{% endif %} +{% endif %} + stage('helm kubeval qa') { steps { sh 'ws helm kubeval --cleanup qa' } } + } + } +{% if @('jenkins.tests.isolated') %} + stage('Build') { + steps { +{% if @('jenkins.tests.use_global_services') %} + sh 'ws install' +{% else %} + sh 'ws enable' +{% endif %} + milestone(30) + } + } + stage('End-to-end Test') { + parallel { + stage('acceptance') { steps { sh 'ws exec composer test-acceptance' } } +{% if @('services.lighthouse.enabled') %} + stage('lighthouse') { steps { sh 'ws lighthouse' } } +{% endif %} + } + } +{% endif %} +{% if bool(@('pipeline.publish.enabled')) %} + stage('Publish') { +{% if @('pipeline.publish.environment') %} + environment { +{% for key, value in @('pipeline.publish.environment') %} + {{ key }} = {{ value }} +{% endfor %} + } +{% endif %} + when { + not { triggeredBy 'TimerTrigger' } + anyOf { +{% for branch in @('pipeline.publish.branches') %} + branch '{{ branch }}' +{% endfor %} +{% if bool(@('pipeline.qa.enabled')) %} + branch '{{ @('pipeline.qa.branch') }}' +{% endif %} +{% if bool(@('pipeline.preview.enabled')) %} +{% for branch in @('pipeline.preview.target_branches') %} + changeRequest target: '{{ branch }}' +{% endfor %} +{% endif %} + } + } + steps { + milestone(50) + sh 'ws app publish' +{% if bool(@('pipeline.publish.chart.enabled')) %} + lock(resource: '{{ @('pipeline.publish.chart.git.repository') }}', inversePrecedence: true) { + sh 'ws app publish chart "${GIT_BRANCH}" "{{ @('workspace.name') }} build artifact ${GIT_COMMIT}"' + } +{% endif %} + } + } +{% endif %} +{% if bool(@('pipeline.qa.enabled')) %} + stage('Deploy (QA)') { +{% if @('pipeline.qa.environment') %} + environment { +{% for key, value in @('pipeline.qa.environment') %} + {{ key }} = {{ value }} +{% endfor %} + } +{% endif %} + 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 0000000..4ebb7d2 --- /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 0000000..c007eac --- /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 0000000..0731d87 --- /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 0000000..128922f --- /dev/null +++ b/application/skeleton/README.md.twig @@ -0,0 +1,208 @@ +{% 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 Desktop for Mac](https://docs.docker.com/docker-for-mac/install/). + - On Linux, add the official Docker repository described under the "Server" section on [Install Docker Engine](https://docs.docker.com/engine/install/) 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.26.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` + +{% include blocks ~ 'websites.md.twig' %} + +**Additional services**: +- Mailhog: [https://mail.my127.site](https://mail.my127.site) +{% if @('services.rabbitmq.enabled') -%} +- RabbitMQ: [https://{{ @('rabbitmq.external_host') }}](https://{{ @('rabbitmq.external_host') }}) +{% endif -%} +{% if @('services.jenkins.enabled') -%} +- Jenkins: [https://{{ @('jenkins.external_host') }}](https://{{ @('jenkins.external_host') }}) +{% endif %} +{% if @('services.solr.enabled') -%} +- Solr: [https://solr-{{ @('hostname') }}/solr/](https://solr-{{ @('hostname') }}/solr/) +{% endif -%} + +### 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 @('services.mysql.enabled') -%} +### 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 (or 9003 for Xdebug v3) 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. + +#### Xdebug 3 + +To upgrade to Xdebug 3, add the following to your `workspace.override.yml`: + +```yml +attribute('php.ext-xdebug.version'): 3 +``` + +then run `ws feature xdebug version-sync` which will upgrade the Xdebug version to the latest stable version. + +Xdebug 3 listens on port 9003, so you may need to update your IDE settings to match. PHPStorm 2020.3 and later has support for +listening to both port 9000 and 9003 simultaneously. + +If you encounter issues on Xdebug 3, remove the above entry and re-run `ws feature xdebug version-sync` to downgrade. + +#### Xdebug 3 Modes + +Xdebug 3 now has the ability to run in different modes, such as step debugging or profiling. The default for Workspace is `debug` to +enable step debugging, but this can be changed (or multiple modes enabled) by adding the following to your `workspace.override.yml`: + +```yml +attribute('php.ext-xdebug.config.v3.mode'): debug,profile +``` + +then enable Xdebug using either `ws feature xdebug on` or `ws feature xdebug cli on`. + +### Performance on macOS + +Page load times with Docker Desktop 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) is a tool to synchronise files between host machine and +docker containers. +It enables production-like performance at the cost of having to synchronise files with an intermediate +"data" container. + +{% if bool(@('mutagen')) %} +Mutagen is enabled on the development environment. +{% else %} +If it takes over 2 seconds to load a page, you should consider enabling mutagen 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 +attribute('mutagen'): true +``` + +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 +``` + +### 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 mutagen and operations such as `mutagen project pause` during `ws disable` or `mutagen daemon start` during `ws install` are hanging: + * Check that your `mutagen version` is at least version 0.11.8, and upgrade mutagen if not. + +# License + +Copyright 2020, Inviqa diff --git a/application/skeleton/_twig/README.md/websites.md.twig b/application/skeleton/_twig/README.md/websites.md.twig new file mode 100644 index 0000000..51900d6 --- /dev/null +++ b/application/skeleton/_twig/README.md/websites.md.twig @@ -0,0 +1,9 @@ +Once installed, the site should be available at [https://{{ @('hostname') }}](https://{{ @('hostname') }}). +{% set additional_hostnames = @('hostname_aliases')|map(alias => "#{alias}." ~ @('domain')) %} +{% if additional_hostnames %} + +**Additional sites**: +{% for host in additional_hostnames -%} +- [https://{{ host }}](https://{{ host }}) +{% endfor -%} +{% endif -%} diff --git a/docker-compose.yml.twig b/docker-compose.yml.twig new file mode 100644 index 0000000..ed1e9d2 --- /dev/null +++ b/docker-compose.yml.twig @@ -0,0 +1,40 @@ +{% set blocks = '_twig/docker-compose.yml/' %} +{% set syncvolume = false %} +{% if @('host.os') == 'darwin' and bool(@('mutagen')) %} +{% set syncvolume = true %} +{% endif %} +version: '{{ @('docker.compose.file_version') }}' +services: +{% for serviceName, service in @('services') %} +{% if service['enabled'] %} +{% include blocks ~ 'service/' ~ serviceName ~ '.yml.twig' %} +{% endif %} +{% endfor %} +{% for service in @('app.services') %} +{% if @('services')[service] is not defined or @('services')[service].enabled is not defined %} +{% include blocks ~ 'service/' ~ service ~ '.yml.twig' %} +{% endif %} +{% endfor %} +networks: + private: + external: false + shared: + external: true + name: my127ws +{% if syncvolume or @('services.solr.enabled') %} +volumes: +{% if syncvolume %} +{% if bool(@('mutagen')) %} +{% for volumeName in get_mutagen_volume_names() %} + {{ volumeName }}: + external: true +{% endfor %} +{% else %} + {{ @('workspace.name') }}-sync: + external: true +{% endif %} +{% endif %} +{% if @('services.solr.enabled') %} + solr_data: +{% endif %} +{% endif %} diff --git a/docker/image/console/.dockerignore b/docker/image/console/.dockerignore new file mode 100644 index 0000000..cf49d54 --- /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 0000000..cd6182a --- /dev/null +++ b/docker/image/console/Dockerfile.twig @@ -0,0 +1,72 @@ +FROM {{ @('docker.image.console') }} +# fix upstream signal +STOPSIGNAL SIGTERM + +COPY .my127ws/docker/image/console/root / +RUN chown -R build:build /home/build /app \ + # installing tools in the image is deprecated + && ([ -e /sbin/tini ] || curl --fail --silent --show-error --location --output /sbin/tini "https://github.com/krallin/tini/releases/download/v0.19.0/tini-$(dpkg --print-architecture)") \ + && chmod +x /sbin/tini \ + && mkdir -p /tmp/php-file-cache \ + && chown -R build:build /tmp/php-file-cache +{%- if @('php.composer.major_version') != '1' %} \ + && composer self-update --{{ @('php.composer.major_version') }} \ + && rm -f /root/.composer/*.phar +{%- endif %} +{%- set install_extensions=@('php.install_extensions')|merge(@('php.cli.install_extensions'))|filter(v => v is not empty) %} +{%- if install_extensions %} \ + && cd /root/installer \ + && ./enable.sh \ + {{ install_extensions|join(" \\\n ") }} +{% endif %} + +{% if @('frontend.build.distribution_packages') or @('backend.build.distribution_packages') %} +RUN apt-get update -qq \ + && DEBIAN_FRONTEND=noninteractive apt-get -qq -y --no-install-recommends install \ + {{ @('frontend.build.distribution_packages') | default([]) | merge(@('backend.build.distribution_packages') | default([])) | join(' ') }} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +{% endif %} + +ENV APP_MODE={{ @('app.mode') }} \ + APP_BUILD={{ @('app.build') }} \ + ASSETS_DIR={{ @('assets.local') }} +{%- set extra_environment_variables=@('services.console.build.environment')|filter(v => v is not empty) %} +{%- if extra_environment_variables %} +{%- for name, value in extra_environment_variables %} \ + {{ name }}="{{ value }}" +{%- endfor %} +{% endif %} + +USER build + +{% if @('node.version') is not null %} +RUN . /home/build/.nvm/nvm.sh \ + && nvm install {{ @('node.version') }} \ + && nvm use {{ @('node.version') }} \ + && nvm alias default {{ @('node.version') }} \ + && npm install -g yarn +{% endif %} + +{% if @('php.composer.major_version') != '1' %} +RUN composer --no-plugins global remove hirak/prestissimo +{% endif %} + +{% if @('app.build') == 'static' %} +COPY --chown=build:build .my127ws/application /home/build/application +COPY --chown=build:build ./ /app +RUN app build +{% else %} +VOLUME /app +VOLUME /home/build/application +{% endif %} + +USER root + +{% if version_compare(@('php.ext-xdebug.version'), '3', '>=') and version_compare(@('php.version'), '7', '>=') and version_compare(@('php.version'), '8', '<') %} +RUN pecl -q upgrade xdebug +{% endif %} + +RUN chmod +x /entrypoint.sh +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 0000000..e69de29 diff --git a/docker/image/console/root/bin/app b/docker/image/console/root/bin/app new file mode 100755 index 0000000..73a8661 --- /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 0000000..43bc1a0 --- /dev/null +++ b/docker/image/console/root/entrypoint.dynamic.sh @@ -0,0 +1,54 @@ +#!/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 fuse.grpcfuse") > /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.twig b/docker/image/console/root/entrypoint.sh.twig new file mode 100755 index 0000000..8c8b35a --- /dev/null +++ b/docker/image/console/root/entrypoint.sh.twig @@ -0,0 +1,51 @@ +#!/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 +} + +run_steps() +{ + # run any command required to be executed at docker startup + {% for step in @('php.entrypoint.steps') -%} + {{ step|raw }} + {% else -%} + : + {% endfor %} + {% for step in @('console.entrypoint.steps') -%} + {{ step|raw }} + {% else -%} + : + {% endfor %} + + # Clean up Tideways module loading if it's meant to be turned off + if [ -n "$TIDEWAYS_ENABLED" ] && [ -f /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini ]; then + if [ "$TIDEWAYS_ENABLED" = "true" ]; then + sed -i'' 's#tideways.connection=.*$#tideways.connection=tcp://'"$TIDEWAYS_HOST"':9135#' /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + else + rm /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + fi + fi +} + +bootstrap() +{ + setup_app_networking + run_steps +} + +bootstrap + +if [ "${1:-}" == "sleep" ]; then + exec /sbin/tini -- bash -c "$(printf "%q " "$@")" +else + exec /sbin/tini -- "$@" +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 0000000..ef83bda --- /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 0000000..4b7b401 --- /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 0000000..9b36437 --- /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 0000000..1e54342 --- /dev/null +++ b/docker/image/console/root/lib/task/assets/apply.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +function task_assets_apply() +{ + local ASSETS_DIR="${ASSETS_DIR:-tools/assets/development}" + local IMPORT_COMMAND=() + local PRE_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 + PRE_COMMAND=("PGPASSWORD=$DB_PASS") + IMPORT_COMMAND=(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 + "${PRE_COMMAND[@]}" passthru "pv --force '$DATABASE_FILE' | zcat - | $(printf ' %q' "${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 0000000..817a67a --- /dev/null +++ b/docker/image/console/root/lib/task/assets/dump.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +function task_assets_dump() +{ + local ASSETS_DIR="${ASSETS_DIR:-tools/assets/development}" + local DUMP_COMMAND=() + local PRE_COMMAND=() + + if [ ! -d "/app/${ASSETS_DIR}" ]; then + run mkdir -p "/app/${ASSETS_DIR}" + fi + + if [ "${DB_PLATFORM}" == "mysql" ]; then + DUMP_COMMAND=(mysqldump -h "${DB_HOST}" -u "${DB_USER}" "-p${DB_PASS}" "${DB_NAME}") + elif [ "${DB_PLATFORM}" == "postgres" ]; then + PRE_COMMAND=("PGPASSWORD=$DB_PASS") + DUMP_COMMAND=(pg_dump -h "${DB_HOST}" -U "${DB_USER}" "${DB_NAME}") + elif [ -n "${DB_PLATFORM}" ]; then + (>&2 echo "invalid database type") + exit 1 + fi + + "${PRE_COMMAND[@]}" run "$(printf ' %q' "${DUMP_COMMAND[@]}") | gzip > '/app/${ASSETS_DIR}/${DB_NAME}.sql.gz'" +} 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 0000000..c49bf57 --- /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 0000000..de39c51 --- /dev/null +++ b/docker/image/console/root/lib/task/build/backend.sh.twig @@ -0,0 +1,18 @@ +#!/bin/bash + +function task_build_backend() +{( + set -e -o pipefail + + cd {{ @('backend.path') }} + + # shellcheck disable=SC2160 + 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 0000000..ae8453c --- /dev/null +++ b/docker/image/console/root/lib/task/build/frontend.sh.twig @@ -0,0 +1,18 @@ +#!/bin/bash + +function task_build_frontend() +{( + set -e -o pipefail + + cd {{ @('frontend.path') }} + + # shellcheck disable=SC2160 + 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/autoload.sh b/docker/image/console/root/lib/task/composer/autoload.sh new file mode 100644 index 0000000..dcd3d37 --- /dev/null +++ b/docker/image/console/root/lib/task/composer/autoload.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +function task_composer_autoload() +{ + passthru composer dump-autoload --optimize --classmap-authoritative + run rm -rf /tmp/php-file-cache/*/app/vendor/composer /tmp/php-file-cache/*/app/vendor/autoload.php* || true +} diff --git a/docker/image/console/root/lib/task/composer/development_dependencies.sh b/docker/image/console/root/lib/task/composer/development_dependencies.sh new file mode 100644 index 0000000..32c9855 --- /dev/null +++ b/docker/image/console/root/lib/task/composer/development_dependencies.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +function task_composer_development_dependencies() +{ + passthru php -d opcache.file_cache_only=0 /usr/bin/composer install --no-interaction --optimize-autoloader + task composer:autoload +} 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 0000000..55c3f5f --- /dev/null +++ b/docker/image/console/root/lib/task/composer/install.sh.twig @@ -0,0 +1,15 @@ +#!/bin/bash + +function task_composer_install() +{ + {% if @('app.mode') == 'development' and @('app.build') != 'static' %} + passthru composer install --no-interaction + {% else %} + passthru php -d opcache.file_cache_only=0 /usr/bin/composer install --no-interaction{% if @('app.mode') != 'development' %} --no-dev{% endif %} --optimize-autoloader + task composer:autoload + {% 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 0000000..c39b848 --- /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 0000000..b491e83 --- /dev/null +++ b/docker/image/console/root/lib/task/database/import.sh.twig @@ -0,0 +1,11 @@ +#!/bin/bash + +function task_database_import() +{ + : + {% for step in @('database.import.steps') -%} + {{ step|raw }} + {% else %} + : + {% 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 0000000..9e1d2a7 --- /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 --fail --silent --show-error --location --insecure --output /dev/null "$@"; 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 0000000..58e04a5 --- /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 0000000..0771761 --- /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 0000000..863a1ad --- /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 0000000..33fec2d --- /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 0000000..bf7159f --- /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 0000000..efc19d7 --- /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 @('services.rabbitmq.enabled') %} + 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 --fail --silent --show-error --user "$RABBITMQ_USER:$RABBITMQ_PASSWORD" --request PUT "$rabbitmq_api_url/vhosts/{{ vhost }}" +{% endfor %} +{% else %} + : +{% 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 0000000..700ab06 --- /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 0000000..c0e0041 --- /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/lib/task/welcome.sh.twig b/docker/image/console/root/lib/task/welcome.sh.twig new file mode 100644 index 0000000..b11f630 --- /dev/null +++ b/docker/image/console/root/lib/task/welcome.sh.twig @@ -0,0 +1,27 @@ +#!/bin/bash +{% set hostnames = [@('hostname')] %} +{% set hostnames = hostnames|merge(@('hostname_aliases')|map(alias => "#{alias}." ~ @('domain'))) %} + +function task_welcome() +{ + echo "" + echo "Welcome!" + echo "--------" + {% for host in hostnames -%} + echo "https://{{ host }}" + {% endfor -%} + echo "Admin: /admin" + echo " Username: admin" + echo " Password: admin123" + {% if @('services.rabbitmq.enabled') -%} + echo "" + echo "RabbitMQ: https://{{ @('rabbitmq.external_host') }}" + echo " Username: {{ @('rabbitmq.user') }}" + echo " Password: {{ @('rabbitmq.password') }}" + {% endif -%} + {% if @('services.solr.enabled') -%} + echo "" + echo "Solr: https://solr-{{ @('hostname') }}/solr/" + {% endif -%} + echo "" +} diff --git a/docker/image/console/root/usr/local/bin/send_mail b/docker/image/console/root/usr/local/bin/send_mail new file mode 100755 index 0000000..5221dda --- /dev/null +++ b/docker/image/console/root/usr/local/bin/send_mail @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +/usr/bin/msmtp --host="$SMTP_HOST" --port="$SMTP_PORT" --remove-bcc-headers=off --read-recipients --read-envelope-from --auto-from=on --maildomain="$APP_HOST" -- "$@" diff --git a/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-blackfire.ini.twig b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-blackfire.ini.twig new file mode 100644 index 0000000..9a36a80 --- /dev/null +++ b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-blackfire.ini.twig @@ -0,0 +1,8 @@ +{% set blackfire = @('php.ext-blackfire') %} + +{% if bool(blackfire.cli.enable) %} + extension=blackfire.so + {% for key, value in blackfire.config -%} + blackfire.{{ key }}={{ value }} + {% endfor %} +{% endif %} diff --git a/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig new file mode 100644 index 0000000..729104c --- /dev/null +++ b/docker/image/console/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig @@ -0,0 +1,8 @@ +{% set tideways = @('php.ext-tideways') %} + +{% if bool(tideways.cli.enable) %} + extension=tideways.so + {% for key, value in tideways.config -%} + tideways.{{ key }}={{ value }} + {% endfor %} +{% endif %} 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 0000000..2e66910 --- /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 bool(xdebug.cli.enable) %} + zend_extension=xdebug.so + {% for key, value in xdebug.config['v' ~ xdebug.version] -%} + 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 0000000..b76369c --- /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 0000000..4741e75 --- /dev/null +++ b/docker/image/cron/Dockerfile.twig @@ -0,0 +1,38 @@ +{% if @('app.build') == 'static' %} +FROM {{ @('docker.repository') ~ ':' ~ @('app.version') }}-php-fpm +{% else %} +FROM {{ @('workspace.name') ~ '-php-fpm:dev' }} +{% endif %} +# fix upstream signal +STOPSIGNAL SIGTERM + +# 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/* \ + && mkdir -p /tmp/php-file-cache \ + && chown -R www-data:www-data /tmp/php-file-cache + +WORKDIR /app +COPY .my127ws/docker/image/cron/root / +RUN chmod +x /cron-run-with-env.sh /entrypoint.sh /entrypoint.dynamic.sh +{%- if @('app.build') == 'static' %} \ + && bash /fix_app_permissions.sh +{% endif %} + +{% if @('app.build') == 'dynamic' %} +VOLUME /app +{% endif %} +ENV APP_MODE {{ @('app.mode') }} + +{% if @('app.build') == 'static' %} +ENTRYPOINT ["/entrypoint.sh"] +{% else %} +ENTRYPOINT ["/entrypoint.dynamic.sh"] +{% endif %} +CMD ["sleep", "infinity"] 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 0000000..caa047d --- /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 0000000..71a84dc --- /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.twig b/docker/image/cron/root/entrypoint.sh.twig new file mode 100644 index 0000000..f88792b --- /dev/null +++ b/docker/image/cron/root/entrypoint.sh.twig @@ -0,0 +1,56 @@ +#!/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 +} + +run_steps() +{ + # run any command required to be executed at docker startup + {% for step in @('php.entrypoint.steps') -%} + {{ step|raw }} + {% else -%} + : + {% endfor %} + {% for step in @('cron.entrypoint.steps') -%} + {{ step|raw }} + {% else -%} + : + {% endfor %} + + # Clean up Tideways module loading if it's meant to be turned off + if [ -n "$TIDEWAYS_ENABLED" ] && [ -f /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini ]; then + if [ "$TIDEWAYS_ENABLED" = "true" ]; then + sed -i'' 's#tideways.connection=.*$#tideways.connection=tcp://'"$TIDEWAYS_HOST"':9135#' /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + else + rm /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + fi + fi +} + +dump_environment_variables() +{ + # this is used to load env vars in crontab commands + env > /app/env.sh +} + +bootstrap() +{ + setup_app_networking + run_steps + dump_environment_variables +} + +bootstrap + +# run +crontab /crontab +exec /sbin/tini -- cron -f -L 15 diff --git a/docker/image/cron/root/usr/local/etc/php/php.ini.twig b/docker/image/cron/root/usr/local/etc/php/php.ini.twig new file mode 100644 index 0000000..600d773 --- /dev/null +++ b/docker/image/cron/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.cron.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/lighthouse/Dockerfile.twig b/docker/image/lighthouse/Dockerfile.twig new file mode 100644 index 0000000..8caf9e5 --- /dev/null +++ b/docker/image/lighthouse/Dockerfile.twig @@ -0,0 +1,10 @@ +# hadolint ignore=DL3007 +FROM {{ @('services.lighthouse.image') }} + +COPY root / + +{% if @('app.build') != 'static' %} +VOLUME /app +{% endif %} + +ENTRYPOINT ["/usr/bin/dumb-init", "--", "/bin/bash", "-i", "/app/run.sh"] diff --git a/docker/image/lighthouse/root/app/run.sh.twig b/docker/image/lighthouse/root/app/run.sh.twig new file mode 100755 index 0000000..3eb44ff --- /dev/null +++ b/docker/image/lighthouse/root/app/run.sh.twig @@ -0,0 +1,88 @@ +#!/bin/bash + +set -o nounset +set -o pipefail +set -o errexit + +TARGET_URL="${TARGET_URL:-${1:-}}" +OUTPUT_RESULTS="${OUTPUT_RESULTS:-no}" + +if [ -z "$TARGET_URL" ] || ! [[ "$TARGET_URL" =~ https?:// ]]; then + echo "Please provide a target URL to run lighthouse against" >&2 + exit 1 +fi + +function output_results() +{ + if [ "$OUTPUT_RESULTS" != "yes" ]; then + return + fi + cat /home/headless/lighthouse-results.json + echo +} + +echo "Running Lighthouse against URL: ${TARGET_URL}" >&2 + +lighthouse --no-enable-error-reporting \ + --chrome-flags="--headless --no-sandbox=true --ignore-certificate-errors --disable-dev-shm-usage" \ + --output json \ + --output-path=/home/headless/lighthouse-results.json \ + "${TARGET_URL}" >&2 + +{% if @('lighthouse.success-thresholds.performance.enabled') %} + test_score=$(jq .categories.performance.score /home/headless/lighthouse-results.json) + pass_value={{ @('lighthouse.success-thresholds.performance.score') }} + echo "Testing Performance score" >&2 + if [ 1 -eq "$(echo "${test_score} < ${pass_value}" | bc)" ] + then + echo "Failed: Performance score of ${test_score} is less then required value of ${pass_value}" >&2 + exit 1 + fi +{% endif %} + +{% if @('lighthouse.success-thresholds.pwa.enabled') %} + test_score=$(jq .categories.pwa.score /home/headless/lighthouse-results.json) + pass_value={{ @('lighthouse.success-thresholds.pwa.score') }} + echo "Testing PWA score" >&2 + if [ 1 -eq "$(echo "${test_score} < ${pass_value}" | bc)" ] + then + echo "Failed: PWA score of ${test_score} is less then required value of ${pass_value}" >&2 + exit 1 + fi +{% endif %} + +{% if @('lighthouse.success-thresholds.seo.enabled') %} + test_score=$(jq .categories.seo.score /home/headless/lighthouse-results.json) + pass_value={{ @('lighthouse.success-thresholds.seo.score') }} + echo "Testing SEO score" >&2 + if [ 1 -eq "$(echo "${test_score} < ${pass_value}" | bc)" ] + then + echo "Failed: SEO score of ${test_score} is less then required value of ${pass_value}" >&2 + exit 1 + fi +{% endif %} + +{% if @('lighthouse.success-thresholds.best-practices.enabled') %} + test_score=$(jq '.categories["best-practices"].score' /home/headless/lighthouse-results.json) + pass_value={{ @('lighthouse.success-thresholds.best-practices.score') }} + echo "Testing Best Practices score" >&2 + if [ 1 -eq "$(echo "${test_score} < ${pass_value}" | bc)" ] + then + echo "Failed: Best Practices score of ${test_score} is less then required value of ${pass_value}" >&2 + exit 1 + fi +{% endif %} + +{% if @('lighthouse.success-thresholds.accessibility.enabled') %} + test_score=$(jq .categories.accessibility.score /home/headless/lighthouse-results.json) + pass_value={{ @('lighthouse.success-thresholds.accessibility.score') }} + echo "Testing Accessibility score" >&2 + if [ 1 -eq "$(echo "${test_score} < ${pass_value}" | bc)" ] + then + echo "Failed: Accessibility score of ${test_score} is less then required value of ${pass_value}" >&2 + exit 1 + fi +{% endif %} + +output_results +echo "Success" >&2 diff --git a/docker/image/nginx/Dockerfile.twig b/docker/image/nginx/Dockerfile.twig new file mode 100644 index 0000000..855a43f --- /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.21-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/docker-entrypoint.d/config_render.sh.twig b/docker/image/nginx/root/docker-entrypoint.d/config_render.sh.twig new file mode 100644 index 0000000..84ac15c --- /dev/null +++ b/docker/image/nginx/root/docker-entrypoint.d/config_render.sh.twig @@ -0,0 +1,15 @@ +#!/bin/bash + +function render_configuration() +{ + # shellcheck disable=SC2016 + local vars='$FPM_HOST' + + for file in /etc/nginx/conf.d/*.template; do + envsubst "$vars" < "$file" > "${file%.template}"; + done +} + +render_configuration + +exec "$@" 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 0000000..c9efff8 --- /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/snippets/certificate.conf b/docker/image/nginx/root/etc/nginx/snippets/certificate.conf new file mode 100644 index 0000000..80e7be3 --- /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 0000000..9b37b60 --- /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 0000000..0d94581 --- /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 0000000..6bb8bcb --- /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 0000000..cf49d54 --- /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 0000000..3968040 --- /dev/null +++ b/docker/image/php-fpm/Dockerfile.twig @@ -0,0 +1,41 @@ +{% if @('app.build') == 'static' %} +FROM {{ @('docker.repository') ~ ':' ~ @('app.version') }}-console as console +LABEL build="{{ @('namespace') ~ ':' ~ @('app.version') }}" +RUN if [ -d /app/tools/assets/ ]; then rm -rf /app/tools/assets/; fi; \ + if [ -d {{ @('frontend.path') }}/node_modules/ ]; then rm -rf {{ @('frontend.path') }}/node_modules/; fi; + +{% endif %} +FROM {{ @('docker.image.php-fpm') }} +WORKDIR /app +COPY root / + +# installing tools in the image is deprecated +RUN ([ -e /sbin/tini ] || curl --fail --silent --show-error --location --output /sbin/tini "https://github.com/krallin/tini/releases/download/v0.19.0/tini-$(dpkg --print-architecture)") \ + && chmod +x /sbin/tini +{%- set install_extensions=@('php.install_extensions')|merge(@('php.fpm.install_extensions'))|filter(v => v is not empty) %} +{%- if install_extensions %} \ + && cd /root/installer \ + && ./enable.sh \ + {{ install_extensions|join(" \\\n ") }} +{% endif %} + +{%- if version_compare(@('php.ext-xdebug.version'), '3', '>=') and version_compare(@('php.version'), '7', '>=') and version_compare(@('php.version'), '8', '<') %} +RUN pecl -q upgrade xdebug +{% endif %} + +{% 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 0000000..e69de29 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 0000000..b9bf82f --- /dev/null +++ b/docker/image/php-fpm/root/entrypoint.dynamic.sh @@ -0,0 +1,69 @@ +#!/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 fuse.grpcfuse") > /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 0000000..47b0f3c --- /dev/null +++ b/docker/image/php-fpm/root/entrypoint.sh.twig @@ -0,0 +1,30 @@ +#!/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.entrypoint.steps') -%} + {{ step|raw }} + {% endfor %} + {% for step in @('php-fpm.entrypoint.steps') -%} + {{ step|raw }} + {% endfor %} + + # Clean up Tideways module loading if it's meant to be turned off + if [ -n "$TIDEWAYS_ENABLED" ] && [ -f /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini ]; then + if [ "$TIDEWAYS_ENABLED" = "true" ]; then + sed -i'' 's#tideways.connection=.*$#tideways.connection=tcp://'"$TIDEWAYS_HOST"':9135#' /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + else + rm /usr/local/etc/php/conf.d/docker-php-ext-tideways.ini + fi + fi +} + +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 0000000..a96f50e --- /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 0000000..6cfe17a --- /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 0000000..705b8ab --- /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/bin/send_mail b/docker/image/php-fpm/root/usr/local/bin/send_mail new file mode 100755 index 0000000..5221dda --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/bin/send_mail @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +/usr/bin/msmtp --host="$SMTP_HOST" --port="$SMTP_PORT" --remove-bcc-headers=off --read-recipients --read-envelope-from --auto-from=on --maildomain="$APP_HOST" -- "$@" 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 0000000..14a055d --- /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-blackfire.ini.twig b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-blackfire.ini.twig new file mode 100644 index 0000000..d7c1abd --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-blackfire.ini.twig @@ -0,0 +1,8 @@ +{% set blackfire = @('php.ext-blackfire') %} + +{% if bool(blackfire.enable) %} + extension=blackfire.so + {% for key, value in blackfire.config -%} + blackfire.{{ key }}={{ value }} + {% endfor %} +{% endif %} diff --git a/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig new file mode 100644 index 0000000..0542b34 --- /dev/null +++ b/docker/image/php-fpm/root/usr/local/etc/php/conf.d/docker-php-ext-tideways.ini.twig @@ -0,0 +1,8 @@ +{% set tideways = @('php.ext-tideways') %} + +{% if bool(tideways.enable) %} + extension=tideways.so + {% for key, value in tideways.config -%} + tideways.{{ key }}={{ value }} + {% endfor %} +{% 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 0000000..96c1e6e --- /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 bool(xdebug.enable) %} + zend_extension=xdebug.so + {% for key, value in xdebug.config['v' ~ xdebug.version] -%} + 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 0000000..90f4ad6 --- /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 0000000..2978798 --- /dev/null +++ b/docker/image/relay/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.21-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 0000000..41bc783 --- /dev/null +++ b/docker/image/relay/root/etc/nginx/nginx.conf @@ -0,0 +1,62 @@ +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; + + # Avoid crashes on boot if mailhog isn't running + resolver 127.0.0.11 valid=30s; + set $upstream_mailhog mailhog; + + proxy_pass $upstream_mailhog:1025; + } + + server { + listen 8025; + + # Avoid crashes on boot if mailhog isn't running + resolver 127.0.0.11 valid=30s; + set $upstream_mailhog mailhog; + + proxy_pass $upstream_mailhog:8025; + } + + server { + listen 6831 udp; + + # Avoid crashes on boot if jaeger isn't running + resolver 127.0.0.11 valid=30s; + set $upstream_jaeger jaeger; + + proxy_pass $upstream_jaeger:6831; + } + + server { + listen 6832 udp; + + # Avoid crashes on boot if jaeger isn't running + resolver 127.0.0.11 valid=30s; + set $upstream_jaeger jaeger; + + proxy_pass $upstream_jaeger:6832; + } + + server { + listen 9411; + + # Avoid crashes on boot if jaeger isn't running + resolver 127.0.0.11 valid=30s; + set $upstream_jaeger jaeger; + + proxy_pass $upstream_jaeger:9411; + } +} diff --git a/docker/image/tls-offload/Dockerfile b/docker/image/tls-offload/Dockerfile new file mode 100644 index 0000000..2978798 --- /dev/null +++ b/docker/image/tls-offload/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.21-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 0000000..c9efff8 --- /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 0000000..d7ee466 --- /dev/null +++ b/docker/image/tls-offload/root/etc/nginx/conf.d/default.conf.twig @@ -0,0 +1,47 @@ + +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_buffers 4 256k; + proxy_buffer_size 128k; + proxy_busy_buffers_size 256k; + 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 0000000..80e7be3 --- /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 0000000..9b37b60 --- /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 0000000..0d94581 --- /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 0000000..6bb8bcb --- /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 0000000..971517b --- /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 0000000..e69de29 diff --git a/harness/attributes/common.yml b/harness/attributes/common.yml new file mode 100644 index 0000000..3d58aa0 --- /dev/null +++ b/harness/attributes/common.yml @@ -0,0 +1,472 @@ +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: false + mutagen: true + + jenkins: + credentials: + my127ws_key: = @('workspace.name') ~ '-my127ws-key' + docker: + buildkit: + enabled: true + tests: + isolated: true + use_global_services: false + + pipeline: + base: + persistence: = @('persistence') + resourcePrefix: ~ + hostname: = @('pipeline.preview.hostname') + publish: + enabled: false + services: = publishable_services(@('services')) + # branches that should publish (other than change requests and qa) + # * is deprecated and will be limited to one branch by default in a future release + branches: + - '*' + # For defining environment variables in Jenkins, e.g. loading up docker username/password from a Jenkins + # credential + environment: {} + # when enabled the application helm chart will be published + # to the given git repository. + chart: + enabled: false + git: + # 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: false + target_branches: + - = @('git.default_branch') + environment: {} + cluster: + name: null + namespace: = @('workspace.name') ~ '-' ~ slugify(branch()) + hostname: = @('pipeline.preview.namespace') ~ '.example.com' + rabbitmq: + external_host: = 'rabbitmq-' ~ @('pipeline.preview.hostname') + smtp: + host: mailhog.mailhog.svc.cluster.local + port: = @('smtp.port') + persistence: + redis-session: + enabled: false + qa: + enabled: false + 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') + smtp: + host: mailhog.mailhog.svc.cluster.local + port: = @('smtp.port') + + docker: + registry: + url: = get_docker_registry(@('docker.repository')) + username: = @('docker.username') # for backwards compatibility + password: = @('docker.password') # for backwards compatibility + repository: = @("workspace.name") + buildkit: + enabled: true + compose: + file_version: '3.7' + host_volume_options: "= ':' ~ (bool(@('delegated-volumes')) ? 'delegated' : 'cached')" + # 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-' ~ (@('php.version') >= 7.4 ? 'buster' : 'stretch') ~ '-console'" + php-fpm: "= 'my127/php:' ~ @('php.version') ~ '-fpm-' ~ (@('php.version') >= 7.4 ? 'buster' : 'stretch')" + port_forward: + enabled: "= @('app.build') == 'dynamic'" + 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: + default_branch: develop + + lighthouse: + target: + url: = 'https://' ~ @('hostname') + success-thresholds: + pwa: + enabled: false + score: 0 + seo: + enabled: false + score: 0 + best-practices: + enabled: false + score: 0 + accessibility: + enabled: false + score: 0 + performance: + enabled: false + score: 0 + + 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') + php_fpm: + conf: [] + + 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: "8.0" + cli: + ini: + max_execution_time: 0 + memory_limit: -1 + opcache.file_cache: "= (@('app.build') == 'static' ? '/tmp/php-file-cache' : '')" + opcache.file_cache_only: "= (@('app.build') == 'static' ? '1' : '0')" + opcache.file_cache_consistency_checks: "= (@('app.build') == 'static' ? '1' : '0')" + install_extensions: [] + composer: + major_version: 2 + cron: + ini: = @('php.cli.ini') + entrypoint: + steps: [] + 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 + 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 + install_extensions: [] + ini: + enable_dl: Off + error_reporting: "E_ALL" + opcache.enable_cli: On + # Use PHP 8+ Just In Time (JIT) optimisations + # 1255 means a tracing JIT optimiser that analyses code for hot paths on the fly for whole scripts. + # One step up from the default of "tracing" which is 1254 which uses callgraph + opcache.jit: "= (version_compare(@('php.version'), '8.0', '>=') ? '1255' : null)" + opcache.jit_buffer_size: "= (version_compare(@('php.version'), '8.0', '>=') ? '100M' : null)" + realpath_cache_ttl: 600 + sendmail_path: /usr/local/bin/send_mail + short_open_tag: Off + install_extensions: + - "= @('services.blackfire.enabled') ? 'blackfire' : ''" + - "= @('services.tideways.enabled') ? 'tideways' : ''" + ext-blackfire: + enable: false + cli: + enable: false + config: + agent_socket: "tcp://blackfire:8707" + ext-tideways: + enable: "= @('services.tideways.enabled')" + cli: + enable: "= @('services.tideways.enabled')" + config: + connection: "tcp://tideways:9135" + collect: tracing + sample_rate: 25 + service: web + ext-xdebug: + version: "= (version_compare(@('php.version'), '8.0', '>=') ? '3' : '2')" + enable: false + cli: + enable: false + config: + v2: + remote_enable: 1 + remote_autostart: 1 + remote_port: 9000 + remote_host: host.docker.internal + idekey: workspace + v3: + mode: debug + start_with_request: "yes" + client_port: 9003 + client_host: host.docker.internal + + assets: + remote: ="s3://"~@("aws.bucket")~"/development" + local: tools/assets/development + + cron: + entrypoint: + steps: [] + + console: + entrypoint: + steps: [] + + database: + # possible platforms are mysql, postgres or ~ for none + platform: mysql + platform_version: 8.0 + host: mysql + port: 3306 + user: app + pass: app + name: app + root_pass: DV6RdNY3QcFsBk7V + port_forward: ~ + # deprecated: database.var will move to services.mysql.options in a future release + var: + default_authentication_plugin: "= (@('database.platform') == 'mysql' && @('database.platform_version') >= 8.0 && @('database.platform_version') < 10.0 ? 'mysql_native_password' : '')" + ignore-db-dir: "= (@('database.platform') == 'mysql' && @('database.platform_version') < 8.0 ? 'lost+found' : '')" + max_allowed_packet: 4M + import: + steps: [] + + elasticsearch: + host: elasticsearch + image: elasticsearch + port: 9200 + tag: '7.16.2' + + 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: + # location of the sealed-secret service to download the active certificate from + controller_name: sealed-secrets + controller_namespace: sealed-secrets + # Use local file (or fetch from http url) as the certificate + # If null, it will be fetched from the controller in the current kubectl context + certificate_file: ~ + # A namespace the secret should be only decryptable by + # Ignored if scope is cluster-wide. + # If null, it will be fetched from the current kubectl context + namespace: ~ + # The scope under which a secret can be decrypted + # Either cluster-wide, namespace-wide, or strict + scope: "= @('helm.sealed_secrets.namespace') ? 'namespace-wide' : 'cluster-wide'" + + php-fpm: + pools: + www: + port: 9000 + entrypoint: + steps: [] + + mysql: + # prefer native platform mysql image. _/mysql doesn't have arm64 images + image: "= host_architecture() == 'amd64' ? 'mysql' : 'mysql/mysql-server'" + tag: '8.0' + + 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: + response: + s-maxage: ~ + target_service: nginx + + 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 + mongodb: + enabled: true + accessMode: ReadWriteOnce + size: 1Gi + 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 0000000..e3e966d --- /dev/null +++ b/harness/attributes/docker-base.yml @@ -0,0 +1,284 @@ +attributes: + services: + php-base: + environment: + HOST_OS_FAMILY: = @('host.os') + APP_NAME: = @('workspace.name') + APP_HOST: = @('hostname') + DB_PLATFORM: = @('database.platform') + DB_PLATFORM_VERSION: = @('database.platform_version') + DB_HOST: = @('database.host') + DB_PORT: = @('database.port') + 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') + SOLR_HOST: = @('services.solr.host') + SOLR_PORT: = @('services.solr.port') + SMTP_HOST: = @('smtp.host') + SMTP_PORT: = @('smtp.port') + PHP_IDE_CONFIG: "serverName=workspace" + TIDEWAYS_HOST: tideways + VARNISH_HOSTNAME_TEMPLATE: "varnish-%d.varnish-headless" + environment_secrets: + DB_PASS: = @('database.pass') + RABBITMQ_PASSWORD: = @('rabbitmq.password') + TIDEWAYS_APIKEY: "" + nginx: + image: = @('docker.repository') ~ ':' ~ @('app.version') ~ '-nginx' + publish: true + environment: + FPM_HOST: php-fpm + resources: + memory: "100Mi" + console: + enabled: true + image: = @('docker.repository') ~ ':' ~ @('app.version') ~ '-console' + publish: true + build: + environment: {} + environment: + DB_ADMIN_USER: root + HAS_ELASTICSEARCH: "= @('services.elasticsearch.enabled') ? 'true' : 'false'" + HAS_VARNISH: "= @('services.varnish.enabled') ? 'true' : 'false'" + environment_secrets: + DB_ADMIN_PASS: = @('database.root_pass') + resources: + memory: "2048Mi" + init_memory: "1024Mi" + migrate_memory: "1024Mi" + cron: + enabled: "= 'cron' in @('app.services')" + image: = @('docker.repository') ~ ':' ~ @('app.version') ~ '-cron' + publish: "= @('services.cron.enabled')" + resources: + memory: "1024Mi" + php-fpm: + enabled: true + image: = @('docker.repository') ~ ':' ~ @('app.version') ~ '-php-fpm' + publish: true + environment: + AUTOSTART_PHP_FPM: "true" + resources: + memory: "1024Mi" + php-fpm-exporter: + image: hipages/php-fpm_exporter + environment: + PHP_FPM_SCRAPE_URI: = php_fpm_exporter_scrape_url('php-fpm', @('php-fpm.pools')) + resources: + memory: "100Mi" + blackfire: + enabled: "= 'blackfire' in @('app.services')" + image: blackfire/blackfire:latest + environment: + BLACKFIRE_AGENT_TIMEOUT: 10 + BLACKFIRE_LOG_LEVEL: 1 + environment_secrets: + BLACKFIRE_CLIENT_ID: "" + BLACKFIRE_CLIENT_TOKEN: "" + BLACKFIRE_SERVER_ID: "" + BLACKFIRE_SERVER_TOKEN: "" + elasticsearch: + enabled: "= 'elasticsearch' in @('app.services')" + image: = @('elasticsearch.image') ~ ':' ~ @('elasticsearch.tag') + resources: + memory: "1024Mi" + lighthouse: + enabled: true + image: = 'quay.io/inviqa_images/lighthouse:' ~ @('services.lighthouse.tag') + tag: "= host_architecture() == 'amd64' ? 'chrome' : 'chromium'" + memcached: + enabled: "= 'memcached' in @('app.services')" + image: memcached:1-alpine + mongodb: + image: mongo:4.4 + environment: + MONGO_INITDB_ROOT_USERNAME: admin + environment_secrets: + MONGO_INITDB_ROOT_PASSWORD: password + resources: + memory: "512Mi" + mysql: + enabled: "= 'mysql' in @('app.services')" + image: = @('mysql.image') ~ ':' ~ @('mysql.tag') + options: = @('database.var') + environment: + MYSQL_DATABASE: = @('database.name') + MYSQL_USER: = @('database.user') + environment_secrets: + MYSQL_PASSWORD: = @('database.pass') + MYSQL_ROOT_PASSWORD: = @('database.root_pass') + resources: + memory: "512Mi" + postgres: + enabled: "= 'postgres' in @('app.services')" + image: postgres:9.6 + environment: + POSTGRES_DB: = @('database.name') + POSTGRES_USER: = @('database.user') + PGDATA: /var/lib/postgresql/data/pgdata + environment_secrets: + POSTGRES_PASSWORD: = @('database.pass') + resources: + memory: "512Mi" + rabbitmq: + enabled: "= 'rabbitmq' in @('app.services')" + image: = @('rabbitmq.image') ~ ':' ~ @('rabbitmq.tag') + 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') + resources: + memory: "1024Mi" + redis: + enabled: "= 'redis' in @('app.services')" + image: redis:5-alpine + resources: + memory: "256Mi" + redis-session: + enabled: "= 'redis-session' in @('app.services')" + image: redis:5-alpine + resources: + memory: "1024Mi" + relay: + enabled: true + publish: false + solr: + enabled: "= 'solr' in @('app.services')" + config_path: "" + environment: + SOLR_CORE_NAME: collection1 + environment_secrets: {} + host: solr + image: "= @('services.solr.major_version') != 4 ? 'solr:' ~ @('services.solr.major_version') ~ '-slim' : 'quay.io/inviqa_images/solr4:latest'" + major_version: 8 + port: 8983 + tideways: + enabled: "= 'tideways' in @('app.services')" + image: quay.io/inviqa_images/tideways:latest + environment: + TIDEWAYS_HOSTNAME: = @('hostname') + TIDEWAYS_ENV: production + resources: + memory: "256Mi" + varnish: + enabled: "= 'varnish' in @('app.services')" + image: varnish:6 + environment: + VARNISH_SIZE: "1024M" + resources: + memory: "1124Mi" + # webapp is by default two services combined, nginx and php-fpm + webapp: + enabled: true + pipeline: + base: + prometheus: + podMonitoring: false + services: + php-base: + environment: + APP_HOST: = @('pipeline.base.hostname') + DB_HOST: = '{{ .Values.resourcePrefix }}' ~ @('database.host') + ELASTICSEARCH_HOST: '{{ if .Values.services.elasticsearch.enabled }}{{ .Values.resourcePrefix }}elasticsearch{{ end }}' + ES_HOST: '{{ if .Values.services.elasticsearch.enabled }}{{ .Values.resourcePrefix }}elasticsearch:9200{{ end }}' + RABBITMQ_HOST: '{{ if .Values.services.rabbitmq.enabled }}{{ .Values.resourcePrefix }}rabbitmq{{ end }}' + RABBITMQ_EXTERNAL_HOST: = @('pipeline.preview.rabbitmq.external_host') + REDIS_HOST: '{{ if .Values.services.redis.enabled }}{{ .Values.resourcePrefix }}redis{{ end }}' + REDIS_SESSION_HOST: '{{ if (index .Values.services "redis-session" "enabled") }}{{ .Values.resourcePrefix }}redis-session{{ end }}' + PHP_IDE_CONFIG: = '' + TIDEWAYS_HOST: "{{ .Values.resourcePrefix }}tideways" + TIDEWAYS_ENABLED: "{{ .Values.services.tideways.enabled }}" + VARNISH_HOSTNAME_TEMPLATE: "{{ .Values.resourcePrefix }}varnish-%d.{{ .Values.resourcePrefix }}varnish-headless" + mysql: + options: = @('services.mysql.options') + console: + environment: + TIDEWAYS_ENABLED: "= @('php.ext-tideways.cli.enable') ? '{{ .Values.services.tideways.enabled }}' : 'false'" + nginx: + environment: + FPM_HOST: localhost + 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 + relay: + enabled: false + tideways: + environment: + TIDEWAYS_HOSTNAME: = @('pipeline.base.hostname') + ingress: + annotations: {} + target_service: "= @('services.varnish.enabled') ? 'varnish' : 'webapp'" + # standard or istio + type: standard + istio: + gateways: + - "istio-system/{{ .Release.Namespace }}-gateway" + additionalGateways: [] + production: + # assumption is that in a production style environment these would be + # managed services outside of the applications control + services: + elasticsearch: + enabled: false + memcached: + enabled: false + mysql: + enabled: false + postgres: + enabled: false + redis: + enabled: false + redis-session: + enabled: false + qa: + services: + php-base: + environment: + APP_HOST: = @('pipeline.qa.hostname') + RABBITMQ_EXTERNAL_HOST: = @('pipeline.qa.rabbitmq.external_host') + SMTP_HOST: = @('pipeline.qa.smtp.host') + SMTP_PORT: = @('pipeline.qa.smtp.port') + tideways: + environment: + TIDEWAYS_HOSTNAME: = @('pipeline.qa.hostname') + preview: + services: + console: + enabled: false + resources: + memory: "1024Mi" + nginx: + resources: + memory: "64Mi" + php-base: + environment: + SMTP_HOST: = @('pipeline.preview.smtp.host') + SMTP_PORT: = @('pipeline.preview.smtp.port') + php-fpm-exporter: + resources: + memory: "32Mi" + redis: + resources: + memory: "64Mi" + redis-session: + resources: + memory: "64Mi" diff --git a/harness/attributes/environment/local.yml b/harness/attributes/environment/local.yml new file mode 100644 index 0000000..41505a5 --- /dev/null +++ b/harness/attributes/environment/local.yml @@ -0,0 +1,6 @@ + +attributes: + app: + build: dynamic + mode: development + version: develop \ No newline at end of file diff --git a/harness/attributes/environment/pipeline.yml b/harness/attributes/environment/pipeline.yml new file mode 100644 index 0000000..7770200 --- /dev/null +++ b/harness/attributes/environment/pipeline.yml @@ -0,0 +1,16 @@ + +attributes: + namespace: =@('workspace.name') ~ '-' ~ exec("git rev-parse --short HEAD") + hostname: =@('workspace.name') ~ '.' ~ @('domain') + app: + build: static + version: =exec("git log -n 1 --pretty=format:'%H'") + mode: production + php: + fpm: + ini: + display_errors: Off + zend.assertions: -1 + ini: + opcache.validate_timestamps: Off + error_reporting: "E_ALL & ~E_DEPRECATED & ~E_STRICT" diff --git a/harness/config/cleanup.yml b/harness/config/cleanup.yml new file mode 100644 index 0000000..2b3742c --- /dev/null +++ b/harness/config/cleanup.yml @@ -0,0 +1,27 @@ +function('built_images', [services]): | + #!php + $builtImages = []; + + foreach ($services as $service) { + if ($service['image'] && count($service['upstream']) > 0) { + $builtImages[] = $service['image']; + } + } + $allImages = explode("\n", shell_exec('docker image ls -a --format \'{{ print .Repository ":" .Tag }}\'')); + + # workspace commands don't allow non-string types + = join(' ', array_intersect($builtImages, $allImages)); + +command('cleanup built-images'): + env: + BUILD_LABEL: = @('namespace') ~ ':' ~ @('app.version') + IMAGES: = built_images(docker_service_images()) + exec: | + #!bash + IMAGES=($IMAGES) + if [ "${#IMAGES[@]}" -gt 0 ]; then + run docker image rm --force -- "${IMAGES[@]}" + fi + run docker image prune --force --filter=label=build="$BUILD_LABEL" + [ -z "$(docker builder 2>&1)" ] || run docker builder prune --force --filter=label=build="$BUILD_LABEL" + diff --git a/harness/config/commands.yml b/harness/config/commands.yml new file mode 100644 index 0000000..78a384b --- /dev/null +++ b/harness/config/commands.yml @@ -0,0 +1,317 @@ + +command('enable'): + env: + USE_MUTAGEN: = boolToString(@('host.os') == 'darwin' and bool(@('mutagen'))) + APP_BUILD: = @('app.build') + APP_MODE: = @('app.mode') + NAMESPACE: = @('namespace') + HAS_ASSETS: = boolToString(@('aws.bucket') !== null and @('aws.bucket') !== '') + COMPOSE_PROJECT_NAME: = @('namespace') + COMPOSE_DOCKER_CLI_BUILD: = @('docker.buildkit.enabled') ? '1':'0' + DOCKER_BUILDKIT: = @('docker.buildkit.enabled') ? '1':'0' + exec: | + #!bash(workspace:/) + set -- all + source .my127ws/harness/scripts/enable.sh + +command('enable console'): + env: + USE_MUTAGEN: = boolToString(@('host.os') == 'darwin' and bool(@('mutagen'))) + APP_BUILD: = @('app.build') + APP_MODE: = @('app.mode') + NAMESPACE: = @('namespace') + HAS_ASSETS: = boolToString(@('aws.bucket') !== null and @('aws.bucket')) + COMPOSE_PROJECT_NAME: = @('namespace') + COMPOSE_DOCKER_CLI_BUILD: = @('docker.buildkit.enabled') ? '1':'0' + DOCKER_BUILDKIT: = @('docker.buildkit.enabled') ? '1':'0' + exec: | + #!bash(workspace:/) + set -- console + source .my127ws/harness/scripts/enable.sh + +command('disable'): + env: + USE_MUTAGEN: = boolToString(@('host.os') == 'darwin' and bool(@('mutagen'))) + NAMESPACE: = @('namespace') + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/disable.sh + +command('destroy [--all]'): + env: + USE_MUTAGEN: = boolToString(@('host.os') == 'darwin' and bool(@('mutagen'))) + NAMESPACE: = @('namespace') + COMPOSE_PROJECT_NAME: = @('namespace') + DESTROY_ALL: = boolToString(input.option('all')) + exec: | + #!bash(workspace:/)|@ + source .my127ws/harness/scripts/destroy.sh + +command('rebuild'): + env: + USE_MUTAGEN: = boolToString(@('host.os') == 'darwin' and bool(@('mutagen'))) + NAMESPACE: = @('namespace') + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/)|@ + source .my127ws/harness/scripts/rebuild.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_ACCESS_KEY_ID: = @('aws.access_key_id') + AWS_ID: = @('aws.access_key_id') + AWS_KEY: = @('aws.secret_access_key') + AWS_SECRET_ACCESS_KEY: = @('aws.secret_access_key') + exec: | + #!bash(workspace:/)|@ + passthru ws.aws s3 sync '@('assets.remote')' '@('assets.local')' + +command('assets upload'): + env: + AWS_ACCESS_KEY_ID: = @('aws.access_key_id') + AWS_ID: = @('aws.access_key_id') + AWS_KEY: = @('aws.secret_access_key') + AWS_SECRET_ACCESS_KEY: = @('aws.secret_access_key') + exec: | + #!bash(workspace:/)|@ + passthru ws.aws s3 sync '@('assets.local')' '@('assets.remote')' + +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 + 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 + 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/' + 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 blackfire (on|off)'): + env: + ATTR_KEY: 'php.ext-blackfire.enable' + ATTR_VAL: = boolToString(input.command(3) == 'on') + 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 blackfire cli (on|off)'): + env: + ATTR_KEY: 'php.ext-blackfire.cli.enable' + ATTR_VAL: = boolToString(input.command(4) == 'on') + 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 + +command('feature tideways (on|off)'): + env: + ATTR_KEY: 'php.ext-tideways.enable' + ATTR_VAL: = boolToString(input.command(3) == 'on') + 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 tideways cli (on|off)'): + env: + ATTR_KEY: 'php.ext-tideways.cli.enable' + ATTR_VAL: = boolToString(input.command(4) == 'on') + 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('feature tideways cli configure '): + env: + TIDEWAYS_SERVERKEY: = input.argument('server_key') + exec: | + #!bash(workspace:/)|= + echo "Importing Provided Tideways CLI Key (from https://app.tideways.io/user/cli-import-settings)" + docker-compose exec -T -u build console tideways import "$TIDEWAYS_SERVERKEY" + echo "Imported Tideways CLI key" + +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 + +command('generate token '): + env: + LENGTH: = input.argument('length') + exec: | + #!php + $ascii_codes = range(48, 57) + range(97, 122) + range(65, 90); + $codes_length = (count($ascii_codes)-1); + shuffle($ascii_codes); + $string = ''; + for($i = 1; $i <= $env['LENGTH']; $i++) { + $previous_char = $char ?? ''; + $char = chr($ascii_codes[random_int(0, $codes_length)]); + while($char == $previous_char){ + $char = chr($ascii_codes[random_int(0, $codes_length)]); + } + $string .= $char; + } + echo $string; + +command('lighthouse [--with-results]'): + env: + COMPOSE_PROJECT_NAME: = @('namespace') + OUTPUT_RESULTS: = boolToString(input.option('with-results')) + exec: | + #!bash(workspace:/)|@ + passthru docker-compose run --rm lighthouse bash -i /app/run.sh diff --git a/harness/config/events.yml b/harness/config/events.yml new file mode 100644 index 0000000..baff40f --- /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 + ws external-images pull + passthru "docker-compose config --services | grep -v cron | grep -v jenkins-runner | grep -v job-queue-consumer | xargs docker-compose build" + passthru "docker-compose config --services | grep -v cron | grep -v jenkins-runner | grep -v job-queue-consumer | 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 0000000..23469a9 --- /dev/null +++ b/harness/config/external-images.yml @@ -0,0 +1,101 @@ +function('external_images', [services]): | + #!php + $upstreamImages = []; + $excludeImages = ['scratch']; + + foreach ($services as $service) { + if (count($service['upstream']) > 0) { + $upstreamImages = array_merge($upstreamImages, $service['upstream']); + if ($service['image']) { + $excludeImages[] = $service['image']; + } + } else if ($service['image']) { + $upstreamImages[] = [ + 'image' => $service['image'], + 'platform' => $service['platform'], + ]; + } + } + $externalImages = array_filter( + $upstreamImages, + function ($image) use ($excludeImages) { + return !in_array($image['image'], $excludeImages); + } + ); + + # workspace commands don't allow non-string types + = json_encode($externalImages); + +command('external-images config [--skip-exists] []'): + env: + IMAGES: = external_images(docker_service_images(input.argument('service'))) + SKIP_EXISTS: "= input.option('skip-exists') ? 1 : 0" + exec: | + #!php + $exclude = []; + if ($env['SKIP_EXISTS']) { + $exclude = explode("\n", shell_exec('docker image ls -a --format \'{{ print .Repository ":" .Tag }}\'')); + } + $include = json_decode($env['IMAGES'], true); + $compose = ['version' => '3', 'services' => []]; + foreach ($include as $image) { + if (!in_array($image['image'], $exclude)) { + $compose['services'][str_replace(['/', ':'], '_', $image['image'])] = array_filter($image, function ($value) { return $value !== null; }); + } + } + echo \Symfony\Component\Yaml\Yaml::dump($compose, 100, 2); + +command('external-images pull []'): + env: + SERVICE: = input.argument('service') + exec: | + #!bash(workspace:/)|@ + CONF_ARGS=() + if [ -n "${CI:-}" ] || [ -n "${BUILD_ID:-}" ]; then + CONF_ARGS+=(--skip-exists) + fi + + if [ -n "${SERVICE}" ]; then + CONF_ARGS+=("$SERVICE") + fi + + passthru "ws external-images config $(printf '%q ' "${CONF_ARGS[@]}") | docker-compose -f - pull" + +command('external-images ls [--all]'): + env: + IMAGES: = external_images(docker_service_images()) + SHOW_REMOTE: "= input.option('all') ? 1 : 0" + exec: | + #!php + $include = array_map( + function ($image) { + return $image['image']; + }, + json_decode($env['IMAGES'], true) + ); + if ($env['SHOW_REMOTE']) { + $images = $include; + } else { + $all = explode("\n", shell_exec('docker image ls -a --format \'{{ print .Repository ":" .Tag }}\'')); + $images = array_intersect($include, $all); + } + + if (count($images) > 0) { + echo join("\n", $images)."\n"; + } + +command('external-images rm [--force]'): + env: + FORCE: = boolToString(input.option('force')) + exec: | + #!bash(workspace:/)|@ + IMAGES=($(ws external-images ls)) + OPTS=() + + if [ "${FORCE}" = yes ]; then + OPTS+=(--force) + fi + + if [ "${#IMAGES[@]}" -gt 0 ]; then + docker image rm "${OPTS[@]}" -- "${IMAGES[@]}" + fi diff --git a/harness/config/functions.yml b/harness/config/functions.yml new file mode 100644 index 0000000..5cb5166 --- /dev/null +++ b/harness/config/functions.yml @@ -0,0 +1,292 @@ +function('host_architecture', [style]): | + #!php + $arch = php_uname('m'); + if ($style == 'native') { + $result = $arch; + } else if (empty($style) || $style == 'go') { + $goArchMap = [ + 'i386' => '386', + 'x86_64' => 'amd64', + 'aarch64' => 'arm64', + 'armv7l' => 'arm', + 'armv6l' => 'arm', + ]; + $result = $goArchMap[$arch] ?? $arch; + } else { + throw new \Exception(sprintf('error: unknown host_architecture style "%s"', $style)); + } + = $result; + +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('filter_local_services', [services]): | + #!php + $filteredServices = []; + foreach ($services as $serviceName => $service) { + $filteredService = []; + foreach ($service as $key => $value) { + switch ($key) { + case 'enabled': + case 'environment': + case 'environment_dynamic': + case 'image': + case 'resources': + $filteredService[$key] = $value; + } + } + if (count($filteredService) > 0) { + $filteredServices[$serviceName] = $filteredService; + } + } + foreach ($filteredServices as $serviceName => $service) { + if (!isset($service['environment_dynamic'])) { + $filteredServices[$serviceName]['environment_dynamic'] = []; + } + } + = $filteredServices; + +function('flatten', [array_input]): | + #!php + $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array_input)); + = iterator_to_array($iterator, false); + +function('get_docker_external_networks'): | + #!php + $configRaw = shell_exec('docker-compose config'); + if ($configRaw === null) { + exit(1); + } + $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', [filterService]): | + #!php + $configRaw = shell_exec('docker-compose config'); + if ($configRaw === null) { + exit(1); + } + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + $images = []; + + foreach ($config['services'] as $serviceName => $service) { + if ($filterService && $serviceName != $filterService) { + continue; + } + + $imageSpec = [ + 'image' => $service['image'] ?? null, + 'platform' => $service['platform'] ?? null, + 'upstream' => [], + ]; + + $aliases = []; + + if ($imageSpec['image'] && strpos($imageSpec['image'], ':') === false) { + $imageSpec['image'] .= ':latest'; + } + + if (isset($service['build'])) { + $dockerfilePath = $service['build']['dockerfile'] ?? 'Dockerfile'; + if (substr($dockerfilePath, 0, 1) !== '/') { + $dockerfilePath = rtrim($service['build']['context'], '/') . '/' . $dockerfilePath; + } + + if (preg_match_all('/^FROM\s+(?:--platform=([^\s]+)\s+)?([^\s]*)(?:\s+AS\s+([^\s]+))?/mi', file_get_contents($dockerfilePath), $matches, PREG_SET_ORDER) === false) { + continue; + } + + foreach ($matches as $match) { + // Skip FROM , as only the upstream images needed + if (in_array($match[2], $aliases)) { + continue; + } + // Docker uses :latest tag if no explicit tag listed + if ($match[2] != 'scratch' && strpos($match[2], ':') === false) { + $match[2] .= ':latest'; + } + if (isset($match[3])) { + $aliases[] = $match[3]; + } + switch ($match[1]) { + case '$BUILDPLATFORM': + $platform = null; + break; + case '$TARGETPLATFORM': + case null: + $platform = $imageSpec['platform']; + break; + default: + $platform = $match[1]; + } + $imageSpec['upstream'][] = [ + 'image' => $match[2], + 'platform' => $platform, + ]; + } + } + $images[$serviceName] = $imageSpec; + } + + = $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('replace', [haystack, needle, replacement]): | + #!php + = str_replace($needle, $replacement, $haystack); + +function('template_key_value', [template, key_value]): | + #!php + $output = []; + foreach ($key_value as $key => $value) { + $output[str_replace('{{key}}', $key, $template)] = $value; + } + = $output; + +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); + +function('bool', [value]): | + #!php + if (is_bool($value)) { + return $value; + } + switch ($value) { + case 'yes': + case 'true': + return true; + case 'no': + case 'false': + return false; + } + echo 'error: unknown boolean string "'.$value.'"'."\n"; + exit(1); + +function('boolToString', [value]): | + #!php + switch ($value) { + case true: return 'yes'; + case false: return 'no'; + } + echo 'error: unknown boolean "'.$value.'"'."\n"; + exit(1); diff --git a/harness/config/mutagen.yml b/harness/config/mutagen.yml new file mode 100644 index 0000000..b6c4ccb --- /dev/null +++ b/harness/config/mutagen.yml @@ -0,0 +1,125 @@ +function('get_mutagen_volume_names'): | + #!php(workspace:/) + $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(workspace:/) + $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(workspace:/) + $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); + +function('get_mutagen_forward_names'): | + #!php(workspace:/) + $forwardNames = []; + + if (file_exists('mutagen.yml')) { + $configRaw = file_get_contents('mutagen.yml'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + + if (isset($config['forward'])) { + $forwardNames = array_keys($config['forward']); + } + } + = join("\n", array_unique($forwardNames)); + +function('get_mutagen_sync_names'): | + #!php(workspace:/) + $syncNames = []; + + if (file_exists('mutagen.yml')) { + $configRaw = file_get_contents('mutagen.yml'); + $config = \Symfony\Component\Yaml\Yaml::parse($configRaw); + + if (isset($config['sync'])) { + $syncNames = array_keys($config['sync']); + } + } + = join("\n", array_unique($syncNames)); + +command('mutagen (start|stop|pause|resume)'): + env: + COMMAND: = input.command(2) + CONTAINER_NAMES: = get_mutagen_volume_containers() + FORWARD_NAMES: = get_mutagen_forward_names() + SYNC_NAMES: = get_mutagen_sync_names() + 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() + FORWARD_NAMES: = get_mutagen_forward_names() + SYNC_NAMES: = get_mutagen_sync_names() + exec: | + #!bash(workspace:/) + CONTAINER_NAMES=($CONTAINER_NAMES) + passthru docker volume rm "${CONTAINER_NAMES[@]}" + +command('switch (cached-volumes|delegated-volumes|mutagen)'): + 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 true + ws set mutagen false + elif [[ "$SYNC" = "mutagen" ]]; then + ws set delegated-volumes false + ws set mutagen true + else + ws set delegated-volumes false + ws set mutagen false + fi + run ws harness prepare + echo 'Bringing up the environment with the new setting' + if [[ "$SYNC" = "mutagen" ]]; then + 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 0000000..8260f75 --- /dev/null +++ b/harness/config/pipeline.yml @@ -0,0 +1,135 @@ + +command('app build'): + env: + HAS_CRON: "= @('services.cron.enabled') ? 'true' : 'false'" + HAS_JENKINS_RUNNER: "= @('services.jenkins-runner.enabled') ? 'true' : 'false'" + HAS_JOB_QUEUE_CONSUMER: "= @('services.job-queue-consumer.enabled') ? 'true' : 'false'" + HAS_WEBAPP: "= @('services.webapp.enabled') ? 'true' : 'false'" + exec: | + #!bash(workspace:/)|@ + ws external-images pull + + # dependency ordered build + passthru docker-compose build console + if [[ "${HAS_WEBAPP}" == "true" ]] || [[ "${HAS_CRON}" == "true" ]] || [ "${HAS_JOB_QUEUE_CONSUMER}" == "true" ]; then + passthru docker-compose build php-fpm + fi + if [[ "${HAS_WEBAPP}" == "true" ]]; then + passthru docker-compose build nginx + fi + if [[ "${HAS_CRON}" == "true" ]]; then + passthru docker-compose build cron + fi + if [[ "${HAS_JENKINS_RUNNER}" == "true" ]]; then + passthru docker-compose build jenkins-runner + fi + if [[ "${HAS_JOB_QUEUE_CONSUMER}" == "true" ]]; then + passthru docker-compose build job-queue-consumer + fi + +command('app build '): + env: + SERVICE: = input.argument('service') + exec: | + #!bash(workspace:/)|@ + passthru docker-compose build "${SERVICE}" + +command('app publish'): + env: + COMPOSE_HTTP_TIMEOUT: 180 + DOCKER_CLIENT_TIMEOUT: 180 + exec: | + #!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 -u HEAD + +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 [--cleanup] '): + env: + CHART_PATH: = input.argument('chart-path') + NAMESPACE: = @('pipeline.' ~ input.argument('environment') ~ '.namespace') + ADDITIONAL_SCHEMA_LOCATIONS: = @('helm.additional_schema_locations') + CLEANUP: "= input.option('cleanup') ? 1 : 0" + 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[@]}" . + + if [ "$CLEANUP" = "1" ]; then + run rm -rf charts/ requirements.lock + fi diff --git a/harness/config/secrets.yml b/harness/config/secrets.yml new file mode 100644 index 0000000..7a778cb --- /dev/null +++ b/harness/config/secrets.yml @@ -0,0 +1,125 @@ +command('secret image-pull-config [--cert=] [--scope=] [--namespace=]'): + env: + SEALED_SECRETS: "= boolToString(@('helm.feature.sealed_secrets'))" + DEFAULT_CONFIG: = docker_config(@('docker.registry')) + SEALED_SECRETS_CONTROLLER_NAME: = @('helm.sealed_secrets.controller_name') + SEALED_SECRETS_CONTROLLER_NAMESPACE: = @('helm.sealed_secrets.controller_namespace') + SEALED_SECRETS_CERTIFICATE_FILE: "= input.option('cert') ?: @('helm.sealed_secrets.certificate_file')" + SECRET_NAMESPACE: "= input.option('namespace') ?: @('helm.sealed_secrets.namespace')" + SECRET_SCOPE: "= input.option('scope') ?: @('helm.sealed_secrets.scope')" + 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 0 ] ; 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 + DEFAULT_SCOPE=cluster-wide + KUBESEAL_OPTS=( + --name "image-pull-config" + ) + 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 + if [ -n "${SEALED_SECRETS_CERTIFICATE_FILE:-}" ]; then + KUBESEAL_OPTS+=( + --cert "${SEALED_SECRETS_CERTIFICATE_FILE}" + ) + fi + if [ -n "${SECRET_NAMESPACE:-}" ]; then + DEFAULT_SCOPE=namespace-wide + KUBESEAL_OPTS+=(--namespace "$SECRET_NAMESPACE") + fi + KUBESEAL_OPTS+=(--scope "${SECRET_SCOPE:-$DEFAULT_SCOPE}") + + echo -n "${DOCKER_CONFIG}" | passthru 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) [--cert=] [--scope=] [--namespace=] '): + env: + INPUT_TYPE: = input.command(3) + SEALED_SECRETS_CONTROLLER_NAME: = @('helm.sealed_secrets.controller_name') + SEALED_SECRETS_CONTROLLER_NAMESPACE: = @('helm.sealed_secrets.controller_namespace') + SEALED_SECRETS_CERTIFICATE_FILE: "= input.option('cert') ?: @('helm.sealed_secrets.certificate_file')" + SECRET_NAME: = input.argument('secret-name') + SECRET_NAMESPACE: "= input.option('namespace') ?: @('helm.sealed_secrets.namespace')" + SECRET_SCOPE: "= input.option('scope') ?: @('helm.sealed_secrets.scope')" + 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 0 ] ; 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 + DEFAULT_SCOPE=cluster-wide + KUBESEAL_OPTS=( + --name "${SECRET_NAME}" + ) + 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 + if [ -n "${SEALED_SECRETS_CERTIFICATE_FILE:-}" ]; then + KUBESEAL_OPTS+=( + --cert "${SEALED_SECRETS_CERTIFICATE_FILE}" + ) + fi + if [ -n "${SECRET_NAMESPACE:-}" ]; then + DEFAULT_SCOPE=namespace-wide + KUBESEAL_OPTS+=(--namespace "$SECRET_NAMESPACE") + fi + KUBESEAL_OPTS+=(--scope "${SECRET_SCOPE:-$DEFAULT_SCOPE}") + + echo -n "${DATA}" | passthru kubeseal --raw "${KUBESEAL_OPTS[@]}" diff --git a/harness/config/xdebug.yml b/harness/config/xdebug.yml new file mode 100644 index 0000000..8095904 --- /dev/null +++ b/harness/config/xdebug.yml @@ -0,0 +1,33 @@ +command('feature xdebug version-sync'): + env: + XDEBUG_VERSION: = @('php.ext-xdebug.version') + COMPOSE_PROJECT_NAME: = @('namespace') + exec: | + #!bash(workspace:/) + source .my127ws/harness/scripts/xdebug_version_sync.sh + +command('feature xdebug (on|off)'): + env: + ATTR_KEY: 'php.ext-xdebug.enable' + ATTR_VAL: = boolToString(input.command(3) == 'on') + 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: = boolToString(input.command(4) == 'on') + 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' diff --git a/harness/scripts/destroy.sh b/harness/scripts/destroy.sh new file mode 100755 index 0000000..6716b07 --- /dev/null +++ b/harness/scripts/destroy.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [ "${DESTROY_ALL}" = yes ]; then + RMI=all +else + RMI=local +fi + +run docker-compose down --rmi "${RMI}" --volumes --remove-orphans --timeout 120 + +if [ "${USE_MUTAGEN}" = yes ]; then + run ws mutagen stop + passthru ws mutagen rm +fi + +passthru ws cleanup built-images + +run rm -f .my127ws/{.flag-built,.flag-console-built} diff --git a/harness/scripts/disable.sh b/harness/scripts/disable.sh new file mode 100755 index 0000000..fe4627b --- /dev/null +++ b/harness/scripts/disable.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if [[ "$USE_MUTAGEN" = "yes" ]]; then + run ws mutagen pause +fi + +run docker-compose stop diff --git a/harness/scripts/enable.sh.twig b/harness/scripts/enable.sh.twig new file mode 100755 index 0000000..1bc7dfa --- /dev/null +++ b/harness/scripts/enable.sh.twig @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +console_enabled() +{ + if [ "$APP_BUILD" = dynamic ]; then + [ -f .my127ws/.flag-console-built ] + else + [ -n "$(docker-compose ps --quiet console)" ] + fi +} + +enable_all() +{ + local IS_BUILT=no + local HAS_FLAG=no + + if [ -f .my127ws/.flag-built ]; then + HAS_FLAG=yes + IS_BUILT=yes + # If the containers no longer exist, we need to rebuild them + if [ -z "$(docker-compose ps --quiet)" ]; then + IS_BUILT=no + fi + fi + + if [ "$APP_BUILD" = dynamic ] && console_enabled && [ -z "$(docker-compose ps --quiet console)" ]; then + rm .my127ws/.flag-console-built + fi + + passthru ws networks external + if [ "$IS_BUILT" = no ]; then + if [ "$HAS_FLAG" = no ]; then + # remove all services, keeping console if considered built + if console_enabled; then + passthru "docker-compose ps --services | grep -v -Fxe console | xargs docker-compose rm --force --stop --" + else + passthru docker-compose down + fi + fi + + if [ "$HAS_ASSETS" = yes ]; then + ws assets download + fi + + "$APP_BUILD" + + initial_start + + touch .my127ws/.flag-built + else + if [ "$APP_BUILD" = dynamic ] && [ "$USE_MUTAGEN" = yes ]; then + passthru ws mutagen resume + fi + + passthru docker-compose up -d + passthru docker-compose exec -T -u build console app welcome + fi +} + +enable_console() { + if [ "$APP_BUILD" = dynamic ] && console_enabled && [ -z "$(docker-compose ps --quiet console)" ]; then + rm .my127ws/.flag-console-built + fi + + passthru ws networks external + passthru ws external-images pull console + "${APP_BUILD}_console" +} + +dynamic_console() +{ + if console_enabled; then + # ensure it is started + if [ -z "$(docker-compose ps --quiet console)" ]; then + passthru docker-compose up -d console + fi + return; + fi + + # we synchronise then stop mutagen to avoid impacting CPU usage during the build + if [ "$USE_MUTAGEN" = yes ]; then + passthru ws mutagen start + passthru ws mutagen pause + fi + + find .my127ws/docker/image/ -type d ! -perm u+rwx,go+rx,o-w -exec chmod u+rwx,go+rx,o-w {} + + + passthru docker-compose up --build -d console + passthru docker-compose exec -T -u build console app build + + if [ "$USE_MUTAGEN" = yes ]; then + ws mutagen resume + fi + + touch .my127ws/.flag-console-built +} + +dynamic() +{ + ws external-images pull + + dynamic_console + + passthru "docker-compose config --services | grep -v -Fwe console -Fxe cron -Fxe jenkins-runner -Fxe job-queue-consumer | xargs docker-compose build" + + {% if @('services.cron.enabled') %} + passthru docker-compose build cron + {% endif %} + {% if @('services.jenkins-runner.enabled') %} + passthru docker-compose build jenkins-runner + {% endif %} + {% if @('services.job-queue-consumer.enabled') %} + passthru docker-compose build job-queue-consumer + {% endif %} +} + +static_console() +{ + if [ "$HAS_ASSETS" = yes ]; then + ws assets download + fi + passthru docker-compose up --build -d console +} + +static() +{ + ws app build +} + +initial_start() +{ + # Bring up all services apart from cron + passthru "docker-compose config --services | grep -v -Fxe cron | xargs docker-compose up -d" + + passthru docker-compose exec -T -u build console app init + + {% if @('services.cron.enabled') %} + passthru docker-compose up -d cron + {% endif %} +} + +"enable_$1" diff --git a/harness/scripts/latest-mutagen-release.php b/harness/scripts/latest-mutagen-release.php new file mode 100644 index 0000000..aa84bbb --- /dev/null +++ b/harness/scripts/latest-mutagen-release.php @@ -0,0 +1,43 @@ + [ + '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.'); +} + +$goArchMap = [ + 'i386' => '386', + 'x86_64' => 'amd64', + 'aarch64' => 'arm64', + 'armv7l' => 'arm', + 'armv6l' => 'arm', +]; + +$osFamily = strtolower(PHP_OS_FAMILY); +$hostArch = php_uname('m'); +$goArch = $goArchMap[php_uname('m')] ?? $hostArch; +$releaseAssets = array_filter($latestStableRelease['assets'], function ($asset) use ($osFamily, $goArch) { + return preg_match("/^mutagen_.*${osFamily}.*${goArch}.*/", $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 0000000..11e8d96 --- /dev/null +++ b/harness/scripts/mutagen.sh @@ -0,0 +1,179 @@ +#!/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) +# shellcheck disable=SC2206 +SYNC_NAMES=($SYNC_NAMES) +# shellcheck disable=SC2206 +FORWARD_NAMES=($FORWARD_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 -qi 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 --fail --silent --show-error --location --output .my127ws/utilities/mutagen/mutagen.tar.gz "$download_url" + 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 --all --filter "name=${CONTAINER_NAME}\$" --format '{{.Names}}')" == "${CONTAINER_NAME}" ]]; then + passthru docker start "${CONTAINER_NAME}" + else + 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 --init --name "${CONTAINER_NAME}" $(printf -- '-v %q ' "${CONTAINER_VOLUME_MAPPINGS[@]}") alpine:latest tail -f /dev/null + fi + done +} + +start_mutagen_daemon() +{ + passthru mutagen daemon start +} + +join_by_character() +( + local IFS="$1" + shift + echo "$*" +) + +clean_existing_projects() +{ + # Clean up the project if it's running + if mutagen project list > /dev/null 2>&1; then + passthru mutagen project terminate + fi + + declare -a EXISTING_PROJECT_LABELS=() + + local SYNC_NAME + local SYNC_LIST + for SYNC_NAME in "${SYNC_NAMES[@]}"; do + # List syncs based on name + SYNC_LIST="$(mutagen sync list "$SYNC_NAME" 2> /dev/null || true)" + # Check if there are entries left + if [ "$(echo "$SYNC_LIST" | grep --count "URL: $(pwd)" | awk '{ print $1 }')" -gt 0 ]; then + # Build an array of sync session IDs to clean up + while IFS='' read -r line; do EXISTING_PROJECT_LABELS+=("$line"); done < <(echo "$SYNC_LIST" | grep -B4 "URL: $(pwd)" | grep io.mutagen.project | awk '{print $1"="$2}' | sed s/:=/=/) + fi + done + + local FORWARD_NAME + local FORWARD_LIST + local CONTAINER_NAMES_REGEX + CONTAINER_NAMES_REGEX="$(join_by_character "\|" "${CONTAINER_NAMES[@]}")" + for FORWARD_NAME in "${FORWARD_NAMES[@]}"; do + # List forwards based on name + FORWARD_LIST="$(mutagen forward list "$FORWARD_NAME" 2> /dev/null || true)" + # Check if there are entries left + if [ "$(echo "$FORWARD_LIST" | grep --count "URL: docker://\($CONTAINER_NAMES_REGEX\):tcp:" | awk '{ print $1 }')" -gt 0 ]; then + # Build an array of sync session IDs to clean up + while IFS='' read -r line; do EXISTING_PROJECT_LABELS+=("$line"); done < <(echo "$FORWARD_LIST" | grep -B6 "URL: docker://\($CONTAINER_NAMES_REGEX\):tcp:" | grep io.mutagen.project | awk '{print $1"="$2}' | sed s/:=/=/) + fi + done + + if [ "${#EXISTING_PROJECT_LABELS[@]}" -gt 0 ]; then + echo "Found multiple mutagen sessions for this project." + echo "This can lead to increased CPU usage." + local REPLY="" + if [ -t 0 ] ; then + read -r -p 'Do you want to remove the other sessions? [Yes]/no: ' REPLY + fi + REPLY="${REPLY:-Yes}" + if ! [[ "$REPLY" =~ ^(Y|Yes|y|yes)$ ]]; then + return + fi + + local LABEL_SELECTOR + for LABEL_SELECTOR in "${EXISTING_PROJECT_LABELS[@]}"; do + passthru mutagen sync terminate --label-selector "${LABEL_SELECTOR}" + passthru mutagen forward terminate --label-selector "${LABEL_SELECTOR}" + done + fi +} + +start() +{ + install_mutagen + setup_sync_container + start_mutagen_daemon + + clean_existing_projects + passthru mutagen project start + passthru mutagen project flush +} + +stop() +{ + passthru mutagen project terminate + clean_existing_projects + 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/harness/scripts/rebuild.sh b/harness/scripts/rebuild.sh new file mode 100644 index 0000000..0ed4504 --- /dev/null +++ b/harness/scripts/rebuild.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +run docker-compose down --rmi local --volumes --remove-orphans --timeout 120 + +passthru ws cleanup built-images + +run rm -f .my127ws/.flag-built .my127ws/.flag-console-built + +passthru ws enable diff --git a/harness/scripts/xdebug_version_sync.sh b/harness/scripts/xdebug_version_sync.sh new file mode 100644 index 0000000..f62f4d5 --- /dev/null +++ b/harness/scripts/xdebug_version_sync.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +UPDATE_CONTAINERS=0 + +xdebug_version_sync() +{ + local INSTALLED_VERSION + INSTALLED_VERSION="$(docker-compose exec -T -u root "$1" pecl info xdebug 2>&1 | grep "Release Version" | awk '{print $3}')" + local INSTALLED_MAJOR_VERSION + INSTALLED_MAJOR_VERSION="$(echo "$INSTALLED_VERSION" | cut -d '.' -f1)" + + if [[ "$INSTALLED_MAJOR_VERSION" == "$XDEBUG_VERSION" ]]; then + echo "Xdebug in $1 is already on the desired major version $XDEBUG_VERSION (found installed version $INSTALLED_VERSION)" + return 0 + fi + + case $XDEBUG_VERSION in + "3") + local INSTALL_CANDIDATE="xdebug" + ;; + "2") + local INSTALL_CANDIDATE="xdebug-2.9.8" + ;; + *) + echo "Invalid Xdebug version provided - please check the 'php.ext-xdebug.version' attribute is '2' or '3'." + exit 1 + esac + + run docker-compose exec -T -u root "$1" pecl uninstall xdebug + run docker-compose exec -T -u root "$1" pecl install "$INSTALL_CANDIDATE" + + UPDATE_CONTAINERS=1 +} + +xdebug_version_sync 'console' +xdebug_version_sync 'php-fpm' + +if [[ "$UPDATE_CONTAINERS" == 1 ]]; then + run ws service php-fpm restart +fi diff --git a/helm/app/Chart.yaml.twig b/helm/app/Chart.yaml.twig new file mode 100644 index 0000000..ee356e3 --- /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 0000000..4d6320a --- /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.services.varnish.enabled }} +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 0000000..6aa2e80 --- /dev/null +++ b/helm/app/templates/_base_helper.tpl @@ -0,0 +1,74 @@ +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.appVersion | default .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Common selectors +*/}} +{{- define "chart.selectors" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{- 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 and .service.environment_secrets (.service.enabled | default true) }} +{{ if .root.Values.feature.sealed_secrets }} +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +{{ else }} +apiVersion: v1 +kind: Secret +{{ end }} +metadata: + name: {{ .root.Values.resourcePrefix }}{{ .service_name }} + labels: + {{- include "chart.labels" .root | nindent 4 }} + app.kubernetes.io/component: {{ .component | default .service_name }} + annotations: + argocd.argoproj.io/sync-wave: "1" +{{ if .root.Values.feature.sealed_secrets }} +{{ if ne .root.Values.sealed_secrets.scope "strict" }} + sealedsecrets.bitnami.com/{{ .root.Values.sealed_secrets.scope }}: "true" +{{ end }} +spec: + encryptedData: +{{ index .service.environment_secrets | toYaml | nindent 4 }} + template: + metadata: + labels: + {{- include "chart.labels" .root | nindent 8 }} + app.kubernetes.io/component: {{ .component | default .service_name }} +{{ 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 0000000..372bb42 --- /dev/null +++ b/helm/app/templates/application/app-init.yaml @@ -0,0 +1,71 @@ +{{- with mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "console") -}} +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" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: app-init +spec: + backoffLimit: 0 + template: + metadata: + labels: + {{- include "chart.labels" $ | nindent 8 }} + app.kubernetes.io/component: app-init + spec: + restartPolicy: Never + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}console + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: app-init +{{ if eq $.Values.ingress.type "istio" }} + command: ["/bin/sh"] + args: ["-c", "/entrypoint.sh app init ; code=$? ; curl -vv --request POST http://127.0.0.1:15020/quitquitquit ; exit $code"] +{{ else }} + command: ["/entrypoint.sh"] + args: ["app", "init"] +{{ end }} + resources: + limits: + memory: {{ .resources.init_memory }} + requests: + memory: {{ .resources.init_memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/application/app-migrate.yaml b/helm/app/templates/application/app-migrate.yaml new file mode 100644 index 0000000..96bdbb8 --- /dev/null +++ b/helm/app/templates/application/app-migrate.yaml @@ -0,0 +1,69 @@ +{{- with mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "console") -}} +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" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: app-migrate +spec: + template: + metadata: + labels: + {{- include "chart.labels" $ | nindent 8 }} + app.kubernetes.io/component: app-migrate + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}console + {{- end }} + image: {{ .image }} + imagePullPolicy: Always + name: app-migrate +{{ if eq $.Values.ingress.type "istio" }} + command: ["/bin/sh"] + args: ["-c", "/entrypoint.sh app migrate ; code=$? ; curl -vv -request POST http://127.0.0.1:15020/quitquitquit ; exit $code"] +{{ else }} + command: ["/entrypoint.sh"] + args: ["app", "migrate"] +{{ end }} + resources: + limits: + memory: {{ .resources.migrate_memory }} + requests: + memory: {{ .resources.migrate_memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/application/console/deployment.yaml b/helm/app/templates/application/console/deployment.yaml new file mode 100644 index 0000000..03ca845 --- /dev/null +++ b/helm/app/templates/application/console/deployment.yaml @@ -0,0 +1,75 @@ +{{- with mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "console") -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}console + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: console + 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: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: console + app.service: {{ $.Values.resourcePrefix }}console + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}console + {{- end }} + image: {{ .image }} + imagePullPolicy: Always + name: console + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/application/console/secret.yaml b/helm/app/templates/application/console/secret.yaml new file mode 100644 index 0000000..5136ca3 --- /dev/null +++ b/helm/app/templates/application/console/secret.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "console") -}} +{{ template "service.environment.secret" (dict "service_name" "console" "service" $service "root" $) }} diff --git a/helm/app/templates/application/cron/deployment.yaml b/helm/app/templates/application/cron/deployment.yaml new file mode 100644 index 0000000..46fe5e2 --- /dev/null +++ b/helm/app/templates/application/cron/deployment.yaml @@ -0,0 +1,86 @@ +{{- with mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "cron") -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}cron + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: cron + 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: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: cron + 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 := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}cron + {{- end }} + image: {{ .image }} + imagePullPolicy: Always + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/application/cron/secret.yaml b/helm/app/templates/application/cron/secret.yaml new file mode 100644 index 0000000..0c6f2bf --- /dev/null +++ b/helm/app/templates/application/cron/secret.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "cron") -}} +{{ template "service.environment.secret" (dict "service_name" "cron" "service" $service "root" $) }} 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 0000000..3a835d7 --- /dev/null +++ b/helm/app/templates/application/image-pull-config.yaml @@ -0,0 +1,30 @@ +{{ 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: + argocd.argoproj.io/sync-wave: "1" +{{ if .Values.feature.sealed_secrets }} + sealedsecrets.bitnami.com/cluster-wide: "true" + labels: + {{- include "chart.labels" $ | nindent 4 }} +spec: + template: + type: kubernetes.io/dockerconfigjson + metadata: + labels: + {{- include "chart.labels" $ | nindent 8 }} + 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 0000000..f2d13d8 --- /dev/null +++ b/helm/app/templates/application/webapp/deployment.yaml @@ -0,0 +1,166 @@ +{{- $service_php_fpm := mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "php-fpm") -}} +{{- if .Values.services.webapp.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.resourcePrefix }}webapp + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: webapp + 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: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: webapp + app.service: {{ .Values.resourcePrefix }}webapp + spec: + {{- if and $service_php_fpm.enabled (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: + {{- with .Values.services.nginx }} + - name: nginx + env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}nginx + {{- end }} + image: {{ .image }} + imagePullPolicy: Always + ports: + - name: http + containerPort: 80 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + {{- if not (eq "" (include "application.volumeMounts.all" $)) }} + {{- include "application.volumeMounts.all" $ | indent 8 }} + {{- else }} [] + {{- end }} + {{- end }} + + {{- with $service_php_fpm }} + {{- if .enabled }} + - name: php-fpm + env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}php-fpm + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + ports: + - containerPort: 9000 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} + {{- end }} + {{- end }} + + {{- with index .Values.services "php-fpm-exporter" }} + {{- if .enabled }} + - name: php-fpm-exporter + env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + {{- $tp := typeOf $value }} + - name: {{ $key | quote }} + {{- if eq $tp "string" }} + value: {{ tpl $value $ | quote }} + {{- else}} + value: {{ $value | quote }} + {{- end }} + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + ports: + - name: php-fpm-metrics + containerPort: 9253 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + readinessProbe: + tcpSocket: + port: 9253 + initialDelaySeconds: 5 + periodSeconds: 10 + {{- end }} + {{- 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/webapp/ingress-istio.yaml b/helm/app/templates/application/webapp/ingress-istio.yaml new file mode 100644 index 0000000..2a6a0c7 --- /dev/null +++ b/helm/app/templates/application/webapp/ingress-istio.yaml @@ -0,0 +1,35 @@ +{{ if and (index .Values.services .Values.ingress.target_service "enabled") (eq .Values.ingress.type "istio") }} +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Release.Namespace }}-{{ .Values.resourcePrefix }}webapp-virtualservice +{{- with .Values.ingress.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} +{{- end }} + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: webapp + app: {{ .Values.resourcePrefix }}webapp + app.service: {{ .Values.resourcePrefix }}webapp +spec: + hosts: + - {{ index .Values.services "php-base" "environment" "APP_HOST" }} + gateways: +{{- range $key, $value := .Values.istio.gateways }} + - {{ tpl $value $ | quote }} +{{- end }} +{{- range $key, $value := .Values.istio.additionalGateways }} + - {{ $value | quote }} +{{- end }} + http: + - route: + - destination: + host: {{ .Values.resourcePrefix }}{{ .Values.ingress.target_service }} + port: + number: 80 + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} +{{ end }} diff --git a/helm/app/templates/application/webapp/ingress.yaml b/helm/app/templates/application/webapp/ingress.yaml new file mode 100644 index 0000000..b8d2439 --- /dev/null +++ b/helm/app/templates/application/webapp/ingress.yaml @@ -0,0 +1,29 @@ +{{ if and (index .Values.services .Values.ingress.target_service "enabled") (eq .Values.ingress.type "standard") }} +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: +{{- with .Values.ingress.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} +{{- end }} + creationTimestamp: null + labels: + {{- include "chart.labels" . | nindent 4 }} + app.kubernetes.io/component: webapp + app.service: {{ .Values.resourcePrefix }}webapp + name: {{ .Values.resourcePrefix }}webapp +spec: + rules: + - host: {{ index .Values.services "php-base" "environment" "APP_HOST" }} + http: + paths: + - backend: + serviceName: {{ .Values.resourcePrefix }}{{ .Values.ingress.target_service }} + servicePort: 80 + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} +status: + loadBalancer: {} +{{- end }} diff --git a/helm/app/templates/application/webapp/podmonitor.yaml b/helm/app/templates/application/webapp/podmonitor.yaml new file mode 100644 index 0000000..ac3157f --- /dev/null +++ b/helm/app/templates/application/webapp/podmonitor.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.services.webapp.enabled .Values.prometheus.podMonitoring -}} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ .Values.resourcePrefix }}webapp + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: webapp + app.service: {{ .Values.resourcePrefix }}webapp +spec: + selector: + matchLabels: + app.service: {{ .Values.resourcePrefix }}webapp + podMetricsEndpoints: +{{- if .Values.services.nginx.metricsEnabled -}} +{{ .Values.services.nginx.metricsEndpoints | toYaml | nindent 6 -}} +{{- end -}} +{{- if and (index .Values.services "php-fpm-exporter" "enabled") (index .Values.services "php-fpm-exporter" "metricsEnabled") -}} +{{ index .Values.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 0000000..889abdf --- /dev/null +++ b/helm/app/templates/application/webapp/secret-nginx.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "component" "webapp" "service_name" "nginx" "service" .Values.services.nginx "root" $) }} 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 0000000..d7c78a9 --- /dev/null +++ b/helm/app/templates/application/webapp/secret-php-fpm.yaml @@ -0,0 +1,2 @@ +{{- $service := mergeOverwrite (dict) (index .Values.services "php-base") (index .Values.services "php-fpm") -}} +{{ template "service.environment.secret" (dict "component" "webapp" "service_name" "php-fpm" "service" $service "root" $) }} diff --git a/helm/app/templates/application/webapp/service.yaml b/helm/app/templates/application/webapp/service.yaml new file mode 100644 index 0000000..69f80c0 --- /dev/null +++ b/helm/app/templates/application/webapp/service.yaml @@ -0,0 +1,19 @@ +{{- if index .Values.services .Values.ingress.target_service "enabled" -}} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: webapp + 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: {} +{{- end }} diff --git a/helm/app/templates/service/elasticsearch/deployment.yaml b/helm/app/templates/service/elasticsearch/deployment.yaml new file mode 100644 index 0000000..ee5e1bd --- /dev/null +++ b/helm/app/templates/service/elasticsearch/deployment.yaml @@ -0,0 +1,66 @@ +{{- with .Values.services.elasticsearch -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}elasticsearch + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: elasticsearch + app.service: {{ $.Values.resourcePrefix }}elasticsearch +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}elasticsearch +{{- if $.Values.persistence.elasticsearch.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: elasticsearch + app.service: {{ $.Values.resourcePrefix }}elasticsearch + spec: + securityContext: + fsGroup: 1000 + containers: + - env: + - name: ES_JAVA_OPTS + value: -Xmx512m -Xms512m + - name: discovery.type + value: single-node + image: {{ .image | quote }} + imagePullPolicy: Always + name: elasticsearch + ports: + - containerPort: 9200 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/service/elasticsearch/pvc.yaml b/helm/app/templates/service/elasticsearch/pvc.yaml new file mode 100644 index 0000000..90011cf --- /dev/null +++ b/helm/app/templates/service/elasticsearch/pvc.yaml @@ -0,0 +1,32 @@ +{{ if and .Values.persistence.elasticsearch.enabled .Values.services.elasticsearch.enabled -}} + +{{- with .Values.persistence.elasticsearch -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}elasticsearch-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: elasticsearch + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..ce156dd --- /dev/null +++ b/helm/app/templates/service/elasticsearch/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.elasticsearch.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: elasticsearch + 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/mongodb/deployment.yaml b/helm/app/templates/service/mongodb/deployment.yaml new file mode 100644 index 0000000..1441864 --- /dev/null +++ b/helm/app/templates/service/mongodb/deployment.yaml @@ -0,0 +1,69 @@ +{{- with .Values.services.mongodb -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}mongodb + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + app.service: {{ $.Values.resourcePrefix }}mongodb +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}mongodb +{{- if $.Values.persistence.mongodb.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.labels" $ | nindent 8 }} + app.kubernetes.io/component: mongodb + app.service: {{ $.Values.resourcePrefix }}mongodb + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}mongodb + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: mongodb + ports: + - containerPort: 27017 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + readinessProbe: + tcpSocket: + port: 27017 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: data-db + mountPath: /data/db + restartPolicy: Always + volumes: + - name: data-db +{{- if $.Values.persistence.mongodb.enabled }} + persistentVolumeClaim: + claimName: {{ $.Values.resourcePrefix }}mongodb +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{- end }} +{{- end }} diff --git a/helm/app/templates/service/mongodb/pvc.yaml b/helm/app/templates/service/mongodb/pvc.yaml new file mode 100644 index 0000000..9c9f12c --- /dev/null +++ b/helm/app/templates/service/mongodb/pvc.yaml @@ -0,0 +1,31 @@ +{{ if and .Values.services.mongodb.enabled .Values.persistence.mongodb.enabled -}} +{{- with .Values.persistence.mongodb -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}mongodb + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + app.service: {{ $.Values.resourcePrefix }}mongodb +spec: + accessModes: + - {{ .accessMode | quote }} + resources: + requests: + storage: {{ .size | quote }} +{{- if .storageClass }} +{{- if (eq "-" .storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: {{ .storageClass | quote }} +{{- end }} +{{- end }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- end }} + +{{- end }} + +{{- end }} diff --git a/helm/app/templates/service/mongodb/secret.yaml b/helm/app/templates/service/mongodb/secret.yaml new file mode 100644 index 0000000..e46a425 --- /dev/null +++ b/helm/app/templates/service/mongodb/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "mongodb" "service" .Values.services.mongodb "root" $) }} diff --git a/helm/app/templates/service/mongodb/service.yaml b/helm/app/templates/service/mongodb/service.yaml new file mode 100644 index 0000000..58c4f2f --- /dev/null +++ b/helm/app/templates/service/mongodb/service.yaml @@ -0,0 +1,21 @@ +{{ if .Values.services.mongodb.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + app.service: {{ $.Values.resourcePrefix }}mongodb + name: {{ .Values.resourcePrefix }}mongodb +spec: + ports: + - name: wire + port: 27017 + targetPort: 27017 + selector: + {{- include "chart.selectors" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + app.service: {{ $.Values.resourcePrefix }}mongodb +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 0000000..5dd7ed8 --- /dev/null +++ b/helm/app/templates/service/mysql/deployment.yaml @@ -0,0 +1,77 @@ +{{- with .Values.services.mysql -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}mysql + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mysql + app.service: {{ $.Values.resourcePrefix }}mysql +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}mysql +{{- if $.Values.persistence.mysql.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: mysql + app.service: {{ $.Values.resourcePrefix }}mysql + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}mysql + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: mysql + {{- if .options }} + args: + {{- range $var, $value := .options }} + {{- if $value }} + - {{ print "--" $var "=" $value | quote }} + {{- end }} + {{- end }} + {{- end }} + ports: + - containerPort: 3306 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/service/mysql/pvc.yaml b/helm/app/templates/service/mysql/pvc.yaml new file mode 100644 index 0000000..653a9a4 --- /dev/null +++ b/helm/app/templates/service/mysql/pvc.yaml @@ -0,0 +1,32 @@ +{{ if and .Values.persistence.mysql.enabled .Values.services.mysql.enabled -}} + +{{- with .Values.persistence.mysql -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}mysql-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mysql + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..c045b0a --- /dev/null +++ b/helm/app/templates/service/mysql/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "mysql" "service" .Values.services.mysql "root" $) }} diff --git a/helm/app/templates/service/mysql/service.yaml b/helm/app/templates/service/mysql/service.yaml new file mode 100644 index 0000000..66bcbdf --- /dev/null +++ b/helm/app/templates/service/mysql/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.mysql.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: mysql + 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 0000000..6f53653 --- /dev/null +++ b/helm/app/templates/service/postgres/deployment.yaml @@ -0,0 +1,69 @@ +{{- with .Values.services.postgres -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}postgres + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: postgres + app.service: {{ $.Values.resourcePrefix }}postgres +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}postgres +{{- if $.Values.persistence.postgres.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: postgres + app.service: {{ $.Values.resourcePrefix }}postgres + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}postgres + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: postgres + ports: + - containerPort: 5432 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/service/postgres/pvc.yaml b/helm/app/templates/service/postgres/pvc.yaml new file mode 100644 index 0000000..d815154 --- /dev/null +++ b/helm/app/templates/service/postgres/pvc.yaml @@ -0,0 +1,32 @@ +{{ if and .Values.persistence.postgres.enabled .Values.services.postgres.enabled -}} + +{{- with .Values.persistence.postgres -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}postgres-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: postgres + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..116762a --- /dev/null +++ b/helm/app/templates/service/postgres/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "postgres" "service" .Values.services.postgres "root" $) }} diff --git a/helm/app/templates/service/postgres/service.yaml b/helm/app/templates/service/postgres/service.yaml new file mode 100644 index 0000000..55d5e98 --- /dev/null +++ b/helm/app/templates/service/postgres/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.postgres.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: postgres + 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 0000000..a861ffa --- /dev/null +++ b/helm/app/templates/service/rabbitmq/deployment.yaml @@ -0,0 +1,70 @@ +{{- with .Values.services.rabbitmq -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}rabbitmq + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: rabbitmq + app.service: {{ $.Values.resourcePrefix }}rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}rabbitmq +{{- if $.Values.persistence.rabbitmq.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: rabbitmq + app.service: {{ $.Values.resourcePrefix }}rabbitmq + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}rabbitmq + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: rabbitmq + ports: + - containerPort: 5672 + - containerPort: 15672 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/service/rabbitmq/pvc.yaml b/helm/app/templates/service/rabbitmq/pvc.yaml new file mode 100644 index 0000000..1b3f2a5 --- /dev/null +++ b/helm/app/templates/service/rabbitmq/pvc.yaml @@ -0,0 +1,32 @@ +{{ if and .Values.persistence.rabbitmq.enabled .Values.services.rabbitmq.enabled -}} + +{{- with .Values.persistence.rabbitmq -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}rabbitmq-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: rabbitmq + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..b75c848 --- /dev/null +++ b/helm/app/templates/service/rabbitmq/secret.yaml @@ -0,0 +1 @@ +{{ template "service.environment.secret" (dict "service_name" "rabbitmq" "service" .Values.services.rabbitmq "root" $) }} diff --git a/helm/app/templates/service/rabbitmq/service.yaml b/helm/app/templates/service/rabbitmq/service.yaml new file mode 100644 index 0000000..3452825 --- /dev/null +++ b/helm/app/templates/service/rabbitmq/service.yaml @@ -0,0 +1,22 @@ +{{ if .Values.services.rabbitmq.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: rabbitmq + 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 0000000..fba5b0f --- /dev/null +++ b/helm/app/templates/service/redis-session/deployment.yaml @@ -0,0 +1,74 @@ +{{- with index .Values.services "redis-session" -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}redis-session + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis-session + app.service: {{ $.Values.resourcePrefix }}redis-session +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}redis-session +{{- if index $.Values.persistence "redis-session" "enabled" }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: redis-session + 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: {{ .image | quote }} + imagePullPolicy: Always + name: redis-session + ports: + - containerPort: 6379 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 index $.Values.persistence "redis-session" "enabled" }} + persistentVolumeClaim: + claimName: {{ $.Values.resourcePrefix }}redis-session-pv-claim +{{- else }} + emptyDir: {} +{{- end }} +status: {} +{{- end }} +{{- 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 0000000..2496b3e --- /dev/null +++ b/helm/app/templates/service/redis-session/pvc.yaml @@ -0,0 +1,31 @@ +{{- with index .Values.persistence "redis-session" -}} +{{ if and .enabled (index $.Values.services "redis-session" "enabled") -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}redis-session-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis-session + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..5e55da1 --- /dev/null +++ b/helm/app/templates/service/redis-session/service.yaml @@ -0,0 +1,21 @@ +{{ with index .Values.services "redis-session" }} +{{ if .enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis-session + 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 }} +{{ end }} diff --git a/helm/app/templates/service/redis/deployment.yaml b/helm/app/templates/service/redis/deployment.yaml new file mode 100644 index 0000000..810f679 --- /dev/null +++ b/helm/app/templates/service/redis/deployment.yaml @@ -0,0 +1,75 @@ +{{- with .Values.services.redis -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}redis + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis + app.service: {{ $.Values.resourcePrefix }}redis +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}redis +{{- if $.Values.persistence.redis.enabled }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + creationTimestamp: null + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: redis + 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: {{ .image | quote }} + imagePullPolicy: Always + name: redis + ports: + - containerPort: 6379 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/templates/service/redis/pvc.yaml b/helm/app/templates/service/redis/pvc.yaml new file mode 100644 index 0000000..8ac0678 --- /dev/null +++ b/helm/app/templates/service/redis/pvc.yaml @@ -0,0 +1,32 @@ +{{ if and .Values.persistence.redis.enabled .Values.services.redis.enabled -}} + +{{- with .Values.persistence.redis -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ $.Values.resourcePrefix }}redis-pv-claim + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis + 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 }} +{{- with (pick . "selector" "volumeMode" "volumeName") }} + {{- . | toYaml | nindent 2 }} +{{- 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 0000000..d10c173 --- /dev/null +++ b/helm/app/templates/service/redis/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.redis.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: redis + 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 }} diff --git a/helm/app/templates/service/tideways/deployment.yaml b/helm/app/templates/service/tideways/deployment.yaml new file mode 100644 index 0000000..8a6a3af --- /dev/null +++ b/helm/app/templates/service/tideways/deployment.yaml @@ -0,0 +1,55 @@ +{{- with .Values.services.tideways -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $.Values.resourcePrefix }}tideways + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: tideways + app.service: {{ $.Values.resourcePrefix }}tideways +spec: + replicas: 1 + selector: + matchLabels: + app.service: {{ $.Values.resourcePrefix }}tideways + template: + metadata: + creationTimestamp: null + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: tideways + app.service: {{ $.Values.resourcePrefix }}tideways + spec: + containers: + - env: + {{- range $key, $value := (mergeOverwrite (dict) .environment .environment_dynamic) }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .environment_secrets }} + envFrom: + - secretRef: + name: {{ $.Values.resourcePrefix }}tideways + {{- end }} + image: {{ .image | quote }} + imagePullPolicy: Always + name: tideways + ports: + - containerPort: 9135 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + readinessProbe: + tcpSocket: + port: 9135 + initialDelaySeconds: 5 + periodSeconds: 10 + restartPolicy: Always +status: {} +{{- end }} +{{- end }} diff --git a/helm/app/templates/service/tideways/service.yaml b/helm/app/templates/service/tideways/service.yaml new file mode 100644 index 0000000..3cbffc0 --- /dev/null +++ b/helm/app/templates/service/tideways/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.tideways.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: tideways + app.service: {{ .Values.resourcePrefix }}tideways + name: {{ .Values.resourcePrefix }}tideways +spec: + ports: + - name: "9135" + port: 9135 + targetPort: 9135 + selector: + app.service: {{ .Values.resourcePrefix }}tideways +status: + loadBalancer: {} +{{ end }} 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 0000000..6f30974 --- /dev/null +++ b/helm/app/templates/service/varnish/headless-service.yaml @@ -0,0 +1,20 @@ +{{ if .Values.services.varnish.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: varnish + 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 0000000..c7523b2 --- /dev/null +++ b/helm/app/templates/service/varnish/service.yaml @@ -0,0 +1,19 @@ +{{ if .Values.services.varnish.enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: varnish + 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 0000000..ddd07fa --- /dev/null +++ b/helm/app/templates/service/varnish/statefulset.yaml @@ -0,0 +1,60 @@ +{{- with .Values.services.varnish -}} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ $.Values.resourcePrefix }}varnish + annotations: + argocd.argoproj.io/sync-wave: "4" + labels: + {{- include "chart.labels" $ | nindent 4 }} + app.kubernetes.io/component: varnish + 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 + annotations: + checksum/config: {{ include (print $.Template.BasePath "/service/varnish/configmap.yaml") $ | sha256sum }} + labels: + {{- include "chart.selectors" $ | nindent 8 }} + app.kubernetes.io/component: varnish + app.service: {{ $.Values.resourcePrefix }}varnish + spec: + containers: + - name: varnish + image: {{ .image | quote }} + imagePullPolicy: Always + ports: + - containerPort: 80 + resources: + limits: + memory: {{ .resources.memory }} + requests: + memory: {{ .resources.memory }} + 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 }} +{{- end }} diff --git a/helm/app/values-preview.yaml.twig b/helm/app/values-preview.yaml.twig new file mode 100644 index 0000000..f81529a --- /dev/null +++ b/helm/app/values-preview.yaml.twig @@ -0,0 +1,6 @@ +{% if @('pipeline.preview.services') %} +services: {{ to_nice_yaml(@('pipeline.preview.services'), 2, 2) | raw }} +{% endif %} +{% if @('pipeline.preview.persistence') %} +persistence: {{ to_nice_yaml(@('pipeline.preview.persistence'), 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 0000000..378f0b4 --- /dev/null +++ b/helm/app/values-production.yaml.twig @@ -0,0 +1,6 @@ +{% if @('pipeline.production.services') %} +services: {{ to_nice_yaml(@('pipeline.production.services'), 2, 2) | raw }} +{% endif %} +{% if @('pipeline.production.persistence') %} +persistence: {{ to_nice_yaml(@('pipeline.production.persistence'), 2, 2) | raw }} +{% endif %} diff --git a/helm/app/values.yaml.twig b/helm/app/values.yaml.twig new file mode 100644 index 0000000..7476e7f --- /dev/null +++ b/helm/app/values.yaml.twig @@ -0,0 +1,26 @@ +appVersion: {{ @('app.version') | json_encode | raw }} + +feature: {{ to_nice_yaml(@('helm.feature'), 2, 2) | raw }} + +ingress: {{ to_nice_yaml(@('pipeline.base.ingress'), 2, 2) | raw }} + +docker: + image_pull_config: {{ @('docker.image_pull_config') | raw }} + +services: {{ to_nice_yaml(deep_merge([ + filter_local_services(@('services')), + @('pipeline.base.services') + ]), 2, 2) | raw }} + +replicas: {{ to_nice_yaml(@('replicas'), 2, 2) | raw }} + +persistence: {{ to_nice_yaml(@('pipeline.base.persistence'), 2, 2) | raw }} + +prometheus: {{ to_nice_yaml(@('pipeline.base.prometheus'), 2, 2) | raw }} + +resourcePrefix: {{ @('pipeline.base.resourcePrefix') | json_encode | raw }} + +sealed_secrets: + scope: {{ @('helm.sealed_secrets.scope') | json_encode | raw }} + +istio: {{ to_nice_yaml(@('pipeline.base.istio'), 2, 2) | raw }} diff --git a/helm/qa/Chart.yaml.twig b/helm/qa/Chart.yaml.twig new file mode 100644 index 0000000..2afdc86 --- /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 0000000..16b0aa8 --- /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/helm/qa/values.yaml.twig b/helm/qa/values.yaml.twig new file mode 100644 index 0000000..88b2b79 --- /dev/null +++ b/helm/qa/values.yaml.twig @@ -0,0 +1,8 @@ +{{ @('workspace.name') }}: + resourcePrefix: {{ @('pipeline.qa.resourcePrefix') | json_encode | raw }} +{% if @('pipeline.qa.services') %} + services: {{ to_nice_yaml(@('pipeline.qa.services'), 2, 4) | raw }} +{% endif %} +{% if @('pipeline.qa.persistence') %} + persistence: {{ to_nice_yaml(@('pipeline.qa.persistence'), 2, 4) | raw }} +{% endif %} diff --git a/mutagen.yml.twig b/mutagen.yml.twig new file mode 100644 index 0000000..d09a48d --- /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