Hitcher 0.1.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,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