docker-cli 0.3.0 → 0.5.0

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