├── .rsync_exclude.txt.dist ├── Command └── DeployCommand.php ├── DependencyInjection ├── Configuration.php └── DeployExtension.php ├── DeployBundle.php ├── README.md └── composer.json /.rsync_exclude.txt.dist: -------------------------------------------------------------------------------- 1 | # IDEs files 2 | .buildpath 3 | .project 4 | .settings 5 | nbproject 6 | 7 | # VCS files 8 | .gitignore 9 | .travis.yml 10 | 11 | /app/config/*_dev.yml 12 | /app/config/parameters.yml 13 | /app/logs 14 | /app/cache 15 | web/* 16 | vendor 17 | -------------------------------------------------------------------------------- /Command/DeployCommand.php: -------------------------------------------------------------------------------- 1 | - http://www.iliveinperego.com/ 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | namespace Hpatoio\DeployBundle\Command; 11 | 12 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 13 | use Symfony\Component\Console\Input\InputArgument; 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Input\InputOption; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 18 | use Symfony\Component\Process\Process; 19 | 20 | class DeployCommand extends ContainerAwareCommand 21 | { 22 | /** 23 | * @see Command 24 | */ 25 | protected function configure() 26 | { 27 | $this 28 | ->setName('project:deploy') 29 | ->setDescription('Deploy your project via rsync') 30 | ->addArgument('env', InputArgument::REQUIRED, 'The environment where you want to deploy the project') 31 | ->addOption('go', null, InputOption::VALUE_NONE, 'Do the deployment') 32 | ->addOption('rsync-options', null, InputOption::VALUE_NONE, 'Options to pass to the rsync executable') 33 | ->addOption('force-vendor', null, InputOption::VALUE_NONE, 'Force sync of vendor dir.') 34 | ; 35 | } 36 | 37 | /** 38 | * @see Command 39 | */ 40 | protected function execute(InputInterface $input, OutputInterface $output) 41 | { 42 | 43 | $config_root_path = $this->getContainer()->get('kernel')->getRootDir()."/config/"; 44 | $output->getFormatter()->setStyle('notice', new OutputFormatterStyle('red', 'yellow')); 45 | $available_env = $this->getContainer()->getParameter('deploy.config'); 46 | 47 | $env = $input->getArgument('env'); 48 | 49 | if (!in_array($env, array_keys($available_env))) { 50 | throw new \InvalidArgumentException(sprintf('\'%s\' is no a valid environment. Valid environments: %s', $env, implode(",",array_keys($available_env)))); 51 | } 52 | 53 | foreach ($available_env[$env] as $key => $value) { 54 | $$key = $value; 55 | } 56 | 57 | $ssh = 'ssh -p '.$port.''; 58 | 59 | if ($input->getOption('rsync-options')) 60 | $rsync_options = $input->getOption('rsync-options'); 61 | 62 | if ($input->getOption('force-vendor')) 63 | $rsync_options .= " --include 'vendor' "; 64 | 65 | $exclude_file_found = false; 66 | 67 | if (file_exists($config_root_path.'rsync_exclude.txt')) { 68 | $rsync_options .= sprintf(' --exclude-from="%srsync_exclude.txt"', $config_root_path); 69 | $exclude_file_found = true; 70 | } 71 | 72 | if (file_exists($config_root_path."rsync_exclude_{$env}.txt")) { 73 | $rsync_options .= sprintf(" --exclude-from=\"%srsync_exclude_{$env}.txt\"", $config_root_path); 74 | $exclude_file_found = true; 75 | } 76 | 77 | if (!$exclude_file_found) { 78 | $output->writeln(sprintf('No rsync_exclude file found, nothing excluded. If you want an rsync_exclude.txt template get it here http://bit.ly/rsehdbsf2', $config_root_path."rsync_exclude.txt")); 79 | $output->writeln(""); 80 | } 81 | 82 | $dryRun = $input->getOption('go') ? '' : '--dry-run'; 83 | 84 | $user = ($user !='') ? $user."@" : ""; 85 | 86 | $command = "rsync $dryRun $rsync_options -e \"$ssh\" ./ $user$host:$dir"; 87 | 88 | $output->writeln(sprintf('%s on %s server with %s command', 89 | ($dryRun) ? 'Fake deploying' : 'Deploying', 90 | $input->getArgument('env'), 91 | $command)); 92 | 93 | $process = new Process($command); 94 | $process->setTimeout(($timeout == 0) ? null : $timeout); 95 | 96 | $output->writeln("\nSTART deploy\n--------------------------------------------"); 97 | 98 | $process->run(function ($type, $buffer) use ($output) { 99 | if ('err' === $type) { 100 | $output->write( 'ERR > '.$buffer); 101 | } else { 102 | $output->write($buffer); 103 | } 104 | }); 105 | 106 | $output->writeln("\nEND deploy\n--------------------------------------------\n"); 107 | 108 | if ($dryRun) { 109 | 110 | $output->writeln('This was a simulation, --go was not specified. Post deploy operation not run.'); 111 | $output->writeln(sprintf('Run the command with --go for really copy the files to %s server.', $env)); 112 | 113 | } else { 114 | 115 | $output->writeln(sprintf("Deployed on %s server!\n", $env)); 116 | 117 | if ( isset($post_deploy_operations) && count($post_deploy_operations) > 0 ) { 118 | 119 | $post_deploy_commands = implode("; ", $post_deploy_operations); 120 | 121 | $output->writeln(sprintf("Running post deploy commands on %s server!\n", $env)); 122 | 123 | $command = "$ssh $user$host 'cd \"$dir\";".$post_deploy_commands."'"; 124 | 125 | $process = new Process($command); 126 | $process->setTimeout(($timeout == 0) ? null : $timeout); 127 | $process->run(function ($type, $buffer) use ($output) { 128 | if ('err' === $type) { 129 | $output->write( 'ERR > '.$buffer); 130 | } else { 131 | $output->write($buffer); 132 | } 133 | }); 134 | 135 | $output->writeln("\nDone"); 136 | 137 | } 138 | 139 | } 140 | 141 | $output->writeln(""); 142 | 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | - http://www.iliveinperego.com/ 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | namespace Hpatoio\DeployBundle\DependencyInjection; 11 | 12 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 13 | use Symfony\Component\Config\Definition\ConfigurationInterface; 14 | 15 | /** 16 | * This is the class that validates and merges configuration from your app/config files 17 | * 18 | * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} 19 | */ 20 | class Configuration implements ConfigurationInterface 21 | { 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function getConfigTreeBuilder() 26 | { 27 | 28 | $treeBuilder = new TreeBuilder(); 29 | $rootNode = $treeBuilder->root('deploy'); 30 | 31 | // Here you should define the parameters that are allowed to 32 | // configure your bundle. See the documentation linked above for 33 | // more information on that topic. 34 | 35 | $rootNode->isRequired() 36 | ->requiresAtLeastOneElement() 37 | ->useAttributeAsKey('name') 38 | ->prototype('array') 39 | ->children() 40 | ->scalarNode('rsync_options') 41 | ->defaultValue('-azC --force --delete --progress -h --checksum') 42 | ->info('Default options used by the rsync command. You can override this value by passing --rsync-options on the command line.') 43 | ->example('-azC --force --delete --progress -h --checksum') 44 | ->end() 45 | ->scalarNode('host') 46 | ->isRequired() 47 | ->cannotBeEmpty() 48 | ->info('Name or IP of the remote server') 49 | ->end() 50 | ->scalarNode('dir') 51 | ->defaultValue('') 52 | ->info('Remote root for your project. NB: this is not the document root. Usually a level before.') 53 | ->end() 54 | ->scalarNode('user') 55 | ->defaultValue('') 56 | ->info('The user on the destination server. If none is specified your local user is used.') 57 | ->end() 58 | ->scalarNode('port') 59 | ->defaultValue('22') 60 | ->info('TCP port.') 61 | ->end() 62 | ->scalarNode('timeout') 63 | ->defaultValue('60') 64 | ->info('Process timeout in seconds. Set it to 0 for no timeout.') 65 | ->end() 66 | ->variableNode('post_deploy_operations') 67 | ->info('Shell commands to run after deploy on the remote machine.') 68 | ->end(); 69 | 70 | return $treeBuilder; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DependencyInjection/DeployExtension.php: -------------------------------------------------------------------------------- 1 | - http://www.iliveinperego.com/ 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | namespace Hpatoio\DeployBundle\DependencyInjection; 11 | 12 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 13 | use Symfony\Component\DependencyInjection\ContainerBuilder; 14 | 15 | class DeployExtension extends Extension 16 | { 17 | 18 | /** 19 | * {@inheritDoc} 20 | */ 21 | public function load(array $configs, ContainerBuilder $container) 22 | { 23 | $configuration = new Configuration(); 24 | $config = $this->processConfiguration($configuration, $configs); 25 | 26 | $container->setParameter('deploy.config', $config); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /DeployBundle.php: -------------------------------------------------------------------------------- 1 | - http://www.iliveinperego.com/ 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | namespace Hpatoio\DeployBundle; 11 | 12 | use Symfony\Component\HttpKernel\Bundle\Bundle; 13 | 14 | class DeployBundle extends Bundle 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DeployBundle 2 | ================= 3 | 4 | [![Total Downloads](https://poser.pugx.org/hpatoio/deploy-bundle/downloads.png)](https://packagist.org/packages/hpatoio/deploy-bundle) 5 | [![Latest Stable Version](https://poser.pugx.org/hpatoio/deploy-bundle/v/stable.png)](https://packagist.org/packages/hpatoio/deploy-bundle) 6 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/b4556cd7-652f-4a58-9126-eb2c1abd6c89/mini.png)](https://insight.sensiolabs.com/projects/b4556cd7-652f-4a58-9126-eb2c1abd6c89) 7 | 8 | ## Installation 9 | Run the command: 10 | 11 | ```bash 12 | $ composer require hpatoio/deploy-bundle ~1.5 13 | ``` 14 | 15 | **N.B.** This project follow [semantic versioning](http://semver.org/). Latest stable branch is `1.5`. 16 | 17 | 18 | ### Enable the bundle in your project 19 | ```php 20 | // app/AppKernel.php 21 | public function registerBundles() 22 | { 23 | $bundles = array( 24 | // ... 25 | new Hpatoio\DeployBundle\DeployBundle(), 26 | // ... 27 | ); 28 | } 29 | ``` 30 | ## Configuration 31 | Configuration is all about defining environments. You can define as many environments as you want, the only mandatory value is `host`. The deploy is made via rsync so default value are used if none are specified. 32 | Remember that to get the configuration reference for this bundle you can run: 33 | ```bash 34 | bin/console config:dump-reference DeployBundle 35 | ``` 36 | 37 | Configuration example: 38 | ```yaml 39 | # app/config/config.yml 40 | deploy: 41 | prod: 42 | rsync-options: '-azC --force --delete --progress -h --checksum' 43 | host: my.destination.env 44 | dir: /path/to/project/root 45 | user: root 46 | port: 22 47 | timeout: 120 # Connection timeout in seconds. 0 for no timeout. 48 | uat: 49 | host: 192.168.1.10 50 | user: root2 51 | dir: /path/to/project/root 52 | port: 22022 53 | post_deploy_operations: 54 | - bin/console cache:clear --env=prod 55 | - bin/console assets:install --env=prod 56 | - bin/console assetic:dump --env=prod 57 | ``` 58 | 59 | Most of the keys don't need explanation except: 60 | 61 | #### post_deploy_operations 62 | You can add a list of command you want run on the remote server after the deploy. In the configuration above you can see the common command you run after a deploy (clear the cache, publish assets etc) 63 | These commands are run as a shell command on the remote server. So you can enter whichever shell command you want (cp, rm etc) 64 | 65 | Please don't confuse Symfony environment with deploy environment. As you can see in the configuration above we run `post_deploy_operations` for Symfony environment `prod` on deploy environment `uat` 66 | 67 | #### rsync-options 68 | If you add the key `rsync-options` to your environment you will override the default options used for rsync. So the system is using: 69 | 70 | * "-azC --force --delete --progress -h --checksum" if nothing is specified 71 | * the value for the key `rsync-options` if specified it in `config.yml` for the target environment 72 | * the value of the command line option `--rsync-options` 73 | 74 | ### Rsync Exclude 75 | Create a `rsync_exclude.txt` file under `app/config` to exclude files from deploy. [here](https://github.com/hpatoio/DeployBundle/blob/master/.rsync_exclude.txt.dist) a good starting point. 76 | 77 | You can also create a per-environment rsync_exclude. Just create a file in `app/config` with name `rsync_exclude_{env}.txt`. For more details you can read here #3 and here #7 78 | 79 | ## Force vendor syncronization 80 | Usually `vendor` dir is excluded from rsync. If you need tou sync it you can add `--force-vendor`. (see later for an example) 81 | 82 | ## Use 83 | Deployment is easy: 84 | ```shell 85 | php bin/console project:deploy --go prod 86 | ``` 87 | Feel a bit unsure ? Simulate the deploy 88 | ```shell 89 | php bin/console project:deploy prod 90 | ``` 91 | Need to update vendor ? Use the option --force-vendor (Usually vendor is excluded from rsync) 92 | ```shell 93 | php bin/console project:deploy --go --force-vendor prod 94 | ``` 95 | Custom parameters for rsync 96 | ```shell 97 | php bin/console project:deploy --rsync-options="-azChdl" prod 98 | ``` 99 | License 100 | ------------- 101 | DeployBundle is licensed under the CC-BY-SA-3.0 - see [here](http://www.spdx.org/licenses/CC-BY-SA-3.0) for details 102 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hpatoio/deploy-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Easy deploy via rsync. Porting of Symfony 1 project:deploy command.", 5 | "keywords": ["deploy","command"], 6 | "homepage": "http://github.com/hpatoio/DeployBundle", 7 | "license": "CC-BY-SA-3.0", 8 | "authors": [ 9 | { 10 | "name": "Simone Fumagalli", 11 | "homepage": "http://www.iliveinperego.com/", 12 | "email": "simone@iliveinperego.com", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.2", 18 | "symfony/framework-bundle": "~2.0|~3.0" 19 | }, 20 | "autoload": { 21 | "psr-0": { "Hpatoio\\DeployBundle": "" } 22 | }, 23 | "target-dir": "Hpatoio/DeployBundle" 24 | } 25 | --------------------------------------------------------------------------------