Skip to content
Merged
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
2 changes: 1 addition & 1 deletion client-sdks/platform/openapi.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client-sdks/platform/rust/openapi-3.0.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client-sdks/platform/rust/openapi.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/alien-cli/src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,8 @@ pub async fn deploy_task(args: DeployArgs, ctx: ExecutionMode) -> Result<()> {
domains: None,
external_bindings: None,
kubernetes: None,
logs: None,
public_endpoints: None,
};

let create_response = sdk_client
Expand Down
2 changes: 2 additions & 0 deletions crates/alien-cli/src/commands/deployments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,8 @@ async fn create_deployment_task(
domains: None,
external_bindings: None,
kubernetes: None,
logs: None,
public_endpoints: None,
};

let request = NewDeploymentRequest {
Expand Down
34 changes: 33 additions & 1 deletion crates/alien-cloudformation/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const PARAM_CERTIFICATE_ARN: &str = "CertificateArn";
const PARAM_UPDATES_MODE: &str = "UpdatesMode";
const PARAM_TELEMETRY_MODE: &str = "TelemetryMode";
const PARAM_HEARTBEATS_MODE: &str = "HeartbeatsMode";
const PARAM_PARSE_APPLICATION_LEVELS: &str = "ParseApplicationLevels";

const CONDITION_NETWORK_CREATE_AZ2: &str = "NetworkCreateUseAz2";
const CONDITION_NETWORK_CREATE_AZ3: &str = "NetworkCreateUseAz3";
Expand All @@ -48,6 +49,7 @@ const CONDITION_NETWORK_MODE_CREATE: &str = "NetworkModeCreate";
const CONDITION_NETWORK_MODE_USE_EXISTING: &str = "NetworkModeUseExisting";
const CONDITION_HAS_VPC_CIDR: &str = "HasVpcCidr";
const CONDITION_HAS_DOMAIN_NAME: &str = "HasDomainName";
const CONDITION_PARSE_APPLICATION_LEVELS: &str = "ParseApplicationLevelsEnabled";

const OUTPUT_SOURCE_KIND: &str = "DeploymentSourceKind";
const OUTPUT_DEPLOYMENT_ID: &str = "DeploymentId";
Expand Down Expand Up @@ -1053,6 +1055,15 @@ fn add_standard_parameters(
false,
),
);
template.parameters.insert(
PARAM_PARSE_APPLICATION_LEVELS.to_string(),
string_parameter(
"Use a recognized top-level JSON level field as log severity while preserving the raw body.",
Some(settings.parses_application_log_levels().to_string()),
Some(vec![CfExpression::from("false"), CfExpression::from("true")]),
false,
),
);
Ok(())
}

Expand Down Expand Up @@ -1304,6 +1315,10 @@ fn add_standard_conditions(
supports_custom_domain: bool,
target: CloudFormationTarget,
) {
template.conditions.insert(
CONDITION_PARSE_APPLICATION_LEVELS.to_string(),
equals_ref(PARAM_PARSE_APPLICATION_LEVELS, "true"),
);
let has_created_network = stack_has_created_network(stack);
if has_dynamic_aws_network_settings(settings.network.as_ref()) || has_created_network {
template.conditions.insert(
Expand Down Expand Up @@ -1446,7 +1461,8 @@ fn add_console_interface_metadata(
"Parameters": [
PARAM_UPDATES_MODE,
PARAM_TELEMETRY_MODE,
PARAM_HEARTBEATS_MODE
PARAM_HEARTBEATS_MODE,
PARAM_PARSE_APPLICATION_LEVELS
]
}));
}
Expand Down Expand Up @@ -1507,6 +1523,11 @@ fn add_console_interface_metadata(
insert_parameter_label(&mut parameter_labels, PARAM_UPDATES_MODE, "Updates");
insert_parameter_label(&mut parameter_labels, PARAM_TELEMETRY_MODE, "Telemetry");
insert_parameter_label(&mut parameter_labels, PARAM_HEARTBEATS_MODE, "Heartbeats");
insert_parameter_label(
&mut parameter_labels,
PARAM_PARSE_APPLICATION_LEVELS,
"Parse JSON log levels",
);
}

