Nicolas Noble | ddef246 | 2015-01-06 18:08:25 -0800 | [diff] [blame] | 1 | #!/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 | |
| 7 | function 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. |
| 18 | umount_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' |
| 28 | check_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. |
| 41 | name_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) |
| 56 | delete_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. |
| 74 | save_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. |
| 90 | create_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 |
| 116 | load_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 | |
| 133 | install_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 | |
| 148 | install_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. |
| 158 | setup_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. |
| 183 | network_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. |
| 197 | gcs_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. |
| 211 | find_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 |
| 219 | update_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 | # |
| 256 | install_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 | |
| 279 | EOF |
| 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 |
| 288 | grpc_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 |
| 306 | grpc_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 |
| 324 | grpc_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 |
| 367 | grpc_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 |
| 389 | grpc_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 Emiola | 58192e8 | 2015-01-15 12:56:22 -0800 | [diff] [blame] | 426 | # call-seq: |
| 427 | # grpc_dockerfile_refresh "grpc/mylabel" /var/local/dockerfile/dir_containing_my_dockerfile |
Nicolas Noble | ddef246 | 2015-01-06 18:08:25 -0800 | [diff] [blame] | 428 | grpc_dockerfile_refresh() { |
Nicolas Noble | ddef246 | 2015-01-06 18:08:25 -0800 | [diff] [blame] | 429 | grpc_dockerfile_install "$@" |
| 430 | } |