Completed
Pull Request — develop (#68)
by Marc
02:38 queued 14s
created

AnsiblePlaybook::colors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Asm\Ansible\Command;
6
7
use InvalidArgumentException;
8
9
/**
10
 * Class AnsiblePlaybook
11
 *
12
 * @package Asm\Ansible\Command
13
 * @author Marc Aschmann <[email protected]>
14
 */
15
final class AnsiblePlaybook extends AbstractAnsibleCommand implements AnsiblePlaybookInterface
16
{
17
18
    private bool $hasInventory = false;
19
20
    /**
21
     * Executes a command process.
22
     * Returns either exit code or string output if no callback is given.
23
     *
24
     * @param callable|null $callback
25
     * @return integer|string
26
     */
27
    public function execute(?callable $callback = null): int|string
28
    {
29
        $this->checkInventory();
30
31
        return $this->runProcess($callback);
32
    }
33
34
    /**
35
     * The play to be executed.
36
     *
37
     * @param string $playbook
38
     * @return AnsiblePlaybookInterface
39
     */
40
    public function play(string $playbook): AnsiblePlaybookInterface
41
    {
42
        $this->addBaseoption($playbook);
43
44
        return $this;
45
    }
46
47
    /**
48
     * Ask for SSH password.
49
     *
50
     * @return AnsiblePlaybookInterface
51
     */
52
    public function askPass(): AnsiblePlaybookInterface
53
    {
54
        $this->addParameter('--ask-pass');
55
56
        return $this;
57
    }
58
59
    /**
60
     * Ask for su password.
61
     *
62
     * @return AnsiblePlaybookInterface
63
     */
64
    public function askSuPass(): AnsiblePlaybookInterface
65
    {
66
        $this->addParameter('--ask-su-pass');
67
68
        return $this;
69
    }
70
71
    /**
72
     * Ask for sudo password.
73
     *
74
     * @return AnsiblePlaybookInterface
75
     */
76
    public function askBecomePass(): AnsiblePlaybookInterface
77
    {
78
        $this->addParameter('--ask-become-pass');
79
80
        return $this;
81
    }
82
83
    /**
84
     * Ask for vault password.
85
     *
86
     * @return AnsiblePlaybookInterface
87
     */
88
    public function askVaultPass(): AnsiblePlaybookInterface
89
    {
90
        $this->addParameter('--ask-vault-pass');
91
92
        return $this;
93
    }
94
95
    /**
96
     * Enable privilege escalation
97
     *
98
     * @return AnsiblePlaybookInterface
99
     * @see http://docs.ansible.com/ansible/become.html
100
     */
101
    public function become(): AnsiblePlaybookInterface
102
    {
103
        $this->addParameter('--become');
104
105
        return $this;
106
    }
107
108
    /**
109
     * Desired sudo user (default=root).
110
     *
111
     * @param string $user
112
     * @return AnsiblePlaybookInterface
113
     */
114
    public function becomeUser(string $user = 'root'): AnsiblePlaybookInterface
115
    {
116
        $this->addOption('--become-user', $user);
117
118
        return $this;
119
    }
120
121
    /**
122
     * Don't make any changes; instead, try to predict some of the changes that may occur.
123
     *
124
     * @return AnsiblePlaybookInterface
125
     */
126
    public function check(): AnsiblePlaybookInterface
127
    {
128
        $this->addParameter('--check');
129
130
        return $this;
131
    }
132
133
    /**
134
     * Connection type to use (default=smart).
135
     *
136
     * @param string $connection
137
     * @return AnsiblePlaybookInterface
138
     */
139
    public function connection(string $connection = 'smart'): AnsiblePlaybookInterface
140
    {
141
        $this->addOption('--connection', $connection);
142
143
        return $this;
144
    }
145
146
    /**
147
     * When changing (small) files and templates, show the
148
     * differences in those files; works great with --check.
149
     *
150
     * @return AnsiblePlaybookInterface
151
     */
152
    public function diff(): AnsiblePlaybookInterface
153
    {
154
        $this->addParameter('--diff');
155
156
        return $this;
157
    }
158
159
    /**
160
     * Example:
161
     * ```php
162
     * $ansible = new Ansible()->extraVars('path=/some/path');
163
     * ```
164
     * Sends extra variables to Ansible. The $extraVars parameter can be one of the following.
165
     *
166
     * ## Array
167
     * If an array is passed, it must contain the [ 'key' => 'value' ] pairs of the variables.
168
     *
169
     * Example:
170
     * ```php
171
     * $ansible = new Ansible()->playbook()->extraVars(['path' => 'some/path']);
172
     * ```
173
     *
174
     * ## File
175
     * As Ansible also supports extra vars loaded from an YML file, you can also pass a file path.
176
     *
177
     * Example:
178
     * ```php
179
     * $ansible = new Ansible()->playbook()->extraVars('/path/to/extra/vars.yml');
180
     * ```
181
     *
182
     * ## String
183
     * You can also pass the raw extra vars string directly.
184
185
     * Example:
186
     * ```php
187
     * $ansible = new Ansible()->playbook()->extraVars('path=/some/path');
188
     * ```
189
     *
190
     * @param string|array $extraVars
191
     * @return AnsiblePlaybookInterface
192
     */
193
    public function extraVars(string|array $extraVars = ''): AnsiblePlaybookInterface
194
    {
195
        if (empty($extraVars)) {
196
            return $this;
197
        }
198
199
        // Building the key=>value parameter
200
        if (is_array($extraVars)) {
201
            $vars = [];
202
            foreach ($extraVars as $key => $value) {
203
                $vars[] = sprintf('%s=%s', $key, $value);
204
            }
205
            $this->addOption('--extra-vars', implode(' ', $vars));
206
            return $this;
207
        }
208
209
        // At this point, the only allowed type is string.
210
        if (!is_string($extraVars)) {
0 ignored issues
show
introduced by
The condition is_string($extraVars) is always true.
Loading history...
211
            throw new InvalidArgumentException(sprintf('Expected string|array, got "%s"', gettype($extraVars)));
212
        }
213
214
        if (!str_contains($extraVars, '=')) {
215
            throw new InvalidArgumentException('The extra vars raw string should be in the "key=value" form.');
216
        }
217
218
        $this->addOption('--extra-vars', $extraVars);
219
        return $this;
220
    }
221
222
    /**
223
     * Run handlers even if a task fails.
224
     *
225
     * @return AnsiblePlaybookInterface
226
     */
227
    public function forceHandlers(): AnsiblePlaybookInterface
228
    {
229
        $this->addParameter('--force-handlers');
230
231
        return $this;
232
    }
233
234
    /**
235
     * Specify number of parallel processes to use (default=5).
236
     *
237
     * @param int $forks
238
     * @return AnsiblePlaybookInterface
239
     */
240
    public function forks(int $forks = 5): AnsiblePlaybookInterface
241
    {
242
        // this is ugly, but on commandline it will later be automatically string anyways
243
        $this->addOption('--forks', (string)$forks);
244
245
        return $this;
246
    }
247
248
    /**
249
     * Show help message and exit.
250
     *
251
     * @return AnsiblePlaybookInterface
252
     */
253
    public function help(): AnsiblePlaybookInterface
254
    {
255
        $this->addParameter('--help');
256
257
        return $this;
258
    }
259
260
    /**
261
     * @inheritDoc
262
     */
263
    public function inventory(array $hosts = []): AnsiblePlaybookInterface
264
    {
265
        if (empty($hosts)) {
266
            return $this;
267
        }
268
269
        // In order to let ansible-playbook understand that the given option is a list of hosts, the list must end by
270
        // comma "," if it contains just an entry. For example, supposing just a single host, "localhosts":
271
        //
272
        //   Wrong: --inventory="locahost"
273
        // Correct: --inventory="locahost,"
274
        $hostList = implode(', ', $hosts);
275
276
        if (count($hosts) === 1) {
277
            $hostList .= ',';
278
        }
279
280
        $this->addOption('--inventory', sprintf('"%s"', $hostList));
281
        $this->hasInventory = true;
282
283
        return $this;
284
    }
285
286
    /**
287
     * Specify inventory host file (default=/etc/ansible/hosts).
288
     *
289
     * @param string $inventory filename for hosts file
290
     * @return AnsiblePlaybookInterface
291
     */
292
    public function inventoryFile(string $inventory = '/etc/ansible/hosts'): AnsiblePlaybookInterface
293
    {
294
        $this->addOption('--inventory-file', $inventory);
295
        $this->hasInventory = true;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Further limit selected hosts to an additional pattern.
302
     *
303
     * @param string|array $subset list of hosts
304
     * @return AnsiblePlaybookInterface
305
     */
306
    public function limit(string|array $subset = ''): AnsiblePlaybookInterface
307
    {
308
        $subset = $this->checkParam($subset, ',');
309
        $this->addOption('--limit', $subset);
310
311
        return $this;
312
    }
313
314
    /**
315
     * Outputs a list of matching hosts; does not execute anything else.
316
     *
317
     * @return AnsiblePlaybookInterface
318
     */
319
    public function listHosts(): AnsiblePlaybookInterface
320
    {
321
        $this->addParameter('--list-hosts');
322
323
        return $this;
324
    }
325
326
    /**
327
     * List all tasks that would be executed.
328
     *
329
     * @return AnsiblePlaybookInterface
330
     */
331
    public function listTasks(): AnsiblePlaybookInterface
332
    {
333
        $this->addParameter('--list-tasks');
334
335
        return $this;
336
    }
337
338
    /**
339
     * Specify path(s) to module library (default=/usr/share/ansible/).
340
     *
341
     * @param array $path list of paths for modules
342
     * @return AnsiblePlaybookInterface
343
     */
344
    public function modulePath(array $path = ['/usr/share/ansible/']): AnsiblePlaybookInterface
345
    {
346
        $this->addOption('--module-path', implode(',', $path));
347
348
        return $this;
349
    }
350
351
    /**
352
     * Disable cowsay
353
     *
354
     * @codeCoverageIgnore
355
     * @return AnsiblePlaybookInterface
356
     */
357
    public function noCows(): AnsiblePlaybookInterface
358
    {
359
        $this->processBuilder->setEnv('ANSIBLE_NOCOWS', 1);
360
361
        return $this;
362
    }
363
364
    /**
365
     * Enable/Disable Colors
366
     *
367
     * @param bool $colors
368
     * @return AnsiblePlaybookInterface
369
     */
370
    public function colors(bool $colors = true): AnsiblePlaybookInterface
371
    {
372
        $this->processBuilder->setEnv('ANSIBLE_FORCE_COLOR', intval($colors));
373
374
        return $this;
375
    }
376
377
    /**
378
     * Enable/Disable Json Output
379
     *
380
     * @return AnsiblePlaybookInterface
381
     */
382
    public function json(): AnsiblePlaybookInterface
383
    {
384
        $this->processBuilder->setEnv('ANSIBLE_STDOUT_CALLBACK', 'json');
385
386
        return $this;
387
    }
388
389
    /**
390
     * Use this file to authenticate the connection.
391
     *
392
     * @param string $file private key file
393
     * @return AnsiblePlaybookInterface
394
     */
395
    public function privateKey(string $file): AnsiblePlaybookInterface
396
    {
397
        $this->addOption('--private-key', $file);
398
399
        return $this;
400
    }
401
402
    /**
403
     * Only run plays and tasks whose tags do not match these values.
404
     *
405
     * @param string|array $tags list of tags to skip
406
     * @return AnsiblePlaybookInterface
407
     */
408
    public function skipTags(string|array $tags = ''): AnsiblePlaybookInterface
409
    {
410
        $tags = $this->checkParam($tags, ',');
411
        $this->addOption('--skip-tags', $tags);
412
413
        return $this;
414
    }
415
416
    /**
417
     * Start the playbook at the task matching this name.
418
     *
419
     * @param string $task name of task
420
     * @return AnsiblePlaybookInterface
421
     */
422
    public function startAtTask(string $task): AnsiblePlaybookInterface
423
    {
424
        $this->addOption('--start-at-task', $task);
425
426
        return $this;
427
    }
428
429
    /**
430
     * One-step-at-a-time: confirm each task before running.
431
     *
432
     * @return AnsiblePlaybookInterface
433
     */
434
    public function step(): AnsiblePlaybookInterface
435
    {
436
        $this->addParameter('--step');
437
438
        return $this;
439
    }
440
441
    /**
442
     * Run operations with su.
443
     *
444
     * @return AnsiblePlaybookInterface
445
     */
446
    public function su(): AnsiblePlaybookInterface
447
    {
448
        $this->addParameter('--su');
449
450
        return $this;
451
    }
452
453
    /**
454
     * Run operations with su as this user (default=root).
455
     *
456
     * @param string $user
457
     * @return AnsiblePlaybookInterface
458
     */
459
    public function suUser(string $user = 'root'): AnsiblePlaybookInterface
460
    {
461
        $this->addOption('--su-user', $user);
462
463
        return $this;
464
    }
465
466
    /**
467
     * Perform a syntax check on the playbook, but do not execute it.
468
     *
469
     * @return AnsiblePlaybookInterface
470
     */
471
    public function syntaxCheck(): AnsiblePlaybookInterface
472
    {
473
        $this->addParameter('--syntax-check');
474
475
        return $this;
476
    }
477
478
    /**
479
     * Only run plays and tasks tagged with these values.
480
     *
481
     * @param string|array $tags list of tags
482
     * @return AnsiblePlaybookInterface
483
     */
484
    public function tags(string|array $tags): AnsiblePlaybookInterface
485
    {
486
        $tags = $this->checkParam($tags, ',');
487
        $this->addOption('--tags', $tags);
488
489
        return $this;
490
    }
491
492
    /**
493
     * Override the SSH timeout in seconds (default=10).
494
     *
495
     * @param int $timeout
496
     * @return AnsiblePlaybookInterface
497
     */
498
    public function timeout(int $timeout = 10): AnsiblePlaybookInterface
499
    {
500
        $this->addOption('--timeout', $timeout);
501
502
        return $this;
503
    }
504
505
    /**
506
     * Connect as this user.
507
     *
508
     * @param string $user
509
     * @return AnsiblePlaybookInterface
510
     */
511
    public function user(string $user): AnsiblePlaybookInterface
512
    {
513
        $this->addOption('--user', $user);
514
515
        return $this;
516
    }
517
518
    /**
519
     * Vault password file.
520
     *
521
     * @param string $file
522
     * @return AnsiblePlaybookInterface
523
     */
524
    public function vaultPasswordFile(string $file): AnsiblePlaybookInterface
525
    {
526
        $this->addoption('--vault-password-file', $file);
527
528
        return $this;
529
    }
530
531
    /**
532
     * Verbose mode (vvv for more, vvvv to enable connection debugging).
533
     *
534
     * @param string $verbose
535
     * @return AnsiblePlaybookInterface
536
     */
537
    public function verbose(string $verbose = 'v'): AnsiblePlaybookInterface
538
    {
539
        $this->addParameter('-' . $verbose);
540
541
        return $this;
542
    }
543
544
    /**
545
     * Show program's version number and exit.
546
     *
547
     * @return AnsiblePlaybookInterface
548
     */
549
    public function version(): AnsiblePlaybookInterface
550
    {
551
        $this->addParameter('--version');
552
553
        return $this;
554
    }
555
556
    /**
557
     * clear the fact cache
558
     *
559
     * @return AnsiblePlaybookInterface
560
     */
561
    public function flushCache(): AnsiblePlaybookInterface
562
    {
563
        $this->addParameter('--flush-cache');
564
565
        return $this;
566
    }
567
568
    /**
569
     * the new vault identity to use for rekey
570
     *
571
     * @param string $vaultId
572
     * @return AnsiblePlaybookInterface
573
     */
574
    public function newVaultId(string $vaultId): AnsiblePlaybookInterface
575
    {
576
        $this->addOption('--new-vault-id', $vaultId);
577
578
        return $this;
579
    }
580
581
    /**
582
     * new vault password file for rekey
583
     *
584
     * @param string $passwordFile
585
     * @return AnsiblePlaybookInterface
586
     */
587
    public function newVaultPasswordFile(string $passwordFile): AnsiblePlaybookInterface
588
    {
589
        $this->addOption('--new-vault-password-file', $passwordFile);
590
591
        return $this;
592
    }
593
594
    /**
595
     * specify extra arguments to pass to scp only (e.g. -l)
596
     *
597
     * @param string|array $scpExtraArgs
598
     * @return AnsiblePlaybookInterface
599
     */
600
    public function scpExtraArgs(string|array $scpExtraArgs): AnsiblePlaybookInterface
601
    {
602
        $scpExtraArgs = $this->checkParam($scpExtraArgs, ',');
603
        $this->addOption('--scp-extra-args', $scpExtraArgs);
604
605
        return $this;
606
    }
607
608
    /**
609
     * specify extra arguments to pass to sftp only (e.g. -f, -l)
610
     *
611
     * @param string|array $sftpExtraArgs
612
     * @return AnsiblePlaybookInterface
613
     */
614
    public function sftpExtraArgs(string|array $sftpExtraArgs): AnsiblePlaybookInterface
615
    {
616
        $sftpExtraArgs = $this->checkParam($sftpExtraArgs, ',');
617
        $this->addOption('--sftp-extra-args', $sftpExtraArgs);
618
619
        return $this;
620
    }
621
622
    /**
623
     * specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand)
624
     *
625
     * @param string|array $sshArgs
626
     * @return AnsiblePlaybookInterface
627
     */
628
    public function sshCommonArgs(string|array $sshArgs): AnsiblePlaybookInterface
629
    {
630
        $sshArgs = $this->checkParam($sshArgs, ',');
631
        $this->addOption('--ssh-common-args', $sshArgs);
632
633
        return $this;
634
    }
635
636
    /**
637
     * specify extra arguments to pass to ssh only (e.g. -R)
638
     *
639
     * @param string|array $extraArgs
640
     * @return AnsiblePlaybookInterface
641
     */
642
    public function sshExtraArgs(string|array $extraArgs): AnsiblePlaybookInterface
643
    {
644
        $extraArgs = $this->checkParam($extraArgs, ',');
645
        $this->addOption('--ssh-extra-args', $extraArgs);
646
647
        return $this;
648
    }
649
650
    /**
651
     * the vault identity to use
652
     *
653
     * @param string $vaultId
654
     * @return AnsiblePlaybookInterface
655
     */
656
    public function vaultId(string $vaultId): AnsiblePlaybookInterface
657
    {
658
        $this->addOption('--vault-id', $vaultId);
659
660
        return $this;
661
    }
662
663
    /**
664
     * Get parameter string which will be used to call ansible.
665
     *
666
     * @param bool $asArray
667
     * @return string|array
668
     */
669
    public function getCommandlineArguments(bool $asArray = true): string|array
670
    {
671
        $this->checkInventory();
672
673
        return $this->prepareArguments($asArray);
674
    }
675
676
    /**
677
     * @inheritDoc
678
     */
679
    public function rolesPath(string $path): AnsiblePlaybookInterface
680
    {
681
        if (empty($path)) {
682
            return $this;
683
        }
684
685
        if (!file_exists($path)) {
686
            throw new InvalidArgumentException(sprintf('The path "%s" does not exist.', $path));
687
        }
688
689
        $this->processBuilder->setEnv('ANSIBLE_ROLES_PATH', $path);
690
        return $this;
691
    }
692
693
    /**
694
     * @inheritDoc
695
     */
696
    public function hostKeyChecking(bool $enable = true): AnsiblePlaybookInterface
697
    {
698
        $enable ? $flag = 'True' : $flag = 'False';
699
700
        $this->processBuilder->setEnv('ANSIBLE_HOST_KEY_CHECKING', $flag);
701
        return $this;
702
    }
703
704
    /**
705
    * Ansible SSH pipelining option
706
    * https://docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-pipelining
707
    *
708
    * @param bool $enable
709
    * @return AnsiblePlaybookInterface
710
    **/
711
    public function sshPipelining(bool $enable = false): AnsiblePlaybookInterface
712
    {
713
        $enable ?
714
            $flag = 'True' :
715
            $flag = 'False';
716
717
        $this->processBuilder->setEnv('ANSIBLE_SSH_PIPELINING', $flag);
718
        return $this;
719
    }
720
721
    /**
722
     * If no inventory file is given, assume
723
     */
724
    private function checkInventory(): void
725
    {
726
        if (!$this->hasInventory) {
727
            $inventory = str_replace('.yml', '', $this->getBaseOptions());
728
            $this->inventoryFile($inventory);
729
        }
730
    }
731
}
732