Created
January 24, 2018 23:47
-
-
Save hbt/01154f70baf63bd1c80620d3b8c2e921 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 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); | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a rundown:
https://gist.github.com/hbt/01154f70baf63bd1c80620d3b8c2e921#file-example-php-L223
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.
The same pattern repeats:
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.