Created
September 17, 2017 15:37
-
-
Save tmarsteel/7789e628ec52321c40908b61e97f6511 to your computer and use it in GitHub Desktop.
PHP Script for pull deployment from git scm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/php5.5-cli | |
<?php | |
/** | |
This script checks out a commit from a git-repository into a directory revision/$hash within the context | |
directory. Then creates the symbolic links defined in $links within that directory and swiches the symbolic | |
link $CURRENT_DEPLOYMENT_POINTER_LINK to that new directory. | |
*/ | |
// CONFIGURATION | |
// !! ALL PATHS MUST BE ABSOLUTE | |
# repository from which to checkout; authentication is supposed to happen using the ssh key of the target machine | |
$REPOSITORY_URL = "git@github.com/me/myrepo"; | |
# directory where all the deployments reside | |
# TRAILING SLASH | |
$CONTEXT_DIRECTORY = "/var/www/my-project"; | |
# the symbolic link that points to the currently deployed revision | |
$CURRENT_DEPLOYMENT_POINTER_LINK = "/var/www/my-project/html"; | |
# this file is used to block concurrent operations with this script | |
$LOCK_FILE_NAME = $CONTEXT_DIRECTORY . ".deployment-lock"; | |
# where a local copy of the repository is stored; you do NOT need to change this unless there is a conflic with file | |
# names; TRAILING SLASH! | |
$LOCAL_REPO_COPY_DIR = "/var/www/my-project/repository"; | |
# symlinks to create; key => link name within the deployment directory, value = target | |
$SYMLINKS = array( | |
"files" => "/var/www/my-project/shared/files" | |
); | |
// END OF CONFIGURATION | |
// the file handle to the lock file | |
$LOCK_HANDLE = null; | |
if (!defined("STDIN") || !defined("STDOUT") || !defined("STDERR")) { | |
exit("not in cli"); | |
} | |
if (version_compare(PHP_VERSION, '5.5') < 0) { | |
fwrite(STDERR, "[ERROR] Minimum PHP version is 5.5, using " . PHP_VERSION . PHP_EOL); | |
exit(1); | |
} | |
// assure file permissions | |
if (!file_exists($CONTEXT_DIRECTORY) || !is_dir($CONTEXT_DIRECTORY)) { | |
fwrite(STDERR, "[ERROR] Context directory " . $CONTEXT_DIRECTORY . " does not exist or is not a directory" . PHP_EOL); | |
exit(1); | |
} | |
assertReadable($CONTEXT_DIRECTORY); | |
assertWritable($CONTEXT_DIRECTORY); | |
if (file_exists($LOCAL_REPO_COPY_DIR)) { | |
assertReadable($LOCAL_REPO_COPY_DIR); | |
assertWritable($LOCAL_REPO_COPY_DIR); | |
} | |
if (file_exists($LOCK_FILE_NAME)) { | |
assertWritable($LOCK_FILE_NAME); | |
} | |
else { | |
assertWritable(dirname($LOCK_FILE_NAME)); | |
} | |
// assure git is installed | |
if (runCommand(["git", "--version"], false) !== 0) { | |
fwrite(STDERR, "[ERROR] git appears not to be installed (`git --version` returned a non-zero exit code)" . PHP_EOL); | |
exit(1); | |
} | |
// parse commands | |
$args = array_slice($GLOBALS["argv"], 1); | |
$selfName = $GLOBALS["argv"][0]; | |
if (count($args) == 0 || $args[0] == "--help") { | |
printHelpAndExit(); | |
} | |
$FORCE_UPDATE_LOCAL_COPY = false; | |
if ($args[0] == "-f" || $args[0] == "--force") { | |
$FORCE_UPDATE_LOCAL_COPY = true; | |
$args = array_slice($args, 1); | |
} | |
// determine the ref to pass to checkout | |
$CHECKOUT_REF = ""; | |
$CHECKOUT_REF_IS_COMMIT = false; | |
if ($args[0] == "commit") { | |
if (!isset($args[1])) { | |
fwrite(STDERR, "[ERROR] No commit hash given, see " . $selfName . " --help" . PHP_EOL); | |
exit(1); | |
} | |
$givenHash = $args[1]; | |
if (!preg_match("/^([0-9a-f]{7}|[0-9a-f]{40})$/", $givenHash)) { | |
fwrite(STDERR, "[ERROR] Invalid commit hash; hexadecimal with 7 or 32 characters needed" . PHP_EOL); | |
exit(1); | |
} | |
$CHECKOUT_REF = $givenHash; | |
$CHECKOUT_REF_IS_COMMIT = true; | |
} | |
else if ($args[0] == "tag") { | |
if (!isset($args[1])) { | |
fwrite(STDERR, "[ERROR] No tag name given, see " . $selfName . " --help" . PHP_EOL); | |
exit(1); | |
} | |
$CHECKOUT_REF = "tags/" . $args[1]; | |
} | |
else if ($args[0] == "branch") { | |
if (!isset($args[1])) { | |
fwrite(STDERR, "[ERROR] No branch name given, see " . $selfName . " --help" . PHP_EOL); | |
exit(1); | |
} | |
$CHECKOUT_REF = $args[1]; | |
} | |
else { | |
fwrite(STDERR, "[ERROR] Unknown checkout type " . $args[0] . ", see " . $selfName . " --help" . PHP_EOL); | |
exit(1); | |
} | |
// assure the repository is present | |
if (!file_exists($LOCAL_REPO_COPY_DIR)) { | |
if (!mkdir($LOCAL_REPO_COPY_DIR, 0750)) { | |
fwrite(STDOUT, "[ERROR] Failed to create directory " . $LOCAL_REPO_COPY_DIR . " with permissions rwxr-x---" . PHP_EOL); | |
exit(); | |
} | |
chdir($LOCAL_REPO_COPY_DIR); | |
runCommand(["git", "init"]); | |
runCommand(["git", "remote", "add", "origin", $REPOSITORY_URL]); | |
} | |
acquireLockOrQuit(); | |
// make sure the "origin" remote points to the correct URL | |
chdir($LOCAL_REPO_COPY_DIR); | |
runCommand(["git", "remote", "set-url", "origin", $REPOSITORY_URL]); | |
// update repo | |
fwrite(STDOUT, "[INFO] updating local repository" . PHP_EOL); | |
runCommand(["git", "fetch", "origin"]); | |
// determine the target commit | |
$gitOutput = []; | |
$verifyRef = $CHECKOUT_REF_IS_COMMIT? $CHECKOUT_REF : "remotes/origin/" . $CHECKOUT_REF; | |
runCommand(["git", "rev-parse", "--verify", $verifyRef], true, $gitOutput); | |
$TARGET_COMMIT = isset($gitOutput[0]) ? trim($gitOutput[0]) : ""; | |
if (!preg_match("/^[0-9a-fA-F]{40}$/", $TARGET_COMMIT)) { | |
fwrite(STDOUT, "[ERROR] Failed to resolve ref " . $CHECKOUT_REF . " to a commit hash"); | |
exit(1); | |
} | |
fwrite(STDOUT, "[INFO] Ref " . $CHECKOUT_REF . " is commit " . $TARGET_COMMIT . PHP_EOL); | |
$TARGET_DIRECTORY = $CONTEXT_DIRECTORY . "revisions/" . $TARGET_COMMIT . "/"; | |
$copyCommitContentsToTargetDirectory = $FORCE_UPDATE_LOCAL_COPY; | |
// assure target exists and has permissions | |
if (file_exists($TARGET_DIRECTORY)) { | |
if (!is_dir($TARGET_DIRECTORY)) { | |
fwrite(STDERR, "[ERROR] " . $TARGET_DIRECTORY . " exists and is not a directory" . PHP_EOL); | |
exit(1); | |
} | |
assertWritable($TARGET_DIRECTORY); | |
if ($FORCE_UPDATE_LOCAL_COPY) { | |
$copyCommitContentsToTargetDirectory = true; | |
} | |
else { | |
fwrite(STDOUT, "[INFO] Commit " . $TARGET_COMMIT . " already checked out -- reusing" . PHP_EOL); | |
} | |
} | |
else { | |
if (!mkdir($TARGET_DIRECTORY, 0705, true)) { | |
fwrite(STDERR, "[ERROR] Failed to create directory " . $TARGET_DIRECTORY . " with permissions rwx---r-x" . PHP_EOL); | |
exit(1); | |
} | |
$copyCommitContentsToTargetDirectory = true; | |
} | |
// assure the hash directory is filled with content | |
if ($copyCommitContentsToTargetDirectory) { | |
// fetch stuff from remote | |
fwrite(STDOUT, "[INFO] Checking out commit " . $TARGET_COMMIT . " from origin" . PHP_EOL); | |
runCommand(["git", "checkout", "--force", $TARGET_COMMIT]); | |
runCommand(["git", "reset", "--hard"]); | |
fwrite(STDOUT, "[INFO] Copying commit contents over to " . $TARGET_DIRECTORY . PHP_EOL); | |
// copy stuff over | |
runCommand(["cp", "-R", $LOCAL_REPO_COPY_DIR . ".", $TARGET_DIRECTORY]); | |
// remove git data from target directory | |
runCommand(["rm", "-rf", $TARGET_DIRECTORY . ".git"]); | |
// create all the symlinks | |
fwrite(STDOUT, "[INFO] Creating symlinks in " . $TARGET_DIRECTORY . PHP_EOL); | |
foreach ($SYMLINKS as $linkName => $target) { | |
runCommand(["ln", "-sf", $target, $TARGET_DIRECTORY . $linkName]); | |
fwrite(STDOUT, "[INFO] Link " . $linkName . " -> " . $target . " created" . PHP_EOL); | |
} | |
} | |
fwrite(STDOUT, "[INFO] Switching symlink " . $CURRENT_DEPLOYMENT_POINTER_LINK . " to " . $TARGET_DIRECTORY . PHP_EOL); | |
// switch the link | |
chdir(dirname($CURRENT_DEPLOYMENT_POINTER_LINK)); | |
if (file_exists($CURRENT_DEPLOYMENT_POINTER_LINK)) { | |
runCommand(["unlink", basename($CURRENT_DEPLOYMENT_POINTER_LINK)]); | |
} | |
runCommand(["ln", "-s", rtrim($TARGET_DIRECTORY, "/"), basename($CURRENT_DEPLOYMENT_POINTER_LINK)]); | |
fwrite(STDOUT, "[INFO] Deployment of " . $CHECKOUT_REF . " complete" . PHP_EOL); | |
releaseLock(); | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// FUNCTIONS | |
function printHelpAndExit() { | |
global $selfName; | |
fwrite(STDOUT, "----- DEPLOYMENT SCRIPT ------" . PHP_EOL); | |
fwrite(STDOUT, "Usage: " . $selfName . " [-f|--force] (commit|branch|tag) git-revision" . PHP_EOL); | |
fwrite(STDOUT, " " . $selfName . " commit 7fc914c" . PHP_EOL); | |
fwrite(STDOUT, " " . $selfName . " branch my-branch" . PHP_EOL); | |
fwrite(STDOUT, " " . $selfName . " tag v1.2" . PHP_EOL); | |
fwrite(STDOUT, "If the given commit has already been checked out it will be reused." . PHP_EOL); | |
fwrite(STDOUT, "Specify the --force flag to check out that commit again." . PHP_EOL); | |
exit(); | |
} | |
function runCommand(array $parts, $failOnNonZeroExitStatus = true, array &$output = null) { | |
$escaped = []; | |
foreach ($parts as $part) { | |
$escaped []= escapeshellarg($part); | |
} | |
$exitStatus = 0; | |
$command = implode(" ", $escaped); | |
// fwrite(STDOUT, "[DEBUG] Running \"" . $command . "\" in " . getcwd() . PHP_EOL); | |
exec($command, $output, $exitStatus); | |
if ($failOnNonZeroExitStatus && $exitStatus != 0) { | |
fwrite(STDERR, "[ERROR] Command \"" . $command . "\" failed with status " . $exitStatus . PHP_EOL); | |
exit($exitStatus); | |
} | |
return $exitStatus; | |
} | |
function assertWritable($dir) { | |
if (!is_writeable($dir)) { | |
fwrite(STDERR, "[ERROR] Missing write permissions on " . $dir . PHP_EOL); | |
exit(1); | |
} | |
} | |
function assertReadable($dir) { | |
if (!is_readable($dir)) { | |
fwrite(STDERR, "[ERROR] Missing read permissions on " . $dir . PHP_EOL); | |
exit(1); | |
} | |
} | |
function acquireLockOrQuit() { | |
global $LOCK_FILE_NAME, $LOCK_HANDLE; | |
$LOCK_HANDLE = fopen($LOCK_FILE_NAME, "w"); | |
if ($LOCK_HANDLE === false) { | |
fwrite(STDERR, "[ERROR] Failed to acquire lock " . $LOCK_FILE_NAME . PHP_EOL); | |
exit(1); | |
} | |
} | |
function releaseLock() { | |
global $LOCK_FILE_NAME, $LOCK_HANDLE; | |
if ($LOCK_HANDLE != null) { | |
fclose($LOCK_HANDLE); | |
} | |
if (file_exists($LOCK_FILE_NAME)) { | |
unlink($LOCK_FILE_NAME); | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment