dip 4.2.0 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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