orchestration 0.5.14 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -28
- data/config/locales/en.yml +11 -15
- data/lib/orchestration.rb +4 -0
- data/lib/orchestration/docker_compose/database_service.rb +1 -1
- data/lib/orchestration/docker_compose/install_generator.rb +3 -3
- data/lib/orchestration/install_generator.rb +2 -28
- data/lib/orchestration/make.rb +4 -0
- data/lib/orchestration/make/orchestration.mk +503 -0
- data/lib/orchestration/service_check.rb +23 -37
- data/lib/orchestration/services/database/adapters.rb +1 -0
- data/lib/orchestration/services/database/adapters/adapter_base.rb +21 -0
- data/lib/orchestration/services/database/adapters/mysql2.rb +2 -0
- data/lib/orchestration/services/database/adapters/postgresql.rb +2 -0
- data/lib/orchestration/services/database/adapters/sqlite3.rb +2 -0
- data/lib/orchestration/services/database/configuration.rb +5 -1
- data/lib/orchestration/services/mixins/configuration_base.rb +1 -1
- data/lib/orchestration/templates/Dockerfile.erb +2 -6
- data/lib/orchestration/templates/application.mk.erb +23 -7
- data/lib/orchestration/terminal.rb +4 -3
- data/lib/orchestration/version.rb +1 -1
- data/lib/tasks/orchestration.rake +17 -6
- data/orchestration.gemspec +1 -0
- metadata +19 -4
- data/lib/orchestration/templates/makefile_macros.mk.erb +0 -112
- data/lib/orchestration/templates/orchestration.mk.erb +0 -391
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 219067059e72b472f7697df1659e31d7d25b3f531a487ef0270949c95e427238
|
4
|
+
data.tar.gz: '079a57d5c7a6a3b59eefbd2e4b6be1b1630686d4c447b97a137c8a967f2c51a6'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e277c0fac7a4f73254c8d011eb3a47b40bd92385b3348a21a345ab8188f246ebacd6f031edfb53fc18ba1d815c45540fd9b4c31fbaf44aa555c52fb6852f4159
|
7
|
+
data.tar.gz: 2ecf615a685df475723c4aef1bb86d60d7e400891a79af22941b2d5dd08f356db461492aa1fd092528e907ca4cc51ff8cb1a55c2d226dc51b99168a8f30fba50
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ At its core _Orchestration_ is simply a `Makefile` and a set of `docker-compose.
|
|
7
7
|
A typical _Rails_ application can be tested, built, pushed to _Docker Hub_, and deployed to _Docker Swarm_ with the following commands:
|
8
8
|
|
9
9
|
```bash
|
10
|
-
make test build push
|
10
|
+
make setup test build push
|
11
11
|
make deploy manager=user@swarm.example.com env_file=/var/configs/myapp.env
|
12
12
|
```
|
13
13
|
|
@@ -27,7 +27,7 @@ The below screenshot demonstrates _Orchestration_ being installed in a brand new
|
|
27
27
|
Add _Orchestration_ to your Gemfile:
|
28
28
|
|
29
29
|
```ruby
|
30
|
-
gem 'orchestration', '~> 0.
|
30
|
+
gem 'orchestration', '~> 0.6.0'
|
31
31
|
```
|
32
32
|
|
33
33
|
Install:
|
@@ -83,7 +83,7 @@ All `make` commands provided by _Orchestration_ (with the exception of `test` an
|
|
83
83
|
e.g.:
|
84
84
|
```
|
85
85
|
# Stop all test containers
|
86
|
-
make stop
|
86
|
+
make stop RAILS_ENV=test
|
87
87
|
```
|
88
88
|
|
89
89
|
The default value for `env` is `development`.
|
@@ -91,7 +91,7 @@ The default value for `env` is `development`.
|
|
91
91
|
As with any `Makefile` targets can be chained together, e.g.:
|
92
92
|
```
|
93
93
|
# Run tests, build, and push image
|
94
|
-
make test build push
|
94
|
+
make setup test build push
|
95
95
|
```
|
96
96
|
|
97
97
|
### Containers
|
@@ -113,7 +113,7 @@ make stop
|
|
113
113
|
#### Interface directly with `docker-compose`
|
114
114
|
|
115
115
|
```bash
|
116
|
-
$(make compose
|
116
|
+
$(make compose RAILS_ENV=test) logs -f database
|
117
117
|
```
|
118
118
|
|
119
119
|
### Images
|
@@ -192,21 +192,15 @@ A default `test` target is provided in your application's main `Makefile`. You a
|
|
192
192
|
|
193
193
|
To launch all dependency containers, run database migrations, and run tests:
|
194
194
|
```bash
|
195
|
-
make test
|
195
|
+
make setup test
|
196
196
|
```
|
197
197
|
|
198
198
|
The default `test` command can (and should) be extended. This command is defined in the root `Makefile` in the project and, by defaults, runs `rspec` and `rubocop`.
|
199
199
|
|
200
|
-
To
|
201
|
-
|
202
|
-
```bash
|
203
|
-
make test light=1
|
204
|
-
```
|
200
|
+
To skip the setup step and just run tests (i.e. once test containers are up and running and ready for use) simply run:
|
205
201
|
|
206
|
-
If you prefer to run tests manually (e.g. if you want to run tests for a specific file) then the `test-setup` target can be used:
|
207
202
|
```bash
|
208
|
-
make test
|
209
|
-
bundle exec rspec spec/my_class_spec.rb
|
203
|
+
make test
|
210
204
|
```
|
211
205
|
|
212
206
|
Note that _Orchestration_ will wait for all services to become fully available (i.e. running and providing valid responses) before attempting to run tests. This is specifically intended to facilitate testing in continuous integration environments.
|
@@ -220,22 +214,29 @@ ORCHESTRATION_RETRY_LIMIT # default: 15
|
|
220
214
|
ORCHESTRATION_RETRY_INTERVAL # default: 10 [seconds]
|
221
215
|
```
|
222
216
|
|
223
|
-
### (Local)
|
217
|
+
### (Local) Deployment
|
224
218
|
|
225
|
-
Run a
|
219
|
+
Run a deployment environment locally to simulate your deployment platform:
|
226
220
|
|
227
|
-
```
|
228
|
-
make
|
221
|
+
```bash
|
222
|
+
make deploy manager=localhost
|
229
223
|
```
|
230
224
|
|
225
|
+
Ensure you have passwordless _SSH_ access to your own workstation and that you have approved the host authenticity/fingerprint.
|
226
|
+
|
231
227
|
#### Deploy to a remote swarm
|
232
228
|
|
233
229
|
To connect via _SSH_ to a remote swarm and deploy, pass the `manager` parameter:
|
234
|
-
```
|
230
|
+
```bash
|
235
231
|
make deploy manager=user@manager.swarm.example.com
|
236
232
|
```
|
237
233
|
|
238
|
-
The file `orchestration/docker-compose.
|
234
|
+
The file `orchestration/docker-compose.deployment.yml` is created automatically. This file will be used for all deployments, regardless of _Rails_ environment. Other environments should be configured using a separate [`.env` file](#env-file) for each environment. i.e. to deploy a staging environment, create a `staging.env` (for example), set `RAILS_ENV=staging` and run:
|
235
|
+
```bash
|
236
|
+
make deploy manager=user@manager.swarm.example.com env_file=staging.env
|
237
|
+
```
|
238
|
+
|
239
|
+
This way you can set different publish ports and other application configuration variables for each stage you want to deploy to.
|
239
240
|
|
240
241
|
#### Roll back a deployment
|
241
242
|
|
@@ -254,11 +255,11 @@ The `project_name` parameter is also supported.
|
|
254
255
|
|
255
256
|
#### Use a custom stack name
|
256
257
|
|
257
|
-
The [_Docker_ stack](https://docs.docker.com/engine/reference/commandline/stack/) name defaults to the name of your repository (as defined in `.orchesration.yml`) and the _Rails_ environment, e.g. `
|
258
|
+
The [_Docker_ stack](https://docs.docker.com/engine/reference/commandline/stack/) name defaults to the name of your repository (as defined in `.orchesration.yml`) and the _Rails_ environment, e.g. `anvil_staging`.
|
258
259
|
|
259
260
|
To override this default, pass the `project_name` parameter:
|
260
261
|
```
|
261
|
-
make deploy project_name=
|
262
|
+
make deploy project_name=custom_stack_name
|
262
263
|
```
|
263
264
|
|
264
265
|
This variable will also be available as `COMPOSE_PROJECT_NAME` for use within your `docker-compose.yml`. e.g. to explicitly name a network after the project name:
|
@@ -300,7 +301,7 @@ tail -f log/orchestration*.log
|
|
300
301
|
A convenience `Makefile` target `dump` is provided. The following command will output all consumed _stdout_, _stderr_, and _Docker Compose_ container logs for the test environment:
|
301
302
|
|
302
303
|
```bash
|
303
|
-
make dump
|
304
|
+
make dump RAILS_ENV=test
|
304
305
|
```
|
305
306
|
|
306
307
|
All commands also support the `verbose` flag which will output all logs immediately to the console:
|
@@ -327,7 +328,7 @@ See related documentation:
|
|
327
328
|
|
328
329
|
## Healthchecks
|
329
330
|
|
330
|
-
[Healthchecks](https://docs.docker.com/engine/reference/builder/#healthcheck) are automatically configured for your application. A healthcheck utility is provided in `orchestration/healthcheck.rb`. The following environment variables can be configured (in the `app` service of `orchestration/docker-compose.
|
331
|
+
[Healthchecks](https://docs.docker.com/engine/reference/builder/#healthcheck) are automatically configured for your application. A healthcheck utility is provided in `orchestration/healthcheck.rb`. The following environment variables can be configured (in the `app` service of `orchestration/docker-compose.deployment.yml`):
|
331
332
|
|
332
333
|
| Variable | Meaning | Default Value |
|
333
334
|
|-|-|-|
|
@@ -355,7 +356,7 @@ Real-world applications will inevitably need to make changes to this file. As wi
|
|
355
356
|
|
356
357
|
## Entrypoint
|
357
358
|
|
358
|
-
An [entrypoint](https://docs.docker.com/engine/reference/builder/#entrypoint) script for your application is provided which does the following:
|
359
|
+
An [entrypoint](https://docs.docker.com/engine/reference/builder/#entrypoint) script for your application is provided at `orchestration/entrypoint.sh` which does the following:
|
359
360
|
|
360
361
|
* Runs the `CMD` process as the same system user that launched the container (rather than the default `root` user);
|
361
362
|
* Creates various required temporary directories and removes stale `pid` files;
|
@@ -370,7 +371,7 @@ If you need to start dependency services (databases, etc.) and connect to them f
|
|
370
371
|
To do this automatically, pass the `sidecar` parameter to the `start` or `test` targets:
|
371
372
|
|
372
373
|
```bash
|
373
|
-
make test sidecar=1
|
374
|
+
make setup test sidecar=1
|
374
375
|
```
|
375
376
|
|
376
377
|
When running in sidecar mode container-to-container networking is used so there is no benefit to binding dependency containers to a specific port on the host machine (only the target port will be used). For this reason a random, ephemeral port (chosen by _Docker_) will be used to allow multiple instances of each dependency to run alongside one another.
|
@@ -381,10 +382,10 @@ Note that a temporary file `orchestration/.sidecar` containing the random projec
|
|
381
382
|
|
382
383
|
```bash
|
383
384
|
# Start dependencies and run tests in sidecar mode
|
384
|
-
make test sidecar=1
|
385
|
+
make setup test sidecar=1
|
385
386
|
|
386
387
|
# Stop test dependencies in sidecar mode
|
387
|
-
make stop
|
388
|
+
make stop RAILS_ENV=test
|
388
389
|
```
|
389
390
|
|
390
391
|
<a name="rabbitmq-configuration"></a>
|
data/config/locales/en.yml
CHANGED
@@ -1,33 +1,21 @@
|
|
1
1
|
en:
|
2
2
|
orchestration:
|
3
|
-
attempt_limit: "Unable to
|
3
|
+
attempt_limit: "Unable to connect after %{limit} attempts. Aborting."
|
4
4
|
default: "default"
|
5
5
|
auto_update: "Orchestration Makefile was automatically updated to the latest version."
|
6
|
+
service:
|
7
|
+
ready: "%{service} is ready"
|
6
8
|
|
7
9
|
app:
|
8
|
-
waiting: "Waiting for app: %{config}"
|
9
|
-
ready: "App is ready."
|
10
10
|
connection_error: "Error attempting to connect to app: received status code %{code}"
|
11
11
|
|
12
12
|
database:
|
13
|
-
waiting: "Waiting for database: %{config}"
|
14
|
-
ready: "Database is ready."
|
15
13
|
unknown_environment: "Environment not defined in database configuration: %{environment}"
|
16
14
|
unknown_adapter: "Unable to determine adapter from database.yml: %{adapter}"
|
17
15
|
|
18
16
|
mongo:
|
19
|
-
waiting: "Waiting for Mongo: %{config}"
|
20
|
-
ready: "Mongo is ready."
|
21
17
|
bad_config: "Unable to parse Mongo config: %{path}. Expected section for one of: %{expected}"
|
22
18
|
|
23
|
-
rabbitmq:
|
24
|
-
waiting: "Waiting for RabbitMQ: %{config}"
|
25
|
-
ready: "RabbitMQ is ready."
|
26
|
-
|
27
|
-
custom_service:
|
28
|
-
waiting: "Waiting for [%{service}]: %{config}"
|
29
|
-
ready: "[%{service}] is ready."
|
30
|
-
|
31
19
|
settings:
|
32
20
|
docker:
|
33
21
|
organization:
|
@@ -44,3 +32,11 @@ en:
|
|
44
32
|
install: "Install Orchestration tools"
|
45
33
|
install_makefile: "(Re)create orchestration/Makefile"
|
46
34
|
wait: "Wait for development/test dependencies to be available"
|
35
|
+
db:
|
36
|
+
url: "Return the database URL for the current environment (RAILS_ENV)"
|
37
|
+
console: "Launch a database console for the current environment (RAILS_ENV)"
|
38
|
+
|
39
|
+
dbconsole:
|
40
|
+
postgresql: "PGPASSWORD='%<password>s' psql --username=%<username>s --host=%<host>s --port=%<port>s --dbname=%<database>s"
|
41
|
+
sqlite3: "sqlite3 %<database>s"
|
42
|
+
mysql2: "mysql --user=%<username>s --port=%<port>s --host=%<host>s --password=%<password>s --no-auto-rehash %<database>s"
|
data/lib/orchestration.rb
CHANGED
@@ -18,8 +18,8 @@ module Orchestration
|
|
18
18
|
create_compose_file(:development)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
create_compose_file(:
|
21
|
+
def docker_compose_deployment_yml
|
22
|
+
create_compose_file(:deployment)
|
23
23
|
end
|
24
24
|
|
25
25
|
def enabled_services(environment)
|
@@ -68,7 +68,7 @@ module Orchestration
|
|
68
68
|
case environment
|
69
69
|
when :test, :development
|
70
70
|
%i[database mongo rabbitmq]
|
71
|
-
when :
|
71
|
+
when :deployment
|
72
72
|
%i[app database mongo rabbitmq]
|
73
73
|
when :local, nil
|
74
74
|
[]
|
@@ -27,32 +27,12 @@ module Orchestration
|
|
27
27
|
@terminal.write(:skip, relpath)
|
28
28
|
end
|
29
29
|
|
30
|
-
def verify_makefile(skip: true)
|
31
|
-
# Only run when called explicitly [from Rake tasks].
|
32
|
-
# (I know this is hacky).
|
33
|
-
return if skip
|
34
|
-
|
35
|
-
content = template('orchestration.mk', makefile_environment)
|
36
|
-
path = @env.orchestration_root.join('Makefile')
|
37
|
-
return if path.exist? && content == File.read(path)
|
38
|
-
|
39
|
-
write_file(path, content)
|
40
|
-
@terminal.write(:update, 'Makefile')
|
41
|
-
@terminal.write(:status, t(:auto_update))
|
42
|
-
end
|
43
|
-
|
44
30
|
def application_makefile
|
45
31
|
path = @env.root.join('Makefile')
|
46
32
|
simple_copy('application.mk', path) unless File.exist?(path)
|
47
33
|
inject_if_missing(path, 'include orchestration/Makefile')
|
48
34
|
end
|
49
35
|
|
50
|
-
def orchestration_makefile
|
51
|
-
content = template('orchestration.mk', makefile_environment)
|
52
|
-
path = @env.orchestration_root.join('Makefile')
|
53
|
-
path.exist? ? update_file(path, content) : create_file(path, content)
|
54
|
-
end
|
55
|
-
|
56
36
|
def dockerfile
|
57
37
|
create_file(
|
58
38
|
orchestration_dir.join('Dockerfile'),
|
@@ -71,7 +51,7 @@ module Orchestration
|
|
71
51
|
def docker_compose
|
72
52
|
@docker_compose.docker_compose_test_yml
|
73
53
|
@docker_compose.docker_compose_development_yml
|
74
|
-
@docker_compose.
|
54
|
+
@docker_compose.docker_compose_deployment_yml
|
75
55
|
end
|
76
56
|
|
77
57
|
def puma
|
@@ -133,14 +113,8 @@ module Orchestration
|
|
133
113
|
I18n.t("orchestration.#{key}")
|
134
114
|
end
|
135
115
|
|
136
|
-
def makefile_environment
|
137
|
-
macros = template('makefile_macros.mk', env: @env)
|
138
|
-
|
139
|
-
{ env: @env, services: enabled_services, macros: macros }
|
140
|
-
end
|
141
|
-
|
142
116
|
def enabled_services
|
143
|
-
%i[test development
|
117
|
+
%i[test development deployment].map do |environment|
|
144
118
|
@docker_compose.enabled_services(environment)
|
145
119
|
end.flatten.uniq
|
146
120
|
end
|
@@ -0,0 +1,503 @@
|
|
1
|
+
|
2
|
+
### Environment setup ###
|
3
|
+
SHELL:=/bin/bash
|
4
|
+
MAKE:=mkpath=${mkpath} make --no-print-directory
|
5
|
+
|
6
|
+
TERM ?= 'dumb'
|
7
|
+
pwd:=$(shell pwd)
|
8
|
+
|
9
|
+
orchestration_dir_name=orchestration
|
10
|
+
orchestration_dir=orchestration
|
11
|
+
|
12
|
+
ifdef env_file
|
13
|
+
custom_env_file ?= 1
|
14
|
+
else
|
15
|
+
custom_env_file ?= 0
|
16
|
+
endif
|
17
|
+
|
18
|
+
ifneq (,$(wildcard ${pwd}/config/database.yml))
|
19
|
+
database_enabled = 1
|
20
|
+
else
|
21
|
+
database_enabled = 0
|
22
|
+
endif
|
23
|
+
|
24
|
+
red:=$(shell tput setaf 1)
|
25
|
+
green:=$(shell tput setaf 2)
|
26
|
+
yellow:=$(shell tput setaf 3)
|
27
|
+
blue:=$(shell tput setaf 4)
|
28
|
+
magenta:=$(shell tput setaf 5)
|
29
|
+
cyan:=$(shell tput setaf 6)
|
30
|
+
gray:=$(shell tput setaf 7)
|
31
|
+
reset:=$(shell tput sgr0)
|
32
|
+
tick=[${green}✓${reset}]
|
33
|
+
cross=[${red}✘${reset}]
|
34
|
+
|
35
|
+
make=$(MAKE) $1
|
36
|
+
orchestration_config_filename:=.orchestration.yml
|
37
|
+
orchestration_config:=${pwd}/${orchestration_config_filename}
|
38
|
+
system_prefix=${reset}[${cyan}exec${reset}]
|
39
|
+
warn_prefix=${reset}[${yellow}warn${reset}]
|
40
|
+
echo_prefix=${reset}[${blue}info${reset}]
|
41
|
+
fail_prefix=${reset}[${red}fail${reset}]
|
42
|
+
logs_prefix=${reset}[${green}logs${reset}]
|
43
|
+
system=echo '${system_prefix} ${cyan}$1${reset}'
|
44
|
+
warn=echo '${warn_prefix} ${reset}$1${reset}'
|
45
|
+
echo=echo '${echo_prefix} ${reset}$1${reset}'
|
46
|
+
fail=echo '${fail_prefix} ${reset}$1${reset}'
|
47
|
+
logs=echo '${logs_prefix} ${reset}$1${reset}'
|
48
|
+
print_error=printf '${red}\#${reset} '$1 | tee '${stderr}'
|
49
|
+
println_error=$(call print_error,$1'\n')
|
50
|
+
print=printf '${blue}\#${reset} '$1
|
51
|
+
println=$(call print,$1'\n')
|
52
|
+
printraw=printf $1
|
53
|
+
stdout=${pwd}/log/orchestration.stdout.log
|
54
|
+
stderr=${pwd}/log/orchestration.stderr.log
|
55
|
+
log_path_length=$(shell echo "${stdout}" | wc -c)
|
56
|
+
ifndef verbose
|
57
|
+
log_tee:= 2>&1 | tee -a ${stdout}
|
58
|
+
log:= >>${stdout} 2>>${stderr}
|
59
|
+
progress_point:=perl -e 'printf("[${magenta}busy${reset}] "); while( my $$line = <STDIN> ) { printf("."); select()->flush(); }'
|
60
|
+
log_progress:= > >(tee -ai ${stdout} >&1 | ${progress_point}) 2> >(tee -ai ${stderr} 2>&1 | ${progress_point})
|
61
|
+
endif
|
62
|
+
hr=$(call println,"$1$(shell head -c ${log_path_length} < /dev/zero | tr '\0' '=')${reset}")
|
63
|
+
managed_env_tag:=\# -|- ORCHESTRATION
|
64
|
+
standard_env_path:=${pwd}/.env
|
65
|
+
backup_env_path:=${pwd}/.env.orchestration.backup
|
66
|
+
is_managed_env:=$$(test -f '${standard_env_path}' && tail -n 1 '${standard_env_path}') == "${managed_env_tag}"*
|
67
|
+
token:=$(shell cat /dev/urandom | LC_CTYPE=C tr -dc 'a-z0-9' | fold -w8 | head -n1)
|
68
|
+
back_up_env:=( \
|
69
|
+
[ ! -f '${standard_env_path}' ] \
|
70
|
+
|| \
|
71
|
+
( \
|
72
|
+
[ -f '${standard_env_path}' ] \
|
73
|
+
&& cp '${standard_env_path}' '${backup_env_path}' \
|
74
|
+
) \
|
75
|
+
)
|
76
|
+
|
77
|
+
key_chars:=[a-zA-Z0-9_]
|
78
|
+
censored:=**********
|
79
|
+
censor=s/\(^${key_chars}*$(1)${key_chars}*\)=\(.*\)$$/\1=${censored}/
|
80
|
+
censor_urls:=s|\([a-zA-Z0-9_+]\+://.*:\).*\(@.*\)$$|\1${censored}\2|
|
81
|
+
format_env:=sed '$(call censor,SECRET); \
|
82
|
+
$(call censor,TOKEN); \
|
83
|
+
$(call censor,PRIVATE); \
|
84
|
+
$(call censor,KEY); \
|
85
|
+
$(censor_urls); \
|
86
|
+
/^\s*$$/d; \
|
87
|
+
/^\s*\#/d; \
|
88
|
+
s/\(^[a-zA-Z0-9_]\+\)=/${blue}\1${reset}=/; \
|
89
|
+
s/^/ /; \
|
90
|
+
s/=\(.*\)$$/=${yellow}\1${reset}/' | \
|
91
|
+
sort
|
92
|
+
|
93
|
+
exit_fail=( \
|
94
|
+
$(call printraw,' ${cross}') ; \
|
95
|
+
$(call make,dump src_cmd=$(MAKECMDGOALS)) ; \
|
96
|
+
echo ; \
|
97
|
+
$(call println,'Failed. ${cross}') ; \
|
98
|
+
exit 1 \
|
99
|
+
)
|
100
|
+
|
101
|
+
ifdef env_file
|
102
|
+
-include ${env_file}
|
103
|
+
else
|
104
|
+
ifneq (${env},test)
|
105
|
+
ifeq (,$(findstring test,$(MAKECMDGOALS)))
|
106
|
+
-include .env
|
107
|
+
endif
|
108
|
+
endif
|
109
|
+
endif
|
110
|
+
|
111
|
+
export
|
112
|
+
|
113
|
+
ifneq (,$(findstring test,$(MAKECMDGOALS)))
|
114
|
+
env=test
|
115
|
+
endif
|
116
|
+
|
117
|
+
ifneq (,$(env))
|
118
|
+
# `env` set by current shell.
|
119
|
+
else ifneq (,$(RAILS_ENV))
|
120
|
+
env=$(RAILS_ENV)
|
121
|
+
else ifneq (,$(RACK_ENV))
|
122
|
+
env=$(RACK_ENV)
|
123
|
+
else
|
124
|
+
env=development
|
125
|
+
endif
|
126
|
+
|
127
|
+
env_human=${gray}${env}${reset}
|
128
|
+
DOCKER_TAG ?= latest
|
129
|
+
|
130
|
+
ifneq (,$(wildcard ./Gemfile))
|
131
|
+
bundle_cmd = bundle exec
|
132
|
+
endif
|
133
|
+
rake=DEVPACK_DISABLE=1 RACK_ENV=${env} RAILS_ENV=${env} ${bundle_cmd} rake
|
134
|
+
|
135
|
+
ifneq (,$(wildcard ${env_file}))
|
136
|
+
rake_cmd:=${rake}
|
137
|
+
rake=. ${env_file} && ${rake_cmd}
|
138
|
+
endif
|
139
|
+
|
140
|
+
docker_config:=$(shell DEVPACK_DISABLE=1 RAILS_ENV=development ${bundle_cmd} rake orchestration:config 2>/dev/null || echo no-org no-repo)
|
141
|
+
docker_organization=$(word 1,$(docker_config))
|
142
|
+
docker_repository=$(word 2,$(docker_config))
|
143
|
+
|
144
|
+
ifeq (,$(project_name))
|
145
|
+
project_base = ${docker_repository}_${env}
|
146
|
+
else
|
147
|
+
project_base := $(project_name)
|
148
|
+
endif
|
149
|
+
|
150
|
+
ifeq (,$(findstring deploy,$(MAKECMDGOALS)))
|
151
|
+
sidecar_suffix := $(shell test -f ${orchestration_dir}/.sidecar && cat ${orchestration_dir}/.sidecar)
|
152
|
+
ifneq (,${sidecar_suffix})
|
153
|
+
sidecar := 1
|
154
|
+
endif
|
155
|
+
|
156
|
+
ifdef sidecar
|
157
|
+
# Set the variable to an empty string so that "#{sidecar-1234}" will
|
158
|
+
# evaluate to "1234" in port mappings.
|
159
|
+
sidecar_compose = sidecar=''
|
160
|
+
ifeq (,${sidecar_suffix})
|
161
|
+
sidecar_suffix := $(call token)
|
162
|
+
_ignore := $(shell echo ${sidecar_suffix} > ${orchestration_dir}/.sidecar)
|
163
|
+
endif
|
164
|
+
|
165
|
+
ifeq (,${sidecar_suffix})
|
166
|
+
$(warning Unable to generate project suffix; project name collisions may occur.)
|
167
|
+
endif
|
168
|
+
compose_project_name = ${project_base}_${sidecar_suffix}
|
169
|
+
else
|
170
|
+
compose_project_name = ${project_base}
|
171
|
+
endif
|
172
|
+
else
|
173
|
+
compose_project_name = ${project_base}
|
174
|
+
endif
|
175
|
+
|
176
|
+
compose_base=env -i \
|
177
|
+
PATH=$(PATH) \
|
178
|
+
HOST_UID=$(shell id -u) \
|
179
|
+
DOCKER_ORGANIZATION="${docker_organization}" \
|
180
|
+
DOCKER_REPOSITORY="${docker_repository}" \
|
181
|
+
COMPOSE_PROJECT_NAME="${compose_project_name}" \
|
182
|
+
${sidecar_compose} \
|
183
|
+
docker-compose \
|
184
|
+
-f ${orchestration_dir}/docker-compose.${env}.yml
|
185
|
+
|
186
|
+
git_branch ?= $(if $(branch),$(branch),$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo no-branch))
|
187
|
+
ifndef dev
|
188
|
+
git_version ?= $(shell git rev-parse --short --verify ${git_branch} 2>/dev/null || echo no-version)
|
189
|
+
else
|
190
|
+
git_version = dev
|
191
|
+
endif
|
192
|
+
|
193
|
+
docker_image=${docker_organization}/${docker_repository}:${git_version}
|
194
|
+
|
195
|
+
compose=${compose_base}
|
196
|
+
compose_human=docker-compose -f ${orchestration_dir_name}/docker-compose.${env}.yml
|
197
|
+
random_str=cat /dev/urandom | LC_ALL=C tr -dc 'a-z' | head -c $1
|
198
|
+
|
199
|
+
ifneq (,$(wildcard ${orchestration_dir}/docker-compose.local.yml))
|
200
|
+
compose:=${compose} -f ${orchestration_dir}/docker-compose.local.yml
|
201
|
+
endif
|
202
|
+
|
203
|
+
all: build
|
204
|
+
|
205
|
+
### Container management commands ###
|
206
|
+
|
207
|
+
.PHONY: start
|
208
|
+
ifndef network
|
209
|
+
start: network := ${compose_project_name}
|
210
|
+
endif
|
211
|
+
start: _create-log-directory _clean-logs
|
212
|
+
@$(call system,${compose_human} up --detach)
|
213
|
+
ifeq (${env},$(filter ${env},test development))
|
214
|
+
@${compose} up --detach --force-recreate --renew-anon-volumes --remove-orphans ${services} ${log} || ${exit_fail}
|
215
|
+
@[ -n '${sidecar}' ] && \
|
216
|
+
( \
|
217
|
+
$(call echo,(joining dependency network ${cyan}${network}${reset})) ; \
|
218
|
+
$(call system,docker network connect "${network}") ; \
|
219
|
+
docker network connect '${network}' '$(shell hostname)' ${log} \
|
220
|
+
|| ( \
|
221
|
+
$(call warn,Unable to join network: "${cyan}${network}${reset}". Container will not be able to connect to dependency services) ; \
|
222
|
+
$(call echo,Try deleting "${cyan}orchestration/.sidecar${reset}" if you do not want to use sidecar mode) ; \
|
223
|
+
) \
|
224
|
+
) \
|
225
|
+
|| ( [ -z '${sidecar}' ] || ${exit_fail} )
|
226
|
+
else
|
227
|
+
@${compose} up --detach --scale app=$${instances:-1} ${log} || ${exit_fail}
|
228
|
+
endif
|
229
|
+
@$(call echo,${env_human} containers started ${tick})
|
230
|
+
@$(call echo,Waiting for services to become available)
|
231
|
+
@$(call make,wait) 2>${stderr} || ${exit_fail}
|
232
|
+
|
233
|
+
.PHONY: stop
|
234
|
+
stop: network := ${compose_project_name}
|
235
|
+
stop:
|
236
|
+
@$(call echo,Stopping ${env_human} containers)
|
237
|
+
@$(call system,${compose_human} down)
|
238
|
+
@if docker ps --format "{{.ID}}" | grep -q $(shell hostname) ; \
|
239
|
+
then \
|
240
|
+
( docker network disconnect ${network} $(shell hostname) ${log} || : ) \
|
241
|
+
&& \
|
242
|
+
( ${compose} down ${log} || ${exit_fail} ) ; \
|
243
|
+
else \
|
244
|
+
${compose} down ${log} || ${exit_fail} ; \
|
245
|
+
fi
|
246
|
+
@$(call echo,${env_human} containers stopped ${tick})
|
247
|
+
|
248
|
+
.PHONY: logs
|
249
|
+
logs:
|
250
|
+
@${compose} logs -f
|
251
|
+
|
252
|
+
.PHONY: config
|
253
|
+
config:
|
254
|
+
@${compose} config
|
255
|
+
|
256
|
+
.PHONY: compose
|
257
|
+
compose:
|
258
|
+
@echo ${compose}
|
259
|
+
|
260
|
+
### Development/Test Utility Commands
|
261
|
+
|
262
|
+
.PHONY: serve
|
263
|
+
serve: env_file ?= ./.env
|
264
|
+
serve: rails = RAILS_ENV='${env}' bundle exec rails server ${server}
|
265
|
+
serve:
|
266
|
+
@if [ -f "${env_file}" ] ; \
|
267
|
+
then ( \
|
268
|
+
$(call echo,Environment${reset}: ${cyan}${env_file}${reset}) && \
|
269
|
+
cat '${env_file}' | ${format_env} \
|
270
|
+
) ; \
|
271
|
+
fi
|
272
|
+
${rails}
|
273
|
+
|
274
|
+
.PHONY: console
|
275
|
+
console: env_file ?= ./.env
|
276
|
+
console: rails = RAILS_ENV='${env}' bundle exec rails
|
277
|
+
console:
|
278
|
+
@if [ -f "${env_file}" ] ; \
|
279
|
+
then ( \
|
280
|
+
$(call echo,Environment${reset}: ${cyan}${env_file}${reset}) && \
|
281
|
+
cat '${env_file}' | ${format_env} \
|
282
|
+
) ; \
|
283
|
+
fi
|
284
|
+
${rails} console
|
285
|
+
|
286
|
+
.PHONY: db-console
|
287
|
+
db-console:
|
288
|
+
@${rake} orchestration:db:console RAILS_ENV=${env}
|
289
|
+
|
290
|
+
.PHONY: setup
|
291
|
+
setup: url = $(shell ${rake} orchestration:db:url RAILS_ENV=${env})
|
292
|
+
setup: _log-notify
|
293
|
+
@$(call echo,Setting up ${env_human} environment)
|
294
|
+
@$(call make,start env=${env})
|
295
|
+
ifneq (,$(wildcard config/database.yml))
|
296
|
+
@$(call echo,Preparing ${env_human} database)
|
297
|
+
@$(call system,rake db:create DATABASE_URL="${url}")
|
298
|
+
@${rake} db:create RAILS_ENV=${env} ${log} || : ${log}
|
299
|
+
ifneq (,$(wildcard db/structure.sql))
|
300
|
+
@$(call system,rake db:structure:load DATABASE_URL="${url}")
|
301
|
+
@${rake} db:structure:load DATABASE_URL='${url}' ${log} || ${exit_fail}
|
302
|
+
else ifneq (,$(wildcard db/schema.rb))
|
303
|
+
@$(call system,rake db:schema:load DATABASE_URL="${url}")
|
304
|
+
@${rake} db:schema:load DATABASE_URL='${url}' ${log} || ${exit_fail}
|
305
|
+
endif
|
306
|
+
@$(call system,rake db:migrate DATABASE_URL="${url}")
|
307
|
+
@${rake} db:migrate RAILS_ENV=${env}
|
308
|
+
endif
|
309
|
+
@$(MAKE) -n post-setup >/dev/null 2>&1 \
|
310
|
+
&& $(call system,make post-setup RAILS_ENV=${env}) \
|
311
|
+
&& $(MAKE) post-setup RAILS_ENV=${env}
|
312
|
+
@$(call echo,${env_human} environment setup complete ${tick})
|
313
|
+
|
314
|
+
.PHONY: dump
|
315
|
+
dump:
|
316
|
+
ifndef verbose
|
317
|
+
@$(call println)
|
318
|
+
@$(call println,'${yellow}Captured${reset} ${green}stdout${reset} ${yellow}and${reset} ${red}stderr${reset} ${yellow}log data [${cyan}${env}${yellow}]${reset}:')
|
319
|
+
@$(call println)
|
320
|
+
@echo
|
321
|
+
@test -f '${stdout}' && ( \
|
322
|
+
$(call hr,${green}) ; \
|
323
|
+
$(call println,'${gray}${stdout}${reset}') ; \
|
324
|
+
$(call hr,${green}) ; \
|
325
|
+
echo ; cat '${stdout}' ; echo ; \
|
326
|
+
$(call hr,${green}) ; \
|
327
|
+
)
|
328
|
+
|
329
|
+
@test -f '${stdout}' && ( \
|
330
|
+
echo ; \
|
331
|
+
$(call hr,${red}) ; \
|
332
|
+
$(call println,'${gray}${stderr}${reset}') ; \
|
333
|
+
$(call hr,${red}) ; \
|
334
|
+
echo ; cat '${stderr}' ; echo ; \
|
335
|
+
$(call hr,${red}) ; \
|
336
|
+
)
|
337
|
+
endif
|
338
|
+
ifneq (build,${src_cmd})
|
339
|
+
ifneq (push,${src_cmd})
|
340
|
+
@echo ; \
|
341
|
+
$(call hr,${yellow}) ; \
|
342
|
+
$(call println,'${gray}docker-compose logs${reset}') ; \
|
343
|
+
$(call hr,${yellow}) ; \
|
344
|
+
echo
|
345
|
+
@${compose} logs
|
346
|
+
@echo ; \
|
347
|
+
$(call hr,${yellow})
|
348
|
+
@$(NOOP)
|
349
|
+
endif
|
350
|
+
endif
|
351
|
+
|
352
|
+
.PHONY: tag
|
353
|
+
tag:
|
354
|
+
@echo ${docker_image}
|
355
|
+
|
356
|
+
### Deployment utility commands ###
|
357
|
+
|
358
|
+
.PHONY: deploy
|
359
|
+
deploy: _log-notify _clean-logs
|
360
|
+
ifdef env_file
|
361
|
+
deploy: env_file_option = --env-file ${env_file}
|
362
|
+
endif
|
363
|
+
deploy: RAILS_ENV := ${env}
|
364
|
+
deploy: RACK_ENV := ${env}
|
365
|
+
deploy: DOCKER_TAG = ${git_version}
|
366
|
+
deploy: base_vars = DOCKER_ORGANIZATION=${docker_organization} DOCKER_REPOSITORY=${docker_repository} DOCKER_TAG=${git_version}
|
367
|
+
deploy: compose_deploy := ${base_vars} COMPOSE_PROJECT_NAME=${project_base} HOST_UID=$(shell id -u) docker-compose ${env_file_option} --project-name ${project_base} -f orchestration/docker-compose.deployment.yml
|
368
|
+
deploy: config_cmd = ${compose_deploy} config
|
369
|
+
deploy: remote_cmd = cat | docker stack deploy --prune --with-registry-auth -c - ${project_base}
|
370
|
+
deploy: ssh_cmd = ssh "${manager}"
|
371
|
+
deploy: deploy_cmd := ${config_cmd} | ${ssh_cmd} "/bin/bash -lc '${remote_cmd}'"
|
372
|
+
deploy:
|
373
|
+
ifndef manager
|
374
|
+
@$(call fail,Missing ${cyan}manager${reset} parameter: ${cyan}make deploy manager=swarm-manager.example.com${reset}) ; exit 1
|
375
|
+
endif
|
376
|
+
@$(call echo,Deploying ${env_human} stack via ${cyan}${manager}${reset} as ${cyan}${project_base}${reset}) && \
|
377
|
+
( \
|
378
|
+
( test -f '${env_file}' && $(call echo,Deployment environment:) && cat '${env_file}' | ${format_env} || : ) && \
|
379
|
+
$(call echo,Application image: ${cyan}${docker_image}${reset}) ; \
|
380
|
+
$(call system,${config_cmd} | ${ssh_cmd} "/bin/bash -lc '\''${remote_cmd}'\''") ; \
|
381
|
+
${deploy_cmd} \
|
382
|
+
)
|
383
|
+
@$(call echo,Deployment ${green}complete${reset} ${tick})
|
384
|
+
|
385
|
+
.PHONY: rollback
|
386
|
+
ifndef service
|
387
|
+
rollback: service = app
|
388
|
+
endif
|
389
|
+
rollback:
|
390
|
+
ifndef manager
|
391
|
+
@$(call fail,Missing `manager` parameter: `make deploy manager=swarm-manager.example.com`)
|
392
|
+
@exit 1
|
393
|
+
endif
|
394
|
+
@$(call echo,Rolling back ${cyan}${compose_project_name}_${service}${reset} via ${cyan}${manager}${reset} ...)
|
395
|
+
@$(call system,docker service rollback --detach "${compose_project_name}_${service}")
|
396
|
+
@ssh "${manager}" 'docker service rollback --detach "${compose_project_name}_${service}"' ${log} || ${exit_fail}
|
397
|
+
@$(call echo,Rollback request ${green}complete${reset} ${tick})
|
398
|
+
|
399
|
+
### Service healthcheck commands ###
|
400
|
+
|
401
|
+
.PHONY: wait
|
402
|
+
wait:
|
403
|
+
@${rake} orchestration:wait
|
404
|
+
@$(call echo,${env_human} services ${green}ready${reset} ${tick})
|
405
|
+
|
406
|
+
## Generic Listener healthcheck for TCP services ##
|
407
|
+
|
408
|
+
wait-listener:
|
409
|
+
@${rake} orchestration:listener:wait service=${service} sidecar=${sidecar}
|
410
|
+
|
411
|
+
### Docker build commands ###
|
412
|
+
|
413
|
+
.PHONY: build
|
414
|
+
build: _log-notify _clean-logs
|
415
|
+
build: build_dir = ${orchestration_dir}/.build
|
416
|
+
build: context = ${build_dir}/context.tar
|
417
|
+
build: build_args := --build-arg GIT_COMMIT='${git_version}'
|
418
|
+
build: tag_human = ${cyan}${docker_organization}/${docker_repository}:${git_version}${reset}
|
419
|
+
build: latest_tag_human = ${cyan}${docker_organization}/${docker_repository}:latest${reset}
|
420
|
+
ifdef BUNDLE_GITHUB__COM
|
421
|
+
build: build_args := ${build_args} --build-arg BUNDLE_GITHUB__COM
|
422
|
+
endif
|
423
|
+
ifdef BUNDLE_BITBUCKET__ORG
|
424
|
+
build: build_args := ${build_args} --build-arg BUNDLE_BITBUCKET__ORG
|
425
|
+
endif
|
426
|
+
build: _create-log-directory check-local-changes
|
427
|
+
@$(call echo,Preparing build context from ${cyan}${git_branch}${reset} (${cyan}${git_version}${reset})${reset})
|
428
|
+
@$(call system,git archive --format "tar" -o "${context}" "${git_branch}")
|
429
|
+
@mkdir -p ${orchestration_dir}/.build ${log} || ${exit_fail}
|
430
|
+
ifndef dev
|
431
|
+
@git show ${git_branch}:./Gemfile > ${orchestration_dir}/.build/Gemfile 2>${stderr} || ${exit_fail}
|
432
|
+
@git show ${git_branch}:./Gemfile.lock > ${orchestration_dir}/.build/Gemfile.lock 2>${stderr} || ${exit_fail}
|
433
|
+
@git archive --format 'tar' -o '${context}' '${git_branch}' ${log} || ${exit_fail}
|
434
|
+
else
|
435
|
+
@tar -cvf '${context}' . ${log} || ${exit_fail}
|
436
|
+
endif
|
437
|
+
ifdef include
|
438
|
+
@$(call echo,Including files from: ${cyan}${include}${reset})
|
439
|
+
@(while read line; do \
|
440
|
+
_system () { echo '${system_prefix}' $$1 }
|
441
|
+
export line; \
|
442
|
+
include_dir="${build_dir}/$$(dirname "$${line}")/" && \
|
443
|
+
mkdir -p "$${include_dir}" && cp "$${line}" "$${include_dir}" \
|
444
|
+
&& (cd '${orchestration_dir}/.build/' && tar rf 'context.tar' "$${line}"); \
|
445
|
+
_system "tar rf 'context.tar' '$${line}'")
|
446
|
+
done < '${include}') ${log} || ${exit_fail}
|
447
|
+
@$(call echo,Build context ${green}ready${reset} ${tick})
|
448
|
+
endif
|
449
|
+
@$(call echo,Building image ${tag_human})
|
450
|
+
@$(call system,docker build ${build_args} -t ${docker_organization}/${docker_repository}:${git_version} ${orchestration_dir}/)
|
451
|
+
@docker build ${build_args} \
|
452
|
+
-t ${docker_organization}/${docker_repository}:${git_version} \
|
453
|
+
${orchestration_dir}/ ${log_progress} || ${exit_fail}
|
454
|
+
@echo
|
455
|
+
@$(call echo,Build ${green}complete${reset} ${tick})
|
456
|
+
@$(call echo,[${green}tag${reset}] ${tag_human})
|
457
|
+
|
458
|
+
.PHONY: push
|
459
|
+
push: _log-notify _clean-logs
|
460
|
+
@$(call echo,Pushing ${cyan}${docker_image}${reset} to registry)
|
461
|
+
@$(call system,docker push ${docker_image})
|
462
|
+
@docker push ${docker_image} ${log_progress} || ${exit_fail}
|
463
|
+
@echo
|
464
|
+
@$(call echo,Push ${green}complete${reset} ${tick})
|
465
|
+
|
466
|
+
.PHONY: check-local-changes
|
467
|
+
check-local-changes:
|
468
|
+
ifndef dev
|
469
|
+
@changes="$$(git status --porcelain)"; if ! [ -z "${changes}" ] && [[ "${changes}" != "?? orchestration/.sidecar" ]]; \
|
470
|
+
then \
|
471
|
+
$(call warn,You have uncommitted changes which will not be included in your build:) ; \
|
472
|
+
git status --porcelain ; \
|
473
|
+
$(call echo,Commit these changes to Git or, alternatively, build in development mode to test your changes before committing: ${cyan}make build dev=1${reset}) ; \
|
474
|
+
fi
|
475
|
+
endif
|
476
|
+
|
477
|
+
### Internal Commands ###
|
478
|
+
#
|
479
|
+
.PHONY: _log-notify
|
480
|
+
_log-notify: comma=,
|
481
|
+
_log-notify: _verify-repository
|
482
|
+
ifndef verbose
|
483
|
+
@$(call logs,${green}stdout${reset}: ${cyan}log/orchestration.stdout.log${reset}${comma} ${red}stderr${reset}: ${cyan}log/orchestration.stderr.log)
|
484
|
+
endif
|
485
|
+
|
486
|
+
.PHONY: _verify-repository
|
487
|
+
_verify-repository:
|
488
|
+
@if ! git rev-parse HEAD >/dev/null 2>&1 ; then $(call fail,You must make at least one commit before you can use Orchestration commands) ; exit 1 ; fi
|
489
|
+
|
490
|
+
.PHONY: _clean-logs
|
491
|
+
_clean-logs:
|
492
|
+
_clean-logs: _create-log-directory
|
493
|
+
@rm -f '${stdout}' '${stderr}'
|
494
|
+
@touch '${stdout}' '${stderr}'
|
495
|
+
|
496
|
+
.PHONY: _create-log-directory
|
497
|
+
_create-log-directory:
|
498
|
+
@mkdir -p log
|
499
|
+
|
500
|
+
# Used by Orchestration test suite to verify Makefile syntax
|
501
|
+
.PHONY: _test
|
502
|
+
_test:
|
503
|
+
@echo 'test command'
|