{"id":11919,"date":"2025-10-07T21:49:45","date_gmt":"2025-10-07T21:49:45","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=11919"},"modified":"2025-10-07T21:49:45","modified_gmt":"2025-10-07T21:49:45","slug":"creating-kubectl-plugins","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/creating-kubectl-plugins\/","title":{"rendered":"Creating kubectl plugins"},"content":{"rendered":"<p>To create a kubectl plugin whereby, for example, we could rung a new tool like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubectl log index echo 3 -n dev\r\n<\/pre>\n<p>Where the above would find pods with a partial name of <em>echo<\/em> and from those pods that match, finds the index 3 (0 indexed).<\/p>\n<p>To create a plugin you use the naming convention <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubeclt-&lt;your-plugin-name&gt;\r\n<\/pre>\n<p>You need to build your plugin then ensure it&#8217;s copied into your PATH.<\/p>\n<p>Once built and copied, you can use the following to check if kubectl can find the plugin<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubectl plugin list\r\n<\/pre>\n<p><strong>Sample Plugin<\/strong><\/p>\n<p>I&#8217;ve created the plugin using Rust.<\/p>\n<p><em>Note: This is just a quick implementation and not fully tested, but gives an idea of how to create such a plugin.<\/em><\/p>\n<p>Set your Cargo.toml dependencies as follows<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nk8s-openapi = { version = &quot;0.26.0&quot;, features = &#x5B;&quot;v1_32&quot;] }\r\nkube = { version = &quot;2.0.1&quot;, features = &#x5B;&quot;runtime&quot;, &quot;derive&quot;] }\r\ntokio = { version = &quot;1&quot;, features = &#x5B;&quot;full&quot;] }\r\nclap = { version = &quot;4&quot;, features = &#x5B;&quot;derive&quot;] }\r\nanyhow = &quot;1.0&quot;\r\n<\/pre>\n<p>Next we want to create the command line arguments using the following<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n#&#x5B;derive(Parser, Debug)]\r\n#&#x5B;command(name = &quot;kubectl-log-index&quot;)]\r\n#&#x5B;command(author, version, about)]\r\npub struct Args {\r\n    \/\/\/ Partial name of the pod to match\r\n    #&#x5B;arg()]\r\n    pub pod_part: String,\r\n    \/\/\/ Index of the pod (0-based)\r\n    pub index: usize,\r\n    \/\/\/ Follow the log stream\r\n    #&#x5B;arg(short = &#039;f&#039;, long)]\r\n    pub follow: bool,\r\n    \/\/\/ Kubernetes namespace (optional)\r\n    #&#x5B;arg(short, long)]\r\n    pub namespace: Option&lt;String&gt;,\r\n}\r\n<\/pre>\n<p>We&#8217;re supplying some short form parameters such as -f which can be used instead of &#8211;follow, likewise -n in place of &#8211;namespace.<\/p>\n<p>Our main.rs looks like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmod args;\r\n\r\nuse clap::Parser;\r\nuse anyhow::Result;\r\nuse kube::{Api, Client};\r\nuse k8s_openapi::api::core::v1::Pod;\r\nuse std::process::Command;\r\nuse kube::api::ListParams;\r\nuse kube::runtime::reflector::Lookup;\r\nuse crate::args::Args;\r\n\r\n\/\/\/ kubectl plugin to get logs by container index\r\n#&#x5B;tokio::main]\r\nasync fn main() -&gt; Result&lt;()&gt; {\r\n    let args = Args::parse();\r\n\r\n    let namespace: &amp;str = args.namespace\r\n        .as_deref()\r\n        .unwrap_or(&quot;default&quot;);\r\n\r\n    let client = Client::try_default().await?;\r\n    let pods: Api&lt;Pod&gt; = Api::namespaced(client, namespace);\r\n\r\n    let pod_list = find_matching_pods(pods, &amp;args.pod_part).await.expect(&quot;Failed to find matching pods&quot;);\r\n    \r\n    let pod = pod_list\r\n        .get(args.index)\r\n        .cloned()\r\n        .ok_or_else(|| anyhow::anyhow!(&quot;Pod not found&quot;))?;\r\n\r\n    let pod_name = &amp;pod.name().ok_or_else(|| anyhow::anyhow!(&quot;Pod name not found&quot;))?;\r\n\r\n    let mut cmd = Command::new(&quot;kubectl&quot;);\r\n\r\n    cmd.args(&#x5B;&quot;logs&quot;, pod_name]);\r\n\r\n    if namespace != &quot;default&quot; {\r\n        cmd.args(&#x5B;&quot;-n&quot;, namespace]);\r\n    }\r\n\r\n    if args.follow {\r\n        cmd.arg(&quot;-f&quot;);\r\n    }\r\n\r\n    cmd\r\n        .status()?;\r\n\r\n    Ok(())\r\n}\r\n\r\npub async fn find_matching_pods(\r\n    pods: Api&lt;Pod&gt;,\r\n    partial: &amp;str,\r\n) -&gt; Result&lt;Vec&lt;Pod&gt;, Box&lt;dyn std::error::Error&gt;&gt; {\r\n    let pod_list = pods.list(&amp;ListParams::default()).await?;\r\n\r\n    let matches: Vec&lt;Pod&gt; = pod_list.items\r\n        .into_iter()\r\n        .filter(|pod| {\r\n            pod.metadata.name\r\n                .as_ref()\r\n                .map(|name| name.contains(partial))\r\n                .unwrap_or(false)\r\n        })\r\n        .collect();\r\n\r\n    Ok(matches)\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>To create a kubectl plugin whereby, for example, we could rung a new tool like this kubectl log index echo 3 -n dev Where the above would find pods with a partial name of echo and from those pods that match, finds the index 3 (0 indexed). To create a plugin you use the naming [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[760,191],"tags":[],"class_list":["post-11919","post","type-post","status-publish","format-standard","hentry","category-kubectl","category-rust"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11919","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=11919"}],"version-history":[{"count":1,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11919\/revisions"}],"predecessor-version":[{"id":11920,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11919\/revisions\/11920"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=11919"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=11919"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=11919"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}