├── VERSION ├── recipes ├── mongodb │ ├── Dockerfile │ └── link.sh ├── redis │ ├── Dockerfile │ └── link.sh ├── postgresql │ ├── Dockerfile │ └── link.sh ├── mysql │ ├── Dockerfile │ └── link.sh ├── rabbitmq │ ├── Dockerfile │ └── link.sh ├── base │ ├── travis.json │ └── Dockerfile └── php │ └── Dockerfile ├── .gitignore ├── CHANGELOG.md ├── src ├── Exception.php ├── Task │ ├── RunContainer.php │ ├── BuildRecipe.php │ ├── RunService.php │ ├── CreateRunScript.php │ └── CreateStartScript.php ├── Tasks.php ├── ConfigParser │ ├── RoboEnv.php │ └── Travis.php ├── Cleanup.php ├── Command │ ├── Travis │ │ ├── Build.php │ │ └── Prepare.php │ └── CI.php ├── Config.php └── Runner.php ├── composer.json ├── RoboFile.php ├── LICENSE ├── README.md └── composer.lock /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 -------------------------------------------------------------------------------- /recipes/mongodb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mongo -------------------------------------------------------------------------------- /recipes/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis -------------------------------------------------------------------------------- /recipes/postgresql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres -------------------------------------------------------------------------------- /recipes/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centurylink/mysql -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | .idea 4 | -------------------------------------------------------------------------------- /recipes/redis/link.sh: -------------------------------------------------------------------------------- 1 | socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:redis:6379 & -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | #### 0.1.0 4 | 5 | * initial *2014-09-27* 6 | -------------------------------------------------------------------------------- /recipes/mongodb/link.sh: -------------------------------------------------------------------------------- 1 | socat TCP4-LISTEN:27017,fork,reuseaddr TCP4:mongodb:27017 & -------------------------------------------------------------------------------- /recipes/rabbitmq/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frodenas/rabbitmq 2 | ENV RABBITMQ_USERNAME guest 3 | ENV RABBITMQ_PASSWORD guest -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | /etc/init.d/xvfb 3 | RUN chef-solo -o php::multi,composer -j travis.json 4 | RUN curl -s http://getcomposer.org/installer | php 5 | RUN mv composer.phar /usr/bin/composer 6 | USER travis 7 | ENV PATH $PATH:/home/travis/.phpenv/bin 8 | RUN ["/bin/bash", "-l", "-c", "eval \"$(phpenv init -)\""] 9 | RUN phpenv rehash 2>/dev/null -------------------------------------------------------------------------------- /src/Task/RunContainer.php: -------------------------------------------------------------------------------- 1 | option('link', $linkedService); 15 | } 16 | 17 | return $this; 18 | } 19 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codegyre/robo-ci", 3 | "description": "Travis CI cli runner", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Davert", 8 | "email": "davert.php@resend.cc" 9 | } 10 | ], 11 | "autoload":{ 12 | "psr-4":{ 13 | "Codegyre\\RoboCI\\":"src" 14 | } 15 | }, 16 | "require": { 17 | "php": ">=5.4.0", 18 | "symfony/yaml": "~2.0", 19 | "codegyre/robo-docker": "0.1.*", 20 | "codegyre/Robo": "0.4.*" 21 | } 22 | } -------------------------------------------------------------------------------- /src/Tasks.php: -------------------------------------------------------------------------------- 1 | > /etc/sudoers -------------------------------------------------------------------------------- /src/Task/BuildRecipe.php: -------------------------------------------------------------------------------- 1 | recipe = $recipe; 17 | $this->path = Config::getRecipeDir($recipe); 18 | } 19 | 20 | public function run() 21 | { 22 | if (!$this->path) { 23 | throw new TaskException($this, "Recipe for {$this->recipe} not found in ".Config::getUserRecipesDir()); 24 | } 25 | return parent::run(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/ConfigParser/RoboEnv.php: -------------------------------------------------------------------------------- 1 | config = Yaml::parse($file); 20 | } 21 | 22 | function services() 23 | { 24 | return isset($this->config['services']) ? $this->config['services'] : []; 25 | } 26 | } -------------------------------------------------------------------------------- /RoboFile.php: -------------------------------------------------------------------------------- 1 | taskChangelog() 14 | ->version($this->getVersion()) 15 | ->change($change) 16 | ->run(); 17 | } 18 | 19 | public function release() 20 | { 21 | $this->say("Releasing"); 22 | 23 | $this->taskGitStack() 24 | ->add('CHANGELOG.md') 25 | ->commit('updated') 26 | ->push() 27 | ->run(); 28 | 29 | $this->taskGitHubRelease($this->version()) 30 | ->uri('Codegyre/RoboCI') 31 | ->run(); 32 | } 33 | 34 | 35 | protected function getVersion() 36 | { 37 | return trim(file_get_contents('VERSION')); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Codegyre developers team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Task/RunService.php: -------------------------------------------------------------------------------- 1 | service = $service; 18 | } 19 | 20 | public function name($name) 21 | { 22 | $this->name = $name; 23 | return $this; 24 | } 25 | 26 | public function run() 27 | { 28 | $image = "roboci_service_".$this->service; 29 | $images = []; 30 | exec('docker images | grep '.$image, $images); 31 | 32 | if (empty($images)) { 33 | $this->printTaskInfo("Service {$this->service} does not exist, building..."); 34 | (new BuildRecipe($this->service)) 35 | ->tag($image) 36 | ->run(); 37 | } 38 | 39 | $this->printTaskInfo("Running {$this->service} service container"); 40 | return (new Run($image)) 41 | ->option('-d') 42 | ->name($this->name) 43 | ->run(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Cleanup.php: -------------------------------------------------------------------------------- 1 | printTaskInfo("Getting all robo_* containers"); 18 | $res = $this->taskExec('docker ps -a | grep robo_')->printed(false)->run(); 19 | if (!$res->wasSuccessful()) { 20 | $this->printTaskInfo("No containers matched"); 21 | return; 22 | } 23 | $containerLines = explode("\n", $res->getMessage()); 24 | foreach ($containerLines as $container) { 25 | $data = explode(' ', $container); 26 | $id = trim(reset($data)); 27 | if (!$id) continue; 28 | $this->containers[] = $id; 29 | } 30 | $this->printTaskInfo("Containers are: ".implode(', ', $this->containers).""); 31 | } 32 | 33 | public function stopContainers() 34 | { 35 | foreach ($this->containers as $container) { 36 | $this->taskDockerStop($container)->run(); 37 | } 38 | } 39 | 40 | public function removeContainers() 41 | { 42 | foreach ($this->containers as $container) { 43 | $this->taskDockerRemove($container)->run(); 44 | } 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /src/Task/CreateRunScript.php: -------------------------------------------------------------------------------- 1 | filename = $dir.DIRECTORY_SEPARATOR.Config::RUN_SCRIPT; 21 | } 22 | 23 | function script($script) 24 | { 25 | $this->body .= 'echo "---------------------------"'."\n"; 26 | $this->body .= 'echo "running '.str_replace('"', '', $script).'"' ."\n"; 27 | $this->body .= 'echo "---------------------------"'."\n"; 28 | $this->body .= $script ."\n"; 29 | return $this; 30 | } 31 | 32 | public function scripts(array $scripts) 33 | { 34 | foreach ($scripts as $script) { 35 | $this->script($script); 36 | } 37 | return $this; 38 | } 39 | 40 | public function run() 41 | { 42 | $this->printTaskInfo("Writing {$this->filename}"); 43 | $res = file_put_contents($this->filename, $this->body); 44 | if ($res === false) return Result::error($this, "File {$this->filename} couldnt be created"); 45 | return Result::success($this); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Task/CreateStartScript.php: -------------------------------------------------------------------------------- 1 | filename = $dir.DIRECTORY_SEPARATOR.Config::START_SCRIPT; 21 | } 22 | 23 | public function line($line) 24 | { 25 | $this->body .= $line ."\n"; 26 | return $this; 27 | } 28 | 29 | public function lines(array $lines) 30 | { 31 | $this->body .= implode("\n", $lines)."\n"; 32 | return $this; 33 | } 34 | 35 | public function comment($line) 36 | { 37 | return $this->line("# ".$line); 38 | } 39 | 40 | public function linkService($service) 41 | { 42 | $linkDir = Config::getRecipeDir($service); 43 | if (!file_exists($linkDir.DIRECTORY_SEPARATOR.Config::LINK_SCRIPT)) return $this; 44 | $this->comment("linking $service service"); 45 | $linkScript = file_get_contents($linkDir.DIRECTORY_SEPARATOR.Config::LINK_SCRIPT); 46 | return $this->line($linkScript); 47 | } 48 | 49 | public function run() 50 | { 51 | $this->printTaskInfo("Writing {$this->filename}"); 52 | $res = file_put_contents($this->filename, $this->body); 53 | if ($res === false) return Result::error($this, "File {$this->filename} couldnt be created"); 54 | return Result::success($this); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Command/Travis/Build.php: -------------------------------------------------------------------------------- 1 | null, 'recipes' => false, 'image' => false]) 18 | { 19 | if (!$opts['image']) $opts['image'] = Config::$baseImage; 20 | return (new Builder)->execute($opts); 21 | } 22 | 23 | } 24 | 25 | class Builder 26 | { 27 | use \Robo\Task\Exec; 28 | use \Robo\Task\FileSystem; 29 | use \Robo\Output; 30 | use \Codegyre\RoboDocker\DockerTasks; 31 | 32 | function execute($opts) 33 | { 34 | $answer = $this->ask("Bulding Docker container takes some time, you can use already provisioned image instead.\nDo you want to continue? (y/n)"); 35 | if (trim(strtolower($answer)) != 'y') return; 36 | 37 | Result::$stopOnFail = true; 38 | 39 | $recipes = $opts['recipes'] ?: implode(',', Config::$travisRecipes); 40 | 41 | (new BuildRecipe('base')) 42 | ->tag('roboci_base') 43 | ->run(); 44 | 45 | $res = (new DockerRun('base')) 46 | ->option('-i') 47 | ->option('--privileged') 48 | ->exec('/usr/bin/chef-solo -j /travis.json -o ' . $recipes) 49 | ->run(); 50 | 51 | $this->taskDeleteDir(Config::$buildDir)->run(); 52 | $data = $res->getData(); 53 | $this->yell("Container built in successfully. Run `docker commit {$data['cid']} yourname/imagename` to save it"); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | [ 67 | 'user' => 'travis', 68 | 'group' => 'travis', 69 | 'home' => '/home/travis/', 70 | 'update_hosts' => false, 71 | ] 72 | ]; 73 | 74 | static function getDefaultRecipesDir() 75 | { 76 | return __DIR__.'/../recipes'; 77 | } 78 | 79 | static function getUserRecipesDir() 80 | { 81 | return self::$runDir.'/_recipes'; 82 | } 83 | 84 | /** 85 | * @param $recipe 86 | * @return string $recipePath 87 | */ 88 | static function getRecipeDir($recipe) 89 | { 90 | $userRecipe = Config::getUserRecipesDir() . "/$recipe"; 91 | if (file_exists($userRecipe . '/Dockerfile')) { 92 | return $userRecipe; 93 | } 94 | 95 | $defaultRecipe = Config::getDefaultRecipesDir() . "/$recipe"; 96 | if (file_exists($defaultRecipe . '/Dockerfile')) { 97 | return $defaultRecipe; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/Runner.php: -------------------------------------------------------------------------------- 1 | env = $env; 24 | $this->envConfig = new RoboEnv($env); 25 | $this->build = uniqid(); 26 | $this->dir = Config::$runDir . "/$env/"; 27 | 28 | if (!file_exists($this->dir)) throw new Exception("Roboci environment directory {$this->dir} not prepared!"); 29 | } 30 | 31 | public function buildImage() 32 | { 33 | $preserveDockerFile = file_exists('Dockerfile'); 34 | $fs = $this->taskFileSystemStack(); 35 | if ($preserveDockerFile) { 36 | $fs->remove('Dockerfile.saved')->rename('Dockerfile', 'Dockerfile.saved'); 37 | } 38 | $fs->copy($this->dir . "Dockerfile", "Dockerfile") 39 | ->run(); 40 | 41 | // load start script in shell 42 | $this->taskWriteToFile('Dockerfile') 43 | ->append() 44 | ->line("RUN cat {$this->dir}".Config::START_SCRIPT." >> /home/travis/.bashrc") 45 | ->run(); 46 | 47 | $res = $this->taskDockerBuild() 48 | ->tag(Config::$runImage) 49 | ->run(); 50 | 51 | $fs = $this->taskFileSystemStack()->remove('Dockerfile'); 52 | if ($preserveDockerFile) { 53 | $fs->rename('Dockerfile.saved', 'Dockerfile'); 54 | } 55 | $fs->run(); 56 | return $res; 57 | } 58 | 59 | public function runServices() 60 | { 61 | foreach ($this->envConfig->services() as $service) { 62 | $container = 'robo_service_' . $service . $this->build; 63 | $this->services[$container.':'.$service] = $this->taskRunService($service) 64 | ->name($container) 65 | ->run(); 66 | } 67 | } 68 | 69 | /** 70 | * @return RunContainer 71 | */ 72 | public function getContainerRunner() 73 | { 74 | return $this->taskRunContainer(Config::$runImage) 75 | ->name('robo_build_'.$this->build) 76 | ->env('TRAVIS_BUILD_NUMBER', $this->build) 77 | ->linkServices(array_keys($this->services)) 78 | ->containerWorkdir(Config::$containerWorkDir) 79 | ->privileged(); 80 | } 81 | 82 | public function getRunCommand() 83 | { 84 | $start = $this->getStartCommand(); 85 | $command = ""; 86 | if (file_exists($this->dir.Config::RUN_SCRIPT)) { 87 | $command = '/bin/bash '.$this->dir.Config::RUN_SCRIPT; 88 | } 89 | if ($start) $command = "$start && $command"; 90 | return $command; 91 | } 92 | 93 | public function getStartCommand() 94 | { 95 | if (file_exists($this->dir.Config::START_SCRIPT)) { 96 | return '/bin/bash '.$this->dir.Config::START_SCRIPT; 97 | } 98 | return ""; 99 | } 100 | 101 | public function getServices() 102 | { 103 | return $this->services; 104 | } 105 | 106 | public function stopServices() 107 | { 108 | foreach ($this->services as $service) { 109 | if (!$service instanceof \Codegyre\RoboDocker\Result) continue; 110 | $this->taskDockerStop($service)->run(); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/ConfigParser/Travis.php: -------------------------------------------------------------------------------- 1 | config = Yaml::parse($file); 22 | 23 | if (!isset($this->config['php'])) { 24 | throw new ParseException("No PHP versions in this config, exiting"); 25 | } 26 | 27 | if (!isset($this->config['script'])) { 28 | throw new ParseException('No scripts defined in .travis.yml to run, exiting'); 29 | } 30 | } 31 | 32 | function install() 33 | { 34 | $beforeInstall = isset($this->config['before_install']) ? $this->config['before_install'] : []; 35 | $install = isset($this->config['install']) ? $this->config['install'] : []; 36 | return array_merge($beforeInstall, $install); 37 | } 38 | 39 | function services() 40 | { 41 | $services = isset($this->config['services']) ? $this->config['services'] : []; 42 | return array_merge(Config::$defaultServices, $services); 43 | } 44 | 45 | function beforeScript() 46 | { 47 | return isset($this->config['before_script']) ? $this->config['before_script'] : []; 48 | } 49 | 50 | function scripts() 51 | { 52 | return isset($this->config['script']) ? $this->config['script'] : []; 53 | } 54 | 55 | public function getConfig() 56 | { 57 | return $this->config; 58 | } 59 | 60 | /** 61 | * (PHP 5 >= 5.0.0)
62 | * Whether a offset exists 63 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 64 | * @param mixed $offset

