/** * Retrieves the list of the roles that are available on the Environment */ public function describeAction() { $this->checkScopedPermissions('ROLES'); $r = new Entity\Role(); $re = new Entity\RoleEnvironment(); $ri = new Entity\RoleImage(); $criteria = []; $criteria[Entity\Role::STMT_DISTINCT] = true; $criteria[Entity\Role::STMT_FROM] = $r->table() . " LEFT JOIN " . $re->table() . " ON {$r->columnId} = {$re->columnRoleId}\n LEFT JOIN " . $ri->table() . " ON {$r->columnId} = {$ri->columnRoleId}"; switch ($this->getScope()) { case ScopeInterface::SCOPE_ENVIRONMENT: $criteria[Entity\Role::STMT_WHERE] = "({$r->columnAccountId} IS NULL AND {$ri->columnRoleId} IS NOT NULL\n OR {$r->columnAccountId} = '" . $this->getUser()->accountId . "' AND {$r->columnEnvId} IS NULL\n AND ({$re->columnEnvId} IS NULL OR {$re->columnEnvId} = '" . $this->getEnvironment()->id . "')\n OR {$r->columnEnvId} = '" . $this->getEnvironment()->id . "'\n ) AND {$r->columnGeneration} = 2"; break; case ScopeInterface::SCOPE_ACCOUNT: $criteria[Entity\Role::STMT_WHERE] = "({$r->columnAccountId} IS NULL AND {$ri->columnRoleId} IS NOT NULL OR " . "{$r->columnAccountId} = '" . $this->getUser()->accountId . "' AND {$r->columnEnvId} IS NULL) AND {$r->columnGeneration} = 2"; break; case ScopeInterface::SCOPE_SCALR: $criteria = [['envId' => null], ['accountId' => null]]; break; } return $this->adapter('role')->getDescribeResult($criteria); }
/** * Get list of roles for listView * * @param int $roleId optional * @param string $platform optional * @param string $cloudLocation optional * @param string $imageId optional * @param string $scope optional * @param int $chefServerId optional * @param int $catId optional * @param string $osFamily optional * @param bool $isQuickStart optional * @param bool $isDeprecated optional * @param string $status optional * @param JsonData $addImage optional */ public function xListRolesAction($roleId = null, $platform = null, $cloudLocation = null, $imageId = null, $scope = null, $chefServerId = null, $catId = null, $osFamily = null, $isQuickStart = false, $isDeprecated = false, $status = null, JsonData $addImage = null) { $this->request->restrictAccess('ROLES'); $args = []; $inUseJoin = ''; $envId = $this->getEnvironmentId(true); $accountId = $this->user->getAccountId() ?: NULL; if ($accountId) { $inUseJoin = " JOIN farms ON farm_roles.farmid = farms.id AND farms.clientid = ? "; $args[] = $accountId; if ($envId) { $inUseJoin .= " AND farms.env_id = ?"; $args[] = $envId; } } $role = new Role(); $sql = "\n SELECT DISTINCT " . $role->fields('r') . ", os.name as osName, os.family as osFamily,\n (SELECT EXISTS(SELECT 1 FROM farm_roles " . $inUseJoin . "\n WHERE farm_roles.role_id = r.id)) AS inUse,\n (SELECT GROUP_CONCAT(name SEPARATOR ',') FROM `client_environments` ce LEFT JOIN role_environments re ON ce.id = re.env_id\n WHERE re.role_id = r.id) AS environments\n FROM " . $role->table('r') . "\n LEFT JOIN role_images ON r.id = role_images.role_id\n LEFT JOIN os ON r.os_id = os.id\n LEFT JOIN role_environments re ON re.role_id = r.id\n WHERE\n "; if ($this->request->getScope() == ScopeInterface::SCOPE_SCALR) { $sql .= " r.client_id IS NULL"; } else { if ($this->request->getScope() == ScopeInterface::SCOPE_ACCOUNT) { $sql .= " (r.client_id IS NULL AND role_images.role_id IS NOT NULL OR r.client_id = ? AND r.env_id IS NULL) AND r.generation = ?"; $args = array_merge($args, [$accountId, 2]); } else { $sql .= " (r.client_id IS NULL AND role_images.role_id IS NOT NULL OR r.client_id = ? AND r.env_id IS NULL AND (re.env_id IS NULL OR re.env_id = ?) OR r.env_id = ?) AND r.generation = ?"; $args = array_merge($args, [$accountId, $envId, $envId, 2]); } } if ($roleId) { $sql .= " AND r.id = ?"; $args[] = $roleId; } else { $sql .= " AND :FILTER: "; if ($scope == ScopeInterface::SCOPE_SCALR) { $sql .= " AND r.client_id IS NULL"; } else { if ($scope == ScopeInterface::SCOPE_ACCOUNT) { $sql .= " AND r.client_id = ? AND r.env_id IS NULL"; $args[] = $accountId; } else { if ($scope == ScopeInterface::SCOPE_ENVIRONMENT) { $sql .= " AND r.env_id = ?"; $args[] = $envId; } } } if ($platform) { $sql .= " AND role_images.platform = ?"; $args[] = $platform; } if ($cloudLocation) { $sql .= " AND role_images.cloud_location = ?"; $args[] = $cloudLocation; } if ($imageId) { $sql .= " AND role_images.image_id = ?"; $args[] = $imageId; } if ($catId) { $sql .= " AND r.cat_id = ?"; $args[] = $catId; } if ($osFamily) { $sql .= " AND os.family = ?"; $args[] = $osFamily; } if ($scope) { $sql .= " AND r.origin = ?"; $args[] = $scope == 'scalr' ? 'Shared' : 'Custom'; } if ($status) { $sql .= " AND ("; $used = $status == 'inUse' ? true : false; if ($this->user->getAccountId() != 0) { $sql .= "r.id " . ($used ? '' : "NOT") . " IN (SELECT role_id FROM farm_roles fr " . "JOIN farms f ON f.id = fr.farmid WHERE f." . ($envId ? "env_id" : "clientid") . " = ?)"; $args[] = $envId ?: $this->user->getAccountId(); } else { $sql .= "r.id " . ($used ? '' : "NOT") . " IN (SELECT role_id FROM farm_roles)"; } $sql .= ')'; } if ($chefServerId) { $sql .= " AND r.id IN (SELECT role_id FROM role_properties WHERE name = ? AND value = ?)"; $sql .= " AND r.id IN (SELECT role_id FROM role_properties WHERE name = ? AND value = ?)"; $args[] = \Scalr_Role_Behavior_Chef::ROLE_CHEF_SERVER_ID; $args[] = $chefServerId; $args[] = \Scalr_Role_Behavior_Chef::ROLE_CHEF_BOOTSTRAP; $args[] = 1; } if ($addImage) { if (isset($addImage['osId'])) { $sql .= " AND r.os_id = ?"; $args[] = $addImage['osId']; } if (isset($addImage['isScalarized']) && isset($addImage['hasCloudInit']) && $addImage['isScalarized'] == 0 && $addImage['hasCloudInit'] == 0) { $sql .= " AND r.is_scalarized = 0"; } } if ($isQuickStart) { $sql .= " AND r.is_quick_start = 1"; } if ($isDeprecated) { $sql .= " AND r.is_deprecated = 1"; } } $response = $this->buildResponseFromSql2($sql, ['id', 'name', 'os_id'], ['r.name'], $args); $data = []; $allPlatforms = array_flip(array_keys(SERVER_PLATFORMS::GetList())); foreach ($response['data'] as $r) { $role = new Role(); $role->load($r); $row = new stdClass(); $row->name = $role->name; $row->behaviors = $role->getBehaviors(); $row->id = $role->id; $row->accountId = $role->accountId; $row->envId = $role->envId; $row->status = $r['inUse'] ? 'In use' : 'Not used'; $row->scope = $role->getScope(); $row->os = $r['osName']; $row->osId = $role->osId; $row->osFamily = $r['osFamily']; $row->dtAdded = $role->added ? Scalr_Util_DateTime::convertTz($role->added) : null; $row->dtLastUsed = $role->lastUsed ? Scalr_Util_DateTime::convertTz($role->lastUsed) : null; $row->isQuickStart = $role->isQuickStart ? "1" : "0"; $row->isDeprecated = $role->isDeprecated ? "1" : "0"; $row->isScalarized = $role->isScalarized ? 1 : 0; $row->client_name = $role->accountId == 0 ? 'Scalr' : 'Private'; $row->environments = $r['environments'] ? explode(',', $r['environments']) : []; $platforms = array_keys($role->fetchImagesArray()); usort($platforms, function ($a, $b) use($allPlatforms) { return $allPlatforms[$a] > $allPlatforms[$b] ? 1 : -1; }); $row->platforms = $platforms; if ($addImage->count()) { try { $role->getImage($addImage['platform'], $addImage['cloudLocation']); $row->canAddImage = false; } catch (Exception $e) { $row->canAddImage = true; } } $data[] = $row; } $this->response->data(['total' => $response['total'], 'data' => $data]); }
/** * {@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"); } }