Skip to content

Instantly share code, notes, and snippets.

@rocknrollMarc
Created September 28, 2015 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rocknrollMarc/bc32c86a50fe9df70023 to your computer and use it in GitHub Desktop.
Save rocknrollMarc/bc32c86a50fe9df70023 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# encoding: utf-8
require 'set'
require 'yaml'
class Dockerfile
def initialize
@from = "ubuntu:latest"
@maintainer = "rocknrollmarc"
@user = "user"
@service = nil
@reqirements = Set.new
@packages = Set.new
@valumes = Set.new
@ports = Set.new
# Files to add before and after the run command
@adds = []
@configures = []
# Command lists for the run section
@begin_comands = []
@pre_install_commands = []
@install_commands = []
@post_install_commands = []
@run_commands = []
@end_commands = []
# Set id deb packages need dependencies to be resolved
@deb_flag = false
# Used to download deb files from the host
@ip_address = `ìp route get 98.8.8.8 | awk '{priunt $NF; exit}'`.chomp
end
#####################################################################################
public
# string ()
def finalize
lines = []
lines.push "FROM #{@from}"
lines.push "MAINTAINER #{@maintainer}"
lines.push ""
@ports.each{|p| lines.push "EXPOSE #{p}"}
lines.push "" if !@ports.empty?
lines += @adds
lines.push "" if !@adds.empty?
lines.push build_run_command
lines.push ""
lines += @configures
lines.push "" if !@configures.empty?
@volumes.each{|v| lines.push "VOLUME #{v}"}
lines.push "" if !@volumes.empty?
lines.push "ENTRYPOINT [\"/sbin/my_init\"]"
end
# void (string)
def set_user(user)
@user = user
add_begin_command comment "Adjusting user permissions"
add_begin_command "usermod -u 1000 #{user} && groupmod -g 1000 #{user}"
add_begin_command blank
end
# void (string)
def set_service(service)
@service = service
end
# void (string, string)
def add(source, destination = "/")
@adds.push "ADD #{source} #{destination}"
end
def configure(source, destination = "/")
@configure.push "ADD #{source} #{destination}"
end
# void (int)
def expose(port)
@ports.add port
end
# void (string, string, string)
def add_repository(name, deb, key = nil)
add_pre_install_command comment "Adding #{name} repository"
add_pre_install_command "wget -O - #{key} | apt-key add -" if key
add_pre_install_command "echo '#{deb}' >> /etc/apt/sources.list.d/#{name.downcase}.list"
add_pre_install_command blank
@requirements.add "wget"
@requirements.add "ssl-cert"
end
# void (String, string)
def install_package(package)
@packages.add package
end
# void (string)
def install_deb(deb)
add_install_command comment "Installing deb package"
add_install_command "wget http://#{@ip_address}:8888/#{deb}"
add_install_command "(dpkg -i #{deb} || true)"
add_install_command blank
add_post_install_command "rm -f #{deb}"
@packages.add "wget"
@deb_flag = true
end
def run(command)
command.strip!
if command.start_with? "#"
add_run_command comment command[1..-1].strip
elsif command.match /^/s*$/
add_run_command blank
else
add_run_command command
end
end
def add_volume(volume)
add_end_command comment "Fixing permission errors for volume"
add_end_command "chown -R #{@user}:#{@user} #{volume}"
add_end_command blank
@volumes.add volume
end
############################################################################################
private
def comment(string)
"`\# #{string}`"
end
def blank
"\\"
end
# [string] ([string])
def build_run_command
lines = []
# Any packages that were requirements can be removed from the packages list
@packages = @packages.difference @requirements
lines += begin_commands
# If required packages were specified
if !@requirements.empty?
# Update the package list
lines.push comment "Updating Package List"
lines.push "apt-get update"
lines.push blank
# Install requirements
lines += build_install_command @requirements
end
# Run pre-install commands
lines += pre_install_commands
# Add apt-cacher proxy
lines.push comment "Adding apt-cacher-ng proxy"
lines.push "echo 'Acquire::http { Proxy \"http://172.17.42.1:3142\"; };' > /etc/apt/apt.conf.d/01proxy"
lines.push blank
# Update
lines.push comment "Updating Package List"
lines.push "apt-get update"
lines.push blank
# Install packages
lines += build_install_command @packages unless @packages.empty?
# Run install commands
lines += install_commands
# If manual deb packages were specified
if @deb_flag
# Resolve their dependencies
lines.push comment "Installing deb package dependencies"
lines.push "apt-get -y -f install --no-install-recommends"
lines.push blank
end
# Run post-install commands
if !@post_install_commands.empty?
lines.push comment "Removing temporary files"
lines += post_install_commands
lines.push blank
end
# Clean up
lines.push comment "Cleaning up after installation"
lines.push "apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*"
lines.push blank
# Remove apt-cacher proxy
lines.push comment "Removing apt-cacher-ng proxy"
lines.push "rm -f /etc/apt/apt.conf.d/01proxy"
lines.push blank
# Run commands
lines += run_commands
lines.push blank if !@run_commands.empty?
# Enable service on startup
if @service
lines.push comment "Enable service on startup"
lines.push "sed -i \"s@exit 0@service #{@service} start@g\" /etc/rc.local"
lines.push blank
end
# End commands
lines += end_commands
# Determine the longest line
longest_length = lines.max_by(&:length).length
# For each line
lines.collect do |l|
# Determine how many spaces needed to indent
length_to_extend = longest_length - l.length
# Indent the line
length_to_extend += 1 if l.start_with? "`"
l.insert(0, " " * (l.start_with?("`") ? 4 : 5))
# Add or Extend end markers
if l.end_with? " && \\"
length_to_extend += 5
l.insert(-6, " " * length_to_extend)
elsif l.end_with? " \\"
length_to_extend += 5
l.insert(-3, " " * length_to_extend)
else
l.insert(-1, " " * length_to_extend)
l.insert(-1, " && \\")
end
end
# First line should start with "RUN"
lines[0][0..2] = "RUN"
# Last line should not end with marker
lines[-1].gsub! " && \\", ""
lines[-1].gsub! " \\", ""
# Last line might be blank now, do it again
if lines[-1].match /^\s*$/
lines.delete_at -1
# Last line should not end with marker
lines[-1].gsub! " && \\", ""
lines[-1].gsub! " \\", ""
end
# Make a string
lines.join "\n"
end
##############################################################################
# Some metaprogramming to handle the various command lists
def self.handle(arg)
self.class_eval("def #{arg};@#{arg};end")
self.class_eval("def add_#{arg[0..-2]}(val);@#{arg}.push val;end")
end
handle :begin_commands
handle :pre_install_commands
handle :install_commands
handle :post_install_commands
handle :run_commands
handle :end_commands
end
################################################################################
# Parse Dockerfile.yml
dockerfile = Dockerfile.new
yaml = YAML::load_file("Dockerfile.yml")
# Parse User tag
dockerfile.set_user yaml["User"] if yaml.has_key? "User"
# Parse Service tag
dockerfile.set_service yaml["Service"] if yaml.has_key? "Service"
# Parse Add tag
yaml["Add"].each do |i|
if i.is_a? Hash
dockerfile.add i.first[0], i.first[1]
else
dockerfile.add i
end
end if yaml.has_key? "Add"
# Parse Repositories tag
yaml["Repositories"].each do |r|
if r["URL"].start_with? "deb "
dockerfile.add_repository(r["Name"], r["URL"], r["Key"])
elsif r["URL"].start_with? "ppa:"
dockerfile.add_ppa(r["Name"], r["URL"])
end
end if yaml.has_key? "Repositories"
# Parse Install tag
yaml["Install"].each do |package|
if package.end_with? ".deb"
dockerfile.install_deb package
else
dockerfile.install_package package
end
end if yaml.has_key? "Install"
# Parse Run tag
yaml["Run"].split("\n").each do |line|
dockerfile.run line
end if yaml.has_key? "Run"
# Parse Configure tag
yaml["Configure"].each do |i|
if i.is_a? Hash
dockerfile.add i.first[0], i.first[1]
else
dockerfile.add i
end
end if yaml.has_key? "Configure"
# Parse Expose tag
yaml["Expose"].each do |port|
dockerfile.expose port
end if yaml.has_key? "Expose"
# Parse Volumes tag
yaml["Volumes"].each do |volume|
dockerfile.add_volume volume
end if yaml.has_key? "Volumes"
# Output Dockerfile
puts dockerfile.finalize
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment