# Installation functions for Chef 0.8 RPMs obtained from the ELFF repo. export CHEF_STATUS_PORT="1234" export STATUS_MONITOR_DIR="/root/status_monitor" export SERVICE_BIN="/usr/sbin/service" [ -f /bin/rpm ] && SERVICE_BIN="/sbin/service" function configure_chef_server { echo "" } function print_client_validation_key { cat /etc/chef/validation.pem } function configure_chef_client { if (( $# != 3 )); then echo "Unable to configure chef client." echo "usage: configure_chef_client " exit 1 fi local SERVER_NAME=$1 local CLIENT_VALIDATION_KEY=$2 local INTERVAL=${3:-"600"} # default is 10 minutes if [ ! -f "/etc/chef/validation.pem" ]; then cat > /etc/chef/validation.pem <<-EOF_VALIDATION_PEM $CLIENT_VALIDATION_KEY EOF_VALIDATION_PEM sed -e "/^$/d" -i /etc/chef/validation.pem fi local CLIENT_CONFIG="/etc/chef/client.rb" local CHEF_NOTIFICATION_HANDLER=/var/lib/chef/handlers/netcat.rb sed -e "s|localhost|$SERVER_NAME|g" -i $CLIENT_CONFIG sed -e "s|^chef_server_url.*|chef_server_url \"http://$SERVER_NAME:4000\"|g" -i $CLIENT_CONFIG sed -e "s|^log_location.*|log_location \"\/var/log/chef/client.log\"|g" -i $CLIENT_CONFIG if ! grep "$CHEF_NOTIFICATION_HANDLER" $CLIENT_CONFIG &> /dev/null; then cat >> $CLIENT_CONFIG <<-EOF_CAT_CHEF_CLIENT_CONF # custom Chef notification handler require "$CHEF_NOTIFICATION_HANDLER" netcat_handler = NetcatHandler.new report_handlers << netcat_handler exception_handlers << netcat_handler EOF_CAT_CHEF_CLIENT_CONF fi local CHEF_SYSCONFIG=/etc/default/chef-client [ -d /etc/sysconfig/ ] && CHEF_SYSCONFIG=/etc/sysconfig/chef-client cat > $CHEF_SYSCONFIG <<-EOF_CAT_CHEF_SYSCONFIG INTERVAL=$INTERVAL SPLAY=20 CONFIG=/etc/chef/client.rb LOGFILE=/var/log/chef/client.log EOF_CAT_CHEF_SYSCONFIG mkdir -p /var/lib/chef/handlers cat > $CHEF_NOTIFICATION_HANDLER <<-EOF_CHEF_NOTIFY require 'socket' class NetcatHandler < Chef::Handler def report begin socket = TCPSocket.open('$SERVER_NAME', $CHEF_STATUS_PORT) hostname=%x{hostname}.chomp if success? socket.write("#{hostname}:ONLINE\n") else socket.write("#{hostname}:FAILURE\n") end socket.close() rescue Exception => e Chef::Log.error("Netcat handler failed: " + e.message) end end end EOF_CHEF_NOTIFY } # This function will only run on the Chef Server for initial registration function configure_knife { local KNIFE_EDITOR=${1:-"vim"} [ ! -f $HOME/.chef/chef-admin.pem ] || { echo "Knife already configured."; return 0; } local COUNT=0 until [ -f /etc/chef/webui.pem ]; do echo "waiting for /etc/chef/webui.pem" sleep 1 COUNT=$(( $COUNT + 1 )) if (( $COUNT > 30 )); then echo "timeout waiting for /etc/chef/webui.pem" exit 1 break; fi done cd /tmp /usr/bin/knife configure -i -s "http://localhost:4000" -u "chef-admin" -r "/root/cookbook-repos/chef-repo/" -y -d \ || { echo "Failed to configure knife."; exit 1; } cat > /etc/profile.d/knife.sh <<-EOF_CAT_KNIFE_SH alias knife='EDITOR=$KNIFE_EDITOR knife' EOF_CAT_KNIFE_SH cat > /etc/profile.d/knife.csh <<-EOF_CAT_KNIFE_CSH alias knife '/usr/bin/env EDITOR=$KNIFE_EDITOR knife' EOF_CAT_KNIFE_CSH chown root:root /etc/profile.d/knife* chmod 755 /etc/profile.d/knife* } function knife_add_node { if (( $# != 3 )); then echo "Unable to add node with knife." echo "usage: knife_add_node " exit 1 fi local NODE_NAME=$1 local RUN_LIST=$2 local ATTRIBUTES_JSON=$3 local DOMAIN_NAME=$(hostname -d) local TMP_FILE=/tmp/node.json cat > $TMP_FILE <<-EOF_CAT_CHEF_CLIENT_CONF { "overrides": { }, "name": "$NODE_NAME.$DOMAIN_NAME", "chef_type": "node", "json_class": "Chef::Node", "attributes": $ATTRIBUTES_JSON, "run_list": $RUN_LIST, "defaults": { } } EOF_CAT_CHEF_CLIENT_CONF knife node from file $TMP_FILE 1> /dev/null || \ { echo "Failed to add node with knife."; exit 1; } rm $TMP_FILE } function knife_delete_node { if (( $# != 1 )); then echo "Unable to add node with knife." echo "usage: knife_delete_node " exit 1 fi local NODE_NAME=$1 local DOMAIN_NAME=$(hostname -d) knife node delete "$NODE_NAME.$DOMAIN_NAME" -y &> /dev/null || \ { echo "Failed to delete node with knife. Ignoring..."; } knife client delete "$NODE_NAME.$DOMAIN_NAME" -y &> /dev/null || \ { echo "Failed to delete client with knife. Ignoring..."; } #send a reset notification for the Chef client status monitor echo "$NODE_NAME:RESET" | nc localhost $CHEF_STATUS_PORT } function knife_create_databag { if (( $# != 3 )); then echo "Unable to create databag with knife." echo "usage: knife_create_databag " exit 1 fi local BAG_NAME=$1 local ITEM_ID=$2 local ITEM_JSON=$3 local TMP_FILE=/tmp/databag.json cat > $TMP_FILE <<-EOF_CAT_CHEF_DATA_BAG $ITEM_JSON EOF_CAT_CHEF_DATA_BAG knife data bag from file $BAG_NAME $TMP_FILE 1> /dev/null || \ { echo "Failed to create data bag with knife."; exit 1; } rm $TMP_FILE } function download_cookbook_repos { local COOKBOOK_URLS=${1:?"Please specify a list of cookbook repos to download."} local REPOS_BASEDIR=${2:-"/root/cookbook-repos"} # download and extract the cookbooks for CB_REPO in $COOKBOOK_URLS; do echo -n "Downloading $CB_REPO..." if [ "http:" == ${CB_REPO:0:5} ] || [ "https:" == ${CB_REPO:0:6} ]; then wget --no-check-certificate "$CB_REPO" -O "/tmp/cookbook-repo.tar.gz" &> /dev/null || { echo "Failed to download cookbook tarball."; return 1; } elif [ "git:" == ${CB_REPO:0:4} ]; then if [ -f /usr/bin/yum ]; then rpm -q git &> /dev/null || yum install -y -q git elif [ -f /usr/bin/dpkg ]; then dpkg -L git > /dev/null 2>&1 || apt-get install -y --quiet git > /dev/null 2>&1 else echo "Failed to install git." exit 1 fi pushd $REPOS_BASEDIR git clone "$CB_REPO" popd else download_cloud_file "$CB_REPO" "/tmp/cookbook-repo.tar.gz" fi echo "OK" if [ -f /tmp/cookbook-repo.tar.gz ]; then [ -d "$REPOS_BASEDIR" ] || mkdir -p "$REPOS_BASEDIR" cd $REPOS_BASEDIR echo -n "Extracting $CB_REPO..." tar xzf /tmp/cookbook-repo.tar.gz rm /tmp/cookbook-repo.tar.gz echo "OK" fi done } function knife_upload_cookbooks_and_roles { local REPOS_BASEDIR=${1:-"/root/cookbook-repos"} # install cookbooks local REPOS="" for CB_REPO in $(ls $REPOS_BASEDIR); do [ -n "$REPOS" ] && REPOS="$REPOS," REPOS="$REPOS'$REPOS_BASEDIR/$CB_REPO/cookbooks', '$REPOS_BASEDIR/$CB_REPO/site-cookbooks'" done sed -e "s|^cookbook_path.*|cookbook_path [ $REPOS ]|" -i $HOME/.chef/knife.rb /usr/bin/knife cookbook metadata -a &> /dev/null || { echo "Failed to generate cookbook metadata."; exit 1; } /usr/bin/knife cookbook upload -a &> /dev/null || { echo "Failed to install cookbooks."; exit 1; } # install roles for CB_REPO in $(ls $REPOS_BASEDIR); do for ROLE in $(ls $REPOS_BASEDIR/$CB_REPO/roles/); do [[ "$ROLE" == "README" ]] || \ /usr/bin/knife role from file "$REPOS_BASEDIR/$CB_REPO/roles/$ROLE" 1> /dev/null done done } function start_chef_server { [ -d /var/run/chef ] && chown chef:chef /var/run/chef if [ ! -f /var/run/chef/server.main.pid ]; then $SERVICE_BIN couchdb start 1> /dev/null /sbin/chkconfig couchdb on &> /dev/null $SERVICE_BIN rabbitmq-server start /dev/null /sbin/chkconfig rabbitmq-server on &> /dev/null sleep 3 local RABBIT_ERR_SIZE=$(stat -c%s "/var/log/rabbitmq/startup_err") if (( $RABBIT_ERR_SIZE > 0 )); then $SERVICE_BIN rabbitmq-server stop /dev/null $SERVICE_BIN rabbitmq-server start /dev/null sleep 3 fi # Chef 0.9: chef-solr chef-solr-indexer chef-server chef-server-webui # Chef 0.10: chef-solr chef-expander chef-server chef-server-webui for svc in chef-solr chef-expander chef-solr-indexer chef-server chef-server-webui; do if [ -f /etc/init.d/$svc ]; then $SERVICE_BIN $svc start /sbin/chkconfig $svc on &> /dev/null sleep 3 fi done fi } function start_chef_client { $SERVICE_BIN chef-client start if [ -f /sbin/chkconfig ]; then chkconfig chef-client on &> /dev/null fi } function start_notification_server { [ -d "$STATUS_MONITOR_DIR" ] && return 0; if [ -f /usr/bin/yum ]; then rpm -q nc &> /dev/null || yum install -y -q nc elif [ -f /usr/bin/dpkg ]; then dpkg -L netcat-openbsd > /dev/null 2>&1 || apt-get install -y --quiet netcat-openbsd > /dev/null 2>&1 else echo "Failed to install netcat. (for Chef client status monitoring)" exit 1 fi mkdir -p $STATUS_MONITOR_DIR cat >> $STATUS_MONITOR_DIR/server.sh <<-EOF_NC_NOTIFY_SERVER #!/bin/bash while true; do nc -d -k -l $CHEF_STATUS_PORT > $STATUS_MONITOR_DIR/status.out done EOF_NC_NOTIFY_SERVER bash $STATUS_MONITOR_DIR/server.sh &> /dev/null < /dev/null & if [ -f /etc/rc.local ]; then echo "bash $STATUS_MONITOR_DIR/server.sh &> /dev/null < /dev/null &" >> /etc/rc.local fi } function poll_chef_client_online { local CLIENT_NAMES=${1:?"Please specify a chef client name."} local SECS=${2:-"600"} #10 minutes local RESTART_TIMEOUT=${3:-"$SECS"} #Restart clients if they haven't finished by timeout local RESTART_ON_FAILURE=${4} #Restart clients on failure (once) local TMP_RESTART=$(mktemp) local SLEEP_COUNT=5 local COUNT=1 local MAX_COUNT=$(( $SECS / $SLEEP_COUNT )) local MAX_RETRY_COUNT=$(( $RESTART_TIMEOUT / $SLEEP_COUNT )) local FAILED_CLIENTS="" local ALL_ONLINE="true" until (( $COUNT == $MAX_COUNT )); do ALL_ONLINE="true" FAILED_CLIENTS="" for NAME in $CLIENT_NAMES; do if ! grep "$NAME:" $STATUS_MONITOR_DIR/status.out | tail -n 1 | grep -c ":ONLINE" &> /dev/null; then ALL_ONLINE="false" FAILED_CLIENTS="$NAME $FAILED_CLIENTS" #Restart any chef clients that might have failed (do this once) if [ -n "$RESTART_ON_FAILURE" ] && grep "$NAME:" $STATUS_MONITOR_DIR/status.out &> /dev/null && ! grep ":$NAME:" $TMP_RESTART &> /dev/null; then echo ":$NAME:" >> $TMP_RESTART ssh "$NAME" bash <<-EOF_SSH_CHEF_RESTART $SERVICE_BIN chef-client restart EOF_SSH_CHEF_RESTART fi #Restart any chef clients that haven't completed by timeout if (( $COUNT >= $MAX_RETRY_COUNT )) && ! grep "$NAME:" $STATUS_MONITOR_DIR/status.out &> /dev/null && ! grep ":$NAME:" $TMP_RESTART &> /dev/null; then echo ":$NAME:" >> $TMP_RESTART ssh "$NAME" bash <<-EOF_SSH_CHEF_RESTART $SERVICE_BIN chef-client restart EOF_SSH_CHEF_RESTART fi fi done if [[ $ALL_ONLINE == "true" ]]; then echo "All Chef client(s) ran successfully." return 0 fi COUNT=$(( $COUNT + 1 )) sleep $SLEEP_COUNT done [ -f "$TMP_RESTART" ] && rm $TMP_RESTART echo "Chef client(s) failed to run: $FAILED_CLIENTS" return 1 }