Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/reference/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ environments:
codedeploy:
strategy: without-load-balancing
sqs:
queues:
- default
- high
depth-alarm-evaluation-periods: 3
depth-alarm-period: 300
depth-alarm-threshold: 100
Expand Down Expand Up @@ -117,6 +120,49 @@ aws:

`logging` toggles the EventBridge → CloudWatch pipeline; `log-retention-days` overrides the log retention.

#### IVS Real-Time recording

Enable S3 composite recording for IVS Real-Time stages:

```yaml
aws:
ivs:
recording:
real_time: true
```

Setting `recording.real_time` to `true` provisions the S3 bucket, IVS `StorageConfiguration`, and `EncoderConfiguration` required for composite recording.

| Key | Description |
|---|---|
| `recording.real_time` | Set to `true` to enable IVS Real-Time composite recording provisioning. Provisions an auto-named S3 bucket (`yolo-{env}-{app}-ivs-realtime-recordings`), a `StorageConfiguration` pointing to that bucket, and an `EncoderConfiguration` (720p30). |

After running `sync:recording`, three values are printed for the app's `.env`:

| Env var | Description |
|---|---|
| `AWS_IVS_REALTIME_RECORDINGS_BUCKET` | Name of the S3 bucket IVS writes recordings to |
| `AWS_IVS_STORAGE_CONFIGURATION_ARN` | ARN passed to `createStage` for automatic participant recording |
| `AWS_IVS_ENCODER_CONFIGURATION_ARN` | ARN passed to `startComposition` to define video resolution and bitrate |

Omitting `recording` entirely skips all recording steps without affecting existing resources.

### `aws.sqs.queues`

Defines the SQS queue types provisioned per tenant. Each type is appended to the tenant queue name — `default` uses the base name, any other type appends `-{type}`.

```yaml
aws:
sqs:
queues:
- default # → yolo-{env}-{app}-{tenantId}
- high # → yolo-{env}-{app}-{tenantId}-high
```

Omitting `queues` defaults to `[default]`, preserving existing behaviour. A CloudWatch depth alarm is created for each queue type.

Use additional queue types to isolate time-sensitive jobs — for example, routing live-event messages to a dedicated high-priority queue so they don't compete with default workers.

### `mysqldump`

