xianyun_cli/commands/
setkubecontext.rs1use anyhow::Context as _;
6use clap::Args;
7use xianyun_cluster::kubeconfig::{file, merge};
8use xianyun_core::config::caproni::load_from_context;
9use xianyun_core::config::global::Context;
10
11#[derive(Args, Debug)]
12pub struct SetKubeContextArgs {
13 #[arg(short = 'u', long = "use")]
15 pub use_context: bool,
16
17 #[arg(long = "remove")]
19 pub remove: bool,
20
21 #[arg(long = "dry-run")]
23 pub dry_run: bool,
24
25 #[arg(long = "kubeconfig")]
27 pub kubeconfig: Option<String>,
28}
29
30#[derive(Debug, Default, Clone)]
32pub struct Options {
33 pub use_context: bool,
34 pub remove: bool,
35 pub dry_run: bool,
36 pub kubeconfig: String,
37}
38
39impl From<&SetKubeContextArgs> for Options {
40 fn from(args: &SetKubeContextArgs) -> Self {
41 Self {
42 use_context: args.use_context,
43 remove: args.remove,
44 dry_run: args.dry_run,
45 kubeconfig: args.kubeconfig.clone().unwrap_or_default(),
46 }
47 }
48}
49
50pub fn run(args: &SetKubeContextArgs, ctx: &Context) -> anyhow::Result<()> {
51 run_set_kube_context(ctx, &Options::from(args))
52}
53
54pub fn run_set_kube_context(ctx: &Context, opts: &Options) -> anyhow::Result<()> {
65 crate::drivers::ensure_registered();
66
67 let container = load_from_context(ctx).context("loading configuration")?;
68
69 let context_name = xianyun_core::naming::context_name(&container.global.profile_name);
70
71 let path = file::target_path(&opts.kubeconfig).context("resolving kubeconfig path")?;
72
73 let current = file::read(&path)
74 .context("reading kubeconfig")?
75 .unwrap_or_default();
76
77 let updated = render_update(&container, opts, ¤t, &context_name)?;
78
79 apply(opts, &path, &context_name, ¤t, &updated)
80}
81
82fn render_update(
85 container: &xianyun_core::config::container::Container,
86 opts: &Options,
87 current: &[u8],
88 context_name: &str,
89) -> anyhow::Result<Vec<u8>> {
90 if opts.remove {
92 return merge::uninstall(current, context_name)
93 .with_context(|| format!("removing context {context_name}"));
94 }
95
96 let manager = xianyun_cluster::registry::new_manager(
97 container.caproni.cluster.clone(),
98 container.global.profile_name.clone(),
99 )
100 .context("creating cluster manager")?;
101
102 let cluster_kubeconfig = manager
103 .get_kubeconfig()
104 .context("getting kubeconfig from the cluster")?;
105
106 merge::install(
107 current,
108 cluster_kubeconfig.as_bytes(),
109 context_name,
110 opts.use_context,
111 )
112 .with_context(|| format!("merging context {context_name}"))
113}
114
115fn apply(
118 opts: &Options,
119 path: &str,
120 context_name: &str,
121 current: &[u8],
122 updated: &[u8],
123) -> anyhow::Result<()> {
124 if current == updated {
125 tracing::info!(
126 context = context_name,
127 kubeconfig = path,
128 "kubeconfig already up to date"
129 );
130 return Ok(());
131 }
132
133 if opts.dry_run {
134 tracing::info!(
135 context = context_name,
136 kubeconfig = path,
137 action = action(opts.remove),
138 "dry-run mode: would update kubeconfig"
139 );
140 tracing::info!("run without --dry-run to apply changes");
141 return Ok(());
142 }
143
144 let backup_path = file::write(path, updated).context("writing kubeconfig")?;
145
146 tracing::info!(
147 context = context_name,
148 kubeconfig = path,
149 action = action(opts.remove),
150 backup = backup_path,
151 "updated kubeconfig"
152 );
153
154 if opts.remove {
155 return Ok(());
156 }
157
158 tracing::info!(
159 kubectl = format!("kubectl --context {context_name} get pods"),
160 helm = format!("helm --kube-context {context_name} list"),
161 "cluster is now reachable outside caproni"
162 );
163
164 Ok(())
165}
166
167fn action(remove: bool) -> &'static str {
168 if remove { "remove" } else { "add" }
169}