Edit yaml file key and value in rust

I'm trying to update kubernetes config yaml file which looks like this.

contexts:
- context:
    cluster: kubernetes
    namespace: test1
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
- context:
    cluster: kubernetes
    user: myuser
  name: myuser
- context:
    cluster: rancher-desktop
    namespace: default    ###### This line i need to add if it does not exists  ######
    user: rancher-desktop
  name: rancher-desktop

Here is my code which can edit values but can't add new key (contexts[3].context.namespace)

            let kn = "test";
            let index_count = 3;
            let contents = fs::read_to_string(/home/home/.kube/config)
            .expect("Something went wrong reading the file");
            let mut value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
            *value.get_mut("contexts").unwrap().get_mut(index_count).unwrap().get_mut("context").unwrap().get_mut("namespace").unwrap() = kn.into();

            let writer = serde_yaml::to_string(&value).unwrap();
            let mut file = std::fs::File::create(kubeconfig).expect("create failed");
            file.write_all(writer.as_bytes()).expect("write failed");
            println!("Switched namespace to \"{}\"",kn.green());

I get this error thread 'main' panicked at 'called Option::unwrap() on a None value'

Please help.

I'm able to edit yaml file as below but i think there would be better way to do it.

            let kn = "test";
            let index_count = 3;
            let contents = fs::read_to_string("/home/home/.kube/config")
            .expect("Something went wrong reading the file");

            let mut value: serde_yaml::Mapping = serde_yaml::from_str(&contents).unwrap();
            let data: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
            let cluster = data["contexts"][index_count]["context"]["cluster"].as_str().unwrap();
            let user = data["contexts"][index_count]["context"]["user"].as_str().unwrap();

            let mut map = Mapping::new();
            map.insert("cluster".into(), cluster.into());
            map.insert("namespace".into(), kn.into());
            map.insert("user".into(), user.into());

            *value.get_mut("contexts").unwrap().get_mut(index_count).unwrap().get_mut("context").unwrap().as_mapping_mut().unwrap() = map.into();
            let writer = serde_yaml::to_string(&value).unwrap();
            let mut file = std::fs::File::create(kubeconfig).expect("create failed");
            file.write_all(writer.as_bytes()).expect("write failed");
            println!("Switched namespace to \"{}\"",kn.green());

Your code isn't currently trying to check whether anything exists in the map in the first place, which is what the YAML comment says you want to do.

Additionally the sequences are zero indexed so index_count = 3 is causing a panic in your initial code, you aren't even getting to the editing part. get_mut also doesn't allow you to insert a new key if it didn't already exist in the map. You need to use insert on Mapping for that.

Here's a solution that actually checks whether the key is present before inserting.

Playground

fn main() {
    let kn = "test";
    let index_count = 2; // You had 3 here, but the index is 0 based, so 3 was outside the range hence the panic.
    let contents = "\
    contexts:
    - context:
        cluster: kubernetes
        namespace: test1
        user: kubernetes-admin
      name: kubernetes-admin@kubernetes
    - context:
        cluster: kubernetes
        user: myuser
      name: myuser
    - context:
        cluster: rancher-desktop
        #namespace: default    ###### This line i need to add if it does not exists  ######
        user: rancher-desktop
      name: rancher-desktop";

    let mut value: serde_yaml::Value = serde_yaml::from_str(contents).unwrap();
    let target = value
        .get_mut("contexts")
        .unwrap()
        .get_mut(index_count)
        .unwrap()
        .get_mut("context")
        .unwrap()
        .as_mapping_mut()
        .unwrap();

    if !target.contains_key(&"namespace".into()) {
        target.insert("namespace".into(), kn.into());
    }

    println!("{}", serde_yaml::to_string(&value).unwrap());
}

If you uncomment the namespace in the last context in the YAML string you'll see that the output doesn't change the namespace.

2 Likes

Thank you @semicoleon again :slight_smile: . Code looks more clear now.

Sorry again for mistake for index value. It was actually 2.

Here is the implementation - optimized code for namespace switch · koolwithk/kx-kn-rust@a622012 · GitHub