djin 0.2.0 → 0.6.1
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 +14 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +17 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +33 -10
- data/README.md +133 -32
- data/Rakefile +6 -4
- data/Vertofile +45 -0
- data/bin/console +4 -3
- data/djin.gemspec +25 -21
- data/djin.yml +26 -13
- data/examples/djin.yml +25 -23
- data/exe/djin +3 -0
- data/lib/djin.rb +30 -23
- data/lib/djin/cli.rb +13 -1
- data/lib/djin/config_loader.rb +94 -0
- data/lib/djin/entities/task.rb +2 -0
- data/lib/djin/entities/types.rb +2 -0
- data/lib/djin/executor.rb +2 -0
- data/lib/djin/extensions/hash_extensions.rb +7 -1
- data/lib/djin/interpreter.rb +11 -63
- data/lib/djin/interpreter/base_command_builder.rb +26 -0
- data/lib/djin/interpreter/docker_command_builder.rb +29 -0
- data/lib/djin/interpreter/docker_compose_command_builder.rb +17 -0
- data/lib/djin/interpreter/local_command_builder.rb +11 -0
- data/lib/djin/repositories/task_repository.rb +2 -0
- data/lib/djin/task_contract.rb +37 -7
- data/lib/djin/version.rb +3 -1
- metadata +48 -11
- data/lib/djin/extensions/custom_predicates.rb +0 -18
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'djin'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "djin"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
data/djin.gemspec
CHANGED
@@ -1,36 +1,40 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
5
|
+
require 'djin/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
8
|
+
spec.name = 'djin'
|
7
9
|
spec.version = Djin::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
10
|
+
spec.authors = ['Carlos Atkinson']
|
11
|
+
spec.email = ['carlos.atks@gmail.com']
|
10
12
|
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage =
|
13
|
-
spec.license =
|
13
|
+
spec.summary = 'djin is a make-like utility for docker containers'
|
14
|
+
spec.homepage = 'https://github.com/catks/djin'
|
15
|
+
spec.license = 'MIT'
|
14
16
|
|
15
17
|
# spec.metadata["homepage_uri"] = spec.homepage
|
16
|
-
#spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
17
|
-
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
18
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
18
20
|
|
19
21
|
# Specify which files should be added to the gem when it is released.
|
20
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
-
spec.files = Dir.chdir(File.expand_path(
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
25
|
end
|
24
|
-
spec.bindir =
|
26
|
+
spec.bindir = 'exe'
|
25
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = [
|
28
|
+
spec.require_paths = ['lib']
|
27
29
|
|
28
|
-
spec.add_dependency
|
29
|
-
spec.add_dependency
|
30
|
-
spec.add_dependency
|
31
|
-
spec.add_dependency
|
32
|
-
spec.
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
30
|
+
spec.add_dependency 'dry-cli', '~> 0.6.0'
|
31
|
+
spec.add_dependency 'dry-struct', '~> 1.3.0'
|
32
|
+
spec.add_dependency 'dry-validation', '~> 1.5.1'
|
33
|
+
spec.add_dependency 'mustache', '~> 1.1.1'
|
34
|
+
spec.add_dependency 'vseries', '~> 0.1.0'
|
35
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
36
|
+
spec.add_development_dependency 'byebug'
|
37
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
38
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
39
|
+
spec.add_development_dependency 'rubocop'
|
36
40
|
end
|
data/djin.yml
CHANGED
@@ -1,18 +1,31 @@
|
|
1
|
-
djin_version: '0.
|
1
|
+
djin_version: '0.6.1'
|
2
2
|
|
3
3
|
_default_run_options: &default_run_options
|
4
4
|
options: "--rm --entrypoint=''"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
tasks:
|
7
|
+
test:
|
8
|
+
docker-compose:
|
9
|
+
service: app
|
10
|
+
run:
|
11
|
+
commands: "cd /usr/src/djin && rspec {{args}}"
|
12
|
+
<<: *default_run_options
|
12
13
|
|
13
|
-
sh:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
sh:
|
15
|
+
docker-compose:
|
16
|
+
service: app
|
17
|
+
run:
|
18
|
+
commands: "sh"
|
19
|
+
<<: *default_run_options
|
20
|
+
run:
|
21
|
+
docker-compose:
|
22
|
+
service: app
|
23
|
+
run:
|
24
|
+
commands: "sh -c '{{args}}'"
|
25
|
+
<<: *default_run_options
|
26
|
+
|
27
|
+
release:
|
28
|
+
local:
|
29
|
+
run:
|
30
|
+
- verto tag up {{args}}
|
31
|
+
- bundle exec rake release
|
data/examples/djin.yml
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
docker:
|
4
|
-
image: "ruby:2.5"
|
5
|
-
run:
|
6
|
-
- "ruby -e 'puts \\\" Hello\\\"'"
|
7
|
-
test:
|
8
|
-
docker-compose:
|
9
|
-
service: app
|
10
|
-
run:
|
11
|
-
commands: rspec
|
12
|
-
options: "--rm"
|
2
|
+
djin_version: '0.6.1'
|
13
3
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
- "ruby
|
20
|
-
|
4
|
+
tasks:
|
5
|
+
default:
|
6
|
+
docker:
|
7
|
+
image: "ruby:2.5"
|
8
|
+
run:
|
9
|
+
- "ruby -e 'puts \\\" Hello\\\"'"
|
10
|
+
test:
|
11
|
+
docker-compose:
|
12
|
+
service: app
|
13
|
+
run:
|
14
|
+
commands: rspec
|
15
|
+
options: "--rm"
|
21
16
|
|
17
|
+
script:
|
18
|
+
docker:
|
19
|
+
image: "ruby:2.6"
|
20
|
+
run:
|
21
|
+
commands:
|
22
|
+
- "ruby /scripts/my_ruby_script.rb"
|
23
|
+
options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
|
22
24
|
|
23
|
-
with_build:
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
with_build:
|
26
|
+
docker:
|
27
|
+
build: .
|
28
|
+
run:
|
29
|
+
- "ruby -e 'puts \" Hello\"'"
|
data/exe/djin
CHANGED
data/lib/djin.rb
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'djin/version'
|
4
|
+
require 'pathname'
|
5
|
+
require 'yaml'
|
6
|
+
require 'dry-struct'
|
7
|
+
require 'dry-validation'
|
8
|
+
require 'vseries'
|
9
|
+
require 'dry/cli'
|
10
|
+
require 'mustache'
|
11
|
+
require 'djin/extensions/hash_extensions'
|
12
|
+
require 'djin/entities/types'
|
13
|
+
require 'djin/entities/task'
|
14
|
+
require 'djin/interpreter/base_command_builder'
|
15
|
+
require 'djin/interpreter/docker_command_builder'
|
16
|
+
require 'djin/interpreter/docker_compose_command_builder'
|
17
|
+
require 'djin/interpreter/local_command_builder'
|
18
|
+
require 'djin/interpreter'
|
19
|
+
require 'djin/config_loader'
|
20
|
+
require 'djin/executor'
|
21
|
+
require 'djin/cli'
|
22
|
+
require 'djin/task_contract'
|
23
|
+
require 'djin/repositories/task_repository'
|
17
24
|
|
18
25
|
module Djin
|
19
26
|
class Error < StandardError; end
|
20
|
-
# Your code goes here...
|
21
27
|
|
22
28
|
def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
|
23
29
|
abort 'Error: djin.yml not found' unless path.exist?
|
24
30
|
|
25
|
-
|
26
|
-
|
31
|
+
djin_config = ConfigLoader.load!(path.read)
|
32
|
+
|
33
|
+
# TODO: Make all tasks be under 'tasks' key, passing only the tasks here
|
34
|
+
tasks = Interpreter.load!(djin_config)
|
27
35
|
|
28
36
|
@task_repository = TaskRepository.new(tasks)
|
29
37
|
CLI.load_tasks!(tasks)
|
30
|
-
|
31
|
-
|
32
|
-
abort(
|
38
|
+
rescue Djin::Interpreter::InvalidConfigurationError => e
|
39
|
+
error_name = e.class.name.split('::').last
|
40
|
+
abort("[#{error_name}] #{e.message}")
|
33
41
|
end
|
34
42
|
|
35
43
|
def self.tasks
|
@@ -40,4 +48,3 @@ module Djin
|
|
40
48
|
@task_repository ||= TaskRepository.new
|
41
49
|
end
|
42
50
|
end
|
43
|
-
|
data/lib/djin/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
class CLI
|
3
5
|
extend Dry::CLI::Registry
|
@@ -9,7 +11,7 @@ module Djin
|
|
9
11
|
|
10
12
|
define_method(:task) { task }
|
11
13
|
|
12
|
-
def call(**
|
14
|
+
def call(**)
|
13
15
|
Executor.new.call(task)
|
14
16
|
end
|
15
17
|
end
|
@@ -17,5 +19,15 @@ module Djin
|
|
17
19
|
register task.name, command
|
18
20
|
end
|
19
21
|
end
|
22
|
+
|
23
|
+
class Version < Dry::CLI::Command
|
24
|
+
desc 'Prints Djin Version'
|
25
|
+
|
26
|
+
def call(*)
|
27
|
+
puts Djin::VERSION
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
register '--version', Version, aliases: ['-v']
|
20
32
|
end
|
21
33
|
end
|
@@ -0,0 +1,94 @@
|
|
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].freeze
|
10
|
+
|
11
|
+
def self.load!(template_file)
|
12
|
+
new(template_file).load!
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(template_file)
|
16
|
+
@template_file = template_file
|
17
|
+
end
|
18
|
+
|
19
|
+
def load!
|
20
|
+
validate_version!
|
21
|
+
|
22
|
+
# TODO: Return a DjinConfig Entity
|
23
|
+
tasks
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def raw_djin_config
|
29
|
+
@raw_djin_config ||= yaml_load(@template_file)
|
30
|
+
rescue Psych::SyntaxError => e
|
31
|
+
raise Interpreter::InvalidConfigFileError, e.message
|
32
|
+
end
|
33
|
+
|
34
|
+
def rendered_djin_config
|
35
|
+
@rendered_djin_config ||= begin
|
36
|
+
locals = env.merge(variables)
|
37
|
+
|
38
|
+
rendered_yaml = Mustache.render(@template_file,
|
39
|
+
args: args.join(' '),
|
40
|
+
args?: args.any?,
|
41
|
+
**locals)
|
42
|
+
yaml_load(rendered_yaml)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def version
|
47
|
+
# TODO: Deprecates djin_version and use version instead
|
48
|
+
@version || raw_djin_config['djin_version']
|
49
|
+
end
|
50
|
+
|
51
|
+
def variables
|
52
|
+
@variables ||= raw_djin_config['variables']&.symbolize_keys || {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def tasks
|
56
|
+
rendered_djin_config['tasks'] || legacy_tasks
|
57
|
+
end
|
58
|
+
|
59
|
+
def legacy_tasks
|
60
|
+
warn '[DEPRECATED] Root tasks are deprecated and will be removed in Djin 1.0.0,' \
|
61
|
+
' put the tasks under \'tasks\' keyword'
|
62
|
+
|
63
|
+
rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
|
64
|
+
end
|
65
|
+
|
66
|
+
def args
|
67
|
+
index = ARGV.index('--')
|
68
|
+
|
69
|
+
return [] unless index
|
70
|
+
|
71
|
+
ARGV.slice((index + 1)..ARGV.size)
|
72
|
+
end
|
73
|
+
|
74
|
+
def env
|
75
|
+
@env ||= ENV.to_h.symbolize_keys
|
76
|
+
end
|
77
|
+
|
78
|
+
def yaml_load(text)
|
79
|
+
YAML.safe_load(text, [], [], true)
|
80
|
+
end
|
81
|
+
|
82
|
+
def version_supported?
|
83
|
+
Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(version)
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_version!
|
87
|
+
raise Interpreter::MissingVersionError, 'Missing djin_version' unless version
|
88
|
+
|
89
|
+
return if version_supported?
|
90
|
+
|
91
|
+
raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/djin/entities/task.rb
CHANGED
data/lib/djin/entities/types.rb
CHANGED
data/lib/djin/executor.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
module HashExtensions
|
3
5
|
refine Hash do
|
4
6
|
def except(*keys)
|
5
|
-
reject { |key,_| keys.include?(key) }
|
7
|
+
reject { |key, _| keys.include?(key) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def symbolize_keys
|
11
|
+
map { |key, value| [key.to_sym, value] }.to_h
|
6
12
|
end
|
7
13
|
end
|
8
14
|
end
|
data/lib/djin/interpreter.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
class Interpreter
|
3
5
|
using Djin::HashExtensions
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
# TODO: Move Errors to ConfigLoader
|
7
8
|
InvalidConfigurationError = Class.new(StandardError)
|
9
|
+
InvalidConfigFileError = Class.new(InvalidConfigurationError)
|
8
10
|
MissingVersionError = Class.new(InvalidConfigurationError)
|
9
11
|
VersionNotSupportedError = Class.new(InvalidConfigurationError)
|
10
12
|
InvalidSyntaxError = Class.new(InvalidConfigurationError)
|
11
13
|
|
12
14
|
class << self
|
13
|
-
def load!(
|
14
|
-
version = params['djin_version']
|
15
|
-
raise MissingVersionError, 'Missing djin_version' unless version
|
16
|
-
raise VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher" unless version_supported?(version)
|
17
|
-
|
18
|
-
tasks_params = params.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
|
15
|
+
def load!(tasks_params)
|
19
16
|
contract = TaskContract.new
|
20
17
|
|
21
18
|
tasks_params.map do |task_name, options|
|
22
19
|
result = contract.call(options)
|
23
20
|
|
24
|
-
raise InvalidSyntaxError, result.errors.to_h if result.failure?
|
21
|
+
raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
|
25
22
|
|
26
23
|
command, build_command = build_commands(options, task_name: task_name)
|
27
24
|
|
@@ -42,62 +39,13 @@ module Djin
|
|
42
39
|
# Validate that only one ot the two is passed
|
43
40
|
docker_params = params['docker']
|
44
41
|
docker_compose_params = params['docker-compose']
|
42
|
+
local_params = params['local']
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def build_docker_commands(params, task_name:)
|
51
|
-
current_folder_name = Pathname.getwd.basename.to_s
|
52
|
-
image = params['image'] || "djin_#{current_folder_name}_#{task_name}"
|
53
|
-
|
54
|
-
build_params = params['build']
|
55
|
-
|
56
|
-
if build_params.is_a?(Hash)
|
57
|
-
build_context = build_params['context']
|
58
|
-
build_options = build_params['options']
|
59
|
-
end
|
60
|
-
|
61
|
-
build_context ||= build_params
|
62
|
-
|
63
|
-
run_command, run_options = build_run_params(params['run'])
|
64
|
-
|
65
|
-
command = %Q{docker run #{run_options} #{image} sh -c "#{run_command}"}.squeeze(' ')
|
66
|
-
|
67
|
-
build_command = "docker build #{build_context} #{build_options} -t #{image}".squeeze(' ') if build_context
|
68
|
-
|
69
|
-
[command, build_command]
|
70
|
-
end
|
71
|
-
|
72
|
-
def build_docker_compose_commands(params)
|
73
|
-
service = params['service']
|
74
|
-
|
75
|
-
compose_options = params['options']
|
76
|
-
|
77
|
-
run_command, run_options = build_run_params(params['run'])
|
78
|
-
|
79
|
-
[%Q{docker-compose #{compose_options} run #{run_options} #{service} sh -c "#{run_command}"}.squeeze(' '), nil]
|
80
|
-
end
|
81
|
-
|
82
|
-
def build_run_params(run_params)
|
83
|
-
run_command = run_params
|
84
|
-
|
85
|
-
if run_params.is_a?(Hash)
|
86
|
-
run_command = run_params['commands']
|
87
|
-
run_options = run_params['options']
|
88
|
-
end
|
89
|
-
|
90
|
-
run_command = run_command.join(' && ') if run_command.is_a?(Array)
|
91
|
-
|
92
|
-
[run_command, run_options]
|
93
|
-
end
|
94
|
-
|
95
|
-
def validate_version!(version)
|
96
|
-
|
97
|
-
end
|
44
|
+
# TODO: Refactor to use chain of responsability
|
45
|
+
return DockerCommandBuilder.call(docker_params, task_name: task_name) if docker_params
|
46
|
+
return DockerComposeCommandBuilder.call(docker_compose_params) if docker_compose_params
|
98
47
|
|
99
|
-
|
100
|
-
Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(version)
|
48
|
+
LocalCommandBuilder.call(local_params) if local_params
|
101
49
|
end
|
102
50
|
end
|
103
51
|
end
|