Switchboard
  • Namespace
  • Class
  • Tree

Namespaces

  • Fluxsauce
    • Switchboard

Classes

  • EnvDb
  • Environment
  • Persistent
  • Project
  • Provider
  • ProviderAcquia
  • ProviderPantheon
  • Site
  • Sqlite
  1 <?php
  2 /**
  3  * @file
  4  * Pantheon specific API interactions.
  5  */
  6 
  7 namespace Fluxsauce\Switchboard;
  8 
  9 /**
 10  * Pantheon specific API interactions.
 11  * @package Fluxsauce\Switchboard
 12  */
 13 class ProviderPantheon extends Provider {
 14   /**
 15    * @var string Machine name of the Provider.
 16    */
 17   protected $name = 'pantheon';
 18 
 19   /**
 20    * @var string Human readable label for the Provider.
 21    */
 22   protected $label = 'Pantheon';
 23 
 24   /**
 25    * @var string Homepage URL for the provider.
 26    */
 27   protected $homepage = 'https://www.getpantheon.com/';
 28 
 29   /**
 30    * @var string Endpoint URL for the provider.
 31    */
 32   protected $endpoint = 'https://terminus.getpantheon.com';
 33 
 34   /**
 35    * A mapping function that calls the appropriate API to populate a field.
 36    *
 37    * @param string $site_name
 38    *   Machine name of the site in question.
 39    * @param string $field
 40    *   Name of the field to populate.
 41    *
 42    * @return string
 43    *   The value of the field.
 44    * @throws \Exception
 45    *   Unknown field name.
 46    */
 47   public function siteGetField($site_name, $field) {
 48     switch ($field) {
 49       // No API required.
 50       case 'name':
 51       case 'provider':
 52         break;
 53 
 54       case 'vcsUrl':
 55       case 'vcsType':
 56       case 'vcsProtocol':
 57       case 'sshPort':
 58         $this->apiGetSite($site_name);
 59         break;
 60 
 61       case 'unixUsername':
 62       case 'realm':
 63       case 'uuid':
 64         $this->apiGetSites();
 65         break;
 66 
 67       case 'title':
 68         $this->apiGetSiteName($site_name);
 69       case 'environments':
 70         $this->apiGetSiteEnvironments($site_name);
 71         break;
 72 
 73       default:
 74         throw new \Exception('Unknown field ' . $field . ' in ' . __CLASS__);
 75     }
 76     return $this->sites[$site_name]->$field;
 77   }
 78 
 79   /**
 80    * Perform an API call to get site information from a Provider.
 81    *
 82    * @param string $site_name
 83    *   The name of the site in question.
 84    */
 85   public function apiGetSite($site_name) {
 86     $site = new Site('pantheon', $site_name);
 87 
 88     $repository = 'codeserver.dev.' . $site->uuid;
 89     $repository .= '@codeserver.dev.' . $site->uuid;
 90     $repository .= '.drush.in:2222/~/repository.git';
 91 
 92     $site->update(array(
 93       'vcsUrl' => $repository,
 94       'vcsType' => 'git',
 95       'vcsProtocol' => 'ssh',
 96       'sshPort' => 2222,
 97     ));
 98     $this->sites[$site_name] = $site;
 99   }
100 
101   /**
102    * Populate available Sites from a Provider.
103    */
104   public function apiGetSites() {
105     $user_uuid = drush_cache_get('user_uuid', $this->drushCacheBinAuthName());
106     $result = switchboard_request($this, array(
107       'method' => 'GET',
108       'realm' => 'sites',
109       'resource' => 'user',
110       'uuid' => $user_uuid->data,
111     ));
112     $site_metadata = json_decode($result->body);
113 
114     $sites = array();
115 
116     foreach ($site_metadata as $uuid => $data) {
117       $site = new Site($this->name, $data->information->name);
118       $site->uuid = $uuid;
119       $site->realm = $data->information->preferred_zone;
120       $site->unixUsername = '';
121       $site->update();
122       $this->sites[$site->name] = $site;
123     }
124   }
125 
126   /**
127    * API call to get the human readable name of a Site.
128    *
129    * @param string $site_name
130    *   The machine name of a site.
131    */
132   public function apiGetSiteName($site_name) {
133     $site =& $this->sites[$site_name];
134     $result = switchboard_request($this, array(
135       'method' => 'GET',
136       'realm' => 'attributes',
137       'resource' => 'site',
138       'uuid' => $site->uuid,
139     ));
140     $site_attributes = json_decode($result->body);
141     $site->title = $site_attributes->label;
142     $site->update();
143     $this->sites[$site->name] = $site;
144   }
145 
146   /**
147    * Provider specific options for Requests.
148    *
149    * @return array
150    *   Options for the request; see Requests::request for details.
151    */
152   public function requestsOptionsCustom() {
153     $options = array();
154     $cookies = drush_cache_get('session', $this->drushCacheBinAuthName());
155     if (isset($cookies->data)) {
156       $options = array(
157         'cookies' => array($cookies->data),
158       );
159     }
160     return $options;
161   }
162 
163   /**
164    * Log in to target Provider.
165    *
166    * @param string $email
167    *   The email address of the user.
168    * @param string $password
169    *   The password of the user.
170    *
171    * @return bool
172    *   Indicates success.
173    */
174   public function authLogin($email, $password) {
175     $url = $this->endpoint . '/login';
176 
177     // Get the form build ID.
178     try {
179       $response = \Requests::post($url);
180     }
181     catch (\Requests_Exception $e) {
182       return drush_set_error('SWITCHBOARD_AUTH_LOGIN_PANTHEON_ENDPOINT_UNAVAILABLE', dt('Pantheon endpoint unavailable: @error', array(
183         '@error' => $e->getMessage(),
184       )));
185     }
186     $form_build_id = $this->authLoginGetFormBuildId($response->body);
187     if (!$form_build_id) {
188       return drush_set_error('SWITCHBOARD_AUTH_LOGIN_PANTHEON_LOGIN_UNAVAILABLE', dt('Pantheon login unavailable.'));
189     }
190 
191     // Attempt to log in.
192     try {
193       $response = \Requests::post($url, array(), array(
194         'email' => $email,
195         'password' => $password,
196         'form_build_id' => $form_build_id,
197         'form_id' => 'atlas_login_form',
198         'op' => 'Login',
199       ), $this->requestsOptions(array('follow_redirects' => FALSE)));
200     }
201     catch (\Requests_Exception $e) {
202       return drush_set_error('SWITCHBOARD_AUTH_LOGIN_PANTHEON_LOGIN_FAILURE', dt('Pantheon login failure: @error', array(
203         '@error' => $e->getMessage(),
204       )));
205     }
206 
207     $session = $this->authLoginGetSessionFromHeaders($response->headers->getValues('set-cookie'));
208 
209     if (!$session) {
210       return drush_set_error('SWITCHBOARD_AUTH_LOGIN_PANTHEON_NO_SESSION', dt('Pantheon Session not found; please check your credentials and try again.'));
211     }
212 
213     // Get the UUID.
214     $user_uuid = array_pop(explode('/', $response->headers->offsetGet('Location')));
215     if (!switchboard_validate_uuid($user_uuid)) {
216       return drush_set_error('SWITCHBOARD_AUTH_LOGIN_PANTHEON_NO_UUID', dt('Pantheon User UUID not found; please check your credentials and try again.'));
217     }
218 
219     drush_cache_clear_all('*', $this->drushCacheBinAuthName(), TRUE);
220     drush_cache_set('user_uuid', $user_uuid, $this->drushCacheBinAuthName());
221     drush_cache_set('session', $session, $this->drushCacheBinAuthName());
222     drush_cache_set('email', $email, $this->drushCacheBinAuthName());
223     return TRUE;
224   }
225 
226   /**
227    * Determine whether a user is logged-in to a Provider.
228    *
229    * @return bool
230    *   TRUE if they are.
231    */
232   public function authIsLoggedIn() {
233     $session = drush_cache_get('session', $this->drushCacheBinAuthName());
234     return isset($session->data) ? TRUE : FALSE;
235   }
236 
237   /**
238    * Parse session out of a header.
239    *
240    * Based on terminus_pauth_get_session_from_headers().
241    *
242    * @param array $headers
243    *   Headers to parse.
244    *
245    * @return string
246    *   The session cookie, if found.
247    */
248   public function authLoginGetSessionFromHeaders($headers) {
249     $session = FALSE;
250     foreach ($headers as $header) {
251       foreach (explode('; ', $header) as $cookie) {
252         if (strpos($cookie, 'SSESS') === 0) {
253           $session = $cookie;
254         }
255       }
256     }
257     return $session;
258   }
259 
260   /**
261    * Parse form build ID, based on terminus_pauth_login_get_form_build_id().
262    *
263    * @param string $html
264    *   The raw HTML to parse.
265    *
266    * @return string
267    *   The login form_build_id, if found.
268    */
269   public function authLoginGetFormBuildId($html) {
270     if (!$html) {
271       return FALSE;
272     }
273     // Parse form build ID.
274     $dom = new \DOMDocument();
275     @$dom->loadHTML($html);
276     $login_form = $dom->getElementById('atlas-login-form');
277     if (!$login_form) {
278       return FALSE;
279     }
280 
281     foreach ($login_form->getElementsByTagName('input') as $input) {
282       if ($input->getAttribute('name') == 'form_build_id') {
283         return $input->getAttribute('value');
284       }
285     }
286     return FALSE;
287   }
288 
289   /**
290    * Populate available Site Environments from a Provider.
291    *
292    * @param string $site_name
293    *   The machine name of the site in question.
294    */
295   public function apiGetSiteEnvironments($site_name) {
296     $site =& $this->sites[$site_name];
297     $result = switchboard_request($this, array(
298       'method' => 'GET',
299       'realm' => 'environments',
300       'resource' => 'site',
301       'uuid' => $site->uuid,
302     ));
303     $environment_data = json_decode($result->body);
304     foreach ($environment_data as $environment_name => $environment) {
305       $new_environment = new Environment($site->id, $environment_name);
306       $new_environment->branch = 'master';
307       $new_environment->host = "appserver.$environment_name.{$site->uuid}.drush.in";
308       $new_environment->username = "$environment_name.$site_name";
309       $new_environment->update();
310       $site->environmentAdd($new_environment);
311     }
312   }
313 
314   /**
315    * Get and populate list of Databases for a particular Environment.
316    *
317    * @param string $site_name
318    *   The machine name of the Site.
319    * @param string $env_name
320    *   The machine name of the Site Environment.
321    */
322   public function apiGetSiteEnvDbs($site_name, $env_name) {
323     $site =& $this->sites[$site_name];
324     $env =& $site->environments[$env_name];
325     $new_db = new EnvDb($env->id, 'pantheon');
326     $new_db->update();
327     $env->dbAdd($new_db);
328   }
329 
330   /**
331    * Get a list of database backups for a particular Site Environment.
332    *
333    * @param string $site_name
334    *   The machine name of the Site.
335    * @param string $env_name
336    *   The machine name of the Site Environment.
337    *
338    * @return array
339    *   An array of Backup arrays keyed by the timestamp. Each Backup
340    *   array has the following keys:
341    *   - 'filename'
342    *   - 'url'
343    *   - 'timestamp'
344    */
345   public function apiGetSiteEnvDbBackups($site_name, $env_name) {
346     $site = $this->sites[$site_name];
347     $result = switchboard_request($this, array(
348       'method' => 'GET',
349       'resource' => 'site',
350       'realm' => 'environments/' . $env_name . '/backups/catalog',
351       'uuid' => $site->uuid,
352     ));
353     $backups = array();
354     $backup_data = json_decode($result->body);
355     foreach ($backup_data as $id => $backup) {
356       $parts = explode('_', $id);
357       if (!isset($backup->filename) || $parts[2] != 'database') {
358         continue;
359       }
360       $backups[$backup->timestamp] = array(
361         'filename' => $backup->filename,
362         'url' => '',
363         'bucket' => $parts[0] . '_' . $parts[1],
364         'timestamp' => $backup->timestamp,
365       );
366     }
367     arsort($backups);
368     return $backups;
369   }
370 
371   /**
372    * Helper function to get the latest database backup.
373    *
374    * @param string $site_name
375    *   The machine name of the Site in question.
376    * @param string $env_name
377    *   The machine name of the Site Environment in question.
378    *
379    * @return array
380    *   A backup array as defined in apiGetSiteEnvDbBackups().
381    */
382   public function getSiteEnvDbBackupLatest($site_name, $env_name) {
383     $backup = parent::getSiteEnvDbBackupLatest($site_name, $env_name);
384     $backup['url'] = $this->apiGetBackupDownloadUrl($site_name, $env_name, $backup['bucket'], 'database');
385     unset($backup['bucket']);
386     return $backup;
387   }
388 
389   /**
390    * Helper function to get the S3 backup URL.
391    *
392    * @param string $site_name
393    *   The machine name of the Site in question.
394    * @param string $env_name
395    *   The machine name of the Site Environment in question.
396    * @param string $bucket
397    *   The S3 bucket.
398    * @param string $element
399    *   Elements are db, code, files.
400    *
401    * @return string
402    *   The S3 URL of the backup.
403    */
404   public function apiGetBackupDownloadUrl($site_name, $env_name, $bucket, $element) {
405     $site = $this->sites[$site_name];
406     $result = switchboard_request($this, array(
407       'method' => 'POST',
408       'resource' => 'site',
409       'realm' => 'environments/' . $env_name . '/backups/catalog/' . $bucket . '/' . $element . '/s3token',
410       'uuid' => $site->uuid,
411       'data' => array('method' => 'GET'),
412     ));
413     $token = json_decode($result->body);
414     return $token->url;
415   }
416 
417   /**
418    * Download a backup.
419    *
420    * @param array $backup
421    *   An array from apiGetSiteEnvDbBackups().
422    * @param string $destination
423    *   The path to the destination.
424    *
425    * @return string
426    *   The full path to the downloaded backup.
427    */
428   public function apiDownloadBackup($backup, $destination) {
429     // See Drush's package_handler_download_project().
430     $destination_path = $destination . DIRECTORY_SEPARATOR . $backup['filename'];
431     $path = _drush_download_file($backup['url'], $destination_path, 31556926);
432     if ($path || drush_get_context('DRUSH_SIMULATE')) {
433       return $destination_path;
434     }
435     else {
436       return drush_set_error('SWITCHBOARD_PANTHEON_BACKUP_DL_FAIL', dt('Unable to download!'));
437     }
438   }
439 
440   /**
441    * Get the remote path to files for a particular Site Environment.
442    *
443    * @param string $site_name
444    *   The machine name of the Site in question.
445    * @param string $env_name
446    *   The machine name of the Site Environment in question.
447    *
448    * @return string
449    *   The full path of the files directory.
450    */
451   public function getFilesPath($site_name, $env_name) {
452     return 'files';
453   }
454 }
455 
Switchboard API documentation generated by ApiGen 2.8.0