Be smarter about building and the --realclean option:
[arvados.git] / docker / build_tools / build.rb
1 #! /usr/bin/env ruby
2
3 require 'optparse'
4 require 'tempfile'
5 require 'yaml'
6
7 def main options
8   if not ip_forwarding_enabled?
9     warn "NOTE: IP forwarding must be enabled in the kernel."
10     warn "Turning IP forwarding on now."
11     sudo %w(/sbin/sysctl net.ipv4.ip_forward=1)
12   end
13
14   # Check that:
15   #   * Docker is installed and can be found in the user's path
16   #   * Docker can be run as a non-root user
17   #      - TODO: put the user is in the docker group if necessary
18   #      - TODO: mount cgroup automatically
19   #      - TODO: start the docker service if not started
20
21   docker_path = %x(which docker.io).chomp
22
23   if docker_path.empty?
24     docker_path = %x(which docker).chomp
25   end
26
27   if docker_path.empty?
28     warn "Docker not found."
29     warn ""
30     warn "Please make sure that Docker has been installed and"
31     warn "can be found in your PATH."
32     warn ""
33     warn "Installation instructions for a variety of platforms can be found at"
34     warn "http://docs.docker.io/en/latest/installation/"
35     exit 1
36   elsif not docker_ok? docker_path
37     warn "WARNING: docker could not be run."
38     warn "Please make sure that:"
39     warn "  * You have permission to read and write /var/run/docker.sock"
40     warn "  * a 'cgroup' volume is mounted on your machine"
41     warn "  * the docker daemon is running"
42     exit 2
43   end
44
45   # Check that debootstrap is installed.
46   if not debootstrap_ok?
47     warn "Installing debootstrap."
48     sudo '/usr/bin/apt-get', 'install', 'debootstrap'
49   end
50
51   # Generate a config.yml if it does not exist or is empty
52   if not File.size? 'config.yml'
53     print "Generating config.yml.\n"
54     print "Arvados needs to know the email address of the administrative user,\n"
55     print "so that when that user logs in they are automatically made an admin.\n"
56     print "This should be an email address associated with a Google account.\n"
57     print "\n"
58     admin_email_address = ""
59     until is_valid_email? admin_email_address
60       print "Enter your Google ID email address here: "
61       admin_email_address = gets.strip
62       if not is_valid_email? admin_email_address
63         print "That doesn't look like a valid email address. Please try again.\n"
64       end
65     end
66
67     File.open 'config.yml', 'w' do |config_out|
68       config = YAML.load_file 'config.yml.example'
69       config['API_AUTO_ADMIN_USER'] = admin_email_address
70       config['API_HOSTNAME'] = generate_api_hostname
71       config['PUBLIC_KEY_PATH'] = find_or_create_ssh_key(config['API_HOSTNAME'])
72       config.each_key do |var|
73         if var.end_with?('_PW') or var.end_with?('_SECRET')
74           config[var] = rand(2**256).to_s(36)
75         end
76         config_out.write "#{var}: #{config[var]}\n"
77       end
78     end
79   end
80
81   # If all prerequisites are met, go ahead and build.
82   if ip_forwarding_enabled? and
83       docker_ok? docker_path and
84       debootstrap_ok? and
85       File.exists? 'config.yml'
86     warn "Building Arvados."
87     system({"DOCKER" => docker_path}, '/usr/bin/make', '-f', options[:makefile], *ARGV)
88   end
89 end
90
91 # sudo
92 #   Execute the arg list 'cmd' under sudo.
93 #   cmd can be passed either as a series of arguments or as a
94 #   single argument consisting of a list, e.g.:
95 #     sudo 'apt-get', 'update'
96 #     sudo(['/usr/bin/gpasswd', '-a', ENV['USER'], 'docker'])
97 #     sudo %w(/usr/bin/apt-get install lxc-docker)
98 #
99 def sudo(*cmd)
100   # user can pass a single list in as an argument
101   # to allow usage like: sudo %w(apt-get install foo)
102   warn "You may need to enter your password here."
103   if cmd.length == 1 and cmd[0].class == Array
104     cmd = cmd[0]
105   end
106   system '/usr/bin/sudo', *cmd
107 end
108
109 # is_valid_email?
110 #   Returns true if its arg looks like a valid email address.
111 #   This is a very very loose sanity check.
112 #
113 def is_valid_email? str
114   str.match /^\S+@\S+\.\S+$/
115 end
116
117 # generate_api_hostname
118 #   Generates a 5-character randomly chosen API hostname.
119 #
120 def generate_api_hostname
121   rand(2**256).to_s(36)[0...5]
122 end
123
124 # ip_forwarding_enabled?
125 #   Returns 'true' if IP forwarding is enabled in the kernel
126 #
127 def ip_forwarding_enabled?
128   %x(/sbin/sysctl -n net.ipv4.ip_forward) == "1\n"
129 end
130
131 # debootstrap_ok?
132 #   Returns 'true' if debootstrap is installed and working.
133 #
134 def debootstrap_ok?
135   return system '/usr/sbin/debootstrap --version > /dev/null 2>&1'
136 end
137
138 # docker_ok?
139 #   Returns 'true' if docker can be run as the current user.
140 #
141 def docker_ok?(docker_path)
142   return system "#{docker_path} images > /dev/null 2>&1"
143 end
144
145 # find_or_create_ssh_key arvados_name
146 #   Returns the SSH public key appropriate for this Arvados instance,
147 #   generating one if necessary.
148 #
149 def find_or_create_ssh_key arvados_name
150   ssh_key_file = "#{ENV['HOME']}/.ssh/arvados_#{arvados_name}_id_rsa"
151   unless File.exists? ssh_key_file
152     system 'ssh-keygen',
153            '-f', ssh_key_file,
154            '-C', "arvados@#{arvados_name}",
155            '-P', ''
156   end
157
158   return "#{ssh_key_file}.pub"
159 end
160
161 # install_docker
162 #   Determines which Docker package is suitable for this Linux distro
163 #   and installs it, resolving any dependencies.
164 #   NOTE: not in use yet.
165
166 def install_docker
167   linux_distro = %x(lsb_release --id).split.last
168   linux_release = %x(lsb_release --release).split.last
169   linux_version = linux_distro + " " + linux_release
170   kernel_release = `uname -r`
171
172   case linux_distro
173   when 'Ubuntu'
174     if not linux_release.match '^1[234]\.'
175       warn "Arvados requires at least Ubuntu 12.04 (Precise Pangolin)."
176       warn "Your system is Ubuntu #{linux_release}."
177       exit 3
178     end
179     if linux_release.match '^12' and kernel_release.start_with? '3.2'
180       # Ubuntu Precise ships with a 3.2 kernel and must be upgraded.
181       warn "Your kernel #{kernel_release} must be upgraded to run Docker."
182       warn "To do this:"
183       warn "  sudo apt-get update"
184       warn "  sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
185       warn "  sudo reboot"
186       exit 4
187     else
188       # install AUFS
189       sudo 'apt-get', 'update'
190       sudo 'apt-get', 'install', "linux-image-extra-#{kernel_release}"
191     end
192
193     # add Docker repository
194     sudo %w(/usr/bin/apt-key adv
195               --keyserver keyserver.ubuntu.com
196               --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9)
197     source_file = Tempfile.new('arv')
198     source_file.write("deb http://get.docker.io/ubuntu docker main\n")
199     source_file.close
200     sudo '/bin/mv', source_file.path, '/etc/apt/sources.list.d/docker.list'
201     sudo %w(/usr/bin/apt-get update)
202     sudo %w(/usr/bin/apt-get install lxc-docker)
203
204     # Set up for non-root access
205     sudo %w(/usr/sbin/groupadd docker)
206     sudo '/usr/bin/gpasswd', '-a', ENV['USER'], 'docker'
207     sudo %w(/usr/sbin/service docker restart)
208   when 'Debian'
209   else
210     warn "Must be running a Debian or Ubuntu release in order to run Docker."
211     exit 5
212   end
213 end
214
215
216 if __FILE__ == $PROGRAM_NAME
217   options = { :makefile => File.join(File.dirname(__FILE__), 'Makefile') }
218   OptionParser.new do |opts|
219     opts.on('-m', '--makefile MAKEFILE-PATH',
220             'Path to the Makefile used to build Arvados Docker images') do |mk|
221       options[:makefile] = mk
222     end
223   end
224
225   main options
226 end