Skip to content

Add support for cv plugins #191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Sep 10, 2024
Merged

Add support for cv plugins #191

merged 18 commits into from
Sep 10, 2024

Conversation

totten
Copy link
Member

@totten totten commented Mar 5, 2024

Overview

Adds support for loading plugins (*.php files) to modify the behavior of cv. In particular, some useful things you might do are (a) defining new subcommands or (b) defining alias services (cv @mysite ext:list).

The design is based on heavily on loco CLI plugins.

Plugin Loading

Plugins are loaded from the CV_PLUGIN_PATH. If the path is not set, then it loads PHP files from these folders:

  • $HOME/.cv/plugin
  • /etc/cv/plugin
  • /usr/local/share/cv/plugin
  • /usr/share/cv/plugin

Each plugin is a *.php file.

Example: "Hello" Command

For example, to implement cv hello, you could create:

// FILE: /etc/cv/plugin/hello-command.php
use Civi\Cv\Cv;
use CvDeps\Symfony\Component\Console\Input\InputInterface;
use CvDeps\Symfony\Component\Console\Output\OutputInterface;
use CvDeps\Symfony\Component\Console\Command\Command;

Cv::dispatcher()->addListener('cv.app.commands', function($e) {
  $e['commands'][] = new class extends Command {
    protected function configure() {
      $this->setName('hello')
        ->setDescription('Say a greeting');
    }
    protected function execute(InputInterface $input, OutputInterface $output): int {
      $output->writeln('Hello there!');
      return 0;
    }
  };
});

The file doc/plugins.md gives more details.

Example: Site Aliases

Additionally, I've drafted a bigger example with support for site-aliases, eg

cv @mysite ext:list

The draft is at https://gist.github.com/totten/8241b80440221555c0051d4f3447fa40

Why do this as a plugin? Well, there are several little questions to sort out. Like:

  • Do you register aliases globally (in $HOME/.cv or /etc)? Or do you have contextual aliases (like @prod and @staging for a specific site)?
  • Do you use JSON or YAML?
  • Does each file define a single alias... or multiple?
  • Do you give a small number of powerful options... or a large number of narrow options? Compare:
    ## One generic option, "remote_command", can work with many kind of remotes...
    ## such as ssh, mosh, docker, or virsh.
    remote_command: "ssh myuser@example.com -P4567"
    
    ## If you don't know how to write those commands, or if the commands get somehow fiddly,
    ## then smaller flags might be easier for the user. But as a dev, you have to fiddle more to support each.
    ssh:
      hostname: example.com
      user: myuser
      port: 4567
  • Do you read metadata from other sources -- like drush config-files, wp-cli config-files, or civibuild config-files?

Whatever the eventual answers, we'd probably want to include something like this in cv by default. But TBH, I'm kinda ambivalent about the details of those questions. Seems appealing to incubate as a plugin...

Documentation

The PR also adds a file doc/plugins.md.

@totten totten force-pushed the master-plugin branch 2 times, most recently from a15109d to 8213535 Compare March 6, 2024 00:37
@totten
Copy link
Member Author

totten commented Mar 6, 2024

civibot, test this please

@totten
Copy link
Member Author

totten commented Jun 7, 2024

This might be problematic. cv uses namespace prefixing for cross-CMS support -- but only for PHAR. So:

  • Expressions like Cv::dispatcher() should be fine with basic-source-deployments or with PHAR-deployments.
  • Expressions like use ....\InputInterface may be problematic -- because the basic-source-deployments and PHAR-deployments would need different incantations:
    • use Symfony\Component\Console\Input\InputInterface (basic-source-deployment)
    • use Cvphar\Symfony\Component\Console\Input\InputInterface (PHAR-deployment)

