Skip to content

Commit

Permalink
publish 'magento1' harness
Browse files Browse the repository at this point in the history
andytson-inviqa committed Jan 10, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 4659233 commit 5ea3300
Showing 176 changed files with 6,448 additions and 0 deletions.
13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions _twig/docker-compose.yml/service/blackfire.yml.twig
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions _twig/docker-compose.yml/service/chrome.yml.twig
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions _twig/docker-compose.yml/service/console.yml.twig
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions _twig/docker-compose.yml/service/cron.yml.twig
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions _twig/docker-compose.yml/service/elasticsearch.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
12 changes: 12 additions & 0 deletions _twig/docker-compose.yml/service/lighthouse.yml.twig
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions _twig/docker-compose.yml/service/memcached.yml.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
memcached:
image: {{ @('services.memcached.image') }}
labels:
- traefik.enable=false
networks:
- private
15 changes: 15 additions & 0 deletions _twig/docker-compose.yml/service/mongodb.yml.twig
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions _twig/docker-compose.yml/service/mysql.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
42 changes: 42 additions & 0 deletions _twig/docker-compose.yml/service/nginx.yml.twig
Original file line number Diff line number Diff line change
@@ -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: {}
13 changes: 13 additions & 0 deletions _twig/docker-compose.yml/service/php-fpm-exporter.yml.twig
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions _twig/docker-compose.yml/service/php-fpm.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
15 changes: 15 additions & 0 deletions _twig/docker-compose.yml/service/postgres.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
17 changes: 17 additions & 0 deletions _twig/docker-compose.yml/service/rabbitmq.yml.twig
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions _twig/docker-compose.yml/service/redis-session.yml.twig
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions _twig/docker-compose.yml/service/redis.yml.twig
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions _twig/docker-compose.yml/service/relay.yml.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
relay:
build: .my127ws/docker/image/relay
labels:
- traefik.enable=false
networks:
private:
aliases:
- jaeger-relay
- mailhog-relay
shared: {}
34 changes: 34 additions & 0 deletions _twig/docker-compose.yml/service/solr.yml.twig
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions _twig/docker-compose.yml/service/tideways.yml.twig
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions _twig/docker-compose.yml/service/varnish.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
3 changes: 3 additions & 0 deletions _twig/docker-compose.yml/service/webapp.yml.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% set blocks = '_twig/docker-compose.yml/' %}
{% include blocks ~ 'service/nginx.yml.twig' %}
{% include blocks ~ 'service/php-fpm.yml.twig' %}
8 changes: 8 additions & 0 deletions application/overlay/.dockerignore.twig
Original file line number Diff line number Diff line change
@@ -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 %}
133 changes: 133 additions & 0 deletions application/overlay/Jenkinsfile.twig
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
2 changes: 2 additions & 0 deletions application/overlay/_twig/.dockerignore/dynamic.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.my127ws
1 change: 1 addition & 0 deletions application/overlay/_twig/.dockerignore/static.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# no need for exclusions for now
15 changes: 15 additions & 0 deletions application/overlay/auth.json.twig
Original file line number Diff line number Diff line change
@@ -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 %}
}
}
208 changes: 208 additions & 0 deletions application/skeleton/README.md.twig
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions application/skeleton/_twig/README.md/websites.md.twig
Original file line number Diff line number Diff line change
@@ -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 -%}
40 changes: 40 additions & 0 deletions docker-compose.yml.twig
Original file line number Diff line number Diff line change
@@ -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 %}
1 change: 1 addition & 0 deletions docker/image/console/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.twig
72 changes: 72 additions & 0 deletions docker/image/console/Dockerfile.twig
Original file line number Diff line number Diff line change
@@ -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"]
Empty file.
19 changes: 19 additions & 0 deletions docker/image/console/root/bin/app
Original file line number Diff line number Diff line change
@@ -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 "$@"
54 changes: 54 additions & 0 deletions docker/image/console/root/entrypoint.dynamic.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
51 changes: 51 additions & 0 deletions docker/image/console/root/entrypoint.sh.twig
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions docker/image/console/root/home/build/.my.cnf.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[client]
host=mysql
user=root
password={{ @('database.root_pass') }}
21 changes: 21 additions & 0 deletions docker/image/console/root/lib/functions.sh
Original file line number Diff line number Diff line change
@@ -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
}
124 changes: 124 additions & 0 deletions docker/image/console/root/lib/sidekick.sh
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions docker/image/console/root/lib/task/assets/apply.sh
Original file line number Diff line number Diff line change
@@ -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
}
24 changes: 24 additions & 0 deletions docker/image/console/root/lib/task/assets/dump.sh
Original file line number Diff line number Diff line change
@@ -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'"
}
13 changes: 13 additions & 0 deletions docker/image/console/root/lib/task/build.sh
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions docker/image/console/root/lib/task/build/backend.sh.twig
Original file line number Diff line number Diff line change
@@ -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 %}

)}
18 changes: 18 additions & 0 deletions docker/image/console/root/lib/task/build/frontend.sh.twig
Original file line number Diff line number Diff line change
@@ -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 %}

)}
7 changes: 7 additions & 0 deletions docker/image/console/root/lib/task/composer/autoload.sh
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions docker/image/console/root/lib/task/composer/install.sh.twig
Original file line number Diff line number Diff line change
@@ -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 %}
}
31 changes: 31 additions & 0 deletions docker/image/console/root/lib/task/database/available.sh
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions docker/image/console/root/lib/task/database/import.sh.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

function task_database_import()
{
:
{% for step in @('database.import.steps') -%}
{{ step|raw }}
{% else %}
:
{% endfor %}
}
19 changes: 19 additions & 0 deletions docker/image/console/root/lib/task/http/wait.sh
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions docker/image/console/root/lib/task/init.sh.twig
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions docker/image/console/root/lib/task/install.sh.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

function task_install()
{
task database:available

{% for step in @('backend.install.steps') -%}
{{ step|raw }}
{% endfor %}

}
10 changes: 10 additions & 0 deletions docker/image/console/root/lib/task/migrate.sh.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

function task_migrate()
{
task database:available

{% for step in @('backend.migrate.steps') -%}
{{ step|raw }}
{% endfor %}
}
6 changes: 6 additions & 0 deletions docker/image/console/root/lib/task/overlay/apply.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

function task_overlay_apply()
{
run rsync --exclude='*.twig' --exclude='_twig' -a /home/build/application/overlay/ /app/
}
25 changes: 25 additions & 0 deletions docker/image/console/root/lib/task/phpstan.sh
Original file line number Diff line number Diff line change
@@ -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"
)
15 changes: 15 additions & 0 deletions docker/image/console/root/lib/task/rabbitmq/vhosts.sh.twig
Original file line number Diff line number Diff line change
@@ -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 %}
}
6 changes: 6 additions & 0 deletions docker/image/console/root/lib/task/skeleton/apply.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

function task_skeleton_apply()
{
run rsync --exclude='*.twig' --exclude='_twig' -a /home/build/application/skeleton/ /app/
}
8 changes: 8 additions & 0 deletions docker/image/console/root/lib/task/state.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

