djin 0.5.0 → 0.9.0

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: a40e01844f1a9b034d592696cf6af915622c834a9e231f0c56209c03d11660ad
4
- data.tar.gz: facbe98a7362ec81b5f819af27a554807790a1445c7cc47161a85d5f0d5b7276
3
+ metadata.gz: e763f1dad923699c768e998f4dd5caa53ac7a8b0ad4d7acd84c708effdcb627c
4
+ data.tar.gz: 9ab841b39557b775ec7832a3c1cd0befaf983e89ba931459b89a52d5a5df0bda
5
5
  SHA512:
6
- metadata.gz: 72f5ea393c498311acf74a482b02670d94f11704a75032e31e914e68b947de694bec077ba03e22b75fd73c9307f9f98242ea35009f5341c2ab9b3d1af0314901
7
- data.tar.gz: 4bf6650c80d7defc1dd3b2850c21b74c54ecdb7f4dd374c0a98dd3bfe2610c7175f7102a71de53d6367a4dfd1fcdae47437a2db24bee83bc1c195a4c0fea9da7
6
+ metadata.gz: e22bc8b7d3deb635180db1d5c49f1e7d5df1eef3f1b4184b13d65b24eaa45b6f716edfae1bb4799abfe4d4255387958f5f078d931c72cf0b5e407abf9d85ee51
7
+ data.tar.gz: e603ff5f1bab170ed9758331e7c64e39f1832424fc2c0e069aeef2ce85372081ccc15e1a2080cae3c5edc4c8d8fd1286643e4aaa02616249f7173c6d660ffcf2
@@ -14,10 +14,12 @@ jobs:
14
14
  - uses: actions/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: ${{ matrix.ruby }}
17
- - run: |
17
+ - name: Install Gems
18
+ run: |
18
19
  gem install bundler
19
20
  bundle install --jobs 4 --retry 3
20
- bundle exec rake
21
+ - name: Run tests
22
+ run: bundle exec rake
21
23
 
22
24
  lint:
23
25
  runs-on: ubuntu-16.04
@@ -27,8 +29,10 @@ jobs:
27
29
  - uses: actions/setup-ruby@v1
28
30
  with:
29
31
  ruby-version: '2.6'
30
- - run: |
32
+ - name: Install Gems
33
+ run: |
31
34
  gem install bundler
32
35
  bundle install --jobs 4 --retry 3
33
- bundle exec rubocop
36
+ - name: Rubocop
37
+ run: bundle exec rubocop
34
38
 
@@ -1,3 +1,18 @@
1
+ ## 0.9.0 - 17/09/2020
2
+ * [FEATURE] Include Option
3
+
4
+ ## 0.8.0 - 31/08/2020
5
+ * [FEATURE] Adds aliases option
6
+
7
+ ## 0.7.0 - 20/08/2020
8
+ * [FEATURE] Adds description option
9
+
10
+ ## 0.6.1 - 11/08/2020
11
+ * [TECH] Better Error Handling
12
+
13
+ ## 0.6.0 - 22/07/2020
14
+ * [FEATURE] Djin Variables
15
+
1
16
  ## 0.5.0 - 13/07/2020
2
17
  * [FEATURE] Adds local command task
3
18
  * [FEATURE] Template Args for entire djin.yml
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- djin (0.5.0)
4
+ djin (0.9.0)
5
5
  dry-cli (~> 0.6.0)
6
+ dry-equalizer (~> 0.3.0)
6
7
  dry-struct (~> 1.3.0)
7
8
  dry-validation (~> 1.5.1)
8
9
  mustache (~> 1.1.1)
@@ -13,7 +14,7 @@ GEM
13
14
  specs:
14
15
  ast (2.4.1)
15
16
  byebug (11.1.1)
16
- concurrent-ruby (1.1.6)
17
+ concurrent-ruby (1.1.7)
17
18
  diff-lcs (1.3)
18
19
  dry-cli (0.6.0)
19
20
  concurrent-ruby (~> 1.0)
@@ -29,11 +30,11 @@ GEM
29
30
  dry-equalizer (0.3.0)
30
31
  dry-inflector (0.2.0)
31
32
  dry-initializer (3.0.3)
32
- dry-logic (1.0.6)
33
+ dry-logic (1.0.7)
33
34
  concurrent-ruby (~> 1.0)
34
35
  dry-core (~> 0.2)
35
36
  dry-equalizer (~> 0.2)
36
- dry-schema (1.5.2)
37
+ dry-schema (1.5.4)
37
38
  concurrent-ruby (~> 1.0)
38
39
  dry-configurable (~> 0.8, >= 0.8.3)
39
40
  dry-core (~> 0.4)
@@ -53,13 +54,13 @@ GEM
53
54
  dry-equalizer (~> 0.3)
54
55
  dry-inflector (~> 0.1, >= 0.1.2)
55
56
  dry-logic (~> 1.0, >= 1.0.2)
56
- dry-validation (1.5.1)
57
+ dry-validation (1.5.6)
57
58
  concurrent-ruby (~> 1.0)
58
59
  dry-container (~> 0.7, >= 0.7.1)
59
60
  dry-core (~> 0.4)
60
61
  dry-equalizer (~> 0.2)
61
62
  dry-initializer (~> 3.0)
62
- dry-schema (~> 1.5)
63
+ dry-schema (~> 1.5, >= 1.5.2)
63
64
  ice_nine (0.11.2)
64
65
  mustache (1.1.1)
65
66
  parallel (1.19.2)
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Djin
2
2
 
3
3
  ![](https://github.com/catks/djin/workflows/Ruby/badge.svg?branch=master)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/824a2e78399813543212/maintainability)](https://codeclimate.com/github/catks/djin/maintainability)
4
5
 
5
6
  Djin is a make-like utility for docker containers
6
7
 
@@ -25,139 +26,253 @@ If you use Rbenv you can install djin only once and create a alias in your .basr
25
26
  To use djin first you need to create a djin.yml file:
26
27
 
27
28
  ```yaml
28
- djin_version: '0.5.0'
29
-
30
- # With a docker image
31
- script:
32
- docker:
33
- image: "ruby:2.6"
34
- run:
35
- commands:
36
- - "ruby /scripts/my_ruby_script.rb"
37
- options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
38
-
39
- # Using a docker-compose service
40
- test:
41
- docker-compose:
42
- service: app
43
- run:
44
- commands: rspec
45
- options: "--rm"
46
-
29
+ djin_version: '0.9.0'
30
+
31
+ tasks:
32
+ # With a docker image
33
+ script:
34
+ docker:
35
+ image: "ruby:2.6"
36
+ run:
37
+ commands:
38
+ - "ruby /scripts/my_ruby_script.rb"
39
+ options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
40
+
41
+ # Using a docker-compose service
42
+ test:
43
+ docker-compose:
44
+ service: app
45
+ run:
46
+ commands: rspec
47
+ options: "--rm"
48
+ aliases: # Optional Array of strings
49
+ - rspec
47
50
  ```
48
51
 
49
52
  You can also set task dependencies with depends_on option:
50
53
 
51
54
 
52
55
  ```yaml
53
- djin_version: '0.5.0'
56
+ djin_version: '0.9.0'
54
57
 
55
58
  _default_run_options: &default_run_options
56
59
  options: "--rm"
57
60
 
58
- "db:create":
59
- docker-compose:
60
- service: app
61
- run:
62
- commands: rake db:create
63
- <<: *default_run_options
64
-
65
- "db:migrate":
66
- docker-compose:
67
- service: app
68
- run:
69
- commands: rake db:migrate
70
- <<: *default_run_options
71
-
72
- "db:setup":
73
- depends_on:
74
- - "db:create"
75
- - "db:migrate"
76
-
61
+ tasks:
62
+ "db:create":
63
+ docker-compose:
64
+ service: app
65
+ run:
66
+ commands: rake db:create
67
+ <<: *default_run_options
68
+
69
+ "db:migrate":
70
+ docker-compose:
71
+ service: app
72
+ run:
73
+ commands: rake db:migrate
74
+ <<: *default_run_options
75
+
76
+ "db:setup":
77
+ depends_on:
78
+ - "db:create"
79
+ - "db:migrate"
77
80
  ```
78
81
 
79
82
  Or mix local commands and docker/docker-compose commands:
80
83
 
81
84
  ```yaml
82
- djin_version: '0.5.0'
85
+ djin_version: '0.9.0'
83
86
 
84
87
  _default_run_options: &default_run_options
85
88
  options: "--rm"
86
89
 
87
- "db:create":
88
- docker-compose:
89
- service: app
90
- run:
91
- commands: rake db:create
92
- <<: *default_run_options
93
-
94
- "db:migrate":
95
- docker-compose:
96
- service: app
97
- run:
98
- commands: rake db:migrate
99
- <<: *default_run_options
100
-
101
- "setup:copy_samples":
102
- local:
103
- run:
104
- - cp config/database.yml.sample config/database.yml
105
-
106
- "setup":
107
- depends_on:
108
- - "setup:copy_samples"
109
- - "db:create"
110
- - "db:migrate"
111
-
90
+ tasks:
91
+ "db:create":
92
+ docker-compose:
93
+ service: app
94
+ run:
95
+ commands: rake db:create
96
+ <<: *default_run_options
97
+
98
+ "db:migrate":
99
+ docker-compose:
100
+ service: app
101
+ run:
102
+ commands: rake db:migrate
103
+ <<: *default_run_options
104
+
105
+ "setup:copy_samples":
106
+ local:
107
+ run:
108
+ - cp config/database.yml.sample config/database.yml
109
+
110
+ "setup":
111
+ depends_on:
112
+ - "setup:copy_samples"
113
+ - "db:create"
114
+ - "db:migrate"
112
115
  ```
113
116
 
114
117
  After that you can run `djin {{task_name}}`, like `djin script` or `djin test`
115
118
 
116
- ## Using Environment variables and custom args in djin.yml run tasks
119
+ ## Using Environment variables, custom variables and custom args in djin.yml tasks
117
120
 
118
121
  You can also use environment variables using the '{{YOUR_ENV_HERE}}' syntax, like so:
119
122
 
120
123
  ```yaml
121
- djin_version: '0.5.0'
124
+ djin_version: '0.9.0'
122
125
 
123
126
  _default_run_options: &default_run_options
124
127
  options: "--rm"
125
128
 
126
- "db:migrate":
127
- docker-compose:
128
- service: app
129
- run:
130
- commands: ENV={{ENV}} rake db:migrate
131
- <<: *default_run_options
129
+ tasks:
130
+ "db:migrate":
131
+ docker-compose:
132
+ service: app
133
+ run:
134
+ commands: ENV={{ENV}} rake db:migrate
135
+ <<: *default_run_options
132
136
 
133
137
  ```
134
138
 
135
- It's also possible to pass custom arguments to the command, wich means is possible to make a djin task act like the command itself:
139
+ Or define some variables to use in multiple locations
140
+ ```yaml
141
+ djin_version: '0.9.0'
142
+
143
+ _default_run_options: &default_run_options
144
+ options: "--rm"
145
+
146
+ variables:
147
+ my_ssh_user: user
148
+ some_host: test.local
149
+
150
+ tasks:
151
+ "some_host:ssh":
152
+ local:
153
+ run:
154
+ - ssh {{my_ssh_user}}@{{some_host}}
155
+
156
+ "some_host:logs":
157
+ local:
158
+ run:
159
+ - ssh -t {{my_ssh_user}}@{{some_host}} 'tail -f /var/log/syslog'
160
+ ```
161
+
162
+ It's also possible to pass custom arguments to the command, which means is possible to make a djin task act like the command itself:
136
163
 
137
164
  ```yaml
138
- djin_version: '0.5.0'
165
+ djin_version: '0.9.0'
139
166
 
140
167
  _default_run_options: &default_run_options
141
168
  options: "--rm"
142
169
 
143
- "rubocop":
144
- docker-compose:
145
- service: app
146
- run:
147
- commands: rubocop {{args}}
148
- <<: *default_run_options
170
+ tasks:
171
+ "rubocop":
172
+ docker-compose:
173
+ service: app
174
+ run:
175
+ commands: rubocop {{args}}
176
+ <<: *default_run_options
177
+ aliases:
178
+ - lint
149
179
 
150
180
  ```
151
181
 
152
182
  With that you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which wil make djin runs `rubocop --parallel` inside the service `app`.
153
183
 
154
- Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: {{#args} {{args}} --and-other-thing{{/args?}}), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
184
+ Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: `{{#args?}} {{args}} --and-other-thing{{/args?}}`), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
185
+
186
+ ### Reusing tasks
187
+
188
+ If you have multiple tasks with similar behaviour and with small differences you can use the `include` keyword, so this:
189
+
190
+ ```yaml
191
+ djin_version: '0.9.0'
192
+
193
+ tasks:
194
+ "host1:ssh":
195
+ local:
196
+ run:
197
+ - ssh my_user@host1.com.br
198
+
199
+ "host1:restart":
200
+ local:
201
+ run:
202
+ - ssh -t my_user@host1.com.br restart
203
+
204
+ "host1:logs":
205
+ local:
206
+ run:
207
+ - ssh -t my_user@host1.com.br tail -f /var/log/my_log
208
+
209
+ "host2:ssh":
210
+ local:
211
+ run:
212
+ - ssh my_user@host2.com.br
213
+
214
+ "host2:restart":
215
+ local:
216
+ run:
217
+ - ssh -t my_user@host2.com.br restart
218
+
219
+ "host2:logs":
220
+ local:
221
+ run:
222
+ - ssh -t my_user@host2.com.br tail -f /var/log/my_file
223
+
224
+ ```
225
+
226
+ can become this:
227
+
228
+ ```yaml
229
+ # djin.yml
230
+ djin_version: '0.9.0'
231
+
232
+ include:
233
+ - file: '.djin/server_tasks.yml'
234
+ context:
235
+ variables:
236
+ namespace: host1
237
+ host: host1.com
238
+ ssh_user: my_user
239
+
240
+ - file: '.djin/server_tasks.yml'
241
+ context:
242
+ variables:
243
+ namespace: host2
244
+ host: host2.com
245
+ ssh_user: my_user
246
+
247
+ ```
248
+
249
+
250
+ ```yaml
251
+ # .djin/server_tasks.yml
252
+ djin_version: '0.9.0'
253
+
254
+ tasks:
255
+ "{{namespace}}:ssh":
256
+ local:
257
+ run:
258
+ - ssh {{ssh_user}}@{{host}}
259
+
260
+ "{{namespace}}:restart":
261
+ local:
262
+ run:
263
+ - ssh -t {{ssh_user}}@{{host}} restart
264
+
265
+ "{{namespace}}:logs":
266
+ local:
267
+ run:
268
+ - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
269
+ ```
155
270
 
156
271
  ## Development
157
272
 
158
273
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
159
274
 
160
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
275
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `djin release -- {{increment_option}}` (where {{incremment_option}} can be `--patch`, `--minor` or `major`), which will change version, update the CHANGELOG.md, create a new commit, create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
161
276
 
162
277
  ## TODO:
163
278
 
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'dry-cli', '~> 0.6.0'
31
+ spec.add_dependency 'dry-equalizer', '~> 0.3.0'
31
32
  spec.add_dependency 'dry-struct', '~> 1.3.0'
32
33
  spec.add_dependency 'dry-validation', '~> 1.5.1'
33
34
  spec.add_dependency 'mustache', '~> 1.1.1'
data/djin.yml CHANGED
@@ -1,33 +1,35 @@
1
- djin_version: '0.5.0'
1
+ djin_version: '0.9.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
5
5
 
6
- test:
7
- docker-compose:
8
- service: app
9
- run:
10
- commands: "cd /usr/src/djin && rspec {{args}}"
11
- <<: *default_run_options
12
-
13
- sh:
14
- docker-compose:
15
- service: app
16
- run:
17
- commands: "sh"
18
- <<: *default_run_options
19
- run:
20
- docker-compose:
21
- service: app
22
- run:
23
- commands: "sh -c '{{args}}'"
24
- <<: *default_run_options
25
-
26
- release:
27
- local:
28
- run:
29
- - verto tag up {{args}}
30
- - rake release
31
-
6
+ tasks:
7
+ test:
8
+ description: Runs Specs
9
+ docker-compose:
10
+ service: app
11
+ run:
12
+ commands: "cd /usr/src/djin && rspec {{args}}"
13
+ <<: *default_run_options
14
+ aliases:
15
+ - rspec
32
16
 
17
+ sh:
18
+ description: Enter app service shell
19
+ docker-compose:
20
+ service: app
21
+ run:
22
+ commands: "sh"
23
+ <<: *default_run_options
24
+ run:
25
+ docker-compose:
26
+ service: app
27
+ run:
28
+ commands: "sh -c '{{args}}'"
29
+ <<: *default_run_options
33
30
 
31
+ release:
32
+ local:
33
+ run:
34
+ - verto tag up {{args}}
35
+ - bundle exec rake release
@@ -1,29 +1,35 @@
1
1
  ---
2
- djin_version: '0.5.0'
2
+ djin_version: '0.9.0'
3
3
 
4
- default:
5
- docker:
6
- image: "ruby:2.5"
7
- run:
8
- - "ruby -e 'puts \\\" Hello\\\"'"
9
- test:
10
- docker-compose:
11
- service: app
12
- run:
13
- commands: rspec
14
- options: "--rm"
4
+ include:
5
+ - file: 'djin_lib/test.yml'
6
+ context:
7
+ variables:
8
+ namespace: 'test:'
15
9
 
16
- script:
17
- docker:
18
- image: "ruby:2.6"
19
- run:
20
- commands:
21
- - "ruby /scripts/my_ruby_script.rb"
22
- options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
10
+ - file: 'djin_lib/test.yml'
11
+ context:
12
+ variables:
13
+ namespace: 'test2:'
23
14
 
24
15
 
25
- with_build:
26
- docker:
27
- build: .
28
- run:
29
- - "ruby -e 'puts \" Hello\"'"
16
+ tasks:
17
+ default:
18
+ docker:
19
+ image: "ruby:2.5"
20
+ run:
21
+ - "ruby -e 'puts \\\" Hello\\\"'"
22
+
23
+ script:
24
+ docker:
25
+ image: "ruby:2.6"
26
+ run:
27
+ commands:
28
+ - "ruby /scripts/my_ruby_script.rb"
29
+ options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
30
+
31
+ with_build:
32
+ docker:
33
+ build: .
34
+ run:
35
+ - "ruby -e 'puts \" Hello\"'"
@@ -0,0 +1,12 @@
1
+ djin_version: '0.9.0'
2
+
3
+ _default_run_options: &default_run_options
4
+ options: "--rm --entrypoint=''"
5
+
6
+ tasks:
7
+ "{{namespace}}unit":
8
+ docker-compose:
9
+ service: app
10
+ run:
11
+ commands: "cd /usr/src/djin && rspec {{args}}"
12
+ <<: *default_run_options
@@ -0,0 +1,17 @@
1
+ djin_version: '0.8.0'
2
+
3
+ tasks:
4
+ "{{namespace}}:ssh":
5
+ local:
6
+ run:
7
+ - ssh {{ssh_user}}@{{host}}
8
+
9
+ "{{namespace}}:restart":
10
+ local:
11
+ run:
12
+ - ssh -t {{ssh_user}}@{{host}} restart
13
+
14
+ "{{namespace}}:logs":
15
+ local:
16
+ run:
17
+ - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
@@ -0,0 +1,22 @@
1
+ djin_version: '0.9.0'
2
+
3
+ include:
4
+ - file: '.djin/server_tasks.yml'
5
+ context:
6
+ variables:
7
+ namespace: host1
8
+ host: host1.com
9
+ ssh_user: my_user
10
+
11
+ - file: '.djin/server_tasks.yml'
12
+ context:
13
+ variables:
14
+ namespace: host2
15
+ host: host2.com
16
+ ssh_user: my_user
17
+
18
+ tasks:
19
+ hello_command:
20
+ local:
21
+ run:
22
+ - echo 'Hello Djin'
@@ -11,32 +11,35 @@ require 'mustache'
11
11
  require 'djin/extensions/hash_extensions'
12
12
  require 'djin/entities/types'
13
13
  require 'djin/entities/task'
14
+ require 'djin/entities/file_config'
14
15
  require 'djin/interpreter/base_command_builder'
15
16
  require 'djin/interpreter/docker_command_builder'
16
17
  require 'djin/interpreter/docker_compose_command_builder'
17
18
  require 'djin/interpreter/local_command_builder'
18
19
  require 'djin/interpreter'
19
- require 'djin/template_renderer'
20
+ require 'djin/config_loader'
20
21
  require 'djin/executor'
21
22
  require 'djin/cli'
22
23
  require 'djin/task_contract'
23
24
  require 'djin/repositories/task_repository'
25
+ require 'djin/memory_cache'
24
26
 
25
27
  module Djin
26
28
  class Error < StandardError; end
27
29
 
28
- def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
29
- abort 'Error: djin.yml not found' unless path.exist?
30
+ def self.load_tasks!(file_path = Pathname.getwd.join('djin.yml'))
31
+ abort 'Error: djin.yml not found' unless file_path.exist?
30
32
 
31
- rendered_djin_file = TemplateRenderer.render(path.read)
32
- djin_config = YAML.safe_load(rendered_djin_file, [], [], true)
33
+ file_config = ConfigLoader.load!(file_path)
33
34
 
34
- tasks = Interpreter.load!(djin_config)
35
+ # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
36
+ tasks = Interpreter.load!(file_config)
35
37
 
36
38
  @task_repository = TaskRepository.new(tasks)
37
39
  CLI.load_tasks!(tasks)
38
40
  rescue Djin::Interpreter::InvalidConfigurationError => e
39
- abort(e.message)
41
+ error_name = e.class.name.split('::').last
42
+ abort("[#{error_name}] #{e.message}")
40
43
  end
41
44
 
42
45
  def self.tasks
@@ -46,4 +49,12 @@ module Djin
46
49
  def self.task_repository
47
50
  @task_repository ||= TaskRepository.new
48
51
  end
52
+
53
+ def self.cache
54
+ @cache ||= MemoryCache.new
55
+ end
56
+
57
+ def self.root_path
58
+ Pathname.new File.expand_path(File.dirname(__FILE__) + '/..')
59
+ end
49
60
  end
@@ -7,7 +7,7 @@ module Djin
7
7
  def self.load_tasks!(tasks)
8
8
  tasks.each do |task|
9
9
  command = Class.new(Dry::CLI::Command) do
10
- desc "Runs: #{task.command}"
10
+ desc task.description
11
11
 
12
12
  define_method(:task) { task }
13
13
 
@@ -16,7 +16,7 @@ module Djin
16
16
  end
17
17
  end
18
18
 
19
- register task.name, command
19
+ register(task.name, command, aliases: task.aliases)
20
20
  end
21
21
  end
22
22
 
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ # TODO: Refactor this class to be the Interpreter
5
+ # class and use the current interpreter as
6
+ # a TaskLoader
7
+ class ConfigLoader
8
+ using Djin::HashExtensions
9
+ RESERVED_WORDS = %w[djin_version variables tasks include].freeze
10
+
11
+ def self.load!(template_file, runtime_config: {})
12
+ new(template_file, runtime_config: runtime_config).load!
13
+ end
14
+
15
+ def initialize(template_file, runtime_config: {})
16
+ @template_file = template_file
17
+ @template_file_content = Djin.cache.fetch(template_file.realpath.to_s) { template_file.read }
18
+ @runtime_config = runtime_config
19
+ end
20
+
21
+ def load!
22
+ validate_version!
23
+
24
+ file_config
25
+ end
26
+
27
+ private
28
+
29
+ def file_config
30
+ FileConfig.new(
31
+ djin_version: version,
32
+ variables: variables,
33
+ tasks: tasks,
34
+ raw_tasks: raw_tasks
35
+ )
36
+ end
37
+
38
+ def version
39
+ # TODO: Deprecates djin_version and use version instead
40
+ @version || raw_djin_config['djin_version']
41
+ end
42
+
43
+ def variables
44
+ @variables ||= included_variables.merge(raw_djin_config['variables']&.symbolize_keys || {})
45
+ end
46
+
47
+ def tasks
48
+ included_tasks.merge(rendered_djin_config['tasks'] || legacy_tasks)
49
+ end
50
+
51
+ def raw_tasks
52
+ included_raw_tasks.merge(raw_djin_config['tasks'] || legacy_raw_tasks)
53
+ end
54
+
55
+ def legacy_tasks
56
+ warn '[DEPRECATED] Root tasks are deprecated and will be removed in Djin 1.0.0,' \
57
+ ' put the tasks under \'tasks\' keyword'
58
+
59
+ rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
60
+ end
61
+
62
+ def legacy_raw_tasks
63
+ raw_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
64
+ end
65
+
66
+ def included_variables
67
+ return {} unless included_config
68
+
69
+ included_config.variables
70
+ end
71
+
72
+ def included_tasks
73
+ return {} unless included_config
74
+
75
+ included_config.tasks
76
+ end
77
+
78
+ def included_raw_tasks
79
+ return {} unless included_config
80
+
81
+ included_config.raw_tasks
82
+ end
83
+
84
+ def included_config
85
+ @included_config ||= raw_djin_config['include']&.map do |tasks_reference|
86
+ external_config_file = Pathname.new(tasks_reference['file'])
87
+
88
+ ConfigLoader.load!(external_config_file, runtime_config: tasks_reference['context'] || {})
89
+ end&.reduce(:deep_merge)
90
+ rescue Errno::ENOENT => e
91
+ raise Interpreter::InvalidConfigFileError, e.message
92
+ end
93
+
94
+ def args
95
+ index = ARGV.index('--')
96
+
97
+ return [] unless index
98
+
99
+ ARGV.slice((index + 1)..ARGV.size)
100
+ end
101
+
102
+ def env
103
+ @env ||= ENV.to_h.symbolize_keys
104
+ end
105
+
106
+ def raw_djin_config
107
+ @raw_djin_config ||= yaml_load(@template_file_content).deep_merge(@runtime_config)
108
+ rescue Psych::SyntaxError => e
109
+ raise Interpreter::InvalidConfigFileError, "File: #{@template_file.realpath}\n #{e.message}"
110
+ end
111
+
112
+ def rendered_djin_config
113
+ @rendered_djin_config ||= begin
114
+ locals = env.merge(variables)
115
+
116
+ rendered_yaml = Mustache.render(@template_file_content,
117
+ args: args.join(' '),
118
+ args?: args.any?,
119
+ **locals)
120
+ yaml_load(rendered_yaml).merge(@runtime_config)
121
+ end
122
+ end
123
+
124
+ def yaml_load(text)
125
+ YAML.safe_load(text, [], [], true)
126
+ end
127
+
128
+ def validate_version!
129
+ raise Interpreter::MissingVersionError, 'Missing djin_version' unless version
130
+
131
+ return if file_config.version_supported?
132
+
133
+ raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class FileConfig < Dry::Struct
5
+ using HashExtensions
6
+
7
+ attribute :djin_version, Types::String
8
+ attribute :variables, Types::Hash.optional.default({}.freeze)
9
+ attribute :tasks, Types::Hash
10
+ attribute :raw_tasks, Types::Hash
11
+ # TODO: Add env and args
12
+
13
+ include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
14
+
15
+ def version_supported?
16
+ Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
17
+ end
18
+
19
+ def merge(file_config)
20
+ merged_hash = to_h.merge(file_config.to_h)
21
+
22
+ FileConfig.new(merged_hash)
23
+ end
24
+
25
+ def deep_merge(file_config)
26
+ merged_hash = to_h.deep_merge(file_config.to_h)
27
+
28
+ FileConfig.new(merged_hash)
29
+ end
30
+ end
31
+ end
@@ -6,12 +6,10 @@ module Djin
6
6
  attribute :description, Types::String.optional.default(nil)
7
7
  attribute :build_command, Types::String.optional.default(nil)
8
8
  attribute :command, Types::String.optional.default(nil)
9
+ attribute :raw_command, Types::String.optional.default(nil)
10
+ attribute :aliases, Types::Array.of(Types::String).optional.default([].freeze)
9
11
  attribute :depends_on, Types::Array.of(Types::String).optional.default([].freeze)
10
12
 
11
- def ==(other)
12
- name == other.name &&
13
- command == other.command &&
14
- build_command == other.build_command
15
- end
13
+ include Dry::Equalizer(:name, :command, :build_command)
16
14
  end
17
15
  end
@@ -6,6 +6,24 @@ module Djin
6
6
  def except(*keys)
7
7
  reject { |key, _| keys.include?(key) }
8
8
  end
9
+
10
+ def symbolize_keys
11
+ map { |key, value| [key.to_sym, value] }.to_h
12
+ end
13
+
14
+ def deep_merge(other_hash)
15
+ dup.deep_merge!(other_hash)
16
+ end
17
+
18
+ def deep_merge!(other_hash)
19
+ merge!(other_hash) do |_, this_val, other_val|
20
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
21
+ this_val.deep_merge(other_val)
22
+ else
23
+ other_val
24
+ end
25
+ end
26
+ end
9
27
  end
10
28
  end
11
29
  end
@@ -4,41 +4,46 @@ module Djin
4
4
  class Interpreter
5
5
  using Djin::HashExtensions
6
6
 
7
- RESERVED_WORDS = %w[djin_version _default_options].freeze
8
-
7
+ # TODO: Move Errors to ConfigLoader
9
8
  InvalidConfigurationError = Class.new(StandardError)
9
+ InvalidConfigFileError = Class.new(InvalidConfigurationError)
10
10
  MissingVersionError = Class.new(InvalidConfigurationError)
11
11
  VersionNotSupportedError = Class.new(InvalidConfigurationError)
12
12
  InvalidSyntaxError = Class.new(InvalidConfigurationError)
13
13
 
14
14
  class << self
15
- def load!(params)
16
- version = params['djin_version']
17
- raise MissingVersionError, 'Missing djin_version' unless version
18
- unless version_supported?(version)
19
- raise VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
20
- end
21
-
22
- tasks_params = params.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
23
- contract = TaskContract.new
15
+ # rubocop:disable Metrics/AbcSize
16
+ def load!(file_config)
17
+ # TODO: Move task validation to ConfigLoader and add variables/include validations
18
+ task_contract = TaskContract.new
24
19
 
25
- tasks_params.map do |task_name, options|
26
- result = contract.call(options)
20
+ file_config.tasks.map do |task_name, options|
21
+ result = task_contract.call(options)
27
22
 
28
23
  raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
29
24
 
30
25
  command, build_command = build_commands(options, task_name: task_name)
31
26
 
27
+ # FIXME(1): Handle dynamic named tasks, eg: {{namespace}}unit and remove condition
28
+ if file_config.raw_tasks[task_name]
29
+ raw_command, = build_commands(file_config.raw_tasks[task_name], task_name: task_name)
30
+ end
31
+
32
32
  task_params = {
33
33
  name: task_name,
34
34
  build_command: build_command,
35
+ # TODO: Remove `|| command` after FIXME(1)
36
+ description: options['description'] || "Runs: #{raw_command || command}",
35
37
  command: command,
38
+ raw_command: raw_command,
39
+ aliases: options['aliases'],
36
40
  depends_on: options['depends_on']
37
41
  }.compact
38
42
 
39
43
  Djin::Task.new(**task_params)
40
44
  end
41
45
  end
46
+ # rubocop:enable Metrics/AbcSize
42
47
 
43
48
  private
44
49
 
@@ -54,10 +59,6 @@ module Djin
54
59
 
55
60
  LocalCommandBuilder.call(local_params) if local_params
56
61
  end
57
-
58
- def version_supported?(version)
59
- Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(version)
60
- end
61
62
  end
62
63
  end
63
64
  end
@@ -17,6 +17,7 @@ module Djin
17
17
  run_options = run_params['options']
18
18
  end
19
19
 
20
+ # TODO: Remove empty values
20
21
  run_command = run_command.join(' && ') if run_command.is_a?(Array)
21
22
 
22
23
  [run_command, run_options]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class MemoryCache
5
+ def initialize(hash_store = {})
6
+ @hash_store = hash_store
7
+ end
8
+
9
+ def fetch(key)
10
+ @hash_store[key] || @hash_store[key] = yield
11
+ end
12
+
13
+ def clear
14
+ @hash_store = {}
15
+ end
16
+ end
17
+ end
@@ -49,6 +49,7 @@ module Djin
49
49
  hash(LocalSchema)
50
50
  end
51
51
 
52
+ optional(:aliases).each(:str?)
52
53
  optional(:depends_on).each(:str?)
53
54
  end
54
55
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.5.0'
4
+ VERSION = '0.9.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: djin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Atkinson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-14 00:00:00.000000000 Z
11
+ date: 2020-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-equalizer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dry-struct
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -178,9 +192,14 @@ files:
178
192
  - djin.yml
179
193
  - docker-compose.yml
180
194
  - examples/djin.yml
195
+ - examples/djin_lib/test.yml
196
+ - examples/local_tasks/.djin/server_tasks.yml
197
+ - examples/local_tasks/djin.yml
181
198
  - exe/djin
182
199
  - lib/djin.rb
183
200
  - lib/djin/cli.rb
201
+ - lib/djin/config_loader.rb
202
+ - lib/djin/entities/file_config.rb
184
203
  - lib/djin/entities/task.rb
185
204
  - lib/djin/entities/types.rb
186
205
  - lib/djin/executor.rb
@@ -190,9 +209,9 @@ files:
190
209
  - lib/djin/interpreter/docker_command_builder.rb
191
210
  - lib/djin/interpreter/docker_compose_command_builder.rb
192
211
  - lib/djin/interpreter/local_command_builder.rb
212
+ - lib/djin/memory_cache.rb
193
213
  - lib/djin/repositories/task_repository.rb
194
214
  - lib/djin/task_contract.rb
195
- - lib/djin/template_renderer.rb
196
215
  - lib/djin/version.rb
197
216
  homepage: https://github.com/catks/djin
198
217
  licenses:
@@ -213,7 +232,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
232
  - !ruby/object:Gem::Version
214
233
  version: '0'
215
234
  requirements: []
216
- rubygems_version: 3.0.3
235
+ rubyforge_project:
236
+ rubygems_version: 2.7.6
217
237
  signing_key:
218
238
  specification_version: 4
219
239
  summary: djin is a make-like utility for docker containers
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Djin
4
- class TemplateRenderer
5
- def self.render(template_file)
6
- new(template_file).render
7
- end
8
-
9
- def initialize(template_file)
10
- @template_file = template_file
11
- end
12
-
13
- def render
14
- Mustache.render(@template_file,
15
- args: args.join(' '),
16
- args?: args.any?,
17
- **env)
18
- end
19
-
20
- private
21
-
22
- def args
23
- index = ARGV.index('--')
24
-
25
- return [] unless index
26
-
27
- ARGV.slice((index + 1)..ARGV.size)
28
- end
29
-
30
- def env
31
- @env ||= ENV.to_h.map { |k, v| [k.to_sym, v] }.to_h
32
- end
33
- end
34
- end