Skip to content

Commit

Permalink
Merge pull request #41 from embik/label-selector-remove-label
Browse files Browse the repository at this point in the history
`label` / `remove`: allow to use with `-l` / `--selector` flag
  • Loading branch information
embik authored Dec 10, 2023
2 parents 8cfadc6 + 29015ef commit 201c9c7
Show file tree
Hide file tree
Showing 11 changed files with 675 additions and 338 deletions.
1 change: 0 additions & 1 deletion src/cmd/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ pub fn command() -> Command {
.long("set-labels")
.short('l')
.required(false)
.num_args(0..)
.value_delimiter(',')
.value_parser(metadata::labels::parse_key_val),
)
Expand Down
104 changes: 73 additions & 31 deletions src/cmd/label.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
use crate::metadata::{self, labels, ConfigMetadata, Metadata};
use anyhow::{anyhow, bail, Result};
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use clap::{value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
use std::path::Path;

pub const NAME: &str = "label";

pub fn command() -> Command {
Command::new(NAME)
.visible_alias("l")
.about("Manage labels on kubeconfigs")
.about("Manage labels on kubeconfigs in the data store")
.arg_required_else_help(true)
.arg(Arg::new("kubeconfig").value_parser(value_parser!(String)))
.arg(
Arg::new("labels")
.value_parser(metadata::labels::parse_key_val)
.num_args(1..)
.value_delimiter(','),
.value_delimiter(',')
.required(true)
)
.arg(Arg::new("kubeconfig")
.long("name")
.short('n')
.value_parser(value_parser!(String))
.required_unless_present("selectors")
)
.arg(
Arg::new("selectors")
.help("Selector (label query) to filter on. Supports key=value comma-separated values")
.long("selector")
.short('l')
.value_delimiter(',')
.value_parser(metadata::labels::parse_key_val)
.conflicts_with("kubeconfig"),
)
.arg(
Arg::new("overwrite")
Expand All @@ -25,16 +39,16 @@ pub fn command() -> Command {
.action(ArgAction::SetTrue)
.value_parser(clap::value_parser!(bool)),
)
.group(ArgGroup::new("target").args(&["selectors", "kubeconfig"]).required(true))
.arg_required_else_help(true)
}

pub fn execute(config_dir: &Path, matches: &ArgMatches) -> Result<()> {
let config = matches
.get_one::<String>("kubeconfig")
.ok_or_else(|| anyhow!("failed to get kubeconfig argument"))?;
let mut to_label: Vec<String> = vec![];

let metadata_path = metadata::file_path(config_dir);
log::debug!("loading metadata from {}", metadata_path.display());
let metadata = match Metadata::from_file(&metadata_path) {
let mut metadata = match Metadata::from_file(&metadata_path) {
Ok(metadata) => metadata,
Err(metadata::Error::IO(_, std::io::ErrorKind::NotFound)) => {
log::debug!("failed to find metadata file, creating empty metadata store");
Expand All @@ -43,39 +57,67 @@ pub fn execute(config_dir: &Path, matches: &ArgMatches) -> Result<()> {
Err(err) => bail!(err),
};

if matches.contains_id("selectors") && !matches.contains_id("kubeconfig") {
let selectors = matches
.get_many::<(String, String)>("selectors")
.map(|values_ref| values_ref.into_iter().collect::<Vec<&(String, String)>>());

metadata.kubeconfigs.iter().for_each(|k| {
if let Some(labels) = &k.1.labels {
if metadata::labels::matches_labels(&labels, &selectors) {
to_label.push(k.0.to_string());
}
}
})
} else if matches.contains_id("kubeconfig") && !matches.contains_id("selectors") {
let config = matches
.get_one::<String>("kubeconfig")
.ok_or_else(|| anyhow!("failed to get kubeconfig argument"))?;

to_label.push(config.to_string());
} else {
bail!("cannot set both name and label selector");
}

// collect labels from argument into map
let labels = labels::collect_from_args(matches, "labels")?;

// if kubeconfig has metadata already, we need to merge labels
if let Some(config_metadata) = metadata.get(config) {
let mut config_metadata = config_metadata.clone();
for f in to_label.iter() {
// if kubeconfig has metadata already, we need to merge labels
if let Some(config_metadata) = metadata.get(f) {
let mut config_metadata = config_metadata.clone();

config_metadata.labels = Some(labels::merge_labels(
&config_metadata,
&labels,
matches.get_flag("overwrite"),
)?);
config_metadata.labels = Some(labels::merge_labels(
&config_metadata,
&labels,
matches.get_flag("overwrite"),
)?);

metadata
.set(config.to_string(), config_metadata)
.write(&metadata_path)?;
metadata
.set(f.to_string(), config_metadata)
.write(&metadata_path)?;

log::info!("updated labels for {}", config);
log::info!("updated labels for {}", f);

return Ok(());
}
return Ok(());
}

// no previous metadata exists for this kubeconfig, so we can safely set it
metadata
.set(
config.to_string(),
// no previous metadata exists for this kubeconfig, so we can safely set it
metadata = metadata.set(
f.to_string(),
ConfigMetadata {
labels: Some(labels),
labels: Some(labels.clone()),
},
)
.write(&metadata_path)?;
);

log::info!("updated labels for {}", f);
}

log::info!("updated labels for {}", config);
metadata.write(&metadata_path)?;
log::debug!(
"wrote metadata database update to {}",
metadata_path.display()
);

Ok(())
}
21 changes: 6 additions & 15 deletions src/cmd/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub fn command() -> Command {
.long("selector")
.short('l')
.required(false)
.num_args(0..)
.value_delimiter(',')
.value_parser(metadata::labels::parse_key_val),
)
Expand Down Expand Up @@ -68,9 +67,11 @@ pub fn execute(config_dir: &Path, matches: &ArgMatches) -> Result<()> {
println!("{0: <25}\t{1: <25}", "NAME", "LABELS");
}

let files = fs::read_dir(config_dir)?;
let mut files: Vec<_> = fs::read_dir(config_dir)?.map(|r| r.unwrap()).collect();
files.sort_by_key(|f| f.path());

for file in files {
let file = file?.path();
let file = file.path();

if !is_kubeconfig(&file) {
continue;
Expand All @@ -87,18 +88,8 @@ pub fn execute(config_dir: &Path, matches: &ArgMatches) -> Result<()> {
None => BTreeMap::new(),
};

if let Some(ref selector) = selectors {
let mut matched = true;

for label in selector.iter() {
let (key, value) = label;
let opt = labels.get(key);
matched = opt.is_some() && opt.unwrap() == value;
}

if !matched {
continue;
}
if !metadata::labels::matches_labels(&labels, &selectors) {
continue;
}

log::debug!("found a kubeconfig at {}", file.display());
Expand Down
66 changes: 49 additions & 17 deletions src/cmd/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::kubeconfig;
use crate::metadata::{self, Metadata};
use anyhow::Result;
use anyhow::{anyhow, bail};
use clap::{value_parser, Arg, ArgMatches, Command};
use clap::{value_parser, Arg, ArgGroup, ArgMatches, Command};
use std::{fs, path::Path};

pub const NAME: &str = "remove";
Expand All @@ -13,36 +13,68 @@ pub fn command() -> Command {
.visible_alias("delete")
.about("Remove kubeconfig from data store")
.arg(Arg::new("kubeconfig").value_parser(value_parser!(String)))
.arg(
Arg::new("selectors")
.help("Selector (label query) to filter on. Supports key=value comma-separated values")
.long("selector")
.short('l')
.num_args(0..)
.value_delimiter(',')
.value_parser(metadata::labels::parse_key_val),
)
.group(ArgGroup::new("target")
.args(["kubeconfig", "selectors"])
.required(true))
.arg_required_else_help(true)
}

pub fn execute(config_dir: &Path, matches: &ArgMatches) -> Result<()> {
let config = matches
.get_one::<String>("kubeconfig")
.ok_or_else(|| anyhow!("failed to get kubeconfig argument"))?;

let kubeconfig_path = config_dir.join(format!("{config}.kubeconfig"));
let mut removals: Vec<String> = vec![];

let metadata_path = metadata::file_path(config_dir);
log::debug!("loading metadata from {}", metadata_path.display());
let metadata = match Metadata::from_file(&metadata_path) {
let mut metadata = match Metadata::from_file(&metadata_path) {
Ok(metadata) => metadata,
Err(metadata::Error::IO(_, std::io::ErrorKind::NotFound)) => Metadata::new(),
Err(err) => bail!(err),
};

if kubeconfig::get(&kubeconfig_path).is_ok() {
fs::remove_file(&kubeconfig_path)?;
log::info!("removed kubeconfig at {}", kubeconfig_path.display());
if matches.contains_id("selectors") {
let selectors = matches
.get_many::<(String, String)>("selectors")
.map(|values_ref| values_ref.into_iter().collect::<Vec<&(String, String)>>());

metadata.kubeconfigs.iter().for_each(|k| {
if let Some(labels) = &k.1.labels {
if metadata::labels::matches_labels(&labels, &selectors) {
removals.push(k.0.to_string());
}
}
})
} else {
let config = matches
.get_one::<String>("kubeconfig")
.ok_or_else(|| anyhow!("failed to get kubeconfig argument"))?;

metadata.remove(config).write(&metadata_path)?;
log::debug!(
"wrote metadata database update to {}",
metadata_path.display()
);
removals.push(config.to_string());
}

return Ok(());
for f in removals.iter() {
let kubeconfig_path = config_dir.join(format!("{f}.kubeconfig"));
if kubeconfig::get(&kubeconfig_path).is_ok() {
fs::remove_file(&kubeconfig_path)?;
log::info!("removed kubeconfig at {}", kubeconfig_path.display());
metadata = metadata.remove(f);
} else {
return Err(anyhow!("Kubeconfig not found: {:?}", f));
}
}

Err(anyhow!("Kubeconfig not found: {:?}", kubeconfig_path))
metadata.write(&metadata_path)?;
log::debug!(
"wrote metadata database update to {}",
metadata_path.display()
);

Ok(())
}
Loading

0 comments on commit 201c9c7

Please sign in to comment.