function task_state()
{
task database:available

echo "Ready!"
}
5 changes: 5 additions & 0 deletions docker/image/console/root/usr/local/bin/send_mail
Original file line number Diff line number Diff line change
@@ -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" -- "$@"
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
10 changes: 10 additions & 0 deletions docker/image/console/root/usr/local/etc/php/php.ini.twig
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions docker/image/cron/Dockerfile.twig
Original file line number Diff line number Diff line change
@@ -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"]
5 changes: 5 additions & 0 deletions docker/image/cron/root/cron-run-with-env.sh
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions docker/image/cron/root/crontab.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for cronjob in @('backend.cron.jobs') -%}
{{ cronjob|raw }}
{% endfor %}
56 changes: 56 additions & 0 deletions docker/image/cron/root/entrypoint.sh.twig
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions docker/image/cron/root/usr/local/etc/php/php.ini.twig
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions docker/image/lighthouse/Dockerfile.twig
Original file line number Diff line number Diff line change
@@ -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"]
88 changes: 88 additions & 0 deletions docker/image/lighthouse/root/app/run.sh.twig
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions docker/image/nginx/Dockerfile.twig
Original file line number Diff line number Diff line change
@@ -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;"]
15 changes: 15 additions & 0 deletions docker/image/nginx/root/docker-entrypoint.d/config_render.sh.twig
Original file line number Diff line number Diff line change
@@ -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 "$@"
3 changes: 3 additions & 0 deletions docker/image/nginx/root/etc/nginx/conf.d/0-nginx.conf.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for name, value in @('nginx.global.conf') %}
{{ name }} {{ value }};
{% endfor %}
2 changes: 2 additions & 0 deletions docker/image/nginx/root/etc/nginx/snippets/certificate.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ssl_certificate /etc/ssl/certs/app.crt;
ssl_certificate_key /etc/ssl/private/app.key;
17 changes: 17 additions & 0 deletions docker/image/nginx/root/etc/nginx/snippets/ssl-params.conf
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions docker/image/nginx/root/etc/ssl/certs/app.crt.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ @('tls.crt') }}
1 change: 1 addition & 0 deletions docker/image/nginx/root/etc/ssl/private/app.key.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ @('tls.key') }}
1 change: 1 addition & 0 deletions docker/image/php-fpm/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.twig
41 changes: 41 additions & 0 deletions docker/image/php-fpm/Dockerfile.twig
Original file line number Diff line number Diff line change
@@ -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 %}
Empty file.
69 changes: 69 additions & 0 deletions docker/image/php-fpm/root/entrypoint.dynamic.sh
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions docker/image/php-fpm/root/entrypoint.sh.twig
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions docker/image/php-fpm/root/etc/supervisor/supervisord.conf
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions docker/image/php-fpm/root/fix_app_permissions.sh.twig
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions docker/image/php-fpm/root/usr/local/bin/send_mail
Original file line number Diff line number Diff line change
@@ -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" -- "$@"
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
10 changes: 10 additions & 0 deletions docker/image/php-fpm/root/usr/local/etc/php/php.ini.twig
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions docker/image/relay/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM nginx:1.21-alpine
COPY root /
62 changes: 62 additions & 0 deletions docker/image/relay/root/etc/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 2 additions & 0 deletions docker/image/tls-offload/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM nginx:1.21-alpine
COPY root /
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for name, value in @('nginx.global.conf') %}
{{ name }} {{ value }};
{% endfor %}
47 changes: 47 additions & 0 deletions docker/image/tls-offload/root/etc/nginx/conf.d/default.conf.twig
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ssl_certificate /etc/ssl/certs/app.crt;
ssl_certificate_key /etc/ssl/private/app.key;
17 changes: 17 additions & 0 deletions docker/image/tls-offload/root/etc/nginx/snippets/ssl-params.conf
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions docker/image/tls-offload/root/etc/ssl/certs/app.crt.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ @('tls.crt') }}
1 change: 1 addition & 0 deletions docker/image/tls-offload/root/etc/ssl/private/app.key.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ @('tls.key') }}
41 changes: 41 additions & 0 deletions docker/image/varnish/root/etc/varnish/default.vcl.twig
Original file line number Diff line number Diff line change
@@ -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.
}
Empty file added docs/.gitkeep
Empty file.
472 changes: 472 additions & 0 deletions harness/attributes/common.yml

Large diffs are not rendered by default.

284 changes: 284 additions & 0 deletions harness/attributes/docker-base.yml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 6 additions & 0 deletions harness/attributes/environment/local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

attributes:
app:
build: dynamic
mode: development
version: develop
16 changes: 16 additions & 0 deletions harness/attributes/environment/pipeline.yml
Original file line number Diff line number Diff line change
@@ -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"
27 changes: 27 additions & 0 deletions harness/config/cleanup.yml
Original file line number Diff line number Diff line change
@@ -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"

317 changes: 317 additions & 0 deletions harness/config/commands.yml
Original file line number Diff line number Diff line change
@@ -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 <service>'):
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 <attribute> <value>'):
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 <server_key>'):
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 <database_file>'):
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 <length>'):
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
15 changes: 15 additions & 0 deletions harness/config/events.yml
Original file line number Diff line number Diff line change
@@ -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
101 changes: 101 additions & 0 deletions harness/config/external-images.yml
Original file line number Diff line number Diff line change
@@ -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] [<service>]'):
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 [<service>]'):
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
Loading

0 comments on commit 5ea3300

Please sign in to comment.