Skip to content

Instantly share code, notes, and snippets.

@hbt
Created January 24, 2018 23:47
Show Gist options
  • Save hbt/01154f70baf63bd1c80620d3b8c2e921 to your computer and use it in GitHub Desktop.
Save hbt/01154f70baf63bd1c80620d3b8c2e921 to your computer and use it in GitHub Desktop.
#!/usr/bin/env php
<?php
# install helpers in bin
# Usage: dpm-install-helpers
include(dirname(__FILE__) . '/../../src/php/lib/lib.php');
$loader = require dirname(__FILE__) . '/../../src/php' . '/vendor/vendor/autoload.php';
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Yaml;
use \Symfony\Component\Finder\SplFileInfo;
// TODO(hbt) ENHANCE get rid of $PWD in docker-compose files and mount it as volume when using run
// TODO(hbt) ENHANCE add check for volumes and prevent relative paths -- absolute only
// TODO(hbt) ENHANCE check for entrypoints - should use helpers instead
// TODO(hbt) ENHANCE add check for user namespace issues - asserts
function main($argv)
{
$getAllHelperFiles = function ()
{
$finder = new Finder();
$finder->files()->in(getDockerFilesLocation() . '*/helpers')->exclude(getExcludedDirs());
$files = fixFinderExcludedDirs($finder);
return $files;
};
$installHelper = function (\Symfony\Component\Finder\SplFileInfo $file)
{
$makeExecutable = function (\Symfony\Component\Finder\SplFileInfo $file)
{
if(!$file->isExecutable())
{
shell_exec('chmod +x ' . $file->getRealPath());
}
};
$createBinary = function (\Symfony\Component\Finder\SplFileInfo $file)
{
$getBinaryName = function (\Symfony\Component\Finder\SplFileInfo $file)
{
$name = basename($file->getRealPath());
$parts = explode('.', $name);
array_pop($parts);
$ret = implode('.', $parts);
return $ret;
};
$getTemplate = function (\Symfony\Component\Finder\SplFileInfo $file)
{
$isService = function (\Symfony\Component\Finder\SplFileInfo $file)
{
$ret = false;
$dcFile = dirname($file->getRealPath()) . '/../docker-compose.yml';
assert(file_exists($dcFile));
$yaml = Yaml::parse(file_get_contents($dcFile));
if(array_key_exists('x-custom', $yaml) && isset($yaml['x-custom']['service']))
{
$ret = $yaml['x-custom']['service'];
}
return $ret;
};
$getLoadedDockerComposeFilesString = function (\Symfony\Component\Finder\SplFileInfo $file) {
$folder = @array_pop(explode('/', realpath(dirname($file->getRealPath()) . '/../')));
$mainDCFile = "-f $(dirname $0)/../$folder/docker-compose.yml";
$ret[] = $mainDCFile;
$dcFile = getDockerComposeFileFromHelper($file);
$yaml = Yaml::parse(file_get_contents($dcFile));
if(array_key_exists('x-custom', $yaml) && isset($yaml['x-custom']['dc_files']))
{
$additionalDockerComposeFilePrefixes= $yaml['x-custom']['dc_files'];
foreach($additionalDockerComposeFilePrefixes as $additionalDockerComposeFilePrefix)
{
$additionalDockerComposeFile = realpath(dirname($dcFile)) . DIRECTORY_SEPARATOR . $additionalDockerComposeFilePrefix . '.docker-compose.yml';
assert(file_exists($additionalDockerComposeFile));
$ret[] = "-f $(dirname $0)/../$folder/$additionalDockerComposeFilePrefix.docker-compose.yml";
}
}
$ret = implode(' ', $ret);
return $ret;
};
$folder = @array_pop(explode('/', realpath(dirname($file->getRealPath()) . '/../')));
$realFilePath = $file->getRealPath();
$common = <<<EOD
#!/bin/bash
export PWD="\${USER_PWD:-`pwd`}"
export HOME="\${USER_HOME:-\$HOME}"
EOD;
$loadedDcFiles= $getLoadedDockerComposeFilesString($file);
$cmdTemplate = <<<EOD
$common
#docker-compose -f $(dirname $0)/../$folder/docker-compose.yml run --user \$UID:\$GID --rm --entrypoint $realFilePath $folder "$@"
docker-compose $loadedDcFiles run --rm --entrypoint $realFilePath $folder "$@"
EOD;
$serviceTemplate = <<<EOD
$common
docker-compose $loadedDcFiles up --force-recreate -d
EOD;
$ret = $cmdTemplate;
if($isService($file))
{
$ret = $serviceTemplate;
}
return $ret;
};
$writeFile = function ($filename, $content)
{
$binFilePath = realpath(dirname(__FILE__) . '/../../bin/') . DIRECTORY_SEPARATOR . $filename;
file_put_contents($binFilePath, $content);
assert(file_exists($binFilePath));
shell_exec('chmod +x ' . $binFilePath);
};
$str = $getTemplate($file);
$binName = $getBinaryName($file);
echo "\nInstalling - " . $binName;
$writeFile($binName, $str);
};
$makeExecutable($file);
$createBinary($file);
};
$createDataDirectories = function () {
$hasDataEnabled = function (\Symfony\Component\Finder\SplFileInfo $file) {
$yaml = Yaml::parse(file_get_contents($file->getRealPath()));
return isset($yaml['x-custom']) && isset($yaml['x-custom']['data']) && $yaml['x-custom']['data'];
};
$getImageName = function (\Symfony\Component\Finder\SplFileInfo $file) {
$yaml = Yaml::parse(file_get_contents($file->getRealPath()));
$folder = basename(dirname($file->getRealPath()));
assert(isset($yaml['services'][$folder]['image']));
return ($yaml['services'][$folder]['image']);
};
$ret = [];
$dcFiles = getAllDockerComposeFiles();
foreach($dcFiles as $dcFile)
{
if($hasDataEnabled($dcFile))
{
$folder= $getImageName($dcFile);
$fpath = "/home/user/.dpm/data/$folder";
if(!file_exists($fpath))
{
shell_exec('mkdir -p ' . $fpath);
assert(file_exists($fpath));
$ret[] = $fpath;
}
}
}
return $ret;
};
$cleanHelpers = function () {
$removeBinFiles = function () {
$finder = new Finder();
$finder->files()->in(getDockerFilesLocation() . 'bin');
$files = iterator_to_array($finder);
$excludeFilenames = ['dpm-install-helpers', 'README'];
/**
* @var SplFileInfo $file
*
*/
foreach($files as $file)
{
if(!in_array($file->getFilename(), $excludeFilenames))
{
unlink($file->getRealPath());
assert(!file_exists($file->getRealPath()));
}
}
};
$addReadme = function ()
{
$readme = getDockerFilesLocation() . 'bin/README';
$str = <<<EOD
WARNING: This directory is generated using dpm-install-helpers
Any content added/modified will be deleted
EOD;
file_put_contents($readme, $str);
};
$removeBinFiles();
$addReadme();
};
configAsserts();
$cleanHelpers();
$helpers = $getAllHelperFiles();
foreach($helpers as $helperFile)
{
assert(file_exists($helperFile->getRealPath()) === true);
assert(is_file($helperFile->getRealPath()));
$installHelper($helperFile);
}
$dataPaths = $createDataDirectories();
foreach($dataPaths as $dataPath)
{
echo "\nCreated data dir $dataPath";
}
}
main($argv);
?>
@hbt
Copy link
Author

hbt commented Jan 25, 2018

Here is a rundown:

The program follows the same pattern. The core of each function is at the bottom using plain language and logic. The anonymous functions hide all the business logic. If an anonymous function is needed more than once, it can be extracted as a regular function. This way, you don't pollute classes with one-time function calls with weird names.

With this pattern, even debugging is easy. I can focus on getting the function to work, drop in the program, pass the right values and I'm done.
No concerns about variables changing in the middle of the program. Return statements. Changing something at the top and breaking the bottom etc.
The code is isolated and abstracted.
Writing the code is also simpler. I focus on the bottom logic in plain english, almost like pseudo-code and figure out what the function should do. Then build each block in isolation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment