djin 0.8.0 → 0.11.2
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 +4 -4
- data/.github/workflows/ruby.yml +27 -11
- data/.gitignore +1 -0
- data/.irbrc +4 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +17 -0
- data/Dockerfile +10 -3
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -18
- data/README.md +138 -11
- data/Vertofile +12 -19
- data/djin.gemspec +3 -1
- data/djin.yml +13 -2
- data/docker-compose.yml +16 -2
- data/docker-entrypoint.sh +7 -0
- data/examples/djin.yml +13 -7
- data/examples/djin_lib/test.yml +12 -0
- data/examples/local_tasks/.djin/server_tasks.yml +17 -0
- data/examples/local_tasks/djin.yml +22 -0
- data/examples/remote_tasks/djin.yml +9 -0
- data/lib/djin.rb +67 -17
- data/lib/djin/cli.rb +36 -0
- data/lib/djin/config_loader.rb +124 -30
- data/lib/djin/entities/include_config.rb +47 -0
- data/lib/djin/entities/include_config/base.rb +16 -0
- data/lib/djin/entities/include_config/local.rb +16 -0
- data/lib/djin/entities/{file_config.rb → main_config.rb} +16 -1
- data/lib/djin/extensions/hash_extensions.rb +14 -0
- data/lib/djin/extensions/object_extensions.rb +24 -0
- data/lib/djin/include_resolver.rb +50 -0
- data/lib/djin/interpreter.rb +11 -4
- data/lib/djin/interpreter/base_command_builder.rb +1 -0
- data/lib/djin/memory_cache.rb +17 -0
- data/lib/djin/repositories/remote_config_repository.rb +86 -0
- data/lib/djin/root_cli_parser.rb +46 -0
- data/lib/djin/version.rb +1 -1
- metadata +45 -3
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
class IncludeConfig < Dry::Struct
|
5
|
+
attribute :file, Types::String
|
6
|
+
attribute :base_directory, Types::String
|
7
|
+
attribute :context, Types::Hash.default({}.freeze)
|
8
|
+
attribute :git, Types::String.optional.default(nil)
|
9
|
+
attribute :version, Types::String.default('master')
|
10
|
+
attribute :missing, Types::Bool.optional.default(nil)
|
11
|
+
|
12
|
+
using Djin::ObjectExtensions
|
13
|
+
|
14
|
+
include Dry::Equalizer(:git, :version, :file, :context)
|
15
|
+
|
16
|
+
def type
|
17
|
+
@type ||= git.present? ? :remote : :local
|
18
|
+
end
|
19
|
+
|
20
|
+
def present?
|
21
|
+
!missing?
|
22
|
+
end
|
23
|
+
|
24
|
+
def missing?
|
25
|
+
missing
|
26
|
+
end
|
27
|
+
|
28
|
+
def full_path
|
29
|
+
base_directory_pathname.join(file).expand_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def repository_fetched?
|
33
|
+
@repository_fetched ||= base_directory_pathname.join(folder_name).exist?
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: Rethink
|
37
|
+
def folder_name
|
38
|
+
@folder_name ||= "#{git.split('/').last.chomp('.git')}@#{version}"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def base_directory_pathname
|
44
|
+
@base_directory_pathname ||= Pathname.new(base_directory)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
module IncludeConfig
|
5
|
+
class Base < Dry::Struct
|
6
|
+
attribute :file, Types::String
|
7
|
+
attribute :context, Types::Hash.default({}.freeze)
|
8
|
+
|
9
|
+
include Dry::Equalizer(:file, :context)
|
10
|
+
|
11
|
+
def type
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
module IncludeConfig
|
5
|
+
class Local < Dry::Struct
|
6
|
+
attribute :file, Types::String
|
7
|
+
attribute :context, Types::Hash.default({}.freeze)
|
8
|
+
|
9
|
+
include Dry::Equalizer(:file, :context)
|
10
|
+
|
11
|
+
def type
|
12
|
+
:local
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,17 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Djin
|
4
|
-
class
|
4
|
+
class MainConfig < 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
|
8
10
|
attribute :raw_tasks, Types::Hash
|
9
11
|
# TODO: Add env and args
|
12
|
+
attribute :include_configs, Types::Array.of(Djin::IncludeConfig).default([].freeze)
|
10
13
|
|
11
14
|
include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
|
12
15
|
|
13
16
|
def version_supported?
|
14
17
|
Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
|
15
18
|
end
|
19
|
+
|
20
|
+
def merge(file_config)
|
21
|
+
merged_hash = to_h.merge(file_config.to_h)
|
22
|
+
|
23
|
+
MainConfig.new(merged_hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
def deep_merge(file_config)
|
27
|
+
merged_hash = to_h.deep_merge(file_config.to_h)
|
28
|
+
|
29
|
+
MainConfig.new(merged_hash)
|
30
|
+
end
|
16
31
|
end
|
17
32
|
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
module ObjectExtensions
|
5
|
+
refine Object do
|
6
|
+
def presence(default = nil)
|
7
|
+
present? ? self : default
|
8
|
+
end
|
9
|
+
|
10
|
+
def present?
|
11
|
+
!blank?
|
12
|
+
end
|
13
|
+
|
14
|
+
def blank?
|
15
|
+
return true unless self
|
16
|
+
|
17
|
+
# TODO: Improve Validations
|
18
|
+
return empty? if respond_to?(:empty?)
|
19
|
+
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
class IncludeResolver
|
5
|
+
using ObjectExtensions
|
6
|
+
using HashExtensions
|
7
|
+
|
8
|
+
def initialize(base_directory: '.', remote_directory: '~/.djin/remote', entity_class: Djin::IncludeConfig)
|
9
|
+
# TODO: Use chain of responsability
|
10
|
+
@base_directory = Pathname.new(base_directory)
|
11
|
+
@remote_directory = Pathname.new(remote_directory)
|
12
|
+
@entity_class = entity_class
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(params)
|
16
|
+
include_config_params = remote_handler(params)
|
17
|
+
include_config_params ||= local_handler(params)
|
18
|
+
|
19
|
+
build_entity(include_config_params)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def remote_handler(params)
|
25
|
+
return if params['git'].blank?
|
26
|
+
|
27
|
+
version = params['version'] || 'master'
|
28
|
+
# TODO: Extract RemoteConfig git_folder in IncludeConfig to another place and use it here
|
29
|
+
# Maybe create a optional git_folder attribute and fill it in here?
|
30
|
+
git_folder = "#{params['git'].split('/').last.chomp('.git')}@#{version}"
|
31
|
+
|
32
|
+
# TODO: Use RemoteConfigRepository
|
33
|
+
remote_file = @remote_directory.join(git_folder).join(params['file']).expand_path
|
34
|
+
|
35
|
+
missing = !remote_file.exist?
|
36
|
+
|
37
|
+
params.merge(missing: missing, file: remote_file.to_s, base_directory: @remote_directory.expand_path.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def local_handler(params)
|
41
|
+
# TODO: Mark not existing files as missing and handle all the missing files
|
42
|
+
missing = !@base_directory.join(params['file']).exist?
|
43
|
+
params.merge(missing: missing, base_directory: @base_directory.expand_path.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_entity(params)
|
47
|
+
@entity_class.new(**params.symbolize_keys)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/djin/interpreter.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
|
@@ -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
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
class RemoteConfigRepository
|
5
|
+
attr_accessor :base_path
|
6
|
+
|
7
|
+
def initialize(remote_configs, base_path: Pathname.new('~/.djin/remote'), stderr: Djin.stderr)
|
8
|
+
@remote_configs = remote_configs
|
9
|
+
@base_path = base_path
|
10
|
+
@stderr = stderr
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(remote_config)
|
14
|
+
remote_configs << remote_config
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(**filters)
|
18
|
+
remote_configs.select do |remote_config|
|
19
|
+
filters.reduce(true) do |memo, (filter_key, filter_value)|
|
20
|
+
memo && remote_config.public_send(filter_key) == filter_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_all
|
26
|
+
remote_configs_by_folders.each do |rc|
|
27
|
+
git_folder = base_path.join(rc.folder_name).expand_path
|
28
|
+
|
29
|
+
# TODO: Extract STDEER Output, maybe publishing events and subscribing a observer for the logs.
|
30
|
+
stderr.puts "Remote Path: #{base_path.expand_path}"
|
31
|
+
|
32
|
+
git_repo = rc.missing? ? clone_repo(git_folder, rc) : fetch_repo(git_folder, rc)
|
33
|
+
|
34
|
+
stderr.puts "Checking out to '#{rc.version}'"
|
35
|
+
git_repo.checkout(rc.version)
|
36
|
+
git_repo.pull('origin', rc.version)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear
|
41
|
+
remote_configs_by_folders.each do |rc|
|
42
|
+
git_folder = base_path.join(rc.folder_name)
|
43
|
+
|
44
|
+
stderr.puts "Removing #{rc.folder_name} repository..."
|
45
|
+
`rm -rf #{git_folder}`
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear_all
|
50
|
+
remove_remote_folder
|
51
|
+
end
|
52
|
+
|
53
|
+
def remote_configs
|
54
|
+
@remote_configs ||= []
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_accessor :stderr
|
60
|
+
|
61
|
+
def remote_configs_by_folders
|
62
|
+
@remote_configs_by_folders ||= remote_configs
|
63
|
+
.group_by(&:folder_name)
|
64
|
+
.values
|
65
|
+
.map(&:first)
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_remote_folder
|
69
|
+
stderr.puts "Removing #{base_path}..."
|
70
|
+
`rm -rf #{base_path}`
|
71
|
+
end
|
72
|
+
|
73
|
+
def clone_repo(git_folder, remote_config)
|
74
|
+
stderr.puts "Missing #{remote_config.folder_name} repository, cloning in #{git_folder}"
|
75
|
+
Git.clone(remote_config.git.to_s, git_folder.to_s, branch: remote_config.version)
|
76
|
+
end
|
77
|
+
|
78
|
+
def fetch_repo(git_folder, remote_config)
|
79
|
+
stderr.puts "#{remote_config.git} repository already cloned, fetching..."
|
80
|
+
git_repo = Git.open(git_folder)
|
81
|
+
git_repo.fetch
|
82
|
+
|
83
|
+
git_repo
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
## This class is responsible to handle options that must be evaluated
|
5
|
+
# before the load of tasks in djin file(eg: djin.yml)
|
6
|
+
class RootCliParser
|
7
|
+
class << self
|
8
|
+
def parse!(args = ARGV)
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
# TODO: Find a better way to handle -f/--file option,
|
12
|
+
# throw, catch and delete in ARGV are necessary
|
13
|
+
# to only remove the -f/--file option
|
14
|
+
# and bypass everything else to Dry::CLI
|
15
|
+
catch(:root_cli_exit) do
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.on('-f FILE', '--file FILE') do |v|
|
18
|
+
options[:files] ||= []
|
19
|
+
options[:files] << v
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-h', '--help') do
|
23
|
+
throw :root_cli_exit
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on('--all') do
|
27
|
+
throw :root_cli_exit
|
28
|
+
end
|
29
|
+
end.parse(args)
|
30
|
+
end
|
31
|
+
|
32
|
+
remove_file_args!(args)
|
33
|
+
options
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove_file_args!(args)
|
37
|
+
file_option = ['-f', '--file']
|
38
|
+
args_indexes_to_remove = args.each_with_index.map do |value, index|
|
39
|
+
index if (file_option.include?(args[index - 1]) && index.positive?) || file_option.include?(value)
|
40
|
+
end.compact
|
41
|
+
|
42
|
+
args_indexes_to_remove.reverse.each { |index| args.delete_at(index) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/djin/version.rb
CHANGED
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.
|
4
|
+
version: 0.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Atkinson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-cli
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.5.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: git
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.8.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.1
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: mustache
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +178,20 @@ dependencies:
|
|
164
178
|
- - ">="
|
165
179
|
- !ruby/object:Gem::Version
|
166
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: simplecov
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 0.17.0
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 0.17.0
|
167
195
|
description:
|
168
196
|
email:
|
169
197
|
- carlos.atks@gmail.com
|
@@ -174,6 +202,7 @@ extra_rdoc_files: []
|
|
174
202
|
files:
|
175
203
|
- ".github/workflows/ruby.yml"
|
176
204
|
- ".gitignore"
|
205
|
+
- ".irbrc"
|
177
206
|
- ".rspec"
|
178
207
|
- ".rubocop.yml"
|
179
208
|
- ".rubocop_todo.yml"
|
@@ -191,22 +220,35 @@ files:
|
|
191
220
|
- djin.gemspec
|
192
221
|
- djin.yml
|
193
222
|
- docker-compose.yml
|
223
|
+
- docker-entrypoint.sh
|
194
224
|
- examples/djin.yml
|
225
|
+
- examples/djin_lib/test.yml
|
226
|
+
- examples/local_tasks/.djin/server_tasks.yml
|
227
|
+
- examples/local_tasks/djin.yml
|
228
|
+
- examples/remote_tasks/djin.yml
|
195
229
|
- exe/djin
|
196
230
|
- lib/djin.rb
|
197
231
|
- lib/djin/cli.rb
|
198
232
|
- lib/djin/config_loader.rb
|
199
|
-
- lib/djin/entities/
|
233
|
+
- lib/djin/entities/include_config.rb
|
234
|
+
- lib/djin/entities/include_config/base.rb
|
235
|
+
- lib/djin/entities/include_config/local.rb
|
236
|
+
- lib/djin/entities/main_config.rb
|
200
237
|
- lib/djin/entities/task.rb
|
201
238
|
- lib/djin/entities/types.rb
|
202
239
|
- lib/djin/executor.rb
|
203
240
|
- lib/djin/extensions/hash_extensions.rb
|
241
|
+
- lib/djin/extensions/object_extensions.rb
|
242
|
+
- lib/djin/include_resolver.rb
|
204
243
|
- lib/djin/interpreter.rb
|
205
244
|
- lib/djin/interpreter/base_command_builder.rb
|
206
245
|
- lib/djin/interpreter/docker_command_builder.rb
|
207
246
|
- lib/djin/interpreter/docker_compose_command_builder.rb
|
208
247
|
- lib/djin/interpreter/local_command_builder.rb
|
248
|
+
- lib/djin/memory_cache.rb
|
249
|
+
- lib/djin/repositories/remote_config_repository.rb
|
209
250
|
- lib/djin/repositories/task_repository.rb
|
251
|
+
- lib/djin/root_cli_parser.rb
|
210
252
|
- lib/djin/task_contract.rb
|
211
253
|
- lib/djin/version.rb
|
212
254
|
homepage: https://github.com/catks/djin
|