Brainstorming:

  1. Maybe folks using cv-plugins would be focused on PHAR-deployments. So just recommend Cvphar variant.

  2. Maybe we define some different contracts for Commands -- so it's not necessary to reference \Symfony classes directly.

  3. Maybe we standardize on the PHAR-style naming -- and make a compatibility shim for basic-source-deployments.

    spl_autoload_register(function($class){
      if ($class looks like 'Cvphar\Symfony\...') {
        class_alias('Symfony\...', 'Cvphar\Symfony\...', )
      }
    });
  4. Similarly, it might work to have a whitelist of classes in a CvApi namespace, eg

    class_alias('CvApi\Symfony\...\InputInterface', 'Symfony\...\InputInterface');
    class_alias('CvApi\Symfony\...\OutputInterface', 'Symfony\...\OutputInterface');
    // During PHAR compilation, first symbol is preserved -- second symbol goes through namespace-prefixing.
    // The effect is that (on any deployment) `CvApi\...` is an alias pointing to the real class.

@totten
Copy link
Member Author

totten commented Jun 8, 2024

Rebased. Added a unit-test for a plugin which defines a new Command. Added class-aliases that should be portable between PHAR and non-PHAR environments.

@totten
Copy link
Member Author

totten commented Jun 9, 2024

I suspect most of the test-failures are because the test-matrix is written in the older style. This leads to testing some invalid combinations like drupal9,max==php82. Need to switch it over to Duderino to look more like the civix test matrix.

@totten
Copy link
Member Author

totten commented Jun 9, 2024

OK, after some general house-keeping on the repo (switch to phpunit9+duderino) and a rebase, the tests are passing. Also added some coverage for the internal hook that enables plugins to define @aliases.

@totten totten force-pushed the master-plugin branch 2 times, most recently from 510f432 to ae50ef9 Compare June 10, 2024 13:59
@vurt2
Copy link

vurt2 commented Jul 2, 2024

Hi, Martin from the civicamp Hamburg here:
I did test the hello example and rewrote our extension upgrade script as a cv plugin. I all worked like a charm.
It would be nice to have an option to deploy commands in civi-extensions (like drush does). But that would require to search all the extension dirs for a cv-plugin folder...

best, Martin

@totten
Copy link
Member Author

totten commented Sep 10, 2024

civibot, test this please

@totten
Copy link
Member Author

totten commented Sep 10, 2024

Cheers @vurt2 - very glad it worked.

Agree it would be handy for extensions to bundle cv-plugins, though doing so requires sorting some bootstrap issues. (I think... it requires a 2-phase command-dispatcher; an earlier phase to dispatch early/built-in commands like cv core:install; and then if that doesn't find anything, then another phase to boot+scan the Civi extensions. In any event, that'll have to be some kind of follow-up. Don't need to hold this one back any more.)

I had to do a few minor updates to accommodate recent Symfony 5/PHP8.4 updates. (In the example plugin-commands, the execute() methods needs to explicitly return an exit-code.)

But it's passing again. 🟢

@totten totten merged commit d7a2a78 into civicrm:master Sep 10, 2024
1 check passed
@totten totten deleted the master-plugin branch September 10, 2024 23:13
@vurt2
Copy link

vurt2 commented Sep 11, 2024

@totten Very cool. Thanks for the work!

@totten
Copy link
Member Author

totten commented Sep 26, 2024

JFYI @vurt2 - For cv v0.3.56, I've done some rearranging of the command classes.

Strictly speaking, the examples from before should still work the same way.

However, the examples from before were missing important things... like, they didn't actually bootstrap CiviCRM. (They just said "Hello"...) And this undocumented part changed (#218) -- most notably, with the introduction of a new base-class CvCommand.

I've updated the doc file: https://github.com/civicrm/cv/blob/master/doc/plugins.md

@vurt2
Copy link

vurt2 commented Sep 30, 2024

Thanks for the update @totten - yes, I had to boot cv with "$this->boot()". Cool, that you added this.

I encountered a small typo in the plugins.md: the use of functione instead of function in the structured-class style example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants