Completed
Push — master ( d3a073...5737c8 )
by Greg
02:21
created

src/Task/Remote/Ssh.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Robo\Task\Remote;
4
5
use Robo\Contract\CommandInterface;
6
use Robo\Exception\TaskException;
7
use Robo\Task\BaseTask;
8
use Robo\Contract\SimulatedInterface;
9
10
/**
11
 * Runs multiple commands on a remote server.
12
 * Per default, commands are combined with &&, unless stopOnFail is false.
13
 *
14
 * ```php
15
 * <?php
16
 *
17
 * $this->taskSshExec('remote.example.com', 'user')
18
 *     ->remoteDir('/var/www/html')
19
 *     ->exec('ls -la')
20
 *     ->exec('chmod g+x logs')
21
 *     ->run();
22
 *
23
 * ```
24
 *
25
 * You can even exec other tasks (which implement CommandInterface):
26
 *
27
 * ```php
28
 * $gitTask = $this->taskGitStack()
29
 *     ->checkout('master')
30
 *     ->pull();
31
 *
32
 * $this->taskSshExec('remote.example.com')
33
 *     ->remoteDir('/var/www/html/site')
34
 *     ->exec($gitTask)
35
 *     ->run();
36
 * ```
37
 *
38
 * You can configure the remote directory for all future calls:
39
 *
40
 * ```php
41
 * \Robo\Task\Remote\Ssh::configure('remoteDir', '/some-dir');
42
 * ```
43
 */
44
class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
45
{
46
    use \Robo\Common\CommandReceiver;
47
    use \Robo\Common\ExecOneCommand;
48
49
    /**
50
     * @var null|string
51
     */
52
    protected $hostname;
53
54
    /**
55
     * @var null|string
56
     */
57
    protected $user;
58
59
    /**
60
     * @var bool
61
     */
62
    protected $stopOnFail = true;
63
64
    /**
65
     * @var array
66
     */
67
    protected $exec = [];
68
69
    /**
70
     * Changes to the given directory before running commands.
71
     *
72
     * @var string
73
     */
74
    protected $remoteDir;
75
76
    /**
77
     * @param null|string $hostname
78
     * @param null|string $user
79
     */
80
    public function __construct($hostname = null, $user = null)
81
    {
82
        $this->hostname = $hostname;
83
        $this->user = $user;
84
    }
85
86
    /**
87
     * @param string $hostname
88
     *
89
     * @return $this
90
     */
91
    public function hostname($hostname)
92
    {
93
        $this->hostname = $hostname;
94
        return $this;
95
    }
96
97
    /**
98
     * @param string $user
99
     *
100
     * @return $this
101
     */
102
    public function user($user)
103
    {
104
        $this->user = $user;
105
        return $this;
106
    }
107
108
    /**
109
     * Whether or not to chain commands together with && and stop the chain if one command fails.
110
     *
111
     * @param bool $stopOnFail
112
     *
113
     * @return $this
114
     */
115
    public function stopOnFail($stopOnFail = true)
116
    {
117
        $this->stopOnFail = $stopOnFail;
118
        return $this;
119
    }
120
121
    /**
122
     * Changes to the given directory before running commands.
123
     *
124
     * @param string $remoteDir
125
     *
126
     * @return $this
127
     */
128
    public function remoteDir($remoteDir)
129
    {
130
        $this->remoteDir = $remoteDir;
131
        return $this;
132
    }
133
134
    /**
135
     * @param string $filename
136
     *
137
     * @return $this
138
     */
139
    public function identityFile($filename)
140
    {
141
        $this->option('-i', $filename);
142
143
        return $this;
144
    }
145
146
    /**
147
     * @param int $port
148
     *
149
     * @return $this
150
     */
151
    public function port($port)
152
    {
153
        $this->option('-p', $port);
154
155
        return $this;
156
    }
157
158
    /**
159
     * @return $this
160
     */
161
    public function forcePseudoTty()
162
    {
163
        $this->option('-t');
164
165
        return $this;
166
    }
167
168
    /**
169
     * @return $this
170
     */
171
    public function quiet()
172
    {
173
        $this->option('-q');
174
175
        return $this;
176
    }
177
178
    /**
179
     * @return $this
180
     */
181
    public function verbose()
182
    {
183
        $this->option('-v');
184
185
        return $this;
186
    }
187
188
    /**
189
     * @param string|string[]|CommandInterface $command
190
     *
191
     * @return $this
192
     */
193
    public function exec($command)
194
    {
195
        if (is_array($command)) {
196
            $command = implode(' ', array_filter($command));
197
        }
198
199
        $this->exec[] = $command;
200
201
        return $this;
202
    }
203
204
    /**
205
     * Returns command that can be executed.
206
     * This method is used to pass generated command from one task to another.
207
     *
208
     * @return string
209
     */
210
    public function getCommand()
211
    {
212
        $commands = [];
213
        foreach ($this->exec as $command) {
214
            $commands[] = $this->receiveCommand($command);
215
        }
216
217
        $remoteDir = $this->remoteDir ? $this->remoteDir : $this->getConfigValue('remoteDir');
218
        if (!empty($remoteDir)) {
219
            array_unshift($commands, sprintf('cd "%s"', $remoteDir));
220
        }
221
        $command = implode($this->stopOnFail ? ' && ' : ' ; ', $commands);
222
223
        return $this->sshCommand($command);
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function run()
230
    {
231
        $this->validateParameters();
232
        $command = $this->getCommand();
233
        return $this->executeCommand($command);
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function simulate($context)
240
    {
241
        $command = $this->getCommand();
242
        $this->printTaskInfo("Running {command}", ['command' => $command] + $context);
243
    }
244
245
    protected function validateParameters()
246
    {
247
        if (empty($this->hostname)) {
248
            throw new TaskException($this, 'Please set a hostname');
249
        }
250
        if (empty($this->exec)) {
251
            throw new TaskException($this, 'Please add at least one command');
252
        }
253
    }
254
255
    /**
256
     * Returns an ssh command string running $command on the remote.
257
     *
258
     * @param string|CommandInterface $command
259
     *
260
     * @return string
261
     */
262
    protected function sshCommand($command)
263
    {
264
        $command = $this->receiveCommand($command);
265
        $sshOptions = $this->arguments;
266
        $hostSpec = $this->hostname;
267
        if ($this->user) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->user of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
268
            $hostSpec = $this->user . '@' . $hostSpec;
269
        }
270
271
        return "ssh{$sshOptions} {$hostSpec} '{$command}'";
272
    }
273
}
274