65 | * An offset to check for. 66 | *

67 | * @return boolean true on success or false on failure. 68 | *

69 | *

70 | * The return value will be casted to boolean if non-boolean was returned. 71 | */ 72 | public function offsetExists($offset) 73 | { 74 | return isset($this->config[$offset]); 75 | } 76 | 77 | /** 78 | * (PHP 5 >= 5.0.0)
79 | * Offset to retrieve 80 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 81 | * @param mixed $offset

82 | * The offset to retrieve. 83 | *

84 | * @return mixed Can return all value types. 85 | */ 86 | public function offsetGet($offset) 87 | { 88 | return isset($this->config[$offset]) ? $this->config[$offset] : null; 89 | } 90 | 91 | /** 92 | * (PHP 5 >= 5.0.0)
93 | * Offset to set 94 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 95 | * @param mixed $offset

96 | * The offset to assign the value to. 97 | *

98 | * @param mixed $value

99 | * The value to set. 100 | *

101 | * @return void 102 | */ 103 | public function offsetSet($offset, $value) 104 | { 105 | // TODO: Implement offsetSet() method. 106 | } 107 | 108 | /** 109 | * (PHP 5 >= 5.0.0)
110 | * Offset to unset 111 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 112 | * @param mixed $offset

113 | * The offset to unset. 114 | *

115 | * @return void 116 | */ 117 | public function offsetUnset($offset) 118 | { 119 | // TODO: Implement offsetUnset() method. 120 | } 121 | } -------------------------------------------------------------------------------- /src/Command/CI.php: -------------------------------------------------------------------------------- 1 | buildImage(); 29 | $runner->runServices(); 30 | $res = $runner->getContainerRunner() 31 | ->exec($runner->getRunCommand()) 32 | ->args($args) 33 | ->run(); 34 | 35 | $runner->stopServices(); 36 | 37 | !$res->getExitCode() 38 | ? $this->yell('BUILD SUCCESSFUL') 39 | : $this->say(" BUILD FAILED "); 40 | 41 | $data = $res->getData(); 42 | $this->say("To enter this container, save it with docker commit {$data['cid']} travis_build_failed"); 43 | $this->say('Then you can run it: docker run -i -t travis_build_failed bash'); 44 | } 45 | 46 | /** 47 | * Runs interactive bash shell in RoboCI environment 48 | * @param $environment 49 | * @param string $args 50 | */ 51 | public function ciShell($environment, $args = '') 52 | { 53 | $runner = new Runner($environment); 54 | 55 | $res = $runner->buildImage(); 56 | if (!$res->wasSuccessful()) return 1; 57 | $runner->runServices(); 58 | 59 | $links = array_keys($runner->getServices()); 60 | $links = implode(' ', array_map(function($l) {return "--link $l";}, $links )); 61 | 62 | $command = (new DockerRun(Config::$runImage)) 63 | ->option('-t') 64 | ->option('-i') 65 | ->name('robo_shell_'.uniqid()) 66 | ->arg($links) 67 | ->arg($args) 68 | ->exec('bash') 69 | ->getCommand(); 70 | 71 | $this->yell("To enter shell run `$command`\n"); 72 | } 73 | 74 | /** 75 | * Creates raw RoboCI configuration (not taken from .travis.yml) 76 | */ 77 | public function ciBootstrap() 78 | { 79 | if (file_exists(Config::$runDir)) { 80 | $this->say(Config::$runDir . " exists, no need to bootstrap"); 81 | return; 82 | } 83 | @mkdir(Config::$runDir); 84 | 85 | while ($env = $this->ask("Create new build environment. Enter to exit")) { 86 | $dir = Config::$runDir."/$env/"; 87 | @mkdir($dir); 88 | touch($dir.'Dockerfile'); 89 | $this->say($dir."Dockerfile created -> Use it to configure build image"); 90 | touch($dir.'start.sh'); 91 | $this->say($dir."start.sh created. -> executed when container is started"); 92 | touch($dir.'run.sh'); 93 | $this->say($dir."run.sh created. -> executed on build"); 94 | file_put_contents($dir.'env.yml', Yaml::dump(['services' => []])); 95 | $this->say($dir."env.yml created. -> defines running service"); 96 | } 97 | 98 | $this->yell('RoboCI raw setup is prepared. See'.Config::$runDir); 99 | } 100 | 101 | /** 102 | * Stops and removes old RoboCI containers 103 | */ 104 | public function ciCleanup() 105 | { 106 | $cleaner = new Cleanup(); 107 | $cleaner->retrieveContainers(); 108 | $cleaner->stopContainers(); 109 | $cleaner->removeContainers(); 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/Command/Travis/Prepare.php: -------------------------------------------------------------------------------- 1 | createDockerFile(); 31 | $generator->createStartScript(); 32 | $generator->createRunScript(); 33 | $generator->createEnvConfig(); 34 | 35 | } 36 | $this->say("Make sure it is available when executing travis:run"); 37 | } 38 | 39 | } 40 | 41 | class EnvGenerator 42 | { 43 | use \Robo\Task\FileSystem; 44 | use \Robo\Output; 45 | use \Codegyre\RoboCI\Tasks; 46 | 47 | protected $php; 48 | 49 | /** 50 | * @var TravisConfig 51 | */ 52 | protected $config; 53 | 54 | public function __construct(TravisConfig $config, $php) 55 | { 56 | $this->php = $php; 57 | $this->config = $config; 58 | if (!file_exists(Config::$runDir."/$php")) { 59 | @mkdir(Config::$runDir."/$php"); 60 | } 61 | } 62 | 63 | public function createDockerFile() 64 | { 65 | $phpVersion = $this->php; 66 | // create Dockerfile 67 | $filename = Config::$runDir."/$phpVersion/Dockerfile"; 68 | 69 | $dockerFile = $this->taskWriteToFile($filename) 70 | ->line('FROM '.Config::$defaultImage) 71 | ->line('WORKDIR '.Config::$containerWorkDir) 72 | ->line('USER root') 73 | ->line('RUN phpenv global '.$phpVersion) 74 | ->line('RUN ["/bin/bash", "-l", "-c", "eval \"$(phpenv init -)\""]') 75 | ->line('ENV TRAVIS_PHP_VERSION '. $phpVersion) 76 | ->line('ENV TRAVIS true') 77 | ->line('ENV CONTINUOUS_INTEGRATION true') 78 | ->line('ENV TRAVIS_BUILD_DIR /home/travis/builds/current') 79 | ->line('ENV TRAVIS_BUILD_NUMBER 0') 80 | ->line('ENV TRAVIS_OS_NAME linux'); 81 | 82 | foreach ($this->config->install() as $command) { 83 | $command = str_replace('"', '\"', $command); 84 | $dockerFile->line('RUN ["/bin/bash", "-l", "-c", "'.$command.'"]'); 85 | } 86 | 87 | $dockerFile->line('ADD . '.Config::$containerWorkDir) 88 | ->line('USER root') 89 | ->line('RUN chown -R '.Config::TRAVIS_USER.' '.Config::$containerWorkDir) // https://github.com/docker/docker/issues/1295 90 | ->line('USER travis') 91 | ->line('ENV HOME /home/travis') 92 | ->line('ENV PATH $PATH:/home/travis/.phpenv/bin') 93 | ->run(); 94 | 95 | $this->say("Dockerfile for running Travis saved to $filename"); 96 | } 97 | 98 | public function createStartScript() 99 | { 100 | $php = $this->php; 101 | $startScript = $this->taskCreateStartScript(Config::$runDir."/$php") 102 | ->line('echo "------[ RUNNING START SCRIPT ]-----"') 103 | ->comment("switching PHP version") 104 | ->line('phpenv global '.$php) 105 | ->line('eval "$(phpenv init -)"') 106 | ->line('php -v'); 107 | 108 | foreach ($this->config->services() as $service) { 109 | $startScript->linkService($service); 110 | } 111 | 112 | $startScript 113 | ->comment("before_script section .travis.yml") 114 | ->lines($this->config->beforeScript()) 115 | ->run(); 116 | } 117 | 118 | public function createRunScript() 119 | { 120 | $this->taskCreateRunScript(Config::$runDir."/".$this->php) 121 | ->scripts($this->config->scripts()) 122 | ->run(); 123 | } 124 | 125 | public function createEnvConfig() 126 | { 127 | $this->taskWriteToFile(Config::$runDir."/".$this->php.DIRECTORY_SEPARATOR.Config::ROBOCI_ENV_CONFIG_FILE) 128 | ->line(Yaml::dump(['services' => $this->config->services()])) 129 | ->run(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RoboCI 2 | 3 | RoboCI is virtualized environment runner for Continuous Integration servers. 4 | RoboCI is aimed to **run Travis CI builds locally** inside [Docker](http://docker.io) containers as well creating custom build setup. 5 | 6 | ## RoboCI is used to: 7 | 8 | * create virtualized environments with Docker. 9 | * run acceptance, functional, unit, integration tests in isolated containers. 10 | * run Travis CI builds locally or on CI server. 11 | * debug builds inside containers 12 | 13 | ## Requirements 14 | 15 | Requires [Docker](http://docker.io) and [Robo PHP Task Runner](http://robo.li) to be installed. 16 | 17 | ## Installation 18 | 19 | Use Composer 20 | 21 | ``` 22 | { 23 | "require-dev": { 24 | "codegyre/robo": "*", 25 | "codegyre/robo-ci": "@dev" 26 | } 27 | } 28 | 29 | ``` 30 | 31 | Create `RoboFile.php` in the root of your project (if it is not already there), by simply running `robo`. 32 | 33 | Attach composer autoloader to include `Codegyre\RoboCI` into your RoboFile: 34 | 35 | ``` php 36 | =5.4.0", 27 | "symfony/console": "~2.1", 28 | "symfony/filesystem": "~2.1", 29 | "symfony/finder": "~2.1", 30 | "symfony/process": "~2.1" 31 | }, 32 | "require-dev": { 33 | "codeception/aspect-mock": "0.4.*", 34 | "codeception/codeception": "~2.0", 35 | "codeception/verify": "0.2.*", 36 | "natxet/cssmin": "~3.0", 37 | "patchwork/jsqueeze": "~1.0" 38 | }, 39 | "bin": [ 40 | "robo" 41 | ], 42 | "type": "library", 43 | "autoload": { 44 | "psr-4": { 45 | "Robo\\": "src" 46 | } 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Davert", 55 | "email": "davert.php@resend.cc" 56 | } 57 | ], 58 | "description": "Modern task runner", 59 | "time": "2014-12-26 04:22:04" 60 | }, 61 | { 62 | "name": "codegyre/robo-docker", 63 | "version": "0.1.0", 64 | "source": { 65 | "type": "git", 66 | "url": "https://github.com/Codegyre/robo-docker.git", 67 | "reference": "69aec8b828890e86d5c74bb53c74045ce5ca2f70" 68 | }, 69 | "dist": { 70 | "type": "zip", 71 | "url": "https://api.github.com/repos/Codegyre/robo-docker/zipball/69aec8b828890e86d5c74bb53c74045ce5ca2f70", 72 | "reference": "69aec8b828890e86d5c74bb53c74045ce5ca2f70", 73 | "shasum": "" 74 | }, 75 | "type": "library", 76 | "autoload": { 77 | "psr-4": { 78 | "Codegyre\\RoboDocker\\": "src" 79 | } 80 | }, 81 | "notification-url": "https://packagist.org/downloads/", 82 | "license": [ 83 | "MIT" 84 | ], 85 | "authors": [ 86 | { 87 | "name": "Davert", 88 | "email": "davert.php@resend.cc" 89 | } 90 | ], 91 | "description": "Docker Tasks for Robo Task Runner", 92 | "time": "2014-09-26 23:58:29" 93 | }, 94 | { 95 | "name": "henrikbjorn/lurker", 96 | "version": "1.0.0", 97 | "source": { 98 | "type": "git", 99 | "url": "https://github.com/henrikbjorn/Lurker.git", 100 | "reference": "a020d45b3bc37810aeafe27343c51af8a74c9419" 101 | }, 102 | "dist": { 103 | "type": "zip", 104 | "url": "https://api.github.com/repos/henrikbjorn/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419", 105 | "reference": "a020d45b3bc37810aeafe27343c51af8a74c9419", 106 | "shasum": "" 107 | }, 108 | "require": { 109 | "php": ">=5.3.3", 110 | "symfony/config": "~2.2", 111 | "symfony/event-dispatcher": "~2.2" 112 | }, 113 | "suggest": { 114 | "ext-inotify": ">=0.1.6" 115 | }, 116 | "type": "library", 117 | "extra": { 118 | "branch-alias": { 119 | "dev-master": "1.0.x-dev" 120 | } 121 | }, 122 | "autoload": { 123 | "psr-0": { 124 | "Lurker": "src" 125 | } 126 | }, 127 | "notification-url": "https://packagist.org/downloads/", 128 | "license": [ 129 | "MIT" 130 | ], 131 | "authors": [ 132 | { 133 | "name": "Henrik Bjornskov", 134 | "email": "henrik@bjrnskov.dk", 135 | "homepage": "http://henrik.bjrnskov.dk" 136 | }, 137 | { 138 | "name": "Konstantin Kudryashov", 139 | "email": "ever.zet@gmail.com", 140 | "homepage": "http://everzet.com" 141 | }, 142 | { 143 | "name": "Yaroslav Kiliba", 144 | "email": "om.dattaya@gmail.com" 145 | } 146 | ], 147 | "description": "Resource Watcher.", 148 | "keywords": [ 149 | "filesystem", 150 | "resource", 151 | "watching" 152 | ], 153 | "time": "2013-05-24 06:47:29" 154 | }, 155 | { 156 | "name": "symfony/config", 157 | "version": "v2.7.5", 158 | "source": { 159 | "type": "git", 160 | "url": "https://github.com/symfony/config.git", 161 | "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61" 162 | }, 163 | "dist": { 164 | "type": "zip", 165 | "url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61", 166 | "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61", 167 | "shasum": "" 168 | }, 169 | "require": { 170 | "php": ">=5.3.9", 171 | "symfony/filesystem": "~2.3" 172 | }, 173 | "require-dev": { 174 | "symfony/phpunit-bridge": "~2.7" 175 | }, 176 | "type": "library", 177 | "extra": { 178 | "branch-alias": { 179 | "dev-master": "2.7-dev" 180 | } 181 | }, 182 | "autoload": { 183 | "psr-4": { 184 | "Symfony\\Component\\Config\\": "" 185 | } 186 | }, 187 | "notification-url": "https://packagist.org/downloads/", 188 | "license": [ 189 | "MIT" 190 | ], 191 | "authors": [ 192 | { 193 | "name": "Fabien Potencier", 194 | "email": "fabien@symfony.com" 195 | }, 196 | { 197 | "name": "Symfony Community", 198 | "homepage": "https://symfony.com/contributors" 199 | } 200 | ], 201 | "description": "Symfony Config Component", 202 | "homepage": "https://symfony.com", 203 | "time": "2015-09-21 15:02:29" 204 | }, 205 | { 206 | "name": "symfony/console", 207 | "version": "v2.7.5", 208 | "source": { 209 | "type": "git", 210 | "url": "https://github.com/symfony/console.git", 211 | "reference": "06cb17c013a82f94a3d840682b49425cd00a2161" 212 | }, 213 | "dist": { 214 | "type": "zip", 215 | "url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161", 216 | "reference": "06cb17c013a82f94a3d840682b49425cd00a2161", 217 | "shasum": "" 218 | }, 219 | "require": { 220 | "php": ">=5.3.9" 221 | }, 222 | "require-dev": { 223 | "psr/log": "~1.0", 224 | "symfony/event-dispatcher": "~2.1", 225 | "symfony/phpunit-bridge": "~2.7", 226 | "symfony/process": "~2.1" 227 | }, 228 | "suggest": { 229 | "psr/log": "For using the console logger", 230 | "symfony/event-dispatcher": "", 231 | "symfony/process": "" 232 | }, 233 | "type": "library", 234 | "extra": { 235 | "branch-alias": { 236 | "dev-master": "2.7-dev" 237 | } 238 | }, 239 | "autoload": { 240 | "psr-4": { 241 | "Symfony\\Component\\Console\\": "" 242 | } 243 | }, 244 | "notification-url": "https://packagist.org/downloads/", 245 | "license": [ 246 | "MIT" 247 | ], 248 | "authors": [ 249 | { 250 | "name": "Fabien Potencier", 251 | "email": "fabien@symfony.com" 252 | }, 253 | { 254 | "name": "Symfony Community", 255 | "homepage": "https://symfony.com/contributors" 256 | } 257 | ], 258 | "description": "Symfony Console Component", 259 | "homepage": "https://symfony.com", 260 | "time": "2015-09-25 08:32:23" 261 | }, 262 | { 263 | "name": "symfony/event-dispatcher", 264 | "version": "v2.7.5", 265 | "source": { 266 | "type": "git", 267 | "url": "https://github.com/symfony/event-dispatcher.git", 268 | "reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9" 269 | }, 270 | "dist": { 271 | "type": "zip", 272 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae4dcc2a8d3de98bd794167a3ccda1311597c5d9", 273 | "reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9", 274 | "shasum": "" 275 | }, 276 | "require": { 277 | "php": ">=5.3.9" 278 | }, 279 | "require-dev": { 280 | "psr/log": "~1.0", 281 | "symfony/config": "~2.0,>=2.0.5", 282 | "symfony/dependency-injection": "~2.6", 283 | "symfony/expression-language": "~2.6", 284 | "symfony/phpunit-bridge": "~2.7", 285 | "symfony/stopwatch": "~2.3" 286 | }, 287 | "suggest": { 288 | "symfony/dependency-injection": "", 289 | "symfony/http-kernel": "" 290 | }, 291 | "type": "library", 292 | "extra": { 293 | "branch-alias": { 294 | "dev-master": "2.7-dev" 295 | } 296 | }, 297 | "autoload": { 298 | "psr-4": { 299 | "Symfony\\Component\\EventDispatcher\\": "" 300 | } 301 | }, 302 | "notification-url": "https://packagist.org/downloads/", 303 | "license": [ 304 | "MIT" 305 | ], 306 | "authors": [ 307 | { 308 | "name": "Fabien Potencier", 309 | "email": "fabien@symfony.com" 310 | }, 311 | { 312 | "name": "Symfony Community", 313 | "homepage": "https://symfony.com/contributors" 314 | } 315 | ], 316 | "description": "Symfony EventDispatcher Component", 317 | "homepage": "https://symfony.com", 318 | "time": "2015-09-22 13:49:29" 319 | }, 320 | { 321 | "name": "symfony/filesystem", 322 | "version": "v2.7.5", 323 | "source": { 324 | "type": "git", 325 | "url": "https://github.com/symfony/filesystem.git", 326 | "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab" 327 | }, 328 | "dist": { 329 | "type": "zip", 330 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab", 331 | "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab", 332 | "shasum": "" 333 | }, 334 | "require": { 335 | "php": ">=5.3.9" 336 | }, 337 | "require-dev": { 338 | "symfony/phpunit-bridge": "~2.7" 339 | }, 340 | "type": "library", 341 | "extra": { 342 | "branch-alias": { 343 | "dev-master": "2.7-dev" 344 | } 345 | }, 346 | "autoload": { 347 | "psr-4": { 348 | "Symfony\\Component\\Filesystem\\": "" 349 | } 350 | }, 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "Fabien Potencier", 358 | "email": "fabien@symfony.com" 359 | }, 360 | { 361 | "name": "Symfony Community", 362 | "homepage": "https://symfony.com/contributors" 363 | } 364 | ], 365 | "description": "Symfony Filesystem Component", 366 | "homepage": "https://symfony.com", 367 | "time": "2015-09-09 17:42:36" 368 | }, 369 | { 370 | "name": "symfony/finder", 371 | "version": "v2.7.5", 372 | "source": { 373 | "type": "git", 374 | "url": "https://github.com/symfony/finder.git", 375 | "reference": "8262ab605973afbb3ef74b945daabf086f58366f" 376 | }, 377 | "dist": { 378 | "type": "zip", 379 | "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f", 380 | "reference": "8262ab605973afbb3ef74b945daabf086f58366f", 381 | "shasum": "" 382 | }, 383 | "require": { 384 | "php": ">=5.3.9" 385 | }, 386 | "require-dev": { 387 | "symfony/phpunit-bridge": "~2.7" 388 | }, 389 | "type": "library", 390 | "extra": { 391 | "branch-alias": { 392 | "dev-master": "2.7-dev" 393 | } 394 | }, 395 | "autoload": { 396 | "psr-4": { 397 | "Symfony\\Component\\Finder\\": "" 398 | } 399 | }, 400 | "notification-url": "https://packagist.org/downloads/", 401 | "license": [ 402 | "MIT" 403 | ], 404 | "authors": [ 405 | { 406 | "name": "Fabien Potencier", 407 | "email": "fabien@symfony.com" 408 | }, 409 | { 410 | "name": "Symfony Community", 411 | "homepage": "https://symfony.com/contributors" 412 | } 413 | ], 414 | "description": "Symfony Finder Component", 415 | "homepage": "https://symfony.com", 416 | "time": "2015-09-19 19:59:23" 417 | }, 418 | { 419 | "name": "symfony/process", 420 | "version": "v2.7.5", 421 | "source": { 422 | "type": "git", 423 | "url": "https://github.com/symfony/process.git", 424 | "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9" 425 | }, 426 | "dist": { 427 | "type": "zip", 428 | "url": "https://api.github.com/repos/symfony/process/zipball/b27c8e317922cd3cdd3600850273cf6b82b2e8e9", 429 | "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9", 430 | "shasum": "" 431 | }, 432 | "require": { 433 | "php": ">=5.3.9" 434 | }, 435 | "require-dev": { 436 | "symfony/phpunit-bridge": "~2.7" 437 | }, 438 | "type": "library", 439 | "extra": { 440 | "branch-alias": { 441 | "dev-master": "2.7-dev" 442 | } 443 | }, 444 | "autoload": { 445 | "psr-4": { 446 | "Symfony\\Component\\Process\\": "" 447 | } 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "MIT" 452 | ], 453 | "authors": [ 454 | { 455 | "name": "Fabien Potencier", 456 | "email": "fabien@symfony.com" 457 | }, 458 | { 459 | "name": "Symfony Community", 460 | "homepage": "https://symfony.com/contributors" 461 | } 462 | ], 463 | "description": "Symfony Process Component", 464 | "homepage": "https://symfony.com", 465 | "time": "2015-09-19 19:59:23" 466 | }, 467 | { 468 | "name": "symfony/yaml", 469 | "version": "v2.7.5", 470 | "source": { 471 | "type": "git", 472 | "url": "https://github.com/symfony/yaml.git", 473 | "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770" 474 | }, 475 | "dist": { 476 | "type": "zip", 477 | "url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770", 478 | "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770", 479 | "shasum": "" 480 | }, 481 | "require": { 482 | "php": ">=5.3.9" 483 | }, 484 | "require-dev": { 485 | "symfony/phpunit-bridge": "~2.7" 486 | }, 487 | "type": "library", 488 | "extra": { 489 | "branch-alias": { 490 | "dev-master": "2.7-dev" 491 | } 492 | }, 493 | "autoload": { 494 | "psr-4": { 495 | "Symfony\\Component\\Yaml\\": "" 496 | } 497 | }, 498 | "notification-url": "https://packagist.org/downloads/", 499 | "license": [ 500 | "MIT" 501 | ], 502 | "authors": [ 503 | { 504 | "name": "Fabien Potencier", 505 | "email": "fabien@symfony.com" 506 | }, 507 | { 508 | "name": "Symfony Community", 509 | "homepage": "https://symfony.com/contributors" 510 | } 511 | ], 512 | "description": "Symfony Yaml Component", 513 | "homepage": "https://symfony.com", 514 | "time": "2015-09-14 14:14:09" 515 | } 516 | ], 517 | "packages-dev": [], 518 | "aliases": [], 519 | "minimum-stability": "stable", 520 | "stability-flags": [], 521 | "prefer-stable": false, 522 | "prefer-lowest": false, 523 | "platform": { 524 | "php": ">=5.4.0" 525 | }, 526 | "platform-dev": [] 527 | } 528 | --------------------------------------------------------------------------------