blob: 9ea6eca46197fc43585f42861e1b1d7652534650 [file] [log] [blame]
Nicolas Nobleddef2462015-01-06 18:08:25 -08001#!/bin/bash
2# Contains common funcs shared by instance startup scripts.
3#
4# The funcs assume that the code is being run on a GCE instance during instance
5# startup.
6
7function die() {
8 local msg="$0 failed"
9 if [[ -n $1 ]]
10 then
11 msg=$1
12 fi
13 echo $msg
14 exit 1
15}
16
17# umount_by_disk_id umounts a disk given its disk_id.
18umount_by_disk_id() {
19 local disk_id=$1
20 [[ -n $disk_id ]] || { echo "missing arg: disk_id" >&2; return 1; }
21
22 # Unmount the disk first
23 sudo umount /dev/disk/by-id/google-$disk_id || { echo "Could not unmount /mnt/disk-by-id/google-$disk_id" >&2; return 1; }
24}
25
26# check_metadata confirms that the result of curling a metadata url does not
27# contain 'Error 404'
28check_metadata() {
29 local curl_output=$1
30 [[ -n $curl_output ]] || { echo "missing arg: curl_output" >&2; return 1; }
31
32 if [[ $curl_output =~ "Error 404" ]]
33 then
34 return 1
35 fi
36
37 return 0
38}
39
40# name_this_instance determines the current instance name.
41name_this_instance() {
42 local the_full_host_name
43 the_full_host_name=$(load_metadata "http://metadata/computeMetadata/v1/instance/hostname")
44 check_metadata $the_full_host_name || return 1
45 local the_instance
46 the_instance=$(echo $the_full_host_name | cut -d . -f 1 -) || {
47 echo "could not get the instance name from $the_full_host_name" >&2
48 return 1
49 }
50
51 echo $the_instance
52}
53
54# delete_this_instance deletes this GCE instance. (it will shutdown as a result
55# of running this cmd)
56delete_this_instance() {
57 local the_full_zone
58 the_full_zone=$(load_metadata "http://metadata/computeMetadata/v1/instance/zone")
59 check_metadata $the_full_zone || return 1
60 local the_zone
61 the_zone=$(echo $the_full_zone | cut -d / -f 4 -) || { echo "could not get zone from $the_full_zone" >&2; return 1; }
62
63 local the_full_host_name
64 the_full_host_name=$(load_metadata "http://metadata/computeMetadata/v1/instance/hostname")
65 check_metadata $the_full_host_name || return 1
66 local the_instance
67 the_instance=$(echo $the_full_host_name | cut -d . -f 1 -) || { echo "could not get zone from $the_full_host_name" >&2; return 1; }
68
69 echo "using gcloud compute instances delete to remove: ${the_instance}"
70 gcloud compute --quiet instances delete --delete-disks boot --zone $the_zone $the_instance
71}
72
73# save_image_info updates the 'images' release info file on GCS.
74save_image_info() {
75 local image_id=$1
76 [[ -n $image_id ]] || { echo "missing arg: image_id" >&2; return 1; }
77
78 local repo_gs_uri=$2
79 [[ -n $repo_gs_uri ]] || { echo "missing arg: repo_gs_uri" >&2; return 1; }
80
81 local sentinel="/tmp/$image_id.txt"
82 echo $image_id > $sentinel || { echo "could not create /tmp/$image_id.txt" >&2; return 1; }
83
84 local gs_sentinel="$repo_gs_uri/images/info/LATEST"
85 gsutil cp $sentinel $gs_sentinel || { echo "failed to update $gs_sentinel" >&2; return 1; }
86}
87
88# creates an image, getting the name and cloud storage uri from the supplied
89# instance metadata.
90create_image() {
91 local image_id
92 image_id=$(load_metadata "attributes/image_id")
93 [[ -n $image_id ]] || { echo "missing metadata: image_id" >&2; return 1; }
94
95 local repo_gs_uri
96 repo_gs_uri=$(load_metadata "attributes/repo_gs_uri")
97 [[ -n $repo_gs_uri ]] || { echo "missing metadata: repo_gs_uri" >&2; return 1; }
98
99 local the_project
100 the_project=$(load_metadata "http://metadata/computeMetadata/v1/project/project-id")
101 check_metadata $the_project || return 1
102
103 sudo gcimagebundle -d /dev/sda -o /tmp/ --log_file=/tmp/$image_id.log || { echo "image creation failed" >&2; return 1; }
104 image_path=$(ls /tmp/*.tar.gz)
105 image_gs_uri="$repo_gs_uri/images/$image_id.tar.gz"
106
107 # copy the image to cloud storage
108 gsutil cp $image_path $image_gs_uri || { echo "failed to save image to $repo_gs_uri/$image_path " >&2; return 1; }
109 gcloud compute --project=$the_project images create \
110 $image_id --source-uri $image_gs_uri || { echo "failed to register $image_gs_uri as $image_id" >&2; return 1; }
111
112 save_image_info $image_id $repo_gs_uri
113}
114
115# load_metadata curls a metadata url
116load_metadata() {
117 local metadata_root=http://metadata/computeMetadata/v1
118 local uri=$1
119 [[ -n $uri ]] || { echo "missing arg: uri" >&2; return 1; }
120
121 if [[ $uri =~ ^'attributes/' ]]
122 then
123 for a in $(curl -H "X-Google-Metadata-Request: True" $metadata_root/instance/attributes/)
124 do
125 [[ $uri =~ "/$a"$ ]] && { curl $metadata_root/instance/$uri -H "X-Google-Metadata-Request: True"; return; }
126 done
127 fi
128
129 # if the uri is a full request uri
130 [[ $uri =~ ^$metadata_root ]] && { curl $uri -H "X-Google-Metadata-Request: True"; return; }
131}
132
133install_python_module() {
134 local mod=$1
135 [[ -z $mod ]] && { echo "missing arg: mod" >&2; return 1; }
136
137 echo '------------------------------------'
138 echo 'Installing: $mod'
139 echo '------------------------------------'
140 echo
141 install_with_apt_get gcc python-dev python-setuptools
142 sudo apt-get install -y gcc python-dev python-setuptools
143 sudo easy_install -U pip
144 sudo pip uninstall -y $mod
145 sudo pip install -U $mod
146}
147
148install_with_apt_get() {
149 local pkgs=$@
150 echo '---------------------------'
151 echo 'Installing: $pkgs'
152 echo '---------------------------'
153 echo
154 sudo apt-get install -y $pkgs
155}
156
157# pulls code from a git repo @HEAD to a local directory, removing the current version if present.
158setup_git_dir() {
159 local git_http_repo=$1
160 [[ -n $git_http_repo ]] || { echo "missing arg: git_http_repo" >&2; return 1; }
161
162 local git_dir=$2
163 [[ -n $git_dir ]] || { echo "missing arg: git_dir" >&2; return 1; }
164
165 if [[ -e $git_dir ]]
166 then
167 rm -fR $git_dir || { echo "could not remove existing repo at $git_dir" >&2; return 1; }
168 fi
169
170 local git_user
171 git_user=$(load_metadata "http://metadata/computeMetadata/v1/instance/service-accounts/default/email")
172 check_metadata $git_user || return 1
173 urlsafe_git_user=$(echo $git_user | sed -e s/@/%40/g) || return 1
174
175 local access_token=$(load_metadata "http://metadata/computeMetadata/v1/instance/service-accounts/default/token?alt=text")
176 check_metadata $access_token || return 1
177 local git_pwd=$(echo $access_token | cut -d' ' -f 2) || return 1
178
179 git clone https://$urlsafe_git_user:$git_pwd@$git_http_repo $git_dir
180}
181
182# network_copy copies a file to another gce instance.
183network_copy() {
184 local the_node=$1
185 [[ -n $the_node ]] || { echo "missing arg: the_node" >&2; return 1; }
186
187 local src=$2
188 [[ -n $src ]] || { echo "missing arg: src" >&2; return 1; }
189
190 local dst=$3
191 [[ -n $dst ]] || { echo "missing arg: dst" >&2; return 1; }
192
193 gcloud compute copy-files --zone=us-central1-b $src $node:$dst
194}
195
196# gcs_copy copies a file to a location beneath a root gcs object path.
197gcs_copy() {
198 local gce_root=$1
199 [[ -n $gce_root ]] || { echo "missing arg: gce_root" >&2; return 1; }
200
201 local src=$2
202 [[ -n $src ]] || { echo "missing arg: src" >&2; return 1; }
203
204 local dst=$3
205 [[ -n $dst ]] || { echo "missing arg: dst" >&2; return 1; }
206
207 gsutil cp $src $gce_root/$dst
208}
209
210# find_named_ip finds the external ip address for a given name.
211find_named_ip() {
212 local name=$1
213 [[ -n $name ]] || { echo "missing arg: name" >&2; return 1; }
214
215 gcloud compute addresses list | sed -e 's/ \+/ /g' | grep $name | cut -d' ' -f 3
216}
217
218# update_address_to updates this instances ip address to the reserved ip address with a given name
219update_address_to() {
220 local name=$1
221 [[ -n $name ]] || { echo "missing arg: name" >&2; return 1; }
222
223 named_ip=$(find_named_ip $name)
224 [[ -n $named_ip ]] || { echo "did not find an address corresponding to $name" >&2; return 1; }
225
226 local the_full_zone
227 the_full_zone=$(load_metadata "http://metadata/computeMetadata/v1/instance/zone")
228 check_metadata $the_full_zone || return 1
229 local the_zone
230 the_zone=$(echo $the_full_zone | cut -d / -f 4 -) || {
231 echo "could not get zone from $the_full_zone" >&2
232 return 1
233 }
234
235 local the_full_host_name
236 the_full_host_name=$(load_metadata "http://metadata/computeMetadata/v1/instance/hostname")
237 check_metadata $the_full_host_name || return 1
238 local the_instance
239 the_instance=$(echo $the_full_host_name | cut -d . -f 1 -) || {
240 echo "could not determine the instance from $the_full_host_name" >&2
241 return 1
242 }
243
244 gcloud compute instances delete-access-config --zone $the_zone $the_instance || {
245 echo "could not delete the access config for $the_instance" >&2
246 return 1
247 }
248 gcloud compute instances add-access-config --zone $the_zone $the_instance --address $named_ip || {
249 echo "could not update the access config for $the_instance to $named_ip" >&2
250 return 1
251 }
252}
253
254# Allows instances to checkout repos on git-on-borg.
255#
256install_gob_daemon() {
257 local gob_dir=$1
258 [[ -n $gob_dir ]] || { echo "missing args: gob_dir" >&2; return 1; }
259
260 local gob_repo=$2
261 [[ -n $gob_repo ]] || gob_repo='https://gerrit.googlesource.com/gcompute-tools/'
262
263 if [[ -e $gob_dir ]]
264 then
265 rm -fv $gob_dir || {
266 echo "could not remove existing git repo at $gob_dir" >&2
267 return 1
268 }
269 fi
270
271 git clone $gob_repo $gob_dir || { echo "failed to pull gerrit cookie repo" >&2; return 1; }
272 local startup_script=/etc/profile.d/gob_cookie_daemon.sh
273
274 cat <<EOF >> $startup_script
275#!/bin/bash
276
277$gob_dir/git-cookie-authdaemon
278
279EOF
280
281 chmod 755 $startup_script
282 $startup_script
283}
284
285# grpc_docker_add_docker_group
286#
287# Adds a docker group, restarts docker, relaunches the docker registry
288grpc_docker_add_docker_group() {
289 [[ -f /var/log/GRPC_DOCKER_IS_UP ]] || {
290 echo "missing file /var/log/GRPC_DOCKER_IS_UP; either wrong machine or still starting up" >&2;
291 return 1
292 }
293 sudo groupadd docker
294
295 local user=$(id -un)
296 [[ -n ${user} ]] || { echo 'could not determine the user' >&2; return 1; }
297 sudo gpasswd -a ${user} docker
298 sudo service docker restart || return 1;
299 grpc_docker_launch_registry
300}
301
302# grpc_dockerfile_pull <local_docker_parent_dir>
303#
304# requires: attributes/gs_dockerfile_root is set to cloud storage directory
305# containing the dockerfile directory
306grpc_dockerfile_pull() {
307 local dockerfile_parent=$1
308 [[ -n $dockerfile_parent ]] || dockerfile_parent='/var/local'
309
310 local gs_dockerfile_root=$(load_metadata "attributes/gs_dockerfile_root")
311 [[ -n $gs_dockerfile_root ]] || { echo "missing metadata: gs_dockerfile_root" >&2; return 1; }
312
313 mkdir -p $dockerfile_parent
314 gsutil cp -R $gs_dockerfile_root $dockerfile_parent || {
315 echo "Did not copy docker files from $gs_dockerfile_root -> $dockerfile_parent"
316 return 1
317 }
318 }
319
320# grpc_docker_launch_registry
321#
322# requires: attributes/gs_docker_reg is set to the cloud storage directory to
323# use to store docker images
324grpc_docker_launch_registry() {
325 local gs_docker_reg=$(load_metadata "attributes/gs_docker_reg")
326 [[ -n $gs_docker_reg ]] || { echo "missing metadata: gs_docker_reg" >&2; return 1; }
327
328 local gs_bucket=$(echo $gs_docker_reg | sed -r 's|gs://([^/]*?).*|\1|g')
329 [[ -n $gs_bucket ]] || {
330 echo "could not determine cloud storage bucket from $gs_bucket" >&2;
331 return 1
332 }
333
334 local storage_path_env=''
335 local image_path=$(echo $gs_docker_reg | sed -r 's|gs://[^/]*(.*)|\1|g' | sed -e 's:/$::g')
336 [[ -n $image_path ]] && {
337 storage_path_env="-e STORAGE_PATH=$image_path"
338 }
339
340 sudo docker run -d -e GCS_BUCKET=$gs_bucket $storage_path_env -p 5000:5000 google/docker-registry
341 # wait a couple of minutes max, for the registry to come up
342 local is_up=0
343 for i in {1..24}
344 do
345 local secs=`expr $i \* 5`
346 echo "is docker registry up? waited for $secs secs ..."
347 wget -q localhost:5000 && {
348 echo 'docker registry is up!'
349 is_up=1
350 break
351 }
352 sleep 5
353 done
354
355 [[ $is_up == 0 ]] && {
356 echo "docker registry not available after 120 seconds"; return 1;
357 } || return 0
358}
359
360# grpc_docker_pull_known
361#
362# This pulls a set of known docker images from a private docker registry to
363# the local image cache. It re-labels the images so that FROM in dockerfiles
364# used in dockerfiles running on the docker instance can find the images OK.
365#
366# optional: address of a grpc docker registry, the default is 0.0.0.0:5000
367grpc_docker_pull_known() {
368 local addr=$1
369 [[ -n $addr ]] || addr="0.0.0.0:5000"
370 local known="base cxx php_base php ruby_base ruby java_base java"
371 echo "... pulling docker images for '$known'"
372 for i in $known
373 do
374 sudo docker pull ${addr}/grpc/$i \
375 && sudo docker tag ${addr}/grpc/$i grpc/$i || {
376 # log and continue
377 echo "docker op error: could not pull ${addr}/grpc/$i"
378 }
379 done
380}
381
382# grpc_dockerfile_build_install
383#
384# requires: $1 is the label to apply to the docker image
385# requires: $2 is a local directory containing a Dockerfile
386# requires: there is a docker registry running on 5000, e.g, grpc_docker_launch_registry was run
387#
388# grpc_dockerfile_install "grpc/image" /var/local/dockerfile/grpc_image
389grpc_dockerfile_install() {
390 local image_label=$1
391 [[ -n $image_label ]] || { echo "missing arg: image_label" >&2; return 1; }
392 local docker_img_url=0.0.0.0:5000/$image_label
393
394 local dockerfile_dir=$2
395 [[ -n $dockerfile_dir ]] || { echo "missing arg: dockerfile_dir" >&2; return 1; }
396
397 local cache_opt='--no-cache'
398 local cache=$3
399 [[ $cache == "cache=yes" ]] && { cache_opt=''; }
400 [[ $cache == "cache=1" ]] && { cache_opt=''; }
401 [[ $cache == "cache=true" ]] && { cache_opt=''; }
402
403 [[ -d $dockerfile_dir ]] || { echo "not a valid dir: $dockerfile_dir"; return 1; }
404
405 # TODO(temiola): maybe make cache/no-cache a func option?
406 sudo docker build $cache_opt -t $image_label $dockerfile_dir || {
407 echo "docker op error: build of $image_label <- $dockerfile_dir"
408 return 1
409 }
410 sudo docker tag $image_label $docker_img_url || {
411 echo "docker op error: tag of $docker_img_url"
412 return 1
413 }
414 sudo docker push $docker_img_url || {
415 echo "docker op error: push of $docker_img_url"
416 return 1
417 }
418}
419
420# grpc_dockerfile_refresh
421#
422# requires: $1 is the label to apply to the docker image
423# requires: $2 is a local directory containing a Dockerfile
424# requires: there is a docker registry running on 5000, e.g, grpc_docker_launch_registry was run
425#
Tim Emiola58192e82015-01-15 12:56:22 -0800426# call-seq:
427# grpc_dockerfile_refresh "grpc/mylabel" /var/local/dockerfile/dir_containing_my_dockerfile
Nicolas Nobleddef2462015-01-06 18:08:25 -0800428grpc_dockerfile_refresh() {
Nicolas Nobleddef2462015-01-06 18:08:25 -0800429 grpc_dockerfile_install "$@"
430}