dip 3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6a9ad2527f29216fd1a230c2c8acc9032f08cd1eea686f89df433f8c59be50fb
4
+ data.tar.gz: d84a3f71b392136dd0d275391ec0c58424b91a00e3ae0a1ea93233ec3a718026
5
+ SHA512:
6
+ metadata.gz: 99e059244669e8aa9a7ab9393aaad1461231667ef66ce0073ad417462bae8996c9819794e8fba8006ea4fabf6296426b006ae5ff485afb0b92aefaafd5719bf2
7
+ data.tar.gz: 5497a34f27a83e88f91eadca30dbaf3732f38da9b70c5d7bbfbe994ab3b0293156966bb187c3394120ab35a992995d3ea8b7d1e6e4b7d4384b55e141badfd28e
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 bibendi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,206 @@
1
+ # DIP [![Build Status](https://travis-ci.org/bibendi/dip.svg?branch=master)](https://travis-ci.org/bibendi/dip)
2
+
3
+ Docker Interaction Process
4
+
5
+ CLI utility for straightforward provisioning and interacting with an application configured by docker-compose.
6
+
7
+ DIP also contains commands for running support containers such as ssh-agent and DNS server.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ gem install dip
13
+ ```
14
+
15
+ ## Changelog
16
+
17
+ https://github.com/bibendi/dip/releases
18
+
19
+
20
+ ## Docker installation
21
+
22
+ - [Ubuntu](docs/docker-ubuntu-install.md)
23
+ - [Mac OS](docs/docker-for-mac-install.md)
24
+
25
+ ## Usage
26
+
27
+ ```sh
28
+ dip --help
29
+ dip SUBCOMMAND --help
30
+ ```
31
+
32
+ ### dip.yml
33
+
34
+ ```yml
35
+ version: '2'
36
+
37
+ environment:
38
+ COMPOSE_EXT: development
39
+ RAILS_ENV: development
40
+
41
+ compose:
42
+ files:
43
+ - docker/docker-compose.yml
44
+ - docker/docker-compose.$COMPOSE_EXT.yml
45
+ - docker/docker-compose.$DIP_OS.yml
46
+ project_name: bear-$RAILS_ENV
47
+
48
+ interaction:
49
+ sh:
50
+ service: foo-app
51
+ compose_run_options: [no-deps]
52
+
53
+ bundle:
54
+ service: foo-app
55
+ command: bundle
56
+
57
+ rake:
58
+ service: foo-app
59
+ command: bundle exec rake
60
+
61
+ rspec:
62
+ service: foo-app
63
+ environment:
64
+ RAILS_ENV: test
65
+ command: bundle exec rspec
66
+
67
+ rails:
68
+ service: foo-app
69
+ command: bundle exec rails
70
+ subcommands:
71
+ s:
72
+ service: foo-web
73
+ compose_method: up
74
+
75
+ psql:
76
+ service: foo-app
77
+ command: psql -h pg -U postgres
78
+
79
+ provision:
80
+ - dip compose up -d foo-pg foo-redis
81
+ - dip bundle install
82
+ - dip rake db:migrate
83
+ ```
84
+
85
+ ### dip run
86
+
87
+ Run commands defined within `interaction` section of dip.yml
88
+
89
+ ```sh
90
+ dip run rails c
91
+ dip run rake db:migrate
92
+ ```
93
+
94
+ `run` argument can be ommited
95
+
96
+ ```sh
97
+ dip rake db:migrate
98
+ dip VERSION=12352452 rake db:rollback
99
+ ```
100
+
101
+ ### dip provision
102
+
103
+ Run commands each by each from `provision` section of dip.yml
104
+
105
+ ### dip compose
106
+
107
+ Run docker-compose commands that are configured according with application dip.yml
108
+
109
+ ```sh
110
+ dip compose COMMAND [OPTIONS]
111
+
112
+ dip compose up -d redis
113
+ ```
114
+
115
+ ### dip ssh
116
+
117
+ Runs ssh-agent container based on https://github.com/whilp/ssh-agent with your ~/.ssh/id_rsa.
118
+ It creates a named volume `ssh_data` with ssh socket.
119
+ An application's docker-compose.yml should contains environment variable `SSH_AUTH_SOCK=/ssh/auth/sock` and connects to external volume `ssh_data`.
120
+
121
+ ```sh
122
+ dip ssh up
123
+ ```
124
+
125
+ docker-compose.yml
126
+
127
+ ```yml
128
+ services:
129
+ web:
130
+ environment:
131
+ - SSH_AUTH_SOCK=/ssh/auth/sock
132
+ volumes:
133
+ - ssh-data:/ssh:ro
134
+
135
+ volumes:
136
+ ssh-data:
137
+ external:
138
+ name: ssh_data
139
+ ```
140
+
141
+ ### dip nginx
142
+
143
+ Runs Nginx server container based on [bibendi/nginx-proxy](https://github.com/bibendi/nginx-proxy) image. An application's docker-compose.yml should contains environment variable `VIRTUAL_HOST` and `VIRTUAL_PATH` and connects to external network `frontend`.
144
+
145
+ foo-project/docker-compose.yml
146
+
147
+ ```yml
148
+ version: '2'
149
+
150
+ services:
151
+ foo-web:
152
+ image: company/foo_image
153
+ environment:
154
+ - VIRTUAL_HOST=*.bar-app.docker
155
+ - VIRTUAL_PATH=/
156
+ networks:
157
+ - default
158
+ - frontend
159
+ dns: $DIP_DNS
160
+
161
+ networks:
162
+ frontend:
163
+ external:
164
+ name: frontend
165
+ ```
166
+
167
+ baz-project/docker-compose.yml
168
+
169
+ ```yml
170
+ version: '2'
171
+
172
+ services:
173
+ baz-web:
174
+ image: company/baz_image
175
+ environment:
176
+ - VIRTUAL_HOST=*.bar-app.docker
177
+ - VIRTUAL_PATH=/api/v1/baz_service,/api/v2/baz_service
178
+ networks:
179
+ - default
180
+ - frontend
181
+ dns: $DIP_DNS
182
+
183
+ networks:
184
+ frontend:
185
+ external:
186
+ name: frontend
187
+ ```
188
+
189
+ ```sh
190
+ dip nginx up
191
+ cd foo-project && dip compose up
192
+ cd baz-project && dip compose up
193
+ curl www.bar-app.docker/api/v1/quz
194
+ curl www.bar-app.docker/api/v1/baz_service/qzz
195
+ ```
196
+
197
+ ### dip dns
198
+
199
+ Runs DNS server container based on https://github.com/aacebedo/dnsdock It used for container to container requests through nginx. An application's docker-compose.yml should define `dns` configuration with environment variable `$DIP_DNS` and connect to external network `frontend`. `$DIP_DNS` will be automatically assigned by dip.
200
+
201
+ ```sh
202
+ dip dns up
203
+
204
+ cd foo-project
205
+ dip compose exec foo-web curl http://www.bar-app.docker/api/v1/baz_service
206
+ ```
data/exe/dip ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
+
7
+ require 'dip'
8
+ require 'dip/cli'
9
+ require 'pry-byebug' if Dip.debug?
10
+
11
+ Signal.trap('INT') do
12
+ warn("\n#{caller.join("\n")}: interrupted")
13
+ exit(1)
14
+ end
15
+
16
+ begin
17
+ run_vars = []
18
+
19
+ ARGV.each do |arg|
20
+ if !run_vars.frozen? && arg.include?("=")
21
+ key, val = arg.split("=", 2)
22
+ run_vars << "#{'--x-dip-run-vars=' if run_vars.empty?}#{key}:#{val}"
23
+ else
24
+ run_vars.freeze
25
+ end
26
+ end
27
+
28
+ unless run_vars.empty?
29
+ ARGV.shift(run_vars.size)
30
+ ARGV.push(*run_vars)
31
+ end
32
+
33
+ Dip::CLI.start
34
+ rescue Dip::Error => err
35
+ puts "ERROR: #{err.message}"
36
+ exit 1
37
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dip/version"
4
+ require "dip/config"
5
+ require "dip/environment"
6
+
7
+ module Dip
8
+ Error = Class.new(StandardError)
9
+
10
+ class << self
11
+ def config_path
12
+ ENV["DIP_FILE"] || "./dip.yml"
13
+ end
14
+
15
+ def config
16
+ @config ||= Dip::Config.new(config_path)
17
+ end
18
+
19
+ def env
20
+ @env ||= Dip::Environment.new(config.environment)
21
+ end
22
+
23
+ def test?
24
+ ENV["DIP_ENV"] == "test"
25
+ end
26
+
27
+ def debug?
28
+ ENV["DIP_ENV"] == "debug"
29
+ end
30
+
31
+ def reset!
32
+ @config = nil
33
+ @env = nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Dip
6
+ class CLI < Thor
7
+ class << self
8
+ def retrieve_command_name(args)
9
+ meth = args.first.to_sym unless args.empty?
10
+ args.unshift("run") if meth && ::Dip.config.interaction.key?(meth.to_sym)
11
+
12
+ super(args)
13
+ end
14
+
15
+ # Hackery. Take the run method away from Thor so that we can redefine it.
16
+ def is_thor_reserved_word?(word, type)
17
+ return false if word == "run"
18
+
19
+ super
20
+ end
21
+ end
22
+
23
+ desc 'version', 'dip version'
24
+ def version
25
+ require_relative 'version'
26
+ puts Dip::VERSION
27
+ end
28
+ map %w(--version -v) => :version
29
+
30
+ desc 'compose CMD [OPTIONS]', 'Run docker-compose commands'
31
+ method_option :help, aliases: '-h', type: :boolean,
32
+ desc: 'Display usage information'
33
+ def compose(cmd, *argv)
34
+ if options[:help]
35
+ invoke :help, ['compose']
36
+ else
37
+ require_relative 'commands/compose'
38
+ Dip::Commands::Compose.new(cmd, argv).execute
39
+ end
40
+ end
41
+
42
+ desc 'CMD or dip run CMD [OPTIONS]', 'Run configured command in a docker-compose service'
43
+ method_option :help, aliases: '-h', type: :boolean,
44
+ desc: 'Display usage information'
45
+ method_option :x_dip_run_vars,
46
+ type: :hash,
47
+ desc: "Enforce environment variables into container, recommended run like 'dip FOO=bar cmd'"
48
+ def run(cmd, subcmd = nil, *argv)
49
+ if options[:help]
50
+ invoke :help, ['run']
51
+ else
52
+ require_relative 'commands/run'
53
+ Dip::Commands::Run.
54
+ new(cmd, subcmd, argv,
55
+ run_vars: options[:x_dip_run_vars]).
56
+ execute
57
+ end
58
+ end
59
+
60
+ desc "provision", "Execute commands within provision section"
61
+ method_option :help, aliases: '-h', type: :boolean,
62
+ desc: 'Display usage information'
63
+ def provision
64
+ if options[:help]
65
+ invoke :help, ['provision']
66
+ else
67
+ require_relative 'commands/provision'
68
+ Dip::Commands::Provision.new.execute
69
+ end
70
+ end
71
+
72
+ desc "ssh", "ssh-agent container commands"
73
+ def ssh(*args)
74
+ require_relative 'cli/ssh'
75
+ Dip::CLI::SSH.start(args)
76
+ end
77
+
78
+ desc "dns", "DNS server for automatic docker container discovery"
79
+ def dns(*args)
80
+ require_relative 'cli/dns'
81
+ Dip::CLI::DNS.start(args)
82
+ end
83
+
84
+ desc "nginx", "Nginx reverse proxy server"
85
+ def nginx(*args)
86
+ require_relative 'cli/nginx'
87
+ Dip::CLI::Nginx.start(args)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative "../commands/dns"
5
+
6
+ module Dip
7
+ class CLI
8
+ # See more https://github.com/aacebedo/dnsdock
9
+ class DNS < Thor
10
+ desc "dns up", "Run dnsdock container"
11
+ method_option :help, aliases: '-h', type: :boolean,
12
+ desc: 'Display usage information'
13
+ method_option :name, aliases: '-n', type: :string, default: "dnsdock",
14
+ desc: 'Container name'
15
+ method_option :socket, aliases: '-s', type: :string, default: "/var/run/docker.sock",
16
+ desc: 'Path to docker socket'
17
+ method_option :net, aliases: '-t', type: :string, default: "frontend",
18
+ desc: 'Container network name'
19
+ method_option :publish, aliases: '-p', type: :string, default: "53/udp",
20
+ desc: 'Container port'
21
+ method_option :image, aliases: '-i', type: :string, default: "aacebedo/dnsdock:latest-amd64",
22
+ desc: 'Docker image name'
23
+ method_option :domain, aliases: '-d', type: :string, default: "docker",
24
+ desc: 'Top level domain'
25
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
26
+ def up
27
+ if options[:help]
28
+ invoke :help, ['up']
29
+ else
30
+ Dip::Commands::DNS::Up.new(
31
+ name: options.fetch(:name),
32
+ socket: options.fetch(:socket),
33
+ net: options.fetch(:net),
34
+ publish: options.fetch(:publish),
35
+ image: options.fetch(:image),
36
+ domain: options.fetch(:domain)
37
+ ).execute
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
41
+
42
+ desc "dns down", "Stop dnsdock container"
43
+ method_option :help, aliases: '-h', type: :boolean,
44
+ desc: 'Display usage information'
45
+ method_option :name, aliases: '-n', type: :string, default: "dnsdock",
46
+ desc: 'Container name'
47
+ def down
48
+ if options[:help]
49
+ invoke :help, ['down']
50
+ else
51
+ Dip::Commands::DNS::Down.new(
52
+ name: options.fetch(:name)
53
+ ).execute
54
+ end
55
+ end
56
+
57
+ desc "dns restart", "Stop and start dnsdock container"
58
+ method_option :help, aliases: '-h', type: :boolean,
59
+ desc: 'Display usage information'
60
+ def restart(*args)
61
+ if options[:help]
62
+ invoke :help, ['restart']
63
+ else
64
+ Dip::CLI::DNS.start(["down"] + args)
65
+ sleep 1
66
+ Dip::CLI::DNS.start(["up"] + args)
67
+ end
68
+ end
69
+
70
+ desc "dns ip", "Get ip address of dnsdock container"
71
+ method_option :help, aliases: '-h', type: :boolean,
72
+ desc: 'Display usage information'
73
+ method_option :name, aliases: '-n', type: :string, default: "dnsdock",
74
+ desc: 'Container name'
75
+ method_option :net, aliases: '-t', type: :string, default: "frontend",
76
+ desc: 'Container network name'
77
+ def ip
78
+ if options[:help]
79
+ invoke :help, ['status']
80
+ else
81
+ Dip::Commands::DNS::IP.new(
82
+ name: options.fetch(:name),
83
+ net: options.fetch(:net)
84
+ ).execute
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative "../commands/nginx"
5
+
6
+ module Dip
7
+ class CLI
8
+ # See more https://github.com/bibendi/nginx-proxy
9
+ class Nginx < Thor
10
+ desc "nginx up", "Run nginx container"
11
+ method_option :help, aliases: '-h', type: :boolean,
12
+ desc: 'Display usage information'
13
+ method_option :name, aliases: '-n', type: :string, default: "nginx",
14
+ desc: 'Container name'
15
+ method_option :socket, aliases: '-s', type: :string, default: "/var/run/docker.sock",
16
+ desc: 'Path to docker socket'
17
+ method_option :net, aliases: '-t', type: :string, default: "frontend",
18
+ desc: 'Container network name'
19
+ method_option :publish, aliases: '-p', type: :string, default: "80:80",
20
+ desc: 'Container port'
21
+ method_option :image, aliases: '-i', type: :string, default: "bibendi/nginx-proxy:latest",
22
+ desc: 'Docker image name'
23
+ method_option :domain, aliases: '-d', type: :string, default: "docker",
24
+ desc: 'Top level domain'
25
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
26
+ def up
27
+ if options[:help]
28
+ invoke :help, ['up']
29
+ else
30
+ Dip::Commands::Nginx::Up.new(
31
+ name: options.fetch(:name),
32
+ socket: options.fetch(:socket),
33
+ net: options.fetch(:net),
34
+ publish: options.fetch(:publish),
35
+ image: options.fetch(:image),
36
+ domain: options.fetch(:domain)
37
+ ).execute
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
41
+
42
+ desc "nginx down", "Stop nginx container"
43
+ method_option :help, aliases: '-h', type: :boolean,
44
+ desc: 'Display usage information'
45
+ method_option :name, aliases: '-n', type: :string, default: "nginx",
46
+ desc: 'Container name'
47
+ def down
48
+ if options[:help]
49
+ invoke :help, ['down']
50
+ else
51
+ Dip::Commands::Nginx::Down.new(
52
+ name: options.fetch(:name)
53
+ ).execute
54
+ end
55
+ end
56
+
57
+ desc "nginx restart", "Stop and start nginx container"
58
+ method_option :help, aliases: '-h', type: :boolean,
59
+ desc: 'Display usage information'
60
+ def restart(*args)
61
+ if options[:help]
62
+ invoke :help, ['restart']
63
+ else
64
+ Dip::CLI::Nginx.start(["down"] + args)
65
+ sleep 1
66
+ Dip::CLI::Nginx.start(["up"] + args)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative "../commands/ssh"
5
+
6
+ module Dip
7
+ class CLI
8
+ class SSH < Thor
9
+ desc "ssh up", "Run ssh-agent container"
10
+ method_option :help, aliases: '-h', type: :boolean,
11
+ desc: 'Display usage information'
12
+ method_option :key, aliases: '-k', type: :string, default: "$HOME/.ssh/id_rsa",
13
+ desc: 'Path to ssh key'
14
+ method_option :volume, aliases: '-v', type: :string, default: "$HOME",
15
+ desc: 'Mounted docker volume'
16
+ method_option :interactive, aliases: '-t', type: :boolean, default: true,
17
+ desc: 'Run in interactive mode'
18
+ # Backward compatibility
19
+ method_option :nonteractive, aliases: '-T', type: :boolean,
20
+ desc: 'Run in noninteractive mode'
21
+ def up
22
+ if options[:help]
23
+ invoke :help, ['up']
24
+ else
25
+ Dip::Commands::SSH::Up.new(
26
+ key: options.fetch(:key),
27
+ volume: options.fetch(:volume),
28
+ interactive: options.nonteractive? ? false : options.interactive?
29
+ ).execute
30
+ end
31
+ end
32
+
33
+ map add: :up
34
+
35
+ desc "ssh down", "Stop ssh-agent container"
36
+ method_option :help, aliases: '-h', type: :boolean,
37
+ desc: 'Display usage information'
38
+ def down
39
+ if options[:help]
40
+ invoke :help, ['down']
41
+ else
42
+ Dip::Commands::SSH::Down.new.execute
43
+ end
44
+ end
45
+
46
+ desc "ssh restart", "Stop and start ssh-agent container"
47
+ method_option :help, aliases: '-h', type: :boolean,
48
+ desc: 'Display usage information'
49
+ def restart(*args)
50
+ if options[:help]
51
+ invoke :help, ['restart']
52
+ else
53
+ Dip::CLI::SSH.start(["down"] + args)
54
+ sleep 1
55
+ Dip::CLI::SSH.start(["up"] + args)
56
+ end
57
+ end
58
+
59
+ desc "ssh status", "Show status of ssh-agent container"
60
+ method_option :help, aliases: '-h', type: :boolean,
61
+ desc: 'Display usage information'
62
+ def status
63
+ if options[:help]
64
+ invoke :help, ['status']
65
+ else
66
+ Dip::Commands::SSH::Status.new.execute
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Dip
6
+ class Command
7
+ extend Forwardable
8
+
9
+ def_delegators self, :shell, :subshell
10
+
11
+ class ExecRunner
12
+ def self.call(cmd, argv, env: {}, **options)
13
+ ::Process.exec(env, cmd, *argv, options)
14
+ end
15
+ end
16
+
17
+ class SubshellRunner
18
+ def self.call(cmd, argv, env: {}, panic: true, **options)
19
+ return if ::Kernel.system(env, cmd, *argv, options)
20
+ raise Dip::Error, "Command '#{([cmd] + argv).join(' ')}' executed with error." if panic
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def shell(cmd, argv = [], subshell: false, **options)
26
+ cmd = Dip.env.interpolate(cmd)
27
+ argv = argv.map { |arg| Dip.env.interpolate(arg) }
28
+
29
+ puts [Dip.env.vars, cmd, argv].inspect if Dip.debug?
30
+
31
+ runner = subshell ? SubshellRunner : ExecRunner
32
+ runner.call(cmd, argv, env: Dip.env.vars, **options)
33
+ end
34
+
35
+ def subshell(*args, **kwargs)
36
+ kwargs[:subshell] = true
37
+ shell(*args, **kwargs)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+ require_relative 'dns'
5
+
6
+ module Dip
7
+ module Commands
8
+ class Compose < Dip::Command
9
+ DOCKER_EMBEDDED_DNS = "127.0.0.11"
10
+
11
+ def initialize(cmd, argv = [])
12
+ @cmd = cmd
13
+ @argv = argv
14
+ @config = ::Dip.config.compose
15
+ end
16
+
17
+ def execute
18
+ compose_argv = Array(find_files) + Array(find_project_name)
19
+ compose_argv << @cmd
20
+ compose_argv += @argv
21
+
22
+ Dip.env["DIP_DNS"] ||= find_dns
23
+
24
+ shell("docker-compose", compose_argv)
25
+ end
26
+
27
+ private
28
+
29
+ def find_files
30
+ return unless (files = @config[:files])
31
+
32
+ if files.is_a?(Array)
33
+ files.each_with_object([]) do |file_name, memo|
34
+ file_name = ::Dip.env.interpolate(file_name)
35
+ next unless File.exist?(file_name)
36
+
37
+ memo << "--file"
38
+ memo << file_name
39
+ end
40
+ end
41
+ end
42
+
43
+ def find_project_name
44
+ return unless (project_name = @config[:project_name])
45
+
46
+ if project_name.is_a?(String)
47
+ project_name = ::Dip.env.interpolate(project_name)
48
+ ["--project-name", project_name]
49
+ end
50
+ end
51
+
52
+ def find_dns
53
+ name = Dip.env["DNSDOCK_CONTAINER"] || "dnsdock"
54
+ net = Dip.env["FRONTEND_NETWORK"] || "frontend"
55
+
56
+ IO.pipe do |r, w|
57
+ Dip::Commands::DNS::IP.
58
+ new(name: name, net: net).
59
+ execute(out: w, err: File::NULL, panic: false)
60
+
61
+ w.close_write
62
+ ip = r.readlines[0].to_s.strip
63
+ ip.empty? ? DOCKER_EMBEDDED_DNS : ip
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative '../command'
5
+
6
+ module Dip
7
+ module Commands
8
+ module DNS
9
+ class Up < Dip::Command
10
+ def initialize(name:, socket:, net:, publish:, image:, domain:)
11
+ @name = name
12
+ @socket = socket
13
+ @net = net
14
+ @publish = publish
15
+ @image = image
16
+ @domain = domain
17
+ end
18
+
19
+ def execute
20
+ subshell("docker", "network create #{@net}".shellsplit, panic: false, err: File::NULL)
21
+ subshell("docker", "run #{container_args} #{@image} --domain=#{@domain}".shellsplit)
22
+ end
23
+
24
+ private
25
+
26
+ def container_args
27
+ result = %w(--detach)
28
+ result << "--volume #{@socket}:/var/run/docker.sock:ro"
29
+ result << "--restart always"
30
+ result << "--publish #{@publish}"
31
+ result << "--net #{@net}"
32
+ result << "--name #{@name}"
33
+ result.join(' ')
34
+ end
35
+ end
36
+
37
+ class Down < Dip::Command
38
+ def initialize(name:)
39
+ @name = name
40
+ end
41
+
42
+ def execute
43
+ subshell("docker", "stop #{@name}".shellsplit, panic: false, out: File::NULL, err: File::NULL)
44
+ subshell("docker", "rm -v #{@name}".shellsplit, panic: false, out: File::NULL, err: File::NULL)
45
+ end
46
+ end
47
+
48
+ class IP < Dip::Command
49
+ def initialize(name:, net:)
50
+ @name = name
51
+ @net = net
52
+ end
53
+
54
+ def execute(**options)
55
+ subshell("docker",
56
+ "inspect " \
57
+ "--format '{{ .NetworkSettings.Networks.#{@net}.IPAddress }}' " \
58
+ "#{@name}".shellsplit,
59
+ **options)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative '../command'
5
+
6
+ module Dip
7
+ module Commands
8
+ module Nginx
9
+ class Up < Dip::Command
10
+ def initialize(name:, socket:, net:, publish:, image:, domain:)
11
+ @name = name
12
+ @socket = socket
13
+ @net = net
14
+ @publish = publish
15
+ @image = image
16
+ @domain = domain
17
+ end
18
+
19
+ def execute
20
+ subshell("docker", "network create #{@net}".shellsplit, panic: false, err: File::NULL)
21
+ subshell("docker", "run #{container_args} #{@image}".shellsplit)
22
+ end
23
+
24
+ private
25
+
26
+ def container_args
27
+ result = %w(--detach)
28
+ result << "--volume #{@socket}:/tmp/docker.sock:ro"
29
+ result << "--restart always"
30
+ result << "--publish #{@publish}"
31
+ result << "--net #{@net}"
32
+ result << "--name #{@name}"
33
+ result << "--label com.dnsdock.alias=#{@domain}"
34
+ result.join(' ')
35
+ end
36
+ end
37
+
38
+ class Down < Dip::Command
39
+ def initialize(name:)
40
+ @name = name
41
+ end
42
+
43
+ def execute
44
+ subshell("docker", "stop #{@name}".shellsplit, panic: false, out: File::NULL, err: File::NULL)
45
+ subshell("docker", "rm -v #{@name}".shellsplit, panic: false, out: File::NULL, err: File::NULL)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+
5
+ module Dip
6
+ module Commands
7
+ class Provision < Dip::Command
8
+ def execute
9
+ Dip.config.provision.each do |command|
10
+ subshell(command)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require_relative '../command'
5
+ require_relative 'compose'
6
+
7
+ module Dip
8
+ module Commands
9
+ class Run < Dip::Command
10
+ def initialize(cmd, subcmd = nil, argv = [], run_vars: nil)
11
+ @cmd = cmd.to_sym
12
+ @subcmd = subcmd.to_sym if subcmd
13
+ @argv = argv
14
+ @run_vars = run_vars
15
+ @config = ::Dip.config.interaction
16
+ end
17
+
18
+ # TODO: Refactor
19
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
20
+ def execute
21
+ command = @config.fetch(@cmd)
22
+ command[:subcommands] ||= {}
23
+
24
+ if (subcommand = command[:subcommands].fetch(@subcmd, {})).any?
25
+ subcommand[:command] ||= nil
26
+ command.merge!(subcommand)
27
+ elsif @subcmd
28
+ @argv.unshift(@subcmd.to_s)
29
+ end
30
+
31
+ Dip.env.merge(command[:environment]) if command[:environment]
32
+
33
+ compose_method = command.fetch(:compose_method, "run").to_s
34
+
35
+ compose_argv = []
36
+ compose_argv.concat(prepare_compose_run_options(command[:compose_run_options]))
37
+ compose_argv.concat(run_vars)
38
+ compose_argv << "--rm" if compose_method == "run"
39
+ compose_argv << command.fetch(:service).to_s
40
+ compose_argv += command[:command].to_s.shellsplit
41
+ compose_argv.concat(@argv)
42
+
43
+ Dip::Commands::Compose.new(compose_method, compose_argv).execute
44
+ end
45
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
46
+
47
+ def prepare_compose_run_options(value)
48
+ return [] unless value
49
+
50
+ value.map do |o|
51
+ o = o.start_with?("-") ? o : "--#{o}"
52
+ o.shellsplit
53
+ end.flatten
54
+ end
55
+
56
+ def run_vars
57
+ return [] unless @run_vars
58
+
59
+ @run_vars.map { |k, v| ["-e", "#{k}=#{v}"] }.flatten
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative '../command'
5
+
6
+ module Dip
7
+ module Commands
8
+ module SSH
9
+ class Up < Dip::Command
10
+ def initialize(key:, volume:, interactive:)
11
+ @key = key
12
+ @volume = volume
13
+ @interactive = interactive
14
+ end
15
+
16
+ def execute
17
+ subshell("docker", "volume create --name ssh_data".shellsplit, out: File::NULL, err: File::NULL)
18
+
19
+ subshell("docker", "run --detach --volume ssh_data:/ssh --name=ssh-agent whilp/ssh-agent".shellsplit)
20
+
21
+ key = Dip.env.interpolate(@key)
22
+ subshell("docker", "run #{container_args} whilp/ssh-agent ssh-add #{key}".shellsplit)
23
+ end
24
+
25
+ private
26
+
27
+ def container_args
28
+ result = %w(--rm)
29
+ volume = Dip.env.interpolate(@volume)
30
+ result << "--volume ssh_data:/ssh"
31
+ result << "--volume #{volume}:#{volume}"
32
+ result << "--interactive --tty" if @interactive
33
+ result.join(' ')
34
+ end
35
+ end
36
+
37
+ class Down < Dip::Command
38
+ def execute
39
+ subshell("docker", "stop ssh-agent".shellsplit, panic: false, out: File::NULL, err: File::NULL)
40
+ subshell("docker", "rm -v ssh-agent".shellsplit, panic: false, out: File::NULL, err: File::NULL)
41
+ subshell("docker", "volume rm ssh_data".shellsplit, panic: false, out: File::NULL, err: File::NULL)
42
+ end
43
+ end
44
+
45
+ class Status < Dip::Command
46
+ def execute
47
+ subshell("docker", "inspect --format '{{.State.Status}}' ssh-agent".shellsplit)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "erb"
5
+
6
+ module Dip
7
+ class Config
8
+ def initialize(config_path)
9
+ load_or_default(config_path)
10
+ end
11
+
12
+ def merge(config)
13
+ @config.merge!(config)
14
+ end
15
+
16
+ def environment
17
+ @config.fetch(:environment, {})
18
+ end
19
+
20
+ def compose
21
+ @config.fetch(:compose, {})
22
+ end
23
+
24
+ def interaction
25
+ @config.fetch(:interaction, {})
26
+ end
27
+
28
+ def provision
29
+ @config.fetch(:provision, [])
30
+ end
31
+
32
+ private
33
+
34
+ def load_or_default(config_path)
35
+ @config ||= if File.exist?(config_path) && !Dip.test?
36
+ YAML.safe_load(
37
+ ERB.new(File.read(config_path)).result,
38
+ [], [], true,
39
+ symbolize_names: true
40
+ )
41
+ else
42
+ {}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dip
4
+ class Environment
5
+ VAR_REGEX = /\$[\{]?(?<var_name>[a-zA-Z_][a-zA-Z0-9_]*)[\}]?/
6
+ SPECIAL_VARS = {"DIP_OS" => :find_dip_os}.freeze
7
+
8
+ attr_reader :vars
9
+
10
+ def initialize(default_vars)
11
+ @vars = {}
12
+
13
+ merge(default_vars)
14
+ end
15
+
16
+ def merge(new_vars)
17
+ new_vars.each do |key, value|
18
+ key = key.to_s
19
+ @vars[key] = ENV.fetch(key) { interpolate(value.to_s) }
20
+ end
21
+ end
22
+
23
+ def [](name)
24
+ vars.fetch(name) { ENV[name] }
25
+ end
26
+
27
+ def []=(key, value)
28
+ @vars[key] = value
29
+ end
30
+
31
+ def interpolate(value)
32
+ value.gsub(VAR_REGEX) do
33
+ var_name = Regexp.last_match[:var_name]
34
+
35
+ if SPECIAL_VARS.key?(var_name)
36
+ self[var_name] || send(SPECIAL_VARS[var_name])
37
+ else
38
+ self[var_name]
39
+ end
40
+ end
41
+ end
42
+
43
+ alias replace interpolate
44
+
45
+ private
46
+
47
+ def find_dip_os
48
+ @dip_os ||= Gem::Platform.local.os
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dip
4
+ VERSION = "3.0.0"
5
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dip
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - bibendi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.20.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.20.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.59'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.59'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.16'
111
+ - !ruby/object:Gem::Dependency
112
+ name: test-unit
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3'
125
+ description: DIP - Docker Interaction Process.CLI tool for better development experience
126
+ when interacting with docker and docker-compose.
127
+ email:
128
+ - merkushin.m.s@gmail.com
129
+ executables:
130
+ - dip
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - LICENSE.txt
135
+ - README.md
136
+ - exe/dip
137
+ - lib/dip.rb
138
+ - lib/dip/cli.rb
139
+ - lib/dip/cli/dns.rb
140
+ - lib/dip/cli/nginx.rb
141
+ - lib/dip/cli/ssh.rb
142
+ - lib/dip/command.rb
143
+ - lib/dip/commands/compose.rb
144
+ - lib/dip/commands/dns.rb
145
+ - lib/dip/commands/nginx.rb
146
+ - lib/dip/commands/provision.rb
147
+ - lib/dip/commands/run.rb
148
+ - lib/dip/commands/ssh.rb
149
+ - lib/dip/config.rb
150
+ - lib/dip/environment.rb
151
+ - lib/dip/version.rb
152
+ homepage: https://github.com/bibendi/dip
153
+ licenses:
154
+ - MIT
155
+ metadata:
156
+ allowed_push_host: https://rubygems.org/
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.7.6
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Ruby gem CLI tool for better interacting docker-compose files.
177
+ test_files: []