Skip to content

Allow targets to be listed by specific resource type #3312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
25 changes: 23 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions changelog.d/+list-targets-by-type.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the `-t` argument to the `mirrord ls` command to list targets of a specific type. Also allow target types to be read from env.
2 changes: 1 addition & 1 deletion mirrord-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1852,7 +1852,7 @@
"additionalProperties": false
},
"Target": {
"description": "<!--${internal}--> ## path\n\nSpecifies the running pod (or deployment) to mirror.\n\nSupports: - `targetless` - `pod/{pod-name}[/container/{container-name}]`; - `deployment/{deployment-name}[/container/{container-name}]`; - `rollout/{rollout-name}[/container/{container-name}]`; - `job/{job-name}[/container/{container-name}]`; - `cronjob/{cronjob-name}[/container/{container-name}]`; - `statefulset/{statefulset-name}[/container/{container-name}]`; - `service/{service-name}[/container/{container-name}]`; - `replicaset/{replicaset-name}[/container/{container-name}]`;",
"description": "<!--${internal}--> ## path\n\nSpecifies the running pod (or deployment) to mirror.\n\nSupports: - `targetless` - `pod/{pod-name}[/container/{container-name}]`; - `deployment/{deployment-name}[/container/{container-name}]`; - `rollout/{rollout-name}[/container/{container-name}]`; - `job/{job-name}[/container/{container-name}]`; - `cronjob/{cronjob-name}[/container/{container-name}]`; - `statefulset/{statefulset-name}[/container/{container-name}]`; - `service/{service-name}[/container/{container-name}]`; - `replicaset/{replicaset-name}[/container/{container-name}]`;\n\nUsed to derive `TargetType` via the strum crate",
"anyOf": [
{
"description": "<!--${internal}--> [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/).",
Expand Down
6 changes: 6 additions & 0 deletions mirrord/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use mirrord_config::{
MIRRORD_OVERRIDE_ENV_FILE_ENV, MIRRORD_OVERRIDE_ENV_VARS_EXCLUDE_ENV,
MIRRORD_OVERRIDE_ENV_VARS_INCLUDE_ENV,
},
target::TargetType,
LayerConfig,
};
use mirrord_operator::setup::OperatorNamespace;
Expand Down Expand Up @@ -798,6 +799,11 @@ pub(super) struct ListTargetArgs {
/// Specify config file to use.
#[arg(short = 'f', long, value_hint = ValueHint::FilePath)]
pub config_file: Option<PathBuf>,

/// Specify the type of target to be retrieved. If `None`, all types are retrieved.
/// Can be used multiple times to specify multiple target types.
#[arg(short = 't', long)]
pub target_type: Option<Vec<TargetType>>,
}

impl ListTargetArgs {
Expand Down
52 changes: 46 additions & 6 deletions mirrord/cli/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use futures::TryStreamExt;
use k8s_openapi::api::core::v1::Namespace;
use kube::Client;
use mirrord_analytics::NullReporter;
use mirrord_config::{config::ConfigContext, LayerConfig};
use mirrord_config::{config::ConfigContext, target::TargetType, LayerConfig};
use mirrord_kube::{
api::kubernetes::{create_kube_config, seeker::KubeResourceSeeker},
error::KubeApiError,
Expand All @@ -16,6 +16,10 @@ use tracing::Level;

use crate::{util, CliError, CliResult, Format, ListTargetArgs};

/// Name of the environment variable used to specify which resource types to list with `mirrord ls`.
/// Primarily used by the plugins when the user picks a target to fetch fewer targets at once.
const LS_TARGET_TYPES_ENV: &str = "MIRRORD_LS_TARGET_TYPES";

/// A mirrord target found in the cluster.
#[derive(Serialize)]
struct FoundTarget {
Expand Down Expand Up @@ -66,7 +70,11 @@ impl FoundTargets {
/// 1. returned [`FoundTargets`] will contain info about namespaces available in the cluster;
/// 2. only deployment, rollout, and pod targets will be fetched.
#[tracing::instrument(level = Level::DEBUG, skip(config), name = "resolve_targets", err)]
async fn resolve(config: LayerConfig, rich_output: bool) -> CliResult<Self> {
async fn resolve(
config: LayerConfig,
rich_output: bool,
target_types: Option<Vec<TargetType>>,
) -> CliResult<Self> {
let client = create_kube_config(
config.accept_invalid_certificates,
config.kubeconfig.clone(),
Expand Down Expand Up @@ -107,10 +115,12 @@ impl FoundTargets {

let (targets, namespaces) = tokio::try_join!(
async {
let paths = match operator_api {
None if config.operator == Some(true) => Err(CliError::OperatorNotInstalled),
let paths = match (operator_api, target_types) {
(None, _) if config.operator == Some(true) => {
Err(CliError::OperatorNotInstalled)
}

Some(api)
(Some(api), None)
if !rich_output
&& ALL_TARGETS_SUPPORTED_OPERATOR_VERSION
.matches(&api.operator().spec.operator_version) =>
Expand All @@ -120,6 +130,22 @@ impl FoundTargets {
})
}

(Some(api), Some(target_types))
if !rich_output
&& ALL_TARGETS_SUPPORTED_OPERATOR_VERSION
.matches(&api.operator().spec.operator_version) =>
{
seeker.filtered(target_types, true).await.map_err(|error| {
CliError::friendlier_error_or_else(error, CliError::ListTargetsFailed)
})
}

(None, Some(target_types)) => {
seeker.filtered(target_types, false).await.map_err(|error| {
CliError::friendlier_error_or_else(error, CliError::ListTargetsFailed)
})
}

_ => seeker.all_open_source().await.map_err(|error| {
CliError::friendlier_error_or_else(error, CliError::ListTargetsFailed)
}),
Expand Down Expand Up @@ -219,7 +245,21 @@ pub(super) async fn print_targets(args: ListTargetArgs, rich_output: bool) -> Cl
util::remove_proxy_env();
}

let targets = FoundTargets::resolve(layer_config, rich_output).await?;
let target_types = if let Some(target_type) = args.target_type {
Some(target_type)
} else {
match std::env::var(LS_TARGET_TYPES_ENV)
.ok()
.map(|val| serde_json::from_str::<Vec<TargetType>>(val.as_ref()))
.transpose()?
.unwrap_or_default()
{
vec if vec.is_empty() => None,
vec => Some(vec),
}
};

let targets = FoundTargets::resolve(layer_config, rich_output, target_types).await?;

match args.output {
Format::Json => {
Expand Down
63 changes: 1 addition & 62 deletions mirrord/cli/src/verify_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ use error::CliResult;
use futures::TryFutureExt;
use mirrord_config::{
config::ConfigContext,
feature::FeatureConfig,
target::{
cron_job::CronJobTarget, deployment::DeploymentTarget, job::JobTarget, pod::PodTarget,
replica_set::ReplicaSetTarget, rollout::RolloutTarget, service::ServiceTarget,
stateful_set::StatefulSetTarget, Target, TargetConfig,
stateful_set::StatefulSetTarget, Target, TargetConfig, TargetType,
},
LayerConfig,
};
Expand Down Expand Up @@ -102,66 +101,6 @@ impl From<TargetConfig> for VerifiedTargetConfig {
}
}

/// Corresponds to variants of [`Target`].
#[derive(Serialize, PartialEq, Eq, Clone, Copy)]
#[serde(rename_all = "lowercase")]
enum TargetType {
Targetless,
Pod,
Deployment,
Rollout,
Job,
CronJob,
StatefulSet,
Service,
ReplicaSet,
}

impl core::fmt::Display for TargetType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let stringifed = match self {
TargetType::Targetless => "targetless",
TargetType::Pod => "pod",
TargetType::Deployment => "deployment",
TargetType::Rollout => "rollout",
TargetType::Job => "job",
TargetType::CronJob => "cronjob",
TargetType::StatefulSet => "statefulset",
TargetType::Service => "service",
TargetType::ReplicaSet => "replicaset",
};

f.write_str(stringifed)
}
}

impl TargetType {
fn all() -> impl Iterator<Item = Self> {
[
Self::Targetless,
Self::Pod,
Self::Deployment,
Self::Rollout,
Self::Job,
Self::CronJob,
Self::StatefulSet,
Self::Service,
Self::ReplicaSet,
]
.into_iter()
}

fn compatible_with(&self, config: &FeatureConfig) -> bool {
match self {
Self::Targetless | Self::Rollout => !config.copy_target.enabled,
Self::Pod => !(config.copy_target.enabled && config.copy_target.scale_down),
Self::Job | Self::CronJob => config.copy_target.enabled,
Self::Service => !config.copy_target.enabled,
Self::Deployment | Self::StatefulSet | Self::ReplicaSet => true,
}
}
}

/// Produced by calling `verify_config`.
///
/// It's consumed by the IDEs to check if a config is valid, or missing something, without starting
Expand Down
2 changes: 2 additions & 0 deletions mirrord/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ fancy-regex.workspace = true
base64.workspace = true
rand.workspace = true
rustls.workspace = true
strum = "0.27.1"
strum_macros = "0.27.1"

[dev-dependencies]
rstest.workspace = true
57 changes: 56 additions & 1 deletion mirrord/config/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use mirrord_analytics::CollectAnalytics;
use replica_set::ReplicaSetTarget;
use schemars::{gen::SchemaGenerator, schema::SchemaObject, JsonSchema};
use serde::{Deserialize, Serialize};
use strum_macros::{EnumDiscriminants, EnumString};

use self::{
deployment::DeploymentTarget, job::JobTarget, pod::PodTarget, rollout::RolloutTarget,
Expand All @@ -16,6 +17,7 @@ use crate::{
source::MirrordConfigSource,
ConfigContext, ConfigError, FromMirrordConfig, MirrordConfig, Result,
},
feature::FeatureConfig,
util::string_or_struct_option,
};

Expand Down Expand Up @@ -268,9 +270,17 @@ mirrord-layer failed to parse the provided target!
/// - `statefulset/{statefulset-name}[/container/{container-name}]`;
/// - `service/{service-name}[/container/{container-name}]`;
/// - `replicaset/{replicaset-name}[/container/{container-name}]`;
///
/// Used to derive `TargetType` via the strum crate
#[warn(clippy::wildcard_enum_match_arm)]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Debug, JsonSchema)]
#[derive(
Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Debug, JsonSchema, EnumDiscriminants,
)]
#[serde(untagged, deny_unknown_fields)]
#[strum_discriminants(derive(EnumString, Serialize, Deserialize))]
#[strum_discriminants(name(TargetType))]
#[strum_discriminants(strum(serialize_all = "lowercase"))]
#[strum_discriminants(serde(rename_all = "lowercase"))]
pub enum Target {
/// <!--${internal}-->
/// [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/).
Expand Down Expand Up @@ -359,6 +369,51 @@ impl Target {
}
}

impl fmt::Display for TargetType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let stringified = match self {
TargetType::Targetless => "targetless",
TargetType::Pod => "pod",
TargetType::Deployment => "deployment",
TargetType::Rollout => "rollout",
TargetType::Job => "job",
TargetType::CronJob => "cronjob",
TargetType::StatefulSet => "statefulset",
TargetType::Service => "service",
TargetType::ReplicaSet => "replicaset",
};

f.write_str(stringified)
}
}

impl TargetType {
pub fn all() -> impl Iterator<Item = Self> {
[
Self::Targetless,
Self::Pod,
Self::Deployment,
Self::Rollout,
Self::Job,
Self::CronJob,
Self::StatefulSet,
Self::Service,
Self::ReplicaSet,
]
.into_iter()
}

pub fn compatible_with(&self, config: &FeatureConfig) -> bool {
match self {
Self::Targetless | Self::Rollout => !config.copy_target.enabled,
Self::Pod => !(config.copy_target.enabled && config.copy_target.scale_down),
Self::Job | Self::CronJob => config.copy_target.enabled,
Self::Service => !config.copy_target.enabled,
Self::Deployment | Self::StatefulSet | Self::ReplicaSet => true,
}
}
}

/// Trait used to convert different aspects of a [`Target`] into a string.
///
/// It's mainly implemented using the `impl_target_display` macro, except for [`Target`]
Expand Down
Loading
Loading