orchestration 0.5.14 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 291252425784215fed686a5bf414b115dbec55ebca6bcdb23b136d5c6816e195
4
- data.tar.gz: d972df8f59a94eca1912911b4f662fc195fa154efe935d5b4c55aef41c7b61c7
3
+ metadata.gz: ca5c9bd9b0a391d077e39e8447c38483f7149c3435b150289d2bebee4ca951b9
4
+ data.tar.gz: '08d6fdbd61e22b9ab3b5f3367250c7adfa0ad65dfecccc128a1075db8e6a478c'
5
5
  SHA512:
6
- metadata.gz: 8628b095acf09db5f22f9144c4224fdbd3b97d16b737c3269e9ba3db04ca5c91574c926f6ea6382d4e6daea8248a7e297dc81dba06d1a1818077584a1955bd9a
7
- data.tar.gz: 666c46c274e7fe26828e35b2eaa0ff8455b3c6866611e91572f7ad08daff1476985944b41ccf2e6c37b02294256deb9739f1c8413308e72509b0baab6b815e56
6
+ metadata.gz: f07f4b6ad5d5ce98cf65f94ccc9ae6ae280fab784a1760ede004a0cecdc4c90294e781d585868da9784603fca08f7e530b87ad94bf886e9c6f4c78e48264e7f3
7
+ data.tar.gz: 2cf4176c26c23d05733a926a8d22871a0e801a35d7e4b5643e92d7adfdb6a7627b23ee37dd97c01250937ebb31a7c9d638a5902377b91d512a48bcf90e5a0d90
data/README.md CHANGED
@@ -7,12 +7,14 @@ 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
 
14
14
  _Orchestration_ has been successfully used to build continuous delivery pipelines for numerous production applications with a wide range or requirements.
15
15
 
16
+ See [upgrade guide](UPGRADE.md) if you are upgrading from `0.5.x` to `0.6.x`.
17
+
16
18
  ## Example
17
19
 
18
20
  The below screenshot demonstrates _Orchestration_ being installed in a brand new _Rails_ application with support for _PostgreSQL_ (via the _PG_ gem) and _RabbitMQ_ (via the _Bunny_ gem):
@@ -27,7 +29,7 @@ The below screenshot demonstrates _Orchestration_ being installed in a brand new
27
29
  Add _Orchestration_ to your Gemfile:
28
30
 
29
31
  ```ruby
30
- gem 'orchestration', '~> 0.5.14'
32
+ gem 'orchestration', '~> 0.6.3'
31
33
  ```
32
34
 
33
35
  Install:
@@ -83,7 +85,7 @@ All `make` commands provided by _Orchestration_ (with the exception of `test` an
83
85
  e.g.:
84
86
  ```
85
87
  # Stop all test containers
86
- make stop env=test
88
+ make stop RAILS_ENV=test
87
89
  ```
88
90
 
89
91
  The default value for `env` is `development`.
@@ -91,7 +93,7 @@ The default value for `env` is `development`.
91
93
  As with any `Makefile` targets can be chained together, e.g.:
92
94
  ```
93
95
  # Run tests, build, and push image
94
- make test build push
96
+ make setup test build push
95
97
  ```
96
98
 
97
99
  ### Containers
@@ -113,7 +115,7 @@ make stop
113
115
  #### Interface directly with `docker-compose`
114
116
 
115
117
  ```bash
116
- $(make compose env=test) logs -f database
118
+ $(make compose RAILS_ENV=test) logs -f database
117
119
  ```
118
120
 
119
121
  ### Images
@@ -192,21 +194,15 @@ A default `test` target is provided in your application's main `Makefile`. You a
192
194
 
193
195
  To launch all dependency containers, run database migrations, and run tests:
194
196
  ```bash
195
- make test
197
+ make setup test
196
198
  ```
197
199
 
198
200
  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
201
 
200
- To run only the `test` command, without test setup (i.e. without restarting containers etc.), pass the `light` option:
202
+ To skip the setup step and just run tests (i.e. once test containers are up and running and ready for use) simply run:
201
203
 
202
204
  ```bash
203
- make test light=1
204
- ```
205
-
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
- ```bash
208
- make test-setup
209
- bundle exec rspec spec/my_class_spec.rb
205
+ make test
210
206
  ```
211
207
 
212
208
  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 +216,29 @@ ORCHESTRATION_RETRY_LIMIT # default: 15
220
216
  ORCHESTRATION_RETRY_INTERVAL # default: 10 [seconds]
221
217
  ```
222
218
 
223
- ### (Local) Production
219
+ ### (Local) Deployment
224
220
 
225
- Run a production environment locally to simulate your deployment platform:
221
+ Run a deployment environment locally to simulate your deployment platform:
226
222
 
223
+ ```bash
224
+ make deploy manager=localhost
227
225
  ```
228
- make start env=production
229
- ```
226
+
227
+ Ensure you have passwordless _SSH_ access to your own workstation and that you have approved the host authenticity/fingerprint.
230
228
 
231
229
  #### Deploy to a remote swarm
232
230
 
233
231
  To connect via _SSH_ to a remote swarm and deploy, pass the `manager` parameter:
234
- ```
232
+ ```bash
235
233
  make deploy manager=user@manager.swarm.example.com
236
234
  ```
237
235
 
238
- The file `orchestration/docker-compose.production.yml` is created automatically. This file will always be used for deployment, regardless of _Rails_ environment. Other environments should be configured using a separate [`.env` file](#env-file) for each environment.
236
+ 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:
237
+ ```bash
238
+ make deploy manager=user@manager.swarm.example.com env_file=staging.env
239
+ ```
240
+
241
+ This way you can set different publish ports and other application configuration variables for each stage you want to deploy to.
239
242
 
240
243
  #### Roll back a deployment
241
244
 
@@ -254,11 +257,11 @@ The `project_name` parameter is also supported.
254
257
 
255
258
  #### Use a custom stack name
256
259
 
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. `anvil_production`.
260
+ 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
261
 
259
262
  To override this default, pass the `project_name` parameter:
260
263
  ```
261
- make deploy project_name=acme_anvil_production
264
+ make deploy project_name=custom_stack_name
262
265
  ```
263
266
 
264
267
  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 +303,7 @@ tail -f log/orchestration*.log
300
303
  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
304
 
302
305
  ```bash
303
- make dump env=test
306
+ make dump RAILS_ENV=test
304
307
  ```
305
308
 
306
309
  All commands also support the `verbose` flag which will output all logs immediately to the console:
@@ -327,7 +330,7 @@ See related documentation:
327
330
 
328
331
  ## Healthchecks
329
332
 
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.production.yml`):
333
+ [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
334
 
332
335
  | Variable | Meaning | Default Value |
333
336
  |-|-|-|
@@ -355,7 +358,7 @@ Real-world applications will inevitably need to make changes to this file. As wi
355
358
 
356
359
  ## Entrypoint
357
360
 
358
- An [entrypoint](https://docs.docker.com/engine/reference/builder/#entrypoint) script for your application is provided which does the following:
361
+ 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
362
 
360
363
  * Runs the `CMD` process as the same system user that launched the container (rather than the default `root` user);
361
364
  * Creates various required temporary directories and removes stale `pid` files;
@@ -370,7 +373,7 @@ If you need to start dependency services (databases, etc.) and connect to them f
370
373
  To do this automatically, pass the `sidecar` parameter to the `start` or `test` targets:
371
374
 
372
375
  ```bash
373
- make test sidecar=1
376
+ make setup test sidecar=1
374
377
  ```
375
378
 
376
379
  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 +384,10 @@ Note that a temporary file `orchestration/.sidecar` containing the random projec
381
384
 
382
385
  ```bash
383
386
  # Start dependencies and run tests in sidecar mode
384
- make test sidecar=1
387
+ make setup test sidecar=1
385
388
 
386
389
  # Stop test dependencies in sidecar mode
387
- make stop env=test
390
+ make stop RAILS_ENV=test
388
391
  ```
389
392
 
390
393
  <a name="rabbitmq-configuration"></a>
data/UPGRADE.md ADDED
@@ -0,0 +1,90 @@
1
+ # Upgrade guide
2
+
3
+ ## 0.5 to 0.6
4
+
5
+ ### Delete/rename files
6
+
7
+ Delete all files from `orchestration/` directory except:
8
+
9
+ * `docker-compose.production.yml`
10
+ * `docker-compose.development.yml`
11
+ * `docker-compose.test.yml`
12
+ * `Dockerfile`
13
+ * `entrypoint.sh`
14
+
15
+ Rename:
16
+
17
+ ```bash
18
+ orchestration/docker-compose.production.yml => orchestration/docker-compose.deployment.yml
19
+ ```
20
+
21
+ Any environment-specific compose files (e.g. `staging`) should also be removed as the same compose file is now used for all deployment stages.
22
+
23
+ ### Update Makefile
24
+
25
+ Remove the first line of the main `Makefile` (in root of project) and replace it with the new `include` declaration:
26
+
27
+ #### OLD
28
+
29
+ ```make
30
+ include orchestration/Makefile
31
+ ```
32
+
33
+ #### NEW
34
+
35
+ ```make
36
+ include $(shell bundle exec ruby -e 'require "orchestration/make"')
37
+ ```
38
+
39
+ #### Post-setup target
40
+
41
+ Add a new target anywhere in the `Makefile` called `post-setup`:
42
+
43
+ ```make
44
+ .PHONY: post-setup
45
+ post-setup:
46
+ echo 'doing post setup stuff'
47
+ ```
48
+
49
+ Replace the body of this target with any commands that you want to take place once the initial setup (launching development/test containers, running migrations, etc.) is complete. For example, running migrations for a secondary database.
50
+
51
+ ### Continuous Integration files
52
+
53
+ Update any continuous integration scripts (e.g. `Jenkinsfile`) to run the `setup` target before `test`, e.g.:
54
+
55
+ #### OLD
56
+
57
+ ```Jenkinsfile
58
+ stage('Test') {
59
+ steps {
60
+ sh 'make test sidecar=1'
61
+ }
62
+ }
63
+ ```
64
+
65
+ #### NEW
66
+
67
+ ```Jenkinsfile
68
+ stage('Test') {
69
+ steps {
70
+ sh 'make setup test sidecar=1'
71
+ }
72
+ }
73
+ ```
74
+
75
+ (`sidecar` may or may not be needed depending on your setup but, if it was there prior to upgrade, then it should remain after upgrade).
76
+
77
+ ### General Usage
78
+
79
+ All _Orchestration_ commands behave exactly the same as before with the exception of the `test` target. Now, instead of running `make test` to launch all containers and then run tests, _Rubocop_, etc., you must run `make setup test`.
80
+
81
+ This will set up the test environment and then run tests. You may then run `make test` to just run tests without having to go through the full setup process again.
82
+
83
+ Similarly you can set up the development environment by just running `make setup`.
84
+
85
+ To set up the test environment and run tests as two separate steps (i.e. equivalent of using the shorthand `make setup test`) you can run:
86
+
87
+ ```bash
88
+ make setup RAILS_ENV=test
89
+ make test
90
+ ```
@@ -1,33 +1,21 @@
1
1
  en:
2
2
  orchestration:
3
- attempt_limit: "Unable to reconnect after %{limit} attempts. Aborting."
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"
@@ -99,6 +99,7 @@ module Orchestration
99
99
  'RAILS_SERVE_STATIC_FILES' => '1',
100
100
  'WEB_PRELOAD_APP' => '1',
101
101
  'WEB_HEALTHCHECK_PATH' => '/',
102
+ 'WEB_PORT' => 8080,
102
103
  'DATABASE_URL' => database_url
103
104
  }.merge(inherited_environment.map { |key| [key, nil] }.to_h).merge(rabbitmq_urls)
104
105
  end
@@ -34,7 +34,7 @@ module Orchestration
34
34
  end
35
35
 
36
36
  def networks
37
- return {} unless @environment == :production
37
+ return {} unless @environment == :deployment
38
38
 
39
39
  { 'local' => { 'aliases' => ['database-local'] } }
40
40
  end
@@ -18,8 +18,8 @@ module Orchestration
18
18
  create_compose_file(:development)
19
19
  end
20
20
 
21
- def docker_compose_production_yml
22
- create_compose_file(:production)
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 :production
71
+ when :deployment
72
72
  %i[app database mongo rabbitmq]
73
73
  when :local, nil
74
74
  []
@@ -27,30 +27,9 @@ 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
- inject_if_missing(path, 'include orchestration/Makefile')
48
- end
49
-
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
33
  end
55
34
 
56
35
  def dockerfile
@@ -71,7 +50,7 @@ module Orchestration
71
50
  def docker_compose
72
51
  @docker_compose.docker_compose_test_yml
73
52
  @docker_compose.docker_compose_development_yml
74
- @docker_compose.docker_compose_production_yml
53
+ @docker_compose.docker_compose_deployment_yml
75
54
  end
76
55
 
77
56
  def puma
@@ -133,14 +112,8 @@ module Orchestration
133
112
  I18n.t("orchestration.#{key}")
134
113
  end
135
114
 
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
115
  def enabled_services
143
- %i[test development production].map do |environment|
116
+ %i[test development deployment].map do |environment|
144
117
  @docker_compose.enabled_services(environment)
145
118
  end.flatten.uniq
146
119
  end
@@ -0,0 +1,502 @@
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:schema:load DATABASE_URL="${url}")
301
+ @${rake} db:schema: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
+ export line; \
441
+ include_dir="${build_dir}/$$(dirname "$${line}")/" && \
442
+ mkdir -p "$${include_dir}" && cp "$${line}" "$${include_dir}" \
443
+ && (cd '${orchestration_dir}/.build/' && tar rf 'context.tar' "$${line}"); \
444
+ echo "${system_prefix}" "tar rf 'context.tar' '$${line}'"; \
445
+ done < '${include}') ${log} || ${exit_fail}
446
+ @$(call echo,Build context ${green}ready${reset} ${tick})
447
+ endif
448
+ @$(call echo,Building image ${tag_human})
449
+ @$(call system,docker build ${build_args} -t ${docker_organization}/${docker_repository}:${git_version} ${orchestration_dir}/)
450
+ @docker build ${build_args} \
451
+ -t ${docker_organization}/${docker_repository}:${git_version} \
452
+ ${orchestration_dir}/ ${log_progress} || ${exit_fail}
453
+ @echo
454
+ @$(call echo,Build ${green}complete${reset} ${tick})
455
+ @$(call echo,[${green}tag${reset}] ${tag_human})
456
+
457
+ .PHONY: push
458
+ push: _log-notify _clean-logs
459
+ @$(call echo,Pushing ${cyan}${docker_image}${reset} to registry)
460
+ @$(call system,docker push ${docker_image})
461
+ @docker push ${docker_image} ${log_progress} || ${exit_fail}
462
+ @echo
463
+ @$(call echo,Push ${green}complete${reset} ${tick})
464
+
465
+ .PHONY: check-local-changes
466
+ check-local-changes:
467
+ ifndef dev
468
+ @changes="$$(git status --porcelain)"; if ! [ -z "${changes}" ] && [[ "${changes}" != "?? orchestration/.sidecar" ]]; \
469
+ then \
470
+ $(call warn,You have uncommitted changes which will not be included in your build:) ; \
471
+ git status --porcelain ; \
472
+ $(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}) ; \
473
+ fi
474
+ endif
475
+
476
+ ### Internal Commands ###
477
+ #
478
+ .PHONY: _log-notify
479
+ _log-notify: comma=,
480
+ _log-notify: _verify-repository
481
+ ifndef verbose
482
+ @$(call logs,${green}stdout${reset}: ${cyan}log/orchestration.stdout.log${reset}${comma} ${red}stderr${reset}: ${cyan}log/orchestration.stderr.log)
483
+ endif
484
+
485
+ .PHONY: _verify-repository
486
+ _verify-repository:
487
+ @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
488
+
489
+ .PHONY: _clean-logs
490
+ _clean-logs:
491
+ _clean-logs: _create-log-directory
492
+ @rm -f '${stdout}' '${stderr}'
493
+ @touch '${stdout}' '${stderr}'
494
+
495
+ .PHONY: _create-log-directory
496
+ _create-log-directory:
497
+ @mkdir -p log
498
+
499
+ # Used by Orchestration test suite to verify Makefile syntax
500
+ .PHONY: _test
501
+ _test:
502
+ @echo 'test command'