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
6 changes: 6 additions & 0 deletions crates/alien-core/src/deployment/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ pub struct RuntimeMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub setup_update_authorization: Option<SetupUpdateAuthorization>,

/// Last generated CLI package revision whose direct setup was applied.
/// This lets a newer generated CLI refresh setup-owned infrastructure once
/// before handing runtime changes back to the hosted manager.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direct_setup_revision: Option<String>,

/// Whether cross-account registry access has been successfully granted.
/// Set to true after the manager successfully sets the ECR/GAR repo policy
/// for this deployment's target account. Prevents redundant API calls on
Expand Down
5 changes: 5 additions & 0 deletions crates/alien-core/src/embedded_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ pub struct DeployCliConfig {
/// Unix install script URL for this CLI package.
#[serde(skip_serializing_if = "Option::is_none")]
pub install_script_url: Option<String>,
/// Package build revision for setup-affecting code and artifacts. Existing
/// direct-setup deployments reconcile setup once when this value changes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub setup_revision: Option<String>,
/// Branded environment variable that contains the deployment token.
#[serde(skip_serializing_if = "Option::is_none")]
pub token_env_var: Option<String>,
Expand Down Expand Up @@ -195,6 +199,7 @@ mod tests {
"https://packages.example.com/acme/machine-bundle.json".into(),
),
install_script_url: Some("https://packages.example.com/acme/install.sh".into()),
setup_revision: Some("build-123".into()),
token_env_var: Some("ACME_DEPLOYMENT_TOKEN".into()),
name: Some("acme-deploy".into()),
};
Expand Down
2 changes: 2 additions & 0 deletions crates/alien-deploy-cli/src/commands/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2944,6 +2944,7 @@ mod tests {
"https://packages.example.com/machines/manifest.json".to_string(),
),
install_script_url: None,
setup_revision: None,
token_env_var: None,
name: None,
display_name: None,
Expand Down Expand Up @@ -3061,6 +3062,7 @@ mod tests {
"https://packages.example.com/machines/manifest.json".to_string(),
),
install_script_url: None,
setup_revision: None,
token_env_var: None,
name: None,
display_name: None,
Expand Down
102 changes: 100 additions & 2 deletions crates/alien-deploy-cli/src/commands/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,29 @@ mod tests {
}
}

#[test]
fn hosted_setup_reconciles_each_packaged_revision_once() {
for status in ["running", "update-failed", "refresh-failed"] {
assert!(hosted_setup_reconcile_required(
status,
Some("package-b"),
Some("package-a")
));
assert!(!hosted_setup_reconcile_required(
status,
Some("package-b"),
Some("package-b")
));
}

assert!(!hosted_setup_reconcile_required(
"updating",
Some("package-b"),
Some("package-a")
));
assert!(!hosted_setup_reconcile_required("running", None, None));
}

#[test]
fn stable_channel_accepts_exact_semver_tag() {
assert_eq!(
Expand Down Expand Up @@ -1370,7 +1393,7 @@ pub async fn up_command(args: UpArgs, embedded_config: Option<&DeployCliConfig>)
)?;

// Check if the deployment is already active — nothing to do.
let current_deployment = client
let mut current_deployment = client
.get_deployment()
.id(&deployment_id)
.send()
Expand All @@ -1383,6 +1406,51 @@ pub async fn up_command(args: UpArgs, embedded_config: Option<&DeployCliConfig>)

let hosted_platform =
manager_url.trim_end_matches('/') != resolved.base_url.trim_end_matches('/');
let setup_revision = embedded_config.and_then(|config| config.setup_revision.as_deref());
let applied_setup_revision = current_deployment
.runtime_metadata
.as_ref()
.and_then(|metadata| serde_json::to_value(metadata).ok())
.and_then(|metadata| {
metadata
.get("directSetupRevision")
.and_then(serde_json::Value::as_str)
.map(ToString::to_string)
});
if init.deployment_model == DeploymentModel::Push
&& hosted_platform
&& requires_install_context(platform)
&& hosted_setup_reconcile_required(
&current_deployment.status,
setup_revision,
applied_setup_revision.as_deref(),
)
{
output::info("Refreshing setup-owned infrastructure for this CLI revision...");
run_push_model(
&client,
&deployment_id,
platform,
base_platform,
&manager_url,
&effective_token,
install_management_config.clone(),
&args.network,
None,
setup_revision,
)
.await?;
current_deployment = client
.get_deployment()
.id(&deployment_id)
.send()
.await
.into_sdk_error()
.context(ErrorData::ConfigurationError {
message: "Failed to refresh deployment after setup reconciliation".to_string(),
})?
.into_inner();
}
if supports_hosted_compute_update(&current_deployment.status)
&& init.deployment_model == DeploymentModel::Push
&& hosted_platform
Expand Down Expand Up @@ -1507,6 +1575,7 @@ pub async fn up_command(args: UpArgs, embedded_config: Option<&DeployCliConfig>)
&effective_token,
None,
None,
setup_revision,
)
.await?;

