djin 0.1.0 → 0.5.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 +4 -4
- data/.github/workflows/ruby.yml +34 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +17 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +38 -13
- data/README.md +125 -5
- data/Rakefile +6 -4
- data/Vertofile +45 -0
- data/bin/console +4 -3
- data/djin.gemspec +25 -20
- data/djin.yml +28 -2
- data/examples/djin.yml +3 -1
- data/exe/djin +3 -0
- data/lib/djin.rb +35 -22
- data/lib/djin/cli.rb +13 -1
- data/lib/djin/entities/task.rb +4 -1
- data/lib/djin/entities/types.rb +2 -0
- data/lib/djin/executor.rb +24 -10
- data/lib/djin/extensions/hash_extensions.rb +3 -1
- data/lib/djin/interpreter.rb +32 -50
- 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 +19 -0
- data/lib/djin/task_contract.rb +39 -7
- data/lib/djin/template_renderer.rb +34 -0
- data/lib/djin/version.rb +3 -1
- metadata +64 -12
- data/lib/djin/extensions/custom_predicates.rb +0 -18
data/djin.gemspec
CHANGED
@@ -1,35 +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.
|
32
|
-
spec.
|
33
|
-
spec.add_development_dependency
|
34
|
-
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'
|
35
40
|
end
|
data/djin.yml
CHANGED
@@ -1,7 +1,33 @@
|
|
1
|
+
djin_version: '0.5.0'
|
2
|
+
|
3
|
+
_default_run_options: &default_run_options
|
4
|
+
options: "--rm --entrypoint=''"
|
5
|
+
|
1
6
|
test:
|
2
7
|
docker-compose:
|
3
8
|
service: app
|
4
9
|
run:
|
5
|
-
commands: "cd /usr/src/djin && rspec"
|
6
|
-
|
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
|
+
|
32
|
+
|
7
33
|
|
data/examples/djin.yml
CHANGED
data/exe/djin
CHANGED
data/lib/djin.rb
CHANGED
@@ -1,36 +1,49 @@
|
|
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
|
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/template_renderer'
|
20
|
+
require 'djin/executor'
|
21
|
+
require 'djin/cli'
|
22
|
+
require 'djin/task_contract'
|
23
|
+
require 'djin/repositories/task_repository'
|
15
24
|
|
16
25
|
module Djin
|
17
26
|
class Error < StandardError; end
|
18
|
-
# Your code goes here...
|
19
27
|
|
20
28
|
def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
|
21
29
|
abort 'Error: djin.yml not found' unless path.exist?
|
22
30
|
|
23
|
-
|
24
|
-
|
31
|
+
rendered_djin_file = TemplateRenderer.render(path.read)
|
32
|
+
djin_config = YAML.safe_load(rendered_djin_file, [], [], true)
|
25
33
|
|
26
|
-
|
34
|
+
tasks = Interpreter.load!(djin_config)
|
27
35
|
|
28
|
-
|
29
|
-
|
36
|
+
@task_repository = TaskRepository.new(tasks)
|
37
|
+
CLI.load_tasks!(tasks)
|
38
|
+
rescue Djin::Interpreter::InvalidConfigurationError => e
|
39
|
+
abort(e.message)
|
30
40
|
end
|
31
41
|
|
32
42
|
def self.tasks
|
33
|
-
|
43
|
+
task_repository.all
|
34
44
|
end
|
35
|
-
end
|
36
45
|
|
46
|
+
def self.task_repository
|
47
|
+
@task_repository ||= TaskRepository.new
|
48
|
+
end
|
49
|
+
end
|
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
|
data/lib/djin/entities/task.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
class Task < Dry::Struct
|
3
5
|
attribute :name, Types::String
|
4
6
|
attribute :description, Types::String.optional.default(nil)
|
5
7
|
attribute :build_command, Types::String.optional.default(nil)
|
6
|
-
attribute :command, Types::String
|
8
|
+
attribute :command, Types::String.optional.default(nil)
|
9
|
+
attribute :depends_on, Types::Array.of(Types::String).optional.default([].freeze)
|
7
10
|
|
8
11
|
def ==(other)
|
9
12
|
name == other.name &&
|
data/lib/djin/entities/types.rb
CHANGED
data/lib/djin/executor.rb
CHANGED
@@ -1,16 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
class Executor
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def initialize(task_repository: Djin.task_repository)
|
6
|
+
@task_repository = task_repository
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(*tasks)
|
10
|
+
tasks.each do |task|
|
11
|
+
run_task(task)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def run_task(task)
|
18
|
+
@task_repository.find_by_names(task.depends_on).each do |dependent_task|
|
19
|
+
run_task dependent_task
|
20
|
+
end
|
9
21
|
|
10
|
-
|
22
|
+
run task.build_command if task.build_command
|
23
|
+
run task.command if task.command
|
24
|
+
end
|
11
25
|
|
12
|
-
|
13
|
-
|
14
|
-
|
26
|
+
def run(command)
|
27
|
+
system command
|
28
|
+
end
|
15
29
|
end
|
16
30
|
end
|
data/lib/djin/interpreter.rb
CHANGED
@@ -1,23 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Djin
|
2
4
|
class Interpreter
|
3
5
|
using Djin::HashExtensions
|
4
6
|
|
5
|
-
RESERVED_WORDS = %w[
|
7
|
+
RESERVED_WORDS = %w[djin_version _default_options].freeze
|
6
8
|
|
7
|
-
|
9
|
+
InvalidConfigurationError = Class.new(StandardError)
|
10
|
+
MissingVersionError = Class.new(InvalidConfigurationError)
|
11
|
+
VersionNotSupportedError = Class.new(InvalidConfigurationError)
|
12
|
+
InvalidSyntaxError = Class.new(InvalidConfigurationError)
|
8
13
|
|
9
14
|
class << self
|
10
15
|
def load!(params)
|
11
|
-
|
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?('_') }
|
12
23
|
contract = TaskContract.new
|
13
24
|
|
14
|
-
|
25
|
+
tasks_params.map do |task_name, options|
|
15
26
|
result = contract.call(options)
|
16
27
|
|
17
|
-
raise
|
28
|
+
raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
|
18
29
|
|
19
30
|
command, build_command = build_commands(options, task_name: task_name)
|
20
|
-
|
31
|
+
|
32
|
+
task_params = {
|
33
|
+
name: task_name,
|
34
|
+
build_command: build_command,
|
35
|
+
command: command,
|
36
|
+
depends_on: options['depends_on']
|
37
|
+
}.compact
|
38
|
+
|
39
|
+
Djin::Task.new(**task_params)
|
21
40
|
end
|
22
41
|
end
|
23
42
|
|
@@ -27,54 +46,17 @@ module Djin
|
|
27
46
|
# Validate that only one ot the two is passed
|
28
47
|
docker_params = params['docker']
|
29
48
|
docker_compose_params = params['docker-compose']
|
49
|
+
local_params = params['local']
|
30
50
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def build_docker_commands(params, task_name:)
|
36
|
-
current_folder_name = Pathname.getwd.basename.to_s
|
37
|
-
image = params['image'] || "djin_#{current_folder_name}_#{task_name}"
|
38
|
-
|
39
|
-
build_params = params['build']
|
40
|
-
|
41
|
-
if build_params.is_a?(Hash)
|
42
|
-
build_context = build_params['context']
|
43
|
-
build_options = build_params['options']
|
44
|
-
end
|
45
|
-
|
46
|
-
build_context ||= build_params
|
47
|
-
|
48
|
-
run_command, run_options = build_run_params(params['run'])
|
51
|
+
# TODO: Refactor to use chain of responsability
|
52
|
+
return DockerCommandBuilder.call(docker_params, task_name: task_name) if docker_params
|
53
|
+
return DockerComposeCommandBuilder.call(docker_compose_params) if docker_compose_params
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
build_command = "docker build #{build_context} #{build_options} -t #{image}".squeeze(' ') if build_context
|
53
|
-
|
54
|
-
[command, build_command]
|
55
|
+
LocalCommandBuilder.call(local_params) if local_params
|
55
56
|
end
|
56
57
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
compose_options = params['options']
|
61
|
-
|
62
|
-
run_command, run_options = build_run_params(params['run'])
|
63
|
-
|
64
|
-
[%Q{docker-compose #{compose_options} run #{run_options} #{service} sh -c "#{run_command}"}.squeeze(' '), nil]
|
65
|
-
end
|
66
|
-
|
67
|
-
def build_run_params(run_params)
|
68
|
-
run_command = run_params
|
69
|
-
|
70
|
-
if run_params.is_a?(Hash)
|
71
|
-
run_command = run_params['commands']
|
72
|
-
run_options = run_params['options']
|
73
|
-
end
|
74
|
-
|
75
|
-
run_command = run_command.join(' && ') if run_command.is_a?(Array)
|
76
|
-
|
77
|
-
[run_command, run_options]
|
58
|
+
def version_supported?(version)
|
59
|
+
Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(version)
|
78
60
|
end
|
79
61
|
end
|
80
62
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
class Interpreter
|
5
|
+
class BaseCommandBuilder
|
6
|
+
def self.call(*options)
|
7
|
+
new.call(*options)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def build_run_params(run_params)
|
13
|
+
run_command = run_params
|
14
|
+
|
15
|
+
if run_params.is_a?(Hash)
|
16
|
+
run_command = run_params['commands']
|
17
|
+
run_options = run_params['options']
|
18
|
+
end
|
19
|
+
|
20
|
+
run_command = run_command.join(' && ') if run_command.is_a?(Array)
|
21
|
+
|
22
|
+
[run_command, run_options]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Djin
|
4
|
+
class Interpreter
|
5
|
+
class DockerCommandBuilder < BaseCommandBuilder
|
6
|
+
def call(params, task_name:)
|
7
|
+
current_folder_name = Pathname.getwd.basename.to_s
|
8
|
+
image = params['image'] || "djin_#{current_folder_name}_#{task_name}"
|
9
|
+
|
10
|
+
build_params = params['build']
|
11
|
+
|
12
|
+
if build_params.is_a?(Hash)
|
13
|
+
build_context = build_params['context']
|
14
|
+
build_options = build_params['options']
|
15
|
+
end
|
16
|
+
|
17
|
+
build_context ||= build_params
|
18
|
+
|
19
|
+
run_command, run_options = build_run_params(params['run'])
|
20
|
+
|
21
|
+
command = %(docker run #{run_options} #{image} sh -c "#{run_command}").squeeze(' ')
|
22
|
+
|
23
|
+
build_command = "docker build #{build_context} #{build_options} -t #{image}".squeeze(' ') if build_context
|
24
|
+
|
25
|
+
[command, build_command]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|