#!/bin/bash # A set of library functions for the drupal*.sh scripts. # this script has no functionality on its own. # # Terms/variables used below (generally): # install name -- the installation within an install group. Typically something like 'foo.org'. # install group root dir -- the directory that forms the base of the install group. # include standard library file if [[ "${0}" != "-bash" && -e "`dirname "${0}"`/stdLibrary.sh" ]]; then . "`dirname $0`/stdLibrary.sh" else drupalScriptsDir="`dirname \"\`which drupalGitDeploy.sh 2>/dev/null \`\"`" . "${drupalScriptsDir}/stdLibrary.sh" fi # include mysql library file if [[ "${0}" != "-bash" && -e "`dirname "${0}"`/mysqlLibrary.sh" ]]; then . "`dirname $0`/mysqlLibrary.sh" else drupalScriptsDir="`dirname \"\`which drupalGitDeploy.sh 2>/dev/null \`\"`" . "${drupalScriptsDir}/mysqlLibrary.sh" fi # Prefix and suffix for a project name to create a Git repository URL. DRUPAL_GIT_URL_PREFIX="git://git.drupal.org/project/" DRUPAL_GIT_URL_SUFFIX=".git" # Name of project for Drupal core DRUPAL_GIT_PROJECT_NAME="drupal" # Default branch named used for installations and updates. DEFAULT_DRUPAL_BRANCH_NAME="6.x" # Default Drupal lookup major version -- # needs to identify the version which is implied by the 'DEFAULT_DRUPAL_BRANCH_NAME' value DEFAULT_DRUPAL_MAJOR_VERSION=6 # This names the environment variable used to store the install group root dir INSTALL_GROUP_ROOT_DIR_ENVIRONMENT_VARIABLE="DRUPAL_INSTALL_ROOT" # The name of the Drush project name in drupal DRUSH_PROJECT_NAME="drush" # The name of the Drupal Scripts Library sub-directory into which the Drush code should be installed DRUSH_INSTALL_DIR="drush" # The directory w/in the themes and modules directories # used to store contrib themes and modules. CONTRIB_SUBDIR=contrib # The parameters used by mysqldump in drupalDBBackup.sh # included here so available to external scripts. MYSQLDUMP_FLAGS="-c --skip-extended-insert --add-drop-table" ### #### Script functions ### # Outputs the usage lines of the install and group root dir parameters. # See outputGroupRootDirUsage() # $1 - If set, will provide information on using 'all' instead of an installation site name. outputInstallGroupUsage() { echo -e " - the installation site name." echo -e "\tThis will be the name of the install directory within the install group dir." echo -e " " if [ -n "${1}" ]; then echo -e "'all' - directs script to operate on all sites in the install group." echo -e "\tUse caution when performing operations across all sites" echo -e " " fi outputGroupRootDirUsage } # Outputs the usage lines of the group root dir parameter # Called by outputInstallGroupUsage() outputGroupRootDirUsage() { echo -e "[install group root dir] - the root directory for all Drupal installs." echo -e "\tIf not specified, will use the environment variable '${INSTALL_GROUP_ROOT_DIR_ENVIRONMENT_VARIABLE}'." echo -e "\tIf that is nto specified will use a directory called 'drupal' in the curent working directory, if it exists." echo -e "\tFailing those, script will use the current working directory as a root dir." } ### #### Functions dealing with the group root dir ### # Computes and returns the group root dir. # Uses the first of: # If the parameter is supplied, returns that # If the environment variable INSTALL_GROUP_ROOT_DIR_ENVIRONMENT_VARIABLE is set, returns that # If the directory "./drupal" exists, uses that # Failing everything else, returns the current working directory # $1 - supplied group root dir (can be empty) getGroupRootDir() { groupRootDir="${1}" # specified in parameter? if [ -n "${groupRootDir}" ]; then echo "${groupRootDir}" return fi # Try our environment variable envVal="`dereferenceVar ${INSTALL_GROUP_ROOT_DIR_ENVIRONMENT_VARIABLE}`" if [ -n "${envVal}" ]; then echo "${envVal}" return fi # try directory called 'drupal' off local dir pwdPlusDrupal="`pwd`/drupal" if [ -d "${pwdPlusDrupal}" ]; then echo "${pwdPlusDrupal}" return fi # Last resort pwd } # Validates a group root dir # Returns nothing if no problems, error string if there is. # $1 - group root dir to validate validateGroupRootDir() { groupRootDir="${1}" if [ ! -e "${groupRootDir}" ]; then echo "** Group Root Dir '${groupRootDir}' does not exist. Aborting." return fi if [ ! -d "${groupRootDir}" ]; then echo "** Group Root Dir '${groupRootDir}' not a directory. Aborting." return fi } # Outputs a list of sites (site directories) in the group root dir. # All responses are validated to ensure they're actual drupal installs. # $1 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. getSitesInGroupDir() { groupRootDir="${1}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi /bin/ls -F "${groupRootDir}/" | grep '/$' | while read dirname; do site="`basename "${dirname}"`" # validateSite returns an error message. If empty, site is a valid drupal site. if [ -z "`validateSite "${site}" "${groupRootDir}"`" ]; then echo "${site}" fi done } ### #### Functions dealing with the structure of a single deployed Drupal site ### # Returns the 'target dir' -- the directory within the install group for a # particular install. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. getTargetDir() { installName="${1}" groupRootDir="${2}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi echo "${groupRootDir}/${installName}" } # Returns the 'sites' directory for the given installation site name and install group root dir # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getSitesRootDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" if [ -z "${relativeOnly}" ]; then targetDir="`getTargetDir "${installName}" "${groupRootDir}"`" echo "${targetDir}/sites" else echo "sites" fi } # Returns the 'sites' directory for the given installation site name and install group root dir # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getSiteDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" sitesRootDir="`getSitesRootDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${sitesRootDir}/${installName}" } # Returns the 'settings.php' filename, including path, for a site. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getSiteSettingsFilename() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" siteDir="`getSiteDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/settings.php" } # Returns the directory within the site directory used for file uploads # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getFileUploadDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" siteDir="`getSiteDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/files" } # Returns the directory within the site directory used to store modules. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getModuleDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" siteDir="`getSiteDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/modules" } # Returns the directory within the non-contrib module directory used by a particular # (non-contrib) module. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - the non-contrib module name. # $4 - If supplied (any value) will return the relative path not starting with a "/" getDirForNonContribModule() { installName="${1}" groupRootDir="${2}" moduleName="${3}" relativeOnly="${4}" moduleDir="`getModuleDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${moduleDir}/${moduleName}" } # Returns the directory within the module directory used for contributed modules. # These are modules downloaded from the Drupal Git repo. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getContribModuleDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" moduleDir="`getModuleDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${moduleDir}/${CONTRIB_SUBDIR}" } # Returns the directory within the contrib module directory used by a particular # (contrib) module. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - the contrib module name. # $4 - If supplied (any value) will return the relative path not starting with a "/" getDirForContribModule() { installName="${1}" groupRootDir="${2}" moduleName="${3}" relativeOnly="${4}" contribModuleDir="`getContribModuleDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${contribModuleDir}/${moduleName}" } # Returns the directory within the site directory used to store themes. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getThemeDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" siteDir="`getSiteDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/themes" } # Returns the directory within the themes directory used for contributed themes. # These are themes downloaded from the Drupal Git repo. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getContribThemeDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" themeDir="`getThemeDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${themeDir}/${CONTRIB_SUBDIR}" } # Returns the directory within the contrib theme directory used by a particular # (contrib) theme. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - the contrib theme name. # $4 - If supplied (any value) will return the relative path not starting with a "/" getDirForContribTheme() { installName="${1}" groupRootDir="${2}" themeName="${3}" relativeOnly="${4}" contribThemeDir="`getContribThemeDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${contribThemeDir}/${themeName}" } # Returns the directory within the site directory used for the named site theme. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getSiteThemeDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" themeDir="`getThemeDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/${installName}" } # Returns the directory within the site directory used for backups # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. # $3 - If supplied (any value) will return the relative path not starting with a "/" getBackupDir() { installName="${1}" groupRootDir="${2}" relativeOnly="${3}" siteDir="`getSiteDir "${installName}" "${groupRootDir}" "${relativeOnly}"`" echo "${siteDir}/backups" } ### #### Functions dealing with site aliases ### # Outputs the install name for a given site alias. # Outputs nothing if not available (for whatever reason). # Performs no validation of the linked-to install # $1 - the symlink alias # $2 - the install group root dir getInstallNameForAlias() { alias="${1}" groupRootDir="${2}" # Ensure its a symbolic link if [ ! -h "${groupRootDir}/${alias}" ]; then return fi installName="`readlink "${groupRootDir}/${alias}"`" if [ -z "${installName}" ]; then return fi # ensure linked-to item is a valid install #errorStr="`validateSite "${installName}" "${groupRootDir}"`" #if [ -n "${errorStr}" ]; then # return #fi # Success! echo "${installName}" } # Creates alias symlinks for an installation # so that an installed site can be used with other names # Returns error message (or nothing if things are fine) # $1 - the (to be created) alias -- name of symlink # $2 - the install name # $3 - the install group root dir createSiteAlias() { alias="${1}" installName="${2}" groupRootDir="${3}" sitesRootDir="`getSitesRootDir "${installName}" "${groupRootDir}"`" # Ensure file in group root dir does not exist if [ -e "${groupRootDir}/${alias}" ]; then echo "symlink target '${alias}' already exists in group root directory '${groupRootDir}'." return fi # Ensure file in 'sites' dir does not exist. if [ -e "${sitesRootDir}/${alias}" ]; then echo "symlink target '${alias}' already exists in install sites directory '${sitesRootDir}'." return fi # Create symlink in sites root dir pushd "${groupRootDir}" 2>&1 >/dev/null ln -s "${installName}" "${alias}" popd 2>&1 >/dev/null # Create symlink in install 'sites' dir pushd "${sitesRootDir}" 2>&1 >/dev/null ln -s "${installName}" "${alias}" popd 2>&1 >/dev/null } # Removes alias symlinks created with createSiteAlias(), above # Returns error message (or nothing if things are fine) # $1 - the alias (symlink) name to be removed # $2 - the install group root dir removeSiteAlias() { alias="${1}" groupRootDir="${2}" # Ensure link exists in group root dir if [ ! -h "${groupRootDir}/${alias}" ]; then echo "No symlink alias '${alias}' exists in group root directory '${groupRootDir}'." return fi installName="`getInstallNameForAlias "${alias}" "${groupRootDir}"`" if [ -z "${installName}" ]; then echo "Could not get installation from alias symlink '${alias}'." return fi sitesRootDir="`getSitesRootDir "${installName}" "${groupRootDir}"`" # Ensure link exists in sites dir if [ ! -h "${sitesRootDir}/${alias}" ]; then echo "No symlink alias '${alias}' exists in sites directory '${sitesRootDir}'." return fi # remove links rm -f "${groupRootDir}/${alias}" rm -f "${sitesRootDir}/${alias}" } # Outputs a list of alias symlinks for an install in the group root dir. # All aliases are validated. # $1 - intall name for which to return aliases # $2 - the install group root dir getInstallAliases() { installName="${1}" groupRootDir="${2}" /bin/ls -F "${groupRootDir}/" | grep '@$' | while read symlinkname; do alias="`basename "${symlinkname}" | sed 's/@$//'`" holdInstallName="`getInstallNameForAlias "${alias}" "${groupRootDir}"`" if [ "${holdInstallName}" == "${installName}" ]; then echo "${alias}" fi done } # Validates a site alias link. # Ensures that the given alias is a link and points to a valid site # see validateSite() below # Returns error message (or nothing if things are fine) # $1 - the alias # $2 - the install group root dir validateAlias() { alias="${1}" groupRootDir="${2}" installName="`getInstallNameForAlias "${alias}" "${groupRootDir}"`" if [ -z "${installName}" ]; then echo "Could not get installation from alias symlink '${alias}'." return fi # Now call the validateSite() function to do the rest of the work. validateSite "${installName}" "${groupRootDir}" } ### #### Functions dealing with particular site Drupal installs ### # Validates a site installation directory. # Ensures that a site install is complete. # Returns error message (or nothing if things are fine) # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. validateSite() { installName="${1}" groupRootDir="${2}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi if [ -z "${installName}" ]; then echo "Site install name not provied" return fi # This can happen sometimes w/ sloppy parameters. # It can cause problems in some places, so don't allow it. if [ -n "`echo ${installName} | grep "/"`" ]; then echo "Site name includes a slash ('/')" return fi targetDir="`getTargetDir "${installName}" "${groupRootDir}"`" siteDir="`getSiteDir "${installName}" "${groupRootDir}"`" if [ ! -d "${targetDir}" ]; then echo "Site install directory '${targetDir}' does not exist" return fi if [ ! -f "${siteDir}/settings.php" ]; then echo "Site settings file '${siteDir}/settings.php' does not exist" return fi } # Generates a list of non-contrib modules # These are modules in the site's modules directory # Returned as list, one per line # $1 - the install name # $2 - the install group root dir getNonContribModuleList() { installName="${1}" groupRootDir="${2}" moduleDir="`getModuleDir "${installName}" "${groupRootDir}"`" if [ -d "${moduleDir}" ]; then pushd "${moduleDir}" 2>&1 >/dev/null # Get .module files in the current dir /bin/ls *.module 2>/dev/null | sed 's/\.module$//' # ...and directories (which we'll assume to be module packages) find . -maxdepth 1 -type d | grep -v '^\.$' | sed 's/^\.\///' | grep -v "${CONTRIB_SUBDIR}" popd 2>&1 >/dev/null fi } # Generates a list of installed contrib modules # Returned as list, one per line # $1 - the install name # $2 - the install group root dir getInstalledContribModuleList() { installName="${1}" groupRootDir="${2}" moduleDir="`getContribModuleDir "${installName}" "${groupRootDir}"`" if [ -d "${moduleDir}" ]; then /bin/ls "${moduleDir}" | sed 's/\///g' fi } # Generates a list of non-contrib theme # These are modules in the site's themes directory # Returned as list, one per line # $1 - the install name # $2 - the install group root dir getNonContribThemeList() { installName="${1}" groupRootDir="${2}" themeDir="`getThemeDir "${installName}" "${groupRootDir}"`" if [ -d "${themeDir}" ]; then /bin/ls "${themeDir}" | grep -v "${CONTRIB_SUBDIR}" | sed 's/\///g' | grep -v "${CONTRIB_SUBDIR}" fi } # Generates a list of installed contrib themes # Returned as list, one per line # $1 - the install name # $2 - the install group root dir getInstalledContribThemeList() { installName="${1}" groupRootDir="${2}" themeDir="`getContribThemeDir "${installName}" "${groupRootDir}"`" if [ -d "${themeDir}" ]; then /bin/ls "${themeDir}" | sed 's/\///g' fi } # Returns a list of all Drupal core files that need updating. # $1 - the install name # $2 - the install group root dir findDrupalUpdates() { installName="${1}" groupRootDir="${2}" targetDir="`getTargetDir "${installName}" "${groupRootDir}"`" findRepositoryUpdates "${targetDir}" } # Returns a list of files that need updating for a particular contrib module. # $1 - the install name # $2 - the install group root dir # $3 - module to check findContribModuleUpdates() { installName="${1}" groupRootDir="${2}" module="${3}" targetDir="`getDirForContribModule "${installName}" "${groupRootDir}" "${module}"`" findRepositoryUpdates "${targetDir}" } # Returns a list of files that need updating for a particular contrib theme. # $1 - the install name # $2 - the install group root dir # $3 - theme to check findContribThemeUpdates() { installName="${1}" groupRootDir="${2}" theme="${3}" targetDir="`getDirForContribTheme "${installName}" "${groupRootDir}" "${theme}"`" findRepositoryUpdates "${targetDir}" } ### #### Functions to parse the settings file for database connection parameters ### # Gets the database host server from a site's settings file # $1 - the install name # $2 - the install group root dir getDBHost() { installName="${1}" groupRootDir="${2}" getSettingsFileDBInfo "${installName}" "${groupRootDir}" 3 } # Gets the database name from a site's settings file # $1 - the install name # $2 - the install group root dir getDBName() { installName="${1}" groupRootDir="${2}" getSettingsFileDBInfo "${installName}" "${groupRootDir}" 4 } # Gets the database db username from a site's settings file # $1 - the install name # $2 - the install group root dir getDBUsername() { installName="${1}" groupRootDir="${2}" getSettingsFileDBInfo "${installName}" "${groupRootDir}" 1 } # Gets the database db password from a site's settings file # $1 - the install name # $2 - the install group root dir getDBPassword() { installName="${1}" groupRootDir="${2}" getSettingsFileDBInfo "${installName}" "${groupRootDir}" 2 } # Does the actual data collection for the getDBHost(), getDBName(), getDBUsername(), and getDBPassword() functions above # See setSettingsFileDBInfo() # $1 - the install name # $2 - the install group root dir # $3 - token number -- 1-db username, 2-password, 3-host, 4-db name getSettingsFileDBInfo() { installName="${1}" groupRootDir="${2}" tokenNumber="${3}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi # In the regex used by by sed, below, the token number is used as the replacement token. settingsFile=`getSiteSettingsFilename "${installName}" "${groupRootDir}"` if [ ! -f "${settingsFile}" ]; then return fi majorVer="`getSiteDrupalMajorVersion "${installName}" "${groupRootDir}"`" case "${majorVer}" in [4-6]) # The db storage parameters are stored in a 'db_url' variable # Format is: # $db_url = 'mysql://USERNAME:PASSWORD@DBHOST/DBNAME'; cat "${settingsFile}" | grep '^\$db_url' | sed "s|^\$db_url \?= \?'mysqli\?:\/\/\([a-zA-Z0-9_-]*\):\([a-zA-Z0-9_!-]*\)@\([a-zA-Z0-9.-]*\)/\([a-zA-Z0-9_-]*\)';$|\\${tokenNumber}|" ;; [7-8]) awkD7SettingsFileDBInfo "${settingsFile}" "${tokenNumber}" ;; esac } # Gets a database authorization string for a site # Returned in form "-u username --password=password -h host" # See also getDBName() and getDBConnectionString() # $1 - the install name # $2 - the install group root dir getDBAuthString() { installName="${1}" groupRootDir="${2}" dbHost="`getDBHost "${installName}" "${groupRootDir}"`" username="`getDBUsername "${installName}" "${groupRootDir}"`" password="`getDBPassword "${installName}" "${groupRootDir}"`" getMySQLAuthString "${dbHost}" "${username}" "${password}" } # Gets the full database connection string for a site # This is the auth string (see above) plus the host name. # $1 - the install name # $2 - the install group root dir getDBConnectionString() { installName="${1}" groupRootDir="${2}" dbHost="`getDBHost "${installName}" "${groupRootDir}"`" dbName="`getDBName "${installName}" "${groupRootDir}"`" username="`getDBUsername "${installName}" "${groupRootDir}"`" password="`getDBPassword "${installName}" "${groupRootDir}"`" getMySQLConnectionString "${dbHost}" "${dbName}" "${username}" "${password}" } # Sets the database host for a site # $1 - the install name # $2 - the install group root dir # $3 - the host value to set setDBHost() { installName="${1}" groupRootDir="${2}" valueToSet="${3}" setSettingsFileDBInfo "${installName}" "${groupRootDir}" 3 "${valueToSet}" } # Sets the database name for a site # $1 - the install name # $2 - the install group root dir # $3 - the database name value to set setDBName() { installName="${1}" groupRootDir="${2}" valueToSet="${3}" setSettingsFileDBInfo "${installName}" "${groupRootDir}" 4 "${valueToSet}" } # Sets the database db username for a site # $1 - the install name # $2 - the install group root dir # $3 - the username value to set setDBUsername() { installName="${1}" groupRootDir="${2}" valueToSet="${3}" setSettingsFileDBInfo "${installName}" "${groupRootDir}" 1 "${valueToSet}" } # Sets the database db password for a site # $1 - the install name # $2 - the install group root dir # $3 - the password value to set setDBPassword() { installName="${1}" groupRootDir="${2}" valueToSet="${3}" setSettingsFileDBInfo "${installName}" "${groupRootDir}" 2 "${valueToSet}" } # Does the actual data setting for the setDBUsername(), setDBPassword(), setDBHost(), and setDBName() functions above # See getSettingsFileDBInfo() # $1 - the install name # $2 - the install group root dir # $3 - token number -- 1-db username, 2-password, 3-host, 4-db name # $4 - the database name value to set setSettingsFileDBInfo() { installName="${1}" groupRootDir="${2}" tokenNumber="${3}" valueToSet="${4}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi settingsFile="`getSiteSettingsFilename "${installName}" "${groupRootDir}"`" if [ ! -f "${settingsFile}" ]; then return fi # Create a temp file which is a copy of the live one tmpFile="`mktemp`" /bin/cp -f "${settingsFile}" "${tmpFile}" majorVer="`getSiteDrupalMajorVersion "${installName}" "${groupRootDir}"`" case "${majorVer}" in [4-6]) # In the regex used by sed, below, the replacement tokens are: # 2-db username, 3-password, 4-host, 5-db name # (token 1 is the optional 'i' at the end of mysql in the db url) token1="\\2" token2="\\3" token3="\\4" token4="\\5" eval token${tokenNumber}=${valueToSet} # get the new value of the db_url variable... newLine="`cat "${settingsFile}" | grep '^\$db_url' | sed "s|^\\$db_url = 'mysql\(i\?\):\/\/\([a-zA-Z0-9_-]*\):\([a-zA-Z0-9_!-]*\)@\([a-zA-Z0-9.-]*\)/\([a-zA-Z0-9_-]*\)';$|\\$db_url = 'mysql\1://${token1}:${token2}@${token3}/${token4}';|" `" # Now create a new version of the settings file cat "${settingsFile}" | awk '/^\$db_url = '"'"'mysqli?:\/\// {print "'"$newLine"'"; next} {print $0}' > ${tmpFile} # just an editor fix:' ;; [7-8]) awkD7SettingsFileDBInfo "${settingsFile}" "${tokenNumber}" "${valueToSet}" > ${tmpFile} ;; esac # Overwrite settings file w/ new version /bin/mv -f "${tmpFile}" "${settingsFile}" chmod 0644 "${settingsFile}" } # An internal method to work with the Drupal-7+ configuration file's # format of the database configuration. D-7+ is a departure from earlier # versions in the way that database connection parameters are configured. # The awk script in this function will either return parameters from this # file or will generate a new file based on usage. # $1 - The settings file filename to parse # $2 - token number -- 1-db username, 2-password, 3-host, 4-db name # $3 - the database name value to set. (Only required if updating the file.) # If this value is empty, the value corresponding to the token will be retured. # If this value is not empty, the file will be updated to set the value # corresponding to the token updated with this value. awkD7SettingsFileDBInfo() { settingsFile="${1}" tokenNumber="${2}" valueToSet="${3}" # We'll look for a section defining the default database config # and parse it for the requested parameter after that. # The database properties should be specified in one of the # two following ways (and will only work if such a section exists): # $databases['default']['default'] = array( # 'driver' => 'mysql', # 'database' => 'databasename', # 'username' => 'username', # 'password' => 'password', # 'host' => 'localhost', # 'prefix' => 'main_', # 'collation' => 'utf8_general_ci', # ); # or # $databases = array ( # 'default' => # array ( # 'default' => # array ( # 'driver' => 'mysql', # 'database' => 'databasename', # 'username' => 'username', # 'password' => 'password', # 'host' => 'localhost', # 'prefix' => 'main_', # 'collation' => 'utf8_general_ci', # ), # ), # ); cat "${settingsFile}" | awk -v tokenNumber="${tokenNumber}" -v valueToSet="${valueToSet}" ' BEGIN { # inSection will be 1 (true) or 0 (false) depending on whether # the file read is in the database definition section as described above inSection = 0; FS="'"'"'" } # Start for the 1st format of the database config section. /^[ \t]*\$databases\['"'"'default'"'"'\]\['"'"'default'"'"'\] = array\([ \t]*$/ { inSection = 1; next; } # Start for the 2nd format of the database config section. /^[ \t]*\$databases = array \([ \t]*$/ { inSection = 1; } # For each pattern for the username/password/host/database-name # if the line is for the token for which we are looking # either (depending on the presence of the "valueToSet" parameter) # print it out and exit -or- echo a modified line. # Example line (this one for password assignment): # "password" => "password", # (except single quotes are used) # Token #4 of the line is the assigned value (since the field separator is a single quote). /^[ \t]*'"'"'username'"'"' => '"'"'.*'"'"',[ \t]*$/ { if (valueToSet) { # set value if (inSection) { if (tokenNumber == 1) { print $1"'"'"'username'"'"' => '"'"'"valueToSet"'"'"',"; } else { print $0; } } else { print $0; } next; } else { # get value if (inSection) { if (tokenNumber == 1) { print $4; nextfile; } } } } /^[ \t]*'"'"'password'"'"' => '"'"'.*'"'"',[ \t]*$/ { if (valueToSet) { # set value if (inSection) { if (tokenNumber == 2) { print $1"'"'"'password'"'"' => '"'"'"valueToSet"'"'"',"; } else { print $0; } } else { print $0; } next; } else { # get value if (inSection) { if (tokenNumber == 2) { print $4; nextfile; } } } } /^[ \t]*'"'"'host'"'"' => '"'"'.*'"'"',[ \t]*$/ { if (valueToSet) { # set value if (inSection) { if (tokenNumber == 3) { print $1"'"'"'host'"'"' => '"'"'"valueToSet"'"'"',"; } else { print $0; } } else { print $0; } next; } else { # get value if (inSection) { if (tokenNumber == 3) { print $4; nextfile; } } } } /^[ \t]*'"'"'database'"'"' => '"'"'.*'"'"',[ \t]*$/ { if (valueToSet) { # set value if (inSection) { if (tokenNumber == 4) { print $1"'"'"'database'"'"' => '"'"'"valueToSet"'"'"',"; } else { print $0; } } else { print $0; } next; } else { # get value if (inSection) { if (tokenNumber == 4) { print $4; nextfile; } } } } # If we are setting the value (and we are here), need to output this no-op line { if (valueToSet) { print $0; } } # End of a database definition section /^[ \t]*\);/ { inSection = 0; } ' } ### #### Functions to interact with the database connection for the site ### # Runs a connection test against a database specified in a site's settings file. # Returns error message (or nothing if connection was successful) # $1 - the install name # $2 - the install group root dir testDBConnectionForSite() { installName="${1}" groupRootDir="${2}" connString="`getDBConnectionString "${installName}" "${groupRootDir}"`" testDBConnection "${connString}" } # Runs an SQL query on a site database and returns result # $1 - the install name # $2 - the install group root dir # $3 - the query string runSQL() { installName="${1}" groupRootDir="${2}" queryString="${3}" connectionString="`getDBConnectionString "${installName}" "${groupRootDir}"`" runSQLQuery "${connectionString}" "${queryString}" } # Runs an SQL query on a site database and returns result # $1 - the install name # $2 - the install group root dir # $3 - the query string runSQLPretty() { installName="${1}" groupRootDir="${2}" queryString="${3}" connectionString="`getDBConnectionString "${installName}" "${groupRootDir}"`" runSQLQueryPretty "${connectionString}" "${queryString}" } ### #### Functions dealing with the creation of new Drupal DBs. #### Generates names, etc. ### # Generates a domain root from a domain name. # Removes common domain suffixes (.com, .org, etc.) # $1 - The domain name. getDomainRoot() { domain="$1" if [ -z "${domain}" ]; then return fi root="`echo "${domain}" | sed 's/\.org$//' | sed 's/\.com$//' | sed 's/\.net$//' | sed 's/\.info$//' | sed 's/\.//g'`" echo "${root}" } # Generates a db name from a domain root # $1 - The domain root (the output of getDomainRoot(), above) getGeneratedDBName() { domainRoot="$1" # Create and truncate result to max length result="drupal_${domainRoot}" result="`truncate "${result}" 64`" echo "${result}" } # Generates a db username from a domain root # $1 - The domain root (the output of getDomainRoot(), above) getGeneratedDBUser() { domainRoot="$1" # truncate root to max 11 characters # (so that we can add "_user" and still be <= 16 characters) domainRoot="`truncate "${domainRoot}" 11`" result="${domainRoot}_user" echo "${result}" } # Generates a db password from a domain root # $1 - The domain root (the output of getDomainRoot(), above) getGeneratedDBPass() { domainRoot="$1" # truncate root to max 11 characters # (to match username length) domainRoot="`truncate "${domainRoot}" 11`" result="${domainRoot}_pass" echo "${result}" } ### #### Functions that deal with Drupal functionality directly ### # Generates the default module stub. # Assumes the module directory exists. # $1 - the install name # $2 - the module directory # $3 - the drupal version, a single integer representing the drupal major version (e.g. '5'). generateSiteModule() { installName="${1}" moduleDir="${2}" majorVer="${3}" # Need to munge the filename because PHP # chokes if there are dots ('.') in function names # and the filename is the prefix for all function names in the module baseFilename="`echo ${installName} | sed 's/\./_/g'`" infoFilename="${baseFilename}.info" moduleFilename="${baseFilename}.module" cat > "${moduleDir}"/"${infoFilename}" < "${moduleDir}"/"${moduleFilename}" < getModuleDirHereDocument2 } # Returns the Drupal version number run by a site # Only works for drupal 5.x and up. # $1 - the install name # $2 - the install group root dir getSiteDrupalVersion() { installName="${1}" groupRootDir="${2}" targetDir="`getTargetDir "${installName}" "${groupRootDir}"`" # For D-7 installations -- version is in bootstrap.inc: ver="`cat "${targetDir}/includes/bootstrap.inc" | grep "^define('VERSION', '.*');$" | sed "s/^define('VERSION', '\(.*\)');$/\1/"`" # If not set, must be a D-6 or D-5 installation -- # version is in system.module if [ -z "${ver}" ]; then ver="`cat "${targetDir}/modules/system/system.module" | grep "^define('VERSION', '.*');$" | sed "s/^define('VERSION', '\(.*\)');$/\1/"`" fi echo "${ver}" } # Returns the Drupal major version. # E.g. a Drupal-5 install returns '5' # Works with getSiteDrupalVersion(), above # $1 - the install name # $2 - the install group root dir getSiteDrupalMajorVersion() { installName="${1}" groupRootDir="${2}" drupalVersion="`getSiteDrupalVersion "${installName}" "${groupRootDir}"`" echo "${drupalVersion}" | cut -d "." -f 1 } # Creates a Drupal 'serialized' string from a given string. # $1 - the string to make 'serialized' makeSerialized() { stringToSerialize="${1}" echo "s:"${#stringToSerialize}":\""${stringToSerialize}"\";" } # Calls the password-hash.sh function contained in Drupal-7+ installations # This generates a database-stored password for the site. # $1 - The password for which to generate a hash. # $2 - the install name # $3 - the install group root dir generateDrupal7Password() { password="${1}" installName="${2}" groupRootDir="${3}" # Call password hashing function passwordHashingScript="${groupRootDir}/${installName}/scripts/password-hash.sh" if [ -f "${passwordHashingScript}" ]; then phpExecutable="`getPHPExecutable`" if [ -n "${phpExecutable}" ]; then "${phpExecutable}" "${passwordHashingScript}" --root "${groupRootDir}/${installName}" "${password}" | grep -v '^$' | cut -d ' ' -f 4 fi fi } ### #### Functions for dealing with the Drupal Git repository ### # Creates a Git URL for a project name. # $1 - the project name makeGitURL() { projectName="${1}" echo "${DRUPAL_GIT_URL_PREFIX}${projectName}${DRUPAL_GIT_URL_SUFFIX}" } # Creates a Git URL for the drupal core project. makeDrupalGitURL() { echo "`makeGitURL "${DRUPAL_GIT_PROJECT_NAME}"`" } # A function to get the current Git branch (head) or tag # of the given directory under Git control. # $1 - The directory under git control to query. # $2 - Allows the tag/branch content to be modified with the following values of this paramter: # "label" - adds "branch " or "tag " before the result. # "onlylabel" - returns only "branch" or "tag" as appropriate. gitTag() { dir="${1}" modDesignator="${2}" cd "${dir}" || { echo "dir error!" return } # Returns "" or "tags/" # The trailing sed is to remove a "^0" that I've seen attached to the end of tags. ??? output="`git name-rev --name-only HEAD | sed 's/\^0.*$//'`" result="${output##tags/}" # prefix with a label designation? if [ "${modDesignator}" == "label" ]; then if [ "${output}" == "${result}" ]; then echo -n "branch " else echo -n "tag " fi elif [ "${modDesignator}" == "onlylabel" ]; then if [ "${output}" == "${result}" ]; then echo -n "branch" else echo -n "tag" fi return fi echo "${result}" } # Finds updates in a particular directory. # Both pending updates and local modifications are returned. # Returns a list of files. # $1 - dir to examine findRepositoryUpdates() { dir="${1}" # Updates can only exist when working on a branch (tags have no updates). gitTagWithLabel="`gitTag "${dir}" "label"`" gitTagLabel="`echo "${gitTagWithLabel}" | cut -d ' ' -f 1`" gitTag="`echo "${gitTagWithLabel}" | cut -d ' ' -f 2`" if [ "${gitTagLabel}" == "branch" ]; then # Working with a branch pushd "${dir}" 2>&1 >/dev/null && git fetch -q && git log --name-status --oneline origin/${gitTag}... && popd 2>&1 >/dev/null else # Working with a tag # Only pick up modifications pushd "${dir}" 2>&1 >/dev/null && popd 2>&1 >/dev/null fi # TODO Git need to ensure this catches modifications as well } # Determines the latest Git tag associated with the drupal project itself. # Finds all tags with the supplied major version and returns the one with # the greatest minor version. # $1 - Drupal major version to query (e.g. "6") getLatestDrupalTag() { majorVer="${1}" getDrupalVersionTags "${DRUPAL_GIT_PROJECT_NAME}" "${majorVer}" | grep '^T' | # Only select tags cut -b 2- | # strip off the branch/tag designator sort -n -t "." -k 2 | # sort numerically on the token after the dot tail -1 # get last record (the greatest) } # Retrieves the greatest branch (heads) or tag associated with the given project. # $1 - name of the drupal project (module or theme) to query # $2 - Drupal major version to query (e.g. "6") # $3 - optional, "branch" or "tag" to specify specify a branch (head) or tag; # if not specified, returns greatest branch if it exists, otherwise returns greatest tag getLatestContribTag() { project="${1}" majorVer="${2}" branchOrTag="${3}" case "${branchOrTag}" in branch ) # Return last branch getContribTags "${project}" "${majorVer}" branch | tail -1 ;; tag ) # Return last tag getContribTags "${project}" "${majorVer}" tag | tail -1 ;; * ) # Return last branch if it exists latestBranch="`getContribTags "${project}" "${majorVer}" branch | tail -1`" if [ -n "${latestBranch}" ]; then echo "${latestBranch}" else # get latest tag getContribTags "${project}" "${majorVer}" tag | tail -1 fi esac } # Retrieves branchs (heads) or tags associated with the given contributed project. # Tags are retrieved in order. # $1 - name of the drupal project (module or theme) to query # $2 - Drupal major version to query (e.g. "6") # $3 - "branch" or "tag" to specify branches (head) or tags getContribTags() { project="${1}" majorVer="${2}" branchOrTag="${3}" case "${branchOrTag}" in branch ) # Return last branch tagOrBranch=B ;; tag ) # Return last tag tagOrBranch=T ;; * ) return esac # Sorting is done such that # 6.x-1.9 sorts before # 6.x-1.10 and both sort before # 6.x-2.1 getDrupalVersionTags "${project}" "${majorVer}" | grep "^${tagOrBranch}" | # Only select branches/tags cut -b 2- | # strip off the branch/tag designator sed 's/x-/x./' | # Convert the first dash (after the Drupal version) to a dot so we can use dot as a field separator sort -t '.' -k3,3n -k4,4n | # sort as described above sed 's/x\./x-/' # Conert the dot back to a dash... } # Retrieves all Git tags and branches (heads) from the Drupal repo for a particular project. # In the resulting list, tags are prefixed with a 'T' and branches(heads) are prefixed with a 'B'. # Items are returned in sorted (non-numerical) order, branches first, then tags # $1 - project name (e.g. "drupal", "cck") # $2 - Drupal major version to query (e.g. "6") getDrupalVersionTags() { project="${1}" majorVer="${2}" # Run all the branches and tags from the git ls-remote call # thru this neato awk script to add the B/T prefix. git ls-remote "`makeGitURL "${project}"`" | cut -f 2 | awk ' BEGIN { FS="/" } /^refs\/heads\/'"${majorVer}"'/ { # One way a branch is listed. print "B"$3; } /^refs\/remotes\/origin\/'"${majorVer}"'/ { # A remote branch. print "B"$4; } /^refs\/tags\/'"${majorVer}"'/ { print "T"$3; } ' | grep -v '{}' # For some reason, some tags come thru with this token ("{}"). Filter it out. } ### #### Functions that interact with sites over net using wget ### # Login to site as admin. # See loginToSite(), below. # $1 - the install name # $2 - the install group root dir. If not supplied, calls getGroupRootDir() with no parameters. loginToSiteAsAdmin() { installName="${1}" groupRootDir="${2}" if [ -z "${groupRootDir}" ]; then groupRootDir="`getGroupRootDir`" fi username="`runSQL "${installName}" "${groupRootDir}" "SELECT name FROM users WHERE uid = 1;"`" password="`getDBPassword "${installName}" "${groupRootDir}"`" loginToSite "${installName}" "${username}" "${password}" } # Login to site and return the cookies file that holds the wget session data. # Returns nothing if something goes wrong. # $1 - the install name # $2 - the username with which to login to the site # $3 - the password to use loginToSite() { installName="${1}" username="${2}" password="${3}" site="http://${installName}/" # quick check if [ -z "${username}" -o -z "${password}" ]; then return fi # Ensure wget is installed wgetBinary="`which wget 2>/dev/null`" if [ -z "${wgetBinary}" ]; then return fi cookiesFile="`mktemp`" cookiesParams="--load-cookies ${cookiesFile} --save-cookies ${cookiesFile} --keep-session-cookies" "${wgetBinary}" -q -O /dev/null ${cookiesParams} "${site}user" "${wgetBinary}" -q -O /dev/null ${cookiesParams} --post-data="name=${username}&pass=${password}&op=Log%20in&form_id=user_login" "${site}user" echo "${cookiesFile}" } # Returns the contents of a page # Can optionally take a cookies file generated by loginToSite(), above. # Page is echoed to console. # $1 - the install name # $2 - the page to get, e.g. "node/5" # $3 - the cookies file from loginToSite() downloadPage() { installName="${1}" pageToRetrieve="${2}" cookiesFile="${3}" url="http://${installName}/${pageToRetrieve}" unset cookiesParams if [ -n "${cookiesFile}" -a -f "${cookiesFile}" ]; then cookiesParams="--load-cookies ${cookiesFile} --save-cookies ${cookiesFile} --keep-session-cookies" fi # Ensure wget is installed wgetBinary="`which wget 2>/dev/null`" if [ -z "${wgetBinary}" ]; then return fi "${wgetBinary}" -q -O - ${cookiesParams} "${url}" } ### #### Functions that use Drush and work with the local Drush installation ### # Returns the fully qualified Drush installation directory. # $1 - The directory in which the Drupal Scripts Library is installed. getDrushInstallDir() { drupalScriptLibraryDir="${1}" # Drush is installed under the Drupal Script Library directory echo "${drupalScriptLibraryDir}/${DRUSH_INSTALL_DIR}" } # Returns the fully-qualified Drush script filename. # $1 - The directory in which the Drupal Scripts Library is installed. getDrushScriptFile() { drupalScriptLibraryDir="${1}" # Drush file is w/in the Drush install dir drushInstallDir="`getDrushInstallDir "${drupalScriptLibraryDir}"`" echo "${drushInstallDir}/drush.php" } # Returns the location of the PHP executable # Script will look for PHP_HOME variable first, then look for php executable in path. # Returns nothing if nothing found. getPHPExecutable() { if [[ -n "${PHP_HOME}" && -d "${PHP_HOME}" && -x "${PHP_HOME}/bin/php" ]]; then echo "${PHP_HOME}/bin/php" elif [ -x "`which php 2>/dev/null`" ]; then which php 2>/dev/null fi } # Validates the Drush installation dir # Returns nothing if no problems, error string if there is. # $1 - The directory in which the Drupal Scripts Library is installed. validateDrushInstallation() { drupalScriptLibraryDir="${1}" # Where we should find Drush drushInstallDir="`getDrushInstallDir "${drupalScriptLibraryDir}"`" drushScript="`getDrushScriptFile "${drupalScriptLibraryDir}"`" # Check drush directory if [ ! -d "${drushInstallDir}" ]; then echo "Drush install directory '${drushInstallDir}' not found." return fi # Check drush script file if [ ! -f "${drushScript}" ]; then echo "Drush installation missing drush.php script." return fi # Get PHP executable location phpExecutable="`getPHPExecutable`" if [ -z "${phpExecutable}" ]; then echo "PHP executable not found. Please set PHP_HOME or add php bin dir to your PATH." return fi # Check drush if [ -z "`"${phpExecutable}" "${drushScript}" help 2>&1 | grep "Execute a drush command"`" ]; then echo "Drush not executing correctly." return fi } # Executes a Drush command # Runs validation first, will not attempt to execute Drush if validation fails. # $1 - Drupal Script Library installation directory # $2 - the install name # $3 - the install group root dir. # $4... - Drush parameters executeDrushCommand() { drupalScriptLibraryDir="${1}" installName="${2}" groupRootDir="${3}" shift; shift; shift # run validation if [ -n "`validateDrushInstallation "${drupalScriptLibraryDir}"`" ]; then return fi phpExecutable="`getPHPExecutable`" drushScript="`getDrushScriptFile "${drupalScriptLibraryDir}"`" siteroot="`getTargetDir "${installName}" "${groupRootDir}"`" # Run Drush "${phpExecutable}" "${drushScript}" --php="${phpExecutable}" -r "${siteroot}" -l "http://${installName}/" $* }