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.
@@ -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
@@ -0,0 +1,169 @@
1
+
2
+ require 'singleton'
3
+ require 'yaml'
4
+
5
+ module Docker
6
+ module Cli
7
+
8
+ class NonEmptyRecord < StandardError; end
9
+
10
+ class DockerRunLog
11
+ include TR::CondUtils
12
+ include Singleton
13
+
14
+ def log(image, container, opts = {})
15
+ if not_empty?(image) and not_empty?(container)
16
+ logfile[image] = [] if logfile[image].nil?
17
+ cont = { container: container, created_at: Time.now.to_i, last_run: Time.now.to_i }
18
+ cont = cont.merge(opts) if not_empty?(opts)
19
+ logfile[image] << cont
20
+ write
21
+ end
22
+ end
23
+
24
+ #def log_dockerfile(df)
25
+ # if not_empty?(df) and File.exist?(df)
26
+ # logfile[:dockerfile_signature] = [] if logfile[:dockerfile_signature].nil?
27
+ # d = digest_file(df)
28
+ # if not logfile[:dockerfile_signature].include?(d)
29
+ # logfile[:dockerfile_signature] << digest.hexdigest(File.read(df))
30
+ # end
31
+ # write
32
+ # end
33
+ #end
34
+
35
+ def log_dockerfile_image(df, image)
36
+ if not_empty?(df) and File.exist?(df) and not_empty?(image)
37
+ logfile[:dockerfile_images] = { } if logfile[:dockerfile_images].nil?
38
+ d = digest_file(df)
39
+ logfile[:dockerfile_images][d] = [] if logfile[:dockerfile_images][d].nil?
40
+ logfile[:dockerfile_images][d] << image
41
+ write
42
+ end
43
+ end
44
+
45
+ def has_dockerfile_built_to_image?(df)
46
+ if not_empty?(df)
47
+ if File.exist?(df)
48
+ d = digest_file(df)
49
+ else
50
+ d = df
51
+ end
52
+
53
+ not (logfile[:dockerfile_images].nil? or logfile[:dockerfile_images][d].nil?)
54
+ end
55
+ end
56
+
57
+ def dockerfile_images(df)
58
+ if File.exist?(df)
59
+ d = digest_file(df)
60
+ else
61
+ d = df
62
+ end
63
+
64
+ if (logfile[:dockerfile_images].nil? or logfile[:dockerfile_images][d].nil?)
65
+ []
66
+ else
67
+ logfile[:dockerfile_images][d]
68
+ end
69
+ end
70
+
71
+ def has_dockerfile_seen_before?(df)
72
+ logger.debug "dockerfile_seen_before? #{df}"
73
+ if not_empty?(df) and File.exist?(df)
74
+ d = Cli.digest_bin(File.read(df))
75
+ logger.debug "Digest : #{d}"
76
+ logger.debug "Record : #{logfile[:dockerfile_images]}"
77
+ if not logfile[:dockerfile_images].nil?
78
+ logfile[:dockerfile_images].include?(d)
79
+ else
80
+ false
81
+ end
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ def digest_file(path)
88
+ if not_empty?(path) and File.exist?(path)
89
+ Cli.digest_bin(File.read(path))
90
+ else
91
+ ""
92
+ end
93
+ end
94
+
95
+ def image_has_containers?(image)
96
+ not logfile[image].nil? and logfile[image].length > 0
97
+ end
98
+
99
+ def delete_image(image, opts = {})
100
+ if logfile[image].nil? or is_empty?(logfile[image])
101
+ logfile.delete(image)
102
+ elsif not_empty?(opts) and opts[:force] == true
103
+ logfile.delete(image)
104
+ else
105
+ raise NonEmptyRecord, "Image #{image} has #{logfile[image].length} container(s). Remove image failed."
106
+ end
107
+ end
108
+
109
+ def update_last_run(image, cont)
110
+ if not logfile[image].nil? and not logfile[image][cont].nil?
111
+ logfile[image][cont][:last_run] = Time.now.to_i
112
+ write
113
+ end
114
+ end
115
+
116
+ def image_containers(image)
117
+ if not logfile[image].nil?
118
+ logfile[image]
119
+ else
120
+ []
121
+ end
122
+ end
123
+
124
+ def all_logs
125
+ logfile.freeze
126
+ end
127
+
128
+ private
129
+ def logfile
130
+ if @_logfile.nil?
131
+ if File.exist?(log_path)
132
+ @_logfile = YAML.load(File.read(log_path))
133
+ else
134
+ @_logfile = {}
135
+ end
136
+ end
137
+ @_logfile
138
+ end
139
+
140
+ def write
141
+ File.open(log_path,"w") do |f|
142
+ f.write YAML.dump(logfile)
143
+ end
144
+ end
145
+
146
+ def log_path
147
+ if @_logPath.nil?
148
+ @_logPath = File.join(Dir.getwd, ".docker_run_log")
149
+ end
150
+ @_logPath
151
+ end
152
+
153
+ def digest
154
+ if @_digest.nil?
155
+ @_digest = Cli.digest
156
+ end
157
+ @_digest
158
+ end
159
+
160
+ def logger
161
+ if @_logger.nil?
162
+ @_logger = Cli.logger(:drLog)
163
+ end
164
+ @_logger
165
+ end
166
+
167
+ end # class DockerRunLog
168
+ end
169
+ end
@@ -0,0 +1,62 @@
1
+
2
+ require 'openssl'
3
+
4
+ module Docker
5
+ module Cli
6
+ class Dockerfile
7
+
8
+ class NoDockerfileFound < StandardError; end
9
+
10
+ def self.find_available(root = Dir.getwd)
11
+ Dir.glob("**/Dockerfile*")
12
+ end
13
+
14
+ def self.run_before?(dockerfile_path)
15
+ DockerRunLog.instance.has_dockerfile_seen_before?(dockerfile_path)
16
+ end
17
+
18
+ # expect dockerfile is CONTENT, not file path
19
+ def self.images(dockerfile)
20
+ DockerRunLog.instance.dockerfile_images(dockerfile)
21
+ end
22
+
23
+ def initialize(file)
24
+ @dfile = file
25
+ end
26
+
27
+ def render_dockerfile(vals = {}, &block)
28
+ if @_df.nil?
29
+ if @dfile.nil?
30
+ @_df = nil
31
+ else
32
+ if File.exist?(@dfile)
33
+ if is_erb_template?
34
+ @_df = process_dockerfile_template(@dfile, vals)
35
+ else
36
+ @_df = File.read(@dfile)
37
+ end
38
+ else
39
+ @_df = nil
40
+ end
41
+ end
42
+ end
43
+ @_df
44
+ end
45
+
46
+ def is_erb_template?
47
+ if File.exist?(@dfile)
48
+ cont = File.read(@dfile)
49
+ (cont =~ /<%=/ and cont =~ /%>/) != nil
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def process_dockerfile_template(file, values = {})
56
+ raise Error, "Given dockerfile to process as template not found" if not File.exist?(file)
57
+ DockerfileTemplate::TemplateEngine.new.process(File.read(file), values)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module Docker
3
+ module Cli
4
+ module DockerfileTemplate
5
+ module DupGemBundlerEnv
6
+
7
+ # DSL entry by including into DockerfileTemplate module
8
+ def dup_gem_bundler_env(&block)
9
+ if has_dev_gems?
10
+ logger.debug "Detected development gems"
11
+
12
+ #add_mandatory_key(:docker_root)
13
+ #if not has_mandatory_keys? and block
14
+ # @docker_root = block.call(:docker_root)
15
+ #else
16
+ raise TemplateKeyRequired, "docker_root is required for dup_gem_bundler_env to function" if is_empty?(@docker_root)
17
+ #end
18
+
19
+ # gen shell script
20
+ res = gen_script
21
+ block.call(:script_output, res) if block
22
+ localPath = File.join(Dir.getwd,"dup_gem_bundler_env.rb")
23
+ File.open(localPath,"w") do |f|
24
+ f.write res
25
+ end
26
+
27
+ inst = []
28
+ # copy inside docker
29
+ inst << "COPY #{File.basename(localPath)} /tmp/dup_gem_bundler_env.rb"
30
+ # run the script
31
+ inst << "RUN ruby /tmp/dup_gem_bundler_env.rb"
32
+ # for the docker just this two lines
33
+ # but the localPath must be there first before this two
34
+ # lines can come into effect
35
+ inst.join("\n")
36
+ else
37
+ ""
38
+ end
39
+ end
40
+
41
+ private
42
+ def has_dev_gems?
43
+ not Cli.find_dev_gems.empty?
44
+ end
45
+
46
+ def gen_script
47
+
48
+ res = %Q(
49
+ #!/usr/bin/env ruby
50
+
51
+ ## This file is auto-generated.
52
+
53
+ <% Docker::Cli.find_dev_gems.each do |name, pa| %>
54
+ `bundle config --global local.<%= name %> <%= File.join(@docker_root, File.basename(pa)) %>`
55
+ <% end %>
56
+ )
57
+
58
+ ERB.new(res).result(binding)
59
+
60
+ end
61
+
62
+ def logger
63
+ if @_logger
64
+ @_logger = Cli.logger(:temp_dup_gem_bundler_env)
65
+ end
66
+ @_logger
67
+ end
68
+
69
+
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,38 @@
1
+
2
+ require 'erb'
3
+ require_relative '../user_info'
4
+
5
+ module Docker
6
+ module Cli
7
+ module DockerfileTemplate
8
+ module MatchUser
9
+
10
+ # DSL entry
11
+ def match_user
12
+
13
+ logger.debug "match_user called"
14
+ ui = UserInfo.user_info
15
+ gi = UserInfo.group_info
16
+
17
+ ERB.new(user_template).result_with_hash({ user_group_id: gi[:gid], user_group_name: gi[:group_name], user_id: ui[:uid], user_login: ui[:login] })
18
+
19
+ end
20
+
21
+ private
22
+ def user_template
23
+ if @_ut.nil?
24
+ @_ut = []
25
+ @_ut << "RUN apt-get install -y sudo && groupadd -f -g <%= user_group_id %> <%= user_group_name %> && useradd -u <%= user_id %> -g <%= user_group_id %> -m <%= user_login %> && usermod -aG sudo <%= user_login %> && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
26
+ @_ut << "USER <%= user_login %>"
27
+ end
28
+ @_ut.join("\n")
29
+ end
30
+
31
+ def logger
32
+ Cli.logger(:temp_match_user)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,69 @@
1
+
2
+ Dir.glob(File.join(File.dirname(__FILE__),"dockerfile_template","*.rb")).each do |f|
3
+ require_relative f
4
+ end
5
+
6
+ module Docker
7
+ module Cli
8
+ module DockerfileTemplate
9
+ class TemplateKeyRequired < StandardError; end
10
+ class TemplateEngine
11
+ include MatchUser
12
+ include DupGemBundlerEnv
13
+
14
+ def process(cont, values = {}, &block)
15
+ logger.debug "Got values : #{values}"
16
+ values.each do |k,v|
17
+
18
+ logger.debug "Creating field #{k}"
19
+
20
+ self.class.class_eval <<-END
21
+ if not (Class.instance_methods.include?(:#{k}) and Class.instace_methods.include?(:#{k}=))
22
+ attr_accessor :#{k}
23
+ elsif not Class.instance_methods.include?(:#{k})
24
+ attr_reader :#{k}
25
+ elsif not Class.instance_methods.include?(:#{k}=)
26
+ attr_writer :#{k}
27
+ end
28
+ END
29
+
30
+ self.send("#{k}=", v)
31
+ end
32
+
33
+
34
+ ERB.new(cont).result(binding)
35
+ end
36
+
37
+ private
38
+ def logger
39
+ if @_logger.nil?
40
+ @_logger = Cli.logger(:df_template)
41
+ end
42
+ @_logger
43
+ end
44
+
45
+ def add_mandatory_key(key)
46
+ if @_man.nil?
47
+ @_man = []
48
+ end
49
+ @_man << key if not_empty?(key)
50
+ end
51
+
52
+ def mandatory_keys
53
+ @_man
54
+ end
55
+
56
+ def has_mandatory_keys?
57
+ given = true
58
+ mandatory_keys.each do |mk|
59
+ given = Class.instance_methods.include?(mk.to_sym) and Class.instance_methods.include?("#{mk}=".to_sym)
60
+ break if not given
61
+ end
62
+
63
+ given
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,25 @@
1
+
2
+ require 'bundler'
3
+
4
+ module Docker
5
+ module Cli
6
+ module BundlerHelper
7
+
8
+ def find_local_dev_gems
9
+
10
+ res = {}
11
+ Bundler.load.dependencies.each do |d|
12
+ if not d.source.nil?
13
+ src = d.source
14
+ if src.path.to_s != "."
15
+ res[d.name] = src.path.expand_path.to_s
16
+ end
17
+ end
18
+ end
19
+ res
20
+
21
+ end # find_local_dev_gem
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+
2
+
3
+ module Docker
4
+ module Cli
5
+ module ImageHelper
6
+
7
+ def build_image(pmt, cmdFact)
8
+
9
+ root = Dir.getwd
10
+ dockerfile = File.join(root, "Dockerfile")
11
+
12
+ again = true
13
+ while again
14
+ if not File.exist?(dockerfile)
15
+ dockerfile = pmt.ask(" #{dockerfile} does not exist. Please provide new location of Dockerfile: ", required: true)
16
+ else
17
+ again = false
18
+ end
19
+ end
20
+
21
+ again = true
22
+ while again
23
+ dname = pmt.ask(" Please provide name of image at local : ", required: true)
24
+ if cmdFact.is_image_exist?(dname)
25
+
26
+ reuse = pmt.yes?(" Given local image name '#{dname}' already taken. Use back the same image? 'No' to retry with new name : ")
27
+ if reuse
28
+ again = false
29
+ end
30
+ else
31
+ rv = cmdFact.build_image(dname, dockerfile: dockerfile)
32
+ raise CommandFailed, "Build image command failed. Error was : #{rv.err_stream}"
33
+ again = false
34
+ end
35
+ end
36
+
37
+ dname
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end