template.metadata.insert(
Expand Down Expand Up @@ -2103,6 +2124,17 @@ fn stack_settings_expression(
("updates", CfExpression::ref_(PARAM_UPDATES_MODE)),
("telemetry", CfExpression::ref_(PARAM_TELEMETRY_MODE)),
("heartbeats", CfExpression::ref_(PARAM_HEARTBEATS_MODE)),
(
"logs",
CfExpression::object([(
"parseApplicationLevels",
CfExpression::if_(
CONDITION_PARSE_APPLICATION_LEVELS,
CfExpression::from(true),
CfExpression::from(false),
),
)]),
),
(
"network",
network_expression(stack, settings.network.as_ref(), target),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn render(stack: &Stack, description: &str) -> CfTemplate {
}

const QUEUE_CONDITION: &str = "InputQueueEnabledIsTrue";
const LOG_LEVEL_CONDITION: &str = "ParseApplicationLevelsEnabled";

fn gate_inputs() -> Vec<StackInputDefinition> {
vec![gate_input(
Expand Down Expand Up @@ -179,14 +180,14 @@ fn a_gated_queue_is_created_only_when_the_deployer_says_yes() {
fn declined_resources_leave_no_registration_entry() {
let template = render(&stack(true), "gated queue");

let queue_on = HashMap::from([(QUEUE_CONDITION, true)]);
let queue_on = HashMap::from([(QUEUE_CONDITION, true), (LOG_LEVEL_CONDITION, false)]);
assert_eq!(
registration_entry_ids(&template, &queue_on),
vec!["execution-sa".to_string(), "jobs".to_string()],
"everything registers when the deployer says yes"
);

let queue_off = HashMap::from([(QUEUE_CONDITION, false)]);
let queue_off = HashMap::from([(QUEUE_CONDITION, false), (LOG_LEVEL_CONDITION, false)]);
assert_eq!(
registration_entry_ids(&template, &queue_off),
vec!["execution-sa".to_string()],
Expand Down Expand Up @@ -254,15 +255,15 @@ fn the_outputs_fallback_survives_every_resource_being_declined() {
assert_eq!(parsed, serde_json::json!([]));
}

/// Ungated stacks gain no conditions: opt-in means no `.enabled(...)`, so no gating.
/// Ungated stacks gain no resource-gating conditions.
#[test]
fn an_ungated_stack_gains_no_conditions() {
fn an_ungated_stack_gains_no_resource_conditions() {
let template = render(&stack(false), "ungated queue");

assert!(
template.conditions.is_empty(),
"nothing is gated, so no condition belongs in the template: {:?}",
template.conditions
template.conditions.len() == 1 && template.conditions.contains_key(LOG_LEVEL_CONDITION),
"only the standard log setting condition belongs in the template: {:?}",
template.conditions,
);
assert!(
template
Expand All @@ -272,7 +273,7 @@ fn an_ungated_stack_gains_no_conditions() {
"no resource may carry a condition when nothing is gated"
);
assert_eq!(
registration_entry_ids(&template, &HashMap::new()),
registration_entry_ids(&template, &HashMap::from([(LOG_LEVEL_CONDITION, false)]),),
vec!["execution-sa".to_string(), "jobs".to_string()],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use alien_core::{
use std::collections::HashMap;

const GATE_CONDITION: &str = "InputFilesEnabledIsTrue";
const LOG_LEVEL_CONDITION: &str = "ParseApplicationLevelsEnabled";
const BUCKET_ID: &str = "Files";

fn gate() -> alien_core::StackInputDefinition {
Expand Down Expand Up @@ -226,7 +227,7 @@ fn a_declined_bucket_leaves_no_registration_entry() {

let accepted = resolve(
&payload,
&HashMap::from([(GATE_CONDITION, true)]),
&HashMap::from([(GATE_CONDITION, true), (LOG_LEVEL_CONDITION, false)]),
Declined::Removed,
)
.expect("payload survives when the gate is on");
Expand All @@ -238,7 +239,7 @@ fn a_declined_bucket_leaves_no_registration_entry() {

let declined = resolve(
&payload,
&HashMap::from([(GATE_CONDITION, false)]),
&HashMap::from([(GATE_CONDITION, false), (LOG_LEVEL_CONDITION, false)]),
Declined::Removed,
)
.expect("payload survives when the gate is off");
Expand All @@ -254,15 +255,15 @@ fn a_declined_bucket_leaves_no_registration_entry() {
);
}

/// Ungated stacks gain no conditions: opt-in means no `.enabled(...)`, so no gating.
/// Ungated stacks gain no resource-gating conditions.
#[test]
fn an_ungated_storage_stack_gains_no_conditions() {
fn an_ungated_storage_stack_gains_no_resource_conditions() {
let template = render(&ungated_stack(), "ungated storage stack");

assert!(
template.conditions.is_empty(),
"nothing is gated, so no condition belongs in the template: {:?}",
template.conditions
template.conditions.len() == 1 && template.conditions.contains_key(LOG_LEVEL_CONDITION),
"only the standard log setting condition belongs in the template: {:?}",
template.conditions,
);
for (id, resource) in template.resources.iter() {
assert_eq!(
Expand Down
17 changes: 9 additions & 8 deletions crates/alien-cloudformation/tests/generator/enabled_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::collections::HashMap;

const GATE_PARAMETER: &str = "InputStoreEnabled";
const GATE_CONDITION: &str = "InputStoreEnabledIsTrue";
const LOG_LEVEL_CONDITION: &str = "ParseApplicationLevelsEnabled";
const TABLE_ID: &str = "Store";

fn gate() -> alien_core::StackInputDefinition {
Expand Down Expand Up @@ -166,7 +167,7 @@ fn a_declined_resource_leaves_no_registration_entry() {

let accepted = resolve(
&payload,
&HashMap::from([(GATE_CONDITION, true)]),
&HashMap::from([(GATE_CONDITION, true), (LOG_LEVEL_CONDITION, false)]),
Declined::Removed,
)
.expect("payload survives when the gate is on");
Expand All @@ -186,7 +187,7 @@ fn a_declined_resource_leaves_no_registration_entry() {

let declined = resolve(
&payload,
&HashMap::from([(GATE_CONDITION, false)]),
&HashMap::from([(GATE_CONDITION, false), (LOG_LEVEL_CONDITION, false)]),
Declined::Removed,
)
.expect("payload survives when the gate is off");
Expand Down Expand Up @@ -223,16 +224,16 @@ fn the_declined_entry_is_removed_rather_than_blanked() {
);
}

/// An ungated stack's output must not change, or every existing deployment
/// would see a template diff on its next re-apply.
/// An ungated stack has no resource-gating conditions. Standard configuration
/// conditions may still exist, but they must not be attached to resources.
#[test]
fn an_ungated_stack_gains_no_conditions() {
fn an_ungated_stack_gains_no_resource_conditions() {
let (template, _) = render(&ungated_kv_stack(), "ungated kv stack");

assert!(
template.conditions.is_empty(),
"nothing is gated, so no condition belongs in the template: {:?}",
template.conditions
template.conditions.len() == 1 && template.conditions.contains_key(LOG_LEVEL_CONDITION),
"only the standard log setting condition belongs in the template: {:?}",
template.conditions,
);
assert_eq!(
template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use alien_core::{
};
use std::collections::HashMap;

const LOG_LEVEL_CONDITION: &str = "ParseApplicationLevelsEnabled";

fn gated_fixture(resource_type: &str) -> Option<(Stack, StackSettings)> {
let base = || {
Stack::new("matrix-stack".to_string()).inputs(vec![gate_input(
Expand Down Expand Up @@ -234,7 +236,7 @@ fn assert_gated_render(resource_type: &str, stack: &Stack, settings: StackSettin
);

let payload = registration_payload(&template);
let mut answers = HashMap::from([(condition_name, false)]);
let mut answers = HashMap::from([(condition_name, false), (LOG_LEVEL_CONDITION, false)]);
// A fixture that declares a network puts its conditions in the payload too. They are the
// deploy-time answers the fixture asks for, not the gate under test.
answers.extend(network_answers());
Expand Down Expand Up @@ -299,7 +301,10 @@ fn a_gated_vault_renders_conditionally() {
let payload = registration_payload(&template);
let accepted = resolve(
&payload,
&HashMap::from([("InputFixtureEnabledIsTrue", true)]),
&HashMap::from([
("InputFixtureEnabledIsTrue", true),
(LOG_LEVEL_CONDITION, false),
]),
Declined::Removed,
)
.expect("payload resolves");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ Metadata:
- UpdatesMode
- TelemetryMode
- HeartbeatsMode
- ParseApplicationLevels
ParameterLabels:
HeartbeatsMode:
default: Heartbeats
ManagingAccountId:
default: Image account ID
ManagingRoleArn:
default: Management role ARN
ParseApplicationLevels:
default: Parse JSON log levels
TelemetryMode:
default: Telemetry
UpdatesMode:
Expand Down Expand Up @@ -68,6 +71,18 @@ Parameters:
AllowedValues:
- "off"
- "on"
ParseApplicationLevels:
Type: String
Description: Use a recognized top-level JSON level field as log severity while preserving the raw body.
Default: 'false'
AllowedValues:
- 'false'
- 'true'
Conditions:
ParseApplicationLevelsEnabled:
Fn::Equals:
- Ref: ParseApplicationLevels
- 'true'
Resources:
ExecutionSaRole:
Type: AWS::IAM::Role
Expand Down Expand Up @@ -160,6 +175,12 @@ Outputs:
Ref: TelemetryMode
heartbeats:
Ref: HeartbeatsMode
logs:
parseApplicationLevels:
Fn::If:
- ParseApplicationLevelsEnabled
- true
- false
network:
Ref: AWS::NoValue
Description: Deployment registration settings JSON.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ Metadata:
- UpdatesMode
- TelemetryMode
- HeartbeatsMode
- ParseApplicationLevels
ParameterLabels:
HeartbeatsMode:
default: Heartbeats
ManagingAccountId:
default: Image account ID
ManagingRoleArn:
default: Management role ARN
ParseApplicationLevels:
default: Parse JSON log levels
TelemetryMode:
default: Telemetry
UpdatesMode:
Expand Down Expand Up @@ -68,6 +71,18 @@ Parameters:
AllowedValues:
- "off"
- "on"
ParseApplicationLevels:
Type: String
Description: Use a recognized top-level JSON level field as log severity while preserving the raw body.
Default: 'false'
AllowedValues:
- 'false'
- 'true'
Conditions:
ParseApplicationLevelsEnabled:
Fn::Equals:
- Ref: ParseApplicationLevels
- 'true'
Resources:
RegistryRepository:
Type: AWS::ECR::Repository
Expand Down Expand Up @@ -249,6 +264,12 @@ Outputs:
Ref: TelemetryMode
heartbeats:
Ref: HeartbeatsMode
logs:
parseApplicationLevels:
Fn::If:
- ParseApplicationLevelsEnabled
- true
- false
network:
Ref: AWS::NoValue
Description: Deployment registration settings JSON.
Expand Down
Loading
Loading