hasResource($run, 'monitoring_target_id')) { return StepResult::advance(); } $instance = $this->instance($run); $url = 'https://'.$instance->subdomain.'.'.config('provisioning.dns.zone').'/status.php'; // Register with the monitoring service [E] before recording it. // Monitoring is observability, not the product: a paid customer's cloud // must not fail to be delivered because Kuma/the bridge is down. Retry a // few times, then continue DEGRADED with a visible event — unless the // operator declares monitoring mandatory. try { $targetId = $this->monitoring->registerTarget($instance->subdomain, $url); } catch (\Throwable $e) { if (config('provisioning.monitoring.required', false)) { throw $e; // operator wants monitoring to gate delivery } // `attempt` is 0-based (this call IS attempt number attempt+1), so // compare one-based: MONITORING_ATTEMPTS=2 really means two tries. $allowed = max(1, (int) config('provisioning.monitoring.attempts', 2)); if ($run->attempt + 1 < $allowed) { return StepResult::retry(30, 'monitoring unavailable: '.$e->getMessage()); } // Give up on monitoring, keep the cloud. Recorded so it is visible in // the admin console rather than silently missing. $run->events()->create([ 'step' => $this->key(), 'attempt' => $run->attempt, 'outcome' => 'info', 'message' => 'Monitoring übersprungen (Dienst nicht erreichbar): '.$e->getMessage(), ]); return StepResult::advance(); } // Local row BEFORE the breadcrumb (the short-circuit guard). $instance->monitoringTargets()->firstOrCreate( ['external_id' => $targetId], ['url' => $url, 'status' => 'up'], ); $this->recordResource($run, $instance->host, 'monitoring_target_id', $targetId); return StepResult::advance(); } }