dip 4.2.0 → 7.0.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,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../command'
4
- require_relative '../interaction_tree'
3
+ require_relative "../command"
4
+ require_relative "../interaction_tree"
5
5
 
6
6
  module Dip
7
7
  module Commands
@@ -12,7 +12,7 @@ module Dip
12
12
  longest_name = tree.keys.map(&:size).max
13
13
 
14
14
  tree.each do |name, command|
15
- puts "#{name.ljust(longest_name)} ##{command[:description] ? ' ' + command[:description] : ''}"
15
+ puts "#{name.ljust(longest_name)} ##{command[:description] ? " #{command[:description]}" : ""}"
16
16
  end
17
17
  end
18
18
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "shellwords"
4
- require_relative '../command'
4
+ require_relative "../command"
5
5
 
6
6
  module Dip
7
7
  module Commands
@@ -25,15 +25,15 @@ module Dip
25
25
  private
26
26
 
27
27
  def container_args
28
- result = %w(--detach)
28
+ result = %w[--detach]
29
29
  result << "--volume #{@socket}:/tmp/docker.sock:ro"
30
30
  result << "--volume #{@certs}:/etc/nginx/certs" unless @certs.to_s.empty?
31
31
  result << "--restart always"
32
- result << Array(@publish).map { |p| "--publish #{p}" }.join(' ')
32
+ result << Array(@publish).map { |p| "--publish #{p}" }.join(" ")
33
33
  result << "--net #{@net}"
34
34
  result << "--name #{@name}"
35
35
  result << "--label com.dnsdock.alias=#{@domain}"
36
- result.join(' ')
36
+ result.join(" ")
37
37
  end
38
38
  end
39
39
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../command'
3
+ require_relative "../command"
4
4
 
5
5
  module Dip
6
6
  module Commands
@@ -1,50 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'shellwords'
4
- require_relative '../command'
5
- require_relative '../interaction_tree'
6
- require_relative 'compose'
3
+ require "shellwords"
4
+ require_relative "../../../lib/dip/run_vars"
5
+ require_relative "../command"
6
+ require_relative "../interaction_tree"
7
+ require_relative "compose"
7
8
 
8
9
  module Dip
9
10
  module Commands
10
11
  class Run < Dip::Command
11
- def initialize(cmd, *argv)
12
- @command, @argv = InteractionTree.
13
- new(Dip.config.interaction).
14
- find(cmd, *argv)&.
15
- values_at(:command, :argv)
12
+ def initialize(cmd, *argv, publish: nil)
13
+ @publish = publish
16
14
 
17
- raise Dip::Error, "Command `#{[cmd, *argv].join(' ')}` not recognized!" unless command
15
+ @command, @argv = InteractionTree
16
+ .new(Dip.config.interaction)
17
+ .find(cmd, *argv)&.values_at(:command, :argv)
18
+
19
+ raise Dip::Error, "Command `#{[cmd, *argv].join(" ")}` not recognized!" unless command
18
20
 
19
21
  Dip.env.merge(command[:environment])
20
22
  end
21
23
 
22
24
  def execute
23
- Dip::Commands::Compose.new(
24
- command[:compose][:method],
25
- *compose_arguments
26
- ).execute
25
+ if command[:service].nil?
26
+ shell(command[:command], get_args)
27
+ else
28
+ Dip::Commands::Compose.new(
29
+ command[:compose][:method],
30
+ *compose_arguments
31
+ ).execute
32
+ end
27
33
  end
28
34
 
29
35
  private
30
36
 
31
- attr_reader :command, :argv
37
+ attr_reader :command, :argv, :publish
32
38
 
33
39
  def compose_arguments
34
40
  compose_argv = command[:compose][:run_options].dup
35
41
 
36
42
  if command[:compose][:method] == "run"
37
43
  compose_argv.concat(run_vars)
44
+ compose_argv.concat(published_ports)
38
45
  compose_argv << "--rm"
39
46
  end
40
47
 
41
48
  compose_argv << command.fetch(:service)
42
49
 
43
- unless (cmd = command[:command].to_s).empty?
44
- compose_argv.concat(cmd.shellsplit)
50
+ unless (cmd = command[:command]).empty?
51
+ compose_argv << cmd
45
52
  end
46
53
 
47
- compose_argv.concat(argv.any? ? argv : command[:default_args])
54
+ compose_argv.concat(get_args)
48
55
 
49
56
  compose_argv
50
57
  end
@@ -53,7 +60,25 @@ module Dip
53
60
  run_vars = Dip::RunVars.env
54
61
  return [] unless run_vars
55
62
 
56
- run_vars.map { |k, v| ["-e", "#{k}=#{v}"] }.flatten
63
+ run_vars.map { |k, v| ["-e", "#{k}=#{Shellwords.escape(v)}"] }.flatten
64
+ end
65
+
66
+ def published_ports
67
+ if publish.respond_to?(:each)
68
+ publish.map { |p| "--publish=#{p}" }
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ def get_args
75
+ if argv.any?
76
+ argv
77
+ elsif !(default_args = command[:default_args]).empty?
78
+ Array(default_args)
79
+ else
80
+ []
81
+ end
57
82
  end
