diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..216f1625 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,37 @@ +# PHP CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-php/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: factorial/phabalicious-test-runner + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mysql:9.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v2-dependencies-{{ checksum "composer.json" }} + # fallback to using the latest cache if no exact match is found + - v2-dependencies- + + - run: php /composer.phar install + + - save_cache: + paths: + - ./vendor + key: v2-dependencies-{{ checksum "composer.json" }} + + # run tests! + - run: cd tests; ../vendor/bin/phpunit --exclude-group docker . diff --git a/.fabfile.yaml b/.fabfile.yaml deleted file mode 100644 index f6d0406e..00000000 --- a/.fabfile.yaml +++ /dev/null @@ -1,325 +0,0 @@ -name: bi-france -deploymentModule: bif_deploy - -requires: 2.0.0 -needs: - - ssh - - docker - - drush7 - - git - - files - -dockerKeyFile: ./ssh-keys/docker-root-key -dockerAuthorizedKeyFile: https://keys.factorial.io/keys/f5eca34f-f857-4e7b-829d-08f85244dd77 -dockerKnownHostsFile: ./ssh-keys/known_hosts - -# BI Repo. -# repository: https://stephan@bitbucket.bi-scrum.com/scm/fr012/site-code.git - -# Gitlab Repo. -repository: ssh://git@source.factorial.io:2222/BI/fr012/site-code.git - -inheritsFrom: - - https://config.factorial.io/docker/2.0/xdebug.yaml - - -excludeFiles: - backup: - - "styles" - - "tmp" - copyFrom: - - "tmp" - - "styles" - - "php" - - "js" - - "css" - - "twig_cache" - - "xmlsitemap" - -scripts: - behatInstall: - - cd %host.gitRootFolder% && composer install --ignore-platform-reqs - behat: - - fail_on_error(0) - - cd %host.gitRootFolder%.tools/behat && %host.gitRootFolder%/vendor/bin/behat %arguments.combined% - - fail_on_error(1) - copyHtAccess: - - cd %host.gitRootFolder% && cp .tools/htaccess .htaccess - reindex-content: - - cd %host.siteFolder% && drush solr-delete-index; drush solr-mark-all; drush solr-index; - patternlab: - - cd %host.rootFolder%/sites/all/themes/custom/bif_frontend && php core/console --generate - setSolrUrlForDev: - - "drush vset apachesolr_default_environment solr" - - > - drush vset apachesolr_environments - --format=json - '{"solr": {"url": "http://solr:8983/solr/mycore" } }' - resetAdminUser: - - cd %host.siteFolder%; drush sql-query "update users set name='admin' where uid=1" - -common: - resetPrepare: - dev: - - execute(script, resetAdminUser) - - copyDBFromFinished: - dev: - - execute(script, reindex-content) - stage: - - execute(script, reindex-content) - test: - - execute(script, reindex-content) - prod: - - execute(script, reindex-content) - - reset: - dev: - - "drush vset devel_rebuild_theme_registry FALSE" - - "drush vdel -y googleanalytics_account" - - "drush vset -y --exact cache 0" - - "drush vset -y --exact preprocess_css 0" - - "drush vset -y --exact preprocess_js 0" - - "drush en shield -y" - - "drush vset -y shield_user rmh" - - "drush vset -y shield_pass rmh" - - "drush vset -y shield_print 'Enter security credentials to access'" - - "drush vset admin_menu_tweak_modules 1" - - "drush vset admin_menu_tweak_permissions 1" - - execute(script, patternlab) - - execute(script, setSolrUrlForDev) - test: - - execute(script, patternlab) - prod: - - execute(script, patternlab) - -dockerHosts: - default: - tasks: - start: - - docker start %name% - stop: - - docker stop %name% - logs: - - docker logs %name% - ps: - - docker ps - mbb: - environment: - VHOST: "%host.docker.vhost%" - COMPOSE_FILE: "docker-compose.yml:docker-compose-mbb.yml" - inheritsFrom: - - https://config.factorial.io/mbb/2.0/mbb-docker.yaml - - https://config.factorial.io/docker/2.0/docker-compose.yaml - - clients.factorial.io: - environment: - VHOST: "%host.docker.vhost%" - inheritsFrom: - - https://config.factorial.io/clients.factorial.io/2.0/d8/docker.yaml - -hosts: - mbb: - adminUser: admin - host: bi-france.test - user: root - password: root - port: 36997 - type: dev - rootFolder: /var/www/ - gitRootFolder: /var/www/ - siteFolder: /sites/default - filesFolder: /sites/default/files - backupFolder: /var/www/backups - branch: int - supportsInstalls: true - scripts: - test: - - echo "Test" - - cd %host.rootFolder%; echo "hello" - test2: - - echo "Test2" - - execute(script, test) - docker: - name: fr012-bi-france_web_1 - configuration: mbb - vhost: >- - bi-france.test - cardiocity.bi-france.test - oncocity.bi-france.test - pneumocity.bi-france.test - spiolto.bi-france.test - pradaxa.bi-france.test - pradaxa-praxbind.bi-france.test - ofev-pharmacist.bi-france.test - ofev-pneumolog.bi-france.test - projectFolder: fr012-bi-france - database: - name: bifrance - user: root - pass: admin - host: mysql - reset: - - "drush vset -y error_level 2" - - "execute(script, copyHtAccess)" - - "drush vset -y file_public_path 'sites/default/files'" - - "drush vset -y file_private_path 'sites/default/files/private'" - - "drush vset -y file_temporary_path 'sites/default/files/private/tmp'" - xdebug: - php_version: 5 - blueprint: - database: - name: bifrance_%slug% - inheritsFrom: mbb - configName: "mbb-%slug%" - siteFolder: /sites/%slug% - filesFolder: /sites/%slug%/files - reset: - - drush vset -y error_level 2 - - execute(script, copyHtAccess) - - drush vset -y file_public_path 'sites/%slug%/files' - - drush vset -y file_private_path 'sites/%slug%/files/private' - - drush vset -y file_temporary_path 'sites/%slug%/files/private/tmp' - - bi-france-int: - port: 22 - branch: int - host: int-fr012.bi-customerhub.com - user: fr012d - type: stage - ignoreSubmodules: true - supportsInstalls: false - supportsCopyFrom: false - rootFolder: /home/fr012d/public_html - gitRootFolder: /home/fr012d/public_html - siteFolder: /sites/default - filesFolder: /sites/default/files - backupFolder: /home/fr012d/backups - gitOptions: - pull: [] - needs: - - ssh - - drush7 - - git - - files - - - bi-france-int-cardiocity: - # Do chmod -R 775 sites/fr012_j4x8/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_j4x8 - filesFolder: /sites/fr012_j4x8/files - - bi-france-int-pneumocity: - # Do chmod -R 775 sites/fr012_nnrs/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_nnrs - filesFolder: /sites/fr012_nnrs/files - - bi-france-int-oncocity: - # Do chmod -R 775 sites/fr012_5i2p/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_5i2p - filesFolder: /sites/fr012_5i2p/files - - bi-france-int-spiolto: - # Do chmod -R 775 sites/fr012_cwxy/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_cwxy - filesFolder: /sites/fr012_cwxy/files - - bi-france-int-ofev-pneumo: - # Do chmod -R 775 sites/fr012_ssd8/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_ssd8 - filesFolder: /sites/fr012_ssd8/files - - bi-france-int-ofev-pharma: - # Do chmod -R 775 sites/fr012_veuy/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_veuy - filesFolder: /sites/fr012_veuy/files - - bi-france-int-pradaxa: - # Do chmod -R 775 sites/fr012_e3zm/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_e3zm - filesFolder: /sites/fr012_e3zm/files - - bi-france-int-pradaxa-praxabind: - # Do chmod -R 775 sites/fr012_d3e5/files when doing first time install. - inheritsFrom: bi-france-int - siteFolder: /sites/fr012_d3e5 - filesFolder: /sites/fr012_d3e5/files - - int.bifrance.hub-staging.hh.rmh.de: - branch: int - configName: int.bifrance.hub-staging.hh.rmh.de - ignoreSubmodules: true - database: - name: bifrance_int_mysql - host: mysql - docker: - configuration: clients.factorial.io - name: bifrancemultisiteint_web_1 - projectFolder: bifrance-multisite--int - vhost: >- - int.bifrance.cardiocity.hub-staging.hh.rmh.de - int.bifrance.oncocity.hub-staging.hh.rmh.de - int.bifrance.pneumocity.hub-staging.hh.rmh.de - int.bifrance.spiolto.hub-staging.hh.rmh.de - int.bifrance.pradaxa.hub-staging.hh.rmh.de - int.bifrance.pradaxa-praxbind.hub-staging.hh.rmh.de - int.bifrance.ofev-pharmacist.hub-staging.hh.rmh.de - int.bifrance.ofev-pneumolog.hub-staging.hh.rmh.de - gitRootFolder: /var/www - inheritsFrom: http://config.factorial.io/clients.factorial.io/2.0/host.yaml - reset: - - "drush vset -y error_level 0" - - "execute(script, copyHtAccess)" - - "drush vset -y file_public_path 'sites/default/files'" - - "drush vset -y file_private_path 'sites/default/files/private'" - - "drush vset -y file_temporary_path 'sites/default/files/private/tmp'" - blueprint: - inheritsFrom: int.bifrance.hub-staging.hh.rmh.de - configName: int.bifrance.%slug%.hub-staging.hh.rmh.de - database: - name: bifrance%slug%_int_mysql - host: mysql - siteFolder: /sites/%slug% - filesFolder: /sites/%slug%/files - reset: - - "drush vset -y error_level 0" - - "execute(script, copyHtAccess)" - - "drush vset -y rmh_config_domain %slug%" - - "drush vset -y file_public_path 'sites/%slug%/files'" - - "drush vset -y file_private_path 'sites/%slug%/files/private'" - - "drush vset -y file_temporary_path 'sites/%slug%/files/private/tmp'" - -blueprints: - - configName: mbb - variants: - - pneumocity - - spiolto - - pradaxapraxbind - - cardiocity - - oncocity - - pradaxa - - ofevpharmacist - - ofevpneumolog - - configName: int.bifrance.hub-staging.hh.rmh.de - variants: - - pneumocity - - spiolto - - pradaxapraxbind - - cardiocity - - oncocity - - pradaxa - - ofevpharmacist - - ofevpneumolog - - -# Currently installed: -# Url : http://int.bifrance.pneumocity.hub-staging.hh.rmh.de/ -# Url : http://int.bifrance.spiolto.hub-staging.hh.rmh.de/ -# Url : http://int.bifrance.pradaxa-praxbind.hub-staging.hh.rmh.de/ diff --git a/.gitignore b/.gitignore index 6ca381cc..502dc756 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ /bin/console /bin/phpunit /build/phabalicious.phar +/docs/site diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2aec5c81 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +- repo: https://github.com/digitalpulp/pre-commit-php.git + sha: 1.3.0 + hooks: + - id: php-lint + files: \.php$ + - id: php-cs + files: \.php$ + args: + - --standard=PSR2 -p + - id: php-cbf + files: \.php$ + args: + - --standard=PSR2 -p + - id: php-unit + files: \.php$ diff --git a/Changelog.md b/Changelog.md index ec30632c..314935c9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,14 +1,48 @@ # Changelog -## 3.0.0 +## 3.0.0-alpha.6 / 2018-12-11 -Fabalicious is now rewritten in PHP, so we changed then name to make the separation more clear. Phabalicious is now a symfony console app and uses a more unix-style approach to arguments and options. E.g. instead of `config:` use `--config=` + * Some bugfixes for ftp-deployments + * Nicer output + * Add docs for shell-autcompletion + * Fix fish autocompletion (sort of) + * Set version number, when not bundling as phar + +## 3.0.0-alpha.5 / 2018-12-08 + +### fixed + + * Use real version number + * Fix phar-build + +## 3.0.0-alpha.4 / 2018-12-08 + +### new + + * New command `self-update`, will download and install the latest available version + * New method `ftp-sync` to deploy code-bases to a remote ftp-instance + * Introduction of a password-manager for retrieving passwords from the user or a special file + +### changed + + * Switch to box for building phars + +### fixed + + * Do not run empty script lines (Fixes #8) + * Set folder for script-phase + * Set rootFolder fot task-specific scripts + * Support legacy host-types + +## 3.0.0 develop + +Fabalicious is now rewritten in PHP, so we changed the name to make the separation more clear. Phabalicious is now a symfony console app and uses a more unix-style approach to arguments and options. E.g. instead of `config:` use `--config=` ### Why the rewrite Python on Mac OS X is hard, multiple versions, multiple locations etc. Every machine needed some magic hands to get fabalicious working on it. Fabalicious itself is written in python 2.x, but the world is moving on to python 3. Fabric, the underlying lib we used for fabalicious is also moving forward to version 2 which is not backwards compatible yet with fabric 1. On the other side we are now maintaining more and more containerized setups where you do not need ssh to run commands in. A popular example is docker and its whole universe. Fabric couldn't help us here, and fabric is moving into a different direction. -And as a specialized Drupal boutique we write PHP all day long. To make it easier for our team to improve the toolset by ourselves and get help from the rest of the community using PHP/ Symfony as a base for the rewrite was a no-brainer. +And as a specialized Drupal boutique we write PHP all day long. To make it easier for our team to improve the toolset by ourselves and get help from the rest of the community, using PHP/ Symfony as a base for the rewrite was a no-brainer. Why not use existing tools, like [robo](https://robo.li/), [deployer](https://deployer.org/) or other tools? These tools are valuable instruments in our tool-belt, but none of them fit our requirements completely. We see phabalicious as a meta-tool integrating with all of them in nice and easy way. We need a lot of flexibility as we need to support a lot of different tech- and hosting-stacks, so we decided to port fabalicious to phabalicious. @@ -55,18 +89,43 @@ Most notably the handling of arguments and options has changed a lot. Fabric gav * new shell-provider `dockerExec` which will start a shell with the help of `docker exec` instead of ssh. * new config-option `shellProvider`, where you can override the shell-provider to your liking. - hosts: - mbb: - shellProvider: docker-exec + hosts: + mbb: + shellProvider: docker-exec * You can get help for a specific task via `phab help `. It will show all possible options and some help. -* docker-compose version 23 changes the schema how names of docker-containers are constructed. To support this change we can now declare the needed service to compute the correct container-name from. - - hosts: - testHost: - docker: - service: web - - +* docker-compose version 23 changes the schema how names of docker-containers are constructed. To support this change we can now declare the needed service to compute the correct container-name from. + + hosts: + testHost: + docker: + service: web + The `name` will be discarded, if a `service`-entry is set. + +* new method `ftp-sync`, it's a bit special. This method creates the app into a temporary folder, and syncs it via `lftp` to a remote instance. Here's a complete example (most of them are provided via sensible defaults): + + excludeFiles: + ftp-sync: + - .git/ + - node_modules + hosts: + ftpSyncSample: + needs: + - git + - ftp-sync + - local + ftp: + user: + password: # + host: + port: 21 + lftpOptions: + - --ignoreTime + - --verbose=3 + - --no-perms + + You can add your password to the file `.phabalicious-credentials` (see passwords.md) so phabalicious pick it up. + + ### Changed * `docker:startRemoteAccess` is now the task `start-remote-access` as it makes more sense. diff --git a/bin/phab b/bin/phab index 61ff551e..efb8efce 100755 --- a/bin/phab +++ b/bin/phab @@ -45,7 +45,11 @@ $output = $container->get(ConsoleOutput::class); /** @var Application $application */ $application = $container->get(Application::class); -$application->setVersion('3.0.0'); +$version = '@git_tag@'; +if ($version[0] == '@') { + $version = '3.0.0'; +} +$application->setVersion($version); $application->setName('phabalicious'); $application->setDefaultCommand('list'); diff --git a/box.json b/box.json new file mode 100644 index 00000000..9c8a0911 --- /dev/null +++ b/box.json @@ -0,0 +1,10 @@ +{ + "main": "bin/phab", + "files": [ + ".env", + "config/services.yml" + ], + "git-tag": "git_tag", + "output": "build/phabalicious.phar", + "compression": "GZ" +} diff --git a/build/create-release.sh b/build/create-release.sh new file mode 100644 index 00000000..e0178356 --- /dev/null +++ b/build/create-release.sh @@ -0,0 +1,27 @@ +GH_USER=factorial-io +GH_PATH=`cat ~/.ghtoken` +GH_REPO=phabalicious +GH_TARGET=master +ASSETS_PATH=./ +VERSION=`git describe --tags | sed 's/-[0-9]-g[a-z0-9]\{7\}//'` +echo "Releasing ${VERSION} ..." +cd .. +composer build-phar +cd build + +res=`curl --user "$GH_USER:$GH_PATH" -X POST https://api.github.com/repos/${GH_USER}/${GH_REPO}/releases \ +-d " +{ + \"tag_name\": \"$VERSION\", + \"target_commitish\": \"$GH_TARGET\", + \"name\": \"$VERSION\", + \"body\": \"new version $VERSION\", + \"draft\": false, + \"prerelease\": false +}"` +echo Create release result: ${res} +rel_id=`echo ${res} | python -c 'import json,sys;print(json.load(sys.stdin)["id"])'` +file_name=phabalicious.phar + +curl --user "$GH_USER:$GH_PATH" -X POST https://uploads.github.com/repos/${GH_USER}/${GH_REPO}/releases/${rel_id}/assets?name=${file_name}\ + --header 'Content-Type: text/javascript ' --upload-file ${ASSETS_PATH}/phabalicious.phar diff --git a/composer.json b/composer.json index 9ad65792..18448d38 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,13 @@ "symfony/finder": "^4.1", "thibaud-dauce/mattermost-php": "^1.2", "twig/twig": "^2.5", - "ext-openssl": "*", - "jakeasmith/http_build_url": "^1.0" + "ext-openssl": "*", + "jakeasmith/http_build_url": "^1.0", + "padraic/phar-updater": "^1.0" }, "require-dev": { "symfony/phpunit-bridge": "^2.8|^3|^4.1", - "phpunit/phpunit": "^7.3", - "macfja/phar-builder": "dev-sf4-compatibility" + "phpunit/phpunit": "^7.3" }, "autoload": { "psr-4": {"Phabalicious\\": "src/"} @@ -42,7 +42,7 @@ "auto-scripts": { }, - "build-phar": "php -d phar.readonly=0 vendor/bin/phar-builder package", + "build-phar": "box compile", "install-phar": "cp ./build/phabalicious.phar /usr/local/bin/phab; chmod u+x /usr/local/bin/phab" }, "extra": { diff --git a/composer.lock b/composer.lock index 4253cb12..56bdcdd2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,64 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0a7e621832339106f83ee1b5614ed19a", + "content-hash": "9bbcaac27573c8bb1c358baf775fce0a", "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2018-10-18T06:09:13+00:00" + }, { "name": "composer/semver", "version": "1.4.2", @@ -186,32 +242,33 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "9f83dded91781a01c63574e387eaa769be769115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -241,13 +298,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2018-12-04T20:46:45+00:00" }, { "name": "jakeasmith/http_build_url", @@ -282,6 +340,127 @@ "description": "Provides functionality for http_build_url() to environments without pecl_http.", "time": "2017-05-01T15:36:40+00:00" }, + { + "name": "padraic/humbug_get_contents", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/humbug/file_get_contents.git", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/file_get_contents/zipball/dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "ext-openssl": "*", + "php": "^5.3 || ^7.0 || ^7.1 || ^7.2" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.1", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false + }, + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Humbug\\": "src/" + }, + "files": [ + "src/function.php", + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Secure wrapper for accessing HTTPS resources with file_get_contents for PHP 5.3+", + "homepage": "https://github.com/padraic/file_get_contents", + "keywords": [ + "download", + "file_get_contents", + "http", + "https", + "ssl", + "tls" + ], + "time": "2018-02-12T18:47:17+00:00" + }, + { + "name": "padraic/phar-updater", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/humbug/phar-updater.git", + "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/phar-updater/zipball/d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", + "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", + "shasum": "" + }, + "require": { + "padraic/humbug_get_contents": "^1.0", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Humbug\\SelfUpdate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + } + ], + "description": "A thing to make PHAR self-updating easy and secure.", + "keywords": [ + "humbug", + "phar", + "self-update", + "update" + ], + "time": "2018-03-30T12:52:15+00:00" + }, { "name": "psr/container", "version": "1.0.0", @@ -383,16 +562,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -426,7 +605,47 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" }, { "name": "stecman/symfony-console-completion", @@ -475,16 +694,16 @@ }, { "name": "symfony/config", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238" + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", - "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", + "url": "https://api.github.com/repos/symfony/config/zipball/005d9a083d03f588677d15391a716b1ac9b887c0", + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0", "shasum": "" }, "require": { @@ -507,7 +726,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -534,24 +753,25 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-30T22:21:14+00:00" }, { "name": "symfony/console", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595" + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595", + "url": "https://api.github.com/repos/symfony/console/zipball/4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/contracts": "^1.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -575,7 +795,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -602,20 +822,88 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:30:44+00:00" + "time": "2018-11-27T07:40:44+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" }, { "name": "symfony/debug", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d" + "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/19090917b848a799cbae4800abf740fe4eb71c1d", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d", + "url": "https://api.github.com/repos/symfony/debug/zipball/e0a2b92ee0b5b934f973d90c2f58e18af109d276", + "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276", "shasum": "" }, "require": { @@ -631,7 +919,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -658,37 +946,39 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-28T18:24:18+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483" + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e72ee2c23d952e4c368ee98610fa22b79b89b483", - "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4adc57a48d3fa7f394edfffa9e954086d7740e5", + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5", "shasum": "" }, "require": { "php": "^7.1.3", - "psr/container": "^1.0" + "psr/container": "^1.0", + "symfony/contracts": "^1.0" }, "conflict": { - "symfony/config": "<4.1.1", + "symfony/config": "<4.2", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-contracts-implementation": "1.0" }, "require-dev": { - "symfony/config": "~4.1", + "symfony/config": "~4.2", "symfony/expression-language": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -702,7 +992,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -729,20 +1019,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-10-31T10:54:16+00:00" + "time": "2018-12-02T15:59:36+00:00" }, { "name": "symfony/dotenv", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "9f3074b55bc56627f61fb2c17d573ee7df8e1319" + "reference": "97f135ab40f969cbeae27d482ff63acbc33dbe2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/9f3074b55bc56627f61fb2c17d573ee7df8e1319", - "reference": "9f3074b55bc56627f61fb2c17d573ee7df8e1319", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/97f135ab40f969cbeae27d482ff63acbc33dbe2a", + "reference": "97f135ab40f969cbeae27d482ff63acbc33dbe2a", "shasum": "" }, "require": { @@ -754,7 +1044,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -786,24 +1076,25 @@ "env", "environment" ], - "time": "2018-10-12T12:56:03+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "552541dad078c85d9414b09c041ede488b456cd5" + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/552541dad078c85d9414b09c041ede488b456cd5", - "reference": "552541dad078c85d9414b09c041ede488b456cd5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/921f49c3158a276d27c0d770a5a347a3b718b328", + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/contracts": "^1.0" }, "conflict": { "symfony/dependency-injection": "<3.4" @@ -822,7 +1113,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -849,20 +1140,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-10-10T13:52:42+00:00" + "time": "2018-12-01T08:52:38+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981" + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd7bd6535beb1f0a0a9e3ee960666d0598546981", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", "shasum": "" }, "require": { @@ -872,7 +1163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -899,20 +1190,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-30T13:18:25+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/finder", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", "shasum": "" }, "require": { @@ -921,7 +1212,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -948,20 +1239,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/flex", - "version": "v1.1.7", + "version": "v1.1.8", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "803c49664ddc7cbc4be02f41491766be32c90a7f" + "reference": "955774ecf07b10230bb5b44e150ba078b45f68fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/803c49664ddc7cbc4be02f41491766be32c90a7f", - "reference": "803c49664ddc7cbc4be02f41491766be32c90a7f", + "url": "https://api.github.com/repos/symfony/flex/zipball/955774ecf07b10230bb5b44e150ba078b45f68fa", + "reference": "955774ecf07b10230bb5b44e150ba078b45f68fa", "shasum": "" }, "require": { @@ -995,20 +1286,20 @@ } ], "description": "Composer plugin for Symfony", - "time": "2018-11-12T12:25:13+00:00" + "time": "2018-11-15T06:11:38+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af" + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/82d494c1492b0dd24bbc5c2d963fb02eb44491af", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", "shasum": "" }, "require": { @@ -1022,7 +1313,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1049,25 +1340,26 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae" + "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/958be64ab13b65172ad646ef5ae20364c2305fae", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b39ceffc0388232c309cbde3a7c3685f2ec0a624", + "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", + "symfony/contracts": "^1.0.2", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", "symfony/http-foundation": "^4.1.1", @@ -1075,7 +1367,8 @@ }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.1", + "symfony/dependency-injection": "<4.2", + "symfony/translation": "<4.2", "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, @@ -1088,7 +1381,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", + "symfony/dependency-injection": "^4.2", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -1096,7 +1389,7 @@ "symfony/routing": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", + "symfony/translation": "~4.2", "symfony/var-dumper": "^4.1.1" }, "suggest": { @@ -1109,7 +1402,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1136,7 +1429,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-11-03T11:11:23+00:00" + "time": "2018-12-06T17:39:52+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1257,16 +1550,16 @@ }, { "name": "symfony/process", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9" + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e83acef94d979b1de946599ef86b3a352abcdc9", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9", + "url": "https://api.github.com/repos/symfony/process/zipball/2b341009ccec76837a7f46f59641b431e4d4c2b0", + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0", "shasum": "" }, "require": { @@ -1275,7 +1568,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1284,809 +1577,224 @@ }, "exclude-from-classmap": [ "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "time": "2018-10-14T20:48:13+00:00" - }, - { - "name": "symfony/yaml", - "version": "v4.1.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" - }, - { - "name": "thibaud-dauce/mattermost-php", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/ThibaudDauce/mattermost-php.git", - "reference": "7542208205ba160589f54cd8636cbba6fee29a37" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ThibaudDauce/mattermost-php/zipball/7542208205ba160589f54cd8636cbba6fee29a37", - "reference": "7542208205ba160589f54cd8636cbba6fee29a37", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "ThibaudDauce\\Mattermost\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Thibaud Dauce", - "email": "thibaud@dauce.fr", - "homepage": "https://www.formations-laravel.fr" - } - ], - "description": "Mattermost PHP driver to send incoming webhooks", - "homepage": "https://github.com/thibaud-dauce/mattermost-php", - "time": "2018-09-10T16:01:38+00:00" - }, - { - "name": "twig/twig", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/6a5f676b77a90823c2d4eaf76137b771adf31323", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323", - "shasum": "" - }, - "require": { - "php": "^7.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", - "role": "Contributors" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "time": "2018-07-13T07:18:09+00:00" - }, - { - "name": "wikimedia/composer-merge-plugin", - "version": "v1.4.1", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/composer-merge-plugin.git", - "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/81c6ac72a24a67383419c7eb9aa2b3437f2ab100", - "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0", - "php": ">=5.3.2" - }, - "require-dev": { - "composer/composer": "~1.0.0", - "jakub-onderka/php-parallel-lint": "~0.8", - "phpunit/phpunit": "~4.8|~5.0", - "squizlabs/php_codesniffer": "~2.1.0" - }, - "type": "composer-plugin", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - }, - "class": "Wikimedia\\Composer\\MergePlugin" - }, - "autoload": { - "psr-4": { - "Wikimedia\\Composer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bryan Davis", - "email": "bd808@wikimedia.org" - } - ], - "description": "Composer plugin to merge multiple composer.json files", - "time": "2017-04-25T02:31:25+00:00" - } - ], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2017-07-22T11:58:36+00:00" - }, - { - "name": "hoa/consistency", - "version": "1.17.05.02", - "source": { - "type": "git", - "url": "https://github.com/hoaproject/Consistency.git", - "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f", - "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f", - "shasum": "" - }, - "require": { - "hoa/exception": "~1.0", - "php": ">=5.5.0" - }, - "require-dev": { - "hoa/stream": "~1.0", - "hoa/test": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Hoa\\Consistency\\": "." - }, - "files": [ - "Prelude.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" - }, - { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" - } - ], - "description": "The Hoa\\Consistency library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "autoloader", - "callable", - "consistency", - "entity", - "flex", - "keyword", - "library" - ], - "time": "2017-05-02T12:18:12+00:00" - }, - { - "name": "hoa/event", - "version": "1.17.01.13", - "source": { - "type": "git", - "url": "https://github.com/hoaproject/Event.git", - "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54", - "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54", - "shasum": "" - }, - "require": { - "hoa/consistency": "~1.0", - "hoa/exception": "~1.0" - }, - "require-dev": { - "hoa/test": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Hoa\\Event\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" - }, - { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" - } - ], - "description": "The Hoa\\Event library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "event", - "library", - "listener", - "observer" - ], - "time": "2017-01-13T15:30:50+00:00" - }, - { - "name": "hoa/exception", - "version": "1.17.01.16", - "source": { - "type": "git", - "url": "https://github.com/hoaproject/Exception.git", - "reference": "091727d46420a3d7468ef0595651488bfc3a458f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f", - "reference": "091727d46420a3d7468ef0595651488bfc3a458f", - "shasum": "" - }, - "require": { - "hoa/consistency": "~1.0", - "hoa/event": "~1.0" - }, - "require-dev": { - "hoa/test": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Hoa\\Exception\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" - }, - { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" - } - ], - "description": "The Hoa\\Exception library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "exception", - "library" - ], - "time": "2017-01-16T07:53:27+00:00" - }, - { - "name": "hoa/file", - "version": "1.17.07.11", - "source": { - "type": "git", - "url": "https://github.com/hoaproject/File.git", - "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", - "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", - "shasum": "" - }, - "require": { - "hoa/consistency": "~1.0", - "hoa/event": "~1.0", - "hoa/exception": "~1.0", - "hoa/iterator": "~2.0", - "hoa/stream": "~1.0" - }, - "require-dev": { - "hoa/test": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Hoa\\File\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" - }, - { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" - } - ], - "description": "The Hoa\\File library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "Socket", - "directory", - "file", - "finder", - "library", - "link", - "temporary" - ], - "time": "2017-07-11T07:42:15+00:00" - }, - { - "name": "hoa/iterator", - "version": "2.17.01.10", - "source": { - "type": "git", - "url": "https://github.com/hoaproject/Iterator.git", - "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc", - "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc", - "shasum": "" - }, - "require": { - "hoa/consistency": "~1.0", - "hoa/exception": "~1.0" - }, - "require-dev": { - "hoa/test": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Hoa\\Iterator\\": "." - } + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "The Hoa\\Iterator library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "iterator", - "library" - ], - "time": "2017-01-10T10:34:47+00:00" + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-11-20T16:22:05+00:00" }, { - "name": "hoa/protocol", - "version": "1.17.01.14", + "name": "symfony/yaml", + "version": "v4.2.1", "source": { "type": "git", - "url": "https://github.com/hoaproject/Protocol.git", - "reference": "5c2cf972151c45f373230da170ea015deecf19e2" + "url": "https://github.com/symfony/yaml.git", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2", - "reference": "5c2cf972151c45f373230da170ea015deecf19e2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c41175c801e3edfda90f32e292619d10c27103d7", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7", "shasum": "" }, "require": { - "hoa/consistency": "~1.0", - "hoa/exception": "~1.0" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" }, "require-dev": { - "hoa/test": "~2.0" + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { "psr-4": { - "Hoa\\Protocol\\": "." + "Symfony\\Component\\Yaml\\": "" }, - "files": [ - "Wrapper.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "The Hoa\\Protocol library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "library", - "protocol", - "resource", - "stream", - "wrapper" - ], - "time": "2017-01-14T12:26:10+00:00" + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" }, { - "name": "hoa/stream", - "version": "1.17.02.21", + "name": "thibaud-dauce/mattermost-php", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/hoaproject/Stream.git", - "reference": "3293cfffca2de10525df51436adf88a559151d82" + "url": "https://github.com/ThibaudDauce/mattermost-php.git", + "reference": "7542208205ba160589f54cd8636cbba6fee29a37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82", - "reference": "3293cfffca2de10525df51436adf88a559151d82", + "url": "https://api.github.com/repos/ThibaudDauce/mattermost-php/zipball/7542208205ba160589f54cd8636cbba6fee29a37", + "reference": "7542208205ba160589f54cd8636cbba6fee29a37", "shasum": "" }, "require": { - "hoa/consistency": "~1.0", - "hoa/event": "~1.0", - "hoa/exception": "~1.0", - "hoa/protocol": "~1.0" - }, - "require-dev": { - "hoa/test": "~2.0" + "guzzlehttp/guzzle": "^6.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Hoa\\Stream\\": "." + "ThibaudDauce\\Mattermost\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Ivan Enderlin", - "email": "ivan.enderlin@hoa-project.net" - }, - { - "name": "Hoa community", - "homepage": "https://hoa-project.net/" + "name": "Thibaud Dauce", + "email": "thibaud@dauce.fr", + "homepage": "https://www.formations-laravel.fr" } ], - "description": "The Hoa\\Stream library.", - "homepage": "https://hoa-project.net/", - "keywords": [ - "Context", - "bucket", - "composite", - "filter", - "in", - "library", - "out", - "protocol", - "stream", - "wrapper" - ], - "time": "2017-02-21T16:01:06+00:00" + "description": "Mattermost PHP driver to send incoming webhooks", + "homepage": "https://github.com/thibaud-dauce/mattermost-php", + "time": "2018-09-10T16:01:38+00:00" }, { - "name": "league/event", - "version": "2.1.2", + "name": "twig/twig", + "version": "v2.5.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/event.git", - "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd" + "url": "https://github.com/twigphp/Twig.git", + "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/event/zipball/e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", - "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/6a5f676b77a90823c2d4eaf76137b771adf31323", + "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^7.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "~2.0.0" + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.5-dev" } }, "autoload": { + "psr-0": { + "Twig_": "lib/" + }, "psr-4": { - "League\\Event\\": "src/" + "Twig\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frenky.net" + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" } ], - "description": "Event package", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", "keywords": [ - "emitter", - "event", - "listener" + "templating" ], - "time": "2015-05-21T12:24:47+00:00" + "time": "2018-07-13T07:18:09+00:00" }, { - "name": "macfja/phar-builder", - "version": "dev-sf4-compatibility", + "name": "wikimedia/composer-merge-plugin", + "version": "v1.4.1", "source": { "type": "git", - "url": "https://github.com/MacFJA/PharBuilder.git", - "reference": "97f6f22408c4381c3a9570ca626bec105e6dccda" + "url": "https://github.com/wikimedia/composer-merge-plugin.git", + "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MacFJA/PharBuilder/zipball/97f6f22408c4381c3a9570ca626bec105e6dccda", - "reference": "97f6f22408c4381c3a9570ca626bec105e6dccda", + "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/81c6ac72a24a67383419c7eb9aa2b3437f2ab100", + "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100", "shasum": "" }, "require": { - "league/event": "^2.1", - "macfja/symfony-console-filechooser": "~0.2 || ~1", - "neutron/signal-handler": "^1.0", - "php": ">=5.4.0", - "rych/bytesize": "~1.0", - "symfony/console": "~2.7 || ^3.0 || ^4.0", - "symfony/finder": "^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0", - "webignition/readable-duration": "^0.2" + "composer-plugin-api": "^1.0", + "php": ">=5.3.2" }, "require-dev": { - "phpmd/phpmd": "^2.4", - "sebastian/phpcpd": "^2.0", - "squizlabs/php_codesniffer": "^2.5" + "composer/composer": "~1.0.0", + "jakub-onderka/php-parallel-lint": "~0.8", + "phpunit/phpunit": "~4.8|~5.0", + "squizlabs/php_codesniffer": "~2.1.0" }, - "bin": [ - "bin/phar-builder", - "bin/phar-builder.php" - ], - "type": "library", + "type": "composer-plugin", "extra": { - "phar-builder": { - "skip-shebang": false, - "include-dev": false, - "entry-point": "bin/phar-builder.php", - "compression": "None", - "name": "phar-builder.phar", - "output-dir": "./", - "include": [ - "bin" - ] - } + "branch-alias": { + "dev-master": "1.3.x-dev" + }, + "class": "Wikimedia\\Composer\\MergePlugin" }, "autoload": { "psr-4": { - "MacFJA\\PharBuilder\\": "app/" + "Wikimedia\\Composer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2095,34 +1803,48 @@ ], "authors": [ { - "name": "MacFJA" + "name": "Bryan Davis", + "email": "bd808@wikimedia.org" } ], - "description": "CLI tool for create phar of your composer based project", - "time": "2018-10-28T11:59:57+00:00" - }, + "description": "Composer plugin to merge multiple composer.json files", + "time": "2017-04-25T02:31:25+00:00" + } + ], + "packages-dev": [ { - "name": "macfja/symfony-console-filechooser", - "version": "1.0.0", + "name": "doctrine/instantiator", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/MacFJA/Symfony-Console-Filechooser.git", - "reference": "f27248a6993718fcc469d442e305e1d83f5e1eb5" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MacFJA/Symfony-Console-Filechooser/zipball/f27248a6993718fcc469d442e305e1d83f5e1eb5", - "reference": "f27248a6993718fcc469d442e305e1d83f5e1eb5", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "hoa/file": "~1", - "symfony/console": "~2.6 || ^3.0 || ^4.0" + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, "autoload": { "psr-4": { - "MacFJA\\Symfony\\Console\\Filechooser\\": "lib/" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2131,21 +1853,18 @@ ], "authors": [ { - "name": "MacFJA" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" } ], - "description": "Filechooser Helper for Symfony console (with autocompletion support)", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", "keywords": [ - "autocomplete", - "autocompletion", - "chooser", - "console", - "file", - "filesystem", - "interactive", - "symfony" + "constructor", + "instantiate" ], - "time": "2018-10-21T12:14:48+00:00" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "myclabs/deep-copy", @@ -2195,49 +1914,6 @@ ], "time": "2018-06-11T23:09:50+00:00" }, - { - "name": "neutron/signal-handler", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/romainneutron/signal-handler.git", - "reference": "5f2069bf4a5901a65be51f57ea60779a279564c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/romainneutron/signal-handler/zipball/5f2069bf4a5901a65be51f57ea60779a279564c1", - "reference": "5f2069bf4a5901a65be51f57ea60779a279564c1", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~3.7" - }, - "type": "library", - "autoload": { - "psr-0": { - "Neutron": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Romain Neutron", - "email": "imprec@gmail.com", - "homepage": "http://www.lickmychip.com/" - } - ], - "description": "A library to ease the use of signal handling.", - "keywords": [ - "signal" - ], - "time": "2014-01-15T17:24:13+00:00" - }, { "name": "phar-io/manifest", "version": "1.0.3", @@ -2809,16 +2485,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.4.3", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64" + "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c151651fb6ed264038d486ea262e243af72e5e64", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/520723129e2b3fc1dc4c0953e43c9d40e1ecb352", + "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352", "shasum": "" }, "require": { @@ -2839,7 +2515,7 @@ "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", @@ -2863,7 +2539,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -2889,52 +2565,7 @@ "testing", "xunit" ], - "time": "2018-10-23T05:57:41+00:00" - }, - { - "name": "rych/bytesize", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/rchouinard/bytesize.git", - "reference": "297e16ea047461b91e8d7eb90aa46aaa52917824" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rchouinard/bytesize/zipball/297e16ea047461b91e8d7eb90aa46aaa52917824", - "reference": "297e16ea047461b91e8d7eb90aa46aaa52917824", - "shasum": "" - }, - "require": { - "ext-bcmath": "*", - "php": ">=5.3.4" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Rych\\ByteSize\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ryan Chouinard", - "email": "rchouinard@gmail.com", - "homepage": "http://ryanchouinard.com" - } - ], - "description": "Utility component for nicely formatted file sizes.", - "homepage": "https://github.com/rchouinard/bytesize", - "keywords": [ - "filesize" - ], - "time": "2014-04-04T18:06:18+00:00" + "time": "2018-12-07T07:08:12+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -3103,28 +2734,28 @@ }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3149,7 +2780,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2018-11-25T09:31:21+00:00" }, { "name": "sebastian/exporter", @@ -3501,16 +3132,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "2474c5d4a5e3431fee2f6f0dddde9d34983d9ceb" + "reference": "3f03b625710f24071e2937e88112e9a19099c9eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2474c5d4a5e3431fee2f6f0dddde9d34983d9ceb", - "reference": "2474c5d4a5e3431fee2f6f0dddde9d34983d9ceb", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/3f03b625710f24071e2937e88112e9a19099c9eb", + "reference": "3f03b625710f24071e2937e88112e9a19099c9eb", "shasum": "" }, "require": { @@ -3529,7 +3160,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" }, "thanks": { "name": "phpunit/phpunit", @@ -3563,7 +3194,7 @@ ], "description": "Symfony PHPUnit Bridge", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "theseer/tokenizer", @@ -3605,49 +3236,6 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "time": "2017-04-07T12:08:54+00:00" }, - { - "name": "webignition/readable-duration", - "version": "0.2", - "source": { - "type": "git", - "url": "https://github.com/webignition/readable-duration.git", - "reference": "fe7c33e259f82a015db69272e4eaa53c3f394230" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webignition/readable-duration/zipball/fe7c33e259f82a015db69272e4eaa53c3f394230", - "reference": "fe7c33e259f82a015db69272e4eaa53c3f394230", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jon Cram", - "email": "jon@webignition.net" - } - ], - "description": "Convert a value in seconds into a human-readable convenience duration", - "homepage": "https://github.com/webignition/readable-duration", - "keywords": [ - "duration", - "humand-readable", - "readable", - "time" - ], - "time": "2013-03-28T15:26:25+00:00" - }, { "name": "webmozart/assert", "version": "1.3.0", @@ -3701,9 +3289,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "macfja/phar-builder": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docs/docs/Changelog.md b/docs/docs/Changelog.md new file mode 120000 index 00000000..639240cb --- /dev/null +++ b/docs/docs/Changelog.md @@ -0,0 +1 @@ +../../Changelog.md \ No newline at end of file diff --git a/docs/docs/app-create-destroy.md b/docs/docs/app-create-destroy.md new file mode 100644 index 00000000..c0e946fd --- /dev/null +++ b/docs/docs/app-create-destroy.md @@ -0,0 +1,63 @@ +# Creating/ destroying an app from existing configuration + +Phabalicious can create or delete a complete app with two commands: + + * `phab --config= app:create --copy-from=` + * `phab --config= app:destroy` + +Both commands executes a list of stages, which can be influenced via configuration + +## the standard stages + +These are the standard stages. You can override them by adding them to the global section of your fabfile: + +```yaml +appStages: + create: + - stage: installCode + - stage: spinUp + - stage: installDependencies + - stage: install + createCode: + - stage: installCode + - stage: installDependencies + deploy: + - stage: spinUp + destroy: + - stage: spinDown + - stage: deleteContainers +``` + +`createCode` is only used by the `ftp-sync`-method, to create a complete code version of an app. + +## Creating a new app + +Run the phab-command as usual. If you want to copy from an existing installation, add the `--copy-from`-option. + +```shell +phab --config= app:create +``` + +If the app is already created (there's an existing `.projectCreated`-file in the root of the installation), only the `deploy`-stage will be executed, and afterwards the deploy-task will be executed. + +If the app is not created yet, all `create`-stages will be executed. If nothing went wrong you should have a running installation of the given configuration + +## Destroying an app + +```shell +phab --config= app:destroy +``` + +This will execute all `destroy`-stages and delete the project-folder afterwards. + +## Blueprints + +Both commands work best when using blueprints. This will allow to create a config and an app from a single string like a name of a feature. + +Some examples: + +```shell +phab --config=some-config --blueprint=feature/ create:app +``` + +Will create a new app from a blueprinted config. \ No newline at end of file diff --git a/docs/docs/app-scaffold.md b/docs/docs/app-scaffold.md new file mode 100644 index 00000000..38fe6cd6 --- /dev/null +++ b/docs/docs/app-scaffold.md @@ -0,0 +1,192 @@ +# Scaffolding a new app + +Phabalicious has a simple but powerful scaffold-command. It will read a yml-file and interpret the contents. It will use the existing pattern-replacement used for scripts and twig for changing file-contents. The scaffolding and fixture-files can live remotely or on your file-system. + +Here's an example of a scaffold-file: + +```yaml +variables: + composerProject: drupal-composer/drupal-project:8.x-dev + webRoot: web + +assets: + - .fabfile.yaml + - docker-compose.yml + - docker-compose-mbb.yml + +deployModuleAssets: + - deployModule/%shortName%_deploy.info.yml + - deployModule/%shortName%_deploy.module + - deployModule/%shortName%_deploy.install + +scaffold: + - rm -rf %rootFolder% + - composer create-project %composerProject% %rootFolder% --stability dev --no-interaction + - cd %rootFolder%; composer require drupal/coffee + - cd %rootFolder%; composer require drupal/devel + - copy_assets(%rootFolder%) + - copy_assets(%rootFolder%/%webRoot%/modules/custom/%shortName%_deploy, deployModuleAssets) + - cd %rootFolder%; git init . + - cd %rootFolder%; git add . + - cd %rootFolder%; git commit -m "Initial commit by phabalicious" +``` + +The fabfile needs at least the `scaffold`-section. The `scaffold`-section is a list of commands, executed by phabalicious one by one. It will use the pattern-replacement known for scripts. + +## The `scaffold`-section +Phabalicious will provide the following replacement-patterns out of the box: + +|Pattern|Value| +|--------|-------| +|%rootFolder%|The folder the app will be installed into| +|%name%|The name of the app| +|%projectFolder%|a cleaned version of the app-name, suitable for machine-names.| +|%shortName%|The short-name| +|%uuid%|a random-uuid| + +## the `variables`-section +You can add own variables via the `variables`-section. In the above example, the variable `composerProject` will be available in the `scaffold`-section via the pattern `%composerProject%` + +## the `assets`-section +You can have multiple assets-sections, `assets` is a default one. It contains a list if template files which will be processed by twig and placed at a specific location. You'll have all variables available as twig-variables inside the template, to use them, just use `{{ theNameOfVariable }}` e.g. `{{ name }}` +To add a new asset-section, just use a new root-level key (in the above example this would be `deploymentModuleAssets` + +The assets-paths must be relative to the yaml-file containing the scaffold-commands. + +## the internal command `copy_assets` + +`copy_assets` can be used in the scaffold-section to copy assets into a specific location. The syntax is + +``` +copyAssets(, ) +``` + +Phabalicious will load the asset-file, apply the replacement-patterns to the file-name (see the deploymentAssets for an example) and parse the content via twig. The result will bee stored inside the `` + +## Inheritance + +Similar to other parts of Phabalicious, scaffold-files can use inheritance, for example to use the above scaffold-file, but install drupal commerce: + +```yaml +inheritsFrom: + - drupal-8.yml + - +variables: + composerProject: drupalcommerce/project-base +``` + +This will inherit all content from the drupal-8.yml-file and merged with this content. This means, the `composerProject´-variable will be overridden, but everything else will be inherited. This makes it easy to reuse existing scaffold-files + +## Examples + +### Drupal 8 (d8.yml) + +```yaml +variables: + composerProject: drupal-composer/drupal-project:8.x-dev + webRoot: web +assets: + - .fabfile.yaml + - docker-compose.yml + - docker-compose-mbb.yml + +deployModuleAssets: + - deployModule/%shortName%_deploy.info.yml + - deployModule/%shortName%_deploy.module + - deployModule/%shortName%_deploy.install + +sshKeys: + - ssh-keys/docker-root-key + - ssh-keys/docker-root-key.pub + - ssh-keys/known_hosts + +scaffold: + - rm -rf %rootFolder% + - composer create-project %composerProject% %rootFolder% --stability dev --no-interaction + - cd %rootFolder%; composer require drupal/coffee + - cd %rootFolder%; composer require drupal/devel + - copy_assets(%rootFolder%) + - copy_assets(%rootFolder%/%webRoot%/modules/custom/%shortName%_deploy, deployModuleAssets) + - copy_assets(%rootFolder%/ssh-keys, sshKeys) + - cd %rootFolder%; git init . + - cd %rootFolder%; git add . + - cd %rootFolder%; git commit -m "Initial commit by phabalicious" +``` + +### drupal commerce + +```yaml +inheritsFrom: + - d8.yml + +variables: + composerProject: drupalcommerce/project-base +``` + +### thunder + +```yaml +inheritsFrom: + - d8.yml + +variables: + composerProject: burdamagazinorg/thunder-project + webRoot: docroot +``` + +### Laravel + +#### the scaffold-file: +```yaml +variables: + composerProject: laravel/laravel:5.4 + webRoot: public + +assets: + - .fabfile.yaml + - docker-compose.yml + - docker-compose-mbb.yml + +scaffold: + - rm -rf %rootFolder% + - composer create-project %composerProject% %rootFolder% --stability dev --no-interaction + - cd %rootFolder%; composer require factorial-io/fabalicious:dev-develop + - copy_assets(%rootFolder%) + - cd %rootFolder%; git init . + - cd %rootFolder%; git add . + - cd %rootFolder%; git commit -m "Initial commit by phabalicious" +``` + +#### the fabfile-template + +```yaml +name: {{ name }} +key: {{ shortName }} +deploymentModule: {{ shortName }}_deploy + +requires: 2.0.0 + +needs: + - ssh + - composer + - docker + - git + - files + +hosts: + mbb: + host: {{ projectFolder }}.test + user: root + port: {{ 1024 + random(20000) }} + type: dev + rootFolder: /var/www/{{ webRoot }} + gitRootFolder: /var/www + backupFolder: /var/www/backups + branch: develop + database: + name: {{ projectFolder|replace({'-': '_'}) }}_db + user: root + pass: admin + host: mysql +``` + \ No newline at end of file diff --git a/docs/docs/available-tasks.md b/docs/docs/available-tasks.md new file mode 100644 index 00000000..43533645 --- /dev/null +++ b/docs/docs/available-tasks.md @@ -0,0 +1,578 @@ +## -v/ -vv/ -vvv/ -vvvv + +Setting this option will increase the verbosity of phabalicious. Without this settings you'll get only warnings and errors and some informational stuff. If you encounter a problem try increasing the verbosity-level. + +## --config + +```shell +phab --config= +``` + +Most of the phabalicious tasks need the option `config`. Setting the option will lookup `` in the `hosts`-section of your `fabfile.yaml` and the data will be used to run your task with the correct environment. + +## --offline + +```shell +phab --offline=1 --config= +``` + +This task will disable remote configuration files. As phabalicious keeps copies of remote configuration-files in `~/.phabalicious` it will try to load the configuration-file from there. + +## --fabfile +```shell +Phab --fabfile= --blueprint= +``` + +`blueprint` will try to load a blueprint-template from the fabfile.yaml and apply the input given as `` to the template. This is helpful if you want to create/ use a new configuration which has some dynamic parts like the name of the database, the name of the docker-container, etc. + +The task will look first in the host-config for the property `blueprint`, afterwards in the dockerHost-configuration `` and eventually in the global namespace. If you want to print the generated configuration as yaml, then use the `output`-command. The computed configuration is used as the current configuration, that means, you can run other tasks against the generated configuration. + +**Available replacement-patterns** and what they do. + +_Input is `feature/XY-123-my_Branch-name`, the project-name is `Example project`_ + +| Replacement Pattern | value | +|-----------------------------------------|-------------------------------| +| **%slug.with-hyphens.without-feature%** | xy-123-my-branch-name | +| **%slug.with-hyphens%** | feature-xy-123-my-branch-name | +| **%project-slug.with-hypens%** | example-project | +| **%slug%** | featurexy123mybranchname | +| **%project-slug%** | exampleproject | +| **%project-identifier%** | Example project | +| **%identifier%** | feature/XY-123-my_Branch-name | +| **%slug.without-feature%** | xy123mybranchname | + + +Here's an example blueprint: + +```yaml +blueprint: + inheritsFrom: http://some.host/data.yaml + configName: '%project-slug%-%slug.with-hyphens.without-feature%.some.host.tld' + branch: '%identifier%' + database: + name: '%slug.without-feature%_mysql' + docker: + projectFolder: '%project-slug%--%slug.with-hyphens.without-feature%' + vhost: '%project-slug%-%slug.without-feature%.some.host.tld' + name: '%project-slug%%slug.without-feature%_web_1' +``` + +And the output of `phab blueprint:feature/XY-123-my_Branch-name,configNamy=,output=true` is + +```yaml +hosts: + phbackend-xy-123-my-branch-name.some.host.tld: + branch: feature/XY-123-my_Branch-name + configName: phbackend-xy-123-my-branch-name.some.host.tld + database: + name: xy123mybranchname_mysql + docker: + name: phbackendxy123mybranchname_web_1 + projectFolder: phbackend--xy-123-my-branch-name + vhost: phbackend-xy123mybranchname.some.host.tld + inheritsFrom: http://some.host/data.yaml +``` + +**Note** + +You can create new configurations via the global `blueprints`-settings: + +``` +blueprints: + - configName: mbb + variants: + - de + - en + - it + - fr +``` + +will create 4 new configurations using the blueprint-config `mbb`. + +## list + +```shell +phab list +``` + +This command will list all available tasks. You can get specific help for a task with the next command: + +## help +```shell +phab help: +``` + +Will display all available arguments and options for that given `` and some explanatory text. + +## list:hosts + +```shell +phab list:hosts +``` + +This task will list all your hosts defined in your `hosts`-section of your `fabfile.yaml`. + +## list:blueprints + +```shell +Phab list:blueprints +``` + +This command will list all found blueprint configurations. + +## about + +```shell +phab --config= about +``` + +will display the configuration of host ``. + +## output + +```shell +Phab config= --blueprint= output +``` + +This command will print the computed configuration from a blueprint as yams. You can copy it and paste it back to the fabfile to make it permanent. + +## get:property + +```shell +phab --config= get:property +``` + +This will print the property-value to the console. Suitable if you want to use phabalicious from within other scripts. + +**Examples** + +* `phab --config=mbb get:property host` will print the hostname of configuration `mbb`. +* `phab -cmbb get:property docker.service` will print the service of the docker-configuration of `mbb`. + + +## version + +```shell +phab --config= version +``` + +This command will display the installed version of the code on the installation ``. + +**Available methods**: + +* `git`. The task will get the installed version via `git describe`, so if you tag your source properly (.e.g. by using git flow), you'll get a nice version-number. + +## deploy + +```shell +phab --config= deploy +phab --config= deploy +``` + +This task will deploy the latest code to the given installation. If the installation-type is not `dev` or `test` the `backupDB`-task is run before the deployment starts. If `` is stated the specific branch gets deployed. + +After a successfull deployment the `reset`-task will be run. + +**Available methods:** + +* `git` will deploy to the latest commit for the given branch defined in the host-configuration. Submodules will be synced, and updated. +* `platform` will push the current branch to the `platform` remote, which will start the deployment-process on platform.sh +* `ftp-sync` will create a copy of the app in a temporary folder and syncs this folder with the help of `lftp` with a remote-ftp-server. + +**Examples:** + +* `phab --config=mbb deploy` will deploy the app via the config found in `mob` +* `phab --config=mbb deploy feature/some-feature` will deploy the branch `feature/some-feature` regardless the setting in the fabfile. + +## reset + +```shell +phab config= reset +``` + +This task will reset your installation + +**Available methods:** + +* `composer` will run `composer install` to update any dependencies before doing the reset +* `drush` will + * set the site-uuid from fabfile.yaml (drupal 8) + * enable a deployment-module if any stated in the fabfile.yaml + * enable modules listed in file `modules_enabled.txt` + * disable modules listed in file `modules_disabled.txt` + * revert features (drupal 7) if `revertFeatures` is true / import the configuration (drupal 8), + * run update-hooks + * and does a cache-clear. + * if your host-type is `dev` the password gets reset to admin/admin + +**Examples:** + +* `phab --config=mbb reset` will reset the installation and will not reset the password. + +## install + +```shell +phab config= install +``` + +This task will install a new Drupal installation with the minimal-distribution. You can install different distributions, see the examples. + +**Available methods:** + +* `drush` + +**Configuration:** + +You can add a `installOptions`-section to your fabfile.yaml. Here's an example: + +```yaml +installOptions: + distribution: thunder + locale: es +``` + +**Examples:** + +* `phab --config=mbb install` will install a new Drupal installation +* `phab --config=mbb install --skip-reset=1` will install a new Drupal installation and will not run the reset-task afterwards. + + + +## install:from + +```shell +phab --config= install:from +``` + +This task will install a new installation (see the `install`-task) and afterwards will do a `copyFrom`. The `reset`-task after the `install`-task will be skipped and executed after the `copyFrom`-task. You can limit, what should be copied from: `db` or `files`. If `` is omitted, then everything is copied from. + +**See also:** + +* install +* copyFrom + +## backup + +```shell +phab --config= backup +``` + +This command will backup your files and database into the specified `backup`-directory. The file-names will include configuration-name, a timestamp and the git-SHA1. Every backup can be referenced by its filename (w/o extension) or, when git is abailable via the git-commit-hash. + +If `` is omitted, files and db gets backupped, you can limit this by providing `db` and/ or `files`. + +**Available methods:** + +* `git` will prepend the file-names with a hash of the current revision. +* `files` will tar all files in the `filesFolder` and save it into the `backupFolder` +* `drush` will dump the databases and save it to the `backupFolder` + +**Configuration:** + +* your host-configuration will need a `backupFolder` and a `filesFolder` + +**Examples** + +* `phab -cmbb backup` will backup everything +* `phab -cmbb backup files` will backup only public and private files. +* `phan -cmbb backup db` will backup the database only. + + +## list:backups + +```shell +phab --config= list:backups +``` + +This command will print all available backups to the console. + + +## restore + +```shell +phab --config= restore +``` + +This will restore a backup-set. A backup-set consists typically of a database-dump and a gzipped file-archive. You can a list of candidates via `phab --config= list:backups` + +**Available methods** + +* `git` git will checkout the given hash encoded in the filename. +* `files` all files will be restored. An existing files-folder will be renamed for safety reasons. +* `drush` will import the database-dump. + + +## get:backup + +```shell +phab --config: get:backup +``` + +This command will copy a remote backup-set to your local computer into the current working-directory. + +**See also:** + +* restore +* backup + + +## copy-from + +```shell +phab --config= copy-from +``` + +This task will copy all files via rsync from `source-config` to `dest-config` and will dump the database from `source-config` and restore it to `dest-config` when `` is omitted. + +After that the `reset`-task gets executed. This is the ideal task to copy a complete installation from one host to another. + +You can limit what to copy by adding `db` or `files` as arguments. + +**Available methods** + +* `ssh` will create all necessary tunnels to access the hosts. +* `files` will rsync all new and changed files from source to dest +* `drush` will dump the database and restore it on the dest-host. + +**Examples** + +* `phab -cmbb copy-from remote-host` will copy db and files from `remote-host` to `mbb` +* `phab -cmbb copy-from remote-host db` will copy only the db from `remote-host` to `mbb` +* `phab -cmbb copy-from remote-host` will copy only the files from `remote-host` to `mbb` + + +## drush + +```shell +phab --config= drush "" +``` + +This task will execute the `drush-command` on the remote host specified in . Please note, that you'll have to quote the drush-command when it contains spaces. + +**Available methods** + +* Only available for the `drush`-method + +**Examples** + +* `phab --config=staging drush "cc all -y"` +* `phab --config=local drush fra` + + +## drupal + +This task will execute a drupal-console task on the remote host. Please note, that you'll have to quote the command when it contains spaces. + +**Available methods** + +* Only available for the `drupal`-method + +**Examples** + +* `phab --config=local drupal cache:rebuild` +* `phab --config=local drupal "generate:module --module helloworld"` + +## platform + +```shell +phab --config= platform +``` + +Runs a specific platform-command. + +## get:file + +```shell +phab --config= get:file +``` + +Copy a remote file to the current working directory of your current machine. + +## put:file + +```shell +phab --config= put:file +``` + +Copy a local file to the tmp-folder of a remote machine. + +**Configuration** + +* this command will use the `tmpFolder`-host-setting for the destination directory. + + +## get:files-dump + +```shell +phab --config= get:files-dump +``` + +This task will tar all files in `filesFolder` and `privateFilesFolder` and download it to the local computer. + +**Available methods** + +* currently only implemented for the `files`-method + + +## get:sql-dump + +```shell +phab --config= get:sql-dump +``` + +Get a current dump of the remote database and copy it to the local machine into the current working directory. + +**Available methods** + +* currently only implemented for the `drush`-method + + +## restore:sql-from-file + +```shell +phab --config= restore:sql-from-file +``` + +This command will copy the dump-file `path-to-local-sql-dump` to the remote machine and import it into the database. + +**Available methods** + +* currently only implemented for the `drush`-method + + +## script + +```shell +phab --config= script +``` + +This command will run custom scripts on a remote machine. You can declare scripts globally or per host. If the `script-name` can't be found in the fabfile.yaml you'll get a list of all available scripts. + +Additional arguments get passed to the script. See the examples. + +**Examples** + +* `phab --config=mbb script`. List all available scripts for configuration `mbb` +* `phab --config=mbb script behat` Run the `behat`-script +* `phab --config=mbb script behat "--name="Login feature" --format=pretty"` Run the behat-test, apply `--name` and `--format` parameters to the script + +The `script`-command is rather powerful, have a read about it in the extra section. + +## docker + +```shell +phab --config= docker +``` + +The docker command is suitable for orchestrating and administering remote instances of docker-containers. The basic setup is that your host-configuration has a `docker`-section, which contains a `configuration`-key. The `dockerHosts`-section of your fabfile.yaml has a list of tasks which are executed on the "parent-host" of the configuration. Please have a look at the docker-section for more information. + +Most of the time the docker-container do not have a public or known ip-address. phabalicious tries to find out the ip-address of a given instance and use that for communicating with its services. + +There are three implicit tasks available: + +### copySSHKeys + +```shell +phab --config=mbb docker copySSHKeys +``` + +This will copy the ssh-keys into the docker-instance. You'll need to provide the paths to the files via the three configurations: +* `dockerKeyFile`, the path to the private ssh-key to use. +* `dockerAuthorizedKeyFile`, the path to the file for `authoried_keys` or a url. +* `dockerKnownHostsFile`, the path to the file for `known_hosts` + +As docker-container do not have any state, this task is used to copy any necessary ssh-configuration into the docker-container, so communication per ssh does not need any passwords. + +### waitForServices + +This task will try to run `supervisorctl status` in the container and waits until all services are running. This is useful in scripts to wait for any services that need some time to start up. Obviously this task depends on `supervisorctl`. + + +## start-remote-access + +```shell +phab --config= start-remote-access +phab --config= start-remote-access --port= --public-port= --public-ip= +``` + +This task will run a command to forward a local port to a remote port. It starts a new ssh-session which will do the forwarding. When finished, type `exit`. + +**Examples** + +* `phab --config=mbb start-remote-access` will forward `localhost:8888` to port `80` of the docker-container +* `phab --config=mbb start-remote-access --port=3306 --publicPort=33060` will forward `localhost:33060`to port `3306` + +## notify + +```shell +phab --config= notify +``` + +This command will send the notification to Mattermosts channel . For a detailed description have a look into the dedicated documentation. + +**Examples** +* `phab config:mbb notify "hello world" "off-topic": sends `hello world` to `#off-topic` + +## app:scaffold + +```shell +phab app:scaffold --name= --short-name="short name of app" --output= --override="1|0" +``` + +This command will scaffold a new project from a set of scaffold-files. See the dedicated documentation for how to create these files. + +**Examples** +* `phab app:scaffold path/to/scaffold.yml` will scaffold the app in the current folder. Phab will ask for the name and the short-name +* `phab app:scaffold path/to/scaffold.yml --name="Hello World" --short-name="HW"` will scaffold the app with name "Hello World and short-name "HW" +* `phab app:scaffold https://config.factorial.io/scaffold/drupal/d8.yml` will scaffold a Drupal app from the remote configuration. + +## app:create + +```shell +phab --config= app:create --config-from= +``` + +This command will create a new app instance from a given config. Most useful with the usage of blueprints. + +The creation is done in several steps which can be customized. If you apply the `--config-from`-option an additional copyFrom is done afterwards. + +For a deeper explanation please have a look into the dedicated documentation + +## app:update + +```shell +phab --config= app:update +``` + +This command will update the code-base to the latest changes. When using the crush-method, drupal core will be updated to the latest version, if using `composer` then composer will be used to update the existing code. + +**Available methods** + +* `drush` will update Drupal-core, but only if `composer` is not used +* `composer` will update the codebase by running `composer update` + +## app:destroy + +```shell +phab --config= app:destroy +``` + +This command will destroy an app from a given configuration. The process has several steps. Caution: there will be no backup! + +## self-update + +```shell +phab self-update +Phan self-update --allow-unstable=1 +``` + +This will download the latest version of phab and replace the current installed one with the downloaded version. If `allow-unstable` is set, the latest-dev-version will be downloaded. \ No newline at end of file diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md new file mode 100644 index 00000000..69abd351 --- /dev/null +++ b/docs/docs/configuration.md @@ -0,0 +1,388 @@ +# Structure of the configuration file + +## Overview + +The configuration is fetched from the file `fabfile.yaml` and should have the following structure: + +```yaml +name: + +needs: + - list of methods + +requires: 2.0 + +dockerHosts: + docker1: + ... + +hosts: + host1: + ... +``` + +Here's the documentation of the supported and used keys: + +### name + +The name of the project, it's only used for output. + +### needs + +List here all needed methods for that type of project. Available methods are: + + * `local` runs all commands for that configuration locally. + * `git` for deployments via git + * `ssh` + * `drush` for support of drupal installations + * `files` + * `mattermost` for slack-notifications + * `docker` for docker-support + * `composer` for composer support + * `drupalconsole` for drupal-concole support + * `platform` for deploying to platform.sh + * `ftp-sync` to deploy to a ftp-server + +**Example for drupal 7** + +```yaml +needs: + - ssh + - git + - drush + - files +``` + +**Example for drupal 8 composer based and dockerized** + +```yaml +needs: + - ssh + - git + - drush + - composer + - docker + - files +``` + + +### requires + +The file-format of phabalicious changed over time. Set this to the lowest version of phabalicious which can handle the file. Should bei `2.0` + +### hosts + +Hosts is a list of host-definitions which contain all needed data to connect to a remote host. Here's an example + +```yaml +hosts: + exampleHost: + host: example.host.tld + user: example_user + port: 2233 + type: dev + rootFolder: /var/www/public + gitRootFolder: /var/www + siteFolder: /sites/default + filesFolder: /sites/default/files + backupFolder: /var/www/backups + supportsInstalls: true|false + supportsCopyFrom: true|false + type: dev + branch: develop + docker: + ... + database: + ... + scripts: + ... + sshTunnel: + .. + +``` + +You can get all host-information including the default values using the phabalicious command `about`: + +```shell +phab --config=staging about +``` + +This will print all host configuration for the host `staging`. + +#### General keys + +* `type` defines the type of installation. Currently there are four types available: + * `dev` for dev-installations, they won't backup the databases on deployment + * `test` for test-installations, similar than `dev`, no backups on deployments + * `stage` for staging-installations. + * `prod` for live-installations. Some tasks can not be run on live-installations as `install` or as a target for `copyFrom` + The main use-case is to run different scripts per type, see the `common`-section. +* `rootFolder` the web-root-folder of the installation, typically exposed to the public. +* `needs` a list of needed methods, if not set, the globally set `needs` gets used. +* `configName` is set by phabalicious, it's the name of the configuration +* `supportsInstalls`, default is true for `types` != `prod`. If sent to true you can run the `install`-task +* `supportsCopyFrom`, default is true. If set to true, you can use that configuration as a source for the `copy-from`-task. +* `backupBeforeDeploy` is set to true for `types` `stage` and `prod`, if set to true, a backup of the DB is made before a deployment. +* `tmpFolder`, default is `/tmp`. +* `shellProvider` defines how to run a shell, where commands are executed, current values are + * `local`: all commands are run locally + * `ssh`: all commands are run via a ssh-shell + * `docker-exec` all commands are run via docker-exec. + +#### Configuration for the local-method + +* `shellProvider` default is `local`, see above. +* `shellExecutable` default is `/bin/bash` The executable for running a shell. Please note, that phabalicious requires a sh-compatible shell. +* `shellProviderExecutable`, the command, which will create the process for a shell, here `/bin/bash` + +#### Configuration for the ssh-method + +* `host`, `user`, `port` are used to connect via SSH to the remote machine. Please make sure SSH key forwarding is enabled on your installation. +* `disableKnownHosts`, default is false, set to true to ignore the known_hosts-file. +* `sshTunnel` phabalicious supports SSH-Tunnels, that means it can log in into another machine and forward the access to the real host. This is handy for dockerized installations, where the ssh-port of the docker-instance is not public. `sshTunnel` needs the following informations + * `bridgeHost`: the host acting as a bridge. + * `bridgeUser`: the ssh-user on the bridge-host + * `bridgePort`: the port to connect to on the bridge-host + * `localPort`: the local port which gets forwarded to the `destPort`. If `localPort` is omitted, the ssh-port of the host-configuration is used. If the host-configuration does not have a port-property a random port is used. + * `destHost`: the destination host to forward to + * `destHostFromDockerContainer`: if set, the docker's Ip address is used for destHost. This is automatically set when using a `docker`-configuration, see there. + * `destPort`: the destination port to forward to + * `shellProviderExecutable`, default is `/usr/bin/ssh`, the executable to establish the connection. + + +#### Configuration for the git-method + +* `gitRootFolder` the folder, where the git-repository lies. Defaults to `rootFolder` +* `branch` the name of the branch to use for deployments, they get usually checked out and pulled from origin. +* `ignoreSubmodules` default is false, set to false, if you don't want to update a projects' submodule on deploy. +* `gitOptions` a keyed list of options to apply to a git command. Currently only pull is supported. If your git-version does not support `--rebase` you can disable it via an empty array: `pull: []` + +#### Configuration for the composer-method + +* `composerRootFolder` the folder where the composer.json for the project is stored, defaults to `gitRootFolder`. + +#### Configuration for the drush-method + +* `siteFolder` is a drupal-specific folder, where the settings.php resides for the given installation. This allows to interact with multisites etc. +* `filesFolder` the path to the files-folder, where user-assets get stored and which should be backed up by the `files`-method +* `revertFeatures`, defaults to `True`, when set all features will be reverted when running a reset (drush only) +* `configurationManagement`, an array of configuration-labels to import on `reset`, defaults to `['staging']`. You can add command arguments for drush, e.g. `['staging', 'dev --partial']` +* `database` the database-credentials the `install`-tasks uses when installing a new installation. + * `name` the database name + * `host` the database host + * `user` the database user + * `pass` the password for the database user + * `prefix` the optional table-prefix to use +* `adminUser`, default is `admin`, the name of the admin-user to set when running the reset-task on `dev`-instances +* `replaceSettingsFile`, default is true. If set to false, the settings.php file will not be replaced when running an install. +* `installOptions` default is `distribution: minimal, locale: en, options: ''`. You can change the distribution to install and/ or the locale. +* `drupalVersion` set the drupal-version to use. If not set phabalicious is trying to guess it from the `needs`-configuration. +* `drushVersion` set the used crush-version, default is `8`. Drush is not 100% backwards-compatible, for phabalicious needs to know its version. +* `supportsZippedBackups` default is true, set to false, when zipped backups are not supported + +#### Configuration of the ftp-sync-method + +* `ftp` keeps all configuration bundled: + * `user` the ftp-user + * `password` the ftp password + * `host` the ftp host + * `port`, default is 21, the port to connect to + * `rootFolder` the folder to copy the app into on the ftp-host. + * `lftpOptions`, an array of options to pass when executing `lftp` + +#### Configuration of the docker-method + +* `docker` for all docker-relevant configuration. `configuration` and `name`/`service` are the only required keys, all other are optional and used by the docker-tasks. + * `configuration` should contain the key of the dockerHost-configuration in `dockerHosts` + * `name` contains the name of the docker-container. This is needed to get the IP-address of the particular docker-container when using ssh-tunnels (see above). + * for docker-compose-base setups you can provide the `service` instead the name, phabalicious will get the docker name automatically from the service. + +### dockerHosts + +`dockerHosts` is similar structured as the `hosts`-entry. It's a keyed lists of hosts containing all necessary information to create a ssh-connection to the host, controlling the docker-instances, and a list of tasks, the user might call via the `docker`-command. See the `docker`-entry for a more birds-eye-view of the concepts. + +Here's an example `dockerHosts`-entry: + +```yaml +dockerHosts: + mbb: + runLocally: false + host: multibasebox.dev + user: vagrant + password: vagrant + port: 22 + rootFolder: /vagrant + environment: + VHOST: %host.host% + WEBROOT: %host.rootFolder% + tasks: + logs: + - docker logs %host.docker.name% +``` + +Here's a list of all possible entries of a dockerHosts-entry: +* `shellProvider`, the shell-provider to use, currently `local` or `ssh`. +* `runLocally`: if set to true, the `local`-shell-provider will be used. +* `host`, `user` and `port`: when using the `ssh`-shell-provicer. +* `environment` a keyed list of environment-variables to set, when running one of the tasks. The replacement-patterns of `scripts` are supported, see there for more information. +* `tasks` a keyed list of commands to run for a given docker-subtask (similar to `scripts`). Note: these commands are running on the docker-host, not on the host. All replacement-patterns do work, and you can call even other tasks via `execute(, )` e.g. `execute(docker, stop)` See the `scripts`-section for more info. + +You can use `inheritsFrom` to base your configuration on an existing one. You can add any configuration you may need and reference to that information from within your tasks via the replacement-pattern `%dockerHost.keyName%` e.g. `%dockerHost.host%`. + +You can reference a specific docker-host-configuration from your host-configuration via + +```yaml +hosts: + test: + docker: + configuration: mbb +``` + +### common + +common contains a list of commands, keyed by task and type which gets executed when the task is executed. + +Example: +```yaml +common: + reset: + dev: + - echo "running reset on a dev-instance" + stage: + - echo "running reset on a stage-instance" + prod: + - echo "running reset on a prod-instance" + deployPrepare: + dev: + - echo "preparing deploy on a dev instance" + deploy: + dev: + - echo "deploying on a dev instance" + deployFinished: + dev: + - echo "finished deployment on a dev instance" +``` + +The first key is the task-name (`reset`, `deploy`, ...), the second key is the type of the installation (`dev`, `stage`, `prod`, `test`). Every task is prepended by a prepare-stage and appended by a finished-stage, so you can call scripts before and after an actual task. You can even run other scripts via the `execute`-command, see the `scripts`-section. + +### scripts + +A keyed list of available scripts. This scripts may be defined globally (on the root level) or on a per host-level. The key is the name of the script and can be executed via + +```shell +phab --config= script +``` + +A script consists of an array of commands which gets executed sequentially. + +An example: + +```yaml +scripts: + test: + - echo "Running script test" + test2: + - echo "Running script test2 on %host.config_name% + - execute(script, test) +``` + +Scripts can be defined on a global level, but also on a per host-level. + +You can declare default-values for arguments via a slightly modified syntax: + +```yaml +scripts: + defaultArgumentTest: + defaults: + name: Bob + script: + - echo "Hello %arguments.name%" +``` + +Running the script via `phab config:mbb script:defaultArgumentTest,name="Julia"` will show `Hello Julia`. Running `phab config:mbb script:defaultArgumentTest` will show `Hello Bob`. + +For more information see the main scripts section below. + +### other + +* `deploymentModule` name of the deployment-module the drush-method enables when doing a deploy +* `sqlSkipTables` a list of table-names drush should omit when doing a backup. +* `configurationManagement` a list of configuration-labels to import on `reset`. This defaults to `['staging']` and may be overridden on a per-host basis. You can add command arguments to the the configuration label. + +Example: +```yaml +deploymentModule: my_deployment_module +usePty: false +useShell: false +gitOptions: + pull: + - --rebase + - --quiet +sqlSkipTables: + - cache + - watchdog + - session +configurationManagement: + staging: + - drush config-import -y staging + dev: + - Drusch config-import -y dev --partial +``` + + +## Inheritance + +Sometimes it make sense to extend an existing configuration or to include configuration from other places from the file-system or from remote locations. There's a special key `inheritsFrom` which will include the yaml found at the location and merge it with the data. This is supported for entries in `hosts` and `dockerHosts` and for the fabfile itself. + +If a `host`, a `dockerHost` or the fabfile itself has the key `inheritsFrom`, then the given key is used as a base-configuration. Here's a simple example: + +```yaml +hosts: + default: + port: 22 + host: localhost + user: default + example1: + inheritsFrom: default + port: 23 + example2: + inheritsFrom: example1 + user: example2 +``` + +`example1` will store the merged configuration from `default` with the configuration of `example1`. `example2` is a merge of all three configurations: `example2` with `example1` with `default`. + +```yaml +hosts: + example1: + port: 23 + host: localhost + user: default + example2: + port: 23 + host: localhost + user: example2 +``` + +You can even reference external files to inherit from: + +```yaml +hosts: + fileExample: + inheritsFrom: ./path/to/config/file.yaml + httpExapme: + inheritsFrom: http://my.tld/path/to/config_file.yaml +``` + +This mechanism works also for the fabfile.yaml / index.yaml itself, and is not limited to one entry: + +```yaml +name: test fabfile + +inheritsFrom: + - ./mbb.yaml + - ./drupal.yaml +``` + + + diff --git a/docs/docs/contribute.md b/docs/docs/contribute.md new file mode 100644 index 00000000..37d9afbb --- /dev/null +++ b/docs/docs/contribute.md @@ -0,0 +1,30 @@ +# Contributing to Phabalicious + +If you get stuck at any point you can create a [ticket on GitHub](https://github.com/factorial-io/phabalicious/issues). + +## Contributing to development + +It's the usual, fork the repository, create a feature-branch, do your changes and submit a pull-request via Github. If your new feature has test-coverage the better. + +We are happy for every contribution. + +## Improving documenation + +The procedure is the same as with contributing code. + +!!! tip + To edit existing pages, use the **Edit on GitHub** link on the top right corner of the page. + + To add a new page to documentation please follow [the guideline provided by MKdocs](http://www.mkdocs.org/user-guide/writing-your-docs/#configure-pages-and-navigation) to add new page and link them within the documentation. + +!!! note + This document is build using [MkDocs](http://www.mkdocs.org/), [Mike](https://github.com/jimporter/mike) and served using [GitHub Pages](https://pages.github.com/) + + - [Install MkDocs](http://www.mkdocs.org/#installation). + - [Install Mike](https://github.com/jimporter/mike) + - Clone [the repo](https://github.com/factorial-io/phabalicious) locally and switch to `develop` branch. + - Using Terminal, navigate to `/docs` folder. + - Run `mike deploy --push` + +### Resources +* [Writing your docs in MKdocs](http://www.mkdocs.org/user-guide/writing-your-docs/) diff --git a/docs/docs/css/version-select.css b/docs/docs/css/version-select.css new file mode 100644 index 00000000..56e00f2a --- /dev/null +++ b/docs/docs/css/version-select.css @@ -0,0 +1,5 @@ +#version-selector { + display: block; + margin: -10px auto 0.809em; + padding: 2px; +} diff --git a/docs/docs/docker-integration.md b/docs/docs/docker-integration.md new file mode 100644 index 00000000..fd1a1b35 --- /dev/null +++ b/docs/docs/docker-integration.md @@ -0,0 +1,128 @@ +# Docker integration + +The docker-integration is quite simple, but very powerful. In a fabfile, there are host-configuration under the key `hosts` and docker-configs under the key `dockerHosts`. A `host` can reference a docker-configuration, multiple hosts can use one docker-config. + +## A docker-configuration + +A docker configuration must contain these keys: + +* `rootFolder` the rootFolder, where the projectFolder can be found +* `shellProvider`: `local` or `ssh`; in which shell the tasks-commands should be run. The shell-provider might require more information e.g. `user`, `host` and `port`. +* `tasks`: a keyed list of scripts to use. The key is the name of the script, which you can trigger via the `docker command` +* `environment`: a key-value list of environment-variables to set before running a docker-command. This helps to modularize docker-compose-files, for example. + +The tasks can use the pattern-replacement used in other parts of phabalicious to get data from the docker-config or from the host-config into the tasks. Here's a small example: + +```yaml +tasks: + run: + - echo "running container %host.docker.name% for config %host.configName% in %dockerHost.rootFolder% +``` + +If you want to use some host-config, use `host.` as a prefix, if you want to use sth from the docker-config, use `dockerHost.` as prefix. + +## the docker-specific host-configuration + +All docker-configuration is stored inside the `docker`-group. It has only 2 required keys: + + * `configuration` this links to a key under `dockerHosts` + * `projectFolder` the folder, where this project is stored in relation to the `rootFolder` + * `name` or `service`. Some commands need to know with which container you want to interact. Provide the name of the docker-container via the `name`-property, if you are using docker-compose you can set the `service` accordingly, then phabalicious will try to compute the docker-name automatically. + +You can add as many data to the yams file and reference it via the replacement-mechanism described earlier. + +## A simple example: + +```yaml +dockerHosts: + test: + rootFolder: /root/folder + shellProvider: local + environment: + VHOST: %host.configName%.test + tasks: + run: + - echo "docker run" + build: + - echo "docker build" + all: + - echo "current config: %host.configName%" + - execute(docker, build) + - execute(docker, run) + +hosts: + testHostA: + ... + docker: + configuration: test + projectFolder: test-host-a + name: testhosta +``` + +This snippet will expose 3 docker-commands for the host `testHostA`, you can execute them via + +```shell +phab --config=testHostA docker run|build|all +``` + +The output for `phab -ctestHostA docker all` will be: + +``` +current config: testHostA +docker build +docker run +``` + +The inheritance mechanism allows you to store the docker-config in a central location and reuse it in your fabfile: + +```yaml +dockerHosts: + testB: + rootFolder: /some/other/folder + inheritsFrom: + - https://some.host/docker.yml + - https://some.host/docker-compose.yml +``` + + +## Built-in docker commands + +There are two commands builtin, because they are hard to implement in a script-only version: + + * waitForServices + * copySSHKeysToDocker + +### waitForServices + +This will try to run `supervisorctl status` every 10 seconds, and wait until all services are up and running. If you want to disable this command, set the executable to false with + +```yaml +executables: + supervisorctl: false +``` + +### copySSHKeysToDocker + +This command will copy the referenced files from your local computer into the docker container and set the permissions so ssh can use the copied data. + +These are the needed global settings in the fabfile: + +* `dockerKeyFile` will copy the referenced private key and its public key into the container +* `dockerAuthorizedKeyFile`, the `authorized_keys`-file, can be a path to a file or an url +* `dockerKnownHostsFile`, the `known_hosts`-file + +Obviously a ssh-demon should be running inside your docker-container. + +## Predefined docker-tasks + +Phabalicious is running some predefined docker-tasks if set in the fabfile and when using the commands `app:create` or `app:destroy` + +* `spinUp`: get all needed container running +* `spinDown`: stop all app-containers +* `deleteContainer`: remove and delete all app container + +If you want to support this in your configuration, add the tasks to the fabfile and its corresponding commands. + +## Conclusion + +As you can see, there's not much docker-specific besides the naming. So in theory you can control something else like `rancher` or maybe even `kubectl`. All you have is the referencing between a host and a dockerHost and the possibility to run tasks locally or via SSH on a remote instance, and pass data from the host-config or docker-config to your scripts. \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 00000000..e7c64e8a --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,24 @@ +# How does phabalicious work in two sentences + +Phabalicious is using configuration stored in a special file in the root of your project (the `fabfile.yaml`) to run tasks in a shell. This shell can be provided by a docker-container, a ssh-connection or a local shell. This means, you can store all your devops-scripts in the fabfile and apply it to a list of configurations. Phabalicious tries to abstract away the inner workings of a host and give the user a handful useful commands to run common tasks, like: + + * deploying new code to a remote installation + * reset a remote installation to its defaults. + * backup/ restore data + * copy data from one installation to another + * scaffold new projects + * run scripts on different local or remote installations. + * handle SSH-tunnels transparently + * optionally work with our docker-based local development-stack [multibasebox](https://github.com/factorial-io/multibasebox) + +## History + +Phabalicious started as a shell-script to reset a Drupal-installation. We used fabric as a base for all the tasks and fabalicious grew to its first official release 1.0. + +But it got unmaintainable, as it was not flexible enough to handle new requirements, so the first rewrite started to get a more modular and extendable version: Fabalicious 2.0. It supported hooks for custom scripts, was extendable by new methods, later on it got a complete plugin-system and what not. + +Meanwhile fabric (the foundation of fabalicious) took a different route. Fabric 2.0 was not compatible anymore with 1.x and so with fabalicious. As most of our users were on OS X, handling the python dependencies got also more complicated. And only a handful devs in our company could write python. Another hurdle was that fabric supports SSH and local connections only, running commands in different ways (like `docker exec`) was cumbersome. + +So the idea was born, to do another rewrite in PHP and use Symfony console as a base for it. + +Phabalicious 3 still supports the fabfile-format from version 2, but the command-line syntax changed a lot, and is now more compliant with posix. diff --git a/docs/docs/installation.md b/docs/docs/installation.md new file mode 100644 index 00000000..1d3bd871 --- /dev/null +++ b/docs/docs/installation.md @@ -0,0 +1,56 @@ +# Installation of needed dependencies + +Phabalicious needs at least PHP 7.1 with the json- and openssl-extensions. The best way is to install it globally by downloading the phar-file: + +## Download phar + +* Download the latest version from [Github](https://github.com/factorial-io/phabalicious/releases) +* copy the phar to a suitable folder, e.g. `/usr/local/bin` and rename it to `phab` +* Make it executable, e.g. `chmod u+x /usr/local/bin/phab` + +## Installation from source + +* Clone the repository via `git clone https://github.com/factorial-io/phabalicious.git` +* cd into the folder +* run `composer install` +* run `composer build-phar` +* run `composer install-phar`, this will copy the phar to `/usr/local/bin` and make it executable. + +## Install it as a project dependency + +* run `composer require factorial-io/phabalicious` + +Note, phabalicious is using Symfony 4 so you might get some unresolvable conflicts (Merge Requests welcome!) + +## and then ... + +1. Run `fab list`, this should give you a list of all available commands. +2. Create a configuration file called `fabfile.yaml` + +# A simple configuration-example + +```yaml +name: My awesome project + +# We'll need phabalicious >= 3.0 +requires: 3.0 + +# We need git and ssh, there are more options +needs: + - ssh + - git + +# Our list of host-configurations +hosts: + dev: + host: myhost.test + user: root + port: 22 + type: dev + branch: develop + rootFolder: /var/www + backupFolder: /var/backups +``` + +For more infos about the file-format have a look at the file-format-section. + diff --git a/docs/docs/js/version-select.js b/docs/docs/js/version-select.js new file mode 100644 index 00000000..3df2a71c --- /dev/null +++ b/docs/docs/js/version-select.js @@ -0,0 +1,64 @@ +window.addEventListener("DOMContentLoaded", function() { + function normalizePath(path) { + var normalized = []; + path.split("/").forEach(function(bit, i) { + if (bit === "." || (bit === "" && i !== 0)) { + return; + } else if (bit === "..") { + if (normalized.length === 1 && normalized[0] === "") { + // We must be trying to .. past the root! + throw new Error("invalid path"); + } else if (normalized.length === 0 || + normalized[normalized.length - 1] === "..") { + normalized.push(".."); + } else { + normalized.pop(); + } + } else { + normalized.push(bit); + } + }); + return normalized.join("/"); + } + + // `base_url` comes from the base.html template for this theme. + var REL_BASE_URL = base_url; + var ABS_BASE_URL = normalizePath(window.location.pathname + "/" + + REL_BASE_URL); + var CURRENT_VERSION = ABS_BASE_URL.split("/").pop(); + + function makeSelect(options, selected) { + var select = document.createElement("select"); + + options.forEach(function(i) { + var option = new Option(i.text, i.value, undefined, + i.value === selected); + select.add(option); + }); + + return select; + } + + var xhr = new XMLHttpRequest(); + xhr.open("GET", REL_BASE_URL + "/../versions.json"); + xhr.onload = function() { + var versions = JSON.parse(this.responseText); + + var realVersion = versions.find(function(i) { + return i.version === CURRENT_VERSION || + i.aliases.includes(CURRENT_VERSION); + }).version; + + var select = makeSelect(versions.map(function(i) { + return {text: i.title, value: i.version}; + }), realVersion); + select.id = "version-selector"; + select.addEventListener("change", function(event) { + window.location.href = REL_BASE_URL + "/../" + this.value; + }); + + var title = document.querySelector("div.wy-side-nav-search"); + title.insertBefore(select, title.querySelector(".icon-home").nextSibling); + }; + xhr.send(); +}); diff --git a/docs/docs/local-overrides.md b/docs/docs/local-overrides.md new file mode 100644 index 00000000..f973bd20 --- /dev/null +++ b/docs/docs/local-overrides.md @@ -0,0 +1,30 @@ +# Local overrides + +`fabfile.local.yaml` is used to override parts of your fabfile-configuration. If you run a fab-command the code will try to find the `fabfile.local.yaml` up to three folder levels up and merge the data with your fabfile.yaml. + +A small example: + +``` +fabfile.local.yaml ++ project + fabfile.yaml +``` + +Contents fo fabfile.yaml +```yaml +hosts: + local: + host: multibasebox.dev + port: 22 + [...] +``` + +Contents of fabfile.local.yaml: +```yaml +hosts: + local: + host: localhost + port: 2222 +``` + +This will override the `host` and `port` settings of the `local`-configuration. With this technique you can alter an existing fabfile.yaml with local overrides. (In this example, `host=localhost` and `port=2222` \ No newline at end of file diff --git a/docs/docs/passwords.md b/docs/docs/passwords.md new file mode 100644 index 00000000..0b1c6269 --- /dev/null +++ b/docs/docs/passwords.md @@ -0,0 +1,14 @@ +# Passwords + +You should not store any sensitive passwords in the fabfile. It's a possible security risk, as the file is part of your repository. + +That's why phabalicious is heavily relying on key-forwarding for ssh-connections. If key-forwarding does not work, you might get a native ssh-password-prompt. + +If you are using the method `ftp-sync` you can add the password to the fabfile, but we strongly discourage this. If you want to store the password permanently so that phabalicious can pick them up, store them in your user-folder in a yml-file called `.phabalicious-credentials`. The format is as follows + +```yaml +"@:": +"stephan@localhost:21": 123456 +``` + +If no password is available, phabalicious will prompt for one. diff --git a/docs/docs/scripts.md b/docs/docs/scripts.md new file mode 100644 index 00000000..d91454c3 --- /dev/null +++ b/docs/docs/scripts.md @@ -0,0 +1,91 @@ +# Scripts + +Scripts are a powerful concept of phabalicious. There are a lot of places where scripts can be called. The `common`-section defines common scripts to be run for specific task/installation-type-configurations, docker-tasks are also scripts which you can execute via the docker-command. And you can even script phabalicious tasks and create meta-tasks. And you can add custom scripts to the general section of a fabfile or to a host-configuration. Scripts can call other scripts. + +A script is basically a list of commands which get executed via shell on a local or remote machine. To stay independent of the host where the script is executed, phabalicious parses the script before executing it and replaces given variables with their counterpart in the yaml file. + +## Replacement-patterns + +Replacement-Patterns are specific strings enclosed in `%`s, e.g. `%host.port%`, `%dockerHost.rootFolder%` or `%arguments.name%`. + +Here's a simple example; + +```yaml +script: + test: + - echo "I am running on %host.config_name%" +``` + +Calling this script via + +```shell +phab config:mbb script:test +``` + +will show `I am running on mbb`. + +* The host-configuration gets exposes via the `host.`-prefix, so `port` maps to `%host.port%`, etc. +* The dockerHost-configuration gets exposed via the `dockerHost`-prefix, so `rootFolder` maps to `%dockerHost.rootFolder%` +* The global configuration of the yams-file gets exposed to the `settings`-prefix, so `uuid` gets mapped to `%settings.uuid% +* Optional arguments to the `script`-taks get the `argument`-prefix, e.g. `%arguments.name%`. You can get all arguments via `%arguments.combined%`. +* You can access hierarchical information via the dot-operator, e.g. `%host.database.name%` + +If phabalicious detects a pattern it can't replace it will abort the execution of the script and displays a list of available replacement-patterns. + +## Internal commands + +There are currently 3 internal commands. These commands control the flow inside phabalicious: + +* `fail_on_error(1|0)` If fail_on_error is set to one, phabalicious will exit if one of the script commands returns a non-zero return-code. When using `fail_on_error(0)` only a warning is displayed, the script will continue. +* `execute(task, subtask, arguments)` execute a phabalicious task. For example you can run a deployment from a script via `execute(deploy)` or stop a docker-container from a script via `execute(docker, stop)` +* `fail_on_missing_directory(directory, message)` will print message `message` if the directory `directory` does not exist. + +## Task-related scripts + +You can add scripts to the `common`-section, which will called for any host. You can differentiate by task-name and host-type, e.g. create a script which gets called for the task `deploy` and type `dev`. + +You can even run scripts before or after a task is executed. Append the task with `Prepare` or `Finished`. + +You can even run scripts for specific tasks and hosts. Just add your script with the task-name as its key. + +```yaml +host: + test: + deployPrepare: + - echo "Preparing deploy for test" + deploy: + - echo "Deploying on test" + deployFinished: + - echo "Deployment finished for test" +``` + +These scripts in the above examples gets executed only for the host `test` and task `deploy`. + +## Examples + +A rather complex example scripting phabalicious. + +```yaml +scripts: + runTests: + defaults: + branch: develop + script: + - execute(docker, start) + - execute(docker, waitForServices) + - execute(deploy, %arguments.branch%) + - execute(script, behatInstall) + - execute(script, behat, --profile=ci --format=junit --format=progress) + - execute(getFile, /var/www/_tools/behat/build/behat/default.xml, ./_tools/behat) + - execute(docker, stop) +``` + +This script will + +* start the docker-container, +* wait for it, +* deploys the given branch, +* run a script which will install behat, +* run behat with some custom arguments, +* gets the result-file and copy it to a location, +* and finally stops the container. diff --git a/docs/docs/usage.md b/docs/docs/usage.md new file mode 100644 index 00000000..5af9d52b --- /dev/null +++ b/docs/docs/usage.md @@ -0,0 +1,49 @@ +# Running phabalicious + +To execute a task with the help of phabalicious, just + +```shell +cd +phab --config= +``` + +This will read your fabfile.yaml, look for `` in the host-section and run the task + +# Tasks + +## Some Background + +Phabalicious provides a set of so-called methods which implement all listed functionality. The following methods are available: + +* local +* git +* ssh +* drush +* composer +* files +* docker +* drupalconsole +* mattermost +* platform +* ftp-sync + +You declare your needs in the fabfile.yaml with the key `needs`, e.g. + +```yaml +needs: + - git + - ssh + - drush + - files +``` + +Have a look at the file-format documentation for more info. + +## List of available tasks + + +You can get a list of available commands with + +```shell +phab list +``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 00000000..fbf34636 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,30 @@ +site_name: Phabalicious +theme: readthedocs +markdown_extensions: + - admonition + +repo_url: https://github.com/factorial-io/phabalicious/ +repo_name: GitHub +edit_uri: edit/develop/docs/docs/ +site_description: Phabalicious Documentation +site_author: Factorial GmbH, Hamburg +copyright: © 2018 Factorial GmbH, Hamburg + +nav: + - index.md + - installation.md + - usage.md + - available-tasks.md + - configuration.md + - docker-integration.md + - local-overrides.md + - scripts.md + - app-scaffold.md + - app-create-destroy.md + - passwords.md + - contribute.md + - Changelog.md +extra_css: + - css/version-select.css +extra_javascript: + - js/version-select.js diff --git a/readme.md b/readme.md index fa6ae1b5..08b7904e 100644 --- a/readme.md +++ b/readme.md @@ -4,22 +4,54 @@ Phabalicious is the successor of the python tool [fabalicious](https://github.co ## Installation -* Download the `phabalicious.phar` +* Download the `phabalicious.phar` from https://github.com/factorial-io/phabalicious/releases * `cp phabalicious.phar /phab` * `chmod +x /phab` ## Build from source +You'll need [box](https://github.com/humbug/box) for building the phar-file. + * Clone the repository +* run `composer install` * run `composer build-phar` * run `composer install-phar` this will copy the app to /usr/local/bin and make it executable. ## Add it via composer.json -* run `composer require factorial.io/phabalicious` +* run `composer require factorial-io/phabalicious` * then you can run phabalicious via `./vendor/factorial-io/fabablicious/bin/phab` (or create a symbolic link) -## Running pha +## Running phab * Run `phab list` to get a list of all available commands. * run `phab help ` to get some help for a given command. + +## Shell autocompletion + +Add this to your shell-startup script: + +* for fish-shells + + phab _completion --generate-hook --shell-type fish | source + +* for zsh/bash-shells + + source <(phab _completion --generate-hook) + +## Updating phab + +* Run `phab self-update`, this will download the latest release from GitHub. + +If you want to get the latest dev-version, add `--allow-unstable=1` + +## Enhancing phab, contributing to phab + +We welcome contributions! Please fork the repository, create a feature branch and submit a pull-request. +Please add test-cases for your bug-fixes or new features. We are using [pre-commit](https://pre-commit.com/) to check code-style (PSR2) etc. + +* Run `pre-commit install` to install the pre-commit-hooks. + +## Documentation + +You can find an extensive documentation at [https://factorial-io.github.io/phabalicious](https://factorial-io.github.io/phabalicious) diff --git a/src/Command/AppBaseCommand.php b/src/Command/AppBaseCommand.php index 0995c9bc..2bf6a44e 100644 --- a/src/Command/AppBaseCommand.php +++ b/src/Command/AppBaseCommand.php @@ -5,6 +5,7 @@ use Phabalicious\Exception\EarlyTaskExitException; use Phabalicious\Method\TaskContext; use Phabalicious\Method\TaskContextInterface; +use Phabalicious\Utilities\AppDefaultStages; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -21,11 +22,14 @@ abstract class AppBaseCommand extends BaseCommand */ protected function executeStages(array $stages, string $command, TaskContextInterface $context, string $message) { - foreach ($stages as $stage) { - $context->getOutput()->writeln(sprintf('%s, stage %s', $message, $stage['stage'])); - $context->set('currentStage', $stage); - $this->getMethods()->runTask($command, $this->getHostConfig(), $context); - } + AppDefaultStages::executeStages( + $this->getMethods(), + $this->getHostConfig(), + $stages, + $command, + $context, + $message + ); } diff --git a/src/Command/AppCreateCommand.php b/src/Command/AppCreateCommand.php index ded2b04b..07e0ba3e 100644 --- a/src/Command/AppCreateCommand.php +++ b/src/Command/AppCreateCommand.php @@ -5,6 +5,7 @@ use Phabalicious\Exception\EarlyTaskExitException; use Phabalicious\Method\TaskContext; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Utilities\AppDefaultStages; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -75,31 +76,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $stages = $this->configuration->getSetting( 'appStages.deploy', - [ - [ - 'stage' => 'spinUp', - ] - ] + AppDefaultStages::DEPLOY ); $this->executeStages($stages, 'appCreate', $context, 'Creating app'); $this->runCommand('deploy', [], $input, $output); } else { $stages = $this->configuration->getSetting( 'appStages.create', - [ - [ - 'stage' => 'installCode', - ], - [ - 'stage' => 'spinUp', - ], - [ - 'stage' => 'installDependencies', - ], - [ - 'stage' => 'install', - ], - ] + AppDefaultStages::CREATE ); $this->executeStages($stages, 'appCreate', $context, 'Creating app'); diff --git a/src/Command/AppDestroyCommand.php b/src/Command/AppDestroyCommand.php index 5df276f8..304616ff 100644 --- a/src/Command/AppDestroyCommand.php +++ b/src/Command/AppDestroyCommand.php @@ -5,6 +5,7 @@ use Phabalicious\Exception\EarlyTaskExitException; use Phabalicious\Method\TaskContext; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Utilities\AppDefaultStages; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -60,14 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($app_exists) { $stages = $this->configuration->getSetting( 'appStages.deploy', - [ - [ - 'stage' => 'spinDown', - ], - [ - 'stage' => 'deleteContainer', - ], - ] + AppDefaultStages::DESTROY ); $this->executeStages($stages, 'appDestroy', $context, 'Destroying app'); $outer_shell->run(sprintf('sudo rm -rf %s', $install_dir)); diff --git a/src/Command/AppScaffoldCommand.php b/src/Command/AppScaffoldCommand.php index 6a71d745..2e685529 100644 --- a/src/Command/AppScaffoldCommand.php +++ b/src/Command/AppScaffoldCommand.php @@ -128,7 +128,7 @@ protected function execute(InputInterface $input, OutputInterface $output) 'name' => trim($name), 'shortName' => trim(strtolower($short_name)), 'projectFolder' => Utilities::cleanupString($name), - 'rootFolder' => $input->getOption('output') . '/' . Utilities::cleanupString($name), + 'rootFolder' => realpath($input->getOption('output') . '/' . Utilities::cleanupString($name)), 'uuid' => $this->fakeUUID(), ]; @@ -153,7 +153,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $script = new ScriptMethod($logger); $host_config = new HostConfig([ - 'rootFolder' => $input->getOption('output'), + 'rootFolder' => realpath($input->getOption('output')), 'shellExecutable' => '/bin/bash' ], $shell); $context = new TaskContext($this, $input, $output); @@ -171,7 +171,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->twig = new \Twig_Environment($loader, array( )); - $shell->run(sprintf('mkdir -p %s', $tokens['rootFolder'])); if (empty($input->getOption('override')) && is_dir($tokens['rootFolder'])) { $question = new ConfirmationQuestion('Target-folder exists! Continue anyways? ', false); @@ -180,6 +179,8 @@ protected function execute(InputInterface $input, OutputInterface $output) } } + $shell->run(sprintf('mkdir -p %s', $tokens['rootFolder'])); + $script->runScript($host_config, $context); return 0; } diff --git a/src/Command/BackupCommand.php b/src/Command/BackupCommand.php index 2d5ab800..c8a0ac55 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -64,12 +64,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $files = $context->getResult('files', []); - $io = new SymfonyStyle($input, $output); - $io->title('Created backup files'); - $io->table( - ['Type', 'File'], - $files - ); + if (count($files)) { + $io = new SymfonyStyle($input, $output); + $io->title('Created backup files'); + $io->table( + ['Type', 'File'], + $files + ); + + $context->getStyle()->success('Backups created successfully!'); + } return $context->getResult('exitCode', 0); } diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 4c8afa79..23093cb1 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -10,6 +10,7 @@ use Phabalicious\Exception\MismatchedVersionException; use Phabalicious\Exception\ValidationFailedException; use Phabalicious\Exception\MissingHostConfigException; +use Phabalicious\ShellProvider\ShellProviderInterface; use Psr\Log\NullLogger; use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; @@ -17,6 +18,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; abstract class BaseCommand extends BaseOptionsCommand { @@ -175,4 +177,28 @@ public function runCommand(string $command, array $args, InputInterface $origina return $cmd->run($input, $output); } -} \ No newline at end of file + /** + * @param ShellProviderInterface $shell + * @param array $command + * @return Process + */ + protected function startInteractiveShell(ShellProviderInterface $shell, array $command = []) + { + /** @var Process $process */ + $process = $shell->createShellProcess($command, ['tty' => true]); + $stdin = fopen('php://stdin', 'r'); + $process->setInput($stdin); + $process->setTimeout(0); + $process->setTty(true); + $process->start(); + $process->wait(function ($type, $buffer) { + if ($type == Process::ERR) { + fwrite(STDERR, $buffer); + } else { + fwrite(STDOUT, $buffer); + } + }); + + return $process; + } +} diff --git a/src/Command/DrushCommand.php b/src/Command/DrushCommand.php index b9dbe074..7c2dfed0 100644 --- a/src/Command/DrushCommand.php +++ b/src/Command/DrushCommand.php @@ -2,10 +2,7 @@ namespace Phabalicious\Command; -use Phabalicious\Configuration\HostConfig; -use Phabalicious\Exception\EarlyTaskExitException; use Phabalicious\Method\TaskContext; -use Phabalicious\Method\TaskContextInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -49,14 +46,19 @@ protected function execute(InputInterface $input, OutputInterface $output) $context = new TaskContext($this, $input, $output); $context->set('command', implode(' ', $input->getArgument('drush'))); - try { - $this->getMethods()->runTask('drush', $this->getHostConfig(), $context); - } catch (EarlyTaskExitException $e) { - return 1; - } + // Allow methods to override the used shellProvider: + $host_config = $this->getHostConfig(); + $this->getMethods()->runTask('drush', $host_config, $context); + $shell = $context->getResult('shell', $host_config->shell()); + $command = $context->getResult('command'); - return $context->getResult('exitCode', 0); - } + if (!$command) { + throw new \RuntimeException('No command-arguments returned for drush-command!'); + } + $output->writeln('Starting drush on `' . $host_config['configName'] . '`'); + $process = $this->startInteractiveShell($shell, $command); + return $process->getExitCode(); + } } diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php new file mode 100644 index 00000000..4a20193f --- /dev/null +++ b/src/Command/SelfUpdateCommand.php @@ -0,0 +1,60 @@ +setName('self-update') + ->setDescription('Installs the latest version') + ->setHelp('Downloads and install the latest version from github.'); + + $this->addOption( + 'allow-unstable', + null, + InputOption::VALUE_OPTIONAL, + 'Allow updating to unstable versions', + false + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $helper = new SymfonyStyle($input, $output); + $helper->text(sprintf( + 'Current version is %s, looking for a new version ...', + $this->getApplication()->getVersion() + )); + + $new_version = $this->runSelfUpdate($input->getOption('allow-unstable')); + + if ($new_version) { + $helper->success(sprintf('Updated phabalicious successfully to %s', $new_version)); + } else { + $helper->note('No newer version found!'); + } + } + + private function runSelfUpdate($allow_unstable) + { + $updater = new Updater(null, false); + $updater->setStrategy(Updater::STRATEGY_GITHUB); + + $updater->getStrategy()->setPackageName('factorial-io/phabalicious'); + $updater->getStrategy()->setPharName('phabalicious.phar'); + $updater->getStrategy()->setCurrentLocalVersion($this->getApplication()->getVersion()); + $updater->getStrategy()->setStability($allow_unstable ? 'unstable' : 'stable'); + $result = $updater->update(); + + return $result ? $updater->getNewVersion() : false; + } +} diff --git a/src/Command/ShellCommand.php b/src/Command/ShellCommand.php index 229dbff2..4e447a17 100644 --- a/src/Command/ShellCommand.php +++ b/src/Command/ShellCommand.php @@ -52,22 +52,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('Starting shell on `' . $host_config['configName'] . '`'); - /** @var Process $process */ - $process = $shell->createShellProcess([], ['tty' => true]); - $stdin = fopen('php://stdin', 'r'); - $process->setInput($stdin); - $process->setTimeout(0); - $process->setTty(true); - $process->start(); - $process->wait(function ($type, $buffer) { - if ($type == Process::ERR) { - fwrite(STDERR, $buffer); - } else { - fwrite(STDOUT, $buffer); - } - }); - + $process = $this->startInteractiveShell($shell); return $process->getExitCode(); } - -} \ No newline at end of file +} diff --git a/src/Configuration/BlueprintTemplate.php b/src/Configuration/BlueprintTemplate.php index 9b1c4e85..34911948 100644 --- a/src/Configuration/BlueprintTemplate.php +++ b/src/Configuration/BlueprintTemplate.php @@ -2,7 +2,6 @@ namespace Phabalicious\Configuration; - use Phabalicious\Utilities\Utilities; class BlueprintTemplate @@ -44,5 +43,4 @@ public function expand($identifier) return Utilities::expandStrings($this->template, $replacements); } - -} \ No newline at end of file +} diff --git a/src/Configuration/ConfigurationService.php b/src/Configuration/ConfigurationService.php index ab973410..f8928475 100644 --- a/src/Configuration/ConfigurationService.php +++ b/src/Configuration/ConfigurationService.php @@ -134,7 +134,6 @@ public function readConfiguration(string $path, string $override = ''): bool $disallow_deep_merge_for_keys, $method->getKeysForDisallowingDeepMerge() ); - } foreach ($this->methods->all() as $method) { $data = $this->applyDefaults( @@ -412,6 +411,11 @@ private function validateHostConfig($config_name, $data) { $data = $this->resolveInheritance($data, $this->hosts); $type = isset($data['type']) ? $data['type'] : false; + $type = HostType::convertLegacyTypes($type); + if (!empty($type)) { + $data['type'] = $type; + } + $defaults = [ 'type' => $type ? $type : 'dev', 'config_name' => $config_name, // For backwards compatibility @@ -528,6 +532,9 @@ public function getDockerConfig(string $config_name) $data = $this->dockerHosts[$config_name]; $data = $this->resolveInheritance($data, $this->dockerHosts); + $data = $this->applyDefaults($data, [ + 'tmpFolder' => '/tmp', + ]); if (!empty($data['inheritOnly'])) { return $data; } @@ -597,6 +604,7 @@ private function validateDockerConfig(array $data, $config_name) $validation->deprecate(['runLocally']); $validation->hasKey('shellProvider', 'The name of the shell-provider to use'); $validation->hasKey('rootFolder', 'The rootFolder to start with'); + $validation->hasKey('tmpFolder', 'The rootFolder to use'); if ($errors->hasErrors()) { throw new ValidationFailedException($errors); @@ -613,5 +621,4 @@ public function setOffline($offline) { $this->offlineMode = $offline; } - -} \ No newline at end of file +} diff --git a/src/Configuration/HostType.php b/src/Configuration/HostType.php index 7a19ef76..012ec789 100644 --- a/src/Configuration/HostType.php +++ b/src/Configuration/HostType.php @@ -25,4 +25,13 @@ public static function getAll() ]; } + public static function convertLegacyTypes($type) + { + if ($type == 'live') { + return self::PROD; + } + + return $type; + } + } \ No newline at end of file diff --git a/src/Method/DockerMethod.php b/src/Method/DockerMethod.php index f1718ed5..cf61ba60 100644 --- a/src/Method/DockerMethod.php +++ b/src/Method/DockerMethod.php @@ -70,6 +70,13 @@ public function validateConfig(array $config, ValidationErrorBagInterface $error } } + public function alterConfig(ConfigurationService $configuration_service, array &$data) + { + if (!empty($data['docker']['service'])) { + unset($data['docker']['name']); + } + } + /** * @param HostConfig $host_config * @param TaskContextInterface $context @@ -101,6 +108,8 @@ public function docker(HostConfig $host_config, TaskContextInterface $context) $this->runTaskImpl($host_config, $context, $task . 'Prepare', true); $this->runTaskImpl($host_config, $context, $task, false); $this->runTaskImpl($host_config, $context, $task . 'Finished', true); + + $context->getStyle()->success(sprintf('Task `%s` executed successfully!', $task)); } /** @@ -165,6 +174,9 @@ public function getInternalTasks() /** * @param HostConfig $hostconfig * @param TaskContextInterface $context + * @throws ValidationFailedException + * @throws \Phabalicious\Exception\MismatchedVersionException + * @throws \Phabalicious\Exception\MissingDockerHostConfigException */ public function waitForServices(HostConfig $hostconfig, TaskContextInterface $context) { @@ -177,6 +189,13 @@ public function waitForServices(HostConfig $hostconfig, TaskContextInterface $co $container_name = $this->getDockerContainerName($hostconfig, $context); $shell = $docker_config->shell(); + if (!$this->isContainerRunning($docker_config, $container_name)) { + throw new \RuntimeException(sprintf( + 'Docker container %s not running, check your `host.docker.name` configuration!', + $container_name + )); + } + while ($tries < $max_tries) { $error_log_level = new ScopedErrorLogLevel($shell, LogLevel::NOTICE); $result = $shell->run(sprintf('#!docker exec %s #!supervisorctl status', $container_name), true, false); @@ -196,7 +215,7 @@ public function waitForServices(HostConfig $hostconfig, TaskContextInterface $co $this->logger->notice('Error running supervisorctl, check the logs'); } if ($result->getExitCode() == 0 && ($count_running == $count_services)) { - $this->logger->notice('Services up and running!'); + $context->getStyle()->comment('Services up and running!'); return; } $tries++; @@ -247,11 +266,16 @@ private function copySSHKeys(HostConfig $hostconfig, TaskContextInterface $conte } if (count($files) > 0) { $docker_config = $this->getDockerConfig($hostconfig, $context); + $root_folder = $docker_config['rootFolder'] . '/' . $hostconfig['docker']['projectFolder']; + /** @var ShellProviderInterface $shell */ $shell = $docker_config->shell(); $container_name = $this->getDockerContainerName($hostconfig, $context); if (!$this->isContainerRunning($docker_config, $container_name)) { - throw new \RuntimeException(sprintf('Docker container %s not running', $container_name)); + throw new \RuntimeException(sprintf( + 'Docker container %s not running, check your `host.docker.name` configuration!', + $container_name + )); } $shell->run(sprintf('#!docker exec %s mkdir -p /root/.ssh', $container_name)); @@ -264,15 +288,18 @@ private function copySSHKeys(HostConfig $hostconfig, TaskContextInterface $conte $data['source'] = $temp_file; $temp_files[] = $temp_file; } elseif ($data['source'][0] !== '/') { - $data['source'] = realpath( + $data['source'] = $context->getConfigurationService()->getFabfilePath() . '/' . - $data['source'] - ); + $data['source']; } + $temp_file = $docker_config['tmpFolder'] . '/' . 'phab.tmp.' . basename($data['source']); + $shell->putFile($data['source'], $temp_file, $context); - $shell->run(sprintf('#!docker cp %s %s:%s', $data['source'], $container_name, $dest)); + $shell->run(sprintf('#!docker cp %s %s:%s', $temp_file, $container_name, $dest)); $shell->run(sprintf('#!docker exec %s #!chmod %s %s', $container_name, $data['permissions'], $dest)); + $shell->run(sprintf('rm %s', $temp_file)); + $context->getStyle()->comment(sprintf('Handled %s successfully!', $dest)); } $shell->run(sprintf('#!docker exec %s #!chmod 700 /root/.ssh', $container_name)); $shell->run(sprintf('#!docker exec %s #!chown -R root /root/.ssh', $container_name)); @@ -463,5 +490,4 @@ private function getDockerContainerName(HostConfig $host_config, TaskContextInte )); } } - } diff --git a/src/Method/DrushMethod.php b/src/Method/DrushMethod.php index 8ea75ac2..7e6d198c 100644 --- a/src/Method/DrushMethod.php +++ b/src/Method/DrushMethod.php @@ -11,7 +11,6 @@ use Phabalicious\Validation\ValidationErrorBag; use Phabalicious\Validation\ValidationErrorBagInterface; use Phabalicious\Validation\ValidationService; -use webignition\ReadableDuration\ReadableDuration; class DrushMethod extends BaseMethod implements MethodInterface { @@ -217,6 +216,7 @@ public function reset(HostConfig $host_config, TaskContextInterface $context) } } + $context->set('rootFolder', $host_config['siteFolder']); $script_method->runTaskSpecificScripts($host_config, 'reset', $context); // Keep calm and clear the cache. @@ -234,8 +234,12 @@ public function drush(HostConfig $host_config, TaskContextInterface $context) /** @var ShellProviderInterface $shell */ $shell = $this->getShell($host_config, $context); $shell->cd($host_config['siteFolder']); - $result = $shell->run('#!drush ' . $command); - $context->setResult('exitCode', $result->getExitCode()); + $context->setResult('shell', $shell); + $command = sprintf('cd %s; #!drush %s', $host_config['siteFolder'], $command); + $command = $shell->expandCommand($command); + $context->setResult('command', [ + $command + ]); } private function handleModules( @@ -399,7 +403,6 @@ public function listBackups(HostConfig $host_config, TaskContextInterface $conte if ($tokens) { $result[] = $tokens; } - } $existing = $context->getResult('files', []); @@ -612,8 +615,5 @@ private function setupConfigurationManagement(HostConfig $host_config, TaskConte } $shell->cd($cwd); - - - } } diff --git a/src/Method/FtpSyncMethod.php b/src/Method/FtpSyncMethod.php new file mode 100644 index 00000000..5285028e --- /dev/null +++ b/src/Method/FtpSyncMethod.php @@ -0,0 +1,196 @@ + 'filesFolder', + 'private' => 'privateFilesFolder' + ]; + + public function getName(): string + { + return 'ftp-sync'; + } + + public function supports(string $method_name): bool + { + return $method_name === $this->getName(); + } + + public function getGlobalSettings(): array + { + $defaults = parent::getGlobalSettings(); + $defaults['excludeFiles']['ftpSync'] = [ + '.git/', + 'node_modules/' + ]; + + return $defaults; + } + + public function getDefaultConfig(ConfigurationService $configuration_service, array $host_config): array + { + $return = parent::getDefaultConfig($configuration_service, $host_config); + $return['tmpFolder'] = '/tmp'; + $return['executables'] = [ + 'lftp' => 'lftp', + ]; + + $return['deployMethod'] = 'ftp-sync'; + $return['ftp'] = [ + 'port' => 21, + 'lftpOptions' => [ + '--ignore-time', + '--verbose=2', + '--no-perms' + ] + ]; + + return $return; + } + + public function validateConfig(array $config, ValidationErrorBagInterface $errors) + { + parent::validateConfig($config, $errors); + if (in_array('drush', $config['needs'])) { + $errors->addError('needs', 'The method `ftp-sync` is incompatible with the `drush`-method!'); + } + if ($config['deployMethod'] !== 'ftp-sync') { + $errors->addError('deployMethod', 'deployMethod must be `ftp-sync`!'); + } + $service = new ValidationService($config, $errors, 'Host-config '. $config['configName']); + $service->isArray('ftp', 'Please provide ftp-credentials!'); + if (!empty($config['ftp'])) { + $service = new ValidationService($config['ftp'], $errors, 'host-config.ftp '. $config['configName']); + $service->hasKeys([ + 'user' => 'the ftp user-name', + 'host' => 'the ftp host to connect to', + 'port' => 'the port to connect to', + 'rootFolder' => 'the rootfolder of your app on the remote file-system', + ]); + } + } + + /** + * @param HostConfig $host_config + * @param TaskContextInterface $context + * @param ShellProviderInterface $shell + * @param string $install_dir + * @throws \Phabalicious\Exception\MethodNotFoundException + * @throws \Phabalicious\Exception\MissingScriptCallbackImplementation + * @throws \Phabalicious\Exception\TaskNotFoundInMethodException + */ + protected function createAppCode( + HostConfig $host_config, + TaskContextInterface $context, + ShellProviderInterface $shell, + string $install_dir + ) { + // First, create an app in a temporary-folder. + $stages = $context->getConfigurationService()->getSetting( + 'appStages.createCode', + AppDefaultStages::CREATE_CODE + ); + + $cloned_host_config = clone $host_config; + $keys = ['rootFolder', 'composerRootFolder', 'gitRootFolder']; + foreach ($keys as $key) { + $cloned_host_config[$key] = $install_dir; + } + $shell->cd($cloned_host_config['tmpFolder']); + $context->set('outerShell', $shell); + + AppDefaultStages::executeStages( + $context->getConfigurationService()->getMethodFactory(), + $cloned_host_config, + $stages, + 'appCreate', + $context, + 'Creating code' + ); + + // Run deploy scripts + /** @var ScriptMethod $script_method */ + $script_method = $context->getConfigurationService()->getMethodFactory()->getMethod('script'); + $context->set('variables', [ + 'installFolder' => $install_dir + ]); + $context->set('rootFolder', $install_dir); + $script_method->runTaskSpecificScripts($cloned_host_config, 'deploy', $context); + + $context->setResult('skipResetStep', true); + } + /** + * @param HostConfig $host_config + * @param TaskContextInterface $context + * @throws \Phabalicious\Exception\MethodNotFoundException + * @throws \Phabalicious\Exception\MissingScriptCallbackImplementation + * @throws \Phabalicious\Exception\TaskNotFoundInMethodException + */ + public function deploy(HostConfig $host_config, TaskContextInterface $context) + { + if ($host_config['deployMethod'] !== 'ftp-sync') { + return; + } + + if (empty($host_config['ftp']['password'])) { + $ftp = $host_config['ftp']; + $ftp['password'] = $context->getPasswordManager()->getPasswordFor($ftp['host'], $ftp['port'], $ftp['user']); + $host_config['ftp'] = $ftp; + } + + $install_dir = $host_config['tmpFolder'] . '/' . $host_config['configName'] . '-' . time(); + $context->set('installDir', $install_dir); + + $shell = $this->getShell($host_config, $context); + $this->createAppCode($host_config, $context, $shell, $install_dir); + + $exclude = $context->getConfigurationService()->getSetting('excludeFiles.ftpSync', []); + $options = implode(' ', $host_config['ftp']['lftpOptions']); + if (count($exclude)) { + $options .= ' --exclude ' . implode(' --exclude ', $exclude); + } + + // Now we can sync the files via FTP. + $command_file = $host_config['tmpFolder'] . '/lftp_commands_' . time() . '.x'; + $shell->run(sprintf('touch %s', $command_file)); + $shell->run(sprintf( + 'echo "open -u %s,%s -p%s %s" >> %s', + $host_config['ftp']['user'], + $host_config['ftp']['password'], + $host_config['ftp']['port'], + $host_config['ftp']['host'], + $command_file + )); + $shell->run(sprintf( + 'echo "mirror %s -c -e -RL %s %s" >> %s', + $options, + $install_dir, + $host_config['ftp']['rootFolder'], + $command_file + )); + + $shell->run(sprintf('echo "exit" >> %s', $command_file)); + + $shell->run(sprintf('#!lftp -f %s', $command_file)); + + // Cleanup. + $shell->run(sprintf('rm %s', $command_file)); + $shell->run(sprintf('rm -rf %s', $install_dir)); + + // Do not run any next tasks. + $context->setResult('runNextTasks', []); + } +} diff --git a/src/Method/GitMethod.php b/src/Method/GitMethod.php index 4e979846..8249af16 100644 --- a/src/Method/GitMethod.php +++ b/src/Method/GitMethod.php @@ -55,6 +55,14 @@ public function validateConfig(array $config, ValidationErrorBagInterface $error $validation->hasKey('branch', 'git needs a branch-name so it can run deployments.'); } + public function alterConfig(ConfigurationService $configuration_service, array &$data) + { + parent::alterConfig($configuration_service, $data); // TODO: Change the autogenerated stub + if (empty($data['deployMethod'])) { + $data['deployMethod'] = 'git'; + } + } + public function getVersion(HostConfig $host_config, TaskContextInterface $context) { $host_config->shell()->cd($host_config['gitRootFolder']); @@ -88,6 +96,9 @@ public function version(HostConfig $host_config, TaskContextInterface $context) */ public function deploy(HostConfig $host_config, TaskContextInterface $context) { + if ($host_config['deployMethod'] !== 'git') { + return ; + } $shell = $this->getShell($host_config, $context); $shell->cd($host_config['gitRootFolder']); if (!$this->isWorkingcopyClean($host_config, $context)) { @@ -172,5 +183,4 @@ public function appCreate(HostConfig $host_config, TaskContextInterface $context $shell->run('touch .projectCreated'); $shell->cd($cwd); } - -} \ No newline at end of file +} diff --git a/src/Method/MethodFactory.php b/src/Method/MethodFactory.php index 3bbd845d..4d625ebd 100644 --- a/src/Method/MethodFactory.php +++ b/src/Method/MethodFactory.php @@ -94,9 +94,12 @@ public function runTask( TaskContextInterface $context = null, $nextTasks = [] ) { + $context->setResult('runNextTasks', $nextTasks); $this->preflight('preflight', $task_name, $configuration, $context); $this->runTaskImpl($task_name . 'Prepare', $configuration, $context, false); $this->runTaskImpl($task_name, $configuration, $context, true); + + $nextTasks = $context->getResult('runNextTasks', []); if (!empty($nextTasks)) { foreach ($nextTasks as $next_task_name) { $this->runTask($next_task_name, $configuration, $context); diff --git a/src/Method/ScriptMethod.php b/src/Method/ScriptMethod.php index e7ab6220..d95da6ab 100644 --- a/src/Method/ScriptMethod.php +++ b/src/Method/ScriptMethod.php @@ -56,9 +56,7 @@ public function runScript(HostConfig $host_config, TaskContextInterface $context $context->setShell($host_config->shell()); } - $root_folder = isset($host_config['siteFolder']) - ? $host_config['siteFolder'] - : isset($host_config['rootFolder']) + $root_folder = isset($host_config['rootFolder']) ? $host_config['rootFolder'] : '.'; $root_folder = $context->get('rootFolder', $root_folder); @@ -98,10 +96,8 @@ public function runScript(HostConfig $host_config, TaskContextInterface $context ); $context->setResult('exitCode', $result ? $result->getExitCode() : 0); - } catch (UnknownReplacementPatternException $e) { - $context->getOutput() - ->writeln('Unknown replacement in line ' . $e->getOffendingLine() . ''); + $context->getStyle()->error('Unknown replacement in line ' . $e->getOffendingLine()); $printed_replacements = array_map(function ($key) use ($replacements) { $value = $replacements[$key]; @@ -110,8 +106,7 @@ public function runScript(HostConfig $host_config, TaskContextInterface $context } return [$key, $value]; }, array_keys($replacements)); - $style = new SymfonyStyle($context->getInput(), $context->getOutput()); - $style->table(['Key', 'Replacement'], $printed_replacements); + $context->getStyle()->table(['Key', 'Replacement'], $printed_replacements); } } @@ -153,6 +148,10 @@ private function runScriptImpl( } foreach ($commands as $line) { + $line = trim($line); + if (empty($line)) { + continue; + } $result = Utilities::extractCallback($line); $callback_handled = false; if ($result) { @@ -296,7 +295,22 @@ public function runTaskSpecificScripts(HostConfig $config, string $task, TaskCon if (!empty($common_scripts[$task][$type])) { $script = $common_scripts[$task][$type]; - $this->logger->info('Running common script for task `' . $task . '` and type `' . $type . '`'); + $this->logger->info(sprintf( + 'Running common script for task `%s` and type `%s`', + $task, + $type + )); + $context->set('scriptData', $script); + $this->runScript($config, $context); + } + + if (!empty($config[$task])) { + $script = $config[$task]; + $this->logger->info(sprintf( + 'Running host-specific script for task `%s` and host `%s`', + $task, + $config['configName'] + )); $context->set('scriptData', $script); $this->runScript($config, $context); } @@ -343,4 +357,4 @@ public function postflightTask(string $task, HostConfig $config, TaskContextInte parent::postflightTask($task, $config, $context); $this->runTaskSpecificScripts($config, $task . 'Finished', $context); } -} \ No newline at end of file +} diff --git a/src/Method/TaskContext.php b/src/Method/TaskContext.php index eae382d1..1ec438cc 100644 --- a/src/Method/TaskContext.php +++ b/src/Method/TaskContext.php @@ -7,10 +7,15 @@ use Phabalicious\Configuration\ConfigurationService; use Phabalicious\ShellProvider\CommandResult; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Utilities\PasswordManager; use Phabalicious\Utilities\Utilities; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; class TaskContext implements TaskContextInterface { @@ -30,6 +35,10 @@ class TaskContext implements TaskContextInterface private $shell; + private $passwordManager; + + private $io; + public function __construct(BaseOptionsCommand $command, InputInterface $input, OutputInterface $output) { $this->setInput($input); @@ -144,4 +153,31 @@ public function clearResults() { $this->result = []; } -} \ No newline at end of file + + public function askQuestion(string $question) + { + $question = new Question($question); + $question_helper = new QuestionHelper(); + return $question_helper->ask($this->input, $this->output, $question); + } + + public function getPasswordManager() + { + if (!$this->passwordManager) { + $this->passwordManager = new PasswordManager($this); + } + + return $this->passwordManager; + } + + /** + * @return SymfonyStyle + */ + public function getStyle() + { + if (!$this->io) { + $this->io = new SymfonyStyle($this->getInput(), $this->getOutput()); + } + return $this->io; + } +} diff --git a/src/Method/TaskContextInterface.php b/src/Method/TaskContextInterface.php index d9307609..6d41c239 100644 --- a/src/Method/TaskContextInterface.php +++ b/src/Method/TaskContextInterface.php @@ -7,8 +7,10 @@ use Phabalicious\Configuration\ConfigurationService; use Phabalicious\ShellProvider\CommandResult; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Utilities\PasswordManagerInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; interface TaskContextInterface { @@ -55,7 +57,15 @@ public function setShell(ShellProviderInterface $shell); public function mergeResults(TaskContextInterface $context); + public function askQuestion(string $string); + /** + * @return PasswordManagerInterface + */ + public function getPasswordManager(); + /** + * @return SymfonyStyle + */ + public function getStyle(); } - diff --git a/src/ShellCompletion/FishShellCompletionDescriptor.php b/src/ShellCompletion/FishShellCompletionDescriptor.php index f0d269d7..e245c8bb 100644 --- a/src/ShellCompletion/FishShellCompletionDescriptor.php +++ b/src/ShellCompletion/FishShellCompletionDescriptor.php @@ -31,18 +31,19 @@ public function __construct() */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { + global $argv; $command = $options['command']; if (!$command instanceof CompletionAwareInterface) { return; } $this->output->write( - "complete -c pha -n '__fish_seen_subcommand_from " . $command->getName() . + "complete -c phab -n '__fish_seen_subcommand_from " . $command->getName() . "' -f" ); global $argv; $this->output->write( - " -a '(__fish_pha_get_arguments " . + " -a '(__fish_phab_get_arguments " . $command->getName() . ' ' . $argument->getName() . ")'" @@ -63,9 +64,10 @@ protected function describeInputArgument(InputArgument $argument, array $options */ protected function describeInputOption(InputOption $option, array $options = array()) { + global $argv; $command = $options['command']; $this->output->write( - "complete -c pha -n '__fish_seen_subcommand_from " . $command->getName() . + "complete -c phab -n '__fish_seen_subcommand_from " . $command->getName() . "' -f" ); @@ -91,9 +93,9 @@ protected function describeInputOption(InputOption $option, array $options = arr if ($command instanceof CompletionAwareInterface) { global $argv; $this->output->write( - "complete -c pha -n '__fish_seen_subcommand_from " . + "complete -c phab -n '__fish_seen_subcommand_from " . $command->getName() . - " and __fish_seen_argument --" . + "; and __fish_phab_seen_argument -l " . $option->getName() . "' -f" ); @@ -103,7 +105,7 @@ protected function describeInputOption(InputOption $option, array $options = arr } $this->output->writeln( - " -a '(__fish_pha_get_options " . + " -a '(__fish_phab_get_options " . $command->getName() . ' ' . $option->getName() . ")'" @@ -131,8 +133,10 @@ protected function describeInputDefinition(InputDefinition $definition, array $o */ protected function describeCommand(Command $command, array $options = array()) { + global $argv; + $this->output->writeln( - "complete -c pha -n '__fish_use_subcommand' -f -a " . + "complete -c phab -n '__fish_use_subcommand' -f -a " . $command->getName() . " -d '" . $command->getDescription() . @@ -156,17 +160,39 @@ protected function describeCommand(Command $command, array $options = array()) protected function describeApplication(Application $application, array $options = array()) { global $argv; - $this->output->writeln('complete -c pha -e'); - $this->output->writeln('function __fish_pha_get_options'); + $this->output->writeln('complete -c phab -e'); + $this->output->writeln('function __fish_phab_get_options'); $this->output->writeln(' set -l CMD (commandline -ocp)'); - $this->output->writeln( ' ' . $argv[0] . ' _completion --complete-command $argv[1] --complete-option $argv[2] --command-line "$CMD" '); + $this->output->writeln(' ' . $argv[0] . + ' _completion --complete-command $argv[1] --complete-option $argv[2] --command-line "$CMD" '); $this->output->writeln('end'); - $this->output->writeln('function __fish_pha_get_arguments'); + $this->output->writeln('function __fish_phab_get_arguments'); $this->output->writeln(' set -l CMD (commandline -ocp)'); - $this->output->writeln( ' ' . $argv[0] . ' _completion --complete-command $argv[1] --complete-argument $argv[2] --command-line "$CMD" '); + $this->output->writeln(' ' . $argv[0] . + ' _completion --complete-command $argv[1] --complete-argument $argv[2] --command-line "$CMD" '); + $this->output->writeln('end'); + $this->output->writeln('function __fish_phab_seen_argument'); + $this->output->writeln(' argparse \'s/short=+\' \'l/long=+\' -- $argv'); + + $this->output->writeln(' set cmd (commandline -co)'); + $this->output->writeln(' set -e cmd[1]'); + $this->output->writeln(' for t in $cmd'); + $this->output->writeln(' for s in $_flag_s'); + $this->output->writeln(' if string match -qr "^-[A-z0-9]*"$s"[A-z0-9]*\$" -- $t'); + $this->output->writeln(' return 0'); + $this->output->writeln(' end'); + $this->output->writeln(' end'); + $this->output->writeln(''); + $this->output->writeln(' for l in $_flag_l'); + $this->output->writeln(' if string match -q -- "--$l" $t'); + $this->output->writeln(' return 0'); + $this->output->writeln(' end'); + $this->output->writeln(' end'); + $this->output->writeln(' end'); + $this->output->writeln(''); + $this->output->writeln(' return 1'); $this->output->writeln('end'); - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); diff --git a/src/ShellProvider/LocalShellProvider.php b/src/ShellProvider/LocalShellProvider.php index 7436507e..19845e52 100644 --- a/src/ShellProvider/LocalShellProvider.php +++ b/src/ShellProvider/LocalShellProvider.php @@ -54,7 +54,7 @@ public function validateConfig(array $config, ValidationErrorBagInterface $error ); } - public function createShellProcess(array $command = [], $options = []): Process + public function createShellProcess(array $command = [], array $options = []): Process { $shell_command = $this->getShellCommand($options); if (count($command) > 0) { @@ -154,13 +154,7 @@ public function run(string $command, $capture_output = false, $throw_exception_o } while (empty($exit_code)); $exit_code = intval(str_replace(self::RESULT_IDENTIFIER, '', $exit_code), 10); - foreach ($lines as $line) { - if (!$capture_output && $this->output) { - $this->output->writeln($line); - } elseif ($capture_output) { - $this->logger->debug($line); - } - } + $cr = new CommandResult($exit_code, $lines); if ($cr->failed() && !$capture_output && $throw_exception_on_error) { $cr->throwException(sprintf('`%s` failed!', $command)); @@ -235,5 +229,4 @@ public function createTunnelProcess(HostConfig $target_config, array $prefix = [ { throw new \InvalidArgumentException('Local shells cannot handle tunnels!'); } - -} \ No newline at end of file +} diff --git a/src/ShellProvider/ShellProviderInterface.php b/src/ShellProvider/ShellProviderInterface.php index d8311de0..8884622e 100644 --- a/src/ShellProvider/ShellProviderInterface.php +++ b/src/ShellProvider/ShellProviderInterface.php @@ -61,7 +61,7 @@ public function runProcess(array $cmd, TaskContextInterface $context, $interacti public function getShellCommand(array $options = []): array; - public function createShellProcess(array $command = []): Process; + public function createShellProcess(array $command = [], array $options = []): Process; public function createTunnelProcess(HostConfig $target_config, array $prefix = []); diff --git a/src/ShellProvider/SshShellProvider.php b/src/ShellProvider/SshShellProvider.php index a9a7b4f5..2d5f496f 100644 --- a/src/ShellProvider/SshShellProvider.php +++ b/src/ShellProvider/SshShellProvider.php @@ -96,6 +96,9 @@ public function getShellCommand(array $options = []): array $this->hostConfig['port'], ]; $this->addCommandOptions($command); + if (!empty($options['tty'])) { + $command[] = '-t'; + } $command[] = $this->hostConfig['user'] . '@' . $this->hostConfig['host']; return $command; diff --git a/src/Utilities/AppDefaultStages.php b/src/Utilities/AppDefaultStages.php new file mode 100644 index 00000000..4cfec1a0 --- /dev/null +++ b/src/Utilities/AppDefaultStages.php @@ -0,0 +1,74 @@ + 'installCode', + ], + [ + 'stage' => 'spinUp', + ], + [ + 'stage' => 'installDependencies', + ], + [ + 'stage' => 'install', + ], + ]; + + const DEPLOY = [ + [ + 'stage' => 'spinUp', + ] + ]; + + const DESTROY = [ + [ + 'stage' => 'spinDown', + ], + [ + 'stage' => 'deleteContainer', + ], + ]; + + const CREATE_CODE = [ + [ + 'stage' => 'installCode', + ], + [ + 'stage' => 'installDependencies', + ], + ]; + + + /** + * @param MethodFactory $method_factory + * @param HostConfig $host_config + * @param array $stages + * @param string $command + * @param TaskContextInterface $context + * @param string $message + * @throws \Phabalicious\Exception\MethodNotFoundException + * @throws \Phabalicious\Exception\TaskNotFoundInMethodException + */ + public static function executeStages( + MethodFactory $method_factory, + HostConfig $host_config, + array $stages, + string $command, + TaskContextInterface $context, + string $message + ) { + foreach ($stages as $stage) { + $context->getOutput()->writeln(sprintf('%s, stage %s', $message, $stage['stage'])); + $context->set('currentStage', $stage); + $method_factory->runTask($command, $host_config, $context); + } + } +} diff --git a/src/Utilities/PasswordManager.php b/src/Utilities/PasswordManager.php new file mode 100644 index 00000000..86d23eca --- /dev/null +++ b/src/Utilities/PasswordManager.php @@ -0,0 +1,57 @@ +context = $context; + $this->readPasswords(); + } + + public function getPasswordFor(string $host, int $port, string $user) + { + $key = $this->getKey($host, $port, $user); + if (!empty($this->passwords[$key])) { + return $this->passwords[$key]; + } + + $pw = $this->context->askQuestion(sprintf('Please provide a password for `%s@%s`: ', $user, $host)); + $this->passwords[$key] = $pw; + return $pw; + } + + private function getKey($host, $port, $user) + { + return sprintf('%s@%s:%s', $user, $host, $port); + } + + private function readPasswords() + { + $file = getenv("HOME"). '/.phabalicious-credentials'; + if (!file_exists($file)) { + return; + } + + $data = Yaml::parseFile($file); + + if (!is_array($data)) { + throw new \RuntimeException(sprintf('Could not parse %s', $file)); + } + + $this->passwords = $data; + } + +} \ No newline at end of file diff --git a/src/Utilities/PasswordManagerInterface.php b/src/Utilities/PasswordManagerInterface.php new file mode 100644 index 00000000..89eb6d6c --- /dev/null +++ b/src/Utilities/PasswordManagerInterface.php @@ -0,0 +1,10 @@ +application = new Application(); + $this->application->setVersion('3.0.0'); + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + + $configuration = new ConfigurationService($this->application, $logger); + $method_factory = new MethodFactory($configuration, $logger); + $method_factory->addMethod(new ScriptMethod($logger)); + + $this->application->add(new AppScaffoldCommand($configuration, $method_factory)); + } + + /** + * @group docker + */ + public function testAppScaffolder() + { + $target_folder = getcwd() . '/tmp'; + if (!is_dir($target_folder)) { + mkdir($target_folder); + } + + $command = $this->application->find('app:scaffold'); + $commandTester = new CommandTester($command); + $commandTester->execute(array( + '--short-name' => 'TST', + '--name' => 'Test', + '--output' => $target_folder, + '--override' => true, + 'scaffold-url' => getcwd() . '/assets/scaffold-tests/scaffold-drupal-commerce.yml' + )); + + // the output of the command in the console + $output = $commandTester->getDisplay(); + + $this->checkFileContent($target_folder . '/test/.fabfile.yaml', 'name: Test'); + $this->checkFileContent($target_folder . '/test/.fabfile.yaml', 'key: tst'); + $this->checkFileContent($target_folder . '/test/.fabfile.yaml', 'host: test.test'); + $this->checkFileContent( + $target_folder . '/test/web/modules/custom/tst_deploy/tst_deploy.info.yml', + 'name: Test deployment module' + ); + $this->checkFileContent( + $target_folder . '/test/web/modules/custom/tst_deploy/tst_deploy.info.yml', + 'name: Test deployment module' + ); + $this->checkFileContent( + $target_folder . '/test/web/modules/custom/tst_deploy/tst_deploy.install', + 'function tst_deploy_install()' + ); + shell_exec(sprintf('rm -rf %s', $target_folder)); + } + + private function checkFileContent($filename, $needle) + { + + $haystack = file_get_contents($filename); + $this->assertContains($needle, $haystack); + } +} diff --git a/tests/DrushMethodTest.php b/tests/DrushMethodTest.php index ae1f68ed..f738e96c 100644 --- a/tests/DrushMethodTest.php +++ b/tests/DrushMethodTest.php @@ -27,7 +27,6 @@ public function setup() $method_factory->addMethod($this->method); $this->configurationService->readConfiguration(getcwd() . '/assets/drush-tests/fabfile.yaml'); - } public function testGetDefaultConfig() diff --git a/tests/ScriptMethodTest.php b/tests/ScriptMethodTest.php index 6bec760c..4fbe6a83 100644 --- a/tests/ScriptMethodTest.php +++ b/tests/ScriptMethodTest.php @@ -56,7 +56,8 @@ public function setUp() $this->context = new TaskContext( $this->getMockBuilder(BaseCommand::class)->disableOriginalConstructor()->getMock(), $this->getMockBuilder(InputInterface::class)->getMock(), - $this->getMockBuilder(OutputInterface::class)->getMock()); + $this->getMockBuilder(OutputInterface::class)->getMock() + ); $this->context->setConfigurationService($this->configurationService); } @@ -228,10 +229,12 @@ public function testTaskSpecificScripts() $this->assertEquals([ 'deployPrepare on dev', + 'deployPrepare on hostA', 'deploy on dev', - 'deployFinished on dev' + 'deploy on hostA', + 'deployFinished on dev', + 'deployFinished on hostA' ], $this->context->get('debug')); - } public function scriptDebugCallback(TaskContextInterface $context, $message) @@ -243,5 +246,4 @@ public function scriptDebugCallback(TaskContextInterface $context, $message) $debug[] = $message; $context->set('debug', $debug); } - } diff --git a/tests/assets/scaffold-tests/scaffold-drupal-8.yml b/tests/assets/scaffold-tests/scaffold-drupal-8.yml index ba1402c9..4b35509a 100644 --- a/tests/assets/scaffold-tests/scaffold-drupal-8.yml +++ b/tests/assets/scaffold-tests/scaffold-drupal-8.yml @@ -18,11 +18,9 @@ sshKeys: scaffold: - rm -rf %rootFolder% - composer create-project %composerProject% %rootFolder% --stability dev --no-interaction - - cd %rootFolder%; composer require drupal/coffee - cd %rootFolder%; composer require drupal/devel - copy_assets(%rootFolder%) - copy_assets(%rootFolder%/web/modules/custom/%shortName%_deploy, deployModuleAssets) - - copy_assets(%rootFolder%/ssh-keys, sshKeys) - cd %rootFolder%; git init . - cd %rootFolder%; git add . - cd %rootFolder%; git commit -m "Initial commit by phabalicious" \ No newline at end of file diff --git a/tests/assets/scaffold-tests/ssh-keys/docker-root-key b/tests/assets/scaffold-tests/ssh-keys/docker-root-key deleted file mode 100644 index 9a71d707..00000000 --- a/tests/assets/scaffold-tests/ssh-keys/docker-root-key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA5srq1zzi2/PQIkmPGGD2ThyN98yJG1g4tBj+AUH7m6oW2P7M -U6WNWdZF1ajcKN1zQ25GjOw4jfn/8VkC93ANgeRBX/NFQoiYkjBJgBv+/WGD06Fx -8TeKu2zTK3TdYwcLVSHyQEfEma604XTjWI8VGb/yuj6iP0IIxD2g5j9TChD9HBxG -bwwC8WYsLsW8OnRTLKOvGRTIZnjFTJREkBlmhs3lAswDb4Od62N90jOclTmcaSN1 -Fuo0MHeaBF1wsjHlsX3yMvc5yyuSExjwDWjlhs16Pnz56upapmZPk0lh7AjM7MKq -QKjuQZmquPD4Ebp/zeicS3P8Wvdod8pDd4DqDwIDAQABAoIBAQCBCX8X7IDifYUn -Rn1tIflUXv65R3B5C3BYsYiC54Nn20d+96cCNZO8YOMWvJyrdHVXhDaJ4CEWsGp7 -ZEsWUV4b+6TZoshclMbJJZpSuFRvErCECMYOGgHFHOlMaMDG909Mv/gUHIw9aMLV -M2dRQl6H0RxDKXXJbIA+SD7HvSwOebVXqu/RC6pU6hK9pVUq0r7X3ZAD53jp+PBg -oj4S8sUu4Rh8yb9hNMWLZkmvkNm69gr0Yhj6UOpP1s+f1ztvB4uAhWu5bgWzqJsG -8bc5tyDTksb0OsXfIeJMnDg/lte9cF0GFEfp6WlC5m5Dkx1ffnWGfdzzLMDhI7pF -C6361nAxAoGBAPXTN1ri7Q/lVXRU+CiNAZhKaw3XVfbGKSH6w19MQ7BanWvti5QX -At1IGX9KrX9zQ6YtUU/zcsEvB4dr/qaRu8sszGoYLsiizcgrReUeR70WP5nNlAaA -50RfF7IESkfiWAOvnUgsK462di5QtyU+47jNA5cf8MeKu/jE4AvUnxu9AoGBAPBY -apNn0pO/OYkrOTEG3LtXLDVuPTaV1RP5I4bz4Az9UmPUsdMwvBASx/Qe2R5ZgVRa -YvO9sBBH5V21rgH9On2rqGfSKrhvteXa0wN5+iQ3BBlRePhgDQ3D+IyuNGK1q4PY -fTe59pEwAjVBov3wrC4wDtFEURWrEtlJqAirVTO7AoGBAL3KMsSiyvAo2U8Vgvqb -0w1m9zwacq4x0/P+DPT4hpITg9Kd9dOB6J47WiQi3cy2ixYzisG1bXWk/6UYReUI -QvrcPX3z6brRpxrR/gak2DIuiTAPvic/Qk5RNJQmJ8tT/yvpW/8qyv5F9PxRKPVC -lsJI1mrJKDaG8BViuq+nmqfpAoGBANtsXSBmUOGCW0zXoUcZNLv0QfAlzMzgzb+G -XOEAFTTGsUljDVX50Df8bYB6CU6j+GbCfkR4kRzMBqMfEtXOEnBZH05pmYb4teA7 -fxpVagFWGO/kacSYLFK871XAVSMpKIUeSHNv26OBaQKmAeBEsW0Zgu2aqUxW+sZV -cIs5oHexAoGASkK64OrWItZD8R06J3ZAnThumcvJZCSwY53hSBJIcjcL/e8dazTd -wjLqqBDGsKQMTuzqNs0Wj4lvygdNGsB5zFQxGuoocaxyCC2+fvLNzK3maAva+gsX -jLwzoboZiTv0qqDw9ywERvy1AahA0MDGRoFMuarGqXRrkOQNPDx6/NM= ------END RSA PRIVATE KEY----- diff --git a/tests/assets/scaffold-tests/ssh-keys/docker-root-key.pub b/tests/assets/scaffold-tests/ssh-keys/docker-root-key.pub deleted file mode 100644 index 57014ce8..00000000 --- a/tests/assets/scaffold-tests/ssh-keys/docker-root-key.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmyurXPOLb89AiSY8YYPZOHI33zIkbWDi0GP4BQfubqhbY/sxTpY1Z1kXVqNwo3XNDbkaM7DiN+f/xWQL3cA2B5EFf80VCiJiSMEmAG/79YYPToXHxN4q7bNMrdN1jBwtVIfJAR8SZrrThdONYjxUZv/K6PqI/QgjEPaDmP1MKEP0cHEZvDALxZiwuxbw6dFMso68ZFMhmeMVMlESQGWaGzeUCzANvg53rY33SM5yVOZxpI3UW6jQwd5oEXXCyMeWxffIy9znLK5ITGPANaOWGzXo+fPnq6lqmZk+TSWHsCMzswqpAqO5Bmaq48PgRun/N6JxLc/xa92h3ykN3gOoP docker@docker.local diff --git a/tests/assets/scaffold-tests/ssh-keys/known_hosts b/tests/assets/scaffold-tests/ssh-keys/known_hosts deleted file mode 100644 index bd1d53a5..00000000 --- a/tests/assets/scaffold-tests/ssh-keys/known_hosts +++ /dev/null @@ -1,2 +0,0 @@ -[source.factorial.io]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfp3kjMalh/c10bjVYF00IotFIwPQrr8iOhBjfRbe5lS43Gd3+XkTcKdUnmPYvF6wt6YxvUYcn0YYfzncpA0FzFw8461gcBoIVRvC+kV+LOsi/jy7lkfSoCCThLWW0EE8iP0FaorzVl4SrJtHcRiv9v0RY8CzZ8LeADsWu8bhY3eHdcg4F4tBvggrRxja7HbSszisp9ls6fgi+KhowBW7FN0E1cFl8HOZ6K9K0+Qg3PM/3ivpWQ66IGXDRAPHK/bMwME09wAl7o8Qb67SnNKYxqaKunB+bJisez6j5ETuzwZtiKxQ8Hh3Ke33pHRi8xqWmmc4N0EK3ENkPdZ6P7w/9 -github.com,192.30.252.128 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== diff --git a/tests/assets/script-tests/fabfile.yaml b/tests/assets/script-tests/fabfile.yaml index 520b1ebc..adad782a 100644 --- a/tests/assets/script-tests/fabfile.yaml +++ b/tests/assets/script-tests/fabfile.yaml @@ -31,4 +31,10 @@ hosts: scripts: test: - echo "Hello" - - echo "World" \ No newline at end of file + - echo "World" + deployPrepare: + - debug(deployPrepare on hostA) + deploy: + - debug(deploy on hostA) + deployFinished: + - debug(deployFinished on hostA) \ No newline at end of file