fix(team): transfer instance-wide sources to root team on deletion
Instead of nullifying source references on applications when a team is deleted, transfer instance-wide GitHub/GitLab apps to the root team (team_id=0) so they remain available to other teams that depend on them. Non-instance-wide sources are still deleted along with the team.
This commit is contained in:
parent
ad662e5560
commit
7d2c776ae7
3 changed files with 88 additions and 48 deletions
|
|
@ -71,27 +71,31 @@ protected static function booted()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
static::deleting(function ($team) {
|
static::deleting(function (Team $team) {
|
||||||
$keys = $team->privateKeys;
|
foreach ($team->privateKeys as $key) {
|
||||||
foreach ($keys as $key) {
|
|
||||||
$key->delete();
|
$key->delete();
|
||||||
}
|
}
|
||||||
// Only delete sources owned by this team, not system-wide ones from other teams
|
|
||||||
|
// Transfer instance-wide sources to root team so they remain available
|
||||||
|
GithubApp::where('team_id', $team->id)->where('is_system_wide', true)->update(['team_id' => 0]);
|
||||||
|
GitlabApp::where('team_id', $team->id)->where('is_system_wide', true)->update(['team_id' => 0]);
|
||||||
|
|
||||||
|
// Delete non-instance-wide sources owned by this team
|
||||||
$teamSources = GithubApp::where('team_id', $team->id)->get()
|
$teamSources = GithubApp::where('team_id', $team->id)->get()
|
||||||
->merge(GitlabApp::where('team_id', $team->id)->get());
|
->merge(GitlabApp::where('team_id', $team->id)->get());
|
||||||
foreach ($teamSources as $source) {
|
foreach ($teamSources as $source) {
|
||||||
$source->delete();
|
$source->delete();
|
||||||
}
|
}
|
||||||
$tags = Tag::whereTeamId($team->id)->get();
|
|
||||||
foreach ($tags as $tag) {
|
foreach (Tag::whereTeamId($team->id)->get() as $tag) {
|
||||||
$tag->delete();
|
$tag->delete();
|
||||||
}
|
}
|
||||||
$shared_variables = $team->environment_variables();
|
|
||||||
foreach ($shared_variables as $shared_variable) {
|
foreach ($team->environment_variables()->get() as $sharedVariable) {
|
||||||
$shared_variable->delete();
|
$sharedVariable->delete();
|
||||||
}
|
}
|
||||||
$s3s = $team->s3s;
|
|
||||||
foreach ($s3s as $s3) {
|
foreach ($team->s3s as $s3) {
|
||||||
$s3->delete();
|
$s3->delete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -340,4 +344,5 @@ public function webhookNotificationSettings()
|
||||||
{
|
{
|
||||||
return $this->hasOne(WebhookNotificationSettings::class);
|
return $this->hasOne(WebhookNotificationSettings::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,23 +176,6 @@ private static function finalizeTeamDeletion(User $user, Team $team)
|
||||||
$project->forceDelete();
|
$project->forceDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detach applications from other teams that reference this team's sources,
|
|
||||||
// so the GithubApp/GitlabApp deleting guard doesn't block team deletion
|
|
||||||
$githubAppIds = GithubApp::where('team_id', $team->id)->pluck('id');
|
|
||||||
$gitlabAppIds = GitlabApp::where('team_id', $team->id)->pluck('id');
|
|
||||||
|
|
||||||
if ($githubAppIds->isNotEmpty()) {
|
|
||||||
Application::where('source_type', GithubApp::class)
|
|
||||||
->whereIn('source_id', $githubAppIds)
|
|
||||||
->update(['source_id' => null, 'source_type' => null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($gitlabAppIds->isNotEmpty()) {
|
|
||||||
Application::where('source_type', GitlabApp::class)
|
|
||||||
->whereIn('source_id', $gitlabAppIds)
|
|
||||||
->update(['source_id' => null, 'source_type' => null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$team->members()->detach($user->id);
|
$team->members()->detach($user->id);
|
||||||
$team->delete();
|
$team->delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,49 +118,101 @@
|
||||||
expect(GithubApp::find($systemGithubApp->id))->not->toBeNull();
|
expect(GithubApp::find($systemGithubApp->id))->not->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nullifies source references on other teams apps when deleting a user', function () {
|
it('transfers instance-wide github app to root team when owning user is deleted', function () {
|
||||||
// Create the user to be deleted with their own team
|
// Create a user whose team owns an instance-wide GitHub App
|
||||||
$targetUser = User::factory()->create();
|
$targetUser = User::factory()->create();
|
||||||
$targetTeam = $targetUser->teams()->first();
|
$targetTeam = $targetUser->teams()->first();
|
||||||
|
|
||||||
// Create a GitHub App owned by the target team
|
|
||||||
$targetPrivateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
$targetPrivateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
$githubApp = GithubApp::create([
|
$instanceWideApp = GithubApp::create([
|
||||||
'name' => 'Target GitHub App',
|
'name' => 'Instance-Wide GitHub App',
|
||||||
'team_id' => $targetTeam->id,
|
'team_id' => $targetTeam->id,
|
||||||
'private_key_id' => $targetPrivateKey->id,
|
'private_key_id' => $targetPrivateKey->id,
|
||||||
'api_url' => 'https://api.github.com',
|
'api_url' => 'https://api.github.com',
|
||||||
'html_url' => 'https://github.com',
|
'html_url' => 'https://github.com',
|
||||||
'is_public' => false,
|
'is_public' => false,
|
||||||
|
'is_system_wide' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create an application on the ADMIN's team that uses the target team's GitHub App
|
// Create an application on the ROOT team that uses this instance-wide GitHub App
|
||||||
$adminPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
$rootPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
$adminServer = Server::factory()->create([
|
$rootServer = Server::factory()->create([
|
||||||
'team_id' => $this->rootTeam->id,
|
'team_id' => $this->rootTeam->id,
|
||||||
'private_key_id' => $adminPrivateKey->id,
|
'private_key_id' => $rootPrivateKey->id,
|
||||||
]);
|
]);
|
||||||
$adminDestination = StandaloneDocker::factory()->create(['server_id' => $adminServer->id]);
|
$rootDestination = StandaloneDocker::factory()->create(['server_id' => $rootServer->id]);
|
||||||
$adminProject = Project::factory()->create(['team_id' => $this->rootTeam->id]);
|
$rootProject = Project::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
$adminEnvironment = Environment::factory()->create(['project_id' => $adminProject->id]);
|
$rootEnvironment = Environment::factory()->create(['project_id' => $rootProject->id]);
|
||||||
|
|
||||||
$otherTeamApp = Application::factory()->create([
|
$otherTeamApp = Application::factory()->create([
|
||||||
'environment_id' => $adminEnvironment->id,
|
'environment_id' => $rootEnvironment->id,
|
||||||
'destination_id' => $adminDestination->id,
|
'destination_id' => $rootDestination->id,
|
||||||
'destination_type' => StandaloneDocker::class,
|
'destination_type' => StandaloneDocker::class,
|
||||||
'source_id' => $githubApp->id,
|
'source_id' => $instanceWideApp->id,
|
||||||
'source_type' => GithubApp::class,
|
'source_type' => GithubApp::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Delete the target user — should succeed, nullifying the source reference
|
// Delete the user — should succeed and transfer the instance-wide app to root team
|
||||||
$targetUser->delete();
|
$targetUser->delete();
|
||||||
|
|
||||||
// Assert user is deleted
|
// Assert user is deleted
|
||||||
expect(User::find($targetUser->id))->toBeNull();
|
expect(User::find($targetUser->id))->toBeNull();
|
||||||
|
|
||||||
// Assert the other team's application still exists but source is nullified
|
// Assert the instance-wide GitHub App is preserved and transferred to root team
|
||||||
|
$instanceWideApp->refresh();
|
||||||
|
expect($instanceWideApp)->not->toBeNull();
|
||||||
|
expect($instanceWideApp->team_id)->toBe($this->rootTeam->id);
|
||||||
|
|
||||||
|
// Assert the other team's application still has its source intact
|
||||||
$otherTeamApp->refresh();
|
$otherTeamApp->refresh();
|
||||||
expect($otherTeamApp)->not->toBeNull();
|
expect($otherTeamApp->source_id)->toBe($instanceWideApp->id);
|
||||||
expect($otherTeamApp->source_id)->toBeNull();
|
expect($otherTeamApp->source_type)->toBe(GithubApp::class);
|
||||||
expect($otherTeamApp->source_type)->toBeNull();
|
});
|
||||||
|
|
||||||
|
it('transfers instance-wide github app to root team when team is deleted directly', function () {
|
||||||
|
// Create a team that owns an instance-wide GitHub App
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
$targetTeam = $targetUser->teams()->first();
|
||||||
|
|
||||||
|
$targetPrivateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$instanceWideApp = GithubApp::create([
|
||||||
|
'name' => 'Instance-Wide GitHub App',
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $targetPrivateKey->id,
|
||||||
|
'api_url' => 'https://api.github.com',
|
||||||
|
'html_url' => 'https://github.com',
|
||||||
|
'is_public' => false,
|
||||||
|
'is_system_wide' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create an application on the ROOT team that uses this instance-wide GitHub App
|
||||||
|
$rootPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootServer = Server::factory()->create([
|
||||||
|
'team_id' => $this->rootTeam->id,
|
||||||
|
'private_key_id' => $rootPrivateKey->id,
|
||||||
|
]);
|
||||||
|
$rootDestination = StandaloneDocker::factory()->create(['server_id' => $rootServer->id]);
|
||||||
|
$rootProject = Project::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootEnvironment = Environment::factory()->create(['project_id' => $rootProject->id]);
|
||||||
|
|
||||||
|
$otherTeamApp = Application::factory()->create([
|
||||||
|
'environment_id' => $rootEnvironment->id,
|
||||||
|
'destination_id' => $rootDestination->id,
|
||||||
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
'source_id' => $instanceWideApp->id,
|
||||||
|
'source_type' => GithubApp::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete the team directly — should transfer instance-wide app to root team
|
||||||
|
$targetTeam->delete();
|
||||||
|
|
||||||
|
// Assert the instance-wide GitHub App is preserved and transferred to root team
|
||||||
|
$instanceWideApp->refresh();
|
||||||
|
expect($instanceWideApp)->not->toBeNull();
|
||||||
|
expect($instanceWideApp->team_id)->toBe($this->rootTeam->id);
|
||||||
|
|
||||||
|
// Assert the other team's application still has its source intact
|
||||||
|
$otherTeamApp->refresh();
|
||||||
|
expect($otherTeamApp->source_id)->toBe($instanceWideApp->id);
|
||||||
|
expect($otherTeamApp->source_type)->toBe(GithubApp::class);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue