djin 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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