#!/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)