Skip to content

Commit

Permalink
add support of multiple values in host label (#3899)
Browse files Browse the repository at this point in the history
* add support of multiple values in host label

- example of usage:
  host('prod.example.org')
      ->set('hostname', 'example.cloud.google.com');
      ->setLabel(
          'state' => 'prod'
          'role' => ['build','web','redis']
      ]);

  host('prod-db.example.org')
        ->set('hostname', 'example.cloud.google.com');
        ->setLabel(
            'state' => 'prod'
            'role' => ['db']
        ]);
  host('qa.example.org')
      ->set('hostname', 'example.cloud.google.com');
      ->setLabel(
          'state' => 'qa'
          'role' => ['build','web','redis','db']
      ]);
  host('dev.example.org')
        ->set('hostname', 'example.cloud.google.com');
        ->setLabel(
            'state' => 'qa'
            'role' => ['build','web','redis','db']
        ]);
  ...

  desc('Clear pagespeed');
  task('pagespeed:clear', function () {
      run("rm -rf {{deploy_path}}/{{pagespeed_path}}* || true");
  })->select('stage=prod|qa & role=web'); // assuming pagespeed is installed only on prod and qa envs

* fix tests

* update docs

* sanitize the code

* sanitize the code

---------

Co-authored-by: Misha Medzhytov <[email protected]>
  • Loading branch information
magicaner and Misha Medzhytov authored Oct 14, 2024
1 parent 6541dca commit 7cda83f
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 10 deletions.
24 changes: 22 additions & 2 deletions docs/selector.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ host('db.example.com')
'env' => 'prod',
]);
```
or use `->addLables()` method to add labels to the existing host.

Now let's define a task to check labels:

Expand Down Expand Up @@ -54,6 +55,16 @@ task info

Label syntax is represented by [disjunctive normal form](https://en.wikipedia.org/wiki/Disjunctive_normal_form)
(**OR of ANDs**).
```
(condition1 AND condition2) OR (condition3 AND condition4)
```

Each condition in the subquery that is represented by [conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form)
```
(condition1 OR condition2) AND (condition3 OR condition4)
```

### Explanation

For example, `type=web,env=prod` is a selector of: `type=web` **OR** `env=prod`.

Expand All @@ -75,6 +86,15 @@ task info
[web.example.com] type:web env:prod
```

We can use `|` to define **OR** in a subquery. For example, `type=web|db & env=prod` is a selector
for hosts with (`type: web` **OR** `type: db`) **AND** `env: prod` labels.

```bash
$ dep info 'type=web|db & env=prod'
task info
[web.example.com] type:web env:prod
```

We can also use `!=` to negate a label. For example, `type!=web` is a selector for
all hosts which do not have a `type: web` label.

Expand Down Expand Up @@ -119,7 +139,7 @@ You can use the [select()](api.md#select) function to select hosts by selector i

```php
task('info', function () {
$hosts = select('type=web,env=prod');
$hosts = select('type=web|db,env=prod');
foreach ($hosts as $host) {
writeln('type:' . $host->get('labels')['type'] . ' env:' . $host->get('labels')['env']);
}
Expand All @@ -143,7 +163,7 @@ To restrict a task to run only on selected hosts, you can use the [select()](tas
```php
task('info', function () {
// ...
})->select('type=web,env=prod');
})->select('type=web|db,env=prod');
```
## Labels in YAML
Expand Down
7 changes: 7 additions & 0 deletions src/Host/Host.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ public function setLabels(array $labels): self
return $this;
}

public function addLabels(array $labels): self
{
$existingLabels = $this->getLabels() ?? [];
$this->setLabels(array_replace_recursive($existingLabels, $labels));
return $this;
}

public function getLabels(): ?array
{
return $this->config->get('labels', null);
Expand Down
13 changes: 11 additions & 2 deletions src/Selector/Selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ public static function apply(?array $conditions, Host $host): bool
foreach ($conditions as $hmm) {
$ok = [];
foreach ($hmm as [$op, $var, $value]) {
$ok[] = self::compare($op, $labels[$var] ?? null, $value);
if (is_array($value)) {
$orOk = [];
foreach ($value as $val) {
$orOk[] = self::compare($op, $labels[$var] ?? null, $val);
}
$ok[] = count(array_filter($orOk, $isTrue)) > 0;
} else {
$ok[] = self::compare($op, $labels[$var] ?? null, $value);
}
}
if (count($ok) > 0 && array_all($ok, $isTrue)) {
return true;
Expand Down Expand Up @@ -105,7 +113,8 @@ public static function parse(string $expression): array
continue;
}
if (preg_match('/(?<var>.+?)(?<op>!?=)(?<value>.+)/', $part, $match)) {
$conditions[] = [$match['op'], trim($match['var']), trim($match['value'])];
$values = array_map('trim', explode('|', trim($match['value'])));
$conditions[] = [$match['op'], trim($match['var']), $values];
} else {
$conditions[] = ['=', 'alias', trim($part)];
}
Expand Down
17 changes: 11 additions & 6 deletions tests/src/Task/ScriptManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,28 @@ public function testSelectsCombine()
{
$a = new Task('a');
$b = new Task('b');
$c = new Task('c');
$b->select('stage=beta');
$group = new GroupTask('group', ['a', 'b']);
$c->select('stage=alpha|beta & role=db');
$group = new GroupTask('group', ['a', 'b', 'c']);

$taskCollection = new TaskCollection();
$taskCollection->add($a);
$taskCollection->add($b);
$taskCollection->add($c);
$taskCollection->add($group);

$scriptManager = new ScriptManager($taskCollection);
self::assertEquals([$a, $b], $scriptManager->getTasks('group'));
self::assertEquals([$a, $b, $c], $scriptManager->getTasks('group'));
self::assertNull($a->getSelector());
self::assertEquals([[['=', 'stage', 'beta']]], $b->getSelector());
self::assertEquals([[['=', 'stage', ['beta']]]], $b->getSelector());
self::assertEquals([[['=', 'stage', ['alpha', 'beta']],['=', 'role', ['db']]]], $c->getSelector());

$group->select('role=prod');
self::assertEquals([$a, $b], $scriptManager->getTasks('group'));
self::assertEquals([[['=', 'role', 'prod']]], $a->getSelector());
self::assertEquals([[['=', 'stage', 'beta']],[['=', 'role', 'prod']]], $b->getSelector());
self::assertEquals([$a, $b, $c], $scriptManager->getTasks('group'));
self::assertEquals([[['=', 'role', ['prod']]]], $a->getSelector());
self::assertEquals([[['=', 'stage', ['beta']]],[['=', 'role', ['prod']]]], $b->getSelector());
self::assertEquals([[['=', 'stage', ['alpha', 'beta']],['=', 'role', ['db']]],[['=', 'role', ['prod']]]], $c->getSelector());
}

public function testThrowsExceptionIfTaskCollectionEmpty()
Expand Down

0 comments on commit 7cda83f

Please sign in to comment.