mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
Compare commits
No commits in common. "ae68b9449f94990a98d4c2c492e1979d2611e98b" and "8e4a5ed4d8b23fc4667d9f2b2c4b721dbd1ca76b" have entirely different histories.
ae68b9449f
...
8e4a5ed4d8
13 changed files with 43 additions and 166 deletions
|
|
@ -17,11 +17,7 @@
|
|||
# the shared profile/SSO, or the EC2/ECS instance role). Prefer an instance role in prod.
|
||||
|
||||
# --- Sentry error and transaction monitoring ---
|
||||
# The DSN is public ingestion configuration, not a secret. Leave it blank locally
|
||||
# to keep telemetry inactive. AWS deployments receive SENTRY_DSN and
|
||||
# SENTRY_ENVIRONMENT as Elastic Beanstalk environment settings managed by
|
||||
# Terraform: dev is labeled "development", staging "staging". Future production
|
||||
# wiring will pass the production DSN through the same sentry_dsn module variable.
|
||||
# Leave the DSN blank to keep telemetry inactive. Set the environment per deployment.
|
||||
SENTRY_DSN=
|
||||
SENTRY_ENVIRONMENT=development
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sentry;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Helper
|
||||
{
|
||||
|
|
@ -9,18 +8,15 @@ namespace Api.SeaHavenIndustries.Helper
|
|||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly ILogger<VendorDocumentScanWorker> _logger;
|
||||
private readonly IHub _sentryHub;
|
||||
|
||||
public VendorDocumentScanWorker(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IWebHostEnvironment environment,
|
||||
ILogger<VendorDocumentScanWorker> logger,
|
||||
IHub sentryHub)
|
||||
ILogger<VendorDocumentScanWorker> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_environment = environment;
|
||||
_logger = logger;
|
||||
_sentryHub = sentryHub;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
|
|
@ -34,54 +30,36 @@ namespace Api.SeaHavenIndustries.Helper
|
|||
|
||||
private async Task ScanPendingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("vendor-documents.scan-pending", "task");
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
var scanner = scope.ServiceProvider.GetRequiredService<IDocumentScanner>();
|
||||
var pending = await db.VendorCompletionDocuments
|
||||
.Where(document => document.ScanStatus == "Pending")
|
||||
.OrderBy(document => document.CreatedDate)
|
||||
.Take(20)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var document in pending)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
var scanner = scope.ServiceProvider.GetRequiredService<IDocumentScanner>();
|
||||
var pending = await db.VendorCompletionDocuments
|
||||
.Where(document => document.ScanStatus == "Pending")
|
||||
.OrderBy(document => document.CreatedDate)
|
||||
.Take(20)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var document in pending)
|
||||
var path = VendorDocumentStorage.ResolvePath(_environment.ContentRootPath, document);
|
||||
var result = await scanner.ScanAsync(path, cancellationToken);
|
||||
if (result == DocumentScanResult.Unavailable)
|
||||
{
|
||||
var path = VendorDocumentStorage.ResolvePath(_environment.ContentRootPath, document);
|
||||
var result = await scanner.ScanAsync(path, cancellationToken);
|
||||
if (result == DocumentScanResult.Unavailable)
|
||||
{
|
||||
_logger.LogWarning("Vendor document scan service unavailable; document {DocumentId} remains quarantined", document.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
document.ScanStatus = result == DocumentScanResult.Passed ? "Passed" : "Rejected";
|
||||
document.ReviewStatus = result == DocumentScanResult.Passed ? "Processing" : "Rejected";
|
||||
document.RejectionReason = result == DocumentScanResult.Infected
|
||||
? "The upload failed malware scanning."
|
||||
: document.RejectionReason;
|
||||
document.ScannedAt = DateTime.UtcNow;
|
||||
document.LastModificationTime = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
if (result == DocumentScanResult.Infected && File.Exists(path))
|
||||
File.Delete(path);
|
||||
_logger.LogWarning("Vendor document scan service unavailable; document {DocumentId} remains quarantined", document.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
throw;
|
||||
document.ScanStatus = result == DocumentScanResult.Passed ? "Passed" : "Rejected";
|
||||
document.ReviewStatus = result == DocumentScanResult.Passed ? "Processing" : "Rejected";
|
||||
document.RejectionReason = result == DocumentScanResult.Infected
|
||||
? "The upload failed malware scanning."
|
||||
: document.RejectionReason;
|
||||
document.ScannedAt = DateTime.UtcNow;
|
||||
document.LastModificationTime = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
if (result == DocumentScanResult.Infected && File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Microsoft.Extensions.Options;
|
|||
using SeaHaven.Services.Implementation;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using Api.SeaHavenIndustries.Options;
|
||||
using Sentry;
|
||||
|
||||
namespace Api.SeaHavenIndustries.HostedServices
|
||||
{
|
||||
|
|
@ -12,21 +11,18 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
private readonly ILogger<PastDueCacheHostedService> _logger;
|
||||
private readonly PastDueCacheJobOptions _options;
|
||||
private readonly WorkOrderJobRunState _runState;
|
||||
private readonly IHub _sentryHub;
|
||||
private DateOnly? _lastRunDate;
|
||||
|
||||
public PastDueCacheHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<WorkOrderJobsOptions> options,
|
||||
WorkOrderJobRunState runState,
|
||||
ILogger<PastDueCacheHostedService> logger,
|
||||
IHub sentryHub)
|
||||
ILogger<PastDueCacheHostedService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
_options = options.Value.PastDueCache;
|
||||
_runState = runState;
|
||||
_sentryHub = sentryHub;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
|
|
@ -49,9 +45,6 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
|
||||
private async Task<bool> RunJobAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("workorders.past-due-cache-job", "task");
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
|
|
@ -60,19 +53,12 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
|
||||
_runState.LastPastDueCacheRunUtc = DateTime.UtcNow;
|
||||
_runState.LastPastDueCacheError = null;
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_runState.LastPastDueCacheError = ex.Message;
|
||||
_logger.LogError(ex, "PastDue cache hosted job failed.");
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using SeaHaven.Services.Configuration;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using Sentry;
|
||||
|
||||
namespace Api.SeaHavenIndustries.HostedServices
|
||||
{
|
||||
|
|
@ -10,43 +9,32 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<UpliftLifecycleHostedService> _logger;
|
||||
private readonly ApprovalsOptions _options;
|
||||
private readonly IHub _sentryHub;
|
||||
|
||||
public UpliftLifecycleHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<ApprovalsOptions> options,
|
||||
ILogger<UpliftLifecycleHostedService> logger,
|
||||
IHub sentryHub)
|
||||
ILogger<UpliftLifecycleHostedService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
_options = options.Value;
|
||||
_sentryHub = sentryHub;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("uplifts.lifecycle-sweep", "task");
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
{
|
||||
await SweepAsync(stoppingToken);
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Uplift lifecycle sweep failed; will retry on next interval");
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
}
|
||||
await SweepAsync(stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Uplift lifecycle sweep failed; will retry on next interval");
|
||||
}
|
||||
|
||||
await Task.Delay(_options.EffectiveSweepInterval, stoppingToken);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using SeaHaven.Services.Configuration;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using Sentry;
|
||||
|
||||
namespace Api.SeaHavenIndustries.HostedServices
|
||||
{
|
||||
|
|
@ -11,20 +10,17 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
private readonly IOptionsMonitor<WorkOrderReconciliationOptions> _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<WorkOrderReconciliationHostedService> _logger;
|
||||
private readonly IHub _sentryHub;
|
||||
|
||||
public WorkOrderReconciliationHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptionsMonitor<WorkOrderReconciliationOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<WorkOrderReconciliationHostedService> logger,
|
||||
IHub sentryHub)
|
||||
ILogger<WorkOrderReconciliationHostedService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_options = options;
|
||||
_timeProvider = timeProvider;
|
||||
_logger = logger;
|
||||
_sentryHub = sentryHub;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
|
|
@ -60,50 +56,29 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
|
||||
private async Task TriggerAsync(string reason, CancellationToken cancellationToken)
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("workorders.reconciliation-trigger", "task");
|
||||
transaction.Description = reason;
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var runner = scope.ServiceProvider.GetRequiredService<IWorkOrderReconciliationRunner>();
|
||||
await runner.TriggerAsync(reason, cancellationToken);
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "Could not queue procurement reconciliation.");
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunPendingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("workorders.reconciliation-run-pending", "task");
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var runner = scope.ServiceProvider.GetRequiredService<IWorkOrderReconciliationRunner>();
|
||||
await runner.RunPendingAsync(cancellationToken);
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "Could not execute procurement reconciliation.");
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using SeaHaven.Services.Helpers;
|
|||
using SeaHaven.Services.Implementation;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using Api.SeaHavenIndustries.Options;
|
||||
using Sentry;
|
||||
|
||||
namespace Api.SeaHavenIndustries.HostedServices
|
||||
{
|
||||
|
|
@ -13,21 +12,18 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
private readonly ILogger<WorkOrderWeekRolledHostedService> _logger;
|
||||
private readonly WeekRolledJobOptions _options;
|
||||
private readonly WorkOrderJobRunState _runState;
|
||||
private readonly IHub _sentryHub;
|
||||
private DateOnly? _lastRunDate;
|
||||
|
||||
public WorkOrderWeekRolledHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<WorkOrderJobsOptions> options,
|
||||
WorkOrderJobRunState runState,
|
||||
ILogger<WorkOrderWeekRolledHostedService> logger,
|
||||
IHub sentryHub)
|
||||
ILogger<WorkOrderWeekRolledHostedService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
_options = options.Value.WeekRolled;
|
||||
_runState = runState;
|
||||
_sentryHub = sentryHub;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
|
|
@ -50,9 +46,6 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
|
||||
private async Task<bool> RunJobAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
using var sentryScope = _sentryHub.PushScope();
|
||||
var transaction = _sentryHub.StartTransaction("workorders.week-rolled-job", "task");
|
||||
_sentryHub.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try
|
||||
{
|
||||
var sourceWeekStart = WorkOrderOperationalWeek.GetPreviousOperationalWeekStart(DateTime.UtcNow);
|
||||
|
|
@ -63,19 +56,12 @@ namespace Api.SeaHavenIndustries.HostedServices
|
|||
|
||||
_runState.LastWeekRolledRunUtc = DateTime.UtcNow;
|
||||
_runState.LastWeekRolledError = null;
|
||||
transaction.Finish(SpanStatus.Ok);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
transaction.Finish(SpanStatus.Cancelled);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_runState.LastWeekRolledError = ex.Message;
|
||||
_logger.LogError(ex, "WeekRolled hosted job failed.");
|
||||
transaction.Finish(ex, SpanStatus.InternalError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ var builder = WebApplication.CreateBuilder(args);
|
|||
|
||||
builder.WebHost.UseSentry(options =>
|
||||
{
|
||||
options.Dsn = builder.Configuration["SENTRY_DSN"] ?? string.Empty;
|
||||
options.Environment = builder.Configuration["SENTRY_ENVIRONMENT"]
|
||||
?? builder.Environment.EnvironmentName.ToLowerInvariant();
|
||||
options.TracesSampleRate = 1.0;
|
||||
options.SendDefaultPii = false;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ Secret metadata is managed, but secret values are never authored in Terraform
|
|||
configuration. Elastic Beanstalk receives secret values through
|
||||
`environmentsecrets` ARN/key references.
|
||||
|
||||
The Sentry DSN is public ingestion configuration, not a secret: each root passes
|
||||
it through the required `sentry_dsn` module variable as the plain
|
||||
`SENTRY_DSN` environment setting. `SENTRY_ENVIRONMENT` is `development` for the
|
||||
dev root and the root environment name otherwise. Production has no Terraform
|
||||
root yet; production Sentry wiring will reuse the same variable when one is
|
||||
added.
|
||||
|
||||
## HCP credentials
|
||||
|
||||
Org-baseline CloudFormation owns the HCP Terraform plan/apply roles and their
|
||||
|
|
|
|||
|
|
@ -21,12 +21,7 @@ Secret values are not Terraform resources, variables, outputs, or managed EB
|
|||
settings. Terraform manages the app-config secret shell and maps approved JSON
|
||||
keys through `aws:elasticbeanstalk:application:environmentsecrets` using
|
||||
`secret-arn:json-key` references. Ordinary application environment settings are
|
||||
limited to non-secret ASP.NET, webhook, and Sentry configuration. The Sentry
|
||||
DSN is public ingestion configuration, delivered as the plain `SENTRY_DSN`
|
||||
setting from the required `sentry_dsn` variable. `SENTRY_ENVIRONMENT` is
|
||||
`development` for the dev root and the environment name (`staging`) otherwise.
|
||||
No production root exists yet; production Sentry wiring will follow the same
|
||||
variable when one is added. The pinned .NET 8
|
||||
limited to non-secret ASP.NET and webhook configuration. The pinned .NET 8
|
||||
AL2023 platform 3.11.3 supports Secrets Manager JSON-key extraction.
|
||||
|
||||
## Mandatory live secret migration
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ module "environment" {
|
|||
webhook_decrypt_policy_sid = "DecryptWebhookSecretViaSecretsManager"
|
||||
dynamo_reader_role_arn = "arn:aws:iam::328440206208:role/shoc-dynamo-reader"
|
||||
dynamo_policy_sid = "AssumeDynamoReaderInMain"
|
||||
sentry_dsn = "https://15aef90a0e14b5b7c12ec5824b978979@o4511989453029376.ingest.de.sentry.io/4511990377152592"
|
||||
github_repo = "Sea-Haven-Industries/shoc-backend"
|
||||
github_environment = "dev"
|
||||
github_deploy_role_name = "githubdeploy-shoc-backend-dev"
|
||||
|
|
|
|||
|
|
@ -408,16 +408,6 @@ locals {
|
|||
name = "WorkOrderWebhook__Region"
|
||||
value = var.aws_region
|
||||
},
|
||||
{
|
||||
namespace = "aws:elasticbeanstalk:application:environment"
|
||||
name = "SENTRY_DSN"
|
||||
value = var.sentry_dsn
|
||||
},
|
||||
{
|
||||
namespace = "aws:elasticbeanstalk:application:environment"
|
||||
name = "SENTRY_ENVIRONMENT"
|
||||
value = var.environment == "dev" ? "development" : var.environment
|
||||
},
|
||||
],
|
||||
var.instance_security_group_id == null ? [] : [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,11 +71,6 @@ variable "shared_certificate_arn" {
|
|||
description = "Existing shared certificate for dev/staging"
|
||||
}
|
||||
|
||||
variable "sentry_dsn" {
|
||||
type = string
|
||||
description = "Sentry DSN for error and transaction monitoring. The DSN is public ingestion configuration, not a secret: it is safe to expose and is delivered as a plain Elastic Beanstalk environment setting instead of Secrets Manager."
|
||||
}
|
||||
|
||||
variable "runtime_role_name" {
|
||||
type = string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ module "environment" {
|
|||
"SendGrid__ApiKey",
|
||||
]
|
||||
webhook_secret_arn = "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB"
|
||||
sentry_dsn = "https://15aef90a0e14b5b7c12ec5824b978979@o4511989453029376.ingest.de.sentry.io/4511990377152592"
|
||||
github_repo = "Sea-Haven-Industries/shoc-backend"
|
||||
github_environment = "staging"
|
||||
github_deploy_role_name = "githubdeploy-shoc-backend-staging"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue