diff --git a/docs/reference/manifest.md b/docs/reference/manifest.md index 9b5adfc..73aee54 100644 --- a/docs/reference/manifest.md +++ b/docs/reference/manifest.md @@ -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 @@ -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`. diff --git a/src/Steps/Tenant/SyncQueueAlarmStep.php b/src/Steps/Tenant/SyncQueueAlarmStep.php index be59fcc..00ffb46 100644 --- a/src/Steps/Tenant/SyncQueueAlarmStep.php +++ b/src/Steps/Tenant/SyncQueueAlarmStep.php @@ -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; } diff --git a/src/Steps/Tenant/SyncQueueStep.php b/src/Steps/Tenant/SyncQueueStep.php index d1e0001..cebd33d 100644 --- a/src/Steps/Tenant/SyncQueueStep.php +++ b/src/Steps/Tenant/SyncQueueStep.php @@ -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; @@ -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; } }