58
83
  end
59
84
  end
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "shellwords"
4
- require_relative '../command'
4
+ require_relative "../command"
5
5
 
6
6
  module Dip
7
7
  module Commands
8
8
  module SSH
9
9
  class Up < Dip::Command
10
- def initialize(key:, volume:, interactive:)
10
+ def initialize(key:, volume:, interactive:, user: nil)
11
11
  @key = key
12
12
  @volume = volume
13
13
  @interactive = interactive
14
+ @user = user
14
15
  end
15
16
 
16
17
  def execute
17
18
  subshell("docker", "volume create --name ssh_data".shellsplit, out: File::NULL, err: File::NULL)
18
19
 
19
- subshell("docker", "run --detach --volume ssh_data:/ssh --name=ssh-agent whilp/ssh-agent".shellsplit)
20
+ subshell(
21
+ "docker",
22
+ "run #{user_args} --detach --volume ssh_data:/ssh --name=ssh-agent whilp/ssh-agent".shellsplit
23
+ )
20
24
 
21
25
  key = Dip.env.interpolate(@key)
22
26
  subshell("docker", "run #{container_args} whilp/ssh-agent ssh-add #{key}".shellsplit)
@@ -24,13 +28,17 @@ module Dip
24
28
 
25
29
  private
26
30
 
31
+ def user_args
32
+ "-u #{@user}" if @user
33
+ end
34
+
27
35
  def container_args
28
- result = %w(--rm)
36
+ result = %w[--rm]
29
37
  volume = Dip.env.interpolate(@volume)
30
38
  result << "--volume ssh_data:/ssh"
31
39
  result << "--volume #{volume}:#{volume}"
32
40
  result << "--interactive --tty" if @interactive
33
- result.join(' ')
41
+ result.join(" ")
34
42
  end
35
43
  end
36
44
 
data/lib/dip/config.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "yaml"
4
4
  require "erb"
5
+ require "pathname"
5
6
 
6
7
  require "dip/version"
7
8
  require "dip/ext/hash"
@@ -12,47 +13,98 @@ module Dip
12
13
  class Config
13
14
  DEFAULT_PATH = "dip.yml"
14
15
 
15
- class << self
16
- def exist?
17
- File.exist?(path)
16
+ CONFIG_DEFAULTS = {
17
+ environment: {},
18
+ compose: {},
19
+ interation: {},
20
+ provision: []
21
+ }.freeze
22
+
23
+ ConfigKeyMissingError = Class.new(ArgumentError)
24
+
25
+ class ConfigFinder
26
+ attr_reader :file_path
27
+
28
+ def initialize(work_dir, override: false)
29
+ @override = override
30
+
31
+ @file_path = if ENV["DIP_FILE"]
32
+ Pathname.new(prepared_name(ENV["DIP_FILE"]))
33
+ else
34
+ find(Pathname.new(work_dir))
35
+ end
18
36
  end
19
37
 
20
- def path
21
- ENV["DIP_FILE"] || File.join(Dir.pwd, DEFAULT_PATH)
38
+ def exist?
39
+ file_path&.exist?
22
40
  end
23
41
 
24
- def override_path
42
+ private
43
+
44
+ attr_reader :override
45
+
46
+ def prepared_name(path)
47
+ return path unless override
48
+
25
49
  path.gsub(/\.yml$/, ".override.yml")
26
50
  end
27
51
 
52
+ def find(path)
53
+ file = path.join(prepared_name(DEFAULT_PATH))
54
+ return file if file.exist?
55
+ return if path.root?
56
+
57
+ find(path.parent)
58
+ end
59
+ end
60
+
61
+ class << self
28
62
  def load_yaml(file_path = path)
29
63
  return {} unless File.exist?(file_path)
30
64
 
31
65
  YAML.safe_load(
32
66
  ERB.new(File.read(file_path)).result,
33
67
  [], [], true
34
- ).deep_symbolize_keys!
68
+ )&.deep_symbolize_keys! || {}
35
69
  end
36
70
  end
37
71
 
38
- %i[environment compose interaction provision].each do |key|
39
- define_method(key) do
40
- config[key]
41
- end
72
+ def initialize(work_dir = Dir.pwd)
73
+ @work_dir = work_dir
74
+ end
75
+
76
+ def file_path
77
+ finder.file_path
78
+ end
79
+
80
+ def exist?
81
+ finder.exist?
42
82
  end
43
83
 
44
84
  def to_h
45
85
  config
46
86
  end
47
87
 
88
+ %i[environment compose interaction provision].each do |key|
89
+ define_method(key) do
90
+ config[key] || (raise config_missing_error(key))
91
+ end
92
+ end
93
+
48
94
  private
49
95
 
96
+ attr_reader :work_dir
97
+
98
+ def finder
99
+ @finder ||= ConfigFinder.new(work_dir)
100
+ end
101
+
50
102
  def config
51
103
  return @config if @config
52
104
 
