/** * Increments the quantity of the processed queries during current client instance */ protected function _incrementQueriesQuantity() { $this->aws->queriesQuantity++; $eventObserver = $this->aws->getEventObserver(); if (isset($eventObserver) && $eventObserver->isSubscribed(EventType::EVENT_SEND_REQUEST)) { $eventObserver->fireEvent(new SendRequestEvent(array('requestNumber' => $this->aws->queriesQuantity, 'apicall' => isset($this->lastApiCall) ? $this->lastApiCall : null))); } }
/** * {@inheritdoc} * @see \Scalr\System\Zmq\Cron\TaskInterface::enqueue() */ public function enqueue() { $queue = new ArrayObject([]); if (!\Scalr::getContainer()->analytics->enabled) { $this->log("INFO", "Terminating the process as Cost analytics is disabled in the config."); exit; } if (SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)) { $this->log("INFO", "Terminating the process because of overriding AWS prices has been forbidden by financial admin."); exit; } $now = new DateTime('now', new DateTimeZone('UTC')); $urls = array('https://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/mswin-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/previous-generation/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/previous-generation/mswin-od.min.js'); $availableLocations = Aws::getCloudLocations(); foreach ($urls as $link) { $json = trim(preg_replace('/^.+?callback\\((.+?)\\);\\s*$/sU', '\\1', $this->getPricingContent($link))); $data = json_decode(preg_replace('/(\\w+):/', '"\\1":', $json)); if (!empty($data->config->regions)) { foreach ($data->config->regions as $rd) { $rd->url = basename($link); $queue->append($rd); } } } return $queue; }
/** * Constructor */ public function __construct(\Scalr\Service\Aws $aws) { $this->plugins = new \ArrayObject(array()); $this->aws = $aws; $cfEnabled = $this->aws->getContainer()->config('scalr.aws.plugins.enabled'); if (!is_array($cfEnabled)) { $cfEnabled = (array) $cfEnabled; } foreach (EventType::getAllowedValues() as $eventName) { $this->subscriptions[$eventName] = array(); } $iterator = new \RegexIterator(new \DirectoryIterator(__DIR__ . DIRECTORY_SEPARATOR . 'Handlers'), '/^(.+)Plugin\\.php$/'); foreach ($iterator as $di) { /* @var $di \DirectoryIterator */ $plugin = substr($di->getFilename(), 0, -4); $class = __NAMESPACE__ . '\\Handlers\\' . $plugin; $p = new $class(); if (!$p instanceof PluginInterface) { trigger_error(sprintf('AWS client plugin "%s" must implement PluginInterface.', $class), E_USER_WARNING); continue; } $p->setAws($this->aws); $p->setContainer($this->aws->getContainer()); $subscribed = $p->getSubscribedEvents(); if (empty($subscribed)) { continue; } $lcPlugin = strtolower(substr($plugin, 0, -6)); foreach ($subscribed as $eventName) { if (isset($this->subscriptions[$eventName])) { $this->subscriptions[$eventName][$lcPlugin] = $p->priority; } else { throw new Exception\PluginException(sprintf('AWS client plugin "%s" is subscribed to unknown event "%s".', $class, $eventName)); } } $this->plugins[$lcPlugin] = $p; $this->enabled[$lcPlugin] = in_array($lcPlugin, $cfEnabled) ? true : null; } //Sorts subscriptions by plugin priority foreach ($this->subscriptions as $eventName => $v) { if (!empty($v)) { arsort($this->subscriptions[$eventName]); } } }
private function saveEc2() { $pars = []; $enabled = false; $envAutoEnabled = false; $bNew = !$this->env->isPlatformEnabled(SERVER_PLATFORMS::EC2); $currentCloudCredentials = $this->env->keychain(SERVER_PLATFORMS::EC2); $ccProps = $currentCloudCredentials->properties; if ($this->getParam('ec2_is_enabled')) { $enabled = true; $pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE] = trim($this->checkVar(Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE, 'string', "AWS Account Type required", SERVER_PLATFORMS::EC2)); $pars[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY] = trim($this->checkVar(Entity\CloudCredentialsProperty::AWS_ACCESS_KEY, 'string', "AWS Access Key required", SERVER_PLATFORMS::EC2)); $pars[Entity\CloudCredentialsProperty::AWS_SECRET_KEY] = trim($this->checkVar(Entity\CloudCredentialsProperty::AWS_SECRET_KEY, 'password', "AWS Access Key required", SERVER_PLATFORMS::EC2)); $pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] = $this->checkVar(Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY, 'file', '', SERVER_PLATFORMS::EC2); $pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] = $this->checkVar(Entity\CloudCredentialsProperty::AWS_CERTIFICATE, 'file', '', SERVER_PLATFORMS::EC2); if ($this->getContainer()->analytics->enabled) { $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_ENABLED] = $this->checkVar2(Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_ENABLED, 'bool', '', SERVER_PLATFORMS::EC2); if (!empty($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_ENABLED])) { $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET] = $this->checkVar(Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET, 'string', "Detailed billing bucket name is required", SERVER_PLATFORMS::EC2); $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT] = $this->checkVar2(Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT, 'string', '', SERVER_PLATFORMS::EC2); $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION] = $this->checkVar(Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION, 'string', "Aws region is required", SERVER_PLATFORMS::EC2); } else { $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET] = false; $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT] = false; $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION] = false; } } // user can mull certificate and private key, check it if (strpos($pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY], 'BEGIN CERTIFICATE') !== FALSE && strpos($pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE], 'BEGIN PRIVATE KEY') !== FALSE) { // swap it $key = $pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]; $pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] = $pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]; $pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] = $key; } if ($pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE] == Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE_GOV_CLOUD) { $region = \Scalr\Service\Aws::REGION_US_GOV_WEST_1; } else { if ($pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE] == Entity\CloudCredentialsProperty::AWS_ACCOUNT_TYPE_CN_CLOUD) { $region = \Scalr\Service\Aws::REGION_CN_NORTH_1; } else { $region = \Scalr\Service\Aws::REGION_US_EAST_1; } } if (!count($this->checkVarError)) { if ($pars[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY] != $ccProps[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY] or $pars[Entity\CloudCredentialsProperty::AWS_SECRET_KEY] != $ccProps[Entity\CloudCredentialsProperty::AWS_SECRET_KEY] or $pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] != $ccProps[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] or $pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] != $ccProps[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) { $aws = $this->env->aws($region, $pars[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $pars[Entity\CloudCredentialsProperty::AWS_SECRET_KEY], !empty($pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) ? $pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] : null, !empty($pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]) ? $pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] : null); //Validates private key and certificate if they are provided if (!empty($pars[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) || !empty($pars[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY])) { try { //SOAP is not supported anymore //$aws->validateCertificateAndPrivateKey(); } catch (Exception $e) { throw new Exception(_("Incorrect format of X.509 certificate or private key. Make sure that you are using files downloaded from AWS profile. ({$e->getMessage()})")); } } //Validates both access and secret keys try { $buckets = $aws->s3->bucket->getList(); } catch (Exception $e) { throw new Exception(sprintf(_("Failed to verify your EC2 access key and secret key: %s"), $e->getMessage())); } //Extract AWS Account ID $pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID] = $aws->getAccountNumber(); try { if ($ccProps[Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID] != $pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID]) { $this->db->Execute("DELETE FROM client_environment_properties WHERE name LIKE 'ec2.vpc.default%' AND env_id = ?", [$this->env->id]); } } catch (Exception $e) { } } else { $pars[Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID] = $ccProps[Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID]; } } else { $this->response->failure(); $this->response->data(['errors' => $this->checkVarError]); return; } } if ($enabled && $this->getContainer()->analytics->enabled && !empty($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET])) { try { $region = $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION]; $aws = $this->env->aws($region, $pars[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $pars[Entity\CloudCredentialsProperty::AWS_SECRET_KEY]); if (!empty($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) && $aws->getAccountNumber() != $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) { $payerCredentials = $this->getUser()->getAccount()->cloudCredentialsList([SERVER_PLATFORMS::EC2], [], [Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID => [['value' => $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]]]]); if (count($payerCredentials) == 0) { throw new Exception("Payer account not found!"); } $payerCredentials = $payerCredentials->current(); $aws = $this->env->aws($region, $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_SECRET_KEY], !empty($payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) ? $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] : null, !empty($payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]) ? $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] : null); } try { $bucketObjects = $aws->s3->bucket->listObjects($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET]); } catch (ClientException $e) { if ($e->getErrorData() && $e->getErrorData()->getCode() == ErrorData::ERR_AUTHORIZATION_HEADER_MALFORMED && preg_match("/expecting\\s+'(.+?)'/", $e->getMessage(), $matches) && in_array($matches[1], Aws::getCloudLocations())) { $expectingRegion = $matches[1]; if (isset($payerCredentials)) { $aws = $this->env->aws($expectingRegion, $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_SECRET_KEY], !empty($payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) ? $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] : null, !empty($payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]) ? $payerCredentials->properties[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] : null); } else { $aws = $this->env->aws($expectingRegion, $pars[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $pars[Entity\CloudCredentialsProperty::AWS_SECRET_KEY]); } $bucketObjects = $aws->s3->bucket->listObjects($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET]); $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION] = $expectingRegion; } else { throw $e; } } $objectName = (empty($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) ? '' : "{$pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]}-") . 'aws-billing-detailed-line-items-with-resources-and-tags'; $objectExists = false; $bucketObjectName = null; foreach ($bucketObjects as $bucketObject) { /* @var $bucketObject Scalr\Service\Aws\S3\DataType\ObjectData */ if (strpos($bucketObject->objectName, $objectName) !== false) { $bucketObjectName = $bucketObject->objectName; $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_ENABLED] = 1; $objectExists = true; break; } } if (!$objectExists) { $this->response->failure(); $this->response->data(['errors' => [Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT => "Object with name '{$objectName}' does not exist."]]); return; } $aws->s3->object->getMetadata($pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET], $bucketObjectName); } catch (Exception $e) { $this->response->failure(); $this->response->data(['errors' => [Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET => sprintf("Cannot access billing bucket with name %s. Error: %s", $pars[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET], $e->getMessage())]]); return; } } $this->db->BeginTrans(); try { $this->env->enablePlatform(SERVER_PLATFORMS::EC2, $enabled); if ($enabled) { $this->makeCloudCredentials(SERVER_PLATFORMS::EC2, $pars); if ($this->getContainer()->analytics->enabled && $bNew) { $this->getContainer()->analytics->notifications->onCloudAdd('ec2', $this->env, $this->user); } } if (!$this->user->getAccount()->getSetting(Scalr_Account::SETTING_DATE_ENV_CONFIGURED)) { $this->user->getAccount()->setSetting(Scalr_Account::SETTING_DATE_ENV_CONFIGURED, time()); } //TODO: cloud suspension info must work with cloud credentials if ($enabled && $this->env->status == Scalr_Environment::STATUS_INACTIVE && $this->env->getPlatformConfigValue('system.auto-disable-reason')) { // env was inactive due invalid keys for amazon, activate it $this->env->status = Scalr_Environment::STATUS_ACTIVE; $this->env->save(); $this->env->setPlatformConfig(['system.auto-disable-reason' => NULL]); $envAutoEnabled = true; } $this->db->CommitTrans(); } catch (Exception $e) { $this->db->RollbackTrans(); throw new Exception(_("Failed to save AWS settings: {$e->getMessage()}")); } $this->response->success('Cloud credentials have been ' . ($enabled ? 'saved' : 'removed from Scalr')); $this->response->data(['enabled' => $enabled, 'demoFarm' => $demoFarm, 'envAutoEnabled' => $envAutoEnabled]); }
/** * Constructor * * @param \Scalr\Service\Aws $aws AWS Instance for the specified region and * that is associated with this service. * @throws ElbException */ public function __construct(\Scalr\Service\Aws $aws) { $this->aws = $aws; $this->class = get_class($this); $this->setApiVersion($this->getCurrentApiVersion()); $this->em = $aws->getEntityManager(); }
/** * Calls Amazon web service method. * * It ensures execution of the certain AWS action by transporting the request * and receiving response. * * @param string $action REST METHOD (GET|PUT|DELETE|HEAD etc). * @param array $options An options array. It's used to send headers for the HTTP request. * extra options: _subdomain, _putData, _putFile * @param string $path optional A relative path. * @return ClientResponseInterface * @throws ClientException */ public function call($action, $options, $path = '/') { $httpRequest = $this->createRequest(); $httpMethod = $action ?: 'GET'; if (substr($path, 0, 1) !== '/') { $path = '/' . $path; } $this->lastApiCall = null; $eventObserver = $this->getAws()->getEventObserver(); if ($eventObserver && $eventObserver->isSubscribed(EventType::EVENT_SEND_REQUEST)) { foreach (debug_backtrace() as $arr) { if (empty($arr['class']) || !preg_match("/\\\\Service\\\\Aws\\\\.+Api\$/", $arr['class']) || $arr['type'] !== '->') { continue; } $this->lastApiCall = ucfirst($arr['function']); break; } } //Wipes out extra options from headers and moves them to separate array. //It also collects an x-amz headers. $extraOptions = ['region' => null]; foreach ($options as $key => $val) { if (substr($key, 0, 1) === '_') { $extraOptions[substr($key, 1)] = $val; unset($options[$key]); } } if (!isset($options['Host'])) { $options['Host'] = (isset($extraOptions['subdomain']) ? $extraOptions['subdomain'] . '.' : '') . $this->url; } if (strpos($options['Host'], 'http') === 0) { $arr = parse_url($options['Host']); $scheme = $arr['scheme']; $options['Host'] = $arr['host'] . (isset($arr['port']) ? ':' . $arr['port'] : ''); $path = (!empty($arr['path']) && $arr['path'] != '/' ? rtrim($arr['path'], '/') : '') . $path; } else { $scheme = 'https'; } if ($httpMethod === 'PUT' || $httpMethod === 'POST') { if (!empty($extraOptions['putData'])) { $httpRequest->append($extraOptions['putData']); $options['Content-Md5'] = Aws::getMd5Base64Digest($extraOptions['putData']); } elseif (!empty($extraOptions['putFile'])) { $httpRequest->addFiles([$extraOptions['putFile']]); $options['Content-Md5'] = Aws::getMd5Base64DigestFile($extraOptions['putFile']); } } $httpRequest->setRequestUrl($scheme . '://' . $options['Host'] . $path); $httpRequest->setRequestMethod($httpMethod); $httpRequest->addHeaders($options); if (true) { $this->signRequestV4($httpRequest, !empty($extraOptions['region']) ? $extraOptions['region'] : null, empty($extraOptions['putFile']) ? null : $extraOptions['putFile']); } else { $this->signRequestV3($httpRequest, isset($extraOptions['subdomain']) ? $extraOptions['subdomain'] : null); } $response = $this->tryCall($httpRequest); if ($this->getAws() && $this->getAws()->getDebug()) { echo "\n", "{$httpRequest}\n", "{$response->getResponse()}\n"; } return $response; }
/** * {@inheritdoc} * @see ApiEntityAdapter::validateEntity() */ public function validateEntity($entity) { if (!$entity instanceof Farm) { throw new InvalidArgumentException(sprintf("First argument must be instance of Scalr\\Model\\Entity\\Farm class")); } if ($entity->id !== null) { if (!Farm::findPk($entity->id)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find out the Farm with ID: %d", $entity->id)); } } else { if (empty($entity->name)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property name"); } $criteria = $this->controller->getScopeCriteria(); $criteria[] = ['name' => $entity->name]; if (count(Farm::find($criteria))) { throw new ApiErrorException(409, ErrorMessage::ERR_UNICITY_VIOLATION, "Farm with name '{$entity->name}' already exists"); } } if (!empty($entity->settings[FarmSetting::EC2_VPC_REGION])) { $region = $entity->settings[FarmSetting::EC2_VPC_REGION]; $vpcId = $entity->settings[FarmSetting::EC2_VPC_ID]; if (!in_array($region, Aws::getCloudLocations())) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Unknown VPC region"); } $gov = new Scalr_Governance($this->controller->getEnvironment()->id); $vpcGovernanceRegions = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_VPC, 'regions'); if (isset($vpcGovernanceRegions)) { if (!array_key_exists($region, $vpcGovernanceRegions)) { $regions = array_keys($vpcGovernanceRegions); throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($regions) > 1 ? [implode(', ', $regions), 'regions are'] : [array_shift($regions), 'region is'])); } $vpcGovernanceIds = $vpcGovernanceRegions[$region]['ids']; if (!empty($vpcGovernanceIds) && !in_array($vpcId, $vpcGovernanceIds)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($vpcGovernanceIds) > 1 ? [implode(', ', $vpcGovernanceIds), 'vpcs are'] : [array_shift($vpcGovernanceIds), 'vpc is'])); } } $found = null; /* @var $vpc VpcData */ //TODO rewrite aws service usage foreach ($this->controller->getContainer()->aws($region, $this->controller->getEnvironment())->ec2->vpc->describe() as $vpc) { if ($vpcId == $vpc->vpcId) { $found = $vpc; } } if (empty($found)) { throw new ApiErrorException(400, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Could not find out the VPC with ID '{$vpcId}' in region '{$region}'"); } } else { if (!empty($entity->settings[FarmSetting::EC2_VPC_ID])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property vpc.region"); } } if (\Scalr::config('scalr.analytics.enabled')) { if (isset($entity->settings[FarmSetting::PROJECT_ID])) { if (!$this->controller->getContainer()->analytics->projects->get($entity->settings[FarmSetting::PROJECT_ID])) { throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "The project is not allowed for you"); } } else { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property project"); } } if (!$this->controller->hasPermissions($entity, true)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } }
/** * @test */ public function testImagesFunctional() { $testName = str_replace('-', '', $this->getTestName()); $images = null; $uri = self::getUserApiUrl('/images'); do { $query = []; if (isset($images->pagination->next)) { $parts = parse_url($images->pagination->next); parse_str($parts['query'], $query); } $describe = $this->request($uri, Request::METHOD_GET, $query); $this->assertDescribeResponseNotEmpty($describe); $images = $describe->getBody(); foreach ($images->data as $image) { $this->assertImageObjectNotEmpty($image); if (strpos($image->name, $testName) !== false) { $delete = $this->request($uri . '/' . $image->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); } } } while (!empty($images->pagination->next)); // test create action $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => 'invalid']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid scope'); $create = $this->request($uri, Request::METHOD_POST); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Invalid body'); $create = $this->request($uri, Request::METHOD_POST, [], ['invalid' => 'value']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'You are trying to set'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => 'invalidName^$&&']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid name of the Image'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'architecture' => 'invalid', 'name' => $testName]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid architecture of the Image'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'OS must be provided with the request'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['id' => 'invalidOsId']]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Specified OS does not exist'); $os = Os::findOne([['status' => Os::STATUS_ACTIVE]]); /* @var $os Os */ $env = \Scalr_Environment::init()->loadById(static::$testEnvId); $platform = \SERVER_PLATFORMS::EC2; if ($env->isPlatformEnabled($platform)) { $env->setPlatformConfig([$platform . '.is_enabled' => 0]); } $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['id' => $os->id], 'cloudPlatform' => $platform]); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $create); $this->assertErrorMessageStatusEquals(400, $create); $env->setPlatformConfig([$platform . '.is_enabled' => 1]); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['invalid'], 'cloudPlatform' => $platform]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid identifier of the OS'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['id' => $os->id], 'cloudPlatform' => $platform]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Unable to find the requested image on the cloud'); $region = null; $cloudImageId = null; foreach (Aws::getCloudLocations() as $cloudLocation) { $cloudImageId = $this->getNewImageId($env, $cloudLocation); if (!empty($cloudImageId)) { $region = $cloudLocation; break; } } $this->assertNotNull($cloudImageId); $this->assertNotNull($cloudLocation); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => $os->id, 'cloudPlatform' => $platform, 'cloudLocation' => $region, 'cloudImageId' => $cloudImageId]); $this->assertFetchResponseNotEmpty($create); $imageBody = $create->getBody(); $this->assertImageObjectNotEmpty($imageBody->data); $this->assertEquals(201, $create->response->getStatus()); $this->assertNotEmpty($imageBody->data->id); $this->assertEquals(ScopeInterface::SCOPE_ENVIRONMENT, $imageBody->data->scope); $this->assertEquals($testName, $imageBody->data->name); $this->assertEquals($os->id, $imageBody->data->os->id); $this->assertEquals($platform, $imageBody->data->cloudPlatform); $this->assertEquals($region, $imageBody->data->cloudLocation); $this->assertEquals($cloudImageId, $imageBody->data->cloudImageId); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['id' => $os->id], 'cloudPlatform' => $platform, 'cloudLocation' => $region, 'cloudImageId' => $cloudImageId]); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_UNICITY_VIOLATION, $create); $this->assertErrorMessageStatusEquals(409, $create); // test filtering $describe = $this->request($uri, Request::METHOD_GET, ['scope' => ScopeInterface::SCOPE_ENVIRONMENT]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals(ScopeInterface::SCOPE_ENVIRONMENT, $data->scope); } $describe = $this->request($uri, Request::METHOD_GET, ['name' => $testName]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals($testName, $data->name); } $describe = $this->request($uri, Request::METHOD_GET, ['id' => $imageBody->data->id]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals($imageBody->data->id, $data->id); } $describe = $this->request($uri, Request::METHOD_GET, ['os' => $os->id]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals($os->id, $data->os->id); } $describe = $this->request($uri, Request::METHOD_GET, ['os' => 'invalid*&^^%']); $this->assertErrorMessageContains($describe, 400, ErrorMessage::ERR_INVALID_VALUE, "Invalid identifier of the OS"); $describe = $this->request($uri, Request::METHOD_GET, ['cloudPlatform' => $platform, 'cloudLocation' => $region]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals($platform, $data->cloudPlatform); $this->assertEquals($region, $data->cloudLocation); } $describe = $this->request($uri, Request::METHOD_GET, ['cloudLocation' => $region]); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_STRUCTURE, $describe); $this->assertErrorMessageStatusEquals(400, $describe); $describe = $this->request($uri, Request::METHOD_GET, ['cloudImageId' => $cloudImageId]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertImageObjectNotEmpty($data); $this->assertEquals($cloudImageId, $data->cloudImageId); } // test modify action $modify = $this->request($uri, Request::METHOD_PATCH, [], ['name' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_ENDPOINT_NOT_FOUND, $modify); $this->assertErrorMessageStatusEquals(404, $modify); $modify = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_PATCH, [], ['invalid' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_STRUCTURE, $modify); $this->assertErrorMessageStatusEquals(400, $modify); $modify = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_PATCH, [], ['id' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_STRUCTURE, $modify); $this->assertErrorMessageStatusEquals(400, $modify); $modify = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_PATCH, [], ['scope' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $modify); $this->assertErrorMessageStatusEquals(400, $modify); $notFoundId = '11111111-1111-1111-1111-111111111111'; $modify = $this->request($uri . '/' . $notFoundId, Request::METHOD_PATCH, [], ['name' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_OBJECT_NOT_FOUND, $modify); $this->assertErrorMessageStatusEquals(404, $modify); $entity = Image::findOne([['envId' => null], ['status' => Image::STATUS_ACTIVE]]); /* @var $entity Image */ $this->assertNotEmpty($entity); $notAccessibleId = $entity->hash; $modify = $this->request($uri . '/' . $notAccessibleId, Request::METHOD_PATCH, [], ['name' => $testName . 'modify']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $modify); $this->assertErrorMessageStatusEquals(403, $modify); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ENVIRONMENT, 'name' => $testName, 'os' => ['id' => $entity->osId], 'cloudPlatform' => $entity->platform, 'cloudLocation' => $entity->cloudLocation, 'cloudImageId' => $entity->id]); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_UNICITY_VIOLATION, $create); $this->assertErrorMessageStatusEquals(409, $create); // test fetch action $fetch = $this->request($uri . '/' . $notFoundId, Request::METHOD_GET); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_OBJECT_NOT_FOUND, $fetch); $this->assertErrorMessageStatusEquals(404, $fetch); $fetch = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_GET); $this->assertFetchResponseNotEmpty($fetch); $fetchBody = $fetch->getBody(); $this->assertImageObjectNotEmpty($fetchBody->data); $this->assertEquals($imageBody->data->id, $fetchBody->data->id); $fetch = $this->request($uri . '/' . $entity->hash, Request::METHOD_GET); $this->assertFetchResponseNotEmpty($fetch); $fetchBody = $fetch->getBody(); $this->assertImageObjectNotEmpty($fetchBody->data); $this->assertEquals($entity->hash, $fetchBody->data->id); $modify = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_PATCH, [], ['name' => $testName . 'modify']); $this->assertEquals(200, $modify->response->getStatus()); $this->assertImageObjectNotEmpty($modify->getBody()->data); $this->assertEquals($testName . 'modify', $modify->getBody()->data->name); // test copy action $copy = $this->request($uri . '/' . $imageBody->data->id . '/actions/copy', Request::METHOD_POST); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_STRUCTURE, $copy); $this->assertErrorMessageStatusEquals(400, $copy); $copy = $this->request($uri . '/' . $imageBody->data->id . '/actions/copy', Request::METHOD_POST, [], ['cloudLocation' => 'invalid', 'cloudPlatform' => 'ec2']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $copy); $this->assertErrorMessageStatusEquals(400, $copy); $copy = $this->request($uri . '/' . $imageBody->data->id . '/actions/copy', Request::METHOD_POST, [], ['cloudLocation' => Aws::REGION_US_EAST_1, 'cloudPlatform' => 'gce']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $copy); $this->assertErrorMessageStatusEquals(400, $copy); $copy = $this->request($uri . '/' . $imageBody->data->id . '/actions/copy', Request::METHOD_POST, [], ['cloudLocation' => $region, 'cloudPlatform' => 'ec2']); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_BAD_REQUEST, $copy); $this->assertErrorMessageStatusEquals(400, $copy); $awsRegions = Aws::getCloudLocations(); $copyTo = null; foreach ($awsRegions as $awsRegion) { if ($awsRegion != $region) { $copyTo = $awsRegion; break; } } $this->assertNotNull($copyTo); $copy = $this->request($uri . '/' . $notAccessibleId . '/actions/copy', Request::METHOD_POST, [], ['cloudLocation' => $copyTo, 'cloudPlatform' => \SERVER_PLATFORMS::EC2]); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $copy); $this->assertErrorMessageStatusEquals(403, $copy); $copy = $this->request($uri . '/' . $imageBody->data->id . '/actions/copy', Request::METHOD_POST, [], ['cloudLocation' => $copyTo, 'cloudPlatform' => \SERVER_PLATFORMS::EC2]); $copyBody = $copy->getBody(); $this->assertEquals(202, $copy->response->getStatus()); $this->assertFetchResponseNotEmpty($copy); $this->assertImageObjectNotEmpty($copyBody->data); $this->assertEquals(\SERVER_PLATFORMS::EC2, $copyBody->data->cloudPlatform); $this->assertEquals($copyTo, $copyBody->data->cloudLocation); // test delete action $delete = $this->request($uri . '/' . $notFoundId, Request::METHOD_DELETE); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_OBJECT_NOT_FOUND, $delete); $this->assertErrorMessageStatusEquals(404, $delete); $delete = $this->request($uri . '/' . $entity->hash, Request::METHOD_DELETE); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $delete); $this->assertErrorMessageStatusEquals(403, $delete); $delete = $this->request($uri . '/' . $copyBody->data->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); $delete = $this->request($uri . '/' . $imageBody->data->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); }
/** * Get db instance details * * @param Aws $aws * @param DBInstanceData $dbinstance * @param SecurityGroupList|null $vpcSglist optional * @param DBClusterList|null $clusters optional * @return mixed * @throws Scalr_Exception_Core */ private function getDbInstanceData(Aws $aws, DBInstanceData $dbinstance, $vpcSglist = null, $clusters = null) { $cloudLocation = $aws->getRegion(); $createdTime = $dbinstance->instanceCreateTime; $dbinstance = $dbinstance->toArray(true); foreach ($dbinstance['VpcSecurityGroups'] as &$vpcSg) { $vpcSecurityGroupName = null; if (isset($vpcSglist)) { foreach ($vpcSglist as $vpcSqData) { /* @var $vpcSqData \Scalr\Service\Aws\Ec2\DataType\SecurityGroupData */ if ($vpcSqData->groupId == $vpcSg['VpcSecurityGroupId']) { $vpcSecurityGroupName = $vpcSqData->groupName; $vpcId = $vpcSqData->vpcId; break; } } } $vpcSg = ['vpcSecurityGroupId' => $vpcSg['VpcSecurityGroupId'], 'vpcSecurityGroupName' => $vpcSecurityGroupName]; } $dbinstance['VpcId'] = !empty($vpcId) ? $vpcId : null; $dbinstance['DBSubnetGroupName'] = isset($dbinstance['DBSubnetGroup']['DBSubnetGroupName']) ? $dbinstance['DBSubnetGroup']['DBSubnetGroupName'] : null; foreach ($dbinstance['DBSecurityGroups'] as &$dbSg) { $dbSg = $dbSg['DBSecurityGroupName']; } foreach ($dbinstance['OptionGroupMembership'] as &$member) { $dbinstance['OptionGroupName'] = $member['OptionGroupName']; break; } foreach ($dbinstance['DBParameterGroups'] as &$param) { $dbinstance['DBParameterGroup'] = $param['DBParameterGroupName']; break; } $dbinstance['Address'] = $dbinstance['Endpoint']['Address']; $dbinstance['Port'] = (string) $dbinstance['Endpoint']['Port']; $dbinstance['InstanceCreateTime'] = Scalr_Util_DateTime::convertTz($createdTime); $dbinstance['AllocatedStorage'] = (string) $dbinstance['AllocatedStorage']; if ($dbinstance['StorageEncrypted']) { /* @var $key Aws\Kms\DataType\AliasData */ foreach ($aws->kms->alias->list() as $key) { if (str_replace($key->aliasName, "key/{$key->targetKeyId}", $key->aliasArn) == $dbinstance['KmsKeyId']) { $dbinstance['KmsKeyId'] = $key->aliasName; break; } } } if (!empty($dbinstance['DBClusterIdentifier']) && isset($clusters)) { foreach ($clusters as $cluster) { /* @var $cluster DBClusterData */ if ($cluster->dBClusterIdentifier == $dbinstance['DBClusterIdentifier']) { foreach ($cluster->dBClusterMembers as $member) { /* @var $member ClusterMemberData */ if ($dbinstance['DBInstanceIdentifier'] == $member->dBInstanceIdentifier) { $dbinstance['isReplica'] = !$member->isClusterWriter; break; } } $dbinstance['MultiAZ'] = true; break; } } } else { $dbinstance['isReplica'] = !empty($dbinstance['ReadReplicaSourceDBInstanceIdentifier']) ? true : false; $dbinstance['MultiAZ'] = $dbinstance['MultiAZ']; } /* @var $cloudResource CloudResource */ $cloudResource = CloudResource::findPk($dbinstance['DBInstanceIdentifier'], CloudResource::TYPE_AWS_RDS, $this->getEnvironmentId(), \SERVER_PLATFORMS::EC2, $cloudLocation); if ($cloudResource) { $dbinstance['farmId'] = $cloudResource->farmId; $dbinstance['farmName'] = $this->db->GetOne("SELECT name FROM farms WHERE id=? LIMIT 1", [$cloudResource->farmId]); } return $dbinstance; }
/** * {@inheritdoc} * @see ApiEntityAdapter::validateEntity() */ public function validateEntity($entity) { if (!$entity instanceof FarmRole) { throw new InvalidArgumentException(sprintf("First argument must be instance of Scalr\\Model\\Entity\\FarmRole class")); } if ($entity->id !== null) { if (!FarmRole::findPk($entity->id)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find out the Farm with ID: %d", $entity->id)); } } if (empty($entity->farmId)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property farm.id"); } else { /* @var $farm Farm */ $farm = $this->controller->getFarm($entity->farmId, true); } if (empty($entity->alias)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property alias"); } if (!preg_match("/^[[:alnum:]](?:-*[[:alnum:]])*\$/", $entity->alias)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Alias should start and end with letter or number and contain only letters, numbers and dashes."); } if (empty($entity->roleId)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property role.id"); } $roleBehaviors = $entity->getRole()->getBehaviors(); $uniqueBehaviors = array_intersect($roleBehaviors, array_merge(...array_values(static::$uniqueFarmBehaviors))); if (!empty($uniqueBehaviors)) { //farm can include only one mysql or percona role if (array_intersect($uniqueBehaviors, static::$uniqueFarmBehaviors[ROLE_BEHAVIORS::MYSQL])) { $uniqueBehaviors = array_merge($uniqueBehaviors, array_diff(static::$uniqueFarmBehaviors[ROLE_BEHAVIORS::MYSQL], $uniqueBehaviors)); } $farmRoleEntity = new FarmRole(); $roleEntity = new Role(); /* @var $conflicts EntityIterator */ $conflicts = Role::find([AbstractEntity::STMT_FROM => "{$roleEntity->table()} JOIN {$farmRoleEntity->table('fr')} ON {$farmRoleEntity->columnRoleId('fr')} = {$roleEntity->columnId()}", AbstractEntity::STMT_WHERE => "{$farmRoleEntity->columnFarmId('fr')} = {$farmRoleEntity->qstr('farmId', $entity->farmId)} " . (empty($entity->id) ? '' : " AND {$farmRoleEntity->columnId('fr')} <> {$farmRoleEntity->qstr('id', $entity->id)}"), ['behaviors' => ['$regex' => implode('|', $uniqueBehaviors)]]]); if ($conflicts->count() > 0) { $conflictedBehaviors = []; /* @var $role Role */ foreach ($conflicts as $role) { $conflictedBehaviors = array_merge($conflictedBehaviors, array_intersect($uniqueBehaviors, $role->getBehaviors())); } if (!empty(array_intersect($conflictedBehaviors, static::$uniqueFarmBehaviors[ROLE_BEHAVIORS::MYSQL]))) { $conflictedBehaviors = array_diff($conflictedBehaviors, static::$uniqueFarmBehaviors[ROLE_BEHAVIORS::MYSQL]); $conflictedBehaviors[] = 'mysql/percona'; } $conflictedBehaviors = RoleAdapter::behaviorsToData($conflictedBehaviors); throw new ApiErrorException(409, ErrorMessage::ERR_UNICITY_VIOLATION, sprintf('Only one [%s] role can be added to farm', implode(', ', $conflictedBehaviors))); } } if (empty($entity->platform)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property platform"); } switch ($entity->platform) { case SERVER_PLATFORMS::EC2: if (empty($entity->settings[FarmRoleSetting::INSTANCE_TYPE])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Missed property instance.type"); } /* @var $platform Ec2PlatformModule */ $platform = PlatformFactory::NewPlatform(SERVER_PLATFORMS::EC2); if (!in_array($entity->settings[FarmRoleSetting::INSTANCE_TYPE], $platform->getInstanceTypes())) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Wrong instance type"); } $gov = new Scalr_Governance($this->controller->getEnvironment()->id); $allowGovernanceIns = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::INSTANCE_TYPE); if (isset($allowGovernanceIns) && !in_array($entity->settings[FarmRoleSetting::INSTANCE_TYPE], $allowGovernanceIns)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($allowGovernanceIns) > 1 ? [implode(', ', $allowGovernanceIns), 'instances are'] : [array_shift($allowGovernanceIns), 'instance is'])); } if (!in_array($entity->cloudLocation, Aws::getCloudLocations())) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Unknown region"); } $vpcGovernanceRegions = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_VPC, 'regions'); if (isset($vpcGovernanceRegions) && !array_key_exists($entity->cloudLocation, $vpcGovernanceRegions)) { $regions = array_keys($vpcGovernanceRegions); throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($regions) > 1 ? [implode(', ', $regions), 'regions are'] : [array_shift($regions), 'region is'])); } $env = Scalr_Environment::init()->loadById($this->controller->getEnvironment()->id); $aws = $this->controller->getContainer()->aws($entity->cloudLocation, $env); if (!empty($entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE]) && $entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE] !== 'x-scalr-diff') { $availZones = explode(":", str_replace("x-scalr-custom=", '', $entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE])); $ec2availabilityZones = []; foreach ($aws->ec2->availabilityZone->describe() as $zone) { /* @var $zone AvailabilityZoneData */ if (stristr($zone->zoneState, 'available')) { $ec2availabilityZones[] = $zone->zoneName; } } $diffZones = array_diff($availZones, $ec2availabilityZones); if (!empty($diffZones)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf('%s %s available. Available zones are %s', ...count($diffZones) > 1 ? [implode(', ', $diffZones), 'zones are not', implode(', ', $ec2availabilityZones)] : [array_shift($diffZones), 'zone is not', implode(', ', $ec2availabilityZones)])); } } if (!empty($farm->settings[FarmSetting::EC2_VPC_ID])) { if (empty($entity->settings[FarmRoleSetting::AWS_VPC_SUBNET_ID])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "VPC Subnet(s) should be described"); } $vpcId = $farm->settings[FarmSetting::EC2_VPC_ID]; $subnets = $platform->listSubnets($env, $entity->cloudLocation, $vpcId, true); $vpcGovernanceIds = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_VPC, 'ids'); $subnetType = null; foreach (json_decode($entity->settings[FarmRoleSetting::AWS_VPC_SUBNET_ID]) as $subnetId) { $found = false; foreach ($subnets as $subnet) { if ($subnet['id'] == $subnetId) { if ($subnetType == null) { $subnetType = $subnet['type']; } else { if ($subnet['type'] != $subnetType) { throw new ApiErrorException(409, ErrorMessage::ERR_UNICITY_VIOLATION, "All subnets must be a same type"); } } //check governance subnet settings if (isset($vpcGovernanceIds[$vpcId])) { if (!empty($vpcGovernanceIds[$vpcId]) && is_array($vpcGovernanceIds[$vpcId]) && !in_array($subnetId, $vpcGovernanceIds[$vpcId])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed by governance settings", ...count($vpcGovernanceIds[$vpcId]) > 1 ? [implode(', ', $vpcGovernanceIds[$vpcId]), 'subnets are'] : [array_shift($vpcGovernanceIds[$vpcId]), 'subnet is'])); } else { if ($vpcGovernanceIds[$vpcId] == "outbound-only" && $subnetType != 'private') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Only private subnets allowed by governance settings"); } else { if ($vpcGovernanceIds[$vpcId] == "full" && $subnetType != 'public') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Only public subnets allowed by governance settings"); } } } } $found = true; } } if (!$found) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Subnet with id '{$subnetId}' not found"); } } if (!empty($entity->settings[Scalr_Role_Behavior_Router::ROLE_VPC_SCALR_ROUTER_ID])) { $router = $this->controller->getFarmRole($entity->settings[Scalr_Role_Behavior_Router::ROLE_VPC_SCALR_ROUTER_ID]); if (empty($router->settings[Scalr_Role_Behavior_Router::ROLE_VPC_NID])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Farm-role with id '{$router->id}' is not a valid router"); } } else { if (\Scalr::config('scalr.instances_connection_policy') != 'local' && $subnetType == 'private') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "You must describe a VPC Router"); } } } break; default: if (isset(SERVER_PLATFORMS::GetList()[$entity->platform])) { throw new ApiErrorException(501, ErrorMessage::ERR_NOT_IMPLEMENTED, "Platform '{$entity->platform}' is not supported yet"); } else { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Unknown platform '{$entity->platform}'"); } } if (!$this->controller->hasPermissions($entity, true)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } }
/** * @test */ public function testAccountRolesFunctional() { $db = \Scalr::getDb(); $testName = str_replace('-', '', static::getTestName()); $roles = null; $uri = self::getAccountApiUrl('/roles'); do { $query = []; if (isset($roles->pagination->next)) { $parts = parse_url($roles->pagination->next); parse_str($parts['query'], $query); } $describe = $this->request($uri, Request::METHOD_GET, $query); $this->assertDescribeResponseNotEmpty($describe); $this->assertNotEmpty($describe->getBody()); $roles = $describe->getBody(); foreach ($roles->data as $role) { $this->assertRolesObjectNotEmpty($role); if ($role->name == $testName) { $delete = $this->request($uri . '/' . $role->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->status); } } } while (!empty($roles->pagination->next)); // test create action $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => 'invalid']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid scope'); $create = $this->request($uri, Request::METHOD_POST); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Invalid body'); $create = $this->request($uri, Request::METHOD_POST, [], ['invalid' => 'value']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'You are trying to set'); $create = $this->request($uri, Request::METHOD_POST, [], ['id' => 'value']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid name'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => 'invalidName^$&&']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid name of the Role'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'description' => 'invalidDesc<br/>']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid description'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Role category should be provided'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'category' => ['id' => 'not int']]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid identifier of the category'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'category' => ['id' => -1]]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'The Role category does not exist'); $rolesCat = RoleCategory::findOne(); /* @var $rolesCat RoleCategory */ $this->assertNotEmpty($rolesCat); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'category' => ['id' => $rolesCat->id]]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property 'os.id'"); $os = Os::findOne([['status' => Os::STATUS_ACTIVE], ['family' => 'ubuntu'], ['generation' => '12.04']]); /* @var $os Os */ $this->assertNotEmpty($os); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'category' => ['id' => $rolesCat->id], 'os' => ['id' => -1]]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Invalid identifier of the OS'); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'category' => ['id' => $rolesCat->id], 'os' => ['id' => 'invalid']]); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, "OS with id 'invalid' not found."); $create = $this->request($uri, Request::METHOD_POST, [], ['scope' => ScopeInterface::SCOPE_ACCOUNT, 'name' => $testName, 'description' => $testName, 'category' => $rolesCat->id, 'os' => $os->id, 'quickStart' => true, 'deprecated' => true]); $body = $create->getBody(); $this->assertEquals(201, $create->response->getStatus()); $this->assertFetchResponseNotEmpty($create); $this->assertRolesObjectNotEmpty($body->data); $this->assertNotEmpty($body->data->id); $this->assertEquals($testName, $body->data->name); $this->assertEquals($testName, $body->data->description); $this->assertEquals(ScopeInterface::SCOPE_ACCOUNT, $body->data->scope); $this->assertEquals($rolesCat->id, $body->data->category->id); $this->assertEquals($os->id, $body->data->os->id); $this->assertEquals(true, $body->data->quickStart); $this->assertEquals(true, $body->data->deprecated); // test images actions $roleId = $body->data->id; $imagesUri = $uri . '/' . $roleId . '/images'; $images = null; do { $query = []; if (isset($images->pagination->next)) { $parts = parse_url($images->pagination->next); parse_str($parts['query'], $query); } $describeImages = $this->request($imagesUri, Request::METHOD_GET, $query); $this->assertDescribeResponseNotEmpty($describeImages); $images = $describeImages->getBody(); foreach ($images->data as $imageRole) { $this->assertRoleImageObjectNotEmpty($imageRole); $this->assertEquals($roleId, $imageRole->role->id); $image = Image::findPk($imageRole->image->id); /* @var $image Image */ if ($image->name == $testName) { $delete = $this->request($imagesUri . '/' . $imageRole->image->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->status); } } } while (!empty($images->pagination->next)); $env = \Scalr_Environment::init()->loadById(static::$testEnvId); $platform = \SERVER_PLATFORMS::EC2; if (!$env->isPlatformEnabled($platform)) { $env->setPlatformConfig([$platform . '.is_enabled' => 1]); } $region = null; $cloudImageId = null; foreach (Aws::getCloudLocations() as $cloudLocation) { $cloudImageId = $this->getNewImageId($env, $cloudLocation); if (!empty($cloudImageId)) { $region = $cloudLocation; break; } } $this->assertNotNull($cloudImageId); $this->assertNotNull($cloudLocation); $image = $this->createEntity(new Image(), ['accountId' => $this->getUser()->accountId, 'name' => $testName, 'osId' => $os->id, 'platform' => $platform, 'cloudLocation' => $region, 'id' => $cloudImageId, 'architecture' => 'x86_64', 'source' => Image::SOURCE_MANUAL, 'status' => Image::STATUS_ACTIVE]); $createRoleImage = $this->request($imagesUri, Request::METHOD_POST, [], ['role' => ['id' => $roleId + 10], 'image' => ['id' => $image->hash]]); $this->assertErrorMessageStatusEquals(400, $createRoleImage); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $createRoleImage); $createRoleImage = $this->request($imagesUri, Request::METHOD_POST, [], ['role' => ['id' => $roleId]]); $this->assertErrorMessageStatusEquals(400, $createRoleImage); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_STRUCTURE, $createRoleImage); $createRoleImage = $this->request($imagesUri, Request::METHOD_POST, [], ['role' => ['id' => $roleId], 'image' => ['id' => '11111111-1111-1111-1111-111111111111']]); $this->assertErrorMessageStatusEquals(404, $createRoleImage); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_INVALID_VALUE, $createRoleImage); $createRoleImage = $this->request($imagesUri, Request::METHOD_POST, [], ['role' => ['id' => $roleId], 'image' => ['id' => $image->hash]]); $createRoleImageBody = $createRoleImage->getBody(); $this->assertEquals(201, $createRoleImage->response->getStatus()); $this->assertFetchResponseNotEmpty($createRoleImage); $this->assertRoleImageObjectNotEmpty($createRoleImageBody->data); $createRoleImageError = $this->request($imagesUri, Request::METHOD_POST, [], ['role' => ['id' => $roleId], 'image' => ['id' => $image->hash]]); $this->assertErrorMessageStatusEquals(400, $createRoleImageError); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_BAD_REQUEST, $createRoleImageError); $fetchImage = $this->request($imagesUri . '/' . $createRoleImageBody->data->image->id, Request::METHOD_GET); $fetchImageBody = $fetchImage->getBody(); $this->assertEquals(200, $fetchImage->response->getStatus()); $this->assertFetchResponseNotEmpty($fetchImage); $this->assertImageObjectNotEmpty($fetchImageBody->data); $this->assertEquals($cloudImageId, $fetchImageBody->data->cloudImageId); $this->assertEquals($testName, $fetchImageBody->data->name); // test role images filtering $describeRoleImages = $this->request($imagesUri, Request::METHOD_GET, ['role' => $roleId]); $this->assertDescribeResponseNotEmpty($describeRoleImages); foreach ($describeRoleImages->getBody()->data as $data) { $this->assertRoleImageObjectNotEmpty($data); $this->assertEquals($roleId, $data->role->id); } $describeRoleImages = $this->request($imagesUri, Request::METHOD_GET, ['image' => $image->hash]); $this->assertDescribeResponseNotEmpty($describeRoleImages); foreach ($describeRoleImages->getBody()->data as $data) { $this->assertRoleImageObjectNotEmpty($data); $this->assertEquals($image->hash, $data->image->id); } $describeRoleImages = $this->request($imagesUri, Request::METHOD_GET, ['invalid' => 'value']); $this->assertErrorMessageContains($describeRoleImages, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Unsupported filter'); $currentRole = Role::findPk($roleId); /* @var $currentRole Role */ $this->assertNotEmpty($currentRole); $adminImages = Image::find([['envId' => null], ['status' => Image::STATUS_ACTIVE], ['cloudLocation' => $region]]); $this->assertNotEmpty($adminImages); $adminImage = null; foreach ($adminImages as $aImage) { /* @var $aImage Image */ $imageOs = $aImage->getOs(); if (!empty($imageOs) && $imageOs->generation == $currentRole->getOs()->generation && $imageOs->family == $currentRole->getOs()->family) { $adminImage = $aImage; break; } } /* @var $adminImage Image */ $this->assertNotEmpty($adminImage); $this->assertNotEquals($createRoleImageBody->data->image->id, $adminImage->hash); $replaceImage = $this->request($imagesUri . '/' . $createRoleImageBody->data->image->id . '/actions/replace', Request::METHOD_POST, [], ['role' => $roleId, 'image' => $adminImage->hash]); $replaceImageBody = $replaceImage->getBody(); $this->assertEquals(200, $replaceImage->response->getStatus()); $this->assertFetchResponseNotEmpty($replaceImage); $this->assertRoleImageObjectNotEmpty($replaceImageBody->data); $this->assertEquals($adminImage->hash, $replaceImageBody->data->image->id); $deleteImage = $this->request($imagesUri . '/' . $replaceImageBody->data->image->id, Request::METHOD_DELETE); $this->assertEquals(200, $deleteImage->response->getStatus()); $delete = $this->request(static::getAccountApiUrl("images/{$image->hash}"), Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); // test get action $notFoundRoleId = 10 + $db->GetOne("SELECT MAX(r.id) FROM roles r"); $get = $this->request($uri . '/' . $notFoundRoleId, Request::METHOD_GET); $this->assertErrorMessageContains($get, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "The Role either does not exist or isn't in scope for the current Environment"); $get = $this->request($uri . '/' . $body->data->id, Request::METHOD_GET); $getBody = $get->getBody(); $this->assertEquals(200, $get->response->getStatus()); $this->assertFetchResponseNotEmpty($get); $this->assertRolesObjectNotEmpty($getBody->data); $this->assertEquals($body->data->id, $getBody->data->id); $this->assertEquals($testName, $getBody->data->name); $this->assertEquals($testName, $getBody->data->description); $this->assertEquals(ScopeInterface::SCOPE_ACCOUNT, $getBody->data->scope); $this->assertEquals($rolesCat->id, $getBody->data->category->id); $this->assertEquals($os->id, $getBody->data->os->id); // test filters $describe = $this->request($uri, Request::METHOD_GET, ['description' => $testName]); $this->assertErrorMessageContains($describe, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Unsupported filter'); $describe = $this->request($uri, Request::METHOD_GET, ['scope' => 'wrong<br>']); $this->assertErrorMessageContains($describe, 400, ErrorMessage::ERR_INVALID_VALUE, 'Unexpected scope value'); $describe = $this->request($uri, Request::METHOD_GET, ['scope' => ScopeInterface::SCOPE_SCALR]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); } $describe = $this->request($uri, Request::METHOD_GET, ['scope' => ScopeInterface::SCOPE_ACCOUNT]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); $this->assertEquals(ScopeInterface::SCOPE_ACCOUNT, $data->scope); } $describe = $this->request($uri, Request::METHOD_GET, ['name' => $testName]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); $this->assertEquals($testName, $data->name); } $describe = $this->request($uri, Request::METHOD_GET, ['id' => $roleId]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); $this->assertEquals($roleId, $data->id); } $describe = $this->request($uri, Request::METHOD_GET, ['os' => $os->id]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); $this->assertEquals($os->id, $data->os->id); } $describe = $this->request($uri, Request::METHOD_GET, ['os' => 'invalid*&^^%']); $this->assertErrorMessageContains($describe, 400, ErrorMessage::ERR_INVALID_VALUE, "Invalid identifier of the OS"); $describe = $this->request($uri, Request::METHOD_GET, ['category' => $rolesCat->id]); $this->assertDescribeResponseNotEmpty($describe); foreach ($describe->getBody()->data as $data) { $this->assertRolesObjectNotEmpty($data); $this->assertEquals($rolesCat->id, $data->category->id); } $describe = $this->request($uri, Request::METHOD_GET, ['category' => '']); $this->assertErrorMessageContains($describe, 400, ErrorMessage::ERR_INVALID_VALUE, "Invalid identifier of the category"); // test modify action $modify = $this->request($uri . '/' . $body->data->id, Request::METHOD_PATCH); $this->assertErrorMessageContains($modify, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Invalid body'); $modify = $this->request($uri . '/' . $body->data->id, Request::METHOD_PATCH, [], ['id' => 123]); $this->assertErrorMessageContains($modify, 400, ErrorMessage::ERR_INVALID_STRUCTURE); $modify = $this->request($uri . '/' . $body->data->id, Request::METHOD_PATCH, [], ['invalid' => 'err']); $this->assertErrorMessageContains($modify, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'You are trying to set'); $modify = $this->request($uri . '/' . $body->data->id, Request::METHOD_PATCH, [], ['scope' => 'environment']); $this->assertErrorMessageContains($modify, 400, ErrorMessage::ERR_INVALID_VALUE); $modify = $this->request($uri . '/' . $body->data->id, Request::METHOD_PATCH, [], ['description' => '']); $modifyBody = $modify->getBody(); $this->assertEquals(200, $modify->response->getStatus()); $this->assertFetchResponseNotEmpty($modify); $this->assertRolesObjectNotEmpty($modifyBody->data); $this->assertEquals($body->data->id, $modifyBody->data->id); $this->assertEquals($testName, $modifyBody->data->name); $this->assertEquals('', $modifyBody->data->description); $this->assertEquals(ScopeInterface::SCOPE_ACCOUNT, $modifyBody->data->scope); $this->assertEquals($rolesCat->id, $modifyBody->data->category->id); $this->assertEquals($os->id, $modifyBody->data->os->id); // test delete action $delete = $this->request(static::getAccountApiUrl("/roles/{$notFoundRoleId}"), Request::METHOD_DELETE); $this->assertErrorMessageContains($delete, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); $delete = $this->request($uri . '/' . $body->data->id, Request::METHOD_DELETE); $this->assertEquals(200, $delete->status); $db->Execute("INSERT INTO roles SET\n name = ?,\n dtadded = NOW(),\n env_id\t = NULL,\n client_id = NULL,\n generation = 2\n ", [$testName]); $insertedId = $db->_insertid(); $db->Execute("INSERT INTO role_images SET\n role_id = ?,\n platform = 'ec2',\n image_id = 'test'\n ", [$insertedId]); $delete = $this->request($uri . '/' . $insertedId, Request::METHOD_DELETE); $db->Execute("DELETE FROM roles WHERE name = ? AND id = ?", [$testName, $insertedId]); $this->assertErrorMessageContains($delete, 403, ErrorMessage::ERR_SCOPE_VIOLATION); }
/** * @param string $name * @param string $cloudLocation * @return array */ protected function listS3Websites($name, $cloudLocation) { $result = []; $buckets = $this->environment->aws($cloudLocation)->s3->bucket->getWebsite($name); if ($buckets) { $location = $this->environment->aws($cloudLocation)->s3->bucket->getLocation($name); if (empty($location)) { $location = 'us-east-1'; } $zoneIds = Aws::getCloudLocationsZoneIds(); $result[] = array('domainName' => $name . 's3-website-' . $location . '.amazonaws.com', 'aliasZoneId' => isset($zoneIds[$location]) ? $zoneIds[$location] : null, 'title' => self::S3_ALIAS_TARGET_TITLE); } return $result; }
/** * Copies image to different location * * @param string $imageId Unique identifier of the image (uuid) * @return \Scalr\Api\DataType\ResultEnvelope * @throws ApiErrorException */ public function copyAction($imageId) { $this->checkScopedPermissions('IMAGES', 'MANAGE'); $object = $this->request->getJsonBody(); if (empty($object->cloudLocation) || empty($object->cloudPlatform)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Invalid body"); } $locations = Aws::getCloudLocations(); if (!in_array($object->cloudLocation, $locations)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Invalid region"); } if ($object->cloudPlatform !== 'ec2') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Only Ec2 cloud platform is supported"); } $image = $this->getImage($imageId, true); $imageAdapter = $this->adapter('image'); //Re-validates an Entity $imageAdapter->validateEntity($image); if ($image->cloudLocation == $object->cloudLocation) { throw new ApiErrorException(400, ErrorMessage::ERR_BAD_REQUEST, 'Destination region is the same as source one'); } try { $newImage = $image->migrateEc2Location($object->cloudLocation, $this->getUser()); } catch (NotEnabledPlatformException $e) { throw new ApiErrorException(400, ErrorMessage::ERR_NOT_ENABLED_PLATFORM, $e->getMessage()); } $this->response->setStatus(202); return $this->result($imageAdapter->toData($newImage)); }
/** * {@inheritdoc} * @see ApiEntityAdapter::validateEntity() */ public function validateEntity($entity) { if (!$entity instanceof FarmRole) { throw new InvalidArgumentException(sprintf("First argument must be instance of Scalr\\Model\\Entity\\FarmRole class")); } if ($entity->id !== null) { if (!FarmRole::findPk($entity->id)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find out the Farm with ID: %d", $entity->id)); } } if (empty($entity->farmId)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property farm.id"); } else { $farm = $this->controller->getFarm($entity->farmId, true); } if (empty($entity->roleId)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property role.id"); } if (empty($entity->platform)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "Missed property platform"); } switch ($entity->platform) { case SERVER_PLATFORMS::EC2: if (empty($entity->settings[FarmRoleSetting::AWS_INSTANCE_TYPE])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Missed property instance.type"); } /* @var $platform Ec2PlatformModule */ $platform = PlatformFactory::NewPlatform(SERVER_PLATFORMS::EC2); if (!in_array($entity->settings[FarmRoleSetting::AWS_INSTANCE_TYPE], $platform->getInstanceTypes())) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Wrong instance type"); } $gov = new Scalr_Governance($this->controller->getEnvironment()->id); $allowGovernanceIns = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_INSTANCE_TYPE); if (isset($allowGovernanceIns) && !in_array($entity->settings[FarmRoleSetting::AWS_INSTANCE_TYPE], $allowGovernanceIns)) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($allowGovernanceIns) > 1 ? [implode(', ', $allowGovernanceIns), 'instances are'] : [array_shift($allowGovernanceIns), 'instance is'])); } if (!in_array($entity->cloudLocation, Aws::getCloudLocations())) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Unknown region"); } $vpcGovernanceRegions = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_VPC, 'regions'); if (isset($vpcGovernanceRegions) && !array_key_exists($entity->cloudLocation, $vpcGovernanceRegions)) { $regions = array_keys($vpcGovernanceRegions); throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed according to governance settings", ...count($regions) > 1 ? [implode(', ', $regions), 'regions are'] : [array_shift($regions), 'region is'])); } $env = Scalr_Environment::init()->loadById($this->controller->getEnvironment()->id); $aws = $this->controller->getContainer()->aws($entity->cloudLocation, $env); if (!empty($entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE]) && $entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE] !== 'x-scalr-diff') { $availZones = explode(":", str_replace("x-scalr-custom=", '', $entity->settings[FarmRoleSetting::AWS_AVAIL_ZONE])); $ec2availabilityZones = []; foreach ($aws->ec2->availabilityZone->describe() as $zone) { /* @var $zone AvailabilityZoneData */ if (stristr($zone->zoneState, 'available')) { $ec2availabilityZones[] = $zone->zoneName; } } $diffZones = array_diff($availZones, $ec2availabilityZones); if (!empty($diffZones)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf('%s %s available. Available zones are %s', ...count($diffZones) > 1 ? [implode(', ', $diffZones), 'zones are not', implode(', ', $ec2availabilityZones)] : [array_shift($diffZones), 'zone is not', implode(', ', $ec2availabilityZones)])); } } if (!empty($entity->settings[FarmRoleSetting::AWS_VPC_SUBNET_ID])) { $vpcId = $farm->settings[FarmSetting::EC2_VPC_ID]; $subnets = $platform->listSubnets($env, $entity->cloudLocation, $vpcId, true); $vpcGovernanceIds = $gov->getValue(SERVER_PLATFORMS::EC2, Scalr_Governance::AWS_VPC, 'ids'); $subnetType = null; foreach (json_decode($entity->settings[FarmRoleSetting::AWS_VPC_SUBNET_ID]) as $subnetId) { $found = false; foreach ($subnets as $subnet) { if ($subnet['id'] == $subnetId) { if ($subnetType == null) { $subnetType = $subnet['type']; } else { if ($subnet['type'] != $subnetType) { throw new ApiErrorException(409, ErrorMessage::ERR_UNICITY_VIOLATION, "All subnets must be a same type"); } } //check governance subnet settings if (isset($vpcGovernanceIds[$vpcId])) { if (!empty($vpcGovernanceIds[$vpcId]) && is_array($vpcGovernanceIds[$vpcId]) && !in_array($subnetId, $vpcGovernanceIds[$vpcId])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Only %s %s allowed by governance settings", ...count($vpcGovernanceIds[$vpcId]) > 1 ? [implode(', ', $vpcGovernanceIds[$vpcId]), 'subnets are'] : [array_shift($vpcGovernanceIds[$vpcId]), 'subnet is'])); } elseif ($vpcGovernanceIds[$vpcId] == "outbound-only" && $subnetType != 'private') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Only private subnets allowed by governance settings"); } elseif ($vpcGovernanceIds[$vpcId] == "full" && $subnetType != 'public') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Only public subnets allowed by governance settings"); } } $found = true; } } if (!$found) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Subnet with id '{$subnetId}' not found"); } } if (!empty($entity->settings[Scalr_Role_Behavior_Router::ROLE_VPC_SCALR_ROUTER_ID])) { $router = $this->controller->getFarmRole($entity->settings[Scalr_Role_Behavior_Router::ROLE_VPC_SCALR_ROUTER_ID]); if (empty($router->settings[Scalr_Role_Behavior_Router::ROLE_VPC_NID])) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Farm-role with id '{$router->id}' is not a valid router"); } } else { if ($subnetType == 'private') { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "You must describe a VPC Router"); } } } else { if ($farm->settings[FarmSetting::EC2_VPC_ID]) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, "VPC Subnet(s) should be described"); } } break; default: if (in_array($entity->platform, SERVER_PLATFORMS::GetList())) { throw new ApiErrorException(501, ErrorMessage::ERR_NOT_IMPLEMENTED, "Platform '{$entity->platform}' is not supported yet"); } else { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Unknown platform '{$entity->platform}'"); } } if (!$this->controller->hasPermissions($entity, true)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } }
/** * {@inheritdoc} * @see \Scalr\System\Pcntl\ProcessInterface::OnStartForking() */ public function OnStartForking() { if (!\Scalr::getContainer()->analytics->enabled) { die("Terminating the process as Cost analytics is disabled in the config.\n"); } if (SettingEntity::getValue(SettingEntity::ID_FORBID_AUTOMATIC_UPDATE_AWS_PRICES)) { die("Terminating the process because of overriding AWS prices has been forbidden by financial admin.\n"); } $now = new DateTime('now', new DateTimeZone('UTC')); $urls = array('https://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js', 'https://a0.awsstatic.com/pricing/1/ec2/mswin-od.min.js'); $mapping = array('us-east' => 'us-east-1', 'us-west' => 'us-west-1', 'us-west-2' => 'us-west-2', 'eu-ireland' => 'eu-west-1', 'sa-east-1' => 'sa-east-1', 'apac-sin' => 'ap-southeast-1', 'apac-tokyo' => 'ap-northeast-1', 'apac-syd' => 'ap-southeast-2'); $availableLocations = Aws::getCloudLocations(); foreach ($urls as $link) { $json = trim(preg_replace('/^.+?callback\\((.+?)\\);\\s*$/sU', '\\1', file_get_contents($link))); $data = json_decode(preg_replace('/(\\w+):/', '"\\1":', $json)); if (!empty($data->config->regions)) { $cadb = Scalr::getContainer()->cadb; foreach ($data->config->regions as $rd) { foreach ($rd->instanceTypes as $it) { if (!isset($mapping[$rd->region])) { throw new Exception(sprintf("Region %s does not exist in the mapping.", $rd->region)); } $region = $mapping[$rd->region]; $latest = array(); //Gets latest prices for all instance types from current region. $res = $cadb->Execute("\n SELECT p.instance_type, ph.applied, p.os, p.name, HEX(p.price_id) `price_id`, p.cost\n FROM price_history ph\n JOIN prices p ON p.price_id = ph.price_id\n LEFT JOIN price_history ph2 ON ph2.platform = ph.platform\n AND ph2.cloud_location = ph.cloud_location\n AND ph2.account_id = ph.account_id\n AND ph2.url = ph.url\n AND ph2.applied > ph.applied AND ph2.applied <= ?\n LEFT JOIN prices p2 ON p2.price_id = ph2.price_id\n AND p2.instance_type = p.instance_type\n AND p2.os = p.os\n WHERE ph.account_id = 0 AND p2.price_id IS NULL\n AND ph.platform = 'ec2'\n AND ph.cloud_location = ?\n AND ph.url = ''\n AND ph.applied <= ?\n ", array($now->format('Y-m-d'), $region, $now->format('Y-m-d'))); while ($rec = $res->FetchRow()) { $latest[$rec['instance_type']][$rec['os']] = array('applied' => $rec['applied'], 'price_id' => $rec['price_id'], 'cost' => $rec['cost']); } $upd = array(); $needUpdate = false; foreach ($it->sizes as $sz) { foreach ($sz->valueColumns as $v) { $os = $v->name == 'linux' ? PriceEntity::OS_LINUX : PriceEntity::OS_WINDOWS; if (!isset($latest[$sz->size][$os])) { $needUpdate = true; } else { if (abs(($latest[$sz->size][$os]['cost'] - $v->prices->USD) / $v->prices->USD) > 1.0E-6) { $needUpdate = true; $latest[$sz->size][$os]['cost'] = $v->prices->USD; } else { continue; } } $latest[$sz->size][$os] = array('cost' => $v->prices->USD); } } if ($needUpdate) { $priceid = $cadb->GetOne("\n SELECT HEX(`price_id`) AS `price_id`\n FROM price_history\n WHERE platform = 'ec2'\n AND url = ''\n AND cloud_location = ?\n AND applied = ?\n AND account_id = 0\n LIMIT 1\n ", array($region, $now->format('Y-m-d'))); if (!$priceid) { $priceid = str_replace('-', '', Scalr::GenerateUID()); $cadb->Execute("\n INSERT price_history\n SET price_id = UNHEX(?),\n platform = 'ec2',\n url = '',\n cloud_location = ?,\n account_id = 0,\n applied = ?,\n deny_override = 0\n ", array($priceid, $region, $now->format('Y-m-d'))); } foreach ($latest as $instanceType => $ld) { foreach ($ld as $os => $v) { $cadb->Execute("\n REPLACE prices\n SET price_id = UNHEX(?),\n instance_type = ?,\n name = ?,\n os = ?,\n cost = ?\n ", array($priceid, $instanceType, $instanceType, $os, $v['cost'])); } } } } } } } exit; }
/** * @param Entity\CloudCredentials $entity * * @throws ApiErrorException */ public function validateEntity($entity) { $container = $this->controller->getContainer(); if ($container->analytics->enabled) { $ccProps = $entity->properties; if (!empty($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET])) { try { $region = $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION]; $aws = $container->aws($region, $ccProps[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $ccProps[Entity\CloudCredentialsProperty::AWS_SECRET_KEY]); if (!empty($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) && $aws->getAccountNumber() != $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) { $payerCredentials = $this->controller->getEnvironment()->cloudCredentialsList([SERVER_PLATFORMS::EC2], [['accountId' => $this->controller->getUser()->getAccountId()]], [['name' => Entity\CloudCredentialsProperty::AWS_ACCOUNT_ID], ['value' => $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]]]); $payerCredentials = array_shift($payerCredentials) ?: $entity; $payerCcProps = $payerCredentials->properties; $aws = $container->aws($region, $payerCcProps[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $payerCcProps[Entity\CloudCredentialsProperty::AWS_SECRET_KEY], !empty($payerCcProps[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) ? $payerCcProps[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] : null, !empty($payerCcProps[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]) ? $payerCcProps[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] : null); } try { $bucketObjects = $aws->s3->bucket->listObjects($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET]); } catch (Exception $e) { if (strpos($e->getMessage(), 'The authorization header is malformed') !== false) { if (preg_match("/expecting\\s+'(.+?)'/i", $e->getMessage(), $matches) && in_array($matches[1], Aws::getCloudLocations())) { $expectingRegion = $matches[1]; if (isset($payerCcProps)) { $aws = $container->aws($region, $payerCcProps[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $payerCcProps[Entity\CloudCredentialsProperty::AWS_SECRET_KEY], !empty($payerCcProps[Entity\CloudCredentialsProperty::AWS_CERTIFICATE]) ? $payerCcProps[Entity\CloudCredentialsProperty::AWS_CERTIFICATE] : null, !empty($payerCcProps[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY]) ? $payerCcProps[Entity\CloudCredentialsProperty::AWS_PRIVATE_KEY] : null); } else { $aws = $container->aws($expectingRegion, $ccProps[Entity\CloudCredentialsProperty::AWS_ACCESS_KEY], $ccProps[Entity\CloudCredentialsProperty::AWS_SECRET_KEY]); } $bucketObjects = $aws->s3->bucket->listObjects($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET]); $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_REGION] = $expectingRegion; } else { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, $e->getMessage(), $e->getCode(), $e); } } else { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, $e->getMessage(), $e->getCode(), $e); } } $objectName = (empty($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]) ? '' : "{$ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_PAYER_ACCOUNT]}-") . 'aws-billing-detailed-line-items-with-resources-and-tags'; $objectExists = false; $bucketObjectName = null; foreach ($bucketObjects as $bucketObject) { /* @var $bucketObject ObjectData */ if (strpos($bucketObject->objectName, $objectName) !== false) { $bucketObjectName = $bucketObject->objectName; $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_ENABLED] = 1; $objectExists = true; break; } } if (!$objectExists) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Bucket with name '{$objectName}' does not exist"); } $aws->s3->object->getMetadata($ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET], $bucketObjectName); } catch (Exception $e) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, sprintf("Cannot access billing bucket with name %s. Error: %s", $ccProps[Entity\CloudCredentialsProperty::AWS_DETAILED_BILLING_BUCKET], $e->getMessage()), $e->getCode(), $e); } } } }
/** * @test */ public function testGetReflectionClass() { $refl = Aws::getReflectionClass(); $this->assertEquals(self::CLASS_AWS, $refl->getName(), 'Invalid reflection class.'); }
/** * Calls Amazon web service method. * * It ensures execution of the certain AWS action by transporting the request * and receiving response. * * @param string $action REST METHOD (GET|PUT|DELETE|HEAD etc). * @param array $options An options array. It's used to send headers for the HTTP request. * extra options: _subdomain, _putData, _putFile * @param string $path optional A relative path. * @return ClientResponseInterface * @throws ClientException */ public function call($action, $options, $path = '/') { $httpRequest = $this->createRequest(); $httpMethod = $action ?: 'GET'; if (substr($path, 0, 1) !== '/') { $path = '/' . $path; } $this->lastApiCall = null; $eventObserver = $this->getAws()->getEventObserver(); if ($eventObserver && $eventObserver->isSubscribed(EventType::EVENT_SEND_REQUEST)) { foreach (debug_backtrace() as $arr) { if (empty($arr['class']) || !preg_match("/\\\\Service\\\\Aws\\\\.+Api\$/", $arr['class']) || $arr['type'] !== '->') { continue; } $this->lastApiCall = ucfirst($arr['function']); break; } } //Wipes out extra options from headers and moves them to separate array. //It also collects an x-amz headers. $extraOptions = array(); $amzHeaders = array(); foreach ($options as $key => $val) { if (substr($key, 0, 1) === '_') { $extraOptions[substr($key, 1)] = $val; unset($options[$key]); } elseif (preg_match('/^x-amz-/i', $key)) { //Saves amz headers which are used to sign the request $amzHeaders[strtolower($key)] = $val; } } if (!isset($options['Date'])) { $options['Date'] = gmdate('r'); } if (!isset($options['Host'])) { $options['Host'] = (isset($extraOptions['subdomain']) ? $extraOptions['subdomain'] . '.' : '') . $this->url; } if (strpos($options['Host'], 'http') === 0) { $arr = parse_url($options['Host']); $scheme = $arr['scheme']; $options['Host'] = $arr['host'] . (isset($arr['port']) ? ':' . $arr['port'] : ''); $path = (!empty($arr['path']) && $arr['path'] != '/' ? rtrim($arr['path'], '/') : '') . $path; } else { $scheme = 'https'; } if ($httpMethod === 'PUT' || $httpMethod === 'POST') { if (array_key_exists('putData', $extraOptions)) { if ($httpMethod === 'PUT') { $httpRequest->setPutData($extraOptions['putData']); } else { $httpRequest->setBody($extraOptions['putData']); } if (!isset($options['Content-Length'])) { $options['Content-Length'] = strlen($extraOptions['putData']); } if (!isset($options['Content-MD5']) && !empty($options['Content-Length'])) { $options['Content-MD5'] = Aws::getMd5Base64Digest($extraOptions['putData']); } } elseif (array_key_exists('putFile', $extraOptions)) { if ($httpMethod === 'PUT') { $httpRequest->setPutFile($extraOptions['putFile']); } else { $httpRequest->setBody(file_get_contents($extraOptions['putFile'])); } if (!isset($options['Content-Length'])) { $options['Content-Length'] = filesize($extraOptions['putFile']); } if (!isset($options['Content-MD5']) && !empty($options['Content-Length'])) { $options['Content-MD5'] = Aws::getMd5Base64DigestFile($extraOptions['putFile']); } } } //This also includes a mock objects which look like "Mock_S3QueryClient_d65a1dc1". if (preg_match('#(?<=[_\\\\])S3QueryClient(?=_|$)#', get_class($this))) { //S3 Client has a special Authorization string $canonicalizedAmzHeaders = ''; if (!empty($amzHeaders)) { ksort($amzHeaders); foreach ($amzHeaders as $k => $v) { $canonicalizedAmzHeaders .= $k . ':' . trim(preg_replace('/#( *[\\r\\n]+ *)+#/', ' ', $v)) . "\n"; } } //Note that in case of multiple sub-resources, sub-resources must be lexicographically sorted //by sub-resource name and separated by '&'. e.g. ?acl&versionId=value. $t = explode('?', $path); if (!empty($t[1])) { $canonPath = $t[0] . '?'; parse_str($t[1], $subresources); ksort($subresources); $allowed = $this->getAllowedSubResources(); foreach ($subresources as $k => $v) { if (in_array($k, $allowed)) { $canonPath .= $k . ($v !== '' ? '=' . $v : '') . '&'; } } $canonPath = substr($canonPath, 0, -1); } $canonicalizedResource = (isset($extraOptions['subdomain']) ? '/' . strtolower($extraOptions['subdomain']) : '') . (isset($canonPath) ? $canonPath : $path); $stringToSign = $httpMethod . "\n" . (!empty($options['Content-MD5']) ? (string) $options['Content-MD5'] : '') . "\n" . (!empty($options['Content-Type']) ? (string) $options['Content-Type'] : '') . "\n" . (isset($amzHeaders['x-amz-date']) ? '' : $options['Date'] . "\n") . $canonicalizedAmzHeaders . $canonicalizedResource; $options['Authorization'] = "AWS " . $this->awsAccessKeyId . ":" . base64_encode(hash_hmac('sha1', $stringToSign, $this->secretAccessKey, 1)); } else { $options['Authorization'] = "AWS " . $this->awsAccessKeyId . ":" . base64_encode(hash_hmac('sha1', $options['Date'], $this->secretAccessKey, 1)); } $httpRequest->setUrl($scheme . '://' . $options['Host'] . $path); $httpRequest->setMethod(constant('HTTP_METH_' . $httpMethod)); $httpRequest->addHeaders($options); $response = $this->tryCall($httpRequest); if ($this->getAws() && $this->getAws()->getDebug()) { echo "\n"; echo $httpRequest->getRawRequestMessage() . "\n"; echo $httpRequest->getRawResponseMessage() . "\n"; } return $response; }