GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ExecuteCommand::configure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 18
rs 9.4285
cc 1
eloc 13
nc 1
nop 0
1
<?php
2
namespace Kunstmaan\Skylab\Command;
3
4
use CL\Slack\Model\Attachment;
5
use CL\Slack\Model\AttachmentField;
6
use CL\Slack\Payload\ChatDeletePayload;
7
use CL\Slack\Payload\ChatPostMessagePayload;
8
use CL\Slack\Payload\ChatPostMessagePayloadResponse;
9
use CL\Slack\Transport\ApiClient;
10
use Symfony\Component\Console\Input\InputArgument;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Process\Process;
14
use Symfony\Component\Yaml\Exception\ParseException;
15
use Symfony\Component\Yaml\Parser;
16
17
/**
18
 * ExecuteCommand
19
 */
20
class ExecuteCommand extends AbstractCommand
21
{
22
23
    private $ts = null;
24
    private $channel = null;
25
    /** @var ApiClient */
26
    private $slackApiClient = null;
27
28
    /**
29
     * Configures the current command.
30
     */
31
    protected function configure()
32
    {
33
        $this
34
            ->addDefaults()
35
            ->setName('execute')
36
            ->setDescription('Executes a Skylab YAML file')
37
            ->addArgument('file', InputArgument::REQUIRED, 'The full path to the YAML file')
38
            ->addArgument('deploy-environment', InputArgument::OPTIONAL, 'The environment to deploy to')
39
            ->addOption("--skip-tests", null, InputOption::VALUE_NONE, 'If set, the test steps will be skipped')
40
            ->addOption("--skip-deploy", null, InputOption::VALUE_NONE, 'If set, the deploy steps will be skipped')
41
            ->addOption("--debug-yml", null, InputOption::VALUE_NONE, 'If set, the resulting yml will be shown without executing it')
42
            ->setHelp(<<<EOT
43
The <info>execute</info> command will execute a Skylab YAML file, used for testing and deploying via Jenkins
44
45
<info>php skylab.phar execute /opt/skylab/templates/execute/deploy.yml</info>
46
EOT
47
            );
48
    }
49
50
    protected function runStep($step, $yaml, $deployEnv, $successStep = null)
51
    {
52
        if (isset($yaml[$step])) {
53
            $this->dialogProvider->logStep($step);
54
55
            foreach ($yaml[$step] as $list) {
56
                foreach ($list as $source) {
57
                    foreach ($source as $command) {
58
                        try {
59
                            $result = $this->processProvider->executeCommand($command, false, function ($type, $buffer) {
60
                                if (Process::ERR === $type) {
61
                                    $this->dialogProvider->logOutput($buffer, true);
62
                                } else {
63
                                    if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
64
                                        $this->dialogProvider->logOutput($buffer, false);
65
                                    }
66
                                }
67
                            }, $yaml["env"]);
68
                            if ($result === false) {
69
                                $this->notifySlack("Error while running the " . $step . " phase, check the console log in Jenkins", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), array(), "#CC0000", true);
70
                                $this->dialogProvider->logError($step . " failed!", false);
71
                            }
72
                        } catch (\Exception $ex) {
73
                            $this->notifySlack("Error while running the " . $step . " phase with error: " . $ex->getMessage(), $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), array(), "#CC0000", true);
74
                            $extra = array();
75
                            $tags = array();
76
                            $this->dialogProvider->logException($ex, $tags, $extra);
77
                        }
78
                    }
79
                }
80
            }
81
            if (!is_null($successStep)) {
82
                $this->runStep($successStep, $yaml, $deployEnv);
83
            }
84
        } else {
85
            return;
86
        }
87
    }
88
89
    protected function notifySlack($message, $project, $env, $user, $resolverArray, $color = "#FFCC00", $update = false)
90
    {
91
        if ($update) {
92
            $payload = new ChatDeletePayload();
93
            $payload->setSlackTimestamp($this->ts);
94
            $payload->setChannelId($this->channel);
95
            $response = $this->slackApiClient->send($payload);
96
97 View Code Duplication
            if ($response->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98
                if ($response instanceof ChatPostMessagePayloadResponse) {
99
                    /** @var ChatPostMessagePayloadResponse $response */
100
                    $this->ts = $response->getSlackTimestamp();
101
                }
102
            } else {
103
                // something went wrong, but what?
104
                // simple error (Slack's error message)
105
                echo $response->getError();
106
                // explained error (Slack's explanation of the error, according to the documentation)
107
                echo $response->getErrorExplanation();
108
                exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method notifySlack() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
109
            }
110
        }
111
112
        $payload = new ChatPostMessagePayload();
113
        $payload->setChannel("#" . getenv("slack_channel"));
114
        $payload->setIconUrl("https://www.dropbox.com/s/ivrj3wcze7cwh54/masterjenkins.png?dl=1");
115
        $payload->setUsername("Master Jenkins");
116
117
        $attachment = new Attachment();
118
        $attachment->setColor($color);
119
        $attachment->setFallback("[".$project."#" . getenv("BUILD_NUMBER"). " - branch *" . getenv("GIT_BRANCH") . "* to *". $env . "* by _" . $user . "_] " . $message);
120
        $attachment->setText("[".$project."#" . getenv("BUILD_NUMBER"). " - branch *" . getenv("GIT_BRANCH") . "* to *". $env . "* by _" . $user ."_] " . $message . "\n<" . getenv("BUILD_URL") . "console|Jenkins Console> - <".getenv("BUILD_URL")."changes|Changes>" . (isset($resolverArray["shared_package_target"]) && file_exists($resolverArray["shared_package_target"])?" - <" . $resolverArray["shared_package_url"] . "|Download>":""));
121
        $payload->addAttachment($attachment);
122
123
        $response = $this->slackApiClient->send($payload);
124
125 View Code Duplication
        if ($response->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
            if ($response instanceof ChatPostMessagePayloadResponse) {
127
                /** @var ChatPostMessagePayloadResponse $response */
128
                $this->ts = $response->getSlackTimestamp();
129
                $this->channel = $response->getChannelId();
130
            }
131
        } else {
132
            // something went wrong, but what?
133
            // simple error (Slack's error message)
134
            echo $response->getError();
135
            // explained error (Slack's explanation of the error, according to the documentation)
136
            echo $response->getErrorExplanation();
137
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method notifySlack() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
138
        }
139
    }
140
141
    protected function doExecute()
142
    {
143
        if (isset($this->app["config"]["slack_api_key"])) {
144
            $this->slackApiClient = new ApiClient($this->app["config"]["slack_api_key"]);
145
        } else {
146
            $this->slackApiClient = new ApiClient("fake key");
147
        }
148
149
        $deployEnv = $this->input->getArgument('deploy-environment');
150
        list($yaml, $resolverArray) = $this->parseYaml();
151
152
        if ($this->input->getOption('debug-yml')) {
153
            print_r($yaml);
154
            print_r($resolverArray);
155
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method doExecute() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
156
        }
157
158
        if (is_null($deployEnv)) {
159
            $this->dialogProvider->logError("You cannot run a deploy step without an environment", true);
160
        }
161
        if (isset($yaml["deploy_matrix"][$deployEnv])) {
162
163
            //build
164
            $this->notifySlack("Build started", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray);
165
            $this->runStep("before_build", $yaml, $deployEnv);
166
            $this->runStep("build", $yaml, $deployEnv, "after_build_success");
167
            $this->notifySlack("Build successful", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray, "#FFCC00", true);
168
169
            // test
170 View Code Duplication
            if (!$this->input->getOption('skip-tests')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
                $this->notifySlack("Tests started", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray, "#FFCC00", true);
172
                $this->runStep("before_test", $yaml, $deployEnv);
173
                $this->runStep("test", $yaml, $deployEnv, "after_test_success");
174
                $this->notifySlack("Tests successful", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray, ($this->input->getOption('skip-deploy')?"#7CD197":"#FFCC00"), true);
175
            }
176
177
            // deploy
178 View Code Duplication
            if (!$this->input->getOption('skip-deploy')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
                $this->notifySlack("Deploy started", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray, "#FFCC00", true);
180
                $this->runStep("before_deploy", $yaml, $deployEnv);
181
                $this->runStep("deploy", $yaml, $deployEnv, "after_deploy_success");
182
                $this->notifySlack("Deploy successful", $yaml["deploy_matrix"][$deployEnv]["project"], $deployEnv, getenv("slack_user"), $resolverArray, "#7CD197", true);
183
            } else {
184
                $this->dialogProvider->logNotice("Deploy is skipped");
185
            }
186
        } else {
187
            $this->dialogProvider->logError("The deploy environment " . $deployEnv . " does not exist", true);
188
        }
189
    }
190
191
    /**
192
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
193
     * @throws \Exception
194
     */
195
    protected function parseYaml()
196
    {
197
        try {
198
            $mergedYaml = $this->buildMergedYaml();
199
            $resolverArray = $this->buildResolverArray($mergedYaml);
200
            $resolvedYaml = $this->resolveYaml($mergedYaml, $resolverArray);
201
            $mergedYaml["env"]["SHELL"] = "/bin/bash";
202
            return array($resolvedYaml, $resolverArray);
203
        } catch (\Exception $ex){
204
            $deployEnv = $this->input->getArgument('deploy-environment');
205
            $this->notifySlack("Error while parding the YAML files, reason: " . $ex->getMessage(), "unknown", $deployEnv, getenv("slack_user"), array(), "#CC0000", true);
206
            throw $ex;
207
        }
208
    }
209
210
    /**
211
     * @param $mergedYaml
212
     * @return array
213
     */
214
    protected function buildResolverArray($mergedYaml)
215
    {
216
        $resolverArray = array_merge($this->app["config"], $mergedYaml["env"]);
217
        $resolverArray["base_dir"] = BASE_DIR;
218
        $deployEnv = $this->input->getArgument('deploy-environment');
219
        if (!empty($deployEnv) && isset($mergedYaml["deploy_matrix"][$deployEnv])) {
220
            $resolverArray = $this->collectDeploySettings($mergedYaml["deploy_matrix"][$deployEnv], "deploy", $resolverArray);
221
        }
222
        if (isset($mergedYaml["database_source"])) {
223
            $dbSource = $mergedYaml["database_source"];
224
            if (isset($mergedYaml["deploy_matrix"][$dbSource])) {
225
                $resolverArray = $this->collectDeploySettings($mergedYaml["deploy_matrix"][$dbSource], "dbsource", $resolverArray);
226
            }
227
            $resolverArray["fetch_mysql"] = "yes";
228
        } else {
229
            $resolverArray["fetch_mysql"] = "no";
230
        }
231
        $parametersFile = dirname(dirname($this->input->getArgument('file'))) . "/app/config/parameters.yml";
232
        if (file_exists($parametersFile) && strpos(file_get_contents($parametersFile), 'database_host') !== false) {
233
            $parametersYaml = $this->loadYaml($parametersFile);
234
            $resolverArray = array_merge($parametersYaml["parameters"], $resolverArray);
235
            $resolverArray["run_mysql"] = (getenv('NO_MYSQL')?"no":"yes");
236
        } else {
237
            $resolverArray["run_mysql"] = "no";
238
        }
239
        $resolverArray["webserver_engine"] = $this->app["config"]["webserver"]["engine"];
240
        $resolverArray["mysql_root_password"] = $this->app["config"]["mysql"]["password"];
241
        $resolverArray["buildtag"] = $deployEnv . "-" . $this->getRevision();
242
        $resolverArray["home"] = getenv("HOME");
243
        $resolverArray["job_name"] = getenv("JOB_NAME");
244
        $resolverArray["build_package_target"] = $resolverArray["home"] . "/builds/".$resolverArray["job_name"]."-".$resolverArray["buildtag"].".tar.gz";
245
        $resolverArray["shared_package_folder"] = "/home/projects/build/data/shared/web/uploads/";
246
        $resolverArray["shared_package_target"] = "/home/projects/build/data/shared/web/uploads/".$resolverArray["job_name"]."-".$resolverArray["deploy_timestamp"] . "-" . $resolverArray["buildtag"].".tar.gz";
247
        $resolverArray["shared_package_url"] = "http://build.kunstmaan.be/uploads/".$resolverArray["job_name"]."-".$resolverArray["deploy_timestamp"] . "-" . $resolverArray["buildtag"].".tar.gz";
248
        return $resolverArray;
249
    }
250
251
    /**
252
     * @param $mergedYaml
253
     * @param $resolverArray
254
     * @return mixed
255
     */
256
    protected function resolveYaml($mergedYaml, $resolverArray)
257
    {
258
        array_walk_recursive($mergedYaml, function (&$item, $key, $resolver) {
259
            $item = preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($key, $resolver) {
260
261
                // skip %%
262
                if (!isset($match[1])) {
263
                    return '%%';
264
                }
265
266
                $key = $match[1];
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $key, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
267
268
                if (isset($resolver[$key])) {
269
                    return $resolver[$key];
270
                }
271
272
                return $match[0];
273
            }, $item);
274
        }, $resolverArray);
275
276
        return $mergedYaml;
277
    }
278
279
    /**
280
     * @return array
281
     */
282
    protected function buildMergedYaml()
283
    {
284
        $ymlPath = $this->input->getArgument('file');
285
        $parsedYaml = $this->loadYaml($ymlPath);
286
        $mergedYaml = $this->handleTemplateYaml($parsedYaml);
287
        $mergedYaml = $this->handleResources($mergedYaml);
288
        return $mergedYaml;
289
    }
290
291
    protected function handleResources($mergedYaml)
292
    {
293
294
        array_walk_recursive($mergedYaml, function (&$item, $key, &$mergedYaml) {
0 ignored issues
show
Unused Code introduced by
The parameter $mergedYaml is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
295
            if ($key === "resource") {
296
                $resourceYaml = $this->loadYaml(BASE_DIR . "/templates/execute/" . $item);
297
                $item = $resourceYaml["steps"];
298
            }
299
        }, $mergedYaml);
300
301
        return $mergedYaml;
302
    }
303
304
    /**
305
     * @param $ymlPath
306
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|\stdClass|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
307
     */
308
    protected function loadYaml($ymlPath)
309
    {
310
        $yaml = new Parser();
311
        try {
312
            $parsedYaml = $yaml->parse(file_get_contents($ymlPath));
313
            return $parsedYaml;
314
        } catch (ParseException $e) {
315
            $this->dialogProvider->logError(sprintf("Unable to parse the YAML string: %s", $e->getMessage()), false);
316
        }
317
    }
318
319
    protected function handleTemplateYaml($parsedYaml)
320
    {
321
        if (isset($parsedYaml["template"])) {
322
            $templateYaml = $this->loadYaml(BASE_DIR . "/templates/execute/" . $parsedYaml["template"] . ".yml");
323
            $mergedYaml = array_merge($templateYaml, $parsedYaml);
324
        } else {
325
            $mergedYaml = $parsedYaml;
326
        }
327
        $mergedYaml["env"] = array_merge((isset($templateYaml["env"]) ? $templateYaml["env"] : array()), (isset($parsedYaml["env"]) ? $parsedYaml["env"] : array()));
328
        return $mergedYaml;
329
    }
330
331
    protected function collectDeploySettings($deploySettings, $prefix, $resolverArray)
332
    {
333
        $resolverArray[$prefix . "_server"] = $deploySettings["server"];
334 View Code Duplication
        if (isset($deploySettings["port"])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
            $resolverArray[$prefix . "_port"] = $deploySettings["port"];
336
        } else {
337
            $resolverArray[$prefix . "_port"] = 22;
338
        }
339
        $resolverArray[$prefix . "_project"] = $deploySettings["project"];
340 View Code Duplication
        if (isset($deploySettings["app_path"])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
341
            $resolverArray[$prefix . "_app_path"] = $deploySettings["app_path"];
342
        } else {
343
            $resolverArray[$prefix . "_app_path"] = "/ROOT";
344
        }
345 View Code Duplication
        if (isset($deploySettings["symfony_env"])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
            $resolverArray[$prefix . "_symfony_env"] = $deploySettings["symfony_env"];
347
        } else {
348
            $resolverArray[$prefix . "_symfony_env"] = "prod";
349
        }
350
        $resolverArray[$prefix . "_timestamp"] = time();
351
        return $resolverArray;
352
    }
353
354
    /**
355
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
356
     */
357
    protected function getRevision()
358
    {
359
        return $this->processProvider->executeCommand('git log --pretty=format:"%h" -1');
360
    }
361
362
    /**
363
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
364
     */
365
    protected function getBranch()
366
    {
367
        return $this->processProvider->executeCommand('git rev-parse --abbrev-ref HEAD');
368
    }
369
}
370