djin 0.3.0 → 0.7.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.
@@ -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,41 @@
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-equalizer', '~> 0.3.0'
32
+ spec.add_dependency 'dry-struct', '~> 1.3.0'
33
+ spec.add_dependency 'dry-validation', '~> 1.5.1'
34
+ spec.add_dependency 'mustache', '~> 1.1.1'
35
+ spec.add_dependency 'vseries', '~> 0.1.0'
36
+ spec.add_development_dependency 'bundler', '~> 2.0'
37
+ spec.add_development_dependency 'byebug'
38
+ spec.add_development_dependency 'rake', '~> 13.0'
39
+ spec.add_development_dependency 'rspec', '~> 3.0'
40
+ spec.add_development_dependency 'rubocop'
36
41
  end
data/djin.yml CHANGED
@@ -1,18 +1,33 @@
1
- djin_version: '0.2.0'
1
+ djin_version: '0.7.0'
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
+ description: Runs Specs
9
+ docker-compose:
10
+ service: app
11
+ run:
12
+ commands: "cd /usr/src/djin && rspec {{args}}"
13
+ <<: *default_run_options
12
14
 
13
- sh:
14
- docker-compose:
15
- service: app
16
- run:
17
- commands: "sh"
18
- <<: *default_run_options
15
+ sh:
16
+ description: Enter app service shell
17
+ docker-compose:
18
+ service: app
19
+ run:
20
+ commands: "sh"
21
+ <<: *default_run_options
22
+ run:
23
+ docker-compose:
24
+ service: app
25
+ run:
26
+ commands: "sh -c '{{args}}'"
27
+ <<: *default_run_options
28
+
29
+ release:
30
+ local:
31
+ run:
32
+ - verto tag up {{args}}
33
+ - 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.7.0'
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,44 @@
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/entities/file_config'
15
+ require 'djin/interpreter/base_command_builder'
16
+ require 'djin/interpreter/docker_command_builder'
17
+ require 'djin/interpreter/docker_compose_command_builder'
18
+ require 'djin/interpreter/local_command_builder'
19
+ require 'djin/interpreter'
20
+ require 'djin/config_loader'
21
+ require 'djin/executor'
22
+ require 'djin/cli'
23
+ require 'djin/task_contract'
24
+ require 'djin/repositories/task_repository'
17
25
 
18
26
  module Djin
19
27
  class Error < StandardError; end
20
- # Your code goes here...
21
28
 
22
29
  def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
23
30
  abort 'Error: djin.yml not found' unless path.exist?
24
31
 
25
- djin_file = YAML.safe_load(path.read, [], [], true)
26
- tasks = Djin::Interpreter.load!(djin_file)
32
+ file_config = ConfigLoader.load!(path.read)
33
+
34
+ # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
35
+ tasks = Interpreter.load!(file_config)
27
36
 
28
37
  @task_repository = TaskRepository.new(tasks)
29
38
  CLI.load_tasks!(tasks)
30
-
31
- rescue Djin::Interpreter::InvalidConfigurationError => ex
32
- abort(ex.message)
39
+ rescue Djin::Interpreter::InvalidConfigurationError => e
40
+ error_name = e.class.name.split('::').last
41
+ abort("[#{error_name}] #{e.message}")
33
42
  end
34
43
 
35
44
  def self.tasks
@@ -40,4 +49,3 @@ module Djin
40
49
  @task_repository ||= TaskRepository.new
41
50
  end
42
51
  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
@@ -5,23 +7,21 @@ module Djin
5
7
  def self.load_tasks!(tasks)
6
8
  tasks.each do |task|
7
9
  command = Class.new(Dry::CLI::Command) do
8
- desc "Runs: #{task.command}"
10
+ desc task.description
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
16
18
 
17
19
  register task.name, command
18
-
19
-
20
20
  end
21
21
  end
22
22
 
23
23
  class Version < Dry::CLI::Command
24
- desc "Prints Djin Version"
24
+ desc 'Prints Djin Version'
25
25
 
26
26
  def call(*)
27
27
  puts Djin::VERSION
@@ -0,0 +1,106 @@
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
+ file_config
23
+ end
24
+
25
+ private
26
+
27
+ def file_config
28
+ FileConfig.new(
29
+ djin_version: version,
30
+ variables: variables,
31
+ tasks: tasks,
32
+ raw_tasks: raw_tasks
33
+ )
34
+ end
35
+
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
+ def version
55
+ # TODO: Deprecates djin_version and use version instead
56
+ @version || raw_djin_config['djin_version']
57
+ end
58
+
59
+ def variables
60
+ @variables ||= raw_djin_config['variables']&.symbolize_keys || {}
61
+ end
62
+
63
+ def tasks
64
+ rendered_djin_config['tasks'] || legacy_tasks
65
+ end
66
+
67
+ def raw_tasks
68
+ raw_djin_config['tasks'] || legacy_raw_tasks
69
+ end
70
+
71
+ def legacy_tasks
72
+ warn '[DEPRECATED] Root tasks are deprecated and will be removed in Djin 1.0.0,' \
73
+ ' put the tasks under \'tasks\' keyword'
74
+
75
+ rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
76
+ end
77
+
78
+ def legacy_raw_tasks
79
+ raw_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
80
+ end
81
+
82
+ def args
83
+ index = ARGV.index('--')
84
+
85
+ return [] unless index
86
+
87
+ ARGV.slice((index + 1)..ARGV.size)
88
+ end
89
+
90
+ def env
91
+ @env ||= ENV.to_h.symbolize_keys
92
+ end
93
+
94
+ def yaml_load(text)
95
+ YAML.safe_load(text, [], [], true)
96
+ end
97
+
98
+ def validate_version!
99
+ raise Interpreter::MissingVersionError, 'Missing djin_version' unless version
100
+
101
+ return if file_config.version_supported?
102
+
103
+ raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class FileConfig < Dry::Struct
5
+ attribute :djin_version, Types::String
6
+ attribute :variables, Types::Hash.optional.default({}.freeze)
7
+ attribute :tasks, Types::Hash
8
+ attribute :raw_tasks, Types::Hash
9
+ # TODO: Add env and args
10
+
11
+ include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
12
+
13
+ def version_supported?
14
+ Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
15
+ end
16
+ end
17
+ end
@@ -1,15 +1,14 @@
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
8
  attribute :command, Types::String.optional.default(nil)
9
+ attribute :raw_command, Types::String.optional.default(nil)
7
10
  attribute :depends_on, Types::Array.of(Types::String).optional.default([].freeze)
8
11
 
9
- def ==(other)
10
- name == other.name &&
11
- command == other.command &&
12
- build_command == other.build_command
13
- end
12
+ include Dry::Equalizer(:name, :command, :build_command)
14
13
  end
15
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Djin
2
4
  module Types
3
5
  include Dry.Types()