djin 0.8.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: 558aca989aad2d1cfd7b19768a7a5d6fd2f660afb5135fb9ca3ae03eacb233b4
4
- data.tar.gz: f2e5c3861c6db601b77bc04321386cb2c22ec049d315781966830ba8a5f6e8af
3
+ metadata.gz: e763f1dad923699c768e998f4dd5caa53ac7a8b0ad4d7acd84c708effdcb627c
4
+ data.tar.gz: 9ab841b39557b775ec7832a3c1cd0befaf983e89ba931459b89a52d5a5df0bda
5
5
  SHA512:
6
- metadata.gz: a898ea486006d81980a4e963a1e7b2d31f40c5bec00d9b5948f4b32d0be5c222b61525c90fd214523cd482aadeab88e3927cb3a3697f7fc86b8180901e015543
7
- data.tar.gz: 3d6b0a29b1faa6a3bffb0fa5076ee88083229cbb36f77f2166e44bc19ddd84b73bd6b254578da2c721e1c08ffc7e9583f1325bdd73acc87511ceec47b3f0e512
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,6 @@
1
+ ## 0.9.0 - 17/09/2020
2
+ * [FEATURE] Include Option
3
+
1
4
  ## 0.8.0 - 31/08/2020
2
5
  * [FEATURE] Adds aliases option
3
6
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- djin (0.8.0)
4
+ djin (0.9.0)
5
5
  dry-cli (~> 0.6.0)
6
6
  dry-equalizer (~> 0.3.0)
7
7
  dry-struct (~> 1.3.0)
@@ -34,7 +34,7 @@ GEM
34
34
  concurrent-ruby (~> 1.0)
35
35
  dry-core (~> 0.2)
36
36
  dry-equalizer (~> 0.2)
37
- dry-schema (1.5.3)
37
+ dry-schema (1.5.4)
38
38
  concurrent-ruby (~> 1.0)
39
39
  dry-configurable (~> 0.8, >= 0.8.3)
40
40
  dry-core (~> 0.4)
@@ -54,13 +54,13 @@ GEM
54
54
  dry-equalizer (~> 0.3)
55
55
  dry-inflector (~> 0.1, >= 0.1.2)
56
56
  dry-logic (~> 1.0, >= 1.0.2)
57
- dry-validation (1.5.4)
57
+ dry-validation (1.5.6)
58
58
  concurrent-ruby (~> 1.0)
59
59
  dry-container (~> 0.7, >= 0.7.1)
60
60
  dry-core (~> 0.4)
61
61
  dry-equalizer (~> 0.2)
62
62
  dry-initializer (~> 3.0)
63
- dry-schema (~> 1.5)
63
+ dry-schema (~> 1.5, >= 1.5.2)
64
64
  ice_nine (0.11.2)
65
65
  mustache (1.1.1)
66
66
  parallel (1.19.2)
data/README.md CHANGED
@@ -26,7 +26,7 @@ If you use Rbenv you can install djin only once and create a alias in your .basr
26
26
  To use djin first you need to create a djin.yml file:
27
27
 
28
28
  ```yaml
29
- djin_version: '0.8.0'
29
+ djin_version: '0.9.0'
30
30
 
31
31
  tasks:
32
32
  # With a docker image
@@ -53,7 +53,7 @@ You can also set task dependencies with depends_on option:
53
53
 
54
54
 
55
55
  ```yaml
56
- djin_version: '0.8.0'
56
+ djin_version: '0.9.0'
57
57
 
58
58
  _default_run_options: &default_run_options
59
59
  options: "--rm"
@@ -82,7 +82,7 @@ tasks:
82
82
  Or mix local commands and docker/docker-compose commands:
83
83
 
84
84
  ```yaml
85
- djin_version: '0.8.0'
85
+ djin_version: '0.9.0'
86
86
 
87
87
  _default_run_options: &default_run_options
88
88
  options: "--rm"
@@ -121,7 +121,7 @@ After that you can run `djin {{task_name}}`, like `djin script` or `djin test`
121
121
  You can also use environment variables using the '{{YOUR_ENV_HERE}}' syntax, like so:
122
122
 
123
123
  ```yaml
124
- djin_version: '0.8.0'
124
+ djin_version: '0.9.0'
125
125
 
