Replace all uses of `forceFill`, `forceCreate`, and `forceFill` with their non-force equivalents across models, actions, controllers, and Livewire components. Add explicit `$fillable` arrays to all affected Eloquent models to enforce mass assignment protection. Add ModelFillableCreationTest and ModelFillableRegressionTest to verify that model creation respects fillable constraints and prevent regressions.
199 lines
10 KiB
PHP
199 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Webhook;
|
|
|
|
use App\Actions\Application\CleanupPreviewDeployment;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationPreview;
|
|
use Exception;
|
|
use Illuminate\Http\Request;
|
|
use Visus\Cuid2\Cuid2;
|
|
|
|
class Bitbucket extends Controller
|
|
{
|
|
public function manual(Request $request)
|
|
{
|
|
try {
|
|
$return_payloads = collect([]);
|
|
$payload = $request->collect();
|
|
$headers = $request->headers->all();
|
|
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', '');
|
|
$x_bitbucket_event = data_get($headers, 'x-event-key.0', '');
|
|
$handled_events = collect(['repo:push', 'pullrequest:updated', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
|
|
if (! $handled_events->contains($x_bitbucket_event)) {
|
|
return response([
|
|
'status' => 'failed',
|
|
'message' => 'Nothing to do. Event not handled.',
|
|
]);
|
|
}
|
|
if ($x_bitbucket_event === 'repo:push') {
|
|
$branch = data_get($payload, 'push.changes.0.new.name');
|
|
$full_name = data_get($payload, 'repository.full_name');
|
|
$commit = data_get($payload, 'push.changes.0.new.target.hash');
|
|
|
|
if (! $branch) {
|
|
return response([
|
|
'status' => 'failed',
|
|
'message' => 'Nothing to do. No branch found in the request.',
|
|
]);
|
|
}
|
|
}
|
|
if ($x_bitbucket_event === 'pullrequest:updated' || $x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
|
$branch = data_get($payload, 'pullrequest.destination.branch.name');
|
|
$base_branch = data_get($payload, 'pullrequest.source.branch.name');
|
|
$full_name = data_get($payload, 'repository.full_name');
|
|
$pull_request_id = data_get($payload, 'pullrequest.id');
|
|
$pull_request_html_url = data_get($payload, 'pullrequest.links.html.href');
|
|
$commit = data_get($payload, 'pullrequest.source.commit.hash');
|
|
}
|
|
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
|
$applications = $applications->where('git_branch', $branch)->get();
|
|
if ($applications->isEmpty()) {
|
|
return response([
|
|
'status' => 'failed',
|
|
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
|
|
]);
|
|
}
|
|
foreach ($applications as $application) {
|
|
$webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket');
|
|
$payload = $request->getContent();
|
|
|
|
[$algo, $hash] = explode('=', $x_bitbucket_token, 2);
|
|
$payloadHash = hash_hmac($algo, $payload, $webhook_secret);
|
|
if (! hash_equals($hash, $payloadHash) && ! isDev()) {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'failed',
|
|
'message' => 'Invalid signature.',
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
$isFunctional = $application->destination->server->isFunctional();
|
|
if (! $isFunctional) {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'failed',
|
|
'message' => 'Server is not functional.',
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
if ($x_bitbucket_event === 'repo:push') {
|
|
if ($application->isDeployable()) {
|
|
$deployment_uuid = new Cuid2;
|
|
$result = queue_application_deployment(
|
|
application: $application,
|
|
deployment_uuid: $deployment_uuid,
|
|
commit: $commit,
|
|
force_rebuild: false,
|
|
is_webhook: true
|
|
);
|
|
if ($result['status'] === 'queue_full') {
|
|
return response($result['message'], 429)->header('Retry-After', 60);
|
|
} elseif ($result['status'] === 'skipped') {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'skipped',
|
|
'message' => $result['message'],
|
|
]);
|
|
} else {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'success',
|
|
'message' => 'Deployment queued.',
|
|
]);
|
|
}
|
|
} else {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'failed',
|
|
'message' => 'Auto deployment disabled.',
|
|
]);
|
|
}
|
|
}
|
|
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:updated') {
|
|
if ($application->isPRDeployable()) {
|
|
$deployment_uuid = new Cuid2;
|
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
|
if (! $found) {
|
|
if ($application->build_pack === 'dockercompose') {
|
|
$pr_app = ApplicationPreview::create([
|
|
'git_type' => 'bitbucket',
|
|
'application_id' => $application->id,
|
|
'pull_request_id' => $pull_request_id,
|
|
'pull_request_html_url' => $pull_request_html_url,
|
|
'docker_compose_domains' => $application->docker_compose_domains,
|
|
]);
|
|
$pr_app->generate_preview_fqdn_compose();
|
|
} else {
|
|
$pr_app = ApplicationPreview::create([
|
|
'git_type' => 'bitbucket',
|
|
'application_id' => $application->id,
|
|
'pull_request_id' => $pull_request_id,
|
|
'pull_request_html_url' => $pull_request_html_url,
|
|
]);
|
|
$pr_app->generate_preview_fqdn();
|
|
}
|
|
}
|
|
$result = queue_application_deployment(
|
|
application: $application,
|
|
pull_request_id: $pull_request_id,
|
|
deployment_uuid: $deployment_uuid,
|
|
force_rebuild: false,
|
|
commit: $commit,
|
|
is_webhook: true,
|
|
git_type: 'bitbucket'
|
|
);
|
|
if ($result['status'] === 'queue_full') {
|
|
return response($result['message'], 429)->header('Retry-After', 60);
|
|
} elseif ($result['status'] === 'skipped') {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'skipped',
|
|
'message' => $result['message'],
|
|
]);
|
|
} else {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'success',
|
|
'message' => 'Preview deployment queued.',
|
|
]);
|
|
}
|
|
} else {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'failed',
|
|
'message' => 'Preview deployments disabled.',
|
|
]);
|
|
}
|
|
}
|
|
if ($x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
|
if ($found) {
|
|
// Use comprehensive cleanup that cancels active deployments,
|
|
// kills helper containers, and removes all PR containers
|
|
CleanupPreviewDeployment::run($application, $pull_request_id, $found);
|
|
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'success',
|
|
'message' => 'Preview deployment closed.',
|
|
]);
|
|
} else {
|
|
$return_payloads->push([
|
|
'application' => $application->name,
|
|
'status' => 'failed',
|
|
'message' => 'No preview deployment found.',
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return response($return_payloads);
|
|
} catch (Exception $e) {
|
|
return handleError($e);
|
|
}
|
|
}
|
|
}
|