Skip to content

Commit

Permalink
Use the new user-access API
Browse files Browse the repository at this point in the history
  • Loading branch information
pjcdawkins committed Dec 18, 2023
1 parent 47cade7 commit d0cd905
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 150 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"guzzlehttp/guzzle": "^5.3",
"guzzlehttp/ringphp": "^1.1",
"platformsh/console-form": ">=0.0.37 <2.0",
"platformsh/client": ">=0.78.1 <2.0",
"platformsh/client": ">=0.79.0 <2.0",
"symfony/console": "^3.0 >=3.2",
"symfony/yaml": "^3.0 || ^2.6",
"symfony/finder": "^3.0",
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

182 changes: 105 additions & 77 deletions src/Command/User/UserAddCommand.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php
namespace Platformsh\Cli\Command\User;

use Platformsh\Cli\Command\CommandBase;
use Platformsh\Cli\Console\ArrayArgument;
use Platformsh\Cli\Util\OsUtil;
use Platformsh\Cli\Util\Wildcard;
use Platformsh\Client\Model\EnvironmentAccess;
use Platformsh\Client\Model\EnvironmentType;
use Platformsh\Client\Model\Invitation\AlreadyInvitedException;
use Platformsh\Client\Model\Invitation\Permission;
use Platformsh\Client\Model\ProjectAccess;
use Platformsh\Client\Model\UserAccess\ProjectUserAccess;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -18,7 +19,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;