Enable scheduled MySQL backups via `mysqldump`.
Expand Down
70 changes: 39 additions & 31 deletions src/Steps/Tenant/SyncQueueAlarmStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,51 @@ class SyncQueueAlarmStep extends TenantStep
{
public function __invoke(array $options): StepResult
{
$alarmName = Helpers::keyedResourceName(sprintf('%s-queue-depth-alarm', $this->tenantId()));

try {
AwsResources::alarm($alarmName);
} catch (ResourceDoesNotExistException) {
// CloudWatch accepts an upsert operation, so we'll
// always sync the alarm with the desired state.
if (Arr::get($options, 'dry-run')) {
return StepResult::WOULD_SYNC;
}

$snsTopic = AwsResources::alarmTopic();

if (Arr::get($options, 'dry-run')) {
return StepResult::WOULD_SYNC;
}
foreach (Manifest::get('aws.sqs.queues', ['default']) as $type) {
$queueName = $type === 'default'
? Helpers::keyedResourceName($this->tenantId())
: Helpers::keyedResourceName(sprintf('%s-%s', $this->tenantId(), $type));

$alarmName = $type === 'default'
? Helpers::keyedResourceName(sprintf('%s-queue-depth-alarm', $this->tenantId()))
: Helpers::keyedResourceName(sprintf('%s-%s-queue-depth-alarm', $this->tenantId(), $type));

Aws::cloudWatch()->putMetricAlarm([
'ActionsEnabled' => true,
'AlarmName' => $alarmName,
'AlarmDescription' => 'Alarm if queue is too deep. Created by yolo CLI',
'ComparisonOperator' => 'GreaterThanThreshold',
'Dimensions' => [
[
'Name' => 'QueueName',
'Value' => Helpers::keyedResourceName($this->tenantId()),
try {
AwsResources::alarm($alarmName);
} catch (ResourceDoesNotExistException) {
// CloudWatch accepts an upsert operation, so we'll
// always sync the alarm with the desired state.
}

Aws::cloudWatch()->putMetricAlarm([
'ActionsEnabled' => true,
'AlarmName' => $alarmName,
'AlarmDescription' => 'Alarm if queue is too deep. Created by yolo CLI',
'ComparisonOperator' => 'GreaterThanThreshold',
'Dimensions' => [
[
'Name' => 'QueueName',
'Value' => $queueName,
],
],
],
'EvaluationPeriods' => Manifest::get('aws.sqs.depth-alarm-evaluation-periods', 3), // number of breaches of the Period before alarm
'MetricName' => 'ApproximateNumberOfMessagesVisible',
'Namespace' => 'AWS/SQS',
'Period' => Manifest::get('aws.sqs.depth-alarm-period', 300), // time to evaluate the metric
'Statistic' => 'Average',
'Threshold' => Manifest::get('aws.sqs.depth-alarm-threshold', 100),
'TreatMissingData' => 'notBreaching',
'AlarmActions' => [$snsTopic['TopicArn']],
'OKActions' => [$snsTopic['TopicArn']],
...Aws::tags(),
]);
'EvaluationPeriods' => Manifest::get('aws.sqs.depth-alarm-evaluation-periods', 3), // number of breaches of the Period before alarm
'MetricName' => 'ApproximateNumberOfMessagesVisible',
'Namespace' => 'AWS/SQS',
'Period' => Manifest::get('aws.sqs.depth-alarm-period', 300), // time to evaluate the metric
'Statistic' => 'Average',
'Threshold' => Manifest::get('aws.sqs.depth-alarm-threshold', 100),
'TreatMissingData' => 'notBreaching',
'AlarmActions' => [$snsTopic['TopicArn']],
'OKActions' => [$snsTopic['TopicArn']],
...Aws::tags(),
]);
}

return StepResult::SYNCED;
}
Expand Down
39 changes: 23 additions & 16 deletions src/Steps/Tenant/SyncQueueStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Arr;
use Codinglabs\YoloAlpha\Aws;
use Codinglabs\YoloAlpha\Helpers;
use Codinglabs\YoloAlpha\Manifest;
use Codinglabs\YoloAlpha\AwsResources;
use Codinglabs\YoloAlpha\Enums\StepResult;
use Codinglabs\YoloAlpha\Steps\TenantStep;
Expand All @@ -14,26 +15,32 @@ class SyncQueueStep extends TenantStep
{
public function __invoke(array $options): StepResult
{
$name = Helpers::keyedResourceName($this->tenantId());
$result = StepResult::SYNCED;

try {
AwsResources::queue($name);
foreach (Manifest::get('aws.sqs.queues', ['default']) as $type) {
$name = $type === 'default'
? Helpers::keyedResourceName($this->tenantId())
: Helpers::keyedResourceName(sprintf('%s-%s', $this->tenantId(), $type));

return StepResult::SYNCED;
} catch (ResourceDoesNotExistException) {
if (! Arr::get($options, 'dry-run')) {
Aws::sqs()->createQueue([
'QueueName' => $name,
'Attributes' => [
'MessageRetentionPeriod' => '1209600', // 14 days
],
...Aws::tags(wrap: 'tags', associative: true),
]);
try {
AwsResources::queue($name);
} catch (ResourceDoesNotExistException) {
if (! Arr::get($options, 'dry-run')) {
Aws::sqs()->createQueue([
'QueueName' => $name,
'Attributes' => [
'MessageRetentionPeriod' => '1209600', // 14 days
],
...Aws::tags(wrap: 'tags', associative: true),
]);

return StepResult::CREATED;
$result = StepResult::CREATED;
} else {
$result = StepResult::WOULD_CREATE;
}
}

return StepResult::WOULD_CREATE;
}

return $result;
}
}
Loading