PageRenderTime 24ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/php/class-terminus-command.php

https://gitlab.com/blueprintmrk/cli
PHP | 385 lines | 233 code | 37 blank | 115 comment | 19 complexity | 518f6ef0f53023127129d3fe9b0cf702 MD5 | raw file
  1. <?php
  2. use Terminus\Auth;
  3. use Terminus\Endpoint;
  4. use Terminus\Request;
  5. use Terminus\Session;
  6. use Terminus\Utils;
  7. use Terminus\Exceptions\TerminusException;
  8. use Terminus\Loggers\Regular as Logger;
  9. /**
  10. * The base class for Terminus commands
  11. */
  12. abstract class TerminusCommand {
  13. public $cache;
  14. public $session;
  15. public $sites;
  16. protected static $blacklist = array('password');
  17. protected $func;
  18. /**
  19. * @var LoggerInterface
  20. */
  21. protected $logger;
  22. /**
  23. * @var OutputterInterface
  24. */
  25. protected $outputter;
  26. /**
  27. * Instantiates object, sets cache and session
  28. *
  29. * @return [TerminusCommand] $this
  30. */
  31. public function __construct() {
  32. //Load commonly used data from cache
  33. $this->cache = Terminus::getCache();
  34. $this->logger = Terminus::getLogger();
  35. $this->outputter = Terminus::getOutputter();
  36. $this->session = Session::instance();
  37. if (!Terminus::isTest()) {
  38. $this->checkForUpdate();
  39. }
  40. }
  41. /**
  42. * Make a request to the Dashbord's internal API
  43. *
  44. * @param [string] $path API path (URL)
  45. * @param [array] $options Options for the request
  46. * [string] method GET is default
  47. * [mixed] data Native PHP data structure (e.g. int, string array, or
  48. * simple object) to be sent along with the request. Will be encoded as
  49. * JSON for you.
  50. * @return [array] $return
  51. */
  52. public static function pagedRequest($path, $options = array()) {
  53. $limit = 100;
  54. if (isset($options['limit'])) {
  55. $limit = $options['limit'];
  56. }
  57. //$results is an associative array so we don't refetch
  58. $results = array();
  59. $finished = false;
  60. $start = null;
  61. while (!$finished) {
  62. $paged_path = $path . '?limit=' . $limit;
  63. if ($start) {
  64. $paged_path .= '&start=' . $start;
  65. }
  66. $resp = self::simpleRequest($paged_path);
  67. $data = $resp['data'];
  68. if (count($data) > 0) {
  69. $start = end($data)->id;
  70. //If the last item of the results has previously been received,
  71. //that means there are no more pages to fetch
  72. if (isset($results[$start])) {
  73. $finished = true;
  74. continue;
  75. }
  76. foreach ($data as $item) {
  77. $results[$item->id] = $item;
  78. }
  79. } else {
  80. $finished = true;
  81. }
  82. }
  83. $return = array('data' => array_values($results));
  84. return $return;
  85. }
  86. /**
  87. * Make a request to the Pantheon API
  88. *
  89. * @param [string] $realm Permissions realm for data request (e.g. user,
  90. * site organization, etc. Can also be "public" to simply pull read-only
  91. * data that is not privileged.
  92. * @param [string] $uuid The UUID of the item in the realm to access
  93. * @param [string] $path API path (URL)
  94. * @param [string] $method HTTP method to use
  95. * @param [mixed] $options A native PHP data structure (e.g. int, string,
  96. * array, or stdClass) to be sent along with the request
  97. * @return [array] $data
  98. */
  99. public static function request(
  100. $realm,
  101. $uuid,
  102. $path = false,
  103. $method = 'GET',
  104. $options = array()
  105. ) {
  106. $logger = Terminus::getLogger();
  107. try {
  108. if (!in_array($realm, array('auth/refresh', 'login', 'user'))) {
  109. if (!isset($options['headers'])) {
  110. $options['headers'] = array();
  111. }
  112. $options['headers']['Cookie'] = array(
  113. 'X-Pantheon-Session' => Session::getValue('id_token')
  114. );
  115. }
  116. if (!in_array($realm, array('login', 'user'))) {
  117. $options['cookies'] = array(
  118. 'X-Pantheon-Session' => Session::getValue('session')
  119. );
  120. $options['verify'] = false;
  121. }
  122. $url = Endpoint::get(
  123. array(
  124. 'realm' => $realm,
  125. 'uuid' => $uuid,
  126. 'path' => $path,
  127. )
  128. );
  129. $logger->debug('Request URL: ' . $url);
  130. Terminus::getLogger()->debug('URL: {url}', compact('url'));
  131. $resp = Request::send($url, $method, $options);
  132. $json = $resp->getBody(true);
  133. $data = array(
  134. 'info' => $resp->getInfo(),
  135. 'headers' => $resp->getRawHeaders(),
  136. 'json' => $json,
  137. 'data' => json_decode($json),
  138. 'status_code' => $resp->getStatusCode()
  139. );
  140. return $data;
  141. } catch (Guzzle\Http\Exception\BadResponseException $e) {
  142. $response = $e->getResponse();
  143. throw new TerminusException($response->getBody(true));
  144. } catch (Guzzle\Http\Exception\HttpException $e) {
  145. $request = $e->getRequest();
  146. $sanitized_request = TerminusCommand::stripSensitiveData(
  147. (string)$request,
  148. TerminusCommand::$blacklist
  149. );
  150. throw new TerminusException(
  151. 'API Request Error. {msg} - Request: {req}',
  152. array('req' => $sanitized_request, 'msg' => $e->getMessage())
  153. );
  154. } catch (Exception $e) {
  155. throw new TerminusException(
  156. 'API Request Error: {msg}',
  157. array('msg' => $e->getMessage())
  158. );
  159. }
  160. }
  161. /**
  162. * Simplified request method for Pantheon API
  163. *
  164. * @param [string] $path API path (URL)
  165. * @param [array] $options Options for the request
  166. * [string] method GET is default
  167. * [mixed] data Native PHP data structure (e.g. int, string array, or
  168. * simple object) to be sent along with the request. Will be encoded as
  169. * JSON for you.
  170. * @return [array] $data
  171. */
  172. public static function simpleRequest($path, $options = array()) {
  173. $method = 'get';
  174. if (isset($options['method'])) {
  175. $method = $options['method'];
  176. unset($options['method']);
  177. }
  178. $url = sprintf(
  179. '%s://%s:%s/api/%s',
  180. TERMINUS_PROTOCOL,
  181. TERMINUS_HOST,
  182. TERMINUS_PORT,
  183. $path
  184. );
  185. if (Session::getValue('session')) {
  186. $options['cookies'] = array(
  187. 'X-Pantheon-Session' => Session::getValue('session')
  188. );
  189. }
  190. try {
  191. Terminus::getLogger()->debug('URL: {url}', compact('url'));
  192. $resp = Request::send($url, $method, $options);
  193. } catch (Guzzle\Http\Exception\BadResponseException $e) {
  194. throw new TerminusException(
  195. 'API Request Error: {msg}',
  196. array('msg' => $e->getMessage())
  197. );
  198. }
  199. $json = $resp->getBody(true);
  200. $data = array(
  201. 'info' => $resp->getInfo(),
  202. 'headers' => $resp->getRawHeaders(),
  203. 'json' => $json,
  204. 'data' => json_decode($json),
  205. 'status_code' => $resp->getStatusCode()
  206. );
  207. return $data;
  208. }
  209. /**
  210. * Strips sensitive data out of the JSON printed in a request string
  211. *
  212. * @param [string] $request The string with a JSON with sensitive data
  213. * @param [array] $blacklist Array of string keys to remove from request
  214. * @return [string] $result Sensitive data-stripped version of $request
  215. */
  216. public static function stripSensitiveData($request, $blacklist = array()) {
  217. //Locate the JSON in the string, turn to array
  218. $regex = '~\{(.*)\}~';
  219. preg_match($regex, $request, $matches);
  220. $request_array = json_decode($matches[0], true);
  221. //See if a blacklisted items are in the arrayed JSON, replace
  222. foreach ($blacklist as $blacklisted_item) {
  223. if (isset($request_array[$blacklisted_item])) {
  224. $request_array[$blacklisted_item] = '*****';
  225. }
  226. }
  227. //Turn array back to JSON, put back in string
  228. $result = str_replace($matches[0], json_encode($request_array), $request);
  229. return $result;
  230. }
  231. /**
  232. * Downloads the given URL to the given target
  233. *
  234. * @param [string] $url Location of file to download
  235. * @param [string] $target Location to download file to
  236. * @return [void]
  237. */
  238. protected function download($url, $target) {
  239. try {
  240. $response = Request::download($url, $target);
  241. return $target;
  242. } catch (Exception $e) {
  243. $this->log()->error($e->getMessage());
  244. }
  245. }
  246. /**
  247. * Sends the given message to logger as an error and exits with -1
  248. *
  249. * @param [string] $message Message to log as error before exit
  250. * @param [array] $context Vars to interpolate in message
  251. * @return [void]
  252. */
  253. protected function failure(
  254. $message = 'Command failed',
  255. array $context = array(),
  256. $exit_code = 1
  257. ) {
  258. throw new TerminusException($message, $context, $exit_code);
  259. }
  260. /**
  261. * Retrieves the logger for use
  262. *
  263. * @return [LoggerInterface] $this->logger
  264. */
  265. protected function log() {
  266. return $this->logger;
  267. }
  268. /**
  269. * Retrieves the outputter for use
  270. *
  271. * @return [OutputterInterface] $this->outputter
  272. */
  273. protected function output() {
  274. return $this->outputter;
  275. }
  276. /**
  277. * Saves the logger object as a class property
  278. *
  279. * @param [LoggerInterface] $logger Logger object to save
  280. * @return [void]
  281. */
  282. protected function setLogger($logger) {
  283. $this->logger = $logger;
  284. }
  285. /**
  286. * Saves the outputter object as a class property
  287. *
  288. * @param [OutputterInterface] $outputter Outputter object to save
  289. * @return [void]
  290. */
  291. protected function setOutputter($outputter) {
  292. $this->outputter = $outputter;
  293. }
  294. /**
  295. * Outputs basic workflow success/failure messages
  296. *
  297. * @param [Workflow] $workflow Workflow to output message about
  298. * @return [void]
  299. */
  300. protected function workflowOutput($workflow) {
  301. if ($workflow->get('result') == 'succeeded') {
  302. $this->log()->info($workflow->get('active_description'));
  303. } else {
  304. $final_task = $workflow->get('final_task');
  305. $this->log()->error($final_task->reason);
  306. }
  307. }
  308. /**
  309. * Retrieves current version number from repository and saves it to the cache
  310. *
  311. * @return [string] $response->name The version number
  312. */
  313. protected function checkCurrentVersion() {
  314. $url = 'https://api.github.com/repos/pantheon-systems/cli/releases?per_page=1';
  315. $response = Request::send($url, 'GET');
  316. $json = $response->getBody(true);
  317. $data = json_decode($json);
  318. $release = array_shift($data);
  319. $this->cache->put_data('latest_release', array('version' => $release->name, 'check_date' => time()));
  320. return $release->name;
  321. }
  322. /**
  323. * Checks for new versions of Terminus once per week and saves to cache
  324. *
  325. * @return [void]
  326. */
  327. private function checkForUpdate() {
  328. $cache_data = $this->cache->get_data(
  329. 'latest_release',
  330. array('decode_array' => true)
  331. );
  332. if (!$cache_data
  333. || ((int)$cache_data['check_date'] < (int)strtotime('-7 days'))
  334. ) {
  335. try {
  336. $current_version = $this->checkCurrentVersion();
  337. if (version_compare($current_version, TERMINUS_VERSION, '>')) {
  338. $this->log()->info(
  339. 'An update to Terminus is available. Please update to {version}.',
  340. array('version' => $current_version)
  341. );
  342. }
  343. } catch (\Exception $e) {
  344. $this->log()->info('Cannot retrieve current Terminus version.');
  345. }
  346. }
  347. }
  348. }