1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
#!/usr/bin/env ruby
require 'optparse'
require 'ostruct'
require 'tempfile'
OPTIONS = OpenStruct.new({
noop: false,
drumkit: ".drumkit",
erase: false
})
class Drumkit
def self.parse_file(filename)
Drumkit.parse(File.read(filename))
end
def self.parse(input)
input = input.lines if input.is_a?(String)
input = input.map(&:strip).reject(&:empty?)
drumkit = Drumkit.new(input)
end
def initialize(lines = [])
@bassdrum = nil
@kickdrum = nil
@snaredrum = nil
@drumstick = nil
lines.each do |line|
case line
when /^BASSDRUM/
@bassdrum = line[9..-1]
when /^KICKDRUM/
@kickdrum = line[9..-1]
when /^SNAREDRUM/
@snaredrum = line[10..-1]
when /^DRUMSTICK/
@drumstick = line[10..-1].split(" ")
end
end
end
attr_accessor :bassdrum, :kickdrum, :snaredrum, :drumstick
def to_s
"This file will be modified automatically. Only make changes if you know what you are doing. Otherwise, use drum.\n\n" +
(@bassdrum ? "BASSDRUM #{@bassdrum}\n" : "") +
(@kickdrum ? "KICKDRUM #{@kickdrum}\n" : "") +
(@snaredrum ? "SNAREDRUM #{@snaredrum}\n" : "") +
(@drumstick ? "DRUMSTICK #{@drumstick.join(" ")}\n" : "")
end
end
def help(parser)
puts parser.banner
puts parser.summarize
end
def info(str)
puts "INFO: #{str}"
end
def error(str, rc = nil)
$stderr.puts "ERROR: #{str}"
exit rc unless rc.nil?
end
def entrypoint(dk)
#0. Is there a stick specified?
return dk.drumstick unless dk.drumstick.nil?
files = Dir.entries(Dir.pwd)
#1. Is it a rails app?
return ["rails", "server"] if File.exists?("Gemfile") && File.read("Gemfile").downcase =~ /gem "rails"/
#2. Is there an executable file with the same name as the working dir?
d = File.basename(Dir.pwd)
r = /#{d}(\.rb)?/
s = files.select{|f|f =~ r}
return ["./#{s[0]}"] if s.length == 1 && File.executable?(s[0])
#3. Is there only one ruby file?
d = /\.rb/
s = files.select{|f|f =~ d}
return ["./#{s[0]}"] if s.length == 1 && File.executable?(s[0])
#4. Is there a file that looks like "entrypoint"
r = /entrypoint/
s = files.select{|f|f =~ r}.select{|f|File.executable?(f)}
return ["./#{s[0]}"] if s.length == 1
#5. Is there only one executable file?
s = files.select{|f|File.executable?(f)}
return ["./#{s[0]}"] if s.length == 1
end
parser = OptionParser.new do |opt|
opt.banner = "DRuM: The Docker Ruby Manager\n\nUsage: drum [flags] COMMAND"
opt.on("-n", "--noop", "--dry-run", "Dry run, don't actually *do* anything") do
OPTIONS.noop = true
end
opt.on("-d", "--drumkit", :REQUIRED, "Specify drumkit file (default: .drumkit)") do |dk|
OPTIONS.drumkit = dk
end
opt.on("--erase", "Erase this drum from the drumkit.") do
OPTIONS.erase = true
end
opt.on("-h", "--help", "Print usage") do
help opt
exit 0
end
end
parser.parse!
COMMAND = ARGV.shift&.downcase
if File.exists?(OPTIONS.drumkit)
DRUMKIT = Drumkit.parse_file(OPTIONS.drumkit)
else
DRUMKIT = Drumkit.new
end
if OPTIONS.erase
case COMMAND
when nil
error("No key given to erase", 1)
when "bass"
DRUMKIT.bassdrum = nil
when "kick"
DRUMKIT.kickdrum = nil
when "snare"
DRUMKIT.snaredrum = nil
when "stick"
DRUMKIT.drumstick = nil
else
error("Invalid key: \"#{COMMAND}\"", 1)
end
File.open(OPTIONS.drumkit, "w").puts(DRUMKIT.to_s)
info("Key \"#{COMMAND}\" erased")
exit 0
end
case COMMAND
when nil
error "No command given\n\n"
help parser
exit 1
when "bass"
if !ARGV[0].nil?
bd = ARGV[0].strip
case bd
when /\A[^:]+\Z/
bassdrum = "ruby:#{bd}"
when /:\Z/
bassdrum = bd[0..-2]
when /\w:\w/
bassdrum = bd
else
error("Invalid Bass Drum specification: #{bd}", 1)
end
else
if DRUMKIT.bassdrum.nil?
bassdrum = "ruby:alpine"
else
bassdrum = DRUMKIT.bassdrum
end
end
info "Fetching Bass Drum: #{bassdrum}" if OPTIONS.noop
system "docker pull #{bassdrum}" unless OPTIONS.noop
DRUMKIT.bassdrum = bassdrum unless OPTIONS.noop
when "kick"
error("No Bass Drum specified.", 1) if DRUMKIT.bassdrum.nil?
if DRUMKIT.kickdrum.nil?
kickdrum = "drum-bass-#{DRUMKIT.bassdrum.gsub(/:/,"_")}-kick-#{File.basename(Dir.pwd)}"
else
kickdrum = DRUMKIT.kickdrum
end
puts "Building Kick Drum: #{kickdrum}" if OPTIONS.noop
Tempfile.open("drum-tmp-dockerfile", Dir.pwd) do |f|
f.puts "FROM #{DRUMKIT.bassdrum}"
f.puts "WORKDIR /opt"
f.puts "COPY Gemfile* ./"
f.puts "RUN [\"bundle\", \"install\"]"
f.flush
system "docker build -t #{kickdrum} -f #{File.basename(f)} ."
system "docker run --rm #{kickdrum} cat Gemfile.lock > Gemfile.lock"
end unless OPTIONS.noop
DRUMKIT.kickdrum = kickdrum unless OPTIONS.noop
when "snare"
error("No Kick Drum specified.", 1) if DRUMKIT.kickdrum.nil?
if DRUMKIT.snaredrum.nil?
snaredrum = "#{DRUMKIT.kickdrum}-snare-#{entrypoint(DRUMKIT).join('_').gsub(/[\/.]/, "_").gsub(/(^[-_.]+|[-_.]+$)/, "")}"
else
snaredrum = DRUMKIT.snaredrum
end
puts "Creating Snare Drum: #{snaredrum}" if OPTIONS.noop
Tempfile.open("drum-tmp-dockerfile", Dir.pwd) do |f|
f.puts "FROM #{DRUMKIT.kickdrum}"
f.puts "WORKDIR /opt"
f.puts "COPY . ."
f.puts "ENTRYPOINT [\"#{entrypoint(DRUMKIT).join("\", \"")}\"]"
f.flush
system "docker build -t #{snaredrum} -f #{File.basename(f)} ."
end unless OPTIONS.noop
DRUMKIT.snaredrum = snaredrum unless OPTIONS.noop
when "roll"
error("No Kick Drum specified.", 1) if DRUMKIT.kickdrum.nil?
system "docker run --rm -v `pwd`:/opt -w /opt #{ARGV.join(" ")} #{DRUMKIT.kickdrum} #{entrypoint(DRUMKIT).join(" ")}"
when "stick"
if ARGV[0].nil?
puts entrypoint(DRUMKIT).join(" ")
else
DRUMKIT.drumstick = ARGV unless OPTIONS.noop
end
end
File.open(OPTIONS.drumkit, "w").puts(DRUMKIT.to_s)
|