phpphp-deployer

PHPDeployer: set variable for local task based on host?


I have deployer running as part of a GitHub Action pipeline. I wanted to have the pipeline run the NPM build for me and copy it up as part of the deploy rather than having deployer using npm dev dependencies on the server. For core and stage sites I want to use debug build while prod gets the production build.

Full deploy.php below:

<?php
namespace Deployer;

require 'recipe/laravel.php';
require 'recipe/rsync.php';
require 'recipe/discord.php';

// Project name
set('application', 'xxxxxxxxxx');

// Project repository
set('repository', 'git@github.com:xxxxxxxxxx');

// Shared files/dirs between deploys
add('shared_files', []);
add('shared_dirs', []);

// Writable dirs by web server
add('writable_dirs', []);
set('allow_anonymous_stats', false);

set('dev_db', false);
set('dev_js_mode', 'prod');
set('rsync_src', __DIR__ . '/public/');
set('rsync_dest', '{{release_path}}/public/');
set('discord_channel', 'xxxxxxxxxx');
set('discord_token', 'xxxxxxxxxx');

// Hosts
host('core')
    ->hostname('xxxxxxxxxx.com')
    ->user('xxxxxxxxxx')
    ->set('deploy_path', '/home/xxxxxxxxxx.com')
    ->set('branch', 'master')
    ->set('dev_db', true)
    ->set('dev_js_mode', 'dev');

host('stage')
    ->hostname('xxxxxxxxxx')
    ->user('xxxxxxxxxx')
    ->set('deploy_path', '/home/xxxxxxxxxx.com')
    ->set('branch', 'stage')
    ->set('dev_js_mode', 'dev');

host('prod')
    ->hostname('xxxxxxxxxx.com')
    ->user('xxxxxxxxxx')
    ->set('deploy_path', '/home/xxxxxxxxxx.com')
    ->set('branch', 'prod');

// Custom Tasks
task('reload', function () {
    run('sudo systemctl restart php7.2-fpm');
    run('sudo systemctl restart nginx');
});

task('database', function() {
    $freshDb = get('dev_db');

    if ($freshDb === true) {
        run('composer run refresh-db --working-dir={{release_path}}/');
    } else {
        invoke('artisan:migrate');
    }
});

task('local:javascript:package', function() {
    run('npm ci');
    run('npm run {{dev_js_mode}}');
})->local();

task('local:javascript:deploy', [
    'rsync',
]);

task('local:javascript', [
    'local:javascript:package',
    'local:javascript:deploy',
]);

// modifications
before('deploy:symlink', 'local:javascript');
before('deploy:symlink', 'database');

after('deploy', 'reload');
after('rollback', 'reload');
after('deploy:failed', 'deploy:unlock');

before('deploy', 'discord:notify');
after('success', 'discord:notify:success');
after('deploy:failed', 'discord:notify:failure');

I have the dev_js_mode variable set by default to "prod" just to be sure that a debug build doesn't get out where I don't intend it.

If I run dep -vvv deploy core I can see the local:javascript:package step has dev_js_mode set to "prod", which I get because the command isn't running on a host but I can't figure out a way to get this to work.


Solution

  • After lots of trial n error, I've come up with two ways to solve my issue.

    1. Set a php variable via a remote closure before using it in the local task. Hacky, but it works:
    <?php
    namespace Deployer;
    
    set('testing', 'prod');
    
    $var = 'original';
    
    host('core')
        ->hostname('example.com')
        ->set('testing', 'dev');
    
    host('prod')
        ->hostname('example.com')
        ->set('testing', 'prod');
    
    task('remote:run', function() use (&$var) {
        $var = get('testing');
        run('echo remote {{testing}}, var is '. $var);
    });
    
    task('local:var', function() use (&$var) {
        run('echo local {{testing}}, var is '. $var);
    })->local();
    
    task('mytest', [
        'remote:run',
        'local:var',
    ]);
    
    1. Probably the more supported way -- more flexible or convoluted, depending on how you look at it -- is creating two versions of the local task and constraining them via something like stage. It creates 2-3 tasks instead of one:
    <?php
    namespace Deployer;
    
    host('core')
        ->hostname('example.com')
        ->stage('core');
    
    host('prod')
        ->hostname('example.com')
        ->stage('prod');
    
    task('remote:run', function(){
        run('echo remote');
    });
    
    task('local:core', function() {
        run('echo local, core only');
    })->local()->onStage('core');
    
    task('local:prod', function() {
        run('echo local, prod only');
    })->local()->onStage('prod');
    
    task('local', [
        'local:core',
        'local:prod',
    ]);
    
    task('mytest', [
        'remote:run',
        'local', // instead of 'local', you can just call both 'local:core' and 'local:prod'
    ]);