aes3xs / tasker
任务自动化工具
Requires
- php: >=5.6.0
- doctrine/inflector: ^1.1
- herzult/php-ssh: ~1.0
- monolog/monolog: ~1.22
- phpseclib/phpseclib: ^2.0
- symfony/config: ~2.7|~3.0
- symfony/console: ~2.7|~3.0
- symfony/dependency-injection: ~2.7|~3.0
- symfony/expression-language: ~2.7|~3.0
- symfony/filesystem: ~2.7|~3.0
- symfony/process: ~2.7|~3.0
- symfony/yaml: ~2.7|~3.0
- twig/twig: ~1.32
Requires (Dev)
- phpunit/phpunit: ~5.2
This package is not auto-updated.
Last update: 2024-09-29 04:49:37 UTC
README
Tasker是一个自动化工具,用于编写小型和简单的部署脚本。
它适合没有CI集成的单服务器项目。
如果您需要更多功能,请查看Jenkins、TeamCity和其他工具。它并不试图取代它们。Tasker受到Deployer项目的启发,您也可以查看它。Tasker旨在更加灵活(可覆盖)和面向对象,但功能较少。
安装
通过composer安装
composer require aes3xs/tasker
如何部署
一种好的方法是分割部署为本地和远程部分。
本地部分准备代码、预热文件缓存、下载依赖项和其他操作,这些操作会影响项目目录内的所有内容。
没有外部依赖、服务或文件(除依赖项管理和PHP本身)。
没有数据库请求,没有迁移。
此脚本可以在任何地方执行,在构建服务器或本地机器上。它创建一个准备就绪的部署模块,可以直接在生产环境中复制并运行。
在项目中拥有这样的脚本非常有用,它不应有依赖关系,如composer。因为如果从仓库有裸项目,它负责运行。
其中一些步骤有时在composer.json脚本部分中定义。但我更喜欢有独立的文件。
以下是一个示例(./bin/deploy)
#!/bin/bash set -e set -o pipefail SYMFONY_ENV=${SYMFONY_ENV:="dev"} SYMFONY_DEBUG=${SYMFONY_DEBUG:="1"} for i in "$@"; do case $i in -e=*|--env=*) SYMFONY_ENV="${i#*=}"; shift;; --no-debug) SYMFONY_DEBUG="0"; shift;; *);; esac done ROOT="$(dirname "$(dirname "$(readlink -fm "$0")")")" PHP=$(which "php") || { echo "PHP is not found" ; exit 1; } YARN=$(which "yarn") || { echo "Yarn is not found" ; exit 1; } COMPOSER=$(which "composer") || { echo "Composer is not found" ; exit 1; } if [ "$SYMFONY_DEBUG" == "0" ]; then NO_DEBUG="--no-debug"; fi CONSOLE="${ROOT}/bin/console --quiet --env=${SYMFONY_ENV} ${NO_DEBUG}" echo "SYMFONY_ENV = ${SYMFONY_ENV}" echo "SYMFONY_DEBUG = ${SYMFONY_DEBUG}" echo "PROJECT_ROOT = ${ROOT}" echo "PHP = ${PHP}" echo "YARN = ${YARN}" echo "COMPOSER = ${COMPOSER}" echo "CONSOLE = ${CONSOLE}" echo "" _exec () { echo -e "\033[1m[$(date +%T)] >\033[0m" $1 eval $1 } # Vendors _exec "cd ${ROOT} && ${COMPOSER} install --prefer-dist --no-progress --no-interaction --optimize-autoloader" _exec "cd ${ROOT} && ${YARN} install --prod --non-interactive" # Cache _exec "rm -rf ${ROOT}/var/cache/${SYMFONY_ENV}" _exec "chmod 0775 ${ROOT}/var/cache" _exec "${CONSOLE} cache:warmup" # Assets PATHS=( "${ROOT}/web/js" "${ROOT}/web/css" "${ROOT}/web/bundles" ) _exec "rm -rf ${PATHS[*]}" _exec "${CONSOLE} assets:install ${ROOT}/web --symlink --relative" _exec "${CONSOLE} assetic:dump ${ROOT}/web" # Writable _exec "if [ ! -w ${ROOT}/var/cache ]; then { echo 'Is not writable' ; exit 1; }; fi" _exec "if [ ! -w ${ROOT}/var/logs ]; then { echo 'Is not writable' ; exit 1; }; fi" _exec "if [ ! -w ${ROOT}/var/spool ]; then { echo 'Is not writable' ; exit 1; }; fi"
您也可以使用Tasker编写此脚本,但我建议保留独立的shell脚本。
部署过程的远程部分与外部服务(如nginx、php-fpm、数据库)一起工作。
您还必须能够访问项目仓库以克隆它。
这里有几点重要事项。
首先,以其他用户执行命令。
出于安全原因,服务器上的每个人都必须有自己的凭据。
但项目本身配置为从一个用户运行,例如,www-data
。
因此,您必须在每个调用中添加类似sudo -u USER
的内容,这已经实现了。
其次,在服务器上克隆仓库的认证。
GitLab有登录/密码选项或公钥,例如。
因此,更好的方法是使用SSH密钥,并且相同的密钥用于认证GitLab或其他系统。
这可以通过SSH转发认证来完成。
部署脚本示例
#!/usr/bin/env php <?php require_once "./vendor/autoload.php"; use Aes3xs\Tasker\Connection\Connection; use Aes3xs\Tasker\Service\Git; use Aes3xs\Tasker\Service\Releaser; use Aes3xs\Tasker\Service\Shell; use Aes3xs\Tasker\Service\Symfony; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { public $repository = 'REPOSITORY_PATH'; public $deployPath = '/var/www/PROJECT'; public $runUser = 'www-data'; public $branch = 'master'; protected function configure() { $this ->addArgument('server', InputArgument::REQUIRED) ->addOption('branch', 'b', InputOption::VALUE_OPTIONAL); } public function execute($server, Connection $connection) { if (!in_array($server, ['prod', 'dev'])) { throw new \RuntimeException('Server available values: prod, dev'); } $connection ->getParameters() ->setHost(null) ->setForwarding(true); // Configure to deploy on different servers $this->branch = 'master'; // Set branch if needed try { $this->runActions(array_filter($this->getAvailableActions(), function ($actionName) { return !in_array($actionName, ['execute', 'failNotify']); }), 'Deploy'); } catch (\Exception $e) { $this->runActions(['failNotify'], 'Failback'); } } public function switchUser(Shell $shell, $runUser) { $shell->setUser($runUser); return $runUser; } public function createRelease(Releaser $releaser, Git $git, Shell $shell, $deployPath, $repository, $branch) { $releaser->setDeployPath($deployPath); $releaser->prepare(); if ($releaser->isLocked() && $this->askConfirmationQuestion('Deploy is locked. Unlock?')) { $releaser->unlock(); } $releaser->lock(); $releaser->create(); $releasePath = $releaser->getReleasePath(); $releases = $releaser->getReleaseList(); $reference = $releases ? $releaser->getReleasePathByName(reset($releases)) : null; $shell->setCwd($releasePath); $git->cloneAt($repository, $releasePath, $branch, $reference); // Uses SSH forwarding if presented $releaser->updateReleaseShares(['var/logs', 'var/spool', 'var/sessions'], ['app/config/parameters.yml']); $shell->chmod('./bin', 0777); $shell->exec('./bin/deploy --env=prod --no-debug'); } public function migrate(Symfony $symfony) { $symfony->runCommand('doctrine:migrations:migrate', [], ['allow-no-migration']); } public function release(Releaser $releaser, Git $git, Shell $shell, $server, $branch) { $releaser->release(); $releaser->unlock(); $last_commits = $git->log($releaser->getReleasePath(), 3); $this->info(<<<EOL Server: {{ server }} Released: {{ releaser.getReleaseName() }} Branch: {{ server }} Last commits: $last_commits EOL ); $releaser->cleanup(5); $shell->setUser(null); $shell->exec("sudo service nginx reload"); $shell->exec("sudo service php-fpm reload"); } public function shutdownRoutine(Shell $shell) { $shell->setUser(null); } public function failNotify() { $this->error(<<<EOL Server: {{ server }} Release: {{ releaser.getReleaseName() }} Branch: {{ server }} FALURE EOL ); } } \TestRecipe::run();
概述
Tasker旨在使用一系列操作创建PHP二进制文件。
操作可以在本地或远程服务器上执行。
它们位于"recipe"类中。
Recipe是一个自执行的命令行命令。
#!/usr/bin/env php <?php require_once "./vendor/autoload.php"; class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { public function execute() { // Do smth } } \TestRecipe::run();
使用第一行#!/usr/bin/env php
使您的文件可执行
添加权限 chmod a+x ./testRecipe
然后添加从composer的自动加载require_once "./vendor/autoload.php";
,确保路径正确。
定义您自己的recipe类,从\Aes3xs\Tasker\AbstractRecipe
扩展它
添加execute
(默认)或其他将首先执行的方法。
调用静态::run()
或::run('yourMethodName')
操作
在操作中,您可以实际完成工作。
操作是公开的非静态方法。
魔法方法(__*)或以get[A-Z](例如getSmth)开头的方法不能执行。
使用runActions(['prepare', 'release'])
或runAction('release')
调用操作
您可以使用getAvailableActions()
获取recipe中所有可用操作的列表
如果您想在执行过程中跳过操作,请调用skipAction('only in production')
,就像PhpUnit的$this->markTestSkipped()
#!/usr/bin/env php <?php require_once "./vendor/autoload.php"; class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { public function execute() { $this->runActions($this->getAvailableActions(), 'deployment'); } public function prepare() { } public function deploy($env) { if ('prod' === $env) { $this->runAction('restart'); } } public function restart() { } public function notify($env) { if ('prod' !== $env) { $this->skipAction('Production only'); } } } \TestRecipe::run();
命令
Recipe是一个带有参数和选项的命令。
它基于Symfony Console组件。
您可以使用 configure()
来定义可用的输入。
同样的方法 addArgument()
和 addOption()
,您可以使用所有 Symfony 默认选项。
- 使用
--help
显示命令信息。 - 使用
-v
、-vv
、-vvv
来使输出更详细。 - 使用
-q
禁用输出,除了错误。 - 使用
-n
禁用用户输入(非交互模式)。
<?php use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { protected function configure() { $this ->addArgument('argument', InputArgument::REQUIRED) ->addOption('option', null, InputOption::VALUE_REQUIRED); } public function doSmthAction(InputInterface $input, $argument, $option) { $argumentValue = $input->getArgument('argument'); $optionValue = $input->getArgument('option'); $argumentValue = $argument; // same $optionValue = $option; // same } public function doSmth2Action(OutputInterface $output) { $output->writeln('Hello'); } }
您可以通过名称访问定义的输入。
有一些辅助方法用于用户交互。
要获取布尔结果 $this->askConfirmationQuestion()
。
要从数组选项中选择 $this->askChoiceQuestion()
(它返回值,而不是键)。
要获取字符串输入 $this->askQuestion()
。
<?php class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { public function askSmthAction() { $result = $this->askConfirmationQuestion('Are you sure?', false); $result = $this->askChoiceQuestion('Pick a color', ['red', 'green', 'blue'], 'green'); $result = $this->askQuestion('Enter your name', 'anonymous'); } }
您可以实现自己的问题或覆盖这些方法。
自动注入资源。
资源是操作的参数。
您可以使用资源名称或类名称将现有资源连接到操作调用。
类和名称可以指向不同的资源,因此结果可能是不可预测的。请避免这些情况。
资源的优先级顺序为:
- 配方中的
get[A-Z]
方法,某些类型的动态属性。 - 公共非静态配方属性。
- 输入参数。
- 输入选项。
- 容器服务(内部)。
- 容器参数(内部)。
如果有具有相同名称或类名称的资源,则将使用第一个出现。
Snake_case 和 camelCase 视为相同。
可用的容器服务。
- 输入(Symfony\Component\Console\Input\InputInterface)。
- 输出(Symfony\Component\Console\Output\OutputInterface)。
- 样式(Symfony\Component\Console\Style\SymfonyStyle)。
- 连接(Aes3xs\Tasker\Connection\Connection)。
- 记录器(Monolog\Logger)。
- 运行器(Aes3xs\Tasker\Runner\Runner)。
还有一些辅助工具。
- shell(Aes3xs\Tasker\Service\Shell)。
- composer(Aes3xs\Tasker\Service\Composer)。
- git(Aes3xs\Tasker\Service\Git)。
- 发布者(Aes3xs\Tasker\Service\Releaser)。
- symfony(Aes3xs\Tasker\Service\Symfony)。
<?php class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { /** * Can be obtained by: * dynamicRecipeResource * dynamic_recipe_resource */ public function getDynamicRecipeResource($dependency) { return $dependency * 10; } /** * Can be obtained by: * SomeClass * recipePropertyObject * recipe_property_object */ public $recipePropertyObject; // Contains \SomeClass /** * Can be obtained by: * recipeProperty * recipe_property */ public $recipeProperty; /** * Can be obtained by: * recipePropertyCallback * recipe_property_callback */ public $recipePropertyCallback; public function setupRecipeCallbackProperty() { // Callback arguments resolving same way as actions $this->recipePropertyCallback = function ($dependency) { return $dependency * 10; }; } protected function configure() { /** * Can be obtained by: * inputArgument * input_argument */ $this->addArgument('input_argument'); /** * Can be obtained by: * inputOption * input_option */ $this->addOption('input_option'); } /** * Can be obtained by class or name */ public function containerServicesAction( \Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output, \Symfony\Component\Console\Style\SymfonyStyle $style, \Aes3xs\Tasker\Connection\Connection $connection, \Monolog\Logger $logger, \Aes3xs\Tasker\Runner\Runner $runner, \Aes3xs\Tasker\Service\Shell $shell, \Aes3xs\Tasker\Service\Composer $composer, \Aes3xs\Tasker\Service\Git $git, \Aes3xs\Tasker\Service\Releaser $releaser, \Aes3xs\Tasker\Service\Symfony $symfony ) { // Do smth } }
连接。
简单地在使用之前设置连接参数。
它将在第一次调用时自动初始化。目前不提供重用或重新连接。
本地连接相当简单。要使用它,请将主机留为 null。
远程连接提供了三种认证类型。
- 登录/密码,使用
setPassword('password')
。 - 公钥(可选密码),使用
setPublicKey($path or key itself)
。 - 代理转发,使用
setForwarding(true)
。
基于 PhpSecLib 的远程连接。
实现了 Ssh 扩展,但在转发损坏的情况下已禁用。
当您手动使用 ssh 转发时,存在环境变量 $SSH_AUTH_SOCK,其中包含代理套接字的路径。因此,您可以继续使用转发来连接到另一个服务器。
使用 php ssh 扩展此变量不存在。这是一个非常必要的功能,因此目前我切换回 PhpSecLib。
<?php class TestRecipe extends \Aes3xs\Tasker\AbstractRecipe { protected function connect(\Aes3xs\Tasker\Connection\Connection $connection) { // By default connection is local // Local means host is null, 127.0.0.1 or localhost // Remote connection $connection ->getParameters() ->setHost('192.168.1.1') // Default port 22 ->setLogin('root') ->setPassword('password'); $connection->exec('echo hello'); } }
服务。
Shell。
Shell 是基于连接构建的。因此,如果您已经连接到远程(或本地)服务器,您也可以使用 Shell。
它包含大多数可用的 shell 命令。
- exec
- ln
- chmod
- chown
- rm
- mkdir
- touch
- readlink
- realpath
- dirname
- ls
- which
辅助方法
- exists()
- isFile()
- isDir()
- isLink()
- isWritable()
- isReadable()
- write()
- read()
- copy()
- copyPaths()
- linkPaths()
- checkWritable()
- checkReadable()
如果您想以另一个用户身份运行所有命令,请使用 setUser() 进行配置。因此,所有命令都将使用 sudo -EHu USER bash -c "COMMAND"
预先运行。SSH 代理转发也将可用。
如果您想从特定目录运行所有命令,请使用 setCwd() 进行配置。
这些选项仅对 Shell 服务本身有效,而不是连接。
其他服务(发布者、Git、Composer、Symfony)也使用 Shell,因此请考虑这一点。
发布者
发布者管理发布。它准备目录结构以存储您的发布,并在部署或回滚时链接它们。
首先调用setDeployPath()来指向包含所有相关内容的根目录。
/var/www/project <- deploy_path
│
├─ releases
│ ├─ 20180101221100 (YmdHis format)
│ ├─ 20180101221101
│ ├─ 20180101221102
│ ├─ 20180101221103
│ ├─ 20180101221104 (Symfony example) <- current_path
│ │ ├─ app
│ │ │ └─ config
│ │ │ └─ ~parameters.yml (symlink in shared)
│ │ ├─ var
│ │ │ └─ ~logs (symlink in shared)
│ │ └─ release.lock (exists only in completed releases)
│ │
│ └─ 20180101221105 (deploy in progress...) <- release_path
│
├─ ~current (symlink to 20180101221104 for example)
│
├─ shared
│ ├─ app
│ │ └─ config
│ │ └─ parameters.yml
│ └─ var
│ └─ logs
│ └─ ...
│
└─ deploy.lock
然后调用prepare()来构建目录结构。
使用lock()、unlock()和isLocked()来控制deploy.lock文件,使用它来防止同时部署。
调用create()来创建新发布,调用release()来创建当前链接的符号链接并添加release.lock文件,调用link()来创建特定现有发布的符号链接,调用rollback()来创建上一个发布的符号链接,调用cleanup()来删除未使用的发布。使用getReleaseList()获取所有可用的发布,它只显示已完成的发布,忽略损坏和意外的目录和文件。
使用updateReleaseShares()来更新共享文件和目录。共享在所有发布中都是相同的,并且它们被单独存储。
使用getCurrentReleasePath()来获取当前链接的发布路径。
使用getCurrentReleaseName()来获取当前发布的名称(目录名明显)。
Git
使用私有仓库的首选方式是代理转发。但您也可以设置setKeyPath()来使用您的公钥。
方法
- checkout()
- cloneAt()
- log()
- fetch()
- getBranches()
- getCurrentBranch()
Composer
方法
- install()
- update()
- download()用于下载phar存档
Symfony
首先设置控制台路径setConsolePath(),通常是release_path/bin/console
- setEnv()
- setDebug()
- setInteractive()
- runCommand()
将参数和选项传递给runCommand()。选项是一个关联数组,键是选项名称(如果值为null,则选项被视为标志)。