PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/php/class-terminus-command.php

https://gitlab.com/Blueprint-Marketing/cli
PHP | 418 lines | 293 code | 35 blank | 90 comment | 34 complexity | a6f2c59d2b08d14bf02321bd6146c3da MD5 | raw file
  1. <?php
  2. use \Terminus\Auth;
  3. use \Terminus\Endpoint;
  4. use \Terminus\Request;
  5. use \Terminus\Session;
  6. use \Terminus\Loggers\Regular as Logger;
  7. /**
  8. * The base class for Terminus commands
  9. */
  10. abstract class TerminusCommand {
  11. public $cache;
  12. public $session;
  13. public $sites;
  14. protected static $blacklist = array('password');
  15. protected $func;
  16. /**
  17. * Instantiates object, sets cache and session
  18. *
  19. * @return [TerminusCommand] $this
  20. */
  21. public function __construct() {
  22. //Load commonly used data from cache
  23. $this->cache = Terminus::get_cache();
  24. $this->session = Session::instance();
  25. }
  26. /**
  27. * Downloads the given URL to the given target
  28. *
  29. * @param [string] $url Location of file to download
  30. * @param [string] $target Location to download file to
  31. * @return [void]
  32. */
  33. public static function download($url, $target) {
  34. try {
  35. $response = Request::download($url, $target);
  36. return $target;
  37. } catch (Exception $e) {
  38. Terminus::error($e->getMessage());
  39. }
  40. }
  41. /**
  42. * Make a request to the Pantheon API
  43. *
  44. * @param [string] $realm Permissions realm for data request (e.g. user,
  45. * site organization, etc. Can also be "public" to simply pull read-only
  46. * data that is not privileged.
  47. * @param [string] $uuid The UUID of the item in the realm to access
  48. * @param [string] $path API path (URL)
  49. * @param [string] $method HTTP method to use
  50. * @param [mixed] $options A native PHP data structure (e.g. int, string,
  51. * array, or stdClass) to be sent along with the request
  52. * @return [array] $data
  53. */
  54. public static function request(
  55. $realm,
  56. $uuid,
  57. $path = false,
  58. $method = 'GET',
  59. $options = null
  60. ) {
  61. if (!in_array($realm, array('login', 'user', 'public'))) {
  62. Auth::loggedIn();
  63. }
  64. try {
  65. $cache = Terminus::get_cache();
  66. if (!in_array($realm, array('login', 'user'))) {
  67. $options['cookies'] = array(
  68. 'X-Pantheon-Session' => Session::getValue('session')
  69. );
  70. $options['verify'] = false;
  71. }
  72. $url = Endpoint::get(
  73. array(
  74. 'realm' => $realm,
  75. 'uuid' => $uuid,
  76. 'path' => $path,
  77. )
  78. );
  79. if (Terminus::get_config('debug')) {
  80. Logger::debug('Request URL: ' . $url);
  81. }
  82. $resp = Request::send($url, $method, $options);
  83. $json = $resp->getBody(true);
  84. $data = array(
  85. 'info' => $resp->getInfo(),
  86. 'headers' => $resp->getRawHeaders(),
  87. 'json' => $json,
  88. 'data' => json_decode($json),
  89. 'status_code' => $resp->getStatusCode()
  90. );
  91. return $data;
  92. } catch (Guzzle\Http\Exception\BadResponseException $e) {
  93. $response = $e->getResponse();
  94. \Terminus::error("%s", $response->getBody(true));
  95. } catch (Guzzle\Http\Exception\HttpException $e) {
  96. $request = $e->getRequest();
  97. $sanitized_request = TerminusCommand::stripSensitiveData(
  98. (string)$request,
  99. TerminusCommand::$blacklist
  100. );
  101. \Terminus::error(
  102. 'Request %s had failed: %s',
  103. array($sanitized_request, $e->getMessage())
  104. );
  105. } catch (Exception $e) {
  106. \Terminus::error('Unrecognised request failure: %s', $e->getMessage());
  107. }
  108. }
  109. /**
  110. * Make a request to the Dashbord's internal API
  111. *
  112. * @param [string] $path API path (URL)
  113. * @param [array] $options Options for the request
  114. * [string] method GET is default
  115. * [mixed] data Native PHP data structure (e.g. int, string array, or
  116. * simple object) to be sent along with the request. Will be encoded as
  117. * JSON for you.
  118. * @return [array] $return
  119. */
  120. public static function paged_request($path, $options = array()) {
  121. $limit = 100;
  122. if (isset($options['limit'])) {
  123. $limit = $options['limit'];
  124. }
  125. //$results is an associative array so we don't refetch
  126. $results = array();
  127. $finished = false;
  128. $start = null;
  129. while (!$finished) {
  130. $paged_path = $path . '?limit=' . $limit;
  131. if ($start) {
  132. $paged_path .= '&start=' . $start;
  133. }
  134. $resp = self::simple_request($paged_path);
  135. $data = $resp['data'];
  136. if (count($data) > 0) {
  137. $start = end($data)->id;
  138. //If the last item of the results has previously been received,
  139. //that means there are no more pages to fetch
  140. if (isset($results[$start])) {
  141. $finished = true;
  142. continue;
  143. }
  144. foreach ($data as $item) {
  145. $results[$item->id] = $item;
  146. }
  147. } else {
  148. $finished = true;
  149. }
  150. }
  151. $return = array('data' => array_values($results));
  152. return $return;
  153. }
  154. /**
  155. * Simplified request method for Pantheon API
  156. *
  157. * @param [string] $path API path (URL)
  158. * @param [array] $options Options for the request
  159. * [string] method GET is default
  160. * [mixed] data Native PHP data structure (e.g. int, string array, or
  161. * simple object) to be sent along with the request. Will be encoded as
  162. * JSON for you.
  163. * @return [array] $data
  164. */
  165. public static function simple_request($path, $options = array()) {
  166. $req_options = array();
  167. $method = 'get';
  168. if (isset($options['method'])) {
  169. $method = $options['method'];
  170. }
  171. if (isset($options['data'])) {
  172. $req_options['body'] = json_encode($options['data']);
  173. $req_options['headers'] = array('Content-type' => 'application/json');
  174. }
  175. $url = 'https://' . TERMINUS_HOST . '/api/' . $path;
  176. if (Session::getValue('session')) {
  177. $req_options['cookies'] = array(
  178. 'X-Pantheon-Session' => Session::getValue('session')
  179. );
  180. $req_options['verify'] = false;
  181. }
  182. try {
  183. $resp = Request::send($url, $method, $req_options);
  184. } catch (Guzzle\Http\Exception\BadResponseException $e) {
  185. \Terminus::error('Request Failure: %s', $e->getMessage());
  186. return;
  187. }
  188. $json = $resp->getBody(true);
  189. $data = array(
  190. 'info' => $resp->getInfo(),
  191. 'headers' => $resp->getRawHeaders(),
  192. 'json' => $json,
  193. 'data' => json_decode($json),
  194. 'status_code' => $resp->getStatusCode()
  195. );
  196. return $data;
  197. }
  198. /**
  199. * Constructs table for data going to STDOUT
  200. * TODO: Complexity too high. Refactor.
  201. *
  202. * @param [mixed] $data Object or hash of data for output
  203. * @param [array] $headers Array of strings for table headers
  204. * @return [void]
  205. */
  206. protected function constructTableForResponse($data, $headers = array()) {
  207. $table = new \cli\Table();
  208. if (is_object($data)) {
  209. $data = (array)$data;
  210. }
  211. if (\Terminus\Utils\result_is_multiobj($data)) {
  212. if (!empty($headers)) {
  213. $table->setHeaders($headers);
  214. } elseif (
  215. property_exists($this, '_headers')
  216. && !empty($this->_headers[$this->func])
  217. ) {
  218. if (is_array($this->_headers[$this->func])) {
  219. $table->setHeaders($this->_headers[$this->func]);
  220. }
  221. } else {
  222. $table->setHeaders(\Terminus\Utils\result_get_response_fields($data));
  223. }
  224. foreach ($data as $row => $row_data) {
  225. $row = array();
  226. foreach ($row_data as $key => $value) {
  227. if (is_array($value) || is_object($value)) {
  228. $value = join(', ', (array)$value);
  229. }
  230. $row[] = $value;
  231. }
  232. $table->addRow($row);
  233. }
  234. } else {
  235. if (!empty($headers)) {
  236. $table->setHeaders($headers);
  237. }
  238. foreach ($data as $key => $value) {
  239. if (is_array($value) || is_object($value)) {
  240. $value = implode(', ', (array)$value);
  241. }
  242. $table->addRow(array($key, $value));
  243. }
  244. }
  245. $table->display();
  246. }
  247. /**
  248. * Decides and handles how to display data going to STDOUT
  249. *
  250. * @param [string] $data Data for display
  251. * @param [array] $args Instructions on how to display data
  252. * @param [array] $headers Table headers
  253. * @return [void]
  254. */
  255. protected function handleDisplay($data, $args = array(), $headers = null) {
  256. if (array_key_exists('json', $args) || Terminus::get_config('json')) {
  257. echo \Terminus\Utils\json_dump($data);
  258. } elseif (array_key_exists('bash', $args) || Terminus::get_config('bash')) {
  259. echo \Terminus\Utils\bash_out((array)$data);
  260. } else {
  261. $this->constructTableForResponse((array)$data, $headers);
  262. }
  263. }
  264. /**
  265. * Strips sensitive data out of the JSON printed in a request string
  266. *
  267. * @param [string] $request The string with a JSON with sensitive data
  268. * @param [array] $blacklist Array of string keys to remove from request
  269. * @return [string] $result Sensitive data-stripped version of $request
  270. */
  271. protected function stripSensitiveData($request, $blacklist = array()) {
  272. //Locate the JSON in the string, turn to array
  273. $regex = '~\{(.*)\}~';
  274. preg_match($regex, $request, $matches);
  275. $request_array = json_decode($matches[0], true);
  276. //See if a blacklisted items are in the arrayed JSON, replace
  277. foreach ($blacklist as $blacklisted_item) {
  278. if (isset($request_array[$blacklisted_item])) {
  279. $request_array[$blacklisted_item] = '*****';
  280. }
  281. }
  282. //Turn array back to JSON, put back in string
  283. $result = str_replace($matches[0], json_encode($request_array), $request);
  284. return $result;
  285. }
  286. /**
  287. * Waits and returns response from workflow
  288. *
  289. * @param [string] $object_name Sites/users/organizations/etc
  290. * @param [string] $object_id UUID of $object_name
  291. * @param [string] $workflow_id Workflow UUID to wait on
  292. * @return [array] $workflow['data'] Result of the request
  293. *
  294. * @deprecated Use new WorkFlow() object instead
  295. */
  296. protected function waitOnWorkflow($object_name, $object_id, $workflow_id) {
  297. print "Working .";
  298. $workflow = self::request(
  299. $object_name,
  300. $object_id,
  301. "workflows/$workflow_id",
  302. 'GET'
  303. );
  304. $result = $workflow['data']->result;
  305. $desc = $workflow['data']->active_description;
  306. $type = $workflow['data']->type;
  307. $tries = 0;
  308. while (!isset($result)) {
  309. if (in_array($result, array('failed', 'aborted'))) {
  310. if (
  311. isset($workflow['data']->final_task)
  312. && !empty($workflow['data']->final_task->messages)
  313. ) {
  314. foreach ($workflow['data']->final_task->messages as $message) {
  315. sprintf('[%s] %s', $message->level, $message->message);
  316. }
  317. } else {
  318. Terminus::error(
  319. PHP_EOL . "Couldn't complete workflows: '$type'" . PHP_EOL
  320. );
  321. }
  322. }
  323. $workflow = self::request(
  324. $object_name,
  325. $object_id,
  326. "workflows/$workflow_id",
  327. 'GET'
  328. );
  329. $result = $workflow['data']->result;
  330. if (Terminus::get_config('debug')) {
  331. print_r($workflow);
  332. }
  333. sleep(3);
  334. print ".";
  335. $tries++;
  336. }
  337. print PHP_EOL;
  338. if (in_array($workflow['data']->result, array("succeeded", "aborted"))) {
  339. return $workflow['data'];
  340. }
  341. return false;
  342. }
  343. /**
  344. * Outputs basic workflow success/failure messages
  345. *
  346. * @param [Workflow] $workflow Workflow to output message about
  347. * @return [void]
  348. */
  349. protected function workflowOutput($workflow) {
  350. if ($workflow->get('result') == 'succeeded') {
  351. Logger::coloredOutput(
  352. '%2<K>' . $workflow->get('active_description') . '</K>'
  353. );
  354. } else {
  355. $final_task = $workflow->get('final_task');
  356. Logger::redline($final_task->reason);
  357. }
  358. }
  359. /**
  360. * Outputs basic response success/failure messages
  361. *
  362. * @param [array] $response Array from response
  363. * @param [array] $messages Array of response strings
  364. * [string] success Displayed on success
  365. * [string] failure Displayed on error
  366. */
  367. protected function responseOutput($response, $messages = array()) {
  368. $default_messages = array(
  369. 'success' => 'The operation has succeeded.',
  370. 'failure' => 'The operation was unsuccessful.',
  371. );
  372. $messages = array_merge($default_messages, $messages);
  373. if ($response['status_code'] == 200) {
  374. Terminus::success($messages['success']);
  375. } else {
  376. Terminus::error($messages['failure']);
  377. }
  378. }
  379. }