first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+15 -156
View File
@@ -1,150 +1,21 @@
use galaxy_server_client::cloud_object::Owner;
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::{
auth::AuthStateProvider,
cloud_object::{
model::{
generic_string_model::{GenericStringModel, GenericStringObjectId, StringModel},
json_model::{JsonModel, JsonSerializer},
persistence::CloudModel,
},
GenericCloudObject, GenericStringObjectFormat, GenericStringObjectUniqueKey,
JsonObjectType, Revision, ServerCloudObject,
},
server::{ids::SyncId, sync_queue::QueueItem},
workspaces::user_workspaces::UserWorkspaces,
// Some of these re-exported types aren't used in the wasm build, so we suppress this
// warning.
#[cfg_attr(target_family = "wasm", expect(unused_imports))]
pub use cloud_object_models::{
AmbientAgentEnvironment, AwsProviderConfig, BaseImage, CloudAmbientAgentEnvironment,
CloudAmbientAgentEnvironmentModel, GcpProviderConfig, GithubRepo, ProvidersConfig, SourceRepo,
};
use cloud_objects::cloud_object::Owner;
use galaxyui::{AppContext, SingletonEntity as _};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct GithubRepo {
/// Repository owner (e.g. "warpdotdev")
pub owner: String,
/// Repository name (e.g. "warp-internal")
pub repo: String,
}
impl GithubRepo {
pub fn new(owner: String, repo: String) -> Self {
Self { owner, repo }
}
}
impl fmt::Display for GithubRepo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.owner, self.repo)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum BaseImage {
DockerImage(String),
}
impl fmt::Display for BaseImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BaseImage::DockerImage(s) => s.fmt(f),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct GcpProviderConfig {
pub project_number: String,
pub workload_identity_federation_pool_id: String,
pub workload_identity_federation_provider_id: String,
/// Service account email for impersonation. When set, the federated token
/// is exchanged for a service account access token.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub service_account_email: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AwsProviderConfig {
pub role_arn: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct ProvidersConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gcp: Option<GcpProviderConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aws: Option<AwsProviderConfig>,
}
impl ProvidersConfig {
pub fn is_empty(&self) -> bool {
self.gcp.is_none() && self.aws.is_none()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
/// An AmbientAgentEnvironment represents an environment that we would run a Warp agent in.
pub struct AmbientAgentEnvironment {
/// Environment name
#[serde(default)]
pub name: String,
/// Optional description of the environment (max 240 characters)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// List of GitHub repositories
#[serde(default)]
pub github_repos: Vec<GithubRepo>,
/// Base image specification
#[serde(flatten)]
pub base_image: BaseImage,
/// List of setup commands to run after cloning
#[serde(default)]
pub setup_commands: Vec<String>,
/// Optional cloud provider configurations for automatic auth.
#[serde(default, skip_serializing_if = "ProvidersConfig::is_empty")]
pub providers: ProvidersConfig,
}
pub type CloudAmbientAgentEnvironment =
GenericCloudObject<GenericStringObjectId, CloudAmbientAgentEnvironmentModel>;
pub type CloudAmbientAgentEnvironmentModel =
GenericStringModel<AmbientAgentEnvironment, JsonSerializer>;
impl CloudAmbientAgentEnvironment {
pub fn get_all(app: &AppContext) -> Vec<CloudAmbientAgentEnvironment> {
CloudModel::as_ref(app)
.get_all_objects_of_type::<GenericStringObjectId, CloudAmbientAgentEnvironmentModel>()
.cloned()
.collect()
}
pub fn get_by_id<'a>(
sync_id: &'a SyncId,
app: &'a AppContext,
) -> Option<&'a CloudAmbientAgentEnvironment> {
CloudModel::as_ref(app)
.get_object_of_type::<GenericStringObjectId, CloudAmbientAgentEnvironmentModel>(sync_id)
}
}
impl AmbientAgentEnvironment {
pub fn new(
name: String,
description: Option<String>,
github_repos: Vec<GithubRepo>,
docker_image: String,
setup_commands: Vec<String>,
) -> Self {
Self {
name,
description,
github_repos,
base_image: BaseImage::DockerImage(docker_image),
setup_commands,
providers: ProvidersConfig::default(),
}
}
}
use crate::auth::AuthStateProvider;
use crate::cloud_object::model::generic_string_model::StringModel;
use crate::cloud_object::model::json_model::JsonModel;
use crate::cloud_object::{
GenericStringObjectFormat, GenericStringObjectUniqueKey, JsonObjectType, Revision,
};
use crate::server::sync_queue::QueueItem;
use crate::workspaces::user_workspaces::UserWorkspaces;
impl StringModel for AmbientAgentEnvironment {
type CloudObjectType = CloudAmbientAgentEnvironment;
@@ -181,14 +52,6 @@ impl StringModel for AmbientAgentEnvironment {
None
}
fn new_from_server_update(&self, server_cloud_object: &ServerCloudObject) -> Option<Self> {
if let ServerCloudObject::AmbientAgentEnvironment(server_environment) = server_cloud_object
{
return Some(server_environment.model.clone().string_model);
}
None
}
fn should_show_activity_toasts() -> bool {
false
}
@@ -226,7 +89,3 @@ pub fn owner_for_new_personal_environment(ctx: &AppContext) -> Option<Owner> {
let user_id = AuthStateProvider::as_ref(ctx).get().user_id()?;
Some(Owner::User { user_uid: user_id })
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;
-176
View File
@@ -1,176 +0,0 @@
use super::*;
#[test]
fn deserialize_legacy_environment_without_providers() {
let json = serde_json::json!({
"name": "my-env",
"github_repos": [{"owner": "warpdotdev", "repo": "warp"}],
"docker_image": "ubuntu:latest",
"setup_commands": ["echo hello"]
});
let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();
assert_eq!(env.name, "my-env");
assert_eq!(env.providers, ProvidersConfig::default());
assert_eq!(env.github_repos.len(), 1);
assert_eq!(
env.base_image,
BaseImage::DockerImage("ubuntu:latest".into())
);
assert_eq!(env.setup_commands, vec!["echo hello"]);
}
#[test]
fn deserialize_with_aws_provider() {
let json = serde_json::json!({
"name": "aws-env",
"github_repos": [],
"docker_image": "node:18",
"providers": {
"aws": {
"role_arn": "arn:aws:iam::123456789012:role/my-role"
}
}
});
let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();
assert_eq!(env.name, "aws-env");
let providers = env.providers;
assert_eq!(providers.gcp, None);
let aws = providers.aws.unwrap();
assert_eq!(aws.role_arn, "arn:aws:iam::123456789012:role/my-role");
}
#[test]
fn deserialize_with_gcp_provider() {
let json = serde_json::json!({
"name": "gcp-env",
"github_repos": [],
"docker_image": "node:18",
"providers": {
"gcp": {
"project_number": "123456",
"workload_identity_federation_pool_id": "pool-1",
"workload_identity_federation_provider_id": "provider-1"
}
}
});
let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();
let gcp = env.providers.gcp.unwrap();
assert_eq!(gcp.project_number, "123456");
assert_eq!(gcp.workload_identity_federation_pool_id, "pool-1");
assert_eq!(gcp.workload_identity_federation_provider_id, "provider-1");
assert_eq!(gcp.service_account_email, None);
}
#[test]
fn deserialize_with_gcp_provider_service_account() {
let json = serde_json::json!({
"name": "gcp-sa-env",
"github_repos": [],
"docker_image": "node:18",
"providers": {
"gcp": {
"project_number": "123456",
"workload_identity_federation_pool_id": "pool-1",
"workload_identity_federation_provider_id": "provider-1",
"service_account_email": "sa@project.iam.gserviceaccount.com"
}
}
});
let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();
let gcp = env.providers.gcp.unwrap();
assert_eq!(gcp.project_number, "123456");
assert_eq!(
gcp.service_account_email.as_deref(),
Some("sa@project.iam.gserviceaccount.com")
);
}
#[test]
fn deserialize_with_both_providers() {
let json = serde_json::json!({
"name": "both-env",
"github_repos": [],
"docker_image": "node:18",
"providers": {
"gcp": {
"project_number": "123456",
"workload_identity_federation_pool_id": "pool-1",
"workload_identity_federation_provider_id": "provider-1"
},
"aws": {
"role_arn": "arn:aws:iam::123456789012:role/my-role"
}
}
});
let env: AmbientAgentEnvironment = serde_json::from_value(json).unwrap();
let providers = env.providers;
assert!(providers.gcp.is_some());
assert!(providers.aws.is_some());
}
#[test]
fn serialize_with_providers_none_omits_field() {
let env = AmbientAgentEnvironment::new(
"test-env".into(),
None,
vec![],
"ubuntu:latest".into(),
vec![],
);
let json = serde_json::to_value(&env).unwrap();
assert!(!json.as_object().unwrap().contains_key("providers"));
}
#[test]
fn serialize_with_providers_includes_field() {
let mut env = AmbientAgentEnvironment::new(
"test-env".into(),
None,
vec![],
"ubuntu:latest".into(),
vec![],
);
env.providers = ProvidersConfig {
gcp: None,
aws: Some(AwsProviderConfig {
role_arn: "arn:aws:iam::123456789012:role/test".into(),
}),
};
let json = serde_json::to_value(&env).unwrap();
let providers = json.get("providers").unwrap();
assert!(providers.get("aws").is_some());
assert!(providers.get("gcp").is_none());
}
#[test]
fn roundtrip_serde_with_providers() {
let mut env = AmbientAgentEnvironment::new(
"rt-env".into(),
Some("desc".into()),
vec![GithubRepo::new("owner".into(), "repo".into())],
"alpine:latest".into(),
vec!["make build".into()],
);
env.providers = ProvidersConfig {
gcp: Some(GcpProviderConfig {
project_number: "999".into(),
workload_identity_federation_pool_id: "p".into(),
workload_identity_federation_provider_id: "pr".into(),
service_account_email: Some("sa@proj.iam.gserviceaccount.com".into()),
}),
aws: Some(AwsProviderConfig {
role_arn: "arn:aws:iam::1:role/r".into(),
}),
};
let serialized = serde_json::to_string(&env).unwrap();
let deserialized: AmbientAgentEnvironment = serde_json::from_str(&serialized).unwrap();
assert_eq!(env, deserialized);
}