Hitcher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+
2
+ require 'singleton'
3
+ require 'tlogger'
4
+
5
+ require_relative 'config'
6
+
7
+ module Hitcher
8
+ class Global
9
+ include Singleton
10
+
11
+ attr_accessor :glog, :config
12
+ def initialize
13
+ @glog = Tlogger.init
14
+ @glog.show_source
15
+ @glog.tag = "Hitcher"
16
+
17
+ @config = Config.init_config if @config.nil?
18
+ end
19
+
20
+ def reload_config
21
+ @config = Config.init_config
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,206 @@
1
+
2
+ require_relative '../../global'
3
+ require 'toolrack'
4
+
5
+ require 'ptools'
6
+
7
+ module Hitcher
8
+ module Docker
9
+ module CommandBuilder
10
+ include Antrapol::ToolRack::ConditionUtils
11
+
12
+ def docker_exe
13
+ path = File.which("docker")
14
+ if path.nil?
15
+ "docker"
16
+ else
17
+ path
18
+ end
19
+ end
20
+
21
+ def cb_find_image(name, tag = "", opts = { })
22
+ raise Hitcher::Error, "Name is mandatory" if is_empty?(name)
23
+ cmd = []
24
+ cmd << docker_exe
25
+ cmd << "images"
26
+ cmd << "-q"
27
+ if not_empty?(tag)
28
+ cmd << "#{name}:#{tag}"
29
+ else
30
+ cmd << name
31
+ end
32
+
33
+ ccmd = cmd.join(" ")
34
+ logger.debug "Find image: #{ccmd}"
35
+ ccmd
36
+ end
37
+
38
+ def cb_find_running_container(name, opts = { })
39
+ raise Hitcher::Error, "Name is mandatory" if is_empty?(name)
40
+ cmd = []
41
+ cmd << docker_exe
42
+ cmd << "ps"
43
+ cmd << "-q"
44
+ cmd << "-f"
45
+ cmd << "name=#{name}"
46
+
47
+ ccmd = cmd.join(" ")
48
+ logger.debug "Find container: #{ccmd}"
49
+ ccmd
50
+
51
+ end
52
+
53
+ def cb_find_all_container(name, opts = { })
54
+ raise Hitcher::Error, "Name is mandatory" if is_empty?(name)
55
+ cmd = []
56
+ cmd << docker_exe
57
+ cmd << "ps"
58
+ cmd << "-aq"
59
+ cmd << "-f"
60
+ cmd << "name=#{name}"
61
+
62
+ ccmd = cmd.join(" ")
63
+ logger.debug "Find all container: #{ccmd}"
64
+ ccmd
65
+
66
+ end
67
+
68
+ def cb_build_image(name = "", opts = { })
69
+ cmd = []
70
+ cmd << docker_exe
71
+ cmd << "build"
72
+ if not (name.nil? or name.empty?)
73
+ cmd << "-t #{name}"
74
+ end
75
+ cmd << "."
76
+
77
+ ccmd = cmd.join(" ")
78
+ Hitcher::Global.instance.glog.debug "Build Image : #{ccmd}"
79
+
80
+ ccmd
81
+ end # build_image
82
+
83
+ def cb_create_container_from_image(image, opts = { interactive: true })
84
+ cmd = []
85
+ cmd << docker_exe
86
+ cmd << "run"
87
+ cmd << "-i" if opts[:interactive] == true
88
+ cmd << "-t" if opts[:tty] == true
89
+ if not (opts[:container_name].nil? or opts[:container_name].empty?)
90
+ cname = opts[:container_name]
91
+ cmd << "--name #{cname}"
92
+ end
93
+
94
+ cmd << process_mount(opts)
95
+ cmd << process_port(opts)
96
+
97
+ cmd << image
98
+
99
+ if not_empty?(opts[:command])
100
+ cmd << "\"#{opts[:command]}\""
101
+ end
102
+
103
+ ccmd = cmd.join(" ")
104
+ Hitcher::Global.instance.glog.debug "Run Container from Image : #{ccmd}"
105
+
106
+ ccmd
107
+ end # run_container_from_image
108
+
109
+ def cb_start_container(container, opts = { })
110
+
111
+ cmd = []
112
+ cmd << docker_exe
113
+ cmd << "container"
114
+ cmd << "start"
115
+ cmd << container
116
+
117
+ ccmd = cmd.join(" ")
118
+
119
+ Hitcher::Global.instance.glog.debug "Start Container : #{ccmd}"
120
+
121
+ ccmd
122
+ end
123
+
124
+ def cb_run_command_in_container(container, opts = { })
125
+ cmd = []
126
+ cmd << docker_exe
127
+ cmd << "container"
128
+ cmd << "exec"
129
+
130
+ if is_empty?(opts[:command])
131
+ cmd << "-t" if not_empty?(opts[:tty]) and opts[:tty] == true
132
+ cmd << "-i" if not_empty?(opts[:interactive]) and opts[:interactive] == true
133
+ end
134
+
135
+ cmd << container
136
+
137
+ if is_empty?(opts[:command])
138
+ cmd << "/bin/bash --login"
139
+ else
140
+ cmd << opts[:command]
141
+ end
142
+
143
+ ccmd = cmd.join(" ")
144
+ Hitcher::Global.instance.glog.debug "Run command in container : #{ccmd}"
145
+
146
+ ccmd
147
+ end # container_prompt
148
+
149
+
150
+ private
151
+ def logger
152
+ if @logger.nil?
153
+ @logger = Hitcher::Global.instance.glog
154
+ end
155
+ @logger
156
+ end
157
+
158
+ def process_mount(opts)
159
+ if not (opts[:mounts].nil? or opts[:mounts].empty?)
160
+ m = opts[:mounts]
161
+ m = [m] if not m.is_a?(Array)
162
+ res = []
163
+ m.each do |mm|
164
+ l = []
165
+ l << "-v "
166
+ l << mm[:host]
167
+ l << ":"
168
+ l << mm[:docker]
169
+ res << l.join
170
+ end
171
+
172
+ res.join(" ")
173
+ else
174
+ ""
175
+ end
176
+
177
+ end # process_mount
178
+
179
+ def process_port(opts)
180
+ if not (opts[:ports].nil? or opts[:ports].empty?)
181
+ po = opts[:ports]
182
+ po = [po] if not po.is_a?(Array)
183
+ res = []
184
+ po.each do |ppo|
185
+ l = []
186
+ l << "-p "
187
+ l << ppo[:host]
188
+ l << ":"
189
+ l << ppo[:docker]
190
+ res << l.join
191
+ end
192
+
193
+ res.join(" ")
194
+ else
195
+ ""
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ class DockerCommandBuilder
203
+ include Hitcher::Docker::CommandBuilder
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,156 @@
1
+
2
+ require "tty-prompt"
3
+ require 'tty-command'
4
+ require 'toolrack'
5
+ require_relative "command_builder"
6
+ require_relative "../../command_runner"
7
+
8
+ module Hitcher
9
+ module Docker
10
+ module CommandRunner
11
+ include Antrapol::ToolRack::ConditionUtils
12
+ include Hitcher::Docker::CommandBuilder
13
+ include Hitcher::CommandRunner
14
+
15
+ def initialize(docker)
16
+ @dockerConf = docker
17
+ end
18
+
19
+ def image_exist?(opts = { })
20
+ name = opts[:image_name] || @dockerConf.image
21
+ tag = opts[:image_tag] || ""
22
+
23
+ cmd = cb_find_image(name, tag)
24
+ run_command(cmd)
25
+ end
26
+
27
+ def container_exist?(opts = { })
28
+
29
+ name = opts[:container_name] || @dockerConf.cName
30
+
31
+ if opts[:new_instance]
32
+ name = "#{name}_#{Time.now.localtime.strftime("%Y%m%d_%H%M%S_%L%N")}"
33
+ opts[:new_container_name] = name
34
+ end
35
+
36
+ cmd = cb_find_running_container(name, opts)
37
+
38
+ res, out, err = run_command(cmd)
39
+ if is_empty?(out)
40
+ cmd2 = cb_find_all_container(name, opts)
41
+ res2, out2, err2 = run_command(cmd2)
42
+ if is_empty?(out2)
43
+ [false, ""]
44
+ else
45
+ [true, :stopped, out2]
46
+ end
47
+ else
48
+ [true,:running, out]
49
+ end
50
+ end
51
+
52
+ # build new image based on Dockerfile
53
+ def build_image(opts = { })
54
+
55
+ df = File.join(Dir.getwd, "Dockerfile")
56
+ if not @dockerConf.df.is_empty?
57
+ File.open(df,"w") do |f|
58
+ f.write @dockerConf.df.cont.join("\n")
59
+ end
60
+ elsif not File.exist?(df)
61
+ raise Exception, "No Dockerfile available to build image."
62
+ end
63
+
64
+ name = opts[:image_name] || @dockerConf.image
65
+ cmd = cb_build_image(name, opts)
66
+ run_command(cmd)
67
+ end # build_image
68
+
69
+ def create_container(opts = { })
70
+ cName = opts[:container_name] || @dockerConf.cName
71
+ iName = opts[:image_name] || @dockerConf.image
72
+
73
+ opt = { }
74
+ opt.merge!(opts)
75
+ if opts[:new_instance]
76
+ opt[:container_name] = opts[:new_container_name]
77
+ else
78
+ opt[:container_name] = cName
79
+ end
80
+ opt[:mounts] = @dockerConf.mounts.collect { |m| m.hashify }
81
+ opt[:ports] = @dockerConf.exposes.collect { |e| e.hashify }
82
+ cmd = cb_create_container_from_image(iName, opt)
83
+ begin
84
+ v = run_in_new_terminal(cmd)
85
+ [:term,v]
86
+ rescue TerminalNotDefined => ex
87
+ logger.debug "Terminal is not set. Creating the shell script instead"
88
+ path = File.join(Dir.getwd,"create_container.sh")
89
+ File.open(path,"w") do |f|
90
+ f.puts "#!/bin/sh"
91
+ f.puts ""
92
+ f.puts cmd
93
+ f.puts
94
+ end
95
+
96
+ FileUtils.chmod "+x", path
97
+
98
+ [:file, path]
99
+ end
100
+ end
101
+
102
+ def start_container(opts = { })
103
+ name = opts[:name] || @dockerConf.cName
104
+ if opts[:new_instance]
105
+ name = opts[:new_container_name]
106
+ end
107
+
108
+ cmd = cb_start_container(name, opts)
109
+ run_command(cmd)
110
+ end
111
+
112
+ def run_command_in_container(command = "", opts = { })
113
+
114
+ name = opts[:container_name] || @dockerConf.cName
115
+ if opts[:new_instance]
116
+ name = opts[:new_container_name]
117
+ end
118
+ opt = { }
119
+ opt.merge!(opts)
120
+ opt[:command] = command
121
+ cmd = cb_run_command_in_container(name, opt)
122
+
123
+ # if command is empty default is "/bin/bash --login"
124
+ if is_empty?(command) or opts[:tty] == true or opts[:interactive] == true
125
+ begin
126
+ v = run_in_new_terminal(cmd)
127
+ [:term, v]
128
+ rescue TerminalNotDefined => ex
129
+
130
+ path = File.join(Dir.getwd,"prompt.sh")
131
+ logger.debug "Terminal is not set. Creating the shell script for run command in container instead [#{File.basename(path)}]"
132
+ File.open(path,"w") do |f|
133
+ f.puts "#!/bin/sh"
134
+ f.puts ""
135
+ f.puts cmd
136
+ f.puts
137
+ end
138
+
139
+ FileUtils.chmod "+x", path
140
+
141
+ [:file, path]
142
+
143
+ end
144
+ else
145
+ run_command(cmd)
146
+ end
147
+
148
+ end
149
+
150
+ end # module CommandRunner
151
+
152
+ class DockerCommandExecutor
153
+ include Hitcher::Docker::CommandRunner
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,229 @@
1
+
2
+ require_relative 'template'
3
+ require_relative 'command_builder'
4
+ require_relative 'command_runner'
5
+
6
+ module Hitcher
7
+ module Docker
8
+
9
+ class Mount
10
+ attr_accessor :host, :docker
11
+ def initialize(host = "",docker = "")
12
+ @host = host
13
+ @docker = docker
14
+ end
15
+
16
+ def hashify
17
+ { host: @host, docker: @docker }
18
+ end
19
+
20
+ def self.parse(blc)
21
+ res = []
22
+ if not (blc.nil? or blc.empty?)
23
+ blc.each do |e|
24
+ if e =~ /mount/
25
+ l = e.split(" ")
26
+ if l.length == 2
27
+ else
28
+ end
29
+ elsif e =~ /,/
30
+ # handle host: /home/xxx/dev, docker: /opt/dev
31
+ m = Mount.new
32
+ e.split(",").each do |ee|
33
+ s = ee.split(":")
34
+ case s[0].strip
35
+ when "host"
36
+ m.host = translate(s[1].strip)
37
+ when "docker"
38
+ m.docker = s[1].strip
39
+ else
40
+ raise Hitcher::Error, "Unknown mount point key '#{s[0]}'"
41
+ end
42
+ end
43
+
44
+ res << m
45
+ elsif e =~ /:/
46
+ # handle host:docker ==> /home/xxx/dev:/opt/dev
47
+ ee = e.split(":")
48
+ m = Mount.new
49
+ m.host = translate(ee[0])
50
+ m.docker = ee[1]
51
+ res << m
52
+ end
53
+ end
54
+ end
55
+
56
+ res
57
+ end # parse
58
+
59
+ def self.translate(key)
60
+ case key.upcase
61
+ when "$PWD","."
62
+ Dir.getwd
63
+ else
64
+ if key =~ /../
65
+ File.expand_path(key)
66
+ else
67
+ key
68
+ end
69
+ end
70
+ end
71
+ end # class Mount
72
+
73
+ class Port
74
+ attr_accessor :host, :docker
75
+ def initialize(host = nil, docker = nil)
76
+ @host = host
77
+ @docker = docker
78
+ end
79
+
80
+ def hashify
81
+ { host: @host, docker: @docker }
82
+ end
83
+ end # class Port
84
+
85
+ #
86
+ # class Dockerfile
87
+ #
88
+ class Dockerfile
89
+ attr_reader :cont
90
+ def parse(blc)
91
+ @cont = []
92
+ if not (blc.nil? or blc.empty?)
93
+ blc.each do |e|
94
+ if e =~ /dockerfile/
95
+ l = e.split(" ")
96
+ if l.length == 2
97
+ # no parameters
98
+ else
99
+ # todo extract parameters
100
+ end
101
+
102
+ elsif e.strip != "end"
103
+ @cont << e
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def is_empty?
110
+ @cont.nil? or @cont.empty?
111
+ end
112
+ end # class Dockerfile
113
+
114
+ ##
115
+ ## Module Dsl
116
+ ##
117
+ module Dsl
118
+
119
+ attr_reader :image, :cName, :df, :mounts, :exposes, :interactive_mode, :tty_mode
120
+
121
+ def image_name(name)
122
+ @image = name
123
+ end
124
+
125
+ def container_name(name)
126
+ @cName = name
127
+ end
128
+
129
+ def block_kw?(kw)
130
+ ["dockerfile","mount"].include?(kw.to_s)
131
+ end
132
+
133
+ def handle_block(kw,blc)
134
+ send(kw,blc)
135
+ end
136
+
137
+ def dockerfile(blc)
138
+ @df = Dockerfile.new
139
+ @df.parse(blc)
140
+ end
141
+
142
+ def mount(blc)
143
+ @mounts = Mount.parse(blc)
144
+ end
145
+
146
+ def interactive(val)
147
+ @interactive_mode = val
148
+ end
149
+
150
+ def tty(val)
151
+ @tty_mode = val
152
+ end
153
+
154
+ def expose(port)
155
+ if not port.nil?
156
+ @exposes = [] if @exposes.nil?
157
+ po = Port.new
158
+ if port.to_s =~ /:/
159
+ ppo = port.to_s.split(":")
160
+ po.host = ppo[0]
161
+ po.docker = ppo[1]
162
+ else
163
+ # only single value is given
164
+ po.docker = port
165
+ po.host = port
166
+ end
167
+ @exposes << po
168
+ end
169
+ end # expose
170
+
171
+ ##
172
+ ## End of DSL
173
+ ##
174
+
175
+ def command
176
+ if @builder.nil?
177
+ @builder = DockerCommandBuilder.new
178
+ end
179
+ @builder
180
+ end
181
+
182
+ def command_executor
183
+ if @exec.nil?
184
+ @exec = DockerCommandExecutor.new(self)
185
+ end
186
+ @exec
187
+ end
188
+
189
+ #
190
+ # Operation
191
+ #
192
+ def gen
193
+ @logger.debug "Generate output files!"
194
+
195
+ te = Hitcher::Docker::TemplateEngine.new
196
+
197
+ if not @dockerfile.nil?
198
+ te.build_template(:dockerfile, { image: @image, dockerfile: @df.cont })
199
+ @logger.debug "Dockerfile generated"
200
+ end
201
+
202
+ te.build_template(:build_container, { image_name: @image })
203
+
204
+ # run script
205
+ mm = @mounts.collect { |m| m.hashify }
206
+ pp = @exposes.collect { |po| po.hashify }
207
+ te.build_template(:run_container, { mounts: mm, ports: pp, container_name: @cName, interactive: true, image: @image })
208
+ @logger.debug "run_container generated"
209
+
210
+ te.build_template(:start_container, { container_name: @cName, drop_to_prompt: true })
211
+
212
+ te.build_template(:container_prompt, { container_name: @cName })
213
+
214
+ end # generate output files
215
+
216
+ end # module Dsl
217
+
218
+ # initialize dynamically by
219
+ # DSL method container()
220
+ class DockerDsl
221
+ include Hitcher::Docker::Dsl
222
+
223
+ def initialize(opts = { logger: Hitcher::Global.instance.glog })
224
+ @logger = opts[:logger] || Hitcher::Global.instance.glog
225
+ end
226
+
227
+ end # class DockerDsl
228
+ end
229
+ end