Repository: bottlerocket-os/bottlerocket
Pull request #4528: migrations: add migrations for nvidia deviceliststrategy
Author: login: sky1122
Created: 2025-05-20T01:51:55Z | Merged: 2025-05-21T22:05:19Z

**Description of changes:**
migration when downgrade from enable cdi to un-cdi.
- when the `nvidia-k8s-device-list-strategy` set to below values

| value 1 | value 2 | value 3 | Downgrade Result |
|-----------|-----------|-----------|---------|
| envvar | | | envvar |
| envvar | volume-mounts | | ennvar |
| envvar | volume-mounts | cdi-cri | ennvar |
| envvar | cdi-cri | | ennvar |
| ennvar | cdi-cri | volume-mounts | ennvar |
| volume-mounts | | | volume-mounts |
| volume-mounts | envvar | | volume-mounts |
| volume-mounts | envvar | cdi-cri | volume-mounts |
| volume-mounts | cdi-cri | | volume-mounts |
| volume-mounts | cdi-cri | envvar | volume-mounts |
| cdi-cri | | | volume-mounts |
| cdi-cri | volume-mounts | | volume-mounts |
| cdi-cri | volume-mounts | envvar | volume-mounts |
| cdi-cri | envvar | | volume-mounts |
| cdi-cri | envvar | volume-mounts | volume-mounts |

**Testing done:**

<details>

```
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": []}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": []
      }
    }
  }
}

bash: api: command not found
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }<!--
Tips:
- Please read CONTRIBUTING.md to understand our process and our requests for PRs.
- Please file an issue before creating a PR so we can discuss the change and confirm it's not already being worked on.
-->

  }
}

Bottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}

< settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "cdi-cri"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
< settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}


string:

(`sudo sheltie`).  When run, this tool drops you into a root shell in the
Bottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",<!--
Tips:
- Please read CONTRIBUTING.md to understand our process and our requests for PRs.
- Please file an issue before creating a PR so we can discuss the change and confirm it's not already being worked on.
-->

    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set settings.kubelet-device-plugins.nvidia.device-list-strategy="cdi-cri"
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "cdi-cri"
      }
    }
  }
}
ottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}

<vidia": {"device-list-strategy": ["volume-mounts","envvar"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-li>
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "volume-mounts",
          "envvar"
        ]
      }
    }
  }
}
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",<!--
Tips:
- Please read CONTRIBUTING.md to understand our process and our requests for PRs.
- Please file an issue before creating a PR so we can discuss the change and confirm it's not already being worked on.
-->

    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": <!--
Tips:
- Please read CONTRIBUTING.md to understand our process and our requests for PRs.
- Please file an issue before creating a PR so we can discuss the change and confirm it's not already being worked on.
-->
"1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["volumn-mounts"]}}}}'
Failed to change settings: Failed PATCH request to '/settings?tx=apiclient-set-Q5P7hIRGMUiehHaC': Status 400 when PATCHing /settings?tx=apiclient-set-Q5P7hIRGMUiehHaC: Json deserialize error: data did not match any variant of untagged enum NvidiaDeviceListStrategy at line 1 column 78
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["volume-mounts"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "volume-mounts"
        ]
      }
    }
  }
}

h: ]apiclient: command not found
[ssm-user@control]$ apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[ssm-user@control]$ apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}

(`sudo sheltie`).  When run, this tool drops you into a root shell in the
Bottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}

[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["envvar","cdi-cri","volume-mounts"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "envvar",
          "cdi-cri",
          "volume-mounts"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
< settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "envvar"
      }
    }
  }
}
[root@admin]#


[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}

[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["envvar","volume-mounts"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "envvar",
          "volume-mounts"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "envvar"
      }
    }
  }
}


[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["envvar"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "envvar"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "envvar"
      }
    }
  }
}


Bottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["cdi-cri","envvar","volume-mounts"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "cdi-cri",
          "envvar",
          "volume-mounts"
        ]
      }
    }
  }
}
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}




Bottlerocket host's root filesystem.
[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["cdi-cri","volume-mounts"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "cdi-cri",
          "volume-mounts"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
< settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "7edb3c30-dirty",
    "pretty_name": "Bottlerocket OS 1.40.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.40.0"
  }
}
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["cdi-cri","volume-mounts"]}}}}'
[root@admin]# apiclient set --json '{"settings": {"kubelet-device-plugins": {"nvidia": {"device-list-strategy": ["volume-mounts","envvar","cdi-cri"]}}}}'
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": [
          "volume-mounts",
          "envvar",
          "cdi-cri"
        ]
      }
    }
  }
}

[root@admin]# apiclient get os
{
  "os": {
    "arch": "x86_64",
    "build_id": "0968c061",
    "pretty_name": "Bottlerocket OS 1.39.0 (aws-k8s-1.31-nvidia)",
    "variant_id": "aws-k8s-1.31-nvidia",
    "version_id": "1.39.0"
  }
}
[root@admin]# apiclient get settings.kubelet-device-plugins.nvidia.device-list-strategy
{
  "settings": {
    "kubelet-device-plugins": {
      "nvidia": {
        "device-list-strategy": "volume-mounts"
      }
    }
  }
}

```

