Skip to content

Commit af9bc18

Browse files
Alex HolmbergAlex Holmberg
authored andcommitted
feat: private network service discovery added
1 parent c9d1acc commit af9bc18

5 files changed

Lines changed: 440 additions & 9 deletions

File tree

src/agent/tools/platform/deploy_service.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::wizard::{
2626
get_hetzner_regions_dynamic, get_hetzner_server_types_dynamic, HetznerFetchResult,
2727
DynamicCloudRegion, DynamicMachineType, discover_env_files, parse_env_file,
2828
get_available_endpoints, filter_endpoints_for_provider, match_env_vars_to_services,
29+
extract_network_endpoints,
2930
};
3031
use std::process::Command;
3132

@@ -200,6 +201,14 @@ User: "deploy this service"
200201
- Private endpoints are pre-filtered to only show services on the same provider network
201202
- ALWAYS mention available endpoints when deploying services that have env vars matching deployed services
202203
204+
**Private networks (project_networks):**
205+
- The response includes project_networks showing provisioned VPCs/networks for the target provider
206+
- Each network includes connection_details with key/value pairs (VPC_ID, SUBNET_ID, DEFAULT_DOMAIN, etc.)
207+
- If networks have useful connection details (e.g., a default domain, VPC connector), mention them to the user
208+
- Ask the user if they want to inject any network details as environment variables
209+
- Network details are NOT secrets — they are infrastructure identifiers
210+
- Private networks enable service-to-service communication on the same provider
211+
203212
**Environment variables (secret_keys) and .env files:**
204213
- The preview response includes parsed_env_files: discovered .env files with their parsed keys/values
205214
- If .env files are found, ALWAYS ask the user: "I found a .env file with N variables. Should I inject these into the deployment?"
@@ -767,6 +776,21 @@ User: "deploy this service"
767776
let endpoint_suggestions =
768777
match_env_vars_to_services(&detected_env_var_names, &deployed_endpoints);
769778

779+
// Fetch project networks for the target provider
780+
let project_networks = match client.list_project_networks(&project_id).await {
781+
Ok(nets) => nets,
782+
Err(e) => {
783+
tracing::debug!("Could not fetch project networks: {}", e);
784+
Vec::new()
785+
}
786+
};
787+
788+
let network_endpoints = extract_network_endpoints(
789+
&project_networks,
790+
final_provider_for_check.as_str(),
791+
Some(&resolved_env_id),
792+
);
793+
770794
let response = json!({
771795
"status": "recommendation",
772796
"deployment_mode": deployment_mode,
@@ -889,6 +913,18 @@ User: "deploy this service"
889913
"confidence": format!("{:?}", s.confidence),
890914
"reason": s.reason,
891915
})).collect::<Vec<_>>(),
916+
"project_networks": network_endpoints.iter().map(|ne| json!({
917+
"network_id": ne.network_id,
918+
"cloud_provider": ne.cloud_provider,
919+
"region": ne.region,
920+
"status": ne.status,
921+
"environment_id": ne.environment_id,
922+
"connection_details": ne.connection_details.iter().map(|(k, v)| json!({
923+
"key": k,
924+
"value": v,
925+
"suggested_env_var": k,
926+
})).collect::<Vec<_>>(),
927+
})).collect::<Vec<_>>(),
892928
"next_steps": next_steps,
893929
"confirmation_prompt": if existing_config.is_some() {
894930
format!(

src/platform/api/client.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
use super::error::{PlatformApiError, Result};
77
use super::types::{
88
ApiErrorResponse, ArtifactRegistry, AvailableRepositoriesResponse, CloudCredentialStatus,
9-
CloudProvider, ClusterEntity, ConnectRepositoryRequest, ConnectRepositoryResponse,
10-
CreateDeploymentConfigRequest, CreateDeploymentConfigResponse, CreateRegistryRequest,
11-
CreateRegistryResponse, DeploymentConfig, DeploymentSecretInput, DeploymentTaskStatus,
12-
Environment, GenericResponse, GetLogsResponse, GitHubInstallationUrlResponse,
13-
GitHubInstallationsResponse, InitializeGitOpsRequest, InitializeGitOpsResponse, Organization,
14-
PaginatedDeployments, Project, ProjectRepositoriesResponse, RegistryTaskStatus,
15-
TriggerDeploymentRequest, TriggerDeploymentResponse, UserProfile,
9+
CloudProvider, CloudRunnerNetwork, ClusterEntity, ConnectRepositoryRequest,
10+
ConnectRepositoryResponse, CreateDeploymentConfigRequest, CreateDeploymentConfigResponse,
11+
CreateRegistryRequest, CreateRegistryResponse, DeploymentConfig, DeploymentSecretInput,
12+
DeploymentTaskStatus, Environment, GenericResponse, GetLogsResponse,
13+
GitHubInstallationUrlResponse, GitHubInstallationsResponse, InitializeGitOpsRequest,
14+
InitializeGitOpsResponse, Organization, PaginatedDeployments, Project,
15+
ProjectRepositoriesResponse, RegistryTaskStatus, TriggerDeploymentRequest,
16+
TriggerDeploymentResponse, UserProfile,
1617
};
1718
use crate::auth::credentials;
1819
use reqwest::Client;
@@ -1021,6 +1022,29 @@ impl PlatformApiClient {
10211022
.await
10221023
}
10231024

1025+
// =========================================================================
1026+
// Cloud Runner Network API methods
1027+
// =========================================================================
1028+
1029+
/// List all cloud runner networks for a project
1030+
///
1031+
/// Returns VPCs, subnets, Azure Container App Environments, GCP VPC Connectors, etc.
1032+
/// Use this to discover private networking infrastructure provisioned for the project.
1033+
///
1034+
/// Endpoint: GET /api/v1/cloud-runner/projects/:projectId/networks
1035+
pub async fn list_project_networks(
1036+
&self,
1037+
project_id: &str,
1038+
) -> Result<Vec<CloudRunnerNetwork>> {
1039+
let response: GenericResponse<Vec<CloudRunnerNetwork>> = self
1040+
.get(&format!(
1041+
"/api/v1/cloud-runner/projects/{}/networks",
1042+
project_id
1043+
))
1044+
.await?;
1045+
Ok(response.data)
1046+
}
1047+
10241048
// =========================================================================
10251049
// Health Check API methods
10261050
// =========================================================================

src/platform/api/types.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,53 @@ pub struct ServerTypesResponse {
15151515
pub data: Vec<ServerTypeSummary>,
15161516
}
15171517

1518+
// =============================================================================
1519+
// Cloud Runner Network Types
1520+
// =============================================================================
1521+
1522+
/// A provisioned cloud runner network (VPC/subnet/domain)
1523+
///
1524+
/// Represents infrastructure networking resources provisioned for a project,
1525+
/// including VPCs, subnets, Azure Container App Environments, GCP VPC Connectors, etc.
1526+
#[derive(Debug, Clone, Serialize, Deserialize)]
1527+
#[serde(rename_all = "camelCase")]
1528+
pub struct CloudRunnerNetwork {
1529+
/// Unique network identifier
1530+
pub id: String,
1531+
/// Project this network belongs to
1532+
pub project_id: String,
1533+
/// Organization this network belongs to
1534+
pub organization_id: String,
1535+
/// Environment this network is scoped to (None for shared/default networks)
1536+
pub environment_id: Option<String>,
1537+
/// Cloud provider (e.g., "hetzner", "gcp", "azure")
1538+
pub cloud_provider: String,
1539+
/// Region where the network is provisioned
1540+
pub region: String,
1541+
/// VPC identifier (provider-specific)
1542+
pub vpc_id: Option<String>,
1543+
/// VPC display name
1544+
pub vpc_name: Option<String>,
1545+
/// Subnet identifier
1546+
pub subnet_id: Option<String>,
1547+
/// GCP VPC Connector identifier
1548+
pub vpc_connector_id: Option<String>,
1549+
/// GCP VPC Connector name
1550+
pub vpc_connector_name: Option<String>,
1551+
/// Azure resource group name
1552+
pub resource_group_name: Option<String>,
1553+
/// Azure Container App Environment identifier
1554+
pub container_app_environment_id: Option<String>,
1555+
/// Azure Container App Environment name
1556+
pub container_app_environment_name: Option<String>,
1557+
/// Default domain for services on this network (e.g., Azure ACA default domain)
1558+
pub default_domain: Option<String>,
1559+
/// Network status (e.g., "ready", "provisioning", "error")
1560+
pub status: String,
1561+
/// Error message if status is "error"
1562+
pub error_message: Option<String>,
1563+
}
1564+
15181565
// =============================================================================
15191566
// Hetzner Options Types (from /api/v1/cloud-runner/hetzner/options)
15201567
// =============================================================================

src/wizard/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ pub use recommendations::{
5252
};
5353
pub use render::{count_badge, display_step_header, status_indicator, wizard_render_config};
5454
pub use service_endpoints::{
55-
collect_service_endpoint_env_vars, filter_endpoints_for_provider, get_available_endpoints,
55+
collect_network_endpoint_env_vars, collect_service_endpoint_env_vars,
56+
extract_network_endpoints, filter_endpoints_for_provider, get_available_endpoints,
5657
match_env_vars_to_services, AvailableServiceEndpoint, EndpointSuggestion, MatchConfidence,
58+
NetworkEndpointInfo,
5759
};
5860
pub use target_selection::{select_target, TargetSelectionResult};

0 commit comments

Comments
 (0)