docker-cli 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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(" ")}"
@@ -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 => [{ "/dir/local" => "/dir/inside/docker" }]
238
+ # expecting :mounts => { "/dir/local" => "/dir/inside/docker" }
224
239
  def process_mount(opts)
225
- if not (opts[:mount].nil? or opts[:mount].empty?)
226
- m = opts[:mount]
227
- m = [m] if not m.is_a?(Array)
228
- res = []
229
- m.each do |mm|
230
- host = mm.keys.first
231
- res << "-v #{host}:#{mm[host]}"
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.each do |host,docker|
246
- res << "-p #{host}:#{docker}"
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
- if @logger.nil?
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 parse_container_list(out, &block)
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
@@ -0,0 +1,205 @@
1
+
2
+ require_relative 'command_factory'
3
+
4
+ module Docker
5
+ module Cli
6
+ class DockerImage
7
+ include TR::CondUtils
8
+
9
+ def self.set_master_format(format)
10
+ @_doc_mformat = format
11
+ end
12
+ def self.master_format
13
+ if @_doc_mformat.nil?
14
+ @_doc_mformat = "{{.Repository}}#{self.separator}{{.ID}}#{self.separator}{{.Tag}}"
15
+ end
16
+ @_doc_mformat.freeze
17
+ end
18
+
19
+ def self.set_inspect_format(format)
20
+ @_dec_iformat = format
21
+ end
22
+ def self.inspect_format
23
+ if @_doc_iformat.nil?
24
+ @_doc_iformat = "{{.Created}}#{self.separator}{{.Size}}"
25
+ end
26
+ @_doc_iformat.freeze
27
+ end
28
+
29
+ def self.set_separator(sep)
30
+ @_doc_sep = sep
31
+ end
32
+ def self.separator
33
+ if @_doc_sep.nil?
34
+ @_doc_sep = ";"
35
+ end
36
+ @_doc_sep.freeze
37
+ end
38
+
39
+ def self.images
40
+ logger.warn "loading images from system is an expensive operation. Please implement some cache at aplication level to increase its efficiency"
41
+ cmd = []
42
+ cmd << Cli.docker_exe
43
+ cmd << "images"
44
+ cmd << "--format"
45
+ cmd << "\"#{self.master_format}\""
46
+
47
+ rres = []
48
+ res = Command.new(cmd).run
49
+ if res.successful?
50
+ res.each_line do |l|
51
+ sp = l.strip.split(self.separator)
52
+ rres << { name: sp[0], id: sp[1], tag: sp[2] }
53
+ end
54
+ else
55
+ logger.warn "Command '#{cmd.join(" ")}' failed.\n Error was : #{res.err_stream}"
56
+ end
57
+
58
+ fres = []
59
+ rres.each do |r|
60
+ ccmd = []
61
+ ccmd << cmd[0]
62
+ ccmd << "inspect"
63
+ ccmd << "-f"
64
+ ccmd << "'{{.ID}}#{self.separator}{{.Created}}#{self.separator}{{.Size}}'"
65
+ ccmd << r[:name]
66
+
67
+ rres = Command.new(ccmd).run
68
+ if rres.successful?
69
+ rres.each_line do |l|
70
+ sp = l.strip.split(self.separator)
71
+ rid = sp[0]
72
+ r[:image_id] = rid.split(":")[1]
73
+ r[:created] = sp[1]
74
+ r[:size] = sp[2]
75
+ r[:runtime] = true
76
+ fres << DockerImage.new(r[:name], r)
77
+ end
78
+ else
79
+ logger.warn "Command '#{ccmd.join(" ")}' failed.\n Error was : #{rres.err_stream}"
80
+ end
81
+ end
82
+
83
+ fres
84
+ end
85
+
86
+ def self.image(image)
87
+ cmd = []
88
+ cmd << Cli.docker_exe
89
+ cmd << "images"
90
+ cmd << "--format"
91
+ cmd << "\"#{self.master_format}\""
92
+ # image and tag is in single unit
93
+ # e.g.:
94
+ # jruby:9.2-jdk11
95
+ # jruby:9
96
+ # jruby:9.3.0.0-jdk11
97
+ cmd << image
98
+
99
+ rres = []
100
+ res = Command.new(cmd).run
101
+ if res.successful?
102
+ # single image could have many lines because they have
103
+ # different tags!
104
+ res.each_line do |l|
105
+ sp = l.strip.split(self.separator)
106
+ rres << { name: sp[0], id: sp[1], tag: sp[2] }
107
+ end
108
+ else
109
+ logger.warn "Command '#{cmd.join(" ")}' failed.\n Error was : #{res.err_stream}"
110
+ end
111
+
112
+ fres = []
113
+ rres.each do |r|
114
+ ccmd = []
115
+ ccmd << cmd[0]
116
+ ccmd << "inspect"
117
+ ccmd << "-f"
118
+ ccmd << "'{{.ID}}#{self.separator}{{.Created}}#{self.separator}{{.Size}}'"
119
+ ccmd << r[:name]
120
+
121
+ rres = Command.new(ccmd).run
122
+ if rres.successful?
123
+ rres.each_line do |l|
124
+ sp = l.strip.split(self.separator)
125
+ rid = sp[0]
126
+ r[:image_id] = rid.split(":")[1]
127
+ r[:created] = sp[1]
128
+ r[:size] = sp[2]
129
+ r[:runtime] = true
130
+ fres << DockerImage.new(r[:name], r)
131
+ end
132
+ else
133
+ logger.warn "Command '#{ccmd.join(" ")}' failed.\n Error was : #{rres.err_stream}"
134
+ end
135
+ end
136
+
137
+ fres
138
+ end
139
+
140
+ def self.build(imageName, dockerfile, context = ".")
141
+ command.build_image(imageName, dockerfile: dockerfile, context_root: context).run
142
+ end
143
+
144
+
145
+ attr_reader :name, :tag, :size, :created, :image_id, :sid
146
+
147
+ def initialize(name, opts = {})
148
+ @name = name
149
+ if not_empty?(opts)
150
+ @sid = opts[:sid]
151
+ @image_id = opts[:image_id]
152
+ @tag = opts[:tag]
153
+ @size = opts[:size]
154
+ @created = opts[:created]
155
+ @runtime = opts[:runtime]
156
+ end
157
+
158
+ @runtime = false if is_empty?(@runtime) or not_bool?(@runtime)
159
+
160
+ end
161
+
162
+ def is_runtime_image?
163
+ @runtime
164
+ end
165
+
166
+ def has_containers?
167
+ DockerRunLog.instance.image_has_containers?(@name)
168
+ end
169
+
170
+ def containers
171
+ if @_cont.nil?
172
+ # here assuming every run from system will be logged
173
+ # but what about the one not using the system?
174
+ @_cont = DockerRunLog.instance.image_containers(@name).collect { |e| DockerContainer.new(e[:container], e) }
175
+ end
176
+ @_cont
177
+ end
178
+
179
+ def delete!
180
+ if not_empty?(@tag)
181
+ command.delete_image(@name, @tag, opts)
182
+ else
183
+ raise IndefiniteOption, "Delete image cannot proceed because there might be more than single instance of the image. Please provide a tag for definite selection for deletion."
184
+ end
185
+ end
186
+
187
+ private
188
+ def self.command
189
+ if @_cmd.nil?
190
+ @_cmd = CommandFactory.new
191
+ end
192
+ @_cmd
193
+ end
194
+
195
+ def command
196
+ self.class.command
197
+ end
198
+
199
+ def self.logger
200
+ Cli.logger(:docker_image)
201
+ end
202
+
203
+ end
204
+ end
205
+ end