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.
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "djin"
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 "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
@@ -1,36 +1,40 @@
1
- lib = File.expand_path("lib", __dir__)
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 "djin/version"
5
+ require 'djin/version'
4
6
 
5
7
  Gem::Specification.new do |spec|
6
- spec.name = "djin"
8
+ spec.name = 'djin'
7
9
  spec.version = Djin::VERSION
8
- spec.authors = ["Carlos Atkinson"]
9
- spec.email = ["carlos.atks@gmail.com"]
10
+ spec.authors = ['Carlos Atkinson']
11
+ spec.email = ['carlos.atks@gmail.com']
10
12
 
11
- spec.summary = %q{djin is a make-like utility for docker containers}
12
- spec.homepage = "https://github.com/catks/djin"
13
- spec.license = "MIT"
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('..', __FILE__)) do
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 = "exe"
26
+ spec.bindir = 'exe'
25
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
28
+ spec.require_paths = ['lib']
27
29
 
28
- spec.add_dependency "dry-struct", "~> 1.3.0"
29
- spec.add_dependency "dry-cli", "~> 0.5.0"
30
- spec.add_dependency "dry-validation", "~> 1.5.0"
31
- spec.add_dependency "vseries", "~> 0.1.0"
32
- spec.add_development_dependency "bundler", "~> 2.0"
33
- spec.add_development_dependency "rake", "~> 13.0"
34
- spec.add_development_dependency "rspec", "~> 3.0"
35
- spec.add_development_dependency "byebug"
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.2.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
- test:
7
- docker-compose:
8
- service: app
9
- run:
10
- commands: "cd /usr/src/djin && rspec"
11
- <<: *default_run_options
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
- docker-compose:
15
- service: app
16
- run:
17
- commands: "sh"
18
- <<: *default_run_options
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
@@ -1,27 +1,29 @@
1
1
  ---
2
- default:
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
- script:
15
- docker:
16
- image: "ruby:2.6"
17
- run:
18
- commands:
19
- - "ruby /scripts/my_ruby_script.rb"
20
- options: "--rm -v $(pwd)/my_ruby_script.rb:/scripts/my_ruby_script.rb"
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
- docker:
25
- build: .
26
- run:
27
- - "ruby -e 'puts \" Hello\"'"
25
+ with_build:
26
+ docker:
27
+ build: .
28
+ run:
29
+ - "ruby -e 'puts \" Hello\"'"
data/exe/djin CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ Signal.trap('INT') { exit 2 }
2
5
 
3
6
  require_relative '../lib/djin'
4
7
 
@@ -1,35 +1,43 @@
1
- require "djin/version"
2
- require "pathname"
3
- require "yaml"
4
- require "dry-struct"
5
- require "dry-validation"
6
- require "vseries"
7
- require "dry/cli"
8
- require "djin/extensions/hash_extensions"
9
- require "djin/extensions/custom_predicates"
10
- require "djin/entities/types"
11
- require "djin/entities/task"
12
- require "djin/interpreter"
13
- require "djin/executor"
14
- require "djin/cli"
15
- require "djin/task_contract"
16
- require "djin/repositories/task_repository"
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
- djin_file = YAML.safe_load(path.read, [], [], true)
26
- tasks = Djin::Interpreter.load!(djin_file)
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
- rescue Djin::Interpreter::InvalidConfigurationError => ex
32
- abort(ex.message)
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
-
@@ -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(**options)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Djin
2
4
  class Task < Dry::Struct
3
5
  attribute :name, Types::String
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Djin
2
4
  module Types
3
5
  include Dry.Types()
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Djin
2
4
  class Executor
3
5
  def initialize(task_repository: Djin.task_repository)
@@ -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
@@ -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
- RESERVED_WORDS = %w[djin_version _default_options].freeze
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!(params)
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
- return build_docker_commands(docker_params, task_name: task_name) if docker_params
47
- build_docker_compose_commands(docker_compose_params) if docker_compose_params
48
- end
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
- def version_supported?(version)
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