Expand Down Expand Up @@ -1555,6 +1624,7 @@ pub async fn up_command(args: UpArgs, embedded_config: Option<&DeployCliConfig>)
install_management_config,
&args.network,
Some(on_progress),
setup_revision,
)
.await?;

Expand Down Expand Up @@ -2808,6 +2878,16 @@ fn supports_hosted_compute_update(status: &str) -> bool {
)
}

fn hosted_setup_reconcile_required(
status: &str,
packaged_revision: Option<&str>,
applied_revision: Option<&str>,
) -> bool {
matches!(status, "running" | "update-failed" | "refresh-failed")
&& packaged_revision.is_some()
&& packaged_revision != applied_revision
}

/// Whether the hosted platform must receive the requested compute target.
///
/// Platform persists the desired settings before the deployment engine applies
Expand Down Expand Up @@ -3766,6 +3846,7 @@ async fn run_push_model(
management_config: Option<ManagementConfig>,
network_args: &NetworkArgs,
on_progress: Option<alien_deployment::runner::ProgressCallback>,
setup_revision: Option<&str>,
) -> Result<()> {
let credential_platform = base_platform.unwrap_or(platform);
let client_config = ClientConfig::from_std_env(credential_platform)
Expand All @@ -3788,6 +3869,7 @@ async fn run_push_model(
deployment_token,
Some(network_args),
on_progress,
setup_revision,
)
.await
}
Expand Down Expand Up @@ -3819,6 +3901,7 @@ pub async fn push_initial_setup(
deployment_token: &str,
network_args: Option<&NetworkArgs>,
on_progress: Option<alien_deployment::runner::ProgressCallback>,
setup_revision: Option<&str>,
) -> Result<()> {
let setup_management_config = management_config.clone();

Expand Down Expand Up @@ -4065,7 +4148,12 @@ pub async fn push_initial_setup(
message: "Failed to deserialize runtime_metadata from manager".to_string(),
})?;

if state.status == DeploymentStatus::Running {
if matches!(
state.status,
DeploymentStatus::Running
| DeploymentStatus::UpdateFailed
| DeploymentStatus::RefreshFailed
Comment on lines +4153 to +4155

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Running refresh lacks target release

When a newer generated CLI refreshes a running or refresh-failed hosted deployment, push_initial_setup reconstructs target_release only from the cleared desired_release_id, causing the command to fail with “A setup update requires a desired release” instead of refreshing setup from the current release.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-deploy-cli/src/commands/up.rs
Line: 4153-4155

Comment:
**Running refresh lacks target release**

When a newer generated CLI refreshes a running or refresh-failed hosted deployment, `push_initial_setup` reconstructs `target_release` only from the cleared `desired_release_id`, causing the command to fail with “A setup update requires a desired release” instead of refreshing setup from the current release.

**Knowledge Base Used:**
- [CLI and deployment workflows](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/cli-and-deployment.md)
- [Deployment and packaging](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/deployment-and-packaging.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

) {
let stack_state = state.stack_state.as_ref().ok_or_else(|| {
AlienError::new(ErrorData::ConfigurationError {
message: "A running deployment has no stack state for setup update".to_string(),
Expand Down Expand Up @@ -4129,6 +4217,16 @@ pub async fn push_initial_setup(
)
.await;

if let Ok(result) = &runner_result {
if matches!(result.loop_result.outcome, LoopOutcome::Success) {
if let (Some(revision), Some(metadata)) =
(setup_revision, state.runtime_metadata.as_mut())
{
metadata.direct_setup_revision = Some(revision.to_string());
Comment on lines +4220 to +4225

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Handoff skips revision persistence

When setup reaches its normal Provisioning handoff, the runner returns LoopOutcome::Neutral, but this block records direct_setup_revision only for Success; the revision therefore remains unset and later invocations rerun setup-owned infrastructure for the same package revision.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-deploy-cli/src/commands/up.rs
Line: 4220-4225

Comment:
**Handoff skips revision persistence**

When setup reaches its normal Provisioning handoff, the runner returns `LoopOutcome::Neutral`, but this block records `direct_setup_revision` only for `Success`; the revision therefore remains unset and later invocations rerun setup-owned infrastructure for the same package revision.

**Knowledge Base Used:**
- [CLI and deployment workflows](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/cli-and-deployment.md)
- [Deployment and packaging](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/deployment-and-packaging.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

}
}
}

// Always reconcile + release, even on error.
final_reconcile(client, deployment_id, &session, &state).await;
release_deployment(client, deployment_id, &session).await;
Expand Down
1 change: 1 addition & 0 deletions crates/alien-deploy-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ mod tests {
agent_binary_url: None,
machine_bundle_url: None,
install_script_url: None,
setup_revision: None,
token_env_var: None,
name: Some("acmectl".to_string()),
display_name: Some("Acme Deployment CLI".to_string()),
Expand Down
3 changes: 3 additions & 0 deletions crates/alien-deployment/src/manager_api_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ pub async fn acquire_setup_run_deployment(
"initial-setup".to_string(),
"initial-setup-failed".to_string(),
"waiting-for-machines".to_string(),
"running".to_string(),
"update-failed".to_string(),
"refresh-failed".to_string(),
]),
)
.await
Expand Down
Loading