class UserAddCommand extends CommandBase
class UserAddCommand extends UserCommandBase
{

protected function configure()
Expand Down Expand Up @@ -121,13 +122,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
$email = $input->getArgument('email');
if (!$email) {
$update = stripos($input->getFirstArgument(), ':u');
if ($update && $input->isInteractive()) {
$choices = [];
foreach ($this->api()->getProjectAccesses($project) as $access) {
$account = $this->api()->getAccount($access);
$choices[$account['email']] = $this->getUserLabel($access);
}
$email = $questionHelper->choose($choices, 'Enter a number to choose a user to update:');
if (!$input->isInteractive()) {
throw new InvalidArgumentException('An email address is required (in non-interactive mode).');
} elseif ($update) {
$email = $questionHelper->choose($this->listUsers($project), 'Enter a number to choose a user to update:');
} else {
$question = new Question("Enter the user's email address: ");
$question->setValidator(function ($answer) {
Expand All @@ -140,41 +138,59 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
$this->validateEmail($email);

// Check the user's existing role on the project.
$existingProjectAccess = $this->api()->loadProjectAccessByEmail($project, $email);
$existingTypeRoles = [];
if ($existingProjectAccess) {
// Exit if the user is the owner already.
if ($existingProjectAccess->id === $project->owner) {
if ($hasOutput) {
$this->stdErr->writeln('');
}

$this->stdErr->writeln(sprintf('The user %s is the owner of %s.', $this->getUserLabel($existingProjectAccess), $this->api()->getProjectLabel($project)));
if ($specifiedProjectRole || $specifiedTypeRoles) {
$this->stdErr->writeln('');
$this->stdErr->writeln("<comment>The project owner's role(s) cannot be changed.</comment>");
$existingProjectRole = null;
$existingUserLabel = null;
$existingUserId = null;

/**
* @var ProjectUserAccess|ProjectAccess|null $selection
*/
$selection = $this->loadProjectUserByEmail($project, $email);
if ($selection instanceof ProjectUserAccess) {
$existingUserId = $selection->user_id;
$existingProjectRole = $selection->getProjectRole();
$existingTypeRoles = $selection->getEnvironmentTypeRoles();
$userInfo = $selection->getUserInfo();
$existingUserLabel = sprintf('<info>%s</info> (%s)', trim($userInfo->first_name . ' ' . $userInfo->last_name), $userInfo->email);
} elseif ($selection instanceof ProjectAccess) {
$existingUserId = $selection->id;
$existingProjectRole = $selection->role;
$existingUserLabel = $this->getUserLabel($selection);
$existingTypeRoles = $this->getTypeRoles($selection, $environmentTypes);
}

if ($existingUserId !== null) {
$this->debug(sprintf('The user %s already exists on the project (user ID: %s)', $email, $existingUserId));
}

// Exit if the user is the owner already.
if ($existingUserId !== null && $existingUserId === $project->owner) {
if ($hasOutput) {
$this->stdErr->writeln('');
}

return 1;
}
$this->stdErr->writeln(sprintf('The user %s is the owner of %s.', $existingUserLabel, $this->api()->getProjectLabel($project)));
if ($specifiedProjectRole || $specifiedTypeRoles) {
$this->stdErr->writeln('');
$this->stdErr->writeln("<comment>The project owner's role(s) cannot be changed.</comment>");

return 0;
return 1;
}

// Check the user's existing role(s) on the project's environments and types.
$existingTypeRoles = $this->getTypeRoles($existingProjectAccess, $environmentTypes);
return 0;
}

// If the user already exists, print a summary of their roles on the
// project and environments.
if ($existingProjectAccess) {
if ($existingUserId !== null) {
if ($hasOutput) {
$this->stdErr->writeln('');
}

$this->stdErr->writeln(sprintf('Current role(s) of <info>%s</info> on %s:', $this->getUserLabel($existingProjectAccess), $this->api()->getProjectLabel($project)));
$this->stdErr->writeln(sprintf(' Project role: <info>%s</info>', $existingProjectAccess->role));
if ($existingProjectAccess->role !== ProjectAccess::ROLE_ADMIN) {
$this->stdErr->writeln(sprintf('Current role(s) of %s on %s:', $existingUserLabel, $this->api()->getProjectLabel($project)));
$this->stdErr->writeln(sprintf(' Project role: <info>%s</info>', $existingProjectRole));
if ($existingProjectRole !== ProjectAccess::ROLE_ADMIN) {
foreach ($environmentTypes as $type) {
$role = isset($existingTypeRoles[$type->id]) ? $existingTypeRoles[$type->id] : '[none]';
$this->stdErr->writeln(sprintf(' Role on environment type <info>%s</info>: %s', $type->id, $role));
Expand All @@ -184,7 +200,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

// Resolve or merge the project role.
$desiredProjectRole = $specifiedProjectRole ?: ($existingProjectAccess ? $existingProjectAccess->role : ProjectAccess::ROLE_VIEWER);
$desiredProjectRole = $specifiedProjectRole ?: ($existingProjectRole ?: ProjectAccess::ROLE_VIEWER);
$provideProjectForm = !$input->getOption('role') && $input->isInteractive();
if ($provideProjectForm) {
if ($hasOutput) {
Expand Down Expand Up @@ -219,9 +235,9 @@ protected function execute(InputInterface $input, OutputInterface $output)

// Build a list of the changes that are going to be made.
$changesText = [];
if ($existingProjectAccess) {
if ($existingProjectAccess->role !== $desiredProjectRole) {
$changesText[] = sprintf('Project role: <error>%s</error> -> <info>%s</info>', $existingProjectAccess->role, $desiredProjectRole);
if ($existingUserId !== null) {
if ($existingProjectRole !== $desiredProjectRole) {
$changesText[] = sprintf('Project role: <error>%s</error> -> <info>%s</info>', $existingProjectRole, $desiredProjectRole);
}
} else {
$changesText[] = sprintf('Project role: <info>%s</info>', $desiredProjectRole);
Expand Down Expand Up @@ -271,12 +287,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('<error>No environment types selected.</error>');
$this->stdErr->writeln('A non-admin user must be added to at least one environment type.');

if ($existingProjectAccess) {
if ($existingUserId !== null) {
$this->stdErr->writeln('');
$this->stdErr->writeln(sprintf(
'To delete the user, run: <info>%s user:delete %s</info>',
$this->config()->get('application.executable'),
$this->api()->getAccount($existingProjectAccess)['email']
OsUtil::escapeShellArg($email)
));
}

Expand All @@ -290,7 +306,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

// Print a summary of the changes that are about to be made.
if ($existingProjectAccess) {
if ($existingUserId !== null) {
$this->stdErr->writeln('Summary of changes:');
} else {
$this->stdErr->writeln(sprintf('Adding the user <info>%s</info> to %s:', $email, $this->api()->getProjectLabel($project)));
Expand All @@ -301,7 +317,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('');

// Ask for confirmation.
if ($existingProjectAccess) {
if ($existingUserId !== null) {
if (!$questionHelper->confirm('Are you sure you want to make these change(s)?')) {
return 1;
}
Expand All @@ -317,7 +333,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('');

// If the user does not already exist on the project, then use the Invitations API.
if (!$existingProjectAccess) {
if ($existingUserId === null) {
$this->stdErr->writeln('Inviting the user to the project...');
$permissions = [];
foreach ($desiredTypeRoles as $type => $role) {
Expand All @@ -340,56 +356,68 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 0;
}

// Make the desired changes at the project level.
if ($existingProjectAccess->role !== $desiredProjectRole) {
$this->stdErr->writeln("Setting the user's project role to: $desiredProjectRole");
$result = $existingProjectAccess->update(['role' => $desiredProjectRole]);
$activities = $result->getActivities();
$userId = $existingProjectAccess->id;
} else {
$userId = $existingProjectAccess->id;
$activities = [];
}

// Make the desired changes at the environment type level.
if ($desiredProjectRole !== ProjectAccess::ROLE_ADMIN) {
foreach ($typeChanges as $typeId => $role) {
$type = $project->getEnvironmentType($typeId);
if (!$type) {
$this->stdErr->writeln('Environment type not found: <comment>' . $typeId . '</comment>');
continue;
}
$access = $type->getUser($userId);
if ($role === 'none') {
if ($access) {
$this->stdErr->writeln("Removing the user from the environment type <info>$typeId</info>");
$result = $access->delete();
} else {
$activities = [];
if ($selection instanceof ProjectUserAccess) {
$permissions = [$desiredProjectRole];
foreach ($desiredTypeRoles as $typeId => $role) {
$permissions[] = sprintf('%s:%s', $typeId, $role);
}
if ($permissions != $selection->permissions) {
$this->stdErr->writeln("Updating the user's project access");
$this->debug('New permissions: ' . implode(', ', $permissions));
$selection->update(['permissions' => $permissions]);
} else {
$this->stdErr->writeln('No changes to make');
$this->debug('Permissions match: ' . implode(', ', $permissions));
}
} elseif ($selection instanceof ProjectAccess) {
// Make the desired changes at the project level.
if ($existingProjectRole !== $desiredProjectRole) {
$this->stdErr->writeln("Setting the user's project role to: $desiredProjectRole");
$result = $selection->update(['role' => $desiredProjectRole]);
$activities = $result->getActivities();
}

// Make the desired changes at the environment type level.
if ($desiredProjectRole !== ProjectAccess::ROLE_ADMIN) {
foreach ($typeChanges as $typeId => $role) {
$type = $project->getEnvironmentType($typeId);
if (!$type) {
$this->stdErr->writeln('Environment type not found: <comment>' . $typeId . '</comment>');
continue;
}
} elseif ($access) {
if ($access->role === $role) {
continue;
$access = $type->getUser($existingUserId);
if ($role === 'none') {
if ($access) {
$this->stdErr->writeln("Removing the user from the environment type <info>$typeId</info>");
$result = $access->delete();
} else {
continue;
}
} elseif ($access) {
if ($access->role === $role) {
continue;
}
$this->stdErr->writeln("Setting the user's role on the environment type <info>$typeId</info> to: $role");
$result = $access->update(['role' => $role]);
} else {
$this->stdErr->writeln("Adding the user to the environment type: <info>$typeId</info>");
$result = $type->addUser($existingUserId, $role);
}
$this->stdErr->writeln("Setting the user's role on the environment type <info>$typeId</info> to: $role");
$result = $access->update(['role' => $role]);
} else {
$this->stdErr->writeln("Adding the user to the environment type: <info>$typeId</info>");
$result = $type->addUser($userId, $role);
$activities = array_merge($activities, $result->getActivities());
}
$activities = array_merge($activities, $result->getActivities());
}
}

// Wait for activities to complete.
if (!$activities) {
$this->redeployWarning();
} elseif ($this->shouldWait($input)) {
if ($activities && $this->shouldWait($input)) {
/** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */
$activityMonitor = $this->getService('activity_monitor');
if (!$activityMonitor->waitMultiple($activities, $project)) {
return 1;
}
} elseif (!$this->centralizedPermissionsEnabled()) {
$this->redeployWarning();
}

return 0;
Expand Down
Loading

0 comments on commit d0cd905

Please sign in to comment.