dockdev 0.3.8 → 0.4.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.
- checksums.yaml +4 -4
- data/exe/dockdev +1 -0
- data/lib/dockdev/context/docker-compose.rb +13 -7
- data/lib/dockdev/context/rubygems.rb +80 -26
- data/lib/dockdev/context.rb +17 -3
- data/lib/dockdev/dockdev_config.rb +309 -0
- data/lib/dockdev/image.rb +2 -85
- data/lib/dockdev/version.rb +1 -1
- data/lib/dockdev/workspace.rb +18 -2
- data/lib/dockdev.rb +173 -33
- metadata +9 -9
- data/lib/dockdev/user_info.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 190fb93712b6c1bae63281b42de5937866791af1a1d00f1e42b7ebee453436cb
|
4
|
+
data.tar.gz: 1b6bd909c27c7ab06f419c3f91846f036dd86548668ba31f0b201acda311fb3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d63a111dc55feab9e5b562a4e8fc75667b0fbc5bc8163a4fd67ef15ad991fa0dbb7919f8614e363a29b0f3f7b9e6a9afe4d6f14bd9567d2a2d829f63a9d4fc2
|
7
|
+
data.tar.gz: 36de0b82ec04b79fbf94dff6b375f91eaaee5b676aa9e252a7064f77f96b2c5c81568825dd565936538e900c1d717e85bb4debccb360c2fdfc9eec999f1f4693
|
data/exe/dockdev
CHANGED
@@ -88,14 +88,20 @@ module Dockdev
|
|
88
88
|
[@mounts, @ports]
|
89
89
|
end
|
90
90
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
def apply_context(dockdev_config)
|
92
|
+
ddConf = dockdev_config
|
93
|
+
|
94
|
+
if not ddConf.nil?
|
95
|
+
mounts, ports = parse_docker_compose
|
96
|
+
mounts.each do |host, docker|
|
97
|
+
ddConf.add_mount(host, docker)
|
98
|
+
end
|
99
|
+
ports.each do |host, docker|
|
100
|
+
ddConf.add_port(host, docker)
|
101
|
+
end
|
102
|
+
end
|
95
103
|
|
96
|
-
|
97
|
-
_, ports = parse_docker_compose
|
98
|
-
ports
|
104
|
+
ddConf
|
99
105
|
end
|
100
106
|
|
101
107
|
private
|
@@ -4,6 +4,7 @@ require 'bundler'
|
|
4
4
|
module Dockdev
|
5
5
|
module Context
|
6
6
|
class Rubygems
|
7
|
+
include TR::CondUtils
|
7
8
|
|
8
9
|
def self.init_path(path)
|
9
10
|
Rubygems.new(path)
|
@@ -23,15 +24,10 @@ module Dockdev
|
|
23
24
|
Dir.glob(File.join(@path,"Gemfile"))
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
+
def apply_context(dockdev_config)
|
28
|
+
ddConf = dockdev_config
|
27
29
|
|
28
|
-
if
|
29
|
-
|
30
|
-
dir_inside_docker = opts[:dir_inside_docker]
|
31
|
-
|
32
|
-
script = ["#!/bin/bash"]
|
33
|
-
#script << "alias be > /dev/null 2>&1 && echo 'alias be=bundle exec' >> ~/.bashrc"
|
34
|
-
script << "echo 'alias be=\"bundle exec\"' >> ~/.bashrc"
|
30
|
+
if not_empty?(ddConf)
|
35
31
|
|
36
32
|
#
|
37
33
|
# looking at source code
|
@@ -39,33 +35,91 @@ module Dockdev
|
|
39
35
|
# seems this is the way to set root for Bundler
|
40
36
|
#
|
41
37
|
ENV['BUNDLE_GEMFILE'] = find_gemfile.first
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
if not_empty?(ENV['BUNDLE_GEMFILE'])
|
39
|
+
|
40
|
+
cmd = ["echo 'alias be = \"bundle exec\"' >> /etc/bash.bashrc"]
|
41
|
+
|
42
|
+
Bundler.load.dependencies.each do |d|
|
43
|
+
if not d.source.nil?
|
44
|
+
src = d.source
|
45
|
+
if src.path.to_s != "."
|
46
|
+
pathInsideDocker = File.join(dir_inside_docker, d.name)
|
47
|
+
ddConf.add_mount(src.path.expand_path.to_s,pathInsideDocker)
|
48
|
+
# following line assumed 'bundle' program already installed inside the image
|
49
|
+
cmd << "bundle config --global local.#{d.name} #{pathInsideDocker}"
|
50
|
+
end
|
50
51
|
end
|
51
52
|
end
|
52
|
-
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
if not_empty?(cmd)
|
55
|
+
|
56
|
+
script = ["#!/bin/bash"]
|
57
|
+
script << "if ! command -v bundle &> /dev/null"
|
58
|
+
script << " echo \"Command 'bundle' is available!\""
|
59
|
+
script.concat(cmd.collect { |e| " #{e}"})
|
60
|
+
script << "then"
|
61
|
+
script << " echo \"Command 'bundle' not available\""
|
62
|
+
#script << " exit 1"
|
63
|
+
script << "fi"
|
64
|
+
|
65
|
+
File.open("rubygems_init.sh","w") do |f|
|
66
|
+
f.write script.join("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
ddConf.append_Dockerfile("COPY rubygems_init.sh /tmp/rubygems_init.sh")
|
70
|
+
ddConf.append_Dockerfile("RUN chmod +x /tmp/rubygems_init.sh && /tmp/rubygems_init.sh")
|
71
|
+
end
|
72
|
+
|
57
73
|
end
|
58
|
-
`chmod +x #{scriptOut}`
|
59
74
|
|
60
75
|
end
|
61
76
|
|
62
|
-
|
63
|
-
|
77
|
+
ddConf
|
64
78
|
end
|
65
79
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
80
|
+
#def process_mount(opts = { dir_inside_docker: "/opt" })
|
81
|
+
|
82
|
+
# if @mounts.empty?
|
83
|
+
|
84
|
+
# dir_inside_docker = opts[:dir_inside_docker]
|
85
|
+
|
86
|
+
# script = ["#!/bin/bash"]
|
87
|
+
# #script << "alias be > /dev/null 2>&1 && echo 'alias be=bundle exec' >> ~/.bashrc"
|
88
|
+
# script << "echo 'alias be=\"bundle exec\"' >> ~/.bashrc"
|
89
|
+
|
90
|
+
# #
|
91
|
+
# # looking at source code
|
92
|
+
# # https://github.com/rubygems/rubygems/blob/master/bundler/lib/bundler/shared_helpers.rb#L246
|
93
|
+
# # seems this is the way to set root for Bundler
|
94
|
+
# #
|
95
|
+
# ENV['BUNDLE_GEMFILE'] = find_gemfile.first
|
96
|
+
# Bundler.load.dependencies.each do |d|
|
97
|
+
# if not d.source.nil?
|
98
|
+
# src = d.source
|
99
|
+
# if src.path.to_s != "."
|
100
|
+
# pathInsideDocker = File.join(dir_inside_docker, d.name)
|
101
|
+
# @mounts[src.path.expand_path.to_s] = pathInsideDocker
|
102
|
+
# script << "bundle config --global local.#{d.name} #{pathInsideDocker}"
|
103
|
+
# #res[d.name] = src.path.expand_path.to_s
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
|
108
|
+
# scriptOut = File.join(@path,"to-be-executed-once-inside-docker.sh")
|
109
|
+
# File.open(scriptOut,"w") do |f|
|
110
|
+
# f.write script.join("\n")
|
111
|
+
# end
|
112
|
+
# `chmod +x #{scriptOut}`
|
113
|
+
|
114
|
+
# end
|
115
|
+
|
116
|
+
# @mounts
|
117
|
+
|
118
|
+
#end
|
119
|
+
|
120
|
+
#def process_port(opts = {})
|
121
|
+
# @ports
|
122
|
+
#end
|
69
123
|
|
70
124
|
end
|
71
125
|
end
|
data/lib/dockdev/context.rb
CHANGED
@@ -14,12 +14,26 @@ module Dockdev
|
|
14
14
|
@ctx[name] = cls
|
15
15
|
end
|
16
16
|
|
17
|
+
def registered_context
|
18
|
+
@ctx.keys.freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def registered_context_by_name(name, path)
|
22
|
+
ctx = @ctx[id]
|
23
|
+
if not ctx.nil?
|
24
|
+
ctx.init_path(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
ctx
|
28
|
+
end
|
29
|
+
|
17
30
|
def get_context(path)
|
18
|
-
ctx =
|
19
|
-
@ctx.
|
31
|
+
ctx = {}
|
32
|
+
@ctx.each do |k, v|
|
20
33
|
vv = v.init_path(path)
|
21
34
|
if vv.is_context?
|
22
|
-
ctx << vv
|
35
|
+
#ctx << vv
|
36
|
+
ctx[k] = vv
|
23
37
|
end
|
24
38
|
end
|
25
39
|
ctx
|
@@ -0,0 +1,309 @@
|
|
1
|
+
|
2
|
+
require 'toolrack'
|
3
|
+
|
4
|
+
module Dockdev
|
5
|
+
|
6
|
+
|
7
|
+
## Support multiple Dockerfile different configurations
|
8
|
+
## Default config is the default Dockerfile file name
|
9
|
+
#class Configs
|
10
|
+
# include TR::CondUtils
|
11
|
+
|
12
|
+
# def initialize
|
13
|
+
# @configs = { default: Config.new }
|
14
|
+
# end
|
15
|
+
|
16
|
+
# def config_names
|
17
|
+
# @configs.keys.freeze
|
18
|
+
# end
|
19
|
+
|
20
|
+
# def default_config_name
|
21
|
+
# :default
|
22
|
+
# end
|
23
|
+
|
24
|
+
# def default_config
|
25
|
+
# @configs[:default]
|
26
|
+
# end
|
27
|
+
|
28
|
+
# def config(name)
|
29
|
+
# indx = File.basename(name)
|
30
|
+
# indx = :default if indx == "Dockerfile"
|
31
|
+
|
32
|
+
# conf = @configs[indx]
|
33
|
+
# if conf.nil?
|
34
|
+
# conf = Config.new
|
35
|
+
# @configs[indx] = conf
|
36
|
+
# end
|
37
|
+
# conf
|
38
|
+
# end
|
39
|
+
|
40
|
+
# def is_config_exist?(name)
|
41
|
+
# not_empty?(config(name))
|
42
|
+
# end
|
43
|
+
|
44
|
+
# private
|
45
|
+
# def method_missing(mtd, *args, &block)
|
46
|
+
# default_config.send(mtd, *args, &block)
|
47
|
+
# end
|
48
|
+
#end # end class Configs
|
49
|
+
|
50
|
+
# Content of the config file
|
51
|
+
# which shall configure the to be run docker instance
|
52
|
+
# Hence mostly the accessors/readers are related to docker configuration items
|
53
|
+
class Config
|
54
|
+
include TR::CondUtils
|
55
|
+
|
56
|
+
# for image
|
57
|
+
attr_accessor :workdir
|
58
|
+
|
59
|
+
# for container
|
60
|
+
attr_reader :mounts, :ports
|
61
|
+
|
62
|
+
# for image
|
63
|
+
attr_reader :dockerfile_entries
|
64
|
+
|
65
|
+
# since context activation is automated:
|
66
|
+
# 1. skip_context shall skip all found automated activated context
|
67
|
+
# 2. activate_context shall add those context which is not found by automated discovery
|
68
|
+
attr_reader :skip_context, :activate_context
|
69
|
+
def initialize(val = {})
|
70
|
+
@mounts = val[:mounts] || {}
|
71
|
+
@ports = val[:ports] || {}
|
72
|
+
@dockerfile_entries = val[:dockerfile_entries] || []
|
73
|
+
@workdir = val[:workdir] || "/opt"
|
74
|
+
@skip_context = val[:skip_context] || []
|
75
|
+
@activate_context = val[:activate_context] || []
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Convert internal value into hash to be written to file to get rid of the object
|
80
|
+
# encoding in the yaml file
|
81
|
+
def to_storage
|
82
|
+
{ mounts: @mounts, ports: @ports, dockerfile_entries: @dockerfile_entries, workdir: @workdir, skip_context: @skip_context, activate_context: @activate_context }
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Add mount mapping of host => docker follows docker-cli which follows
|
87
|
+
# docker cli -v format
|
88
|
+
#
|
89
|
+
# @param on_host [String] path on host
|
90
|
+
# @param on_docker [String] path on docker
|
91
|
+
# @param opts [Hash] options for the mount spec definition. Value keys including:
|
92
|
+
# @option opts [Symbol] :duplicated_entry_policy :error (default) raise error if the on_host is duplicated/already defined
|
93
|
+
# @option opts [Symbol] :duplicated_entry_policy :warn_replace raise warning if the on_host is duplicated/already defined and new entry shall replace the old entry
|
94
|
+
# @option opts [Symbol] :duplicated_entry_policy :warn_discard raise warning if the on_host is duplicated/already defined and new entry is discarded
|
95
|
+
def add_mount(on_host, on_docker, opts = { duplicated_entry_policy: :error })
|
96
|
+
if not_empty?(on_host) and not_empty?(on_docker)
|
97
|
+
if @mounts.keys.include?(on_host)
|
98
|
+
policy = opts[:duplicated_entry_policy] || :error
|
99
|
+
case policy
|
100
|
+
when :warn_replace
|
101
|
+
logger.warn "on_host '#{on_host}' was mapped to '#{@mounts[on_host]}'. It shall be replaced with '#{on_docker}'"
|
102
|
+
@mounts[on_host] = on_docker
|
103
|
+
|
104
|
+
when :warn_discard
|
105
|
+
logger.warn "on_host '#{on_host}' already mapped to '#{@mounts[on_host]}'. New value on_docker is ignored."
|
106
|
+
|
107
|
+
else
|
108
|
+
# default policy always raise error
|
109
|
+
raise Error, "on_host '#{on_host}' already mapped to '#{@mounts[on_host]}'"
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@mounts[on_host] = on_docker
|
113
|
+
end
|
114
|
+
else
|
115
|
+
logger.debug "add_mount unsuccessful = on_host : #{on_host} / on_docker : #{on_docker}"
|
116
|
+
raise Error, "on_host mount entry cannot be empty" if is_empty?(on_host)
|
117
|
+
raise Error, "on_docker mount entry cannot be empty" if is_empty?(on_docker)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Add port mapping of host => docker follows docker-cli which follows
|
123
|
+
# docker cli -p format
|
124
|
+
#
|
125
|
+
# @param on_host [String] port on host
|
126
|
+
# @param on_docker [String] port on docker
|
127
|
+
# @param opts [Hash] options for the port spec definition. Value keys including:
|
128
|
+
# @option opts [Symbol] :duplicated_entry_policy :error (default) raise error if the on_host is duplicated/already defined
|
129
|
+
# @option opts [Symbol] :duplicated_entry_policy :warn_replace raise warning if the on_host is duplicated/already defined and new entry shall replace the old entry
|
130
|
+
# @option opts [Symbol] :duplicated_entry_policy :warn_discard raise warning if the on_host is duplicated/already defined and new entry is discarded
|
131
|
+
def add_port(on_host, on_docker, opts = { duplicated_entry_policy: :error })
|
132
|
+
if not_empty?(on_host) and not_empty?(on_docker)
|
133
|
+
if @ports.keys.include?(on_host)
|
134
|
+
policy = opts[:duplicated_entry_policy] || :error
|
135
|
+
case policy
|
136
|
+
when :warn_replace
|
137
|
+
logger.warn "on_host '#{on_host}' was mapped to '#{@mounts[on_host]}'. It shall be replaced with '#{on_docker}'"
|
138
|
+
@ports[on_host] = on_docker
|
139
|
+
|
140
|
+
when :warn_discard
|
141
|
+
logger.warn "on_host '#{on_host}' already mapped to '#{@mounts[on_host]}'. New value on_docker is ignored."
|
142
|
+
|
143
|
+
else
|
144
|
+
# default policy always raise error
|
145
|
+
raise Error, "on_host '#{on_host}' already mapped to '#{@mounts[on_host]}'"
|
146
|
+
end
|
147
|
+
else
|
148
|
+
@ports[on_host] = on_docker
|
149
|
+
end
|
150
|
+
else
|
151
|
+
logger.debug "add_port unsuccessful = on_host : #{on_host} / on_docker : #{on_docker}"
|
152
|
+
raise Error, "on_host port entry cannot be empty" if is_empty?(on_host)
|
153
|
+
raise Error, "on_docker port entry cannot be empty" if is_empty?(on_docker)
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
# Any instruction to be appended into Dockerfile.
|
159
|
+
# Note this did not presume the entry is RUN, COPY or anyting.
|
160
|
+
# A full valid Dockerfile entry has to be provided here
|
161
|
+
#
|
162
|
+
# @param st [String] Full valid Dockerfile line to be embed into Dockerfile
|
163
|
+
def append_Dockerfile(st)
|
164
|
+
logger.debug "Appending : #{st}"
|
165
|
+
@dockerfile_entries << st
|
166
|
+
end
|
167
|
+
|
168
|
+
def is_context_should_skip?(name)
|
169
|
+
@skip_context.include?(name)
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def manual_activated_context
|
174
|
+
@activate_context.freeze
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
private
|
179
|
+
def logger
|
180
|
+
Dockdev.logger(:config)
|
181
|
+
end
|
182
|
+
|
183
|
+
def has_additional_entries?
|
184
|
+
not @dockerfile_entries.empty?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Managing the config
|
189
|
+
class DockdevConfig
|
190
|
+
include TR::CondUtils
|
191
|
+
|
192
|
+
DOCDEV_CONFIG_FILE = "dockdev-config"
|
193
|
+
|
194
|
+
def self.load(root = Dir.getwd, &block)
|
195
|
+
confFile = Dir.glob(File.join(root,"#{DOCDEV_CONFIG_FILE}.*")).grep(/.yml|.yaml$/)
|
196
|
+
if confFile.length > 1
|
197
|
+
block.call(:found_more, contFile)
|
198
|
+
elsif confFile.length == 0
|
199
|
+
block.call(:not_found)
|
200
|
+
else
|
201
|
+
block.call(:found, confFile.first)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def initialize(conf = nil)
|
206
|
+
logger.debug "Given to initialize : #{conf}"
|
207
|
+
if not_empty?(conf)
|
208
|
+
@config = parse(conf)
|
209
|
+
else
|
210
|
+
@config = Config.new
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def save(root = Dir.getwd)
|
216
|
+
path = File.join(root,"#{DOCDEV_CONFIG_FILE}.yml")
|
217
|
+
File.open(path,"w") do |f|
|
218
|
+
f.write YAML.dump(@config.to_storage)
|
219
|
+
end
|
220
|
+
path
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Build the docker image by embedding additional entries into the Dockerfile.
|
225
|
+
# This likely will need a temporary file to be created and it should be managed by
|
226
|
+
# this operation.
|
227
|
+
#
|
228
|
+
# @param [Dockdev::Image] image instance
|
229
|
+
# @param [String] path to selected Dockerfile
|
230
|
+
def build_image(image, dockerfile_path, opts = { root: Dir.getwd }, &block)
|
231
|
+
|
232
|
+
if has_additional_entries?
|
233
|
+
|
234
|
+
logger.debug "dockdev_config has additional entry for Dockerfile. Merging additional entries into Dockerfile"
|
235
|
+
|
236
|
+
root = opts[:root] || Dir.getwd
|
237
|
+
# make it static name so:
|
238
|
+
# 1. No file removal is required
|
239
|
+
# 2. Allow debug on generated Dockerfile
|
240
|
+
# 3. Replace Dockerfile on each run and only single Dockerfile is left on system
|
241
|
+
# 4. Allow this temporary to be added to .gitignore
|
242
|
+
tmpFile = File.join(root, "#{File.basename(dockerfile_path)}-dockdev")
|
243
|
+
File.open(tmpFile,"w") do |f|
|
244
|
+
found = false
|
245
|
+
File.open(dockerfile_path,"r").each_line do |line|
|
246
|
+
# detecting the CMD line if there is any
|
247
|
+
if line =~ /^CMD/
|
248
|
+
found = true
|
249
|
+
# here we append the lines
|
250
|
+
dockerfile_entries.each do |al|
|
251
|
+
f.puts al
|
252
|
+
end
|
253
|
+
f.write line
|
254
|
+
|
255
|
+
else
|
256
|
+
f.write line
|
257
|
+
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
if not found
|
262
|
+
@dockerfile_entries.each do |al|
|
263
|
+
f.puts al
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
image.build(tmpFile)
|
270
|
+
|
271
|
+
else
|
272
|
+
logger.debug "dockdev_config has no additional entry for Dockerfile. Proceed to build found Dockerfile"
|
273
|
+
image.build(dockerfile_path)
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
private
|
280
|
+
def parse(conf)
|
281
|
+
logger.debug "Given to parse : #{conf}"
|
282
|
+
cont = File.read(conf)
|
283
|
+
val = YAML.unsafe_load(cont)
|
284
|
+
Config.new(val)
|
285
|
+
end
|
286
|
+
|
287
|
+
def method_missing(mtd, *args, &block)
|
288
|
+
logger.debug "method_missing '#{mtd}' / #{args}"
|
289
|
+
@config.send(mtd, *args, &block)
|
290
|
+
end
|
291
|
+
|
292
|
+
def logger
|
293
|
+
Dockdev.logger(:dockdev_config)
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
if $0 == __FILE__
|
301
|
+
|
302
|
+
require_relative '../dockdev'
|
303
|
+
|
304
|
+
c = Dockdev::DockdevConfig.new
|
305
|
+
c.add_mount("/Users/chris/01.Workspaces/02.Code-Factory/08-Workspace/docker-cli","/opt/docker-cli")
|
306
|
+
c.save
|
307
|
+
end
|
308
|
+
|
309
|
+
|
data/lib/dockdev/image.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
|
2
2
|
require 'docker/cli'
|
3
3
|
|
4
|
-
require 'securerandom'
|
5
|
-
|
6
|
-
require_relative 'user_info'
|
7
|
-
|
8
4
|
module Dockdev
|
9
5
|
class Image
|
10
6
|
include TR::CondUtils
|
@@ -24,39 +20,22 @@ module Dockdev
|
|
24
20
|
end
|
25
21
|
|
26
22
|
def new_container(cont_name, opts = {})
|
27
|
-
|
28
23
|
optss = {
|
29
24
|
interactive: true,
|
30
25
|
tty: true,
|
31
|
-
container_name: cont_name
|
32
|
-
match_user: TR::RTUtils.on_linux?
|
26
|
+
container_name: cont_name
|
33
27
|
}
|
34
28
|
optss.merge!(opts)
|
35
|
-
|
36
29
|
@cmd_fact.create_container_from_image(@image_name, optss).run
|
37
|
-
|
38
30
|
end
|
39
31
|
|
40
32
|
def build(dockerfile, opts = {})
|
41
|
-
|
42
|
-
dockerfilePath = dockerfile
|
43
|
-
if is_transfer_user?(opts)
|
44
|
-
cont = append_transfer_user_in_dockerfile(dockerfile)
|
45
|
-
dockerfilePath = generated_dockerfile
|
46
|
-
File.open(dockerfilePath, "w") do |f|
|
47
|
-
f.write cont
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
33
|
optss = {
|
52
34
|
context_root: opts[:root],
|
53
|
-
dockerfile:
|
35
|
+
dockerfile: dockerfile
|
54
36
|
}
|
55
37
|
optss.merge!(opts)
|
56
38
|
@cmd_fact.build_image(@image_name, optss).run
|
57
|
-
|
58
|
-
FileUtils.rm(generated_dockerfile) if File.exist?(generated_dockerfile) and not is_keep_generated_dockerfile?
|
59
|
-
|
60
39
|
end
|
61
40
|
|
62
41
|
def destroy
|
@@ -68,67 +47,5 @@ module Dockdev
|
|
68
47
|
end
|
69
48
|
end
|
70
49
|
|
71
|
-
|
72
|
-
private
|
73
|
-
def logger
|
74
|
-
Dockdev.logger(:dockdev_image)
|
75
|
-
end
|
76
|
-
|
77
|
-
def is_keep_generated_dockerfile?
|
78
|
-
v = ENV["DOCKDEV_KEEP_GENERATED_DOCKERFILE"]
|
79
|
-
is_empty?(v) ? false : (v.downcase == "true") ? true : false
|
80
|
-
end
|
81
|
-
|
82
|
-
def generated_dockerfile
|
83
|
-
"Dockerfile-dockdev"
|
84
|
-
end
|
85
|
-
|
86
|
-
def is_transfer_user?(opts = {})
|
87
|
-
if TR::RTUtils.on_linux?
|
88
|
-
true
|
89
|
-
else
|
90
|
-
(opts[:match_user].nil? || not_bool?(opts[:match_user])) ? false : opts[:match_user]
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def append_transfer_user_in_dockerfile(file)
|
95
|
-
if File.exist?(file)
|
96
|
-
logger.debug "Append transfer user in dockerfile '#{file}'"
|
97
|
-
res = []
|
98
|
-
cont = File.read(file)
|
99
|
-
indx = cont =~ /CMD/
|
100
|
-
if indx != nil
|
101
|
-
|
102
|
-
res << cont[0...indx]
|
103
|
-
res << transfer_user_command
|
104
|
-
res << cont[indx..-1]
|
105
|
-
|
106
|
-
else
|
107
|
-
|
108
|
-
res << cont
|
109
|
-
res << transfer_user_command
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
res.join
|
114
|
-
|
115
|
-
else
|
116
|
-
""
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def transfer_user_command
|
121
|
-
|
122
|
-
ui = UserInfo.user_info
|
123
|
-
gi = UserInfo.group_info
|
124
|
-
|
125
|
-
res = []
|
126
|
-
res << "RUN apt-get update && apt-get install -y sudo"
|
127
|
-
res << "RUN groupadd -f -g #{gi[:gid]} #{gi[:group_name]} && useradd -u #{ui[:uid]} -g #{gi[:gid]} -m #{ui[:login]} && usermod -aG sudo #{ui[:login]} && echo '#{ui[:login]} ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
|
128
|
-
res << "USER #{ui[:login]}"
|
129
|
-
res.join("\n")
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
50
|
end
|
134
51
|
end
|
data/lib/dockdev/version.rb
CHANGED
data/lib/dockdev/workspace.rb
CHANGED
@@ -12,11 +12,27 @@ module Dockdev
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def has_dockerfile?
|
15
|
-
|
15
|
+
found_dockerfile_count > 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def found_dockerfile_count
|
19
|
+
dockerfiles.length
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_multiple_dockerfiles?
|
23
|
+
found_dockerfile_count > 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def dockerfiles
|
27
|
+
Dir.glob(File.join(@root,"Dockerfile*"))
|
16
28
|
end
|
17
29
|
|
18
30
|
def dockerfile
|
19
|
-
|
31
|
+
if has_dockerfile?
|
32
|
+
dockerfiles.first
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
20
36
|
end
|
21
37
|
|
22
38
|
def has_docker_compose?
|
data/lib/dockdev.rb
CHANGED
@@ -5,82 +5,195 @@ require 'toolrack'
|
|
5
5
|
require 'docker/cli'
|
6
6
|
require 'colorize'
|
7
7
|
|
8
|
+
require 'tty/prompt'
|
9
|
+
|
8
10
|
require_relative "dockdev/version"
|
9
11
|
|
10
12
|
require_relative 'dockdev/workspace'
|
11
13
|
require_relative 'dockdev/image'
|
12
14
|
require_relative 'dockdev/container'
|
13
15
|
|
16
|
+
require_relative 'dockdev/dockdev_config'
|
17
|
+
|
14
18
|
module Dockdev
|
15
19
|
include TR::CondUtils
|
16
20
|
|
17
21
|
class Error < StandardError; end
|
18
22
|
# Your code goes here...
|
19
23
|
|
24
|
+
# main entry points to start docker
|
20
25
|
def self.with_running_container(contName, opts = {})
|
21
26
|
|
27
|
+
pmt = TTY::Prompt.new
|
22
28
|
root = opts[:root]
|
23
29
|
cmd = opts[:command]
|
24
30
|
|
25
|
-
|
26
|
-
logger.debug("Found context : #{ctx}")
|
31
|
+
ddConf = load_config(root)
|
27
32
|
|
28
33
|
cont = Container.new(contName)
|
29
34
|
if cont.has_container?
|
35
|
+
|
36
|
+
logger.debug "Container '#{contName}' already exist. Just run the container"
|
30
37
|
if cont.running?
|
31
38
|
cont.attach_and_exec(command: cmd)
|
32
39
|
else
|
33
40
|
cont.start_with_command(command: cmd)
|
34
41
|
end
|
42
|
+
|
35
43
|
else
|
44
|
+
|
45
|
+
logger.debug "Container '#{contName}' does not exist. Creating the container"
|
46
|
+
|
36
47
|
img = Image.new(contName)
|
37
48
|
ws = opts[:workspace] || root
|
38
49
|
wss = Workspace.new(ws)
|
39
|
-
if img.has_image?
|
40
|
-
mount = { root => File.join("/opt",File.basename(root)) }
|
41
|
-
port = {}
|
42
|
-
ctx.each do |cctx|
|
43
|
-
mnts = cctx.process_mount(dir_inside_docker: "/opt")
|
44
|
-
logger.debug "Mount points by context : #{mnts}"
|
45
50
|
|
46
|
-
|
51
|
+
# root directory is mounted by default
|
52
|
+
ddConf.add_mount(root, File.join(ddConf.workdir,File.basename(root)))
|
53
|
+
|
54
|
+
ctx = Dockdev::Context::ContextManager.instance.get_context(root)
|
55
|
+
logger.debug("Found context : #{ctx}")
|
47
56
|
|
48
|
-
|
49
|
-
port.merge!(prt) if not prt.empty?
|
57
|
+
ctx.each do |name, cctx|
|
50
58
|
|
51
|
-
|
59
|
+
if ddConf.is_context_should_skip?(name)
|
60
|
+
logger.debug "Context '#{name}' is asked to be skipped"
|
61
|
+
next
|
52
62
|
end
|
53
63
|
|
54
|
-
|
55
|
-
|
64
|
+
logger.debug "Appying context '#{name}' "
|
65
|
+
# here allow context to add additional Dockerfile entries, mounts and ports
|
66
|
+
ddConf = cctx.apply_context(ddConf)
|
67
|
+
end
|
56
68
|
|
57
|
-
|
69
|
+
ddConf.manual_activated_context.each do |cctx|
|
70
|
+
mctx = Dockdev::Context::ContextManager.instance.registered_context_by_name(cctx)
|
71
|
+
logger.debug "Executing manual activated context : #{mctx}"
|
72
|
+
ddConf = mctx.apply_context(ddConf) if not mctx.nil?
|
73
|
+
end
|
58
74
|
|
59
|
-
|
60
|
-
img.build(wss.dockerfile)
|
61
|
-
|
62
|
-
mount = { root => File.join("/opt",File.basename(root)) }
|
63
|
-
port = {}
|
64
|
-
ctx.each do |cctx|
|
65
|
-
mnt = cctx.process_mount(dir_inside_docker: "/opt")
|
66
|
-
mount.merge!(mnt) if not mnt.empty?
|
75
|
+
if not img.has_image?
|
67
76
|
|
68
|
-
|
77
|
+
if wss.has_dockerfile?
|
69
78
|
|
70
|
-
|
71
|
-
port.merge!(prt) if not prt.empty?
|
79
|
+
if wss.has_multiple_dockerfiles?
|
72
80
|
|
73
|
-
|
74
|
-
|
81
|
+
selDockerFile = pmt.select("Please select one of the Dockerfile to proceed : ") do |m|
|
82
|
+
wss.dockerfiles.each do |df|
|
83
|
+
m.choice File.basename(df), df
|
84
|
+
end
|
85
|
+
end
|
75
86
|
|
76
|
-
|
77
|
-
|
87
|
+
else
|
88
|
+
selDockerFile = wss.dockerfile
|
89
|
+
end
|
78
90
|
|
79
|
-
|
91
|
+
# Delegated to config file to allow
|
92
|
+
# config file to embed addtional entries (if there is any) and proceed to build it.
|
93
|
+
# During the process it is very likey a temporary Dockerfile shall be created since
|
94
|
+
# docker cli works on file basis and this temporary file need to be managed after the process.
|
95
|
+
# Hence this makes more sense to let the config handle all those inside the operation
|
96
|
+
res = ddConf.build_image(img, selDockerFile, root: root)
|
97
|
+
raise Error, "Image failed to be built. Error was : #{res.err_stream}" if res.failed?
|
98
|
+
STDOUT.puts "\n Image '#{contName}' built successfully\n\n".green
|
99
|
+
#img.build(wss.dockerfile)
|
80
100
|
|
81
|
-
|
82
|
-
|
101
|
+
else
|
102
|
+
raise Error, "\n No image and no Dockerfile found to build the image. Operation aborted. \n\n".red
|
103
|
+
|
104
|
+
end
|
83
105
|
end
|
106
|
+
|
107
|
+
# image already exist!
|
108
|
+
# Since reach here means container doesn't exist yet.
|
109
|
+
# Proceed to create container
|
110
|
+
|
111
|
+
param = { command: cmd, mounts: ddConf.mounts, ports: ddConf.ports }
|
112
|
+
img.new_container(cont.name, param)
|
113
|
+
|
114
|
+
#if img.has_image?
|
115
|
+
#
|
116
|
+
# # has image but no container
|
117
|
+
# ctx.each do |cctx|
|
118
|
+
#
|
119
|
+
# ddConf = cctx.apply_context(ddConf)
|
120
|
+
|
121
|
+
# #cctx.process_mount(dir_inside_docker: ddConf.workdir).each do |host,docker|
|
122
|
+
# # logger.debug "Mount points by context '#{cctx}' : #{host} => #{docker}"
|
123
|
+
# # ddConf.add_mount(host, docker)
|
124
|
+
# #end
|
125
|
+
|
126
|
+
# #cctx.process_port.each do |host, docker|
|
127
|
+
# # logger.debug "Ports mapping by context '#{cctx}' : #{host} => #{docker}"
|
128
|
+
# # ddConf.add_port(host, docker)
|
129
|
+
# #end
|
130
|
+
|
131
|
+
# #mnts = cctx.process_mount(dir_inside_docker: ddConf.workdir)
|
132
|
+
# #logger.debug "Mount points by context : #{mnts}"
|
133
|
+
|
134
|
+
# #mount.merge!(mnts) if not mnts.empty?
|
135
|
+
|
136
|
+
# #prt = cctx.process_port
|
137
|
+
# #port.merge!(prt) if not prt.empty?
|
138
|
+
|
139
|
+
# #logger.debug "Ports by context #{cctx} : #{prt}"
|
140
|
+
# end
|
141
|
+
|
142
|
+
# #param = { command: cmd, mounts: mount }
|
143
|
+
# #param[:ports] = port if not port.empty?
|
144
|
+
# param = { command: cmd, mounts: ddConf.mounts, ports: ddConf.ports }
|
145
|
+
|
146
|
+
# img.new_container(cont.name, param)
|
147
|
+
|
148
|
+
#elsif wss.has_dockerfile?
|
149
|
+
|
150
|
+
# logger.debug "Dockerfile '#{wss.dockerfile}' found. Proceed building the image."
|
151
|
+
|
152
|
+
# # Delegated to config file to allow
|
153
|
+
# # config file to embed addtional entries (if there is any) and proceed to build it.
|
154
|
+
# # During the process it is very likey a temporary Dockerfile shall be created since
|
155
|
+
# # docker cli works on file basis and this temporary file need to be managed after the process.
|
156
|
+
# # Hence this makes more sense to let the config handle all those inside the operation
|
157
|
+
# ddConf.build_image(img, wss, root: root)
|
158
|
+
# #img.build(wss.dockerfile)
|
159
|
+
#
|
160
|
+
# ddConf.add_mount(root, File.join(ddConf.workdir,File.basename(root)))
|
161
|
+
# #mount = { root => File.join(ddConf.workdir,File.basename(root)) }
|
162
|
+
# #port = {}
|
163
|
+
# ctx.each do |cctx|
|
164
|
+
|
165
|
+
# ddConf = cctx.apply_context(ddConf)
|
166
|
+
|
167
|
+
# #cctx.process_mount(dir_inside_docker: ddConf.workdir).each do |host,docker|
|
168
|
+
# # logger.debug "Mount points by context '#{cctx}' : #{host} => #{docker}"
|
169
|
+
# # ddConf.add_mount(host, docker)
|
170
|
+
# #end
|
171
|
+
|
172
|
+
# #cctx.process_port.each do |host, docker|
|
173
|
+
# # logger.debug "Ports mapping by context '#{cctx}' : #{host} => #{docker}"
|
174
|
+
# # ddConf.add_port(host, docker)
|
175
|
+
# #end
|
176
|
+
|
177
|
+
# #mnt = cctx.process_mount(dir_inside_docker: ddConf.workdir)
|
178
|
+
# #mount.merge!(mnt) if not mnt.empty?
|
179
|
+
|
180
|
+
# #logger.debug "Mount points by context #{cctx} : #{mnt}"
|
181
|
+
|
182
|
+
# #prt = cctx.process_port
|
183
|
+
# #port.merge!(prt) if not prt.empty?
|
184
|
+
|
185
|
+
# #logger.debug "Ports by context #{cctx} : #{prt}"
|
186
|
+
# end
|
187
|
+
|
188
|
+
# #param = { command: cmd, mounts: mount }
|
189
|
+
# #param[:ports] = port if not port.empty?
|
190
|
+
# param = { command: cmd, mounts: ddConf.mounts, ports: ddConf.ports }
|
191
|
+
|
192
|
+
# img.new_container(cont.name, param)
|
193
|
+
|
194
|
+
#else
|
195
|
+
# raise Error, "\n No image and no Dockerfile found to build the image found. Operation aborted. \n\n".red
|
196
|
+
#end
|
84
197
|
end
|
85
198
|
end
|
86
199
|
|
@@ -99,6 +212,33 @@ module Dockdev
|
|
99
212
|
|
100
213
|
end
|
101
214
|
|
215
|
+
# detect if the additional configuration file exist
|
216
|
+
def self.load_config(root)
|
217
|
+
|
218
|
+
ddConf = DockdevConfig.new
|
219
|
+
DockdevConfig.load(root) do |ops, *args|
|
220
|
+
case ops
|
221
|
+
when :found_more
|
222
|
+
found = args.first
|
223
|
+
pmt = TTY::Prompt.new
|
224
|
+
selConf = pmt.select("There are more config files found. Please select one of the files below :") do |m|
|
225
|
+
found.each do |f|
|
226
|
+
m.choice Pathname.new(f).relative_path_from(root),f
|
227
|
+
end
|
228
|
+
end
|
229
|
+
ddConf = DockdevConfig.new(selConf)
|
230
|
+
when :found
|
231
|
+
ddConf = DockdevConfig.new(args.first)
|
232
|
+
else
|
233
|
+
logger.debug "Load config got ops : #{ops}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
logger.debug "Loaded config : #{ddConf}"
|
238
|
+
ddConf
|
239
|
+
|
240
|
+
end
|
241
|
+
|
102
242
|
def self.logger(tag = nil, &block)
|
103
243
|
if @_logger.nil?
|
104
244
|
@_logger = TeLogger::Tlogger.new(STDOUT)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dockdev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: teLogger
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.5.
|
47
|
+
version: 0.5.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.5.
|
54
|
+
version: 0.5.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: tty-prompt
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,15 +128,15 @@ files:
|
|
128
128
|
- lib/dockdev/context.rb
|
129
129
|
- lib/dockdev/context/docker-compose.rb
|
130
130
|
- lib/dockdev/context/rubygems.rb
|
131
|
+
- lib/dockdev/dockdev_config.rb
|
131
132
|
- lib/dockdev/image.rb
|
132
|
-
- lib/dockdev/user_info.rb
|
133
133
|
- lib/dockdev/version.rb
|
134
134
|
- lib/dockdev/workspace.rb
|
135
135
|
- sig/dockdev.rbs
|
136
136
|
homepage: ''
|
137
137
|
licenses: []
|
138
138
|
metadata: {}
|
139
|
-
post_install_message:
|
139
|
+
post_install_message:
|
140
140
|
rdoc_options: []
|
141
141
|
require_paths:
|
142
142
|
- lib
|
@@ -151,8 +151,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
requirements: []
|
154
|
-
rubygems_version: 3.5.
|
155
|
-
signing_key:
|
154
|
+
rubygems_version: 3.5.1
|
155
|
+
signing_key:
|
156
156
|
specification_version: 4
|
157
157
|
summary: ''
|
158
158
|
test_files: []
|
data/lib/dockdev/user_info.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'etc'
|
3
|
-
|
4
|
-
module Dockdev
|
5
|
-
module UserInfo
|
6
|
-
include TR::CondUtils
|
7
|
-
|
8
|
-
def self.user_info(login = nil)
|
9
|
-
login = Etc.getlogin if is_empty?(login)
|
10
|
-
res = { login: login }
|
11
|
-
begin
|
12
|
-
res[:uid] = Etc.getpwnam(login).uid
|
13
|
-
rescue Exception => ex
|
14
|
-
res[:uid] = nil
|
15
|
-
end
|
16
|
-
res
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.group_info(login = nil)
|
20
|
-
login = Etc.getlogin if is_empty?(login)
|
21
|
-
res = { }
|
22
|
-
begin
|
23
|
-
gnm = Etc.getgrnam(login)
|
24
|
-
res[:group_name] = gnm.name
|
25
|
-
res[:gid] = gnm.gid
|
26
|
-
rescue Exception => ex
|
27
|
-
p ex
|
28
|
-
res[:group_name] = ""
|
29
|
-
res[:gid] = nil
|
30
|
-
end
|
31
|
-
res
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|