docker-cli 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.release_history.yml +4 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +24 -27
- data/Rakefile +2 -1
- data/docker-cli.gemspec +5 -2
- data/exe/_gemdocker +225 -0
- data/exe/dc-update +24 -0
- data/exe/gemdocker +52 -0
- data/exe/gemdocker_test +40 -0
- data/lib/docker/cli/command.rb +37 -31
- data/lib/docker/cli/command_factory.rb +50 -26
- data/lib/docker/cli/command_result.rb +16 -0
- data/lib/docker/cli/docker_composer.rb +113 -0
- data/lib/docker/cli/docker_container.rb +216 -0
- data/lib/docker/cli/docker_image.rb +205 -0
- data/lib/docker/cli/docker_run_log.rb +169 -0
- data/lib/docker/cli/dockerfile.rb +62 -0
- data/lib/docker/cli/dockerfile_template/dup_gem_bundler_env.rb +73 -0
- data/lib/docker/cli/dockerfile_template/match_user.rb +38 -0
- data/lib/docker/cli/dockerfile_template.rb +69 -0
- data/lib/docker/cli/gem/bundler_helper.rb +25 -0
- data/lib/docker/cli/image_helper.rb +43 -0
- data/lib/docker/cli/operations/args_parser.rb +53 -0
- data/lib/docker/cli/operations/run.rb +203 -0
- data/lib/docker/cli/operations/run_del.rb +67 -0
- data/lib/docker/cli/operations/run_keep.rb +118 -0
- data/lib/docker/cli/user_info.rb +39 -0
- data/lib/docker/cli/version.rb +1 -1
- data/lib/docker/cli.rb +64 -0
- data/scripts/create_user.sh.erb +10 -0
- metadata +51 -12
@@ -1,12 +1,15 @@
|
|
1
1
|
|
2
|
+
require 'erb'
|
3
|
+
|
2
4
|
require_relative 'command'
|
5
|
+
require_relative 'user_info'
|
3
6
|
|
4
7
|
module Docker
|
5
8
|
module Cli
|
6
9
|
class CommandFactory
|
7
10
|
include TR::CondUtils
|
8
11
|
|
9
|
-
def build_image(name = "", opts = { })
|
12
|
+
def build_image(name = "", opts = { }, &block)
|
10
13
|
|
11
14
|
opts = { } if opts.nil?
|
12
15
|
cmd = []
|
@@ -37,9 +40,9 @@ module Docker
|
|
37
40
|
cmd << "images"
|
38
41
|
cmd << "-q"
|
39
42
|
if not_empty?(tag)
|
40
|
-
cmd << "#{name}:#{tag}"
|
43
|
+
cmd << "\"#{name}:#{tag}\""
|
41
44
|
else
|
42
|
-
cmd << name
|
45
|
+
cmd << "\"#{name}\""
|
43
46
|
end
|
44
47
|
|
45
48
|
logger.debug "Find image: #{cmd.join(" ")}"
|
@@ -79,7 +82,7 @@ module Docker
|
|
79
82
|
cmd << "ps"
|
80
83
|
cmd << "-q"
|
81
84
|
cmd << "-f"
|
82
|
-
cmd << "name
|
85
|
+
cmd << "name=\"#{name}\""
|
83
86
|
|
84
87
|
logger.debug "Find container: #{cmd.join(" ")}"
|
85
88
|
|
@@ -106,6 +109,10 @@ module Docker
|
|
106
109
|
Command.new(cmd)
|
107
110
|
end
|
108
111
|
|
112
|
+
#
|
113
|
+
# Create container from image directly
|
114
|
+
# e.g. > docker run -it <image> "/bin/bash"
|
115
|
+
#
|
109
116
|
def create_container_from_image(image, opts = { })
|
110
117
|
opts = {} if opts.nil?
|
111
118
|
cmd = []
|
@@ -113,13 +120,21 @@ module Docker
|
|
113
120
|
cmd << "run"
|
114
121
|
cmd << "-i" if opts[:interactive] == true
|
115
122
|
cmd << "-t" if opts[:tty] == true
|
123
|
+
cmd << "-d" if opts[:detached] == true
|
124
|
+
cmd << "--rm" if opts[:del] == true
|
116
125
|
if not (opts[:container_name].nil? or opts[:container_name].empty?)
|
117
|
-
cmd << "--name #{opts[:container_name]}"
|
126
|
+
cmd << "--name \"#{opts[:container_name]}\""
|
118
127
|
end
|
119
128
|
|
120
129
|
cmd << process_mount(opts)
|
121
130
|
cmd << process_port(opts)
|
122
131
|
|
132
|
+
if opts[:match_user] == true
|
133
|
+
ui = UserInfo.user_info
|
134
|
+
gi = UserInfo.group_info
|
135
|
+
cmd << "-u #{ui[:uid]}:#{gi[:gid]}"
|
136
|
+
end
|
137
|
+
|
123
138
|
cmd << image
|
124
139
|
|
125
140
|
if not_empty?(opts[:command])
|
@@ -128,7 +143,7 @@ module Docker
|
|
128
143
|
|
129
144
|
interactive = false
|
130
145
|
interactive = true if opts[:interactive] or opts[:tty]
|
131
|
-
|
146
|
+
|
132
147
|
logger.debug "Run Container from Image : #{cmd.join(" ")}"
|
133
148
|
Command.new(cmd, (interactive ? true : false))
|
134
149
|
end # run_container_from_image
|
@@ -220,17 +235,17 @@ module Docker
|
|
220
235
|
|
221
236
|
|
222
237
|
private
|
223
|
-
# expecting :mounts =>
|
238
|
+
# expecting :mounts => { "/dir/local" => "/dir/inside/docker" }
|
224
239
|
def process_mount(opts)
|
225
|
-
if not (opts[:
|
226
|
-
m = opts[:
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
240
|
+
if not_empty?(opts[:mounts]) #not (opts[:mounts].nil? or opts[:mounts].empty?)
|
241
|
+
m = opts[:mounts]
|
242
|
+
if m.is_a?(Hash)
|
243
|
+
res = []
|
244
|
+
m.each do |local, docker|
|
245
|
+
res << "-v #{local}:#{docker}"
|
246
|
+
end
|
247
|
+
res.join(" ")
|
232
248
|
end
|
233
|
-
res.join(" ")
|
234
249
|
else
|
235
250
|
""
|
236
251
|
end
|
@@ -238,13 +253,20 @@ module Docker
|
|
238
253
|
end # process_mount
|
239
254
|
|
240
255
|
def process_port(opts)
|
241
|
-
if not (opts[:ports].nil? or opts[:ports].empty?)
|
256
|
+
if not_empty?(opts[:ports]) #not (opts[:ports].nil? or opts[:ports].empty?)
|
242
257
|
po = opts[:ports]
|
243
|
-
po = [po] if not po.is_a?(Array)
|
244
258
|
res = []
|
245
|
-
po.
|
246
|
-
|
259
|
+
if po.is_a?(Hash)
|
260
|
+
po.each do |host, docker|
|
261
|
+
res << "-p #{host}:#{docker}"
|
262
|
+
end
|
247
263
|
end
|
264
|
+
#po = [po] if not po.is_a?(Array)
|
265
|
+
#po.each do |e|
|
266
|
+
# # 1st is port on host
|
267
|
+
# # 2nd is port inside container
|
268
|
+
# res << "-p #{e.keys.first}:#{e.values.first}"
|
269
|
+
#end
|
248
270
|
res.join(" ")
|
249
271
|
else
|
250
272
|
""
|
@@ -253,15 +275,17 @@ module Docker
|
|
253
275
|
end
|
254
276
|
|
255
277
|
def logger
|
256
|
-
|
257
|
-
@logger = TeLogger::Tlogger.new
|
258
|
-
@logger.tag = :docker_cmd
|
259
|
-
end
|
260
|
-
@logger
|
278
|
+
Cli.logger(:cmdFact)
|
261
279
|
end
|
262
280
|
|
263
|
-
def
|
264
|
-
|
281
|
+
def build_add_user_script
|
282
|
+
path = File.join(File.dirname(__FILE__),"..","..","..","scripts","create_user.sh.erb")
|
283
|
+
if File.exist?(path)
|
284
|
+
ui = UserInfo.user_info
|
285
|
+
gi = UserInfo.group_info
|
286
|
+
|
287
|
+
ERB.new(File.read(path)).result_with_hash({ user_group_id: gi[:gid], user_group_name: gi[:group_name], user_id: ui[:uid], user_login: ui[:login] })
|
288
|
+
end
|
265
289
|
end
|
266
290
|
|
267
291
|
end
|
@@ -19,6 +19,10 @@ module Docker
|
|
19
19
|
is_empty?(@out)
|
20
20
|
end
|
21
21
|
|
22
|
+
def is_err_stream_empty?
|
23
|
+
is_empty?(@err)
|
24
|
+
end
|
25
|
+
|
22
26
|
def out_stream
|
23
27
|
@out.join("\n")
|
24
28
|
end
|
@@ -35,6 +39,18 @@ module Docker
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
42
|
+
def success?
|
43
|
+
not failed?
|
44
|
+
end
|
45
|
+
alias_method :successful?, :success?
|
46
|
+
|
47
|
+
def each_line(&block)
|
48
|
+
out_stream.each_line(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def lines
|
52
|
+
out_stream.lines
|
53
|
+
end
|
38
54
|
end
|
39
55
|
|
40
56
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require_relative 'user_info'
|
5
|
+
|
6
|
+
module Docker
|
7
|
+
module Cli
|
8
|
+
class DockerComposer
|
9
|
+
include TR::CondUtils
|
10
|
+
include TR::ArgUtils
|
11
|
+
|
12
|
+
arg_spec do
|
13
|
+
|
14
|
+
callback :pre_processing do |a|
|
15
|
+
parse_input(a)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_input(argv, &block)
|
21
|
+
@dcPath = argv.first
|
22
|
+
@dcPath = "./docker-compose.yml" if is_empty?(@dcPath)
|
23
|
+
@dcPath = "./docker-compose.yaml" if not File.exist?(@dcPath)
|
24
|
+
raise RuntimeException, "docker-compose.[yml,yaml] not found. Please provide the docker-compose.yml file to load" if not File.exist?(@dcPath)
|
25
|
+
|
26
|
+
@outPath = argv[1]
|
27
|
+
@outPath = "#{@dcPath}-gen.yml" if is_empty?(@outPath)
|
28
|
+
|
29
|
+
process_dc
|
30
|
+
|
31
|
+
[true, []]
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_dc
|
35
|
+
if File.exist?(@dcPath)
|
36
|
+
|
37
|
+
cont = YAML.safe_load(File.read(@dcPath))
|
38
|
+
|
39
|
+
if not_empty?(cont["services"])
|
40
|
+
|
41
|
+
cont["services"].each do |servName, conf|
|
42
|
+
|
43
|
+
# if user key is empty, match with current uid and gid
|
44
|
+
if conf.keys.include?("user") and is_empty?(conf["user"])
|
45
|
+
conf["user"] = "#{user_info[:uid]}:#{user_group_info[:gid]}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# add to volumes if there is development gems found
|
49
|
+
if not_empty?(conf["volumes"])
|
50
|
+
vol = conf["volumes"]
|
51
|
+
|
52
|
+
if vol.include?("devgems")
|
53
|
+
|
54
|
+
vol.delete("devgems")
|
55
|
+
logger.debug "Volume after delete : #{vol.inspect}"
|
56
|
+
|
57
|
+
devGems = Cli.find_dev_gems
|
58
|
+
logger.debug " Found #{devGems.length} devgems"
|
59
|
+
if devGems.length > 0
|
60
|
+
if @parse_argv_block
|
61
|
+
@mount_root = @parse_argv_block.call(:prompt_docker_mount_root)
|
62
|
+
else
|
63
|
+
raise RuntimeException, "Please provide a block to prompt for missing parameters"
|
64
|
+
end
|
65
|
+
|
66
|
+
devGems.each do |name,path|
|
67
|
+
vol << "#{path}:#{File.join(@mount_root, name)}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
reCont = cont.to_yaml
|
79
|
+
reCont = reCont.gsub("---","")
|
80
|
+
File.open(@outPath, "w") do |f|
|
81
|
+
f.write reCont
|
82
|
+
end
|
83
|
+
|
84
|
+
else
|
85
|
+
raise RuntimeException, "Given docker compose file '#{@dcPath}' does not exist"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def user_info
|
91
|
+
if @_user_info.nil?
|
92
|
+
@_user_info = UserInfo.user_info
|
93
|
+
end
|
94
|
+
@_user_info
|
95
|
+
end
|
96
|
+
|
97
|
+
def user_group_info
|
98
|
+
if @_userg_info.nil?
|
99
|
+
@_userg_info = UserInfo.group_info
|
100
|
+
end
|
101
|
+
@_userg_info
|
102
|
+
end
|
103
|
+
|
104
|
+
def logger
|
105
|
+
if @_logger.nil?
|
106
|
+
@_logger = Cli.logger(:docomp)
|
107
|
+
end
|
108
|
+
@_logger
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
|
2
|
+
require_relative 'command_factory'
|
3
|
+
|
4
|
+
module Docker
|
5
|
+
module Cli
|
6
|
+
|
7
|
+
# only use during creation of new container
|
8
|
+
class ContainerProfile
|
9
|
+
include TR::CondUtils
|
10
|
+
|
11
|
+
attr_accessor :run_command, :image_name
|
12
|
+
def initialize
|
13
|
+
@interactive = true
|
14
|
+
@run_detached = false
|
15
|
+
@remove_after_run = false
|
16
|
+
@match_user = false
|
17
|
+
@run_command = "/bin/bash"
|
18
|
+
@mounts = {}
|
19
|
+
@ports = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def mount_points
|
23
|
+
@mounts.freeze
|
24
|
+
end
|
25
|
+
def add_mount_point(host, inside_docker)
|
26
|
+
if not_empty?(host) and not_empty?(inside_docker)
|
27
|
+
@mounts[host] = inside_docker
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def ports
|
32
|
+
@ports.freeze
|
33
|
+
end
|
34
|
+
def add_port_mapping(host, docker)
|
35
|
+
if not_empty?(host) and not_empty?(docker)
|
36
|
+
@ports[host] = docker
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_interactive?
|
41
|
+
@interactive
|
42
|
+
end
|
43
|
+
def interactive=(val)
|
44
|
+
@interactive = val
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_run_detached?
|
48
|
+
@run_detached
|
49
|
+
end
|
50
|
+
def run_detached=(val)
|
51
|
+
@run_detached = val
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_after_run?
|
55
|
+
@remove_after_run
|
56
|
+
end
|
57
|
+
def remove_after_run=(val)
|
58
|
+
@remove_after_run = val
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_match_user?
|
62
|
+
@match_user
|
63
|
+
end
|
64
|
+
def match_user=(val)
|
65
|
+
@match_user = val
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_hash
|
69
|
+
# this returns a hash that match expectation of input in CommandFactory.create_container_from_image
|
70
|
+
{ interactive: @interactive, tty: @interactive, detached: @run_detached, del: @remove_after_run, mounts: @mounts, ports: @ports, match_user: @match_user, command: @run_command }
|
71
|
+
end
|
72
|
+
|
73
|
+
end # class NewContainerProfile
|
74
|
+
|
75
|
+
class DockerContainer
|
76
|
+
include TR::CondUtils
|
77
|
+
|
78
|
+
#def self.is_exists?(name)
|
79
|
+
# res = command.find_from_all_container(name, opts).run
|
80
|
+
# raise CommandFailed, "Command to check if container exist failed. Error was : #{res.err_stream}" if not res.successful?
|
81
|
+
# not res.is_out_stream_empty?
|
82
|
+
#end
|
83
|
+
|
84
|
+
#def self.create(image, opts = {}, &block)
|
85
|
+
# command.create_container_from_image(image, opts).run
|
86
|
+
#end
|
87
|
+
|
88
|
+
#def self.prep_container(image, opts = {})
|
89
|
+
# # render the create_user script
|
90
|
+
# res = build_add_user_script
|
91
|
+
# # system always mount current dir inside docker
|
92
|
+
# dest = File.join(opts[:mount_local],"create_user.sh")
|
93
|
+
# File.open(dest,"w") do |f|
|
94
|
+
# f.write res
|
95
|
+
# end
|
96
|
+
# `chmod +x #{dest}`
|
97
|
+
|
98
|
+
# # create non interactive session to create the user & group first
|
99
|
+
# opts[:command] = "#{File.join(opts[:mount_docker],"create_user.sh")}"
|
100
|
+
# opts[:detached] = true
|
101
|
+
# command.create_container_from_image(image, opts).run
|
102
|
+
#end
|
103
|
+
|
104
|
+
def self.containers_of_image_from_history(img, opts = { })
|
105
|
+
cont = DockerRunLog.instance.containers(img)
|
106
|
+
res = {}
|
107
|
+
if cont.length == 1
|
108
|
+
c = cont.first
|
109
|
+
res[c[:container]] = c
|
110
|
+
else
|
111
|
+
cont.each do |c|
|
112
|
+
res[c[:container]] = "#{c[:container]} [Last Access : #{Time.at(c[:last_run])}] - Config : #{c[:argv]}"
|
113
|
+
end
|
114
|
+
res
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#def self.containers(image = nil)
|
119
|
+
#
|
120
|
+
#end
|
121
|
+
|
122
|
+
#def self.container(name)
|
123
|
+
#
|
124
|
+
#end
|
125
|
+
|
126
|
+
attr_reader :name
|
127
|
+
|
128
|
+
def initialize(name, history = nil)
|
129
|
+
@name = name
|
130
|
+
@history = history
|
131
|
+
end
|
132
|
+
|
133
|
+
def is_exist?
|
134
|
+
res = command.find_from_all_container(@name).run
|
135
|
+
raise CommandFailed, "Command to check if container exist failed. Error was : #{res.err_stream}" if not res.successful?
|
136
|
+
not res.is_out_stream_empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
def create(container_profile)
|
140
|
+
raise CommandFailed, " Image name is not given to create the container '#{@name}'" if is_empty?(container_profile.image_name)
|
141
|
+
opts = container_profile.to_hash
|
142
|
+
opts[:container_name] = @name
|
143
|
+
command.create_container_from_image(container_profile.image_name, opts).run
|
144
|
+
end
|
145
|
+
|
146
|
+
def name_for_display
|
147
|
+
if @history.nil?
|
148
|
+
@name
|
149
|
+
else
|
150
|
+
"#{@history[:container]} [Last Access : #{Time.at(@history[:last_run])}] - Config : #{@history[:argv]}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def run_before?
|
155
|
+
not @history.nil?
|
156
|
+
end
|
157
|
+
|
158
|
+
def history
|
159
|
+
if not @history.nil?
|
160
|
+
@history.freeze
|
161
|
+
else
|
162
|
+
@history = {}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def is_running?
|
167
|
+
res = command.find_running_container(@name).run
|
168
|
+
raise CommandFailed, "Command to find running container failed. Error was : #{res.err_stream}" if not res.successful?
|
169
|
+
not res.is_out_stream_empty?
|
170
|
+
end
|
171
|
+
|
172
|
+
def start(&block)
|
173
|
+
command.start_container(@name).run
|
174
|
+
end
|
175
|
+
|
176
|
+
def stop(&block)
|
177
|
+
command.stop_container(@name).run
|
178
|
+
end
|
179
|
+
|
180
|
+
def attach(&block)
|
181
|
+
command.attach_container(@name).run
|
182
|
+
end
|
183
|
+
|
184
|
+
def delete!(&block)
|
185
|
+
command.delete_container(@name).run
|
186
|
+
end
|
187
|
+
|
188
|
+
def run(cmd, opts = {}, &block)
|
189
|
+
command.run_command_in_running_container(@name, cmd, opts).run
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def self.command
|
194
|
+
if @_cmd.nil?
|
195
|
+
@_cmd = CommandFactory.new
|
196
|
+
end
|
197
|
+
@_cmd
|
198
|
+
end
|
199
|
+
|
200
|
+
def command
|
201
|
+
self.class.command
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.build_add_user_script
|
205
|
+
path = File.join(File.dirname(__FILE__),"..","..","..","scripts","create_user.sh.erb")
|
206
|
+
if File.exist?(path)
|
207
|
+
ui = UserInfo.user_info
|
208
|
+
gi = UserInfo.group_info
|
209
|
+
|
210
|
+
ERB.new(File.read(path)).result_with_hash({ user_group_id: gi[:gid], user_group_name: gi[:group_name], user_id: ui[:uid], user_login: ui[:login] })
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|