53
- raise ArgumentError, "Dip config not found at path '#{self.class.path}'" unless self.class.exist?
105
+ raise Dip::Error, "Could not find dip.yml config" unless finder.exist?
54
106
 
55
- config = self.class.load_yaml
107
+ config = self.class.load_yaml(finder.file_path)
56
108
 
57
109
  unless Gem::Version.new(Dip::VERSION) >= Gem::Version.new(config.fetch(:version))
58
110
  raise VersionMismatchError, "Your dip version is `#{Dip::VERSION}`, " \
@@ -60,9 +112,15 @@ module Dip
60
112
  "Please upgrade your dip!"
61
113
  end
62
114
 
63
- config.deep_merge!(self.class.load_yaml(self.class.override_path))
115
+ override_finder = ConfigFinder.new(work_dir, override: true)
116
+ config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?
117
+
118
+ @config = CONFIG_DEFAULTS.merge(config)
119
+ end
64
120
 
65
- @config = config
121
+ def config_missing_error(config_key)
122
+ msg = "config for %<key>s is not defined in %<path>s" % {key: config_key, path: finder.file_path}
123
+ ConfigKeyMissingError.new(msg)
66
124
  end
67
125
  end
68
126
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  module Dip
4
6
  class Environment
5
- VAR_REGEX = /\$[\{]?(?<var_name>[a-zA-Z_][a-zA-Z0-9_]*)[\}]?/.freeze
6
- SPECIAL_VARS = {"DIP_OS" => :find_dip_os}.freeze
7
+ VAR_REGEX = /\$\{?(?<var_name>[a-zA-Z_][a-zA-Z0-9_]*)\}?/.freeze
8
+ SPECIAL_VARS = %i[os work_dir_rel_path].freeze
7
9
 
8
10
  attr_reader :vars
9
11
 
@@ -24,28 +26,42 @@ module Dip
24
26
  vars.fetch(name) { ENV[name] }
25
27
  end
26
28
 
29
+ def fetch(name, &block)
30
+ vars.fetch(name) { ENV.fetch(name, &block) }
31
+ end
32
+
27
33
  def []=(key, value)
28
34
  @vars[key] = value
29
35
  end
30
36
 
31
37
  def interpolate(value)
32
- value.gsub(VAR_REGEX) do
38
+ value.gsub(VAR_REGEX) do |match|
33
39
  var_name = Regexp.last_match[:var_name]
34
40
 
35
- if SPECIAL_VARS.key?(var_name)
36
- self[var_name] || send(SPECIAL_VARS[var_name])
41
+ if special_vars.key?(var_name)
42
+ fetch(var_name) { send(special_vars[var_name]) }
37
43
  else
38
- self[var_name]
44
+ fetch(var_name) { match }
39
45
  end
40
46
  end
41
47
  end
42
48
 
43
- alias replace interpolate
49
+ alias_method :replace, :interpolate
44
50
 
45
51
  private
46
52
 
47
- def find_dip_os
53
+ def special_vars
54
+ @special_vars ||= SPECIAL_VARS.each_with_object({}) do |key, memo|
55
+ memo["DIP_#{key.to_s.upcase}"] = "find_#{key}"
56
+ end
57
+ end
58
+
59
+ def find_os
48
60
  @dip_os ||= Gem::Platform.local.os
49
61
  end
62
+
63
+ def find_work_dir_rel_path
64
+ @find_work_dir_rel_path ||= Pathname.getwd.relative_path_from(Dip.config.file_path.parent).to_s
65
+ end
50
66
  end
51
67
  end
data/lib/dip/ext/hash.rb CHANGED
@@ -24,7 +24,7 @@ module ActiveSupportHashHelpers
24
24
  merge!(other_hash) do |key, this_val, other_val|
25
25
  if this_val.is_a?(Hash) && other_val.is_a?(Hash)
26
26
  this_val.deep_merge(other_val, &block)
27
- elsif block_given?
27
+ elsif block
28
28
  block.call(key, this_val, other_val)
29
29
  else
30
30
  other_val
@@ -58,9 +58,9 @@ module Dip
58
58
  def build_command(entry)
59
59
  {
60
60
  description: entry[:description],
61
- service: entry.fetch(:service),
62
- command: entry[:command],
63
- default_args: prepare_default_args(entry[:default_args]),
61
+ service: entry[:service],
62
+ command: entry[:command].to_s.strip,
63
+ default_args: entry[:default_args].to_s.strip,
64
64
  environment: entry[:environment] || {},
65
65
  compose: {
66
66
  method: entry.dig(:compose, :method) || entry[:compose_method] || "run",
@@ -76,19 +76,6 @@ module Dip
76
76
  entry[:description] ||= nil
77
77
  end
78
78
 
79
- def prepare_default_args(args)
80
- return [] if args.nil?
81
-
82
- case args
83
- when Array
84
- args
85
- when String
86
- args.shellsplit
87
- else
88
- raise ArgumentError, "Unknown type for default_args: #{args.inspect}"
89
- end
90
- end
91
-
92
79
  def compose_run_options(value)
93
80
  return [] unless value
94
81