dip 4.2.0 → 5.0.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de28bb80ddff1548082c5b8ccd0115bcd650cbdba9a930c666b02751cf0ba976
4
- data.tar.gz: 96a4a6d5b2f78b559ef429f793e1899ec8d35edc7f10ac90693632b8a9e2be06
3
+ metadata.gz: 0de34f9ada82a4cdfc8f56dd7edbba913e502ec08b8975344389c61e534bfd89
4
+ data.tar.gz: 55def66126d5b484529d895a00121b55552bdebcbbfe9e72f73a679b0cae744b
5
5
  SHA512:
6
- metadata.gz: 404af36fd19249124ab8bf8ce3f765c03f8e72ef9d6c1138424704233caf543c7ce2cf8ea7a86ece144407597bfba895cdb140ef277aa71042201a77221c974c
7
- data.tar.gz: 4b788801e5c6e73e259f0de5b4c137aa5dcce5a7e681dae1d21b2c403118cdafdf0870c22bd80be350c25f943e100930c0f8296927898196e0c812e0f1f66254
6
+ metadata.gz: 06f6595f1387da56159c717a13ee4116b76debbd87dc20204909d2e723ff2a00e7e51895dc2fb55eb9fa7e4115151b58d2fc4573ffd3076bf8b1382e44ee101d
7
+ data.tar.gz: c17f4ed583c96c557c6e59479e822be00bb6d390e535108acda62493e3ab93bf502eaa8b2bab7636af3c63cd1f88532f3f2bf95b31166dceb9c0f148ac64c438
data/README.md CHANGED
@@ -17,7 +17,7 @@ A command-line utility that gives the "native" interaction with applications con
17
17
 
18
18
  - [Local development with Docker containers](https://slides.com/bibendi/dip)
19
19
  - Dockerized Ruby on Rails applications: [one](https://github.com/bibendi/dip-example-rails), [two](https://github.com/evilmartians/evil_chat)
20
- - [Dockerized Node.js application](https://github.com/bibendi/yt-graphql-react-event-booking-api)
20
+ - Dockerized Node.js application: [one](https://github.com/bibendi/twinkle.js), [two](https://github.com/bibendi/yt-graphql-react-event-booking-api)
21
21
  - [Dockerized Ruby gem](https://github.com/bibendi/schked)
22
22
 
23
23
  [![asciicast](https://asciinema.org/a/210236.svg)](https://asciinema.org/a/210236)
@@ -98,13 +98,13 @@ dip SUBCOMMAND --help
98
98
 
99
99
  ### dip.yml
100
100
 
101
- The configuration file `dip.yml` should be placed in a project root directory.
101
+ The configuration is loaded from `dip.yml` file. It may be located in a working directory, or it will be found in the nearest parent directory up to the file system root. If nearby places `dip.override.yml` file, it will be merged into the main config.
102
+
102
103
  Also, in some cases, you may want to change the default config path by providing an environment variable `DIP_FILE`.
103
- If nearby places `dip.override.yml` file it would be merged into the main config.
104
104
 
105
105
  Below is an example of a real config.
106
- `dip.yml` reference will be written soon.
107
- Also, you can check out examples in the top.
106
+ Config file reference will be written soon.
107
+ Also, you can check out examples at the top.
108
108
 
109
109
  ```yml
110
110
  # Required minimum dip version
@@ -124,6 +124,7 @@ interaction:
124
124
  bash:
125
125
  description: Open the Bash shell in app's container
126
126
  service: app
127
+ command: bash
127
128
  compose:
128
129
  run_options: [no-deps]
129
130
 
@@ -153,7 +154,7 @@ interaction:
153
154
  description: Run Rails server at http://localhost:3000
154
155
  service: web
155
156
  compose:
156
- run_options: [service-ports]
157
+ run_options: [service-ports, use-aliases]
157
158
 
158
159
  sidekiq:
159
160
  description: Run sidekiq in background
@@ -174,6 +175,44 @@ provision:
174
175
  - dip bash -c ./bin/setup
175
176
  ```
176
177
 
178
+ ### Predefined environment variables
179
+
180
+ #### $DIP_OS
181
+
182
+ Current OS architecture (e.g. `linux`, `darwin`, `freebsd`, and so on). Sometime it may be useful to have one common `docker-compose.yml` and OS-dependent Compose configs.
183
+
184
+ #### $DIP_WORK_DIR_REL_PATH
185
+
186
+ Relative path from the current directory to the nearest directory where a Dip's config is found. It is useful when you need to mount a specific local directory to a container along with ability to change its working dir. For example:
187
+
188
+ ```
189
+ - project_root
190
+ |- dip.yml (1)
191
+ |- docker-compose.yml (2)
192
+ |- sub-project-dir
193
+ |- your current directory is here <<<
194
+ ```
195
+
196
+ ```yml
197
+ # dip.yml (1)
198
+ environment:
199
+ WORK_DIR: /app/${DIP_WORK_DIR_REL_PATH}
200
+ ```
201
+
202
+ ```yml
203
+ # docker-compose.yml (2)
204
+ services:
205
+ app:
206
+ working_dir: ${WORK_DIR:-/app}
207
+ ```
208
+
209
+ ```sh
210
+ cd sub-project-dir
211
+ dip run bash -c pwd
212
+ ```
213
+
214
+ returned is `/app/sub-project-dir`.
215
+
177
216
  ### dip run
178
217
 
179
218
  Run commands defined within the `interaction` section of dip.yml
@@ -183,16 +222,27 @@ dip run rails c
183
222
  dip run rake db:migrate
184
223
  ```
185
224
 
186
- `run` argument can be omitted
225
+ Also, `run` argument can be omitted
187
226
 
188
227
  ```sh
189
228
  dip rake db:migrate
229
+ ```
230
+
231
+ You can pass in a custom environment variable into a container:
232
+
233
+ ```sh
190
234
  dip VERSION=12352452 rake db:rollback
191
235
  ```
192
236
 
237
+ Use options `-p, --publish=[]` if you need to additionally publish a container's port(s) to the host unless this behaviour is not configured at dip.yml:
238
+
239
+ ```sh
240
+ dip run -p 3000:3000 bundle exec rackup config.ru
241
+ ```
242
+
193
243
  ### dip ls
194
244
 
195
- List al available run commands.
245
+ List all available run commands.
196
246
 
197
247
  ```sh
198
248
  dip ls
data/exe/dip CHANGED
@@ -4,16 +4,16 @@
4
4
  lib_path = File.expand_path('../lib', __dir__)
5
5
  $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
6
 
7
+ begin
8
+ require 'pry-byebug' if ENV["DIP_ENV"] == "debug"
9
+ rescue LoadError # rubocop:disable Lint/SuppressedException
10
+ # do nothing
11
+ end
12
+
7
13
  require 'dip'
8
14
  require 'dip/cli'
9
15
  require 'dip/run_vars'
10
16
 
11
- begin
12
- require 'pry-byebug' if Dip.debug?
13
- rescue LoadError
14
- puts "pry-byebug not found!"
15
- end
16
-
17
17
  Signal.trap('INT') do
18
18
  warn("\n#{caller.join("\n")}: interrupted")
19
19
  exit(1)
data/lib/dip.rb CHANGED
@@ -11,7 +11,7 @@ module Dip
11
11
  end
12
12
 
13
13
  def env
14
- @env ||= Dip::Environment.new(Dip::Config.exist? ? config.environment : {})
14
+ @env ||= Dip::Environment.new(config.exist? ? config.environment : {})
15
15
  end
16
16
 
17
17
  def bin_path
@@ -5,6 +5,8 @@ require 'dip/run_vars'
5
5
 
6
6
  module Dip
7
7
  class CLI < Thor
8
+ TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh dns nginx console].freeze
9
+
8
10
  class << self
9
11
  # Hackery. Take the run method away from Thor so that we can redefine it.
10
12
  def is_thor_reserved_word?(word, type)
@@ -13,24 +15,24 @@ module Dip
13
15
  super
14
16
  end
15
17
 
16
- def start(argv)
17
- super Dip::RunVars.call(argv, ENV)
18
+ def exit_on_failure?
19
+ true
18
20
  end
19
- end
20
21
 
21
- stop_on_unknown_option! :up
22
+ def start(argv)
23
+ argv = Dip::RunVars.call(argv, ENV)
22
24
 
23
- def method_missing(cmd, *args)
24
- if Dip.config.interaction.key?(cmd.to_sym)
25
- self.class.start(["run", cmd.to_s, *args])
26
- else
27
- super
25
+ cmd = argv.first
26
+
27
+ if cmd && !TOP_LEVEL_COMMANDS.include?(cmd) && Dip.config.exist? && Dip.config.interaction.key?(cmd.to_sym)
28
+ argv.unshift("run")
29
+ end
30
+
31
+ super Dip::RunVars.call(argv, ENV)
28
32
  end
29
33
  end
30
34
 
31
- def respond_to_missing?(cmd)
32
- Dip.config.interaction.key?(cmd.to_sym)
33
- end
35
+ stop_on_unknown_option! :run
34
36
 
35
37
  desc 'version', 'dip version'
36
38
  def version
@@ -66,10 +68,17 @@ module Dip
66
68
  compose("down", *argv)
67
69
  end
68
70
 
69
- desc 'CMD or dip run CMD [OPTIONS]', 'Run configured command in a docker-compose service'
71
+ desc 'run [OPTIONS] CMD [ARGS]', 'Run configured command in a docker-compose service. `run` prefix may be omitted'
72
+ method_option :publish, aliases: '-p', type: :string, repeatable: true,
73
+ desc: "Publish a container's port(s) to the host"
74
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
70
75
  def run(*argv)
71
- require_relative 'commands/run'
72
- Dip::Commands::Run.new(*argv).execute
76
+ if argv.empty? || options[:help]
77
+ invoke :help, ['run']
78
+ else
79
+ require_relative 'commands/run'
80
+ Dip::Commands::Run.new(*argv, publish: options[:publish]).execute
81
+ end
73
82
  end
74
83
 
75
84
  desc "provision", "Execute commands within provision section"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dip
4
+ class CLI
5
+ class Base < Thor
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require_relative "./base"
4
5
  require_relative "../commands/console"
5
6
 
6
7
  module Dip
7
8
  class CLI
8
- class Console < Thor
9
+ class Console < Base
9
10
  desc "start", "Integrate Dip into current shell"
10
11
  method_option :help, aliases: '-h', type: :boolean,
11
12
  desc: 'Display usage information'
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require_relative "./base"
4
5
  require_relative "../commands/dns"
5
6
 
6
7
  module Dip
7
8
  class CLI
8
9
  # See more https://github.com/aacebedo/dnsdock
9
- class DNS < Thor
10
+ class DNS < Base
10
11
  desc "up", "Run dnsdock container"
11
12
  method_option :help, aliases: '-h', type: :boolean,
12
13
  desc: 'Display usage information'
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require_relative "./base"
4
5
  require_relative "../commands/nginx"
5
6
 
6
7
  module Dip
7
8
  class CLI
8
9
  # See more https://github.com/bibendi/nginx-proxy
9
- class Nginx < Thor
10
+ class Nginx < Base
10
11
  desc "up", "Run nginx container"
11
12
  method_option :help, aliases: '-h', type: :boolean,
12
13
  desc: 'Display usage information'
@@ -16,7 +17,7 @@ module Dip
16
17
  desc: 'Path to docker socket'
17
18
  method_option :net, aliases: '-t', type: :string, default: "frontend",
18
19
  desc: 'Container network name'
19
- method_option :publish, aliases: '-p', type: :array, default: "80:80",
20
+ method_option :publish, aliases: '-p', type: :array, default: ["80:80"],
20
21
  desc: 'Container port(s). For more than one port, separate them by a space'
21
22
  method_option :image, aliases: '-i', type: :string, default: "bibendi/nginx-proxy:latest",
22
23
  desc: 'Docker image name'
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require_relative "./base"
4
5
  require_relative "../commands/ssh"
5
6
 
6
7
  module Dip
7
8
  class CLI
8
- class SSH < Thor
9
+ class SSH < Base
9
10
  desc "up", "Run ssh-agent container"
10
11
  method_option :help, aliases: '-h', type: :boolean,
11
12
  desc: 'Display usage information'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  require_relative '../command'
4
6
  require_relative 'dns'
5
7
 
@@ -29,12 +31,14 @@ module Dip
29
31
  return unless (files = config[:files])
30
32
 
31
33
  if files.is_a?(Array)
32
- files.each_with_object([]) do |file_name, memo|
33
- file_name = ::Dip.env.interpolate(file_name)
34
- next unless File.exist?(file_name)
34
+ files.each_with_object([]) do |file_path, memo|
35
+ file_path = ::Dip.env.interpolate(file_path)
36
+ file_path = Pathname.new(file_path)
37
+ file_path = Dip.config.file_path.parent.join(file_path).expand_path if file_path.relative?
38
+ next unless file_path.exist?
35
39
 
36
40
  memo << "--file"
37
- memo << file_name
41
+ memo << file_path.to_s
38
42
  end
39
43
  end
40
44
  end
@@ -86,7 +86,7 @@ module Dip
86
86
  end
87
87
 
88
88
  def execute
89
- if Dip::Config.exist?
89
+ if Dip.config.exist?
90
90
  add_aliases(*Dip.config.interaction.keys) if Dip.config.interaction
91
91
  add_aliases("compose", "up", "stop", "down", "provision")
92
92
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'shellwords'
4
+ require_relative '../../../lib/dip/run_vars'
4
5
  require_relative '../command'
5
6
  require_relative '../interaction_tree'
6
7
  require_relative 'compose'
@@ -8,7 +9,9 @@ require_relative 'compose'
8
9
  module Dip
9
10
  module Commands
10
11
  class Run < Dip::Command
11
- def initialize(cmd, *argv)
12
+ def initialize(cmd, *argv, publish: nil)
13
+ @publish = publish
14
+
12
15
  @command, @argv = InteractionTree.
13
16
  new(Dip.config.interaction).
14
17
  find(cmd, *argv)&.
@@ -28,13 +31,14 @@ module Dip
28
31
 
29
32
  private
30
33
 
31
- attr_reader :command, :argv
34
+ attr_reader :command, :argv, :publish
32
35
 
33
36
  def compose_arguments
34
37
  compose_argv = command[:compose][:run_options].dup
35
38
 
36
39
  if command[:compose][:method] == "run"
37
40
  compose_argv.concat(run_vars)
41
+ compose_argv.concat(published_ports)
38
42
  compose_argv << "--rm"
39
43
  end
40
44
 
@@ -55,6 +59,14 @@ module Dip
55
59
 
56
60
  run_vars.map { |k, v| ["-e", "#{k}=#{v}"] }.flatten
57
61
  end
62
+
63
+ def published_ports
64
+ if publish.respond_to?(:each)
65
+ publish.map { |p| "--publish=#{p}" }
66
+ else
67
+ []
68
+ end
69
+ end
58
70
  end
59
71
  end
60
72
  end
@@ -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,89 @@ 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
+ class ConfigFinder
17
+ attr_reader :file_path
18
+
19
+ def initialize(work_dir, override: false)
20
+ @override = override
21
+
22
+ @file_path = if ENV["DIP_FILE"]
23
+ Pathname.new(prepared_name(ENV["DIP_FILE"]))
24
+ else
25
+ find(Pathname.new(work_dir))
26
+ end
18
27
  end
19
28
 
20
- def path
21
- ENV["DIP_FILE"] || File.join(Dir.pwd, DEFAULT_PATH)
29
+ def exist?
30
+ file_path&.exist?
22
31
  end
23
32
 
24
- def override_path
33
+ private
34
+
35
+ attr_reader :override
36
+
37
+ def prepared_name(path)
38
+ return path unless override
39
+
25
40
  path.gsub(/\.yml$/, ".override.yml")
26
41
  end
27
42
 
43
+ def find(path)
44
+ file = path.join(prepared_name(DEFAULT_PATH))
45
+ return file if file.exist?
46
+ return if path.root?
47
+
48
+ find(path.parent)
49
+ end
50
+ end
51
+
52
+ class << self
28
53
  def load_yaml(file_path = path)
29
54
  return {} unless File.exist?(file_path)
30
55
 
31
56
  YAML.safe_load(
32
57
  ERB.new(File.read(file_path)).result,
33
58
  [], [], true
34
- ).deep_symbolize_keys!
59
+ )&.deep_symbolize_keys! || {}
35
60
  end
36
61
  end
37
62
 
38
- %i[environment compose interaction provision].each do |key|
39
- define_method(key) do
40
- config[key]
41
- end
63
+ def initialize(work_dir = Dir.pwd)
64
+ @work_dir = work_dir
65
+ end
66
+
67
+ def file_path
68
+ finder.file_path
69
+ end
70
+
71
+ def exist?
72
+ finder.exist?
42
73
  end
43
74
 
44
75
  def to_h
45
76
  config
46
77
  end
47
78
 
79
+ %i[environment compose interaction provision].each do |key|
80
+ define_method(key) do
81
+ config[key]
82
+ end
83
+ end
84
+
48
85
  private
49
86
 
87
+ attr_reader :work_dir
88
+
89
+ def finder
90
+ @finder ||= ConfigFinder.new(work_dir)
91
+ end
92
+
50
93
  def config
51
94
  return @config if @config
52
95
 
53
- raise ArgumentError, "Dip config not found at path '#{self.class.path}'" unless self.class.exist?
96
+ raise Dip::Error, "Could not find dip.yml config" unless finder.exist?
54
97
 
55
- config = self.class.load_yaml
98
+ config = self.class.load_yaml(finder.file_path)
56
99
 
57
100
  unless Gem::Version.new(Dip::VERSION) >= Gem::Version.new(config.fetch(:version))
58
101
  raise VersionMismatchError, "Your dip version is `#{Dip::VERSION}`, " \
@@ -60,7 +103,8 @@ module Dip
60
103
  "Please upgrade your dip!"
61
104
  end
62
105
 
63
- config.deep_merge!(self.class.load_yaml(self.class.override_path))
106
+ override_finder = ConfigFinder.new(work_dir, override: true)
107
+ config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?
64
108
 
65
109
  @config = config
66
110
  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
7
  VAR_REGEX = /\$[\{]?(?<var_name>[a-zA-Z_][a-zA-Z0-9_]*)[\}]?/.freeze
6
- SPECIAL_VARS = {"DIP_OS" => :find_dip_os}.freeze
8
+ SPECIAL_VARS = %i[os work_dir_rel_path].freeze
7
9
 
8
10
  attr_reader :vars
9
11
 
@@ -24,6 +26,10 @@ module Dip
24
26
  vars.fetch(name) { ENV[name] }
25
27
  end
26
28
 
29
+ def fetch(name)
30
+ vars.fetch(name) { ENV.fetch(name) { yield } }
31
+ end
32
+
27
33
  def []=(key, value)
28
34
  @vars[key] = value
29
35
  end
@@ -32,8 +38,8 @@ module Dip
32
38
  value.gsub(VAR_REGEX) do
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
44
  self[var_name]
39
45
  end
@@ -44,8 +50,18 @@ module Dip
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dip
4
- VERSION = "4.2.0"
4
+ VERSION = "5.0.0.rc1"
5
5
  end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dip
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 5.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - bibendi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-06 00:00:00.000000000 Z
11
+ date: 2019-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.20.0
19
+ version: '0.20'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.1'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.20'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: 0.20.0
32
+ version: '1.1'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: bundler
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +142,7 @@ files:
136
142
  - exe/dip
137
143
  - lib/dip.rb
138
144
  - lib/dip/cli.rb
145
+ - lib/dip/cli/base.rb
139
146
  - lib/dip/cli/console.rb
140
147
  - lib/dip/cli/dns.rb
141
148
  - lib/dip/cli/nginx.rb
@@ -172,9 +179,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
172
179
  version: '2.3'
173
180
  required_rubygems_version: !ruby/object:Gem::Requirement
174
181
  requirements:
175
- - - ">="
182
+ - - ">"
176
183
  - !ruby/object:Gem::Version
177
- version: '0'
184
+ version: 1.3.1
178
185
  requirements: []
179
186
  rubyforge_project:
180
187
  rubygems_version: 2.7.7