</details>



**Terms of contribution:**

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.



Diff:
diff --git a/Release.toml b/Release.toml
index 1ad480c561c..a35d0c861cb 100644
--- a/Release.toml
+++ b/Release.toml
@@ -1,4 +1,4 @@
-version = "1.39.1"
+version = "1.40.0"
 
 [migrations]
 "(0.3.1, 0.3.2)" = ["migrate_v0.3.2_admin-container-v0-5-0.lz4"]
@@ -419,4 +419,7 @@ version = "1.39.1"
     "migrate_v1.39.0_kubelet-setting-container-log-single-process-oom-kill.lz4"
 ]
 "(1.39.0, 1.39.1)" = []
+"(1.39.1, 1.40.0)" = [
+    "migrate_v1.40.0_kubelet-device-plugins-cdi-settings.lz4"
+]
 
diff --git a/Twoliter.toml b/Twoliter.toml
index 3be2f12fca1..fc287128ed8 100644
--- a/Twoliter.toml
+++ b/Twoliter.toml
@@ -1,5 +1,5 @@
 schema-version = 1
-release-version = "1.39.1"
+release-version = "1.40.0"
 
 [vendor.bottlerocket]
 registry = "public.ecr.aws/bottlerocket"
diff --git a/sbkeys/README.md b/sbkeys/README.md
index a392724085f..e50f9b59e7f 100644
--- a/sbkeys/README.md
+++ b/sbkeys/README.md
@@ -28,10 +28,10 @@ It also uses [virt-fw-vars](https://pypi.org/project/virt-firmware/) to generate
 When specifying an SDK image, these dependencies are run within a container started from that image, and do not need to be installed on the host.
 
 ```shell
-ARCH="$(uname -m)"
-SDK_VERSION="v0.29.0"
+# replace v0.61.0 with the current SDK version
+SDK_VERSION="v0.61.0"
 ./generate-local-sbkeys \
-  --sdk-image "public.ecr.aws/bottlerocket/bottlerocket-sdk-${ARCH}:${SDK_VERSION}" \
+  --sdk-image "public.ecr.aws/bottlerocket/bottlerocket-sdk:${SDK_VERSION}" \
   --output-dir "${PWD}/my-local-profile"
 ```
 
@@ -50,8 +50,8 @@ Note that the cost of these resources, **especially the private CAs**, is nontri
 Although it is possible to use the same private CA and the same KMS key for all roles, doing so would weaken the security of the implementation, and is **strongly discouraged**.
 
 ```shell
-ARCH="$(uname -m)"
-SDK_VERSION="v0.29.0"
+# replace v0.61.0 with the current SDK version
+SDK_VERSION="v0.61.0"
 
 # AWS Private CAs
 PK_CA="arn:aws:acm-pca:us-west-2:999999999999:certificate-authority/11111111-1111-1111-1111-111111111111"
@@ -65,7 +65,7 @@ CODE_SIGN_KEY="arn:aws:kms:us-west-2:999999999999:key/bbbbbbbb-bbbb-bbbb-bbbb-bb
 CONFIG_SIGN_KEY="arn:aws:kms:us-west-2:999999999999:key/cccccccc-cccc-cccc-cccc-cccccccccccc"
 
 ./generate-aws-sbkeys \
-  --sdk-image "public.ecr.aws/bottlerocket/bottlerocket-sdk-${ARCH}:${SDK_VERSION}" \
+  --sdk-image "public.ecr.aws/bottlerocket/bottlerocket-sdk:${SDK_VERSION}" \
   --aws-region us-west-2 \
   --pk-ca "${PK_CA}" \
   --kek-ca "${KEK_CA}" \
diff --git a/sources/Cargo.lock b/sources/Cargo.lock
index 0b9a5b23373..c786d209b7a 100644
--- a/sources/Cargo.lock
+++ b/sources/Cargo.lock
@@ -259,7 +259,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-model-derive"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "darling",
  "quote",
@@ -268,8 +268,8 @@ dependencies = [
 
 [[package]]
 name = "bottlerocket-modeled-types"
-version = "0.8.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+version = "0.9.0"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "base64",
  "bottlerocket-model-derive",
@@ -304,7 +304,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-scalar"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "serde",
  "serde_plain",
@@ -313,7 +313,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-scalar-derive"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-scalar",
  "darling",
@@ -327,7 +327,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-settings-derive"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.8.0#c9e5f989c24bea2006527c6f1a4708225ab70cc4"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -337,8 +337,8 @@ dependencies = [
 
 [[package]]
 name = "bottlerocket-settings-models"
-version = "0.9.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+version = "0.10.0"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -376,7 +376,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-settings-plugin"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.8.0#c9e5f989c24bea2006527c6f1a4708225ab70cc4"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
 dependencies = [
  "abi_stable",
  "bottlerocket-settings-derive",
@@ -388,7 +388,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-settings-sdk"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "argh",
  "bottlerocket-template-helper",
@@ -401,7 +401,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-string-impls-for"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "serde",
 ]
@@ -409,7 +409,7 @@ dependencies = [
 [[package]]
 name = "bottlerocket-template-helper"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -836,6 +836,16 @@ version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
+[[package]]
+name = "kubelet-device-plugins-cdi-settings"
+version = "0.1.0"
+dependencies = [
+ "maplit",
+ "migration-helpers",
+ "serde_json",
+ "snafu",
+]
+
 [[package]]
 name = "kubelet-device-plugins-mig-settings"
 version = "0.1.0"
@@ -1456,7 +1466,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-autoscaling"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1469,7 +1479,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-aws"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1482,7 +1492,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-bootstrap-commands"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1496,7 +1506,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-bootstrap-containers"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1509,7 +1519,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-cloudformation"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1522,7 +1532,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-container-registry"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1535,7 +1545,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-container-runtime"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1548,7 +1558,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-dns"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1561,7 +1571,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-ecs"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1574,7 +1584,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-host-containers"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1587,7 +1597,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-kernel"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1599,8 +1609,8 @@ dependencies = [
 
 [[package]]
 name = "settings-extension-kubelet-device-plugins"
-version = "0.2.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+version = "0.3.0"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1613,7 +1623,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-kubernetes"
 version = "0.3.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1627,7 +1637,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-metrics"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1640,7 +1650,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-motd"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-settings-sdk",
  "bottlerocket-string-impls-for",
@@ -1652,7 +1662,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-network"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1665,7 +1675,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-ntp"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1678,7 +1688,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-nvidia-container-runtime"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1691,7 +1701,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-oci-defaults"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1705,7 +1715,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-oci-hooks"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1718,7 +1728,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-pki"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
@@ -1731,7 +1741,7 @@ dependencies = [
 [[package]]
 name = "settings-extension-updates"
 version = "0.1.0"
-source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.9.0#0d825fcc0a199e233bed88ea9d9d1685c6d966bc"
+source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=bottlerocket-settings-models-v0.10.0#eb08efb19615dd2ea12e32a9eeac65eef03cb15c"
 dependencies = [
  "bottlerocket-model-derive",
  "bottlerocket-modeled-types",
diff --git a/sources/Cargo.toml b/sources/Cargo.toml
index 1751a794efa..28051c7f188 100644
--- a/sources/Cargo.toml
+++ b/sources/Cargo.toml
@@ -37,6 +37,7 @@ members = [
     "settings-migrations/v1.36.0/kubernetes-ecr-credential-providers-expansion",
     "settings-migrations/v1.37.0/delete-configs-and-services-on-downgrade",
     "settings-migrations/v1.39.0/kubelet-setting-container-log-single-process-oom-kill",
+    "settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings",
 
     "settings-plugins/aws-dev",
     "settings-plugins/aws-ecs-1",
@@ -111,22 +112,22 @@ version = "0.1.0"
 
 [workspace.dependencies.bottlerocket-modeled-types]
 git = "https://github.com/bottlerocket-os/bottlerocket-settings-sdk"
-tag = "bottlerocket-settings-models-v0.9.0"
-version = "0.8.0"
+tag = "bottlerocket-settings-models-v0.10.0"
+version = "0.9.0"
 
 [workspace.dependencies.bottlerocket-settings-models]
 git = "https://github.com/bottlerocket-os/bottlerocket-settings-sdk"
-tag = "bottlerocket-settings-models-v0.9.0"
-version = "0.9.0"
+tag = "bottlerocket-settings-models-v0.10.0"
+version = "0.10.0"
 
 [workspace.dependencies.bottlerocket-settings-plugin]
 git = "https://github.com/bottlerocket-os/bottlerocket-settings-sdk"
-tag = "bottlerocket-settings-models-v0.8.0"
+tag = "bottlerocket-settings-models-v0.9.0"
 version = "0.1.0"
 
 [workspace.dependencies.settings-extension-oci-defaults]
 git = "https://github.com/bottlerocket-os/bottlerocket-settings-sdk"
-tag = "bottlerocket-settings-models-v0.9.0"
+tag = "bottlerocket-settings-models-v0.10.0"
 version = "0.1.0"
 
 [profile.release]
diff --git a/sources/api/migration/migration-helpers/src/error.rs b/sources/api/migration/migration-helpers/src/error.rs
index fdda814bf46..649780f1f3b 100644
--- a/sources/api/migration/migration-helpers/src/error.rs
+++ b/sources/api/migration/migration-helpers/src/error.rs
@@ -121,6 +121,9 @@ pub enum Error {
         source
     ))]
     DeserializeSettingsGenerator { source: serde_json::Error },
+
+    #[snafu(display("Setting data '{}' must be either a string or a list", data))]
+    InvalidSettingType { data: String },
 }
 
 /// Result alias containing our Error type.
diff --git a/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/Cargo.toml b/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/Cargo.toml
new file mode 100644
index 00000000000..80462a70ce4
--- /dev/null
+++ b/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "kubelet-device-plugins-cdi-settings"
+version = "0.1.0"
+authors = ["Jingwei Wang <jweiw@amazon.com>"]
+license = "Apache-2.0 OR MIT"
+edition = "2021"
+publish = false
+# Don't rebuild crate just because of changes to README.
+exclude = ["README.md"]
+
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+migration-helpers.workspace = true
+serde_json.workspace = true
+snafu.workspace = true
+
+[dev-dependencies]
+maplit.workspace = true
diff --git a/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs b/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
new file mode 100644
index 00000000000..2562dc289fa
--- /dev/null
+++ b/sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
@@ -0,0 +1,215 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.");
+        Ok(input)
+    }
+
+    /// Older versions don't know about the setting now accepting both a string, a list and a new accepted value "cdi-cri";
+    /// we remove the list option and the "cdi-cri" value so that old versions don't see them and fail deserialization.
+    /// (The settings must be defaulted or generated in new versions, and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = match list.first() {
+                        None | Some(&"cdi-cri") => "volume-mounts".to_string(),
+                        Some(value) => value.to_string(),
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
+                    data: setting.to_string(),
+                }
+                .fail()?,
+            }
+        } else {
+            println!("Found no '{}' to change on downgrade", setting);
+        }
+
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::*;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use serde_json::Value;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceDeviceListStrategy.forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test() {
+        let test_cases: Vec<(MigrationData, HashMap<String, Value>)> = vec![
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => Vec::<String>::new().into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["volume-mounts", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["envvar", "volume-mounts"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar", "volume-mounts"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["envvar", "volume-mounts"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["volume-mounts", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => "cdi-cri".into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                },
+            ),
+        ];
+
+        for (input, expected) in test_cases {
+            let result = ReplaceDeviceListStrategy.backward(input).unwrap();
+            assert_eq!(result.data, expected);
+        }
+    }
+}


Reviews:
user: login: arnaldo2792
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T19:38:00Z
user: login: sky1122
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T20:56:25Z
user: login: arnaldo2792
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T21:28:42Z
user: login: sky1122
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T22:49:27Z
user: login: arnaldo2792
body: I tested the migration, and I confirmed that the values are updated according to the table in the description, well done! :tada: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T05:02:49Z
user: login: yeazelm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:59:46Z
user: login: KCSesh
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:04:04Z
user: login: arnaldo2792
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:48:37Z
user: login: ytsssun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T19:41:22Z
user: login: sky1122
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:37:09Z
user: login: sky1122
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:39:12Z
user: login: ytsssun
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T21:07:02Z
user: login: KCSesh
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T21:12:15Z
user: login: yeazelm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T21:57:26Z

Review comments:
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: This is too generic of a name, and its implementation is very specific to the use case that we have. I think you can just name this `ReplaceDeviceListStrategy`.
created_at: 2025-05-20T17:55:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: This is too generic of a name, and its implementation is very specific to the use case that we have. I think you can just name this `ReplaceDeviceListStrategy`.
created_at: 2025-05-20T18:03:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceListToStringMigration<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceListToStringMigration has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: You need a safeguard here to default to `volume-mounts` if the list is empty, as we do allow now an empty list of device list strategies.
created_at: 2025-05-20T19:25:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceListToStringMigration<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceListToStringMigration has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
+
+                        *data = serde_json::Value::String(new_value.to_string());
+                    }
+
+                    serde_json::Value::String(setting_str) => {
+                        if setting_str == "cdi-cri" {
+                            *data = serde_json::Value::String("volume-mounts".to_string());
+                        } else {
+                            *data = serde_json::Value::String(setting_str.to_string());
+                        }
+                    }
+
+                    _ => {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: If for whatever reason we got here, you should fail. It is unlikely that we will get here, but if we weren't given a list or a string, we should just fail.
created_at: 2025-05-20T19:34:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceListToStringMigration<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceListToStringMigration has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
+
+                        *data = serde_json::Value::String(new_value.to_string());
+                    }
+
+                    serde_json::Value::String(setting_str) => {
+                        if setting_str == "cdi-cri" {
+                            *data = serde_json::Value::String("volume-mounts".to_string());
+                        } else {
+                            *data = serde_json::Value::String(setting_str.to_string());
+                        }
+                    }
+
+                    _ => {
+                        println!(
+                        "'{}' is set to non-list value '{}'; ReplaceListToStringMigration only handles lists, older version can handle the string",
+                        setting, data
+                    );
+                    }
+                }
+            } else {
+                println!("Found no '{}' to change on downgrade", setting);
+            }
+        }
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::ReplaceListToStringMigration;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceListToStringMigration(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            }
+        );
+    }
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: I don't think you need this case, the one below should be sufficient.
created_at: 2025-05-20T19:36:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceListToStringMigration<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceListToStringMigration has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
+
+                        *data = serde_json::Value::String(new_value.to_string());
+                    }
+
+                    serde_json::Value::String(setting_str) => {
+                        if setting_str == "cdi-cri" {
+                            *data = serde_json::Value::String("volume-mounts".to_string());
+                        } else {
+                            *data = serde_json::Value::String(setting_str.to_string());
+                        }
+                    }
+
+                    _ => {
+                        println!(
+                        "'{}' is set to non-list value '{}'; ReplaceListToStringMigration only handles lists, older version can handle the string",
+                        setting, data
+                    );
+                    }
+                }
+            } else {
+                println!("Found no '{}' to change on downgrade", setting);
+            }
+        }
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::ReplaceListToStringMigration;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceListToStringMigration(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            }
+        );
+    }
+
+    #[test]
+    fn forward_test_2() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceListToStringMigration(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test_1() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            },
+            metadata: HashMap::new(),
+        };
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: Can you please write your test cases in a way that you provide the test case in a vec, and you iterate through the cases?

You will need an extra case to handle the empty list.
created_at: 2025-05-20T19:37:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceListToStringMigration(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceListToStringMigration<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceListToStringMigration<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceListToStringMigration has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
+
+                        *data = serde_json::Value::String(new_value.to_string());
+                    }
+
+                    serde_json::Value::String(setting_str) => {
+                        if setting_str == "cdi-cri" {
+                            *data = serde_json::Value::String("volume-mounts".to_string());
+                        } else {
+                            *data = serde_json::Value::String(setting_str.to_string());
+                        }
+                    }
+
+                    _ => {
+                        println!(
+                        "'{}' is set to non-list value '{}'; ReplaceListToStringMigration only handles lists, older version can handle the string",
+                        setting, data
+                    );
+                    }
+                }
+            } else {
+                println!("Found no '{}' to change on downgrade", setting);
+            }
+        }
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::ReplaceListToStringMigration;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceListToStringMigration(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            }
+        );
+    }
+
+    #[test]
+    fn forward_test_2() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceListToStringMigration(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test_1() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => vec!["cdi-cri"].into(),
+            },
+            metadata: HashMap::new(),
+        };
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: sky1122
body: changed 
created_at: 2025-05-20T20:56:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,6 +121,9 @@ pub enum Error {
         source
     ))]
     DeserializeSettingsGenerator { source: serde_json::Error },
+
+    #[snafu(display("setting date '{}' must be either a string or a list", data))]
path: sources/api/migration/migration-helpers/src/error.rs
user: login: arnaldo2792
body: Do you mean `data`?
created_at: 2025-05-20T21:20:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: ```suggestion
/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
```
created_at: 2025-05-20T21:23:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceDeviceListStrategy<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceDeviceListStrategy<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list.is_empty() {
+                            "volume-mounts".to_string()
+                        } else if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: ```suggestion
                        let new_value = if list.is_empty() || list[0] == "cdi-cri" {
                            "volume-mounts".to_string()
                        } else {
                            list[0].to_string()
                        };
```
created_at: 2025-05-20T21:24:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceDeviceListStrategy<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceDeviceListStrategy<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
+                match data {
+                    serde_json::Value::Array(arr) => {
+                        let list: Vec<&str> = arr
+                            .iter()
+                            .map(|v| v.as_str())
+                            .collect::<Option<Vec<&str>>>()
+                            .context(error::ReplaceListContentsSnafu {
+                                setting: *setting,
+                                data: arr.clone(),
+                            })?;
+
+                        let new_value = if list.is_empty() {
+                            "volume-mounts".to_string()
+                        } else if list[0] == "cdi-cri" {
+                            "volume-mounts".to_string()
+                        } else {
+                            list[0].to_string()
+                        };
+
+                        *data = serde_json::Value::String(new_value.to_string());
+                    }
+
+                    serde_json::Value::String(setting_str) => {
+                        if setting_str == "cdi-cri" {
+                            *data = serde_json::Value::String("volume-mounts".to_string());
+                        } else {
+                            *data = serde_json::Value::String(setting_str.to_string());
+                        }
+                    }
+
+                    _ => error::InvalidSettingTypeSnafu {
+                        data: setting.to_string(),
+                    }
+                    .fail()?,
+                }
+            } else {
+                println!("Found no '{}' to change on downgrade", setting);
+            }
+        }
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::ReplaceDeviceListStrategy;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use std::collections::HashMap;
+    use serde_json::Value;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceDeviceListStrategy(&["hi"]).forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                "hi".into() => "cdi-cri".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test() {
+        struct TestCase {
+            input: MigrationData,
+            expected: HashMap<String, Value>,
+        }
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: You don't need a struct, you can use a `tuple`:

```
(MigrationData, HashMap<String, Value>)
```
created_at: 2025-05-20T21:26:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: Rather than making `settings.kubelet-device-plugins.nvidia.device-list-strategy` a parameter to your migration, can you make it a constant in your file so that we know that's the only value we are changing?
created_at: 2025-05-20T21:27:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceDeviceListStrategy<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceDeviceListStrategy<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: If you change the inputs to the migration, and you use the constant, you can remove this `for` as we only care about the one setting.
created_at: 2025-05-20T21:28:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,234 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy(&[
+        "settings.kubelet-device-plugins.nvidia.device-list-strategy",
+    ]))
+}
+
+/// We change the k8s api nvidia list strategy settings from a string to a list
+/// or "cdi-cri" to "volume-mounts"
+pub struct ReplaceDeviceListStrategy<'a>(pub &'a [&'static str]);
+
+impl Migration for ReplaceDeviceListStrategy<'_> {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        for setting in self.0 {
+            if let Some(data) = input.data.get_mut(*setting) {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: sky1122
body: changed
created_at: 2025-05-20T22:49:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,5 +1,5 @@
 schema-version = 1
-release-version = "1.39.1"
+release-version = "1.40.0"
path: Twoliter.toml
user: login: yeazelm
body: Can this bump be a separate commit?
created_at: 2025-05-21T15:46:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,6 +121,9 @@ pub enum Error {
         source
     ))]
     DeserializeSettingsGenerator { source: serde_json::Error },
+
+    #[snafu(display("setting data '{}' must be either a string or a list", data))]
path: sources/api/migration/migration-helpers/src/error.rs
user: login: KCSesh
body: Nit: 

```suggestion
    #[snafu(display("Setting data '{}' must be either a string or a list", data))]
```
created_at: 2025-05-21T16:26:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
+                    data: setting.to_string(),
+                }
+                .fail()?,
+            }
+        } else {
+            println!("Found no '{}' to change on downgrade", setting);
+        }
+
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::*;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use serde_json::Value;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: KCSesh
body: Nit: Forward test shouldn't be testing "cdi-cri" to "cdi-cri" technically right? 
created_at: 2025-05-21T16:40:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
+                    data: setting.to_string(),
+                }
+                .fail()?,
+            }
+        } else {
+            println!("Found no '{}' to change on downgrade", setting);
+        }
+
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::*;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use serde_json::Value;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "cdi-cri".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceDeviceListStrategy.forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "cdi-cri".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test() {
+        let test_cases: Vec<(MigrationData, HashMap<String, Value>)> = vec![
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => Vec::<String>::new().into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["volume-mounts", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["envvar", "volume-mounts"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar", "volume-mounts"].into(),
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: KCSesh
body: Just so it is clear, is the logic on this one, restore the "[default](https://bottlerocket.dev/en/os/1.34.x/api/settings/kubelet-device-plugins/#nvidia-device-list-strategy)" option when we have to pick the on downgrade? 
created_at: 2025-05-21T16:43:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: KCSesh
body: This comment is a bit inaccurate since older versions do know about the setting, but don't know about the fact that it can be a list and has a new value. 
created_at: 2025-05-21T16:50:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: KCSesh
body: In our [common migrations](https://github.com/bottlerocket-os/bottlerocket/blob/1a917254b75139eec9e411f9767e053ef18cfeaf/sources/api/migration/migration-helpers/src/common_migrations.rs#L297) we often default to not outright failing but instead logging and attempting to move forward. I am not opposed to this behavior, but just want to understand if this was intentional. 
created_at: 2025-05-21T17:03:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
+                    data: setting.to_string(),
+                }
+                .fail()?,
+            }
+        } else {
+            println!("Found no '{}' to change on downgrade", setting);
+        }
+
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::*;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use serde_json::Value;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: Ok, so for correctness, because the older version isn't aware of `cdi-cri`, we should be checking for `envvar` or `volume-mounts`.
created_at: 2025-05-21T17:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
+                    data: setting.to_string(),
+                }
+                .fail()?,
+            }
+        } else {
+            println!("Found no '{}' to change on downgrade", setting);
+        }
+
+        Ok(input)
+    }
+}
+
+#[cfg(test)]
+mod test_replace_list {
+    use super::*;
+    use crate::{Migration, MigrationData};
+    use maplit::hashmap;
+    use serde_json::Value;
+    use std::collections::HashMap;
+
+    #[test]
+    fn forward_test() {
+        let data = MigrationData {
+            data: hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "cdi-cri".into(),
+            },
+            metadata: HashMap::new(),
+        };
+        let result = ReplaceDeviceListStrategy.forward(data).unwrap();
+        assert_eq!(
+            result.data,
+            hashmap! {
+                DEVICE_LIST_STRATEGY_SETTING.into() => "cdi-cri".into(),
+            }
+        );
+    }
+
+    #[test]
+    fn backward_test() {
+        let test_cases: Vec<(MigrationData, HashMap<String, Value>)> = vec![
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => Vec::<String>::new().into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["volume-mounts", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["envvar", "volume-mounts"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "envvar".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar"].into(),
+                    },
+                    metadata: HashMap::new(),
+                },
+                hashmap! {
+                    DEVICE_LIST_STRATEGY_SETTING.into() => "volume-mounts".into(),
+                },
+            ),
+            (
+                MigrationData {
+                    data: hashmap! {
+                        DEVICE_LIST_STRATEGY_SETTING.into() => vec!["cdi-cri", "envvar", "volume-mounts"].into(),
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: Correct.
created_at: 2025-05-21T17:46:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
+                }
+
+                _ => error::InvalidSettingTypeSnafu {
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: arnaldo2792
body: The reason I chose to error was to prevent the host from running in an "undesired" state. Ultimately, if the migration silently fails, the API server will fail if the type isn't something it understands.
created_at: 2025-05-21T17:48:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: ytsssun
body: Nit: drop the extra "," here.
created_at: 2025-05-21T19:01:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: ytsssun
body: Changing it to list is not quite correct here right, now it supports both string and list. Maybe "from string to a more flexible type that supports both string and list"? This explains why the `forward` can be a no-op.
created_at: 2025-05-21T19:06:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: ytsssun
body: This is mostly a coding style preference:

Vec has util function [first](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.first) which is the more "idiomatic" way to get the first item so that we don't need to use [0]. 

This blob can be:

```suggestion
                    let new_value = match list.first() {
                         None | Some(&"cdi-cri") => "volume-mounts".to_string(),
                         Some(value) => value.to_string(),
                     };          
```


created_at: 2025-05-21T19:35:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: ytsssun
body: Given that the setting values are basically enum, can this be:

```suggestion
                   *data = serde_json::Value::String(setting_str.replace("cdi-cri", "volume-mounts").to_string());
```
created_at: 2025-05-21T19:40:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
+pub struct ReplaceDeviceListStrategy;
+
+impl Migration for ReplaceDeviceListStrategy {
+    /// New versions must either have a default for the settings or generate them; we don't need to
+    /// do anything.
+    fn forward(&mut self, input: MigrationData) -> Result<MigrationData> {
+        println!("ReplaceDeviceListStrategy has no work to do on upgrade.",);
+        Ok(input)
+    }
+
+    /// Older versions don't know about the settings; we remove them so that old versions don't see
+    /// them and fail deserialization.  (The settings must be defaulted or generated in new versions,
+    /// and safe to remove.)
+    fn backward(&mut self, mut input: MigrationData) -> Result<MigrationData> {
+        let setting = DEVICE_LIST_STRATEGY_SETTING;
+        if let Some(data) = input.data.get_mut(setting) {
+            match data {
+                serde_json::Value::Array(arr) => {
+                    let list: Vec<&str> = arr
+                        .iter()
+                        .map(|v| v.as_str())
+                        .collect::<Option<Vec<&str>>>()
+                        .context(error::ReplaceListContentsSnafu {
+                            setting,
+                            data: arr.clone(),
+                        })?;
+
+                    let new_value = if list.is_empty() || list[0] == "cdi-cri" {
+                        "volume-mounts".to_string()
+                    } else {
+                        list[0].to_string()
+                    };
+
+                    *data = serde_json::Value::String(new_value.to_string());
+                }
+
+                serde_json::Value::String(setting_str) => {
+                    if setting_str == "cdi-cri" {
+                        *data = serde_json::Value::String("volume-mounts".to_string());
+                    } else {
+                        *data = serde_json::Value::String(setting_str.to_string());
+                    }
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: sky1122
body: I would like to keep the if/else since the logic is more user readable.
created_at: 2025-05-21T20:37:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+use migration_helpers::{error, migrate, Migration, MigrationData, Result};
+use snafu::OptionExt;
+
+const DEVICE_LIST_STRATEGY_SETTING: &str =
+    "settings.kubelet-device-plugins.nvidia.device-list-strategy";
+
+#[snafu::report]
+fn main() -> Result<()> {
+    migrate(ReplaceDeviceListStrategy)
+}
+
+/// We changed the type of the device-list-strategy in the NVIDIA Kubernetes Device
+/// plugin API from a string to a list, and accept "cdi-cri" as a valid value
path: sources/settings-migrations/v1.40.0/kubelet-device-plugins-cdi-settings/src/main.rs
user: login: sky1122
body: this comment intend to explain the different between the old version and the previous version.
created_at: 2025-05-21T20:39:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: ytsssun
created_at: 2025-05-20T17:56:40Z
body: Could we combine this with https://github.com/bottlerocket-os/bottlerocket/pull/4529

SDK bump is a dependency to this.
author_association: CONTRIBUTOR
user: login: sky1122
created_at: 2025-05-21T20:47:30Z
body: force pushed to address the feedbacks
author_association: CONTRIBUTOR
user: login: sky1122
created_at: 2025-05-21T20:51:10Z
body: force pushed again to change one comment to be more accurate
author_association: CONTRIBUTOR