Last active
July 6, 2024 14:23
-
-
Save NickSdot/3b509895e1585fa6ba8f66ba534dd5c4 to your computer and use it in GitHub Desktop.
This file contains hidden or 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/env bash | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
# Stages Functions | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
BranchAware::RunCommandsInAllBranches() | |
{ | |
# clear Laravel bootstrap cache to avoid missing dependeny errors | |
find "$(pwd)/Framework/bootstrap/cache/" -name "*.php" -type f -delete | |
composer install | |
php artisan migrate | |
php artisan dusk:chrome-driver | |
# npm install | |
} | |
BranchAware::RunCommandsInNonExcludedBranches() | |
{ | |
if BranchAware::Helper::SkipWhenUndesiredBranch; then | |
return | |
fi | |
# add your commands here | |
} | |
BranchAware::SetBranch() | |
{ | |
BRANCH_NAME="$(git rev-parse --abbrev-ref HEAD)" | |
if [[ -z $BRANCH_NAME ]]; then | |
BranchAware::Exit 1 "⚠️ Error: Branch could not be detected." | |
fi | |
echo "🌿 BranchAware: Running for branch: ${BRANCH_NAME}" | |
export BRANCH_NAME | |
} | |
BranchAware::CreateAndPopulateDatabaseOrSkip() | |
{ | |
if BranchAware::Helper::SkipWhenUndesiredBranch; then | |
return | |
fi | |
local connection_data | |
local -r connections=$(php artisan app:deployer:connections) | |
while IFS= read -r connection_data; do | |
local keyValuePair | |
# Values we want to set | |
local db_name="" | |
local db_user="" | |
local db_password="" | |
local db_host="" | |
local db_port="" | |
# Set the database credentials to the local | |
# vars for processing. Before we used eval | |
# on the whole line. But this does not | |
# play well with empty passwords. | |
for keyValuePair in $connection_data; do | |
local key | |
local value | |
key=${keyValuePair%%=*} | |
value=${keyValuePair#*=} | |
eval "$key='$value'" | |
done | |
# If the database file exists, and the current database | |
# name is not included, we do not handle the database. | |
if BranchAware::Helper::SkipWhenUndesiredDatabase "${db_name}"; then | |
echo "🚫 Skipping database, because it's not desired: ${db_name}" | |
continue | |
fi | |
local source_db_name="" | |
source_db_name="$(BranchAware::Helper::GetSourceDatabaseNameFromBranchAndDatabaseName "${BRANCH_NAME}" "${db_name}")" | |
# If the database already exists, we skip the rest | |
# of the function, but keep the script running. | |
if mysql -e "use ${db_name}" 2> /dev/null; then | |
echo "🚫 Skipping database, because it already exists: ${db_name}" | |
continue | |
fi | |
# Check if the current user exists, otherwise we | |
# create it. Then we create the new database and | |
# grant the user access to it. | |
if ! mysql -e "SELECT 1 FROM mysql.user WHERE user = '${db_user}'" 2> /dev/null; then | |
mysql -e "CREATE USER '${db_user}'@'${db_host}' IDENTIFIED BY '${db_password}';" | |
fi | |
mysql -e "CREATE DATABASE ${db_name};" | |
mysql -e "GRANT ALL PRIVILEGES ON ${db_name}.* TO '${db_user}'@'${db_host}';" | |
mysql -e "FLUSH PRIVILEGES;" | |
mysqldump --no-tablespaces -u "${db_user}" --password="${db_password}" --host="${db_host}" --port="${db_port}" "${source_db_name}" \ | |
| mysql -u "${db_user}" --password="${db_password}" --host="${db_host}" --port="${db_port}" "${db_name}" | |
done <<< "$connections" | |
} | |
BranchAware::DropUnusedDatabases() | |
{ | |
local connection_data | |
local -r branches=$(git branch --format="%(refname:short)") | |
local -r connections=$(php artisan app:deployer:connections) | |
while IFS= read -r connection_data; do | |
# Set the database credentials to | |
# the local vars for processing. | |
local keyValuePair | |
# Values we want to set | |
local db_name="" | |
local db_user="" | |
local db_password="" | |
for keyValuePair in $connection_data; do | |
local key | |
local value | |
key=${keyValuePair%%=*} | |
value=${keyValuePair#*=} | |
# Only set vars that are already | |
# declared above. | |
if declare -p "$key" 2> /dev/null | grep -q '^declare \-'; then | |
eval "$key='$value'" | |
fi | |
done | |
local branch_name | |
for branch_name in $branches; do | |
local source_db | |
source_db="$(BranchAware::Helper::GetSourceDatabaseNameFromBranchAndDatabaseName "${branch_name}" "${db_name}")" | |
if [[ $source_db == "${db_name}" ]]; then | |
continue | |
fi | |
local databases | |
local database_name | |
local match_found | |
databases=$(mysql -u "${db_user}" --password="${db_password}" \ | |
-e "SHOW DATABASES LIKE '${source_db}\_%';" --skip-column-names 2> /dev/null) | |
for database_name in $databases; do | |
local existing_branch_name | |
local generated_branch_name | |
generated_branch_name="$(BranchAware::Helper::GetBranchNameFromDatabaseName "$database_name" "$source_db")" | |
# We are looping over all existing branches. | |
# if the generated branch name matches an | |
# existing branch name, the database is | |
# still in use. | |
match_found=0 | |
for existing_branch_name in $branches; do | |
if [[ "$existing_branch_name" == "$generated_branch_name" ]]; then | |
match_found=1 | |
break | |
fi | |
done | |
# If there was no match, the branch no longer | |
# exists, and we can delete the database | |
if [[ $match_found -eq 0 ]]; then | |
mysql -u "${db_user}" --password="${db_password}" -e "DROP DATABASE ${database_name};" 2> /dev/null | |
fi | |
done | |
done | |
done <<< "$connections" | |
} | |
BranchAware::Exit() | |
{ | |
local -r code="${1-0}" | |
local -r message="${2-""}" | |
if [[ -n $message && $code -eq 0 ]]; then | |
printf "\n\033[0;32m%s\033[0m\n\n" "$message" | |
fi | |
if [[ -n $message && $code -eq 1 ]]; then | |
printf "\n\033[0;31m%s\033[0m\n\n" "$message" | |
fi | |
exit "${code}" | |
} | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
# Helper Functions | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
BranchAware::Helper::GetSourceDatabaseNameFromBranchAndDatabaseName() | |
{ | |
local branch="${1:?"Branch name is not set."}" | |
local database="${2:?"Database name is not set."}" | |
local branchSuffix="_branch_${branch//[^a-zA-Z0-9]/_}" | |
echo -n "${database/$branchSuffix/}" | |
} | |
BranchAware::Helper::GetBranchNameFromDatabaseName() | |
{ | |
local database_name="${1:?"Database name is not set."}" | |
local source_database_name="${2:?"Source database name is not set."}" | |
local branch="${database_name//"${source_database_name}_branch_"/}" | |
echo -n "${branch//_/-}" | |
} | |
BranchAware::Helper::SkipWhenUndesiredBranch() | |
{ | |
if [[ "${BRANCH_NAME}" == "main" ]]; then | |
return 0 | |
fi | |
# We allow to skip creating the database by naming | |
# convention. When the branche name starts/ends | |
# with an underscore '_' the database handling | |
# is disabled. | |
if [[ "${BRANCH_NAME}" =~ ^_|_$ ]]; then | |
return 0 | |
fi | |
return 1 | |
} | |
BranchAware::Helper::SkipWhenUndesiredDatabase() | |
{ | |
local -r db_name="${1:?"Connection name is missing."}" | |
local -r database_file="./.deployer/local/databases" | |
local desired_database_name | |
if [[ ! -f $database_file ]]; then | |
return 1 | |
fi | |
while IFS= read -r desired_database_name; do | |
local matched=0 | |
if [[ $db_name == "$desired_database_name"* ]]; then | |
matched=1 | |
break | |
fi | |
done < "$database_file" | |
if [[ $matched -eq 1 ]]; then | |
return 1 | |
fi | |
return 0 | |
} | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
# Run Main Program | |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
BranchAware::Execute() | |
{ | |
BranchAware::SetBranch | |
BranchAware::CreateAndPopulateDatabaseOrSkip | |
BranchAware::RunCommandsInAllBranches | |
BranchAware::RunCommandsInNonExcludedBranches | |
BranchAware::DropUnusedDatabases | |
BranchAware::Exit 0 "🚀 BranchAware: All done." | |
} | |
BranchAware::Execute "$@" |
This file contains hidden or 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
<?php | |
declare(strict_types=1); | |
namespace Common; | |
use Business\MultiDomain\DetectEnvironment; | |
use function exec; | |
use function in_array; | |
use function str_ends_with; | |
use function str_replace; | |
use function trim; | |
class BranchAware | |
{ | |
public static function databaseName(string $environmentKey, string $default = null): string | |
{ | |
$defaultDatabaseName = getenv($environmentKey) ?: $default ?? throw new \InvalidArgumentException( | |
'No default database name provided.' | |
); // todo: custom exception | |
if (false === in_array(DetectEnvironment::fromGlobals(), ['local', 'dev'])) { | |
return $defaultDatabaseName; | |
} | |
if ('main' === $currentBranch = self::currentBranch()) { | |
return $defaultDatabaseName; | |
} | |
// By convention, we use a trailing underscore to indicate that the | |
// current branch will not use its own database. | |
if (true === str_starts_with($currentBranch, '_') || true === str_ends_with($currentBranch, '_')) { | |
return $defaultDatabaseName; | |
} | |
$currentBranchSanitised = str_replace('-', '_', $currentBranch); | |
return "{$defaultDatabaseName}_branch_{$currentBranchSanitised}"; | |
} | |
private static function currentBranch(): string | |
{ | |
return trim(exec('git rev-parse --abbrev-ref HEAD')); | |
} | |
} |
This file contains hidden or 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
<?php | |
declare(strict_types=1); | |
namespace Business\MultiDomain; | |
use Symfony\Component\Console\Input\ArgvInput; | |
readonly final class DetectEnvironment | |
{ | |
public static function fromGlobals(): string | |
{ | |
$environment = getenv('APP_ENV') ?: 'production'; | |
if (false === self::isRunningInConsole()) { | |
return $environment; | |
} | |
return (new ArgvInput())->getParameterOption('--env', null) ?? $environment; | |
} | |
private static function isRunningInConsole(): bool | |
{ | |
return false !== getenv('APP_RUNNING_IN_CONSOLE') || 'cli' === \PHP_SAPI || 'phpdbg' === \PHP_SAPI; | |
} | |
} |
This file contains hidden or 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
<?php | |
declare(strict_types=1); | |
namespace Console\Deployer; | |
use Business\MultiDomain\DetectEnvironment; | |
use Common\Result; | |
use Illuminate\Console\Command; | |
use Illuminate\Support\Facades\Config; | |
class GetDatabaseConnectionCredentials extends Command | |
{ | |
protected $signature = 'app:deployer:connections'; | |
protected $description = 'Returns the database connection credentials.'; | |
protected $help = 'The result can be used in a shell script.'; | |
public function handle(): int | |
{ | |
if (false === in_array(DetectEnvironment::fromGlobals(), [ 'local', 'dev' ])) { | |
return Result::Failure->value; | |
} | |
$databaseConfig = Config::get('database'); | |
// output connection credentials to be | |
// consumed by shell script. | |
foreach ($databaseConfig['connections'] as $connection) { | |
// note: each value is followed by an empty space | |
echo "db_name={$connection['database']} "; | |
echo "db_user={$connection['username']} "; | |
echo "db_host={$connection['host']} "; | |
echo "db_port={$connection['port']} "; | |
echo "db_password={$connection['password']} "; | |
echo PHP_EOL; | |
} | |
return Result::Success->value; | |
} | |
} |
A posteriori..
after our conversation I will bookmark this in case of locally raging shitstorms.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@NickSdot not in need of such a thing. But it is nice to see other people's problems and ways they try to tackle it.