diff --git a/changelog.d/+280-mirrord-intellij-copy-target-share.internal.md b/changelog.d/+280-mirrord-intellij-copy-target-share.internal.md new file mode 100644 index 00000000000..825e49ea22c --- /dev/null +++ b/changelog.d/+280-mirrord-intellij-copy-target-share.internal.md @@ -0,0 +1 @@ +Add patch to allow a user to reuse copy-target and fix issue where prelauch commands in intellij prevented execution. \ No newline at end of file diff --git a/mirrord/operator/src/client.rs b/mirrord/operator/src/client.rs index ec304dc73f2..90a0bd05d4a 100644 --- a/mirrord/operator/src/client.rs +++ b/mirrord/operator/src/client.rs @@ -6,7 +6,10 @@ use conn_wrapper::ConnectionWrapper; use error::{OperatorApiError, OperatorApiResult, OperatorOperation}; use http::{request::Request, HeaderName, HeaderValue}; use k8s_openapi::api::apps::v1::Deployment; -use kube::{api::PostParams, Api, Client, Config, Resource}; +use kube::{ + api::{ListParams, PostParams}, + Api, Client, Config, Resource, +}; use mirrord_analytics::{AnalyticsHash, AnalyticsOperatorProperties, Reporter}; use mirrord_auth::{ certificate::Certificate, @@ -669,7 +672,16 @@ impl OperatorApi { let scale_down = config.feature.copy_target.scale_down; let namespace = self.target_namespace(config); let copied = self - .copy_target(target, scale_down, namespace, &config.feature.split_queues) + .copy_target( + target, + scale_down, + namespace, + config + .feature + .split_queues + .is_set() + .then(|| config.feature.split_queues.clone()), + ) .await?; copy_subtask.success(Some("target copied")); @@ -722,8 +734,14 @@ impl OperatorApi { Ok(OperatorSessionConnection { session, tx, rx }) } - /// Creates a new [`CopyTargetCrd`] resource using the operator. - /// This should create a new dummy pod out of the given [`Target`]. + /// Returns client cert's public key in a base64 encoded string (no padding same like in + /// operator logic) + pub fn get_user_id_str(&self) -> String { + general_purpose::STANDARD_NO_PAD.encode(self.client_cert.cert.public_key_data()) + } + + /// Creates a new or reuses existing [`CopyTargetCrd`] resource using the operator. + /// If new this should create a new dummy pod out of the given [`Target`]. /// /// # Note /// @@ -735,22 +753,47 @@ impl OperatorApi { target: Target, scale_down: bool, namespace: &str, - split_queues: &SplitQueuesConfig, + split_queues: Option, ) -> OperatorApiResult { - let name = TargetCrd::urlfied_name(&target); - - let requested = CopyTargetCrd::new( - &name, - CopyTargetSpec { - target, - idle_ttl: Some(Self::COPIED_POD_IDLE_TTL), - scale_down, - split_queues: Some(split_queues.clone()), - }, - ); + let user_id = self.get_user_id_str(); + + let copy_target_api: Api = Api::namespaced(self.client.clone(), namespace); + + let existing_copy_targets = + copy_target_api + .list(&ListParams::default()) + .await + .map_err(|error| OperatorApiError::KubeError { + error, + operation: OperatorOperation::CopyingTarget, + })?; - Api::namespaced(self.client.clone(), namespace) - .create(&PostParams::default(), &requested) + let copy_target_name = TargetCrd::urlfied_name(&target); + let copy_target_spec = CopyTargetSpec { + target, + idle_ttl: Some(Self::COPIED_POD_IDLE_TTL), + scale_down, + split_queues, + }; + + if let Some(copy_target) = existing_copy_targets.items.into_iter().find(|copy_target| { + copy_target.spec == copy_target_spec + && copy_target + .status + .as_ref() + .map(|status| status.creator_session.user_id.as_ref() == Some(&user_id)) + .unwrap_or(false) + }) { + tracing::debug!(?copy_target, "reusing copy_target"); + + return Ok(copy_target); + } + + copy_target_api + .create( + &PostParams::default(), + &CopyTargetCrd::new(©_target_name, copy_target_spec), + ) .await .map_err(|error| OperatorApiError::KubeError { error, diff --git a/mirrord/operator/src/crd.rs b/mirrord/operator/src/crd.rs index 406d64beccb..a6716b9e47c 100644 --- a/mirrord/operator/src/crd.rs +++ b/mirrord/operator/src/crd.rs @@ -224,6 +224,7 @@ pub struct Session { pub target: String, pub namespace: Option, pub locked_ports: Option)>>, + pub user_id: Option, } /// Resource used to access the operator's session management routes. @@ -292,12 +293,13 @@ impl From<&OperatorFeatures> for NewOperatorFeature { /// This [`Resource`](kube::Resource) represents a copy pod created from an existing [`Target`] /// (operator's copy pod feature). -#[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] #[kube( group = "operator.metalbear.co", version = "v1", kind = "CopyTarget", root = "CopyTargetCrd", + status = "CopyTargetStatus", namespaced )] pub struct CopyTargetSpec { @@ -313,6 +315,13 @@ pub struct CopyTargetSpec { pub split_queues: Option, } +/// This is the `status` field for [`CopyTargetCrd`]. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct CopyTargetStatus { + /// The session object of the original session that created this CopyTarget + pub creator_session: Session, +} + /// Features and operations that can be blocked by a `MirrordPolicy`. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] #[serde(rename_all = "kebab-case")] // StealWithoutFilter -> steal-without-filter in yaml.