126
126
  _default_run_options: &default_run_options
127
127
  options: "--rm"
@@ -138,7 +138,7 @@ tasks:
138
138
 
139
139
  Or define some variables to use in multiple locations
140
140
  ```yaml
141
- djin_version: '0.8.0'
141
+ djin_version: '0.9.0'
142
142
 
143
143
  _default_run_options: &default_run_options
144
144
  options: "--rm"
@@ -162,7 +162,7 @@ tasks:
162
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:
163
163
 
164
164
  ```yaml
165
- djin_version: '0.8.0'
165
+ djin_version: '0.9.0'
166
166
 
167
167
  _default_run_options: &default_run_options
168
168
  options: "--rm"
@@ -183,6 +183,91 @@ With that you can pass custom args after `--`, eg: `djin rubocop -- --parallel`,
183
183
 
184
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
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
+ ```
270
+
186
271
  ## Development
187
272
 
188
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.
data/djin.yml CHANGED
@@ -1,4 +1,4 @@
1
- djin_version: '0.8.0'
1
+ djin_version: '0.9.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
@@ -1,5 +1,17 @@
1
1
  ---
2
- djin_version: '0.8.0'
2
+ djin_version: '0.9.0'
3
+
4
+ include:
5
+ - file: 'djin_lib/test.yml'
6
+ context:
7
+ variables:
8
+ namespace: 'test:'
9
+
10
+ - file: 'djin_lib/test.yml'
11
+ context:
12
+ variables:
13
+ namespace: 'test2:'
14
+
3
15
 
4
16
  tasks:
5
17
  default:
@@ -7,12 +19,6 @@ tasks:
7
19
  image: "ruby:2.5"
8
20
  run:
9
21
  - "ruby -e 'puts \\\" Hello\\\"'"
10
- test:
11
- docker-compose:
12
- service: app
13
- run:
14
- commands: rspec
15
- options: "--rm"
16
22
 
17
23
  script:
18
24
  docker:
@@ -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'
@@ -22,14 +22,15 @@ require 'djin/executor'
22
22
  require 'djin/cli'
23
23
  require 'djin/task_contract'
24
24
  require 'djin/repositories/task_repository'
25
+ require 'djin/memory_cache'
25
26
 
26
27
  module Djin
27
28
  class Error < StandardError; end
28
29
 
29
- def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
30
- 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?
31
32
 
32
- file_config = ConfigLoader.load!(path.read)
33
+ file_config = ConfigLoader.load!(file_path)
33
34
 
34
35
  # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
35
36
  tasks = Interpreter.load!(file_config)
@@ -48,4 +49,12 @@ module Djin
48
49
  def self.task_repository
49
50
  @task_repository ||= TaskRepository.new
50
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
51
60
  end
@@ -6,14 +6,16 @@ module Djin
6
6
  # a TaskLoader
7
7
  class ConfigLoader
8
8
  using Djin::HashExtensions
9
- RESERVED_WORDS = %w[djin_version variables tasks].freeze
9
+ RESERVED_WORDS = %w[djin_version variables tasks include].freeze
10
10
 
11
- def self.load!(template_file)
12
- new(template_file).load!
11
+ def self.load!(template_file, runtime_config: {})
12
+ new(template_file, runtime_config: runtime_config).load!
13
13
  end
14
14
 
15
- def initialize(template_file)
15
+ def initialize(template_file, runtime_config: {})
16
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
17
19
  end
18
20
 
19
21
  def load!
@@ -33,39 +35,21 @@ module Djin
33
35
  )
34
36
  end
35
37
 
36
- def raw_djin_config
37
- @raw_djin_config ||= yaml_load(@template_file)
38
- rescue Psych::SyntaxError => e
39
- raise Interpreter::InvalidConfigFileError, e.message
40
- end
41
-
42
- def rendered_djin_config
43
- @rendered_djin_config ||= begin
44
- locals = env.merge(variables)
45
-
46
- rendered_yaml = Mustache.render(@template_file,
47
- args: args.join(' '),
48
- args?: args.any?,
49
- **locals)
50
- yaml_load(rendered_yaml)
51
- end
52
- end
53
-
54
38
  def version
55
39
  # TODO: Deprecates djin_version and use version instead
56
40
  @version || raw_djin_config['djin_version']
57
41
  end
58
42
 
59
43
  def variables
60
- @variables ||= raw_djin_config['variables']&.symbolize_keys || {}
44
+ @variables ||= included_variables.merge(raw_djin_config['variables']&.symbolize_keys || {})
61
45
  end
62
46
 
63
47
  def tasks
64
- rendered_djin_config['tasks'] || legacy_tasks
48
+ included_tasks.merge(rendered_djin_config['tasks'] || legacy_tasks)
65
49
  end
66
50
 
67
51
  def raw_tasks
68
- raw_djin_config['tasks'] || legacy_raw_tasks
52
+ included_raw_tasks.merge(raw_djin_config['tasks'] || legacy_raw_tasks)
69
53
  end
70
54
 
71
55
  def legacy_tasks
@@ -79,6 +63,34 @@ module Djin
79
63
  raw_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
80
64
  end
81
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
+
82
94
  def args
83
95
  index = ARGV.index('--')
84
96
 
@@ -91,6 +103,24 @@ module Djin
91
103
  @env ||= ENV.to_h.symbolize_keys
92
104
  end
93
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
+
94
124
  def yaml_load(text)
95
125
  YAML.safe_load(text, [], [], true)
96
126
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Djin
4
4
  class FileConfig < Dry::Struct
5
+ using HashExtensions
6
+
5
7
  attribute :djin_version, Types::String
6
8
  attribute :variables, Types::Hash.optional.default({}.freeze)
7
9
  attribute :tasks, Types::Hash
@@ -13,5 +15,17 @@ module Djin
13
15
  def version_supported?
14
16
  Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
15
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
16
30
  end
17
31
  end
@@ -10,6 +10,20 @@ module Djin
10
10
  def symbolize_keys
11
11
  map { |key, value| [key.to_sym, value] }.to_h
12
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
13
27
  end
14
28
  end
15
29
  end
@@ -12,22 +12,28 @@ module Djin
12
12
  InvalidSyntaxError = Class.new(InvalidConfigurationError)
13
13
 
14
14
  class << self
15
+ # rubocop:disable Metrics/AbcSize
15
16
  def load!(file_config)
16
- contract = TaskContract.new
17
+ # TODO: Move task validation to ConfigLoader and add variables/include validations
18
+ task_contract = TaskContract.new
17
19
 
18
20
  file_config.tasks.map do |task_name, options|
19
- result = contract.call(options)
21
+ result = task_contract.call(options)
20
22
 
21
23
  raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
22
24
 
23
25
  command, build_command = build_commands(options, task_name: task_name)
24
26
 
25
- raw_command, = build_commands(file_config.raw_tasks[task_name], task_name: task_name)
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
26
31
 
27
32
  task_params = {
28
33
  name: task_name,
29
34
  build_command: build_command,
30
- description: options['description'] || "Runs: #{raw_command}",
35
+ # TODO: Remove `|| command` after FIXME(1)
36
+ description: options['description'] || "Runs: #{raw_command || command}",
31
37
  command: command,
32
38
  raw_command: raw_command,
33
39
  aliases: options['aliases'],
@@ -37,6 +43,7 @@ module Djin
37
43
  Djin::Task.new(**task_params)
38
44
  end
39
45
  end
46
+ # rubocop:enable Metrics/AbcSize
40
47
 
41
48
  private
42
49
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.8.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.8.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-08-31 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
@@ -192,6 +192,9 @@ files:
192
192
  - djin.yml
193
193
  - docker-compose.yml
194
194
  - examples/djin.yml
195
+ - examples/djin_lib/test.yml
196
+ - examples/local_tasks/.djin/server_tasks.yml
197
+ - examples/local_tasks/djin.yml
195
198
  - exe/djin
196
199
  - lib/djin.rb
197
200
  - lib/djin/cli.rb
@@ -206,6 +209,7 @@ files:
206
209
  - lib/djin/interpreter/docker_command_builder.rb
207
210
  - lib/djin/interpreter/docker_compose_command_builder.rb
208
211
  - lib/djin/interpreter/local_command_builder.rb
212
+ - lib/djin/memory_cache.rb
209
213
  - lib/djin/repositories/task_repository.rb
210
214
  - lib/djin/task_contract.rb
211
215
  - lib/djin/version.rb