diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8e34619f8..2410063dd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -56,7 +56,7 @@ updates: # The release branch(es): - package-ecosystem: "gomod" - target-branch: release-1.19 + target-branch: release-1.20 directories: - "/" # We don't update development or test dependencies on release branches @@ -82,7 +82,7 @@ updates: - k8s.io/klog/* - package-ecosystem: "docker" - target-branch: release-1.19 + target-branch: release-1.20 directories: # CUDA image - "/deployments/container" @@ -102,7 +102,7 @@ updates: - maintenance - package-ecosystem: "github-actions" - target-branch: release-1.19 + target-branch: release-1.20 directory: "/" schedule: interval: "weekly" diff --git a/.github/scripts/backport.js b/.github/scripts/backport.js index 4964f76d8..3c0ce4f12 100644 --- a/.github/scripts/backport.js +++ b/.github/scripts/backport.js @@ -100,7 +100,42 @@ for (const targetBranch of branches) { // Push the backport branch (force to handle updates) core.info(`Pushing ${backportBranch} to origin`); execSync(`git push --force-with-lease origin ${backportBranch}`, { stdio: 'inherit' }); - + + // Re-create each new commit through the Git Data API so the resulting chain shows as "Verified" + core.info(`Re-creating commits via the Git Data API to get verified signatures`); + const newCommitShas = execSync(`git log --format=%H ${targetBranch}..${backportBranch}`, { encoding: 'utf-8' }) + .trim().split('\n').filter(Boolean).reverse(); // oldest -> newest + + const { data: baseRef } = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${targetBranch}` + }); + let parentSha = baseRef.object.sha; + + for (const sha of newCommitShas) { + const treeSha = execSync(`git rev-parse ${sha}^{tree}`, { encoding: 'utf-8' }).trim(); + const message = execSync(`git log -1 --format=%B ${sha}`, { encoding: 'utf-8' }); + + const { data: newCommit } = await github.rest.git.createCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + message, + tree: treeSha, + parents: [parentSha] + }); + parentSha = newCommit.sha; + } + + core.info(`Repointing ${backportBranch} at signed commit ${parentSha}`); + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${backportBranch}`, + sha: parentSha, + force: true + }); + // Check if a PR already exists for this backport branch const { data: existingPRs } = await github.rest.pulls.list({ owner: context.repo.owner, diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 5dbb3c8ae..90f1108ae 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -87,7 +87,8 @@ jobs: E2E_SSH_HOST: ${{ steps.holodeck_public_dns_name.outputs.result }} run: | e2e_ssh_key=$(mktemp) - echo "${{ secrets.AWS_SSH_KEY }}" > "$e2e_ssh_key" + trap 'rm -f "$e2e_ssh_key"' EXIT + printf '%s' "${{ secrets.AWS_SSH_KEY }}" > "$e2e_ssh_key" chmod 600 "$e2e_ssh_key" export E2E_SSH_KEY="$e2e_ssh_key" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..b59274bd7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,85 @@ +# Contributor Covenant 3.0 Code of Conduct + +This Code of Conduct applies to participation in the nvidia-container-toolkit project and community. + +## Our Pledge + +We pledge to make our community welcoming, safe, and equitable for all. + +We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant. + +## Encouraged Behaviors + +While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language. + +With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including: + +1. Respecting the **purpose of our community**, our activities, and our ways of gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our community**. + +## Restricted Behaviors + +We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct. + +1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis of immutable identities or traits. +4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. + +### Other Restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions. +2. **Failing to credit sources.** Not properly crediting the sources of content you contribute. +3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the community. +4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors. + +## Reporting an Issue + +Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm. + +When an incident does occur, it is important to report it promptly. To report a possible violation, email [GitHub_Conduct@nvidia.com](mailto:GitHub_Conduct@nvidia.com). + +Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution. + +## Addressing and Repairing Harm + +If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped. + +1. Warning + 1. Event: A violation involving a single incident or series of incidents. + 2. Consequence: A private, written warning from the Community Moderators. + 3. Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations. +2. Temporarily Limited Activities + 1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation. + 2. Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members. + 3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over. +3. Temporary Suspension + 1. Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation. + 2. Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions. + 3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted. +4. Permanent Ban + 1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member. + 2. Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior. + 3. Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). + +Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/). + +For answers to common questions about Contributor Covenant, see the [FAQ](https://www.contributor-covenant.org/faq). Translations are provided at the [translations page](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found on the [resources page](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla's code of conduct team](https://github.com/mozilla/inclusion). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bcaaabbb..78fe7984a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,8 @@ Want to hack on the NVIDIA Container Toolkit Project? Awesome! We only require you to sign your work, the below section describes this! +All contributions must adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). + ## Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 000000000..8d08fe36a --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,78 @@ +# Governance for nvidia-container-toolkit + +## Scope + +This document describes the governance model for the nvidia-container-toolkit project, +including roles, responsibilities, and decision making processes. + +## Roles and responsibilities + +### Maintainers + +Maintainers are the individuals responsible for the long-term health and direction +of the project. They have the final say on key decisions but are expected to +collaborate with the community and seek consensus when possible. + +- **Responsibilities**: + - Oversee the technical direction of the project. + - Make final decisions on contributions and community matters. + - Ensure code quality and maintainability. + - Manage project releases and documentation. + - Resolve conflicts and mediate disputes within the community. + - Actively participate in community discussions. + +### Contributors + +Contributors are individuals who actively participate in the project by submitting +code, bug reports, documentation, or other contributions. Contributors may be +individuals, teams, or organizations. + +- **Responsibilities**: + - Follow the project’s guidelines for contributions. + - Engage in discussions and provide feedback on project decisions. + - Respect the project's code of conduct. + - Strive for high-quality contributions that adhere to the project's goals. + +### Community + +The community consists of all individuals interested in the project, including end users, +contributors, and other stakeholders. The community is encouraged to contribute ideas, +report issues, and engage in discussions. + +- **Responsibilities**: + - Provide feedback on project decisions and contribute to discussions. + - Participate in community events and initiatives. + - Respect the project’s code of conduct. + +## Decision Making + +### Routine Decisions + +Routine decisions — bug fixes, minor features, documentation, dependency updates — proceed +via a PR. At least one maintainer approval is required for technical acceptance; a maintainer +then merges after verifying project requirements are met. + +### Core Decisions + +Core decisions that involve significant changes to the project require a prior proposal before +implementation work begins. A change is significant if it involves: + +- Architectural changes +- Major new features or subsystems +- Breaking changes to APIs or behavior +- Changes to the contribution model, release cadence, or this governance document + +A proposal is a GitHub issue that explains what is changing, why, what alternatives were +considered, and the impact on existing contributors and users. + +Core decisions are made by the maintainers. For contributors who want to propose and contribute +a major change, the process is as follows: + +- A proposal for a major change will be submitted as an issue. +- The maintainers will discuss the proposal and seek feedback from the community. +- Once the discussion is concluded, maintainers will make the final decision. + +## Changes to Governance + +A change to this document follows the process for core decisions defined above. Maintainers make +the final decision on changes to the governance model. diff --git a/README.md b/README.md index 90129d1a1..a1a50af45 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,4 @@ The [user guide](https://docs.nvidia.com/datacenter/cloud-native/container-toolk * Please let us know by [filing a new issue](https://github.com/NVIDIA/nvidia-container-toolkit/issues/new) * You can contribute by creating a [pull request](https://github.com/NVIDIA/nvidia-container-toolkit/compare) to our public GitHub repository +* Contributions must adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 690a1d8b8..5cdb06420 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -19,48 +19,48 @@ busybox binary is added to the image, which is licensed under GPLv2. ## Go Module Index -| Package | License | Module | -|---------|---------|--------| -| `github.com/Masterminds/semver/v3` | MIT | `github.com/Masterminds/semver/v3@v3.5.0` | -| `github.com/NVIDIA/go-nvlib/pkg` | Apache-2.0 | `github.com/NVIDIA/go-nvlib@v0.12.0` | -| `github.com/NVIDIA/go-nvml/pkg` | Apache-2.0 | `github.com/NVIDIA/go-nvml@v0.13.3-1` | -| `github.com/containerd/log` | Apache-2.0 | `github.com/containerd/log@v0.1.0` | -| `github.com/containerd/nri/pkg` | Apache-2.0 | `github.com/containerd/nri@v0.12.1` | -| `github.com/containerd/ttrpc` | Apache-2.0 | `github.com/containerd/ttrpc@v1.2.7` | -| `github.com/cyphar/filepath-securejoin` | BSD-3-Clause / MPL-2.0 | `github.com/cyphar/filepath-securejoin@v0.7.0` | -| `github.com/fsnotify/fsnotify` | BSD-3-Clause | `github.com/fsnotify/fsnotify@v1.7.0` | -| `github.com/google/uuid` | BSD-3-Clause | `github.com/google/uuid@v1.6.0` | -| `github.com/knqyf263/go-plugin/wasm` | MIT | `github.com/knqyf263/go-plugin@v0.9.0` | -| `github.com/moby/sys/capability` | BSD-2-Clause | `github.com/moby/sys/capability@v0.4.0` | -| `github.com/moby/sys/mountinfo` | Apache-2.0 | `github.com/moby/sys/mountinfo@v0.7.2` | -| `github.com/moby/sys/reexec` | Apache-2.0 | `github.com/moby/sys/reexec@v0.1.0` | -| `github.com/opencontainers/cgroups/devices/config` | Apache-2.0 | `github.com/opencontainers/cgroups@v0.0.7` | -| `github.com/opencontainers/runc` | Apache-2.0 | `github.com/opencontainers/runc@v1.4.3` | -| `github.com/opencontainers/runtime-spec/specs-go` | Apache-2.0 | `github.com/opencontainers/runtime-spec@v1.3.0` | -| `github.com/opencontainers/runtime-tools` | Apache-2.0 | `github.com/opencontainers/runtime-tools@v0.9.1-0.20251114084447-edf4cb3d2116` | -| `github.com/pelletier/go-toml` | Apache-2.0 / MIT | `github.com/pelletier/go-toml@v1.9.5` | -| `github.com/prometheus/procfs` | Apache-2.0 | `github.com/prometheus/procfs@v0.21.1` | -| `github.com/sirupsen/logrus` | MIT | `github.com/sirupsen/logrus@v1.9.4` | -| `github.com/tetratelabs/wazero` | Apache-2.0 | `github.com/tetratelabs/wazero@v1.11.0` | -| `github.com/urfave/cli-altsrc/v3` | MIT | `github.com/urfave/cli-altsrc/v3@v3.1.0` | -| `github.com/urfave/cli/v3` | MIT | `github.com/urfave/cli/v3@v3.10.1` | -| `golang.org/x/mod/semver` | BSD-3-Clause | `golang.org/x/mod@v0.38.0` | -| `golang.org/x/sys` | BSD-3-Clause | `golang.org/x/sys@v0.47.0` | -| `google.golang.org/genproto/googleapis/rpc/status` | Apache-2.0 | `google.golang.org/genproto/googleapis/rpc@v0.0.0-20260414002931-afd174a4e478` | -| `google.golang.org/grpc` | Apache-2.0 | `google.golang.org/grpc@v1.82.1` | -| `google.golang.org/protobuf` | BSD-3-Clause | `google.golang.org/protobuf@v1.36.11` | -| `gopkg.in/yaml.v3` | MIT | `gopkg.in/yaml.v3@v3.0.1` | -| `sigs.k8s.io/yaml` | Apache-2.0 / BSD-3-Clause / MIT | `sigs.k8s.io/yaml@v1.4.0` | -| `sigs.k8s.io/yaml/goyaml.v2` | Apache-2.0 | `sigs.k8s.io/yaml@v1.4.0` | -| `tags.cncf.io/container-device-interface` | Apache-2.0 | `tags.cncf.io/container-device-interface@v1.1.0` | -| `tags.cncf.io/container-device-interface/specs-go` | Apache-2.0 | `tags.cncf.io/container-device-interface/specs-go@v1.1.0` | +| Package | License | Dependency | +|---------|---------|------------| +| `github.com/Masterminds/semver/v3` | MIT | `github.com/Masterminds/semver/v3` | +| `github.com/NVIDIA/go-nvlib/pkg` | Apache-2.0 | `github.com/NVIDIA/go-nvlib` | +| `github.com/NVIDIA/go-nvml/pkg` | Apache-2.0 | `github.com/NVIDIA/go-nvml` | +| `github.com/containerd/log` | Apache-2.0 | `github.com/containerd/log` | +| `github.com/containerd/nri/pkg` | Apache-2.0 | `github.com/containerd/nri` | +| `github.com/containerd/ttrpc` | Apache-2.0 | `github.com/containerd/ttrpc` | +| `github.com/cyphar/filepath-securejoin` | BSD-3-Clause / MPL-2.0 | `github.com/cyphar/filepath-securejoin` | +| `github.com/fsnotify/fsnotify` | BSD-3-Clause | `github.com/fsnotify/fsnotify` | +| `github.com/google/uuid` | BSD-3-Clause | `github.com/google/uuid` | +| `github.com/knqyf263/go-plugin/wasm` | MIT | `github.com/knqyf263/go-plugin` | +| `github.com/moby/sys/capability` | BSD-2-Clause | `github.com/moby/sys/capability` | +| `github.com/moby/sys/mountinfo` | Apache-2.0 | `github.com/moby/sys/mountinfo` | +| `github.com/moby/sys/reexec` | Apache-2.0 | `github.com/moby/sys/reexec` | +| `github.com/opencontainers/cgroups/devices/config` | Apache-2.0 | `github.com/opencontainers/cgroups` | +| `github.com/opencontainers/runc` | Apache-2.0 | `github.com/opencontainers/runc` | +| `github.com/opencontainers/runtime-spec/specs-go` | Apache-2.0 | `github.com/opencontainers/runtime-spec` | +| `github.com/opencontainers/runtime-tools` | Apache-2.0 | `github.com/opencontainers/runtime-tools` | +| `github.com/pelletier/go-toml` | Apache-2.0 / MIT | `github.com/pelletier/go-toml` | +| `github.com/prometheus/procfs` | Apache-2.0 | `github.com/prometheus/procfs` | +| `github.com/sirupsen/logrus` | MIT | `github.com/sirupsen/logrus` | +| `github.com/tetratelabs/wazero` | Apache-2.0 | `github.com/tetratelabs/wazero` | +| `github.com/urfave/cli-altsrc/v3` | MIT | `github.com/urfave/cli-altsrc/v3` | +| `github.com/urfave/cli/v3` | MIT | `github.com/urfave/cli/v3` | +| `golang.org/x/mod/semver` | BSD-3-Clause | `golang.org/x/mod` | +| `golang.org/x/sys` | BSD-3-Clause | `golang.org/x/sys` | +| `google.golang.org/genproto/googleapis/rpc/status` | Apache-2.0 | `google.golang.org/genproto/googleapis/rpc` | +| `google.golang.org/grpc` | Apache-2.0 | `google.golang.org/grpc` | +| `google.golang.org/protobuf` | BSD-3-Clause | `google.golang.org/protobuf` | +| `gopkg.in/yaml.v3` | MIT | `gopkg.in/yaml.v3` | +| `sigs.k8s.io/yaml` | Apache-2.0 / BSD-3-Clause / MIT | `sigs.k8s.io/yaml` | +| `sigs.k8s.io/yaml/goyaml.v2` | Apache-2.0 | `sigs.k8s.io/yaml` | +| `tags.cncf.io/container-device-interface` | Apache-2.0 | `tags.cncf.io/container-device-interface` | +| `tags.cncf.io/container-device-interface/specs-go` | Apache-2.0 | `tags.cncf.io/container-device-interface/specs-go` | ## Go Module License Texts ### github.com/Masterminds/semver/v3 * License: MIT -* Module: github.com/Masterminds/semver/v3@v3.5.0 +* Module: github.com/Masterminds/semver/v3 #### LICENSE.txt @@ -91,7 +91,7 @@ THE SOFTWARE. ### github.com/NVIDIA/go-nvlib/pkg * License: Apache-2.0 -* Module: github.com/NVIDIA/go-nvlib@v0.12.0 +* Module: github.com/NVIDIA/go-nvlib #### LICENSE @@ -315,7 +315,7 @@ the PCI ID Project at https://pci-ids.ucw.cz/. ### github.com/NVIDIA/go-nvml/pkg * License: Apache-2.0 -* Module: github.com/NVIDIA/go-nvml@v0.13.3-1 +* Module: github.com/NVIDIA/go-nvml #### LICENSE @@ -529,7 +529,7 @@ the PCI ID Project at https://pci-ids.ucw.cz/. ### github.com/containerd/log * License: Apache-2.0 -* Module: github.com/containerd/log@v0.1.0 +* Module: github.com/containerd/log #### LICENSE @@ -732,7 +732,7 @@ the PCI ID Project at https://pci-ids.ucw.cz/. ### github.com/containerd/nri/pkg * License: Apache-2.0 -* Module: github.com/containerd/nri@v0.12.1 +* Module: github.com/containerd/nri #### LICENSE @@ -945,7 +945,7 @@ the PCI ID Project at https://pci-ids.ucw.cz/. ### github.com/containerd/ttrpc * License: Apache-2.0 -* Module: github.com/containerd/ttrpc@v1.2.7 +* Module: github.com/containerd/ttrpc #### LICENSE @@ -1158,7 +1158,7 @@ the PCI ID Project at https://pci-ids.ucw.cz/. ### github.com/cyphar/filepath-securejoin * License: BSD-3-Clause / MPL-2.0 -* Module: github.com/cyphar/filepath-securejoin@v0.7.0 +* Module: github.com/cyphar/filepath-securejoin #### COPYING.md @@ -2030,7 +2030,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice ### github.com/fsnotify/fsnotify * License: BSD-3-Clause -* Module: github.com/fsnotify/fsnotify@v1.7.0 +* Module: github.com/fsnotify/fsnotify #### LICENSE @@ -2067,7 +2067,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/google/uuid * License: BSD-3-Clause -* Module: github.com/google/uuid@v1.6.0 +* Module: github.com/google/uuid #### LICENSE @@ -2106,7 +2106,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/knqyf263/go-plugin/wasm * License: MIT -* Module: github.com/knqyf263/go-plugin@v0.9.0 +* Module: github.com/knqyf263/go-plugin #### LICENSE @@ -2139,7 +2139,7 @@ SOFTWARE. ### github.com/moby/sys/capability * License: BSD-2-Clause -* Module: github.com/moby/sys/capability@v0.4.0 +* Module: github.com/moby/sys/capability #### LICENSE @@ -2176,7 +2176,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/moby/sys/mountinfo * License: Apache-2.0 -* Module: github.com/moby/sys/mountinfo@v0.7.2 +* Module: github.com/moby/sys/mountinfo #### LICENSE @@ -2390,7 +2390,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/moby/sys/reexec * License: Apache-2.0 -* Module: github.com/moby/sys/reexec@v0.1.0 +* Module: github.com/moby/sys/reexec #### LICENSE @@ -2604,7 +2604,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/opencontainers/cgroups/devices/config * License: Apache-2.0 -* Module: github.com/opencontainers/cgroups@v0.0.7 +* Module: github.com/opencontainers/cgroups #### LICENSE @@ -2817,7 +2817,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### github.com/opencontainers/runc * License: Apache-2.0 -* Module: github.com/opencontainers/runc@v1.4.3 +* Module: github.com/opencontainers/runc #### LICENSE @@ -3043,7 +3043,7 @@ See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. ### github.com/opencontainers/runtime-spec/specs-go * License: Apache-2.0 -* Module: github.com/opencontainers/runtime-spec@v1.3.0 +* Module: github.com/opencontainers/runtime-spec #### LICENSE @@ -3246,7 +3246,7 @@ See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. ### github.com/opencontainers/runtime-tools * License: Apache-2.0 -* Module: github.com/opencontainers/runtime-tools@v0.9.1-0.20251114084447-edf4cb3d2116 +* Module: github.com/opencontainers/runtime-tools #### LICENSE @@ -3449,7 +3449,7 @@ See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. ### github.com/pelletier/go-toml * License: Apache-2.0 / MIT -* Module: github.com/pelletier/go-toml@v1.9.5 +* Module: github.com/pelletier/go-toml #### LICENSE @@ -3708,7 +3708,7 @@ License: ### github.com/prometheus/procfs * License: Apache-2.0 -* Module: github.com/prometheus/procfs@v0.21.1 +* Module: github.com/prometheus/procfs #### LICENSE @@ -3934,7 +3934,7 @@ SoundCloud Ltd. (http://soundcloud.com/). ### github.com/sirupsen/logrus * License: MIT -* Module: github.com/sirupsen/logrus@v1.9.4 +* Module: github.com/sirupsen/logrus #### LICENSE @@ -3967,7 +3967,7 @@ THE SOFTWARE. ### github.com/tetratelabs/wazero * License: Apache-2.0 -* Module: github.com/tetratelabs/wazero@v1.11.0 +* Module: github.com/tetratelabs/wazero #### LICENSE @@ -4188,7 +4188,7 @@ Copyright 2020-2023 wazero authors ### github.com/urfave/cli-altsrc/v3 * License: MIT -* Module: github.com/urfave/cli-altsrc/v3@v3.1.0 +* Module: github.com/urfave/cli-altsrc/v3 #### LICENSE @@ -4221,7 +4221,7 @@ SOFTWARE. ### github.com/urfave/cli/v3 * License: MIT -* Module: github.com/urfave/cli/v3@v3.10.1 +* Module: github.com/urfave/cli/v3 #### LICENSE @@ -4254,7 +4254,7 @@ SOFTWARE. ### golang.org/x/mod/semver * License: BSD-3-Clause -* Module: golang.org/x/mod@v0.38.0 +* Module: golang.org/x/mod #### LICENSE @@ -4293,7 +4293,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### golang.org/x/sys * License: BSD-3-Clause -* Module: golang.org/x/sys@v0.47.0 +* Module: golang.org/x/sys #### LICENSE @@ -4332,7 +4332,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### google.golang.org/genproto/googleapis/rpc/status * License: Apache-2.0 -* Module: google.golang.org/genproto/googleapis/rpc@v0.0.0-20260414002931-afd174a4e478 +* Module: google.golang.org/genproto/googleapis/rpc #### LICENSE @@ -4546,7 +4546,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### google.golang.org/grpc * License: Apache-2.0 -* Module: google.golang.org/grpc@v1.82.1 +* Module: google.golang.org/grpc #### LICENSE @@ -4779,7 +4779,7 @@ limitations under the License. ### google.golang.org/protobuf * License: BSD-3-Clause -* Module: google.golang.org/protobuf@v1.36.11 +* Module: google.golang.org/protobuf #### LICENSE @@ -4818,7 +4818,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### gopkg.in/yaml.v3 * License: MIT -* Module: gopkg.in/yaml.v3@v3.0.1 +* Module: gopkg.in/yaml.v3 #### LICENSE @@ -4899,7 +4899,7 @@ limitations under the License. ### sigs.k8s.io/yaml * License: Apache-2.0 / BSD-3-Clause / MIT -* Module: sigs.k8s.io/yaml@v1.4.0 +* Module: sigs.k8s.io/yaml #### LICENSE @@ -5217,7 +5217,7 @@ Apache license: ### sigs.k8s.io/yaml/goyaml.v2 * License: Apache-2.0 -* Module: sigs.k8s.io/yaml@v1.4.0 +* Module: sigs.k8s.io/yaml #### LICENSE @@ -5449,7 +5449,7 @@ limitations under the License. ### tags.cncf.io/container-device-interface * License: Apache-2.0 -* Module: tags.cncf.io/container-device-interface@v1.1.0 +* Module: tags.cncf.io/container-device-interface #### LICENSE @@ -5662,7 +5662,7 @@ limitations under the License. ### tags.cncf.io/container-device-interface/specs-go * License: Apache-2.0 -* Module: tags.cncf.io/container-device-interface/specs-go@v1.1.0 +* Module: tags.cncf.io/container-device-interface/specs-go #### LICENSE diff --git a/api/config/v1/config.go b/api/config/v1/config.go index a1316b939..0b0a27381 100644 --- a/api/config/v1/config.go +++ b/api/config/v1/config.go @@ -190,8 +190,8 @@ var getDistIDLike = func() []string { scanner := bufio.NewScanner(releaseFile) for scanner.Scan() { line := scanner.Text() - if strings.HasPrefix(line, "ID_LIKE=") { - value := strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), "\"") + if after, ok := strings.CutPrefix(line, "ID_LIKE="); ok { + value := strings.Trim(after, "\"") return strings.Split(value, " ") } } diff --git a/api/config/v1/toml.go b/api/config/v1/toml.go index d548572d6..4b0630771 100644 --- a/api/config/v1/toml.go +++ b/api/config/v1/toml.go @@ -181,7 +181,7 @@ func (t *Toml) configNoOverrides() (*Config, error) { } // Unmarshal wraps the toml.Tree Unmarshal function. -func (t *Toml) Unmarshal(v interface{}) error { +func (t *Toml) Unmarshal(v any) error { return t.tree.Unmarshal(v) } @@ -226,17 +226,17 @@ func (t *Toml) Delete(key string) error { } // Get returns the value for the specified key. -func (t *Toml) Get(key string) interface{} { +func (t *Toml) Get(key string) any { return t.tree.Get(key) } // GetDefault returns the value for the specified key and falls back to the default value if the Get call fails -func (t *Toml) GetDefault(key string, def interface{}) interface{} { +func (t *Toml) GetDefault(key string, def any) any { return t.tree.GetDefault(key, def) } // Set sets the specified key to the specified value in the TOML config. -func (t *Toml) Set(key string, value interface{}) { +func (t *Toml) Set(key string, value any) { t.tree.Set(key, value) t.valuesSet[key] = true } @@ -250,7 +250,7 @@ func (t *Toml) WriteTo(w io.Writer) (int64, error) { // commentDefaults applies the required comments for default values to the Toml. func (t *Toml) commentDefaults() *Toml { asToml := t.tree - commentedDefaults := map[string]interface{}{ + commentedDefaults := map[string]any{ "swarm-resource": "DOCKER_RESOURCE_GPU", "accept-nvidia-visible-devices-envvar-when-unprivileged": true, "accept-nvidia-visible-devices-as-volume-mounts": false, @@ -278,7 +278,7 @@ func (t *Toml) commentDefaults() *Toml { return t } -func shouldComment(key string, defaultValue interface{}, setTo interface{}) bool { +func shouldComment(key string, defaultValue any, setTo any) bool { if key == "nvidia-container-cli.user" && defaultValue == setTo && isSuse() { return false } diff --git a/api/config/v1/toml_test.go b/api/config/v1/toml_test.go index 17010f575..dbdaecc00 100644 --- a/api/config/v1/toml_test.go +++ b/api/config/v1/toml_test.go @@ -219,7 +219,7 @@ func TestGetFormattedConfig(t *testing.T) { func TestTomlContents(t *testing.T) { testCases := []struct { description string - contents map[string]interface{} + contents map[string]any expected string }{ { @@ -262,7 +262,7 @@ func TestConfigFromToml(t *testing.T) { testCases := []struct { description string - contents map[string]interface{} + contents map[string]any expectedError error expectedConfig *Config }{ @@ -276,8 +276,8 @@ func TestConfigFromToml(t *testing.T) { }, { description: "contents overrides default", - contents: map[string]interface{}{ - "nvidia-container-runtime": map[string]interface{}{ + contents: map[string]any{ + "nvidia-container-runtime": map[string]any{ "debug": "/some/log/file.log", "mode": "csv", }, @@ -291,8 +291,8 @@ func TestConfigFromToml(t *testing.T) { }, { description: "invalid ldconfig value raises error", - contents: map[string]interface{}{ - "nvidia-container-cli": map[string]interface{}{ + contents: map[string]any{ + "nvidia-container-cli": map[string]any{ "ldconfig": "/some/ldconfig/path", }, }, @@ -300,11 +300,11 @@ func TestConfigFromToml(t *testing.T) { }, { description: "feature allows ldconfig override", - contents: map[string]interface{}{ - "nvidia-container-cli": map[string]interface{}{ + contents: map[string]any{ + "nvidia-container-cli": map[string]any{ "ldconfig": "/some/ldconfig/path", }, - "features": map[string]interface{}{ + "features": map[string]any{ "allow-ldconfig-from-container": true, }, }, diff --git a/cmd/nvidia-cdi-hook/README.md b/cmd/nvidia-cdi-hook/README.md index 807cf6c75..79354e7d9 100644 --- a/cmd/nvidia-cdi-hook/README.md +++ b/cmd/nvidia-cdi-hook/README.md @@ -29,3 +29,6 @@ The `nvidia-cdi-hook` CLI provides the following functionality: * `chmod` - Change the permissions of a file or directory inside the directory path to be mounted into a container. * `create-symlinks` - Create symlinks inside the directory path to be mounted into a container. * `update-ldcache` - Update the dynamic linker cache inside the directory path to be mounted into a container. +* `enable-cuda-compat` - Ensure that the directory containing the CUDA compat libraries is added to the ldconfig search path if required. +* `disable-device-node-modification` - Ensure that the `/proc/driver/nvidia/params` file present in the container does not allow device node modifications. +* `update-application-profile` - Update driver settings through "application profiles". Currently, this hook sets `EGLVisibleDGPUDevices` to restrict EGL/Vulkan GPU visibility inside the container. diff --git a/cmd/nvidia-cdi-hook/cudacompat/container-root_test.go b/cmd/nvidia-cdi-hook/cudacompat/container-root_test.go index 77251900e..26b6da5b8 100644 --- a/cmd/nvidia-cdi-hook/cudacompat/container-root_test.go +++ b/cmd/nvidia-cdi-hook/cudacompat/container-root_test.go @@ -92,8 +92,8 @@ func TestMkdirAll(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } @@ -180,8 +180,8 @@ func TestOpen(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } @@ -275,8 +275,8 @@ func TestCreate(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } @@ -375,8 +375,8 @@ func TestHasPath(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } @@ -490,8 +490,8 @@ func TestGlobFiles(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } diff --git a/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go b/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go index 2efe1ade2..5b1e674d0 100644 --- a/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go +++ b/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go @@ -155,7 +155,7 @@ func (m command) getContainerForwardCompatDir(containerRoot *root, o *options) ( libs, err := containerRoot.globFiles(filepath.Join(o.cudaCompatContainerRoot, "libcuda.so.*.*")) if err != nil { - m.logger.Warningf("Failed to find CUDA compat library: %w", err) + m.logger.Warningf("Failed to find CUDA compat library: %v", err) return "", nil } @@ -216,7 +216,7 @@ func (m command) useCompatLibraries(libcudaCompatFile *os.File, hostDriverVersio // libraries in the container should be used over the host driver libraries. cudaCompatHeader, err := GetCUDACompatElfHeaderFromReader(libcudaCompatFile) if err != nil { - m.logger.Warningf("failed to get ELF header from CUDA compat library: %w", err) + m.logger.Warningf("failed to get ELF header from CUDA compat library: %v", err) } if cudaCompatHeader != nil { return cudaCompatHeader.UseCompat(compatDriverSemver, hostDriverSemver, hostCUDASemver), nil diff --git a/cmd/nvidia-cdi-hook/cudacompat/cudacompat_test.go b/cmd/nvidia-cdi-hook/cudacompat/cudacompat_test.go index 4988816bf..398ac73dd 100644 --- a/cmd/nvidia-cdi-hook/cudacompat/cudacompat_test.go +++ b/cmd/nvidia-cdi-hook/cudacompat/cudacompat_test.go @@ -164,8 +164,8 @@ func TestCompatLibs(t *testing.T) { target := filepath.Join(containerRootDir, name) require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755)) - if strings.HasPrefix(contents, "symlink=") { - require.NoError(t, os.Symlink(strings.TrimPrefix(contents, "symlink="), target)) + if after, ok := strings.CutPrefix(contents, "symlink="); ok { + require.NoError(t, os.Symlink(after, target)) continue } diff --git a/cmd/nvidia-cdi-hook/update-application-profile/update-application-profile.go b/cmd/nvidia-cdi-hook/update-application-profile/update-application-profile.go index 2c03c82ae..1eff75940 100644 --- a/cmd/nvidia-cdi-hook/update-application-profile/update-application-profile.go +++ b/cmd/nvidia-cdi-hook/update-application-profile/update-application-profile.go @@ -19,9 +19,11 @@ package updateapplicationprofile import ( "context" + "errors" "fmt" "os" "regexp" + "syscall" "github.com/urfave/cli/v3" @@ -125,12 +127,19 @@ func run(_ context.Context, _ *cli.Command, cfg *options, logger logger.Interfac return nil } - if err := containerRoot.MkdirAll(applicationProfileDir, 0555); err != nil { - return fmt.Errorf("failed to create application profiles directory: %w", err) - } - - if err := containerRoot.WriteFile(applicationProfileFile, buildApplicationProfileConfig(mask), 0444); err != nil { - return fmt.Errorf("failed to write application profile: %w", err) + if err = func() error { + if err := containerRoot.MkdirAll(applicationProfileDir, 0555); err != nil { + return fmt.Errorf("failed to create application profiles directory: %w", err) + } + if err := containerRoot.WriteFile(applicationProfileFile, buildApplicationProfileConfig(mask), 0444); err != nil { + return fmt.Errorf("failed to write application profile: %w", err) + } + return nil + }(); err != nil { + if !errors.Is(err, syscall.EROFS) { + return err + } + logger.Warningf("Ignoring read-only filesystem error: %v", err) } return nil diff --git a/cmd/nvidia-container-runtime-hook/container_config.go b/cmd/nvidia-container-runtime-hook/container_config.go index 86288d8e6..9c61b1dc3 100644 --- a/cmd/nvidia-container-runtime-hook/container_config.go +++ b/cmd/nvidia-container-runtime-hook/container_config.go @@ -7,8 +7,8 @@ import ( "os" "path" + "github.com/Masterminds/semver/v3" "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/mod/semver" "github.com/NVIDIA/nvidia-container-toolkit/internal/config/image" ) @@ -107,8 +107,12 @@ func (s *Spec) GetCapabilities() []string { var caps []string // If v1.0.0-rc1 <= OCI version < v1.0.0-rc5 parse s.Process.Capabilities as: // github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/specs-go/config.go#L30-L54 - rc1cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc1") - rc5cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc5") + sv, err := semver.NewVersion(*s.Version) + if err != nil { + sv = semver.MustParse("0.0.0") + } + rc1cmp := sv.Compare(semver.MustParse("v1.0.0-rc1")) + rc5cmp := sv.Compare(semver.MustParse("v1.0.0-rc5")) if (rc1cmp == 1 || rc1cmp == 0) && (rc5cmp == -1) { err := json.Unmarshal(*s.Process.Capabilities, &caps) if err != nil { @@ -120,7 +124,7 @@ func (s *Spec) GetCapabilities() []string { // Otherwise, parse s.Process.Capabilities as: // github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L54 capabilities := specs.LinuxCapabilities{} - err := json.Unmarshal(*s.Process.Capabilities, &capabilities) + err = json.Unmarshal(*s.Process.Capabilities, &capabilities) if err != nil { log.Panicln("could not decode Process.Capabilities in OCI spec:", err) } diff --git a/cmd/nvidia-container-runtime-hook/hook_config.go b/cmd/nvidia-container-runtime-hook/hook_config.go index e2234012f..3c1c781f2 100644 --- a/cmd/nvidia-container-runtime-hook/hook_config.go +++ b/cmd/nvidia-container-runtime-hook/hook_config.go @@ -69,7 +69,7 @@ func getHookConfig() (*hookConfig, error) { // getConfigOption returns the toml config option associated with the // specified struct field. func (c *hookConfig) getConfigOption(fieldName string) string { - t := reflect.TypeOf(&c) + t := reflect.TypeFor[**hookConfig]() f, ok := t.FieldByName(fieldName) if !ok { return fieldName diff --git a/cmd/nvidia-container-runtime-hook/main.go b/cmd/nvidia-container-runtime-hook/main.go index e9c7d2c5a..24e963f8a 100644 --- a/cmd/nvidia-container-runtime-hook/main.go +++ b/cmd/nvidia-container-runtime-hook/main.go @@ -135,7 +135,7 @@ func doPrestart() { args = append(args, fmt.Sprintf("--imex-channel=%s", imexString)) } - for _, cap := range strings.Split(nvidia.DriverCapabilities, ",") { + for cap := range strings.SplitSeq(nvidia.DriverCapabilities, ",") { if len(cap) == 0 { break } @@ -198,6 +198,6 @@ type logInterceptor struct { logger.NullLogger } -func (l *logInterceptor) Infof(format string, args ...interface{}) { +func (l *logInterceptor) Infof(format string, args ...any) { log.Printf(format, args...) } diff --git a/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd.go b/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd.go index 871dccfe1..bd07568b8 100644 --- a/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd.go +++ b/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd.go @@ -150,12 +150,12 @@ func (o *Options) containerAnnotationsFromCDIPrefixes() []string { return annotations } -func (o *Options) runtimeConfigOverride() (map[string]interface{}, error) { +func (o *Options) runtimeConfigOverride() (map[string]any, error) { if o.runtimeConfigOverrideJSON == "" { return nil, nil } - runtimeOptions := make(map[string]interface{}) + runtimeOptions := make(map[string]any) if err := json.Unmarshal([]byte(o.runtimeConfigOverrideJSON), &runtimeOptions); err != nil { return nil, fmt.Errorf("failed to read %v as JSON: %w", o.runtimeConfigOverrideJSON, err) } diff --git a/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd_test.go b/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd_test.go index d5cf515be..a0ac9437b 100644 --- a/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd_test.go +++ b/cmd/nvidia-ctk-installer/container/runtime/containerd/containerd_test.go @@ -26,7 +26,7 @@ func TestRuntimeOptions(t *testing.T) { testCases := []struct { description string options Options - expected map[string]interface{} + expected map[string]any expectedError error }{ { @@ -37,7 +37,7 @@ func TestRuntimeOptions(t *testing.T) { options: Options{ runtimeConfigOverrideJSON: "{}", }, - expected: map[string]interface{}{}, + expected: map[string]any{}, expectedError: nil, }, { @@ -45,7 +45,7 @@ func TestRuntimeOptions(t *testing.T) { options: Options{ runtimeConfigOverrideJSON: "{\"SystemdCgroup\": true}", }, - expected: map[string]interface{}{ + expected: map[string]any{ "SystemdCgroup": true, }, expectedError: nil, @@ -55,7 +55,7 @@ func TestRuntimeOptions(t *testing.T) { options: Options{ runtimeConfigOverrideJSON: "{\"SystemdCgroup\": false}", }, - expected: map[string]interface{}{ + expected: map[string]any{ "SystemdCgroup": false, }, expectedError: nil, diff --git a/cmd/nvidia-ctk-installer/container/runtime/docker/docker_test.go b/cmd/nvidia-ctk-installer/container/runtime/docker/docker_test.go index 129cc143b..f0f73b861 100644 --- a/cmd/nvidia-ctk-installer/container/runtime/docker/docker_test.go +++ b/cmd/nvidia-ctk-installer/container/runtime/docker/docker_test.go @@ -32,7 +32,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { testCases := []struct { setAsDefault bool runtimeName string - expectedDefaultRuntimeName interface{} + expectedDefaultRuntimeName any }{ {}, { @@ -58,7 +58,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { SetAsDefault: tc.setAsDefault, } - config := docker.Config(map[string]interface{}{}) + config := docker.Config(map[string]any{}) err := o.UpdateConfig(&config) require.NoError(t, err, "%d: %v", i, tc) @@ -75,22 +75,22 @@ func TestUpdateConfig(t *testing.T) { config docker.Config setAsDefault bool runtimeName string - expectedConfig map[string]interface{} + expectedConfig map[string]any }{ { - config: map[string]interface{}{}, + config: map[string]any{}, setAsDefault: false, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -98,20 +98,20 @@ func TestUpdateConfig(t *testing.T) { }, }, { - config: map[string]interface{}{}, + config: map[string]any{}, setAsDefault: false, runtimeName: "NAME", - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "NAME": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "NAME": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -119,26 +119,26 @@ func TestUpdateConfig(t *testing.T) { }, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "nvidia-container-runtime", "args": []string{}, }, }, }, setAsDefault: false, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -146,29 +146,29 @@ func TestUpdateConfig(t *testing.T) { }, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "not-nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "not-nvidia": map[string]any{ "path": "some-other-path", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "not-nvidia": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "not-nvidia": map[string]any{ "path": "some-other-path", "args": []string{}, }, - "nvidia": map[string]interface{}{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -176,23 +176,23 @@ func TestUpdateConfig(t *testing.T) { }, }, { - config: map[string]interface{}{ + config: map[string]any{ "default-runtime": "runc", }, setAsDefault: true, runtimeName: "nvidia", - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "default-runtime": "nvidia", - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -200,7 +200,7 @@ func TestUpdateConfig(t *testing.T) { }, }, { - config: map[string]interface{}{ + config: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ @@ -208,23 +208,23 @@ func TestUpdateConfig(t *testing.T) { }, "storage-driver": "overlay2", }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ "max-size": "100m", }, "storage-driver": "overlay2", - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, @@ -234,7 +234,6 @@ func TestUpdateConfig(t *testing.T) { } for i, tc := range testCases { - tc := tc o := &container.Options{ RuntimeName: tc.runtimeName, @@ -258,97 +257,97 @@ func TestUpdateConfig(t *testing.T) { func TestRevertConfig(t *testing.T) { testCases := []struct { config docker.Config - expectedConfig map[string]interface{} + expectedConfig map[string]any }{ { - config: map[string]interface{}{}, - expectedConfig: map[string]interface{}{}, + config: map[string]any{}, + expectedConfig: map[string]any{}, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{}, + expectedConfig: map[string]any{}, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{}, + expectedConfig: map[string]any{}, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, - "nvidia-cdi": map[string]interface{}{ + "nvidia-cdi": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.cdi", "args": []string{}, }, - "nvidia-legacy": map[string]interface{}{ + "nvidia-legacy": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime.legacy", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{}, + expectedConfig: map[string]any{}, }, { - config: map[string]interface{}{ + config: map[string]any{ "default-runtime": "nvidia", - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "default-runtime": "runc", }, }, { - config: map[string]interface{}{ + config: map[string]any{ "default-runtime": "not-nvidia", - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "default-runtime": "not-nvidia", }, }, { - config: map[string]interface{}{ + config: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ "max-size": "100m", }, "storage-driver": "overlay2", - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "/test/runtime/dir/nvidia-container-runtime", "args": []string{}, }, }, }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ @@ -360,7 +359,6 @@ func TestRevertConfig(t *testing.T) { } for i, tc := range testCases { - tc := tc o := &container.Options{} err := o.RevertConfig(&tc.config) diff --git a/cmd/nvidia-ctk-installer/container/runtime/nri/logger.go b/cmd/nvidia-ctk-installer/container/runtime/nri/logger.go index 6c1f13ee4..6225c1d10 100644 --- a/cmd/nvidia-ctk-installer/container/runtime/nri/logger.go +++ b/cmd/nvidia-ctk-installer/container/runtime/nri/logger.go @@ -10,18 +10,18 @@ type toNriLogger struct { logger.Interface } -func (l toNriLogger) Debugf(_ context.Context, fmt string, args ...interface{}) { +func (l toNriLogger) Debugf(_ context.Context, fmt string, args ...any) { l.Interface.Debugf(fmt, args...) } -func (l toNriLogger) Errorf(_ context.Context, fmt string, args ...interface{}) { +func (l toNriLogger) Errorf(_ context.Context, fmt string, args ...any) { l.Interface.Errorf(fmt, args...) } -func (l toNriLogger) Infof(_ context.Context, fmt string, args ...interface{}) { +func (l toNriLogger) Infof(_ context.Context, fmt string, args ...any) { l.Interface.Infof(fmt, args...) } -func (l toNriLogger) Warnf(_ context.Context, fmt string, args ...interface{}) { +func (l toNriLogger) Warnf(_ context.Context, fmt string, args ...any) { l.Warningf(fmt, args...) } diff --git a/cmd/nvidia-ctk-installer/toolkit/installer/executables.go b/cmd/nvidia-ctk-installer/toolkit/installer/executables.go index 30dfa1e2b..533da9508 100644 --- a/cmd/nvidia-ctk-installer/toolkit/installer/executables.go +++ b/cmd/nvidia-ctk-installer/toolkit/installer/executables.go @@ -21,6 +21,7 @@ import ( "bytes" "fmt" "io" + "maps" "path/filepath" "strings" "text/template" @@ -106,9 +107,7 @@ func (t *ToolkitInstaller) collectExecutables(destDir string) ([]Installer, erro "PATH": strings.Join([]string{destDir, "$PATH"}, ":"), }, } - for k, v := range executable.env { - w.Envvars[k] = v - } + maps.Copy(w.Envvars, executable.env) if len(t.defaultRuntimeExecutablePath) > 0 { w.DefaultRuntimeExecutablePath = t.defaultRuntimeExecutablePath diff --git a/cmd/nvidia-ctk-installer/toolkit/toolkit.go b/cmd/nvidia-ctk-installer/toolkit/toolkit.go index aa4561f43..ebfa0a557 100644 --- a/cmd/nvidia-ctk-installer/toolkit/toolkit.go +++ b/cmd/nvidia-ctk-installer/toolkit/toolkit.go @@ -408,7 +408,7 @@ func (t *Installer) installToolkitConfig(c *cli.Command, opts *Options) error { // Use the driver run root as the root: driverLdconfigPath := config.NormalizeLDConfigPath("@" + filepath.Join(opts.DriverRoot, strings.TrimPrefix(ldconfigPath, "@/"))) - configValues := map[string]interface{}{ + configValues := map[string]any{ // Set the options in the root toml table "accept-nvidia-visible-devices-envvar-when-unprivileged": opts.acceptNVIDIAVisibleDevicesWhenUnprivileged, "accept-nvidia-visible-devices-as-volume-mounts": opts.acceptNVIDIAVisibleDevicesAsVolumeMounts, @@ -437,7 +437,7 @@ func (t *Installer) installToolkitConfig(c *cli.Command, opts *Options) error { } // Set the optional config options - optionalConfigValues := map[string]interface{}{ + optionalConfigValues := map[string]any{ "nvidia-container-runtime.debug": opts.ContainerRuntimeDebug, "nvidia-container-runtime.log-level": opts.ContainerRuntimeLogLevel, "nvidia-container-runtime.mode": opts.ContainerRuntimeMode, diff --git a/cmd/nvidia-ctk/cdi/generate/config.go b/cmd/nvidia-ctk/cdi/generate/config.go index de2b2a38f..f0a16a850 100644 --- a/cmd/nvidia-ctk/cdi/generate/config.go +++ b/cmd/nvidia-ctk/cdi/generate/config.go @@ -51,7 +51,7 @@ func (c *configAsValueSource) ValueFrom(key string) cli.ValueSource { } } -func (c *configAsValueSource) Get(key string) interface{} { +func (c *configAsValueSource) Get(key string) any { c.Lock() defer c.Unlock() diff --git a/cmd/nvidia-ctk/cdi/generate/generate.go b/cmd/nvidia-ctk/cdi/generate/generate.go index 2fff6eac5..7c29e9dd9 100644 --- a/cmd/nvidia-ctk/cdi/generate/generate.go +++ b/cmd/nvidia-ctk/cdi/generate/generate.go @@ -302,10 +302,8 @@ func (m command) validateFlags(c *cli.Command, opts *options) error { return fmt.Errorf("invalid CDI class name: %v", err) } - for _, hook := range opts.enabledHooks { - if hook == "all" { - return fmt.Errorf("enabling all hooks is not supported") - } + if slices.Contains(opts.enabledHooks, "all") { + return fmt.Errorf("enabling all hooks is not supported") } if slices.Contains(opts.deviceIDs, "none") && !opts.noAllDevice { diff --git a/cmd/nvidia-ctk/config/config.go b/cmd/nvidia-ctk/config/config.go index 81a8d206b..678f56c45 100644 --- a/cmd/nvidia-ctk/config/config.go +++ b/cmd/nvidia-ctk/config/config.go @@ -162,7 +162,7 @@ var errInvalidFormat = errors.New("invalid format") // setFlagToKeyValue converts a --set flag to a key-value pair. // The set flag is of the form key[=value], with the value being optional if key refers to a // boolean config option. -func setFlagToKeyValue(setFlag string, setListSeparator string) (string, interface{}, error) { +func setFlagToKeyValue(setFlag string, setListSeparator string) (string, any, error) { setParts := strings.SplitN(setFlag, "=", 2) key := setParts[0] @@ -222,7 +222,7 @@ func setFlagToKeyValue(setFlag string, setListSeparator string) (string, interfa } func getField(key string) (reflect.Type, error) { - s, err := getStruct(reflect.TypeOf(config.Config{}), strings.Split(key, ".")...) + s, err := getStruct(reflect.TypeFor[config.Config](), strings.Split(key, ".")...) if err != nil { return nil, err } diff --git a/cmd/nvidia-ctk/config/config_test.go b/cmd/nvidia-ctk/config/config_test.go index 774de6354..c92623db1 100644 --- a/cmd/nvidia-ctk/config/config_test.go +++ b/cmd/nvidia-ctk/config/config_test.go @@ -28,7 +28,7 @@ func TestSetFlagToKeyValue(t *testing.T) { setFlag string setListSeparator string expectedKey string - expectedValue interface{} + expectedValue any expectedError error }{ { diff --git a/cmd/nvidia-ctk/runtime/configure/configure_test.go b/cmd/nvidia-ctk/runtime/configure/configure_test.go index 5e4cde1af..644f3bc72 100644 --- a/cmd/nvidia-ctk/runtime/configure/configure_test.go +++ b/cmd/nvidia-ctk/runtime/configure/configure_test.go @@ -313,11 +313,11 @@ runtime_type = "oci" content, err := os.ReadFile(configPath) require.NoError(t, err) - var dockerConfig map[string]interface{} + var dockerConfig map[string]any err = json.Unmarshal(content, &dockerConfig) require.NoError(t, err) - runtimes := dockerConfig["runtimes"].(map[string]interface{}) + runtimes := dockerConfig["runtimes"].(map[string]any) require.Contains(t, runtimes, "nvidia") // Should NOT have legacy runtimes - only the single specified runtime is added require.NotContains(t, runtimes, "nvidia-cdi") @@ -337,7 +337,7 @@ runtime_type = "oci" configPath := filepath.Join(testRoot, "etc/docker/daemon.json") require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0755)) - existingConfig := map[string]interface{}{ + existingConfig := map[string]any{ "log-driver": "json-file", "log-opts": map[string]string{ "max-size": "100m", @@ -355,7 +355,7 @@ runtime_type = "oci" content, err := os.ReadFile(configPath) require.NoError(t, err) - var dockerConfig map[string]interface{} + var dockerConfig map[string]any err = json.Unmarshal(content, &dockerConfig) require.NoError(t, err) @@ -365,7 +365,7 @@ runtime_type = "oci" // Verify nvidia runtime added and set as default require.Equal(t, "nvidia", dockerConfig["default-runtime"]) - runtimes := dockerConfig["runtimes"].(map[string]interface{}) + runtimes := dockerConfig["runtimes"].(map[string]any) require.Contains(t, runtimes, "nvidia") return nil @@ -383,11 +383,11 @@ runtime_type = "oci" content, err := os.ReadFile(configPath) require.NoError(t, err) - var dockerConfig map[string]interface{} + var dockerConfig map[string]any err = json.Unmarshal(content, &dockerConfig) require.NoError(t, err) - features := dockerConfig["features"].(map[string]interface{}) + features := dockerConfig["features"].(map[string]any) require.Equal(t, true, features["cdi"]) return nil @@ -408,17 +408,17 @@ runtime_type = "oci" content, err := os.ReadFile(hookPath) require.NoError(t, err) - var hook map[string]interface{} + var hook map[string]any err = json.Unmarshal(content, &hook) require.NoError(t, err) require.Equal(t, "1.0.0", hook["version"]) require.Contains(t, hook["stages"], "prestart") - hookSpec := hook["hook"].(map[string]interface{}) + hookSpec := hook["hook"].(map[string]any) require.Equal(t, defaultNVIDIARuntimeHookExpecutablePath, hookSpec["path"]) - when := hook["when"].(map[string]interface{}) + when := hook["when"].(map[string]any) require.Equal(t, true, when["always"]) return nil @@ -467,12 +467,12 @@ runtime_type = "oci" content, err := os.ReadFile(configPath) require.NoError(t, err) - var dockerConfig map[string]interface{} + var dockerConfig map[string]any err = json.Unmarshal(content, &dockerConfig) require.NoError(t, err) // Should have nvidia runtime added - runtimes := dockerConfig["runtimes"].(map[string]interface{}) + runtimes := dockerConfig["runtimes"].(map[string]any) require.Contains(t, runtimes, "nvidia") return nil diff --git a/cmd/nvidia-ctk/system/create-dev-char-symlinks/all.go b/cmd/nvidia-ctk/system/create-dev-char-symlinks/all.go index cafb8f9c7..775fc4f60 100644 --- a/cmd/nvidia-ctk/system/create-dev-char-symlinks/all.go +++ b/cmd/nvidia-ctk/system/create-dev-char-symlinks/all.go @@ -91,7 +91,7 @@ func (m allPossible) DeviceNodes() ([]deviceNode, error) { return nil, fmt.Errorf("failed to get control device nodes: %v", err) } - for gpu := 0; gpu < count; gpu++ { + for gpu := range count { deviceNodes = append(deviceNodes, m.getGPUDeviceNodes(gpu)...) deviceNodes = append(deviceNodes, m.getNVCapDeviceNodes(gpu)...) } diff --git a/deployments/container/Dockerfile b/deployments/container/Dockerfile index b6c3764a1..841e87f34 100644 --- a/deployments/container/Dockerfile +++ b/deployments/container/Dockerfile @@ -66,7 +66,7 @@ RUN apt-get update \ # The packaging stage collects the deb and rpm packages built for # supported architectures. -FROM nvcr.io/nvidia/distroless/go:v4.0.9 AS packaging +FROM nvcr.io/nvidia/distroless/go:v4.1.1 AS packaging USER 0:0 @@ -106,7 +106,8 @@ LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.revision="${GIT_COMMIT}" LABEL org.opencontainers.image.title="NVIDIA Container Toolkit Packages" LABEL org.opencontainers.image.vendor="NVIDIA" -LABEL org.opencontainers.base.name="nvcr.io/nvidia/distroless/go" +LABEL org.opencontainers.image.url="https://catalog.ngc.nvidia.com/orgs/nvidia/k8s/containers/container-toolkit" +LABEL org.opencontainers.image.base.name="nvcr.io/nvidia/distroless/go" COPY LICENSE /licenses/ @@ -167,7 +168,7 @@ COPY --from=build /artifacts/bin /artifacts/build # The application stage contains the application used as a GPU Operator # operand. -FROM nvcr.io/nvidia/distroless/go:v4.0.9 AS application +FROM nvcr.io/nvidia/distroless/go:v4.1.1 AS application USER 0:0 @@ -204,7 +205,8 @@ LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.revision="${GIT_COMMIT}" LABEL org.opencontainers.image.title="NVIDIA Container Toolkit Installer" LABEL org.opencontainers.image.vendor="NVIDIA" -LABEL org.opencontainers.base.name="nvcr.io/nvidia/distroless/go" +LABEL org.opencontainers.image.url="https://catalog.ngc.nvidia.com/orgs/nvidia/k8s/containers/container-toolkit" +LABEL org.opencontainers.image.base.name="nvcr.io/nvidia/distroless/go" COPY LICENSE /licenses/ diff --git a/deployments/devel/Dockerfile b/deployments/devel/Dockerfile index 6630fe6ac..0c1088df3 100644 --- a/deployments/devel/Dockerfile +++ b/deployments/devel/Dockerfile @@ -14,7 +14,7 @@ # This Dockerfile is also used to define the golang version used in this project # This allows dependabot to manage this version in addition to other images. -FROM golang:1.26.5 +FROM golang:1.27.0 WORKDIR /work COPY * . diff --git a/deployments/devel/go.mod b/deployments/devel/go.mod index fd4493588..62fcb8275 100644 --- a/deployments/devel/go.mod +++ b/deployments/devel/go.mod @@ -1,6 +1,6 @@ module github.com/NVIDIA/k8s-device-plugin/deployments/devel -go 1.25 +go 1.26 require ( github.com/google/go-licenses/v2 v2.0.1 diff --git a/deployments/systemd/10-container-engines.conf b/deployments/systemd/10-container-engines.conf new file mode 100644 index 000000000..8504802bb --- /dev/null +++ b/deployments/systemd/10-container-engines.conf @@ -0,0 +1,33 @@ +# Copyright (c) NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Order the container engines after this service so that the first container +# started at boot does not race device node creation and CDI specification +# generation. +# +# This is shipped as a drop-in rather than as part of the unit so that it can be +# removed without overriding the whole unit: an empty file of the same name in +# /etc/systemd/system/nvidia-cdi-refresh.service.d/ takes precedence over this +# one and cancels it. Assigning an empty Before= in a drop-in does not reset the +# list, so an in-unit ordering could not be undone that way. +[Unit] +Before=docker.service containerd.service crio.service + +[Service] +# The unit is Type=oneshot, for which systemd defaults to +# TimeoutStartSec=infinity. Now that the container engines are ordered after it, +# bound the start so that a hung nvidia-smi cannot delay them indefinitely; on +# expiry the engines start and the CDI specification is refreshed on the next +# trigger. The value matches systemd's DefaultTimeoutStartSec. +TimeoutStartSec=90s diff --git a/deployments/systemd/nvidia-cdi-refresh.env b/deployments/systemd/nvidia-cdi-refresh.env index 2d510ac6e..45315914b 100644 --- a/deployments/systemd/nvidia-cdi-refresh.env +++ b/deployments/systemd/nvidia-cdi-refresh.env @@ -18,3 +18,12 @@ # For example, to change the for the generated CDI specification update and # uncomment the following line: # NVIDIA_CTK_CDI_OUTPUT_FILE_PATH=/var/run/cdi/nvidia.yaml + +# The service also runs +# nvidia-ctk system create-device-nodes --control-devices --load-kernel-modules +# before generating the CDI specification. Its driver and device roots can be +# overridden by uncommenting the following lines (nvidia-ctk cdi generate uses +# the separate NVIDIA_CTK_DRIVER_ROOT and NVIDIA_CTK_DEV_ROOT variables): +# +# NVIDIA_DRIVER_ROOT=/ +# NVIDIA_DEV_ROOT=/ diff --git a/deployments/systemd/nvidia-cdi-refresh.service b/deployments/systemd/nvidia-cdi-refresh.service index 7c483c81a..974c7b8f8 100644 --- a/deployments/systemd/nvidia-cdi-refresh.service +++ b/deployments/systemd/nvidia-cdi-refresh.service @@ -29,6 +29,13 @@ Environment=NVIDIA_CTK_CDI_OUTPUT_FILE_PATH=/var/run/cdi/nvidia.yaml EnvironmentFile=-/etc/nvidia-container-toolkit/nvidia-cdi-refresh.env ExecCondition=/bin/sh -c '/usr/bin/grep -qE "/(nvidia|nvidia-current)[.]ko" /lib/modules/%v/modules.dep || [ -e /dev/dxg ]' ExecStart=/bin/sh -c '/usr/bin/nvidia-smi -L || /usr/sbin/nvidia-smi -L || /usr/lib/wsl/lib/nvidia-smi -L' +# Create any missing NVIDIA control device nodes (loading the kernel modules +# they require) before generating the CDI specification; nothing else is +# guaranteed to create them. Best-effort, and skipped on WSL where /dev/dxg is +# used instead, as well as on Tegra platforms using the nvgpu kernel module. +# If module loading fails (for example while a driver upgrade has replaced the +# on-disk modules) still create the nodes for the modules that are loaded. +ExecStart=-/bin/sh -c '[ -e /dev/dxg ] || /usr/bin/grep -q "^nvgpu " /proc/modules || /usr/bin/nvidia-ctk system create-device-nodes --control-devices --load-kernel-modules || /usr/bin/nvidia-ctk system create-device-nodes --control-devices' ExecStart=/usr/bin/nvidia-ctk cdi generate CapabilityBoundingSet=CAP_SYS_MODULE CAP_SYS_ADMIN CAP_MKNOD # We set the service to restart on failure to ensure that a CDI spec is diff --git a/deployments/udev/99-nvidia-cdi-refresh.rules b/deployments/udev/99-nvidia-cdi-refresh.rules new file mode 100644 index 000000000..002b3d85d --- /dev/null +++ b/deployments/udev/99-nvidia-cdi-refresh.rules @@ -0,0 +1,22 @@ +# Copyright (c) NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The NVIDIA control device nodes (/dev/nvidiactl, /dev/nvidia-modeset, and +# /dev/nvidia-uvm*) are created on demand by userspace and are not provided by +# devtmpfs, so loading the NVIDIA kernel module does not guarantee that they +# exist. Trigger nvidia-cdi-refresh.service on module load to create the +# missing nodes and refresh the CDI specification. +# Debian packages the kernel module as nvidia-current, which appears in sysfs +# as nvidia_current. +ACTION=="add", SUBSYSTEM=="module", KERNEL=="nvidia|nvidia_current", TAG+="systemd", ENV{SYSTEMD_WANTS}+="nvidia-cdi-refresh.service" diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 52138ac63..2f0905324 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -56,6 +56,7 @@ RUN make PREFIX=${DIST_DIR} cmds WORKDIR $DIST_DIR COPY packaging/debian ./debian COPY deployments/systemd/ . +COPY deployments/udev/ . RUN dch --create --package="${PKG_NAME}" \ --newversion "${REVISION}" \ diff --git a/docker/Dockerfile.opensuse-leap b/docker/Dockerfile.opensuse-leap index 945155dc1..d14a77e1b 100644 --- a/docker/Dockerfile.opensuse-leap +++ b/docker/Dockerfile.opensuse-leap @@ -48,6 +48,7 @@ RUN make PREFIX=${DIST_DIR} cmds WORKDIR $DIST_DIR/.. COPY packaging/rpm . COPY deployments/systemd/ ${DIST_DIR}/ +COPY deployments/udev/ ${DIST_DIR}/ CMD arch=$(uname -m) && \ rpmbuild --clean --target=$arch -bb \ diff --git a/docker/Dockerfile.rpm-yum b/docker/Dockerfile.rpm-yum index 6a54355c6..cea9bd8d0 100644 --- a/docker/Dockerfile.rpm-yum +++ b/docker/Dockerfile.rpm-yum @@ -76,6 +76,7 @@ RUN make PREFIX=${DIST_DIR} cmds WORKDIR $DIST_DIR/.. COPY packaging/rpm . COPY deployments/systemd/ ${DIST_DIR}/ +COPY deployments/udev/ ${DIST_DIR}/ CMD arch=$(uname -m) && \ rpmbuild --clean --target=$arch -bb \ diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu index 63e2e5cc7..c71e23a12 100644 --- a/docker/Dockerfile.ubuntu +++ b/docker/Dockerfile.ubuntu @@ -54,6 +54,7 @@ RUN make PREFIX=${DIST_DIR} cmds WORKDIR $DIST_DIR COPY packaging/debian ./debian COPY deployments/systemd/ . +COPY deployments/udev/ . RUN dch --create --package="${PKG_NAME}" \ --newversion "${REVISION}" \ diff --git a/go.mod b/go.mod index 11dbed2f6..56d8c976f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/NVIDIA/nvidia-container-toolkit -go 1.25.0 +go 1.26.0 require ( github.com/Masterminds/semver/v3 v3.5.0 @@ -21,7 +21,6 @@ require ( github.com/stretchr/testify v1.11.1 github.com/urfave/cli-altsrc/v3 v3.1.0 github.com/urfave/cli/v3 v3.10.1 - golang.org/x/mod v0.38.0 golang.org/x/sys v0.47.0 tags.cncf.io/container-device-interface v1.1.0 tags.cncf.io/container-device-interface/specs-go v1.1.0 @@ -42,6 +41,7 @@ require ( github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/tetratelabs/wazero v1.11.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + golang.org/x/mod v0.38.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/hack/generate-third-party-notices.sh b/hack/generate-third-party-notices.sh index a2b55d5d4..9b5ddb809 100755 --- a/hack/generate-third-party-notices.sh +++ b/hack/generate-third-party-notices.sh @@ -181,9 +181,11 @@ collapse_index() { ' } -# Rows carry module@version, not a URL: in vendor mode go-licenses points into +# Rows carry module names, not a URL: in vendor mode go-licenses points into # this repo at HEAD, which stops describing released content once main moves. -# Longest-prefix match, because a license may sit below the module root. +# Versions are intentionally omitted because the notices identify dependencies +# and their licenses, not an exact build. Longest-prefix match, because a +# license may sit below the module root. annotate_modules() { awk -v modfile="${MODULES_TXT}" ' BEGIN { @@ -202,10 +204,10 @@ annotate_modules() { exit 1 } mods[++m] = f[2] - disp[f[2]] = f[r] "@" f[r + 1] + disp[f[2]] = f[r] } else { mods[++m] = f[2] - disp[f[2]] = f[2] "@" f[3] + disp[f[2]] = f[2] } } close(modfile) @@ -261,8 +263,8 @@ license_files_for() { emit_index_table() { local index="$1" pkg _url license module - printf '| Package | License | Module |\n' - printf '|---------|---------|--------|\n' + printf '| Package | License | Dependency |\n' + printf '|---------|---------|------------|\n' while IFS=, read -r pkg _url license module; do [[ -z "${pkg}" ]] && continue diff --git a/internal/config/image/builder.go b/internal/config/image/builder.go index c26107a81..4efa97945 100644 --- a/internal/config/image/builder.go +++ b/internal/config/image/builder.go @@ -158,8 +158,8 @@ func WithPreferredVisibleDevicesEnvVars(preferredVisibleDeviceEnvVars ...string) return func(b *builder) error { var normalized []string for _, e := range preferredVisibleDeviceEnvVars { - candidates := strings.Split(e, ",") - for _, c := range candidates { + candidates := strings.SplitSeq(e, ",") + for c := range candidates { trimmed := strings.TrimSpace(c) if len(trimmed) > 0 { normalized = append(normalized, trimmed) diff --git a/internal/config/image/capabilities.go b/internal/config/image/capabilities.go index 824a6db90..64ded92bc 100644 --- a/internal/config/image/capabilities.go +++ b/internal/config/image/capabilities.go @@ -17,6 +17,7 @@ package image import ( + "slices" "sort" "strings" ) @@ -51,7 +52,7 @@ var ( func NewDriverCapabilities(capabilities ...string) DriverCapabilities { dc := make(DriverCapabilities) for _, capability := range capabilities { - for _, c := range strings.Split(capability, ",") { + for c := range strings.SplitSeq(capability, ",") { trimmed := strings.TrimSpace(c) if trimmed == "" { continue @@ -78,12 +79,7 @@ func (c DriverCapabilities) Any(capabilities ...DriverCapability) bool { if c.IsAll() { return true } - for _, cap := range capabilities { - if c.Has(cap) { - return true - } - } - return false + return slices.ContainsFunc(capabilities, c.Has) } // List returns the list of driver capabilities. diff --git a/internal/config/image/cuda_image.go b/internal/config/image/cuda_image.go index 09f73e822..a06a7bb31 100644 --- a/internal/config/image/cuda_image.go +++ b/internal/config/image/cuda_image.go @@ -23,8 +23,8 @@ import ( "strconv" "strings" + "github.com/Masterminds/semver/v3" "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/mod/semver" "tags.cncf.io/container-device-interface/pkg/parser" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger" @@ -154,7 +154,7 @@ func (i CUDA) devicesFromEnvvars(envVars ...string) []string { for _, envVar := range envVars { if devs, ok := i.env[envVar]; ok { isSet = true - for _, d := range strings.Split(devs, ",") { + for d := range strings.SplitSeq(devs, ",") { trimmed := strings.TrimSpace(d) if len(trimmed) == 0 { continue @@ -183,7 +183,7 @@ func (i CUDA) GetDriverCapabilities() DriverCapabilities { env := i.env[EnvVarNvidiaDriverCapabilities] capabilities := make(DriverCapabilities) - for _, c := range strings.Split(env, ",") { + for c := range strings.SplitSeq(env, ",") { capabilities[DriverCapability(c)] = true } @@ -201,16 +201,13 @@ func (i CUDA) legacyVersion() (string, error) { } func parseMajorMinorVersion(version string) (string, error) { - vVersion := "v" + strings.TrimPrefix(version, "v") - - if !semver.IsValid(vVersion) { - return "", fmt.Errorf("invalid version string") + sv, err := semver.NewVersion(version) + if err != nil { + return "", fmt.Errorf("invalid version string: %w", err) } - - majorMinor := strings.TrimPrefix(semver.MajorMinor(vVersion), "v") + majorMinor := fmt.Sprintf("%d.%d", sv.Major(), sv.Minor()) parts := strings.Split(majorMinor, ".") - var err error _, err = strconv.ParseUint(parts[0], 10, 32) if err != nil { return "", fmt.Errorf("invalid major version") @@ -219,6 +216,7 @@ func parseMajorMinorVersion(version string) (string, error) { if err != nil { return "", fmt.Errorf("invalid minor version") } + return majorMinor, nil } diff --git a/internal/config/image/cuda_image_test.go b/internal/config/image/cuda_image_test.go index 5133cc308..858034a41 100644 --- a/internal/config/image/cuda_image_test.go +++ b/internal/config/image/cuda_image_test.go @@ -17,6 +17,7 @@ package image import ( + "maps" "path/filepath" "testing" @@ -674,9 +675,7 @@ func TestVisibleDevices(t *testing.T) { if tc.envvarDevices != "" { env[EnvVarNvidiaVisibleDevices] = tc.envvarDevices } - for k, v := range tc.env { - env[k] = v - } + maps.Copy(env, tc.env) image, err := New( WithEnvMap(env), diff --git a/internal/config/image/devices.go b/internal/config/image/devices.go index f5a6ad950..96b11d24a 100644 --- a/internal/config/image/devices.go +++ b/internal/config/image/devices.go @@ -92,7 +92,7 @@ func newDevices(idOrCommaSeparated ...string) devices { i := 0 for _, commaSeparated := range idOrCommaSeparated { - for _, id := range strings.Split(commaSeparated, ",") { + for id := range strings.SplitSeq(commaSeparated, ",") { lookup[id] = i i++ } diff --git a/internal/config/image/privileged.go b/internal/config/image/privileged.go index 04f32cf5a..47e45e78a 100644 --- a/internal/config/image/privileged.go +++ b/internal/config/image/privileged.go @@ -17,6 +17,8 @@ package image import ( + "slices" + "github.com/opencontainers/runtime-spec/specs-go" ) @@ -37,13 +39,7 @@ func IsPrivileged(s CapabilitiesGetter) bool { if s == nil { return false } - for _, c := range s.GetCapabilities() { - if c == capSysAdmin { - return true - } - } - - return false + return slices.Contains(s.GetCapabilities(), capSysAdmin) } func (s OCISpec) GetCapabilities() []string { diff --git a/internal/discover/graphics.go b/internal/discover/graphics.go index 27d9fa2c0..3b385f100 100644 --- a/internal/discover/graphics.go +++ b/internal/discover/graphics.go @@ -55,29 +55,58 @@ func NewGraphicsMountsDiscoverer(logger logger.Interface, driver *root.Driver, h return nil, fmt.Errorf("failed to construct discoverer for graphics libraries: %w", err) } - configs := NewMounts( + binaries := NewMounts( logger, - driver.Configs(), + lookup.NewExecutableLocator(logger, driver.Root), driver.Root, []string{ + "nvidia-xconfig", + }, + ) + + discover := Merge( + libraries, + binaries, + newGraphicsConfigsDiscoverer(logger, driver), + newVulkanConfigsDiscover(logger, driver), + ) + + return discover, nil +} + +// newGraphicsConfigsDiscoverer creates a discoverer for graphics-related config +// files such as the EGL vendor and external platform ICD files. +// The config files are mounted at the standard locations in the container so +// that they are discovered by the loaders in the container even if they are +// installed at non-standard locations on the host. +func newGraphicsConfigsDiscoverer(logger logger.Interface, driver *root.Driver) Discover { + shareConfigs := WithCache(&mountsToContainerPath{ + logger: logger, + locator: driver.Configs(), + required: []string{ "glvnd/egl_vendor.d/10_nvidia.json", "egl/egl_external_platform.d/15_nvidia_gbm.json", "egl/egl_external_platform.d/10_nvidia_wayland.json", "egl/egl_external_platform.d/09_nvidia_wayland2.json", + "egl/egl_external_platform.d/20_nvidia_xcb.json", + "egl/egl_external_platform.d/20_nvidia_xlib.json", "nvidia/nvoptix.bin", "X11/xorg.conf.d/10-nvidia.conf", "X11/xorg.conf.d/nvidia-drm-outputclass.conf", - "OpenCL/vendors/nvidia.icd", }, - ) + containerRoot: "/usr/share", + }) - discover := Merge( - libraries, - configs, - newVulkanConfigsDiscover(logger, driver), - ) + etcConfigs := WithCache(&mountsToContainerPath{ + logger: logger, + locator: driver.Configs(), + required: []string{ + "OpenCL/vendors/nvidia.icd", + }, + containerRoot: "/etc", + }) - return discover, nil + return Merge(shareConfigs, etcConfigs) } // newVulkanConfigsDiscover creates a discoverer for vulkan ICD files. @@ -101,12 +130,12 @@ func newVulkanConfigsDiscover(logger logger.Interface, driver *root.Driver) Disc case "arm64": required = append(required, "vulkan/icd.d/nvidia_icd.aarch64.json") } - return &mountsToContainerPath{ + return WithCache(&mountsToContainerPath{ logger: logger, locator: locator, required: required, containerRoot: "/etc", - } + }) } type graphicsDriverLibraries struct { @@ -135,11 +164,14 @@ func newGraphicsLibrariesDiscoverer(logger logger.Interface, driver *root.Driver driver.Libraries(), driver.Root, []string{ - // The libnvidia-egl-gbm and libnvidia-egl-wayland libraries do not - // have the RM version. Use the *.* pattern to match X.Y.Z versions. + // The EGL platform libraries such as libnvidia-egl-gbm and + // libnvidia-egl-wayland do not have the RM version. Use the *.* + // pattern to match X.Y.Z versions. "libnvidia-egl-gbm.so.*.*", "libnvidia-egl-wayland.so.*.*", "libnvidia-egl-wayland2.so.*.*", + "libnvidia-egl-xcb.so.*.*", + "libnvidia-egl-xlib.so.*.*", // We include the following libraries to have them available for // symlink creation below: // If CDI injection is used, these should already be detected as: @@ -151,6 +183,10 @@ func newGraphicsLibrariesDiscoverer(logger logger.Interface, driver *root.Driver }, ) + // The X.Org driver modules are mounted at their host paths. This keeps any + // ModulePath specified in the xorg.conf.d config file that is mounted into + // the container valid and means that modules installed to the default X.Org + // module path on the host are also found there in the container. xorgLibraries := NewMounts( logger, lookup.NewFileLocator( @@ -231,7 +267,7 @@ func (d graphicsDriverLibraries) Hooks() ([]Hook, error) { return nil, nil } - hook := d.hookCreator.Create("create-symlinks", links...) + hook := d.hookCreator.Create(CreateSymlinksHook, links...) return hook.Hooks() } diff --git a/internal/discover/graphics_test.go b/internal/discover/graphics_test.go index fff3a0426..e022bb87f 100644 --- a/internal/discover/graphics_test.go +++ b/internal/discover/graphics_test.go @@ -17,6 +17,7 @@ package discover import ( + "os" "path/filepath" "strings" "testing" @@ -25,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/NVIDIA/nvidia-container-toolkit/internal/devices" + "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/root" "github.com/NVIDIA/nvidia-container-toolkit/internal/test" ) @@ -189,6 +191,78 @@ func TestGraphicsLibrariesDiscoverer(t *testing.T) { } } +func TestGraphicsConfigsDiscoverer(t *testing.T) { + logger, _ := testlog.NewNullLogger() + + testCases := []struct { + description string + files []string + // expected maps the path of the file in the driver root to the path + // that it is expected to be mounted at in the container. + expected map[string]string + }{ + { + description: "config files in the standard locations", + files: []string{ + "/usr/share/glvnd/egl_vendor.d/10_nvidia.json", + "/usr/share/egl/egl_external_platform.d/20_nvidia_xcb.json", + "/usr/share/X11/xorg.conf.d/10-nvidia.conf", + "/etc/OpenCL/vendors/nvidia.icd", + }, + expected: map[string]string{ + "/usr/share/glvnd/egl_vendor.d/10_nvidia.json": "/usr/share/glvnd/egl_vendor.d/10_nvidia.json", + "/usr/share/egl/egl_external_platform.d/20_nvidia_xcb.json": "/usr/share/egl/egl_external_platform.d/20_nvidia_xcb.json", + "/usr/share/X11/xorg.conf.d/10-nvidia.conf": "/usr/share/X11/xorg.conf.d/10-nvidia.conf", + "/etc/OpenCL/vendors/nvidia.icd": "/etc/OpenCL/vendors/nvidia.icd", + }, + }, + { + description: "config files in non-standard locations are mounted at the standard locations", + files: []string{ + "/usr/local/share/glvnd/egl_vendor.d/10_nvidia.json", + "/usr/local/share/egl/egl_external_platform.d/20_nvidia_xlib.json", + "/etc/X11/xorg.conf.d/nvidia-drm-outputclass.conf", + }, + expected: map[string]string{ + "/usr/local/share/glvnd/egl_vendor.d/10_nvidia.json": "/usr/share/glvnd/egl_vendor.d/10_nvidia.json", + "/usr/local/share/egl/egl_external_platform.d/20_nvidia_xlib.json": "/usr/share/egl/egl_external_platform.d/20_nvidia_xlib.json", + "/etc/X11/xorg.conf.d/nvidia-drm-outputclass.conf": "/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + // The config search paths include the XDG data dirs. These are + // set explicitly to ensure that the test is not affected by the + // environment that it is run in. + t.Setenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share") + + driverRoot := t.TempDir() + for _, f := range tc.files { + path := filepath.Join(driverRoot, f) + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0755)) + require.NoError(t, os.WriteFile(path, []byte{}, 0600)) + } + + driver := root.New( + root.WithLogger(logger), + root.WithDriverRoot(driverRoot), + ) + + mounts, err := newGraphicsConfigsDiscoverer(logger, driver).Mounts() + require.NoError(t, err) + + discovered := make(map[string]string) + for _, mount := range mounts { + hostPath := strings.TrimPrefix(mount.HostPath, driverRoot) + discovered[hostPath] = mount.Path + } + require.EqualValues(t, tc.expected, discovered) + }) + } +} + func TestDrmDevicesByPath(t *testing.T) { defer devices.SetAllForTest()() moduleRoot, err := test.GetModuleRoot() diff --git a/internal/logger/api.go b/internal/logger/api.go index c48e23eea..ac6d47759 100644 --- a/internal/logger/api.go +++ b/internal/logger/api.go @@ -18,9 +18,9 @@ package logger // Interface defines the API for the logger package type Interface interface { - Debugf(string, ...interface{}) - Errorf(string, ...interface{}) - Infof(string, ...interface{}) - Warningf(string, ...interface{}) - Tracef(string, ...interface{}) + Debugf(string, ...any) + Errorf(string, ...any) + Infof(string, ...any) + Warningf(string, ...any) + Tracef(string, ...any) } diff --git a/internal/logger/lib.go b/internal/logger/lib.go index cfe109f53..89b292e0a 100644 --- a/internal/logger/lib.go +++ b/internal/logger/lib.go @@ -29,16 +29,16 @@ type NullLogger struct{} var _ Interface = (*NullLogger)(nil) // Debugf is a no-op for the null logger -func (l *NullLogger) Debugf(string, ...interface{}) {} +func (l *NullLogger) Debugf(string, ...any) {} // Errorf is a no-op for the null logger -func (l *NullLogger) Errorf(string, ...interface{}) {} +func (l *NullLogger) Errorf(string, ...any) {} // Infof is a no-op for the null logger -func (l *NullLogger) Infof(string, ...interface{}) {} +func (l *NullLogger) Infof(string, ...any) {} // Warningf is a no-op for the null logger -func (l *NullLogger) Warningf(string, ...interface{}) {} +func (l *NullLogger) Warningf(string, ...any) {} // Tracef is a no-op for the null logger -func (l *NullLogger) Tracef(string, ...interface{}) {} +func (l *NullLogger) Tracef(string, ...any) {} diff --git a/internal/modifier/hook_remover.go b/internal/modifier/hook_remover.go index f143fc5c4..f4c9f3791 100644 --- a/internal/modifier/hook_remover.go +++ b/internal/modifier/hook_remover.go @@ -57,7 +57,6 @@ func (m nvidiaContainerRuntimeHookRemover) Modify(spec *specs.Spec) error { var newPrestart []specs.Hook for _, hook := range spec.Hooks.Prestart { - hook := hook if isNVIDIAContainerRuntimeHook(&hook) { m.logger.Debugf("Removing hook %v", hook) continue diff --git a/internal/modifier/stable.go b/internal/modifier/stable.go index 08b0d822d..8e34b7929 100644 --- a/internal/modifier/stable.go +++ b/internal/modifier/stable.go @@ -49,7 +49,6 @@ func (m stableRuntimeModifier) Modify(spec *specs.Spec) error { // If an NVIDIA Container Runtime Hook already exists, we don't make any modifications to the spec. if spec.Hooks != nil { for _, hook := range spec.Hooks.Prestart { - hook := hook if isNVIDIAContainerRuntimeHook(&hook) { m.logger.Infof("Existing nvidia prestart hook (%v) found in OCI spec", hook.Path) return nil diff --git a/internal/nvsandboxutils/cgo_helpers_static.go b/internal/nvsandboxutils/cgo_helpers_static.go index 5924d6227..34e7d2e17 100644 --- a/internal/nvsandboxutils/cgo_helpers_static.go +++ b/internal/nvsandboxutils/cgo_helpers_static.go @@ -19,7 +19,7 @@ package nvsandboxutils var cgoAllocsUnknown = new(struct{}) func clen(n []byte) int { - for i := 0; i < len(n); i++ { + for i := range n { if n[i] == 0 { return i } diff --git a/internal/oci/state.go b/internal/oci/state.go index 411bb306c..9e6740f0a 100644 --- a/internal/oci/state.go +++ b/internal/oci/state.go @@ -27,7 +27,14 @@ import ( ) // State stores an OCI container state. This includes the spec path and the environment -type State specs.State +type State struct { + specs.State + // Root is a non-standard extension included in the container state JSON by some + // OCI runtimes (e.g., crun). When present it provides the rootfs path directly, + // avoiding the need to open config.json — which may be permission-denied when + // running with user namespaces such as --userns=nomap (issue #648). + Root string `json:"root,omitempty"` +} // LoadContainerState loads the container state from the specified filename. If the filename is empty or '-' the state is loaded from STDIN func LoadContainerState(filename string) (*State, error) { @@ -56,17 +63,26 @@ func ReadContainerState(reader io.Reader) (*State, error) { return &s, nil } -// GetContainerRoot returns the root for the container from the associated spec. If the spec is not yet loaded, it is -// loaded and cached. -func (s *State) GetContainerRoot() (string, error) { +func (s *State) getRoot() (string, error) { + if s.Root != "" { + return s.Root, nil + } spec, err := s.loadMinimalSpec() if err != nil { return "", err } - - var containerRoot string if spec.Root != nil { - containerRoot = spec.Root.Path + return spec.Root.Path, nil + } + return "", nil +} + +// GetContainerRoot returns the root for the container from the associated spec. If the spec is not yet loaded, it is +// loaded and cached. +func (s *State) GetContainerRoot() (string, error) { + containerRoot, err := s.getRoot() + if err != nil { + return "", err } if filepath.IsAbs(containerRoot) { diff --git a/internal/oci/state_test.go b/internal/oci/state_test.go new file mode 100644 index 000000000..9a52d0008 --- /dev/null +++ b/internal/oci/state_test.go @@ -0,0 +1,258 @@ +/** +# Copyright (c), NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package oci + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/require" +) + +func TestReadContainerState(t *testing.T) { + testCases := []struct { + description string + contents string + isError bool + expected *State + }{ + { + description: "invalid json returns error", + contents: "not json", + isError: true, + }, + { + description: "empty object decodes to empty state", + contents: "{}", + expected: &State{}, + }, + { + description: "standard fields are decoded", + contents: `{"bundle": "/foo/bar"}`, + expected: &State{ + State: specs.State{ + Bundle: "/foo/bar", + }, + }, + }, + { + description: "non-standard root extension is decoded", + contents: `{"bundle": "/foo/bar", "root": "/foo/bar/rootfs"}`, + expected: &State{ + State: specs.State{ + Bundle: "/foo/bar", + }, + Root: "/foo/bar/rootfs", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + s, err := ReadContainerState(bytes.NewBufferString(tc.contents)) + + if tc.isError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.EqualValues(t, tc.expected, s) + }) + } +} + +func TestLoadContainerState(t *testing.T) { + testCases := []struct { + description string + useStdin bool + useMissingFile bool + stateJSON string + isError bool + expectedBundle string + }{ + { + description: "reads from stdin when filename is empty", + useStdin: true, + stateJSON: `{"bundle": "/from/stdin"}`, + expectedBundle: "/from/stdin", + }, + { + description: "reads from a file when filename is specified", + stateJSON: `{"bundle": "/from/file"}`, + expectedBundle: "/from/file", + }, + { + description: "returns an error when the file does not exist", + useMissingFile: true, + isError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var filename string + switch { + case tc.useStdin: + oldStdin := os.Stdin + r, w, err := os.Pipe() + require.NoError(t, err) + _, err = w.WriteString(tc.stateJSON) + require.NoError(t, err) + require.NoError(t, w.Close()) + os.Stdin = r + t.Cleanup(func() { os.Stdin = oldStdin }) + case tc.useMissingFile: + filename = filepath.Join(t.TempDir(), "does-not-exist.json") + default: + filename = filepath.Join(t.TempDir(), "state.json") + require.NoError(t, os.WriteFile(filename, []byte(tc.stateJSON), 0600)) + } + + s, err := LoadContainerState(filename) + + if tc.isError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedBundle, s.Bundle) + }) + } +} + +func TestGetContainerRoot(t *testing.T) { + testCases := []struct { + description string + root string + specJSON string + writeSpec bool + isError bool + expectedRoot func(bundle string) string + }{ + { + description: "absolute root extension is returned as-is", + root: "/absolute/rootfs", + expectedRoot: func(bundle string) string { + return "/absolute/rootfs" + }, + }, + { + description: "relative root extension is joined with the bundle", + root: "rootfs", + expectedRoot: func(bundle string) string { + return filepath.Join(bundle, "rootfs") + }, + }, + { + description: "falls back to the spec file when root extension is not set", + writeSpec: true, + specJSON: `{"root": {"path": "rootfs"}}`, + expectedRoot: func(bundle string) string { + return filepath.Join(bundle, "rootfs") + }, + }, + { + description: "returns an empty string when neither root extension nor spec root are set", + writeSpec: true, + specJSON: `{}`, + expectedRoot: func(bundle string) string { + return bundle + }, + }, + { + description: "returns an error when the spec file cannot be loaded", + writeSpec: false, + isError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + dir := t.TempDir() + if tc.writeSpec { + require.NoError(t, os.WriteFile(GetSpecFilePath(dir), []byte(tc.specJSON), 0600)) + } + s := &State{ + State: specs.State{Bundle: dir}, + Root: tc.root, + } + + root, err := s.GetContainerRoot() + + if tc.isError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedRoot(dir), root) + }) + } +} + +func TestLoadMinimalSpec(t *testing.T) { + testCases := []struct { + description string + specJSON string + writeSpec bool + isError bool + expectedRoot string + }{ + { + description: "returns an error when the spec file does not exist", + writeSpec: false, + isError: true, + }, + { + description: "returns an error for invalid json", + writeSpec: true, + specJSON: "not json", + isError: true, + }, + { + description: "decodes the root field", + writeSpec: true, + specJSON: `{"root": {"path": "/some/rootfs"}}`, + expectedRoot: "/some/rootfs", + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + dir := t.TempDir() + if tc.writeSpec { + require.NoError(t, os.WriteFile(GetSpecFilePath(dir), []byte(tc.specJSON), 0600)) + } + s := &State{State: specs.State{Bundle: dir}} + + ms, err := s.loadMinimalSpec() + + if tc.isError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedRoot, ms.Root.Path) + }) + } +} diff --git a/internal/requirements/constraints/factory.go b/internal/requirements/constraints/factory.go index d874bdaa6..e8b00bbbf 100644 --- a/internal/requirements/constraints/factory.go +++ b/internal/requirements/constraints/factory.go @@ -68,9 +68,9 @@ func (r factory) newConstraintFromRequirement(requirement string) (Constraint, e } var terms []Constraint - for _, term := range strings.Split(requirement, orSeparator) { + for term := range strings.SplitSeq(requirement, orSeparator) { var factors []Constraint - for _, factor := range strings.Split(term, andSeparator) { + for factor := range strings.SplitSeq(term, andSeparator) { f, err := r.parse(factor) if err != nil { return nil, err diff --git a/internal/requirements/constraints/property.go b/internal/requirements/constraints/property.go index c1a754ebd..d157705b1 100644 --- a/internal/requirements/constraints/property.go +++ b/internal/requirements/constraints/property.go @@ -18,9 +18,8 @@ package constraints import ( "fmt" - "strings" - "golang.org/x/mod/semver" + "github.com/Masterminds/semver/v3" ) // Property represents a property that is used to check requirements @@ -111,20 +110,23 @@ func (p versionProperty) CompareTo(other string) (int, error) { return 0, fmt.Errorf("invailid value for %v: %v", p.name, err) } - vValue := ensurePrefix(p.value, "v") - vOther := ensurePrefix(other, "v") - return semver.Compare(vValue, vOther), nil + value, err := semver.NewVersion(p.value) + if err != nil { + return 0, fmt.Errorf("invalid value for %v: %w", p.name, err) + } + otherVersion, err := semver.NewVersion(other) + if err != nil { + return 0, fmt.Errorf("invalid value for %v: %w", p.name, err) + } + return value.Compare(otherVersion), nil } // Validate checks whether the supplied value is a valid semantic version func (p versionProperty) Validate(value string) error { - if !semver.IsValid(ensurePrefix(value, "v")) { - return fmt.Errorf("invailid value %v; expected a valid version string", value) + _, err := semver.NewVersion(value) + if err != nil { + return fmt.Errorf("invalid version string: %w", err) } return nil } - -func ensurePrefix(s string, prefix string) string { - return prefix + strings.TrimPrefix(s, prefix) -} diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 392459a1a..58b3f9a43 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -91,13 +91,13 @@ func (r rt) Run(argv []string) (rerr error) { return runtime.Exec(argv) } -func (r rt) Errorf(format string, args ...interface{}) { +func (r rt) Errorf(format string, args ...any) { r.logger.Errorf(format, args...) } // TODO: This should be refactored / combined with parseArgs in logger. func hasVersionFlag(args []string) bool { - for i := 0; i < len(args); i++ { + for i := range args { param := args[i] parts := strings.SplitN(param, "=", 2) diff --git a/packaging/debian/nvidia-container-toolkit-base.install b/packaging/debian/nvidia-container-toolkit-base.install index 09a3eb8b4..86d50526e 100644 --- a/packaging/debian/nvidia-container-toolkit-base.install +++ b/packaging/debian/nvidia-container-toolkit-base.install @@ -4,3 +4,5 @@ nvidia-cdi-hook /usr/bin nvidia-cdi-refresh.service /lib/systemd/system/ nvidia-cdi-refresh.path /lib/systemd/system/ nvidia-cdi-refresh.env /etc/nvidia-container-toolkit/ +99-nvidia-cdi-refresh.rules /lib/udev/rules.d/ +10-container-engines.conf /lib/systemd/system/nvidia-cdi-refresh.service.d/ diff --git a/packaging/debian/nvidia-container-toolkit-base.postinst b/packaging/debian/nvidia-container-toolkit-base.postinst index 7ee72e46f..a237f171e 100644 --- a/packaging/debian/nvidia-container-toolkit-base.postinst +++ b/packaging/debian/nvidia-container-toolkit-base.postinst @@ -5,6 +5,12 @@ set -e case "$1" in configure) /usr/bin/nvidia-ctk --quiet config --config-file=/etc/nvidia-container-runtime/config.toml --in-place + + # Reload udev rules so that the nvidia-cdi-refresh rules take effect + # without a reboot. + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules >/dev/null 2>&1 || true + fi ;; abort-upgrade|abort-remove|abort-deconfigure) diff --git a/packaging/debian/rules b/packaging/debian/rules index dd5e0966b..d8796e0ba 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -16,3 +16,5 @@ override_dh_fixperms: chmod 755 debian/$(shell dh_listpackages)/usr/bin/nvidia-cdi-hook || true chmod 644 debian/$(shell dh_listpackages)/lib/systemd/system/nvidia-cdi-refresh.service || true chmod 644 debian/$(shell dh_listpackages)/lib/systemd/system/nvidia-cdi-refresh.path || true + chmod 644 debian/$(shell dh_listpackages)/lib/udev/rules.d/99-nvidia-cdi-refresh.rules || true + chmod 644 debian/$(shell dh_listpackages)/lib/systemd/system/nvidia-cdi-refresh.service.d/10-container-engines.conf || true diff --git a/packaging/rpm/SPECS/nvidia-container-toolkit.spec b/packaging/rpm/SPECS/nvidia-container-toolkit.spec index 606d45d57..d7c8ec1bd 100644 --- a/packaging/rpm/SPECS/nvidia-container-toolkit.spec +++ b/packaging/rpm/SPECS/nvidia-container-toolkit.spec @@ -21,6 +21,8 @@ Source7: nvidia-cdi-refresh.service Source8: nvidia-cdi-refresh.path Source9: nvidia-cdi-refresh.env Source10: 90-nvidia-container-toolkit.preset +Source11: 99-nvidia-cdi-refresh.rules +Source12: 10-container-engines.conf %if 0%{?rhel} == 7 || 0%{?amzn} == 2 BuildRequires: systemd @@ -28,6 +30,10 @@ BuildRequires: systemd BuildRequires: systemd-rpm-macros %endif +# Distributions that don't define the udev rules directory macro fall back to +# the default location. +%{!?_udevrulesdir: %global _udevrulesdir %{_prefix}/lib/udev/rules.d} + Obsoletes: nvidia-container-runtime <= 3.5.0-1, nvidia-container-runtime-hook <= 1.4.0-2 Provides: nvidia-container-runtime Provides: nvidia-container-runtime-hook @@ -38,12 +44,14 @@ Requires: nvidia-container-toolkit-base == %{version}-%{release} Provides tools and utilities to enable GPU support in containers. %prep -cp %{SOURCE0} %{SOURCE1} %{SOURCE2} %{SOURCE3} %{SOURCE4} %{SOURCE5} %{SOURCE6} %{SOURCE7} %{SOURCE8} %{SOURCE9} %{SOURCE10} . +cp %{SOURCE0} %{SOURCE1} %{SOURCE2} %{SOURCE3} %{SOURCE4} %{SOURCE5} %{SOURCE6} %{SOURCE7} %{SOURCE8} %{SOURCE9} %{SOURCE10} %{SOURCE11} %{SOURCE12} . %install mkdir -p %{buildroot}%{_bindir} mkdir -p %{buildroot}%{_unitdir} mkdir -p %{buildroot}%{_presetdir} +mkdir -p %{buildroot}%{_udevrulesdir} +mkdir -p %{buildroot}%{_unitdir}/nvidia-cdi-refresh.service.d mkdir -p %{buildroot}%{_sysconfdir}/nvidia-container-toolkit install -m 755 -t %{buildroot}%{_bindir} nvidia-container-runtime-hook @@ -55,6 +63,8 @@ install -m 755 -t %{buildroot}%{_bindir} nvidia-cdi-hook install -m 644 -t %{buildroot}%{_unitdir} nvidia-cdi-refresh.service install -m 644 -t %{buildroot}%{_unitdir} nvidia-cdi-refresh.path install -m 644 -t %{buildroot}%{_presetdir} 90-nvidia-container-toolkit.preset +install -m 644 -t %{buildroot}%{_udevrulesdir} 99-nvidia-cdi-refresh.rules +install -m 644 -t %{buildroot}%{_unitdir}/nvidia-cdi-refresh.service.d 10-container-engines.conf install -m 644 -t %{buildroot}%{_sysconfdir}/nvidia-container-toolkit nvidia-cdi-refresh.env %post @@ -129,6 +139,12 @@ for unit in nvidia-cdi-refresh.path nvidia-cdi-refresh.service; do fi done +# Reload udev rules so that the nvidia-cdi-refresh rules take effect without a +# reboot. +if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules >/dev/null 2>&1 || : +fi + # Trigger CDI refresh on running systemd hosts without making install depend on # the current system state. if command -v systemctl >/dev/null 2>&1; then @@ -148,6 +164,8 @@ fi %{_unitdir}/nvidia-cdi-refresh.service %{_unitdir}/nvidia-cdi-refresh.path %{_presetdir}/90-nvidia-container-toolkit.preset +%{_udevrulesdir}/99-nvidia-cdi-refresh.rules +%{_unitdir}/nvidia-cdi-refresh.service.d/10-container-engines.conf %config(noreplace) %{_sysconfdir}/nvidia-container-toolkit/nvidia-cdi-refresh.env # The OPERATOR EXTENSIONS package consists of components that are required to enable GPU support in Kubernetes. diff --git a/pkg/config/engine/config.go b/pkg/config/engine/config.go index d24d210ab..2a3a06c55 100644 --- a/pkg/config/engine/config.go +++ b/pkg/config/engine/config.go @@ -33,14 +33,14 @@ type Config struct { type RuntimeConfigSource interface { DefaultRuntime() string GetRuntimeConfig(string) (RuntimeConfig, error) - GetDefaultRuntimeOptions() interface{} + GetDefaultRuntimeOptions() any String() string } // A RuntimeConfigDestination allows a runtime with specific settings to be // WRITTEN to a config. type RuntimeConfigDestination interface { - AddRuntimeWithOptions(string, string, bool, interface{}) error + AddRuntimeWithOptions(string, string, bool, any) error EnableCDI() RemoveRuntime(string) error UpdateDefaultRuntime(string, string) error diff --git a/pkg/config/engine/containerd/config.go b/pkg/config/engine/containerd/config.go index acaf393ac..c4c28db3a 100644 --- a/pkg/config/engine/containerd/config.go +++ b/pkg/config/engine/containerd/config.go @@ -32,7 +32,7 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { return c.AddRuntimeWithOptions(name, path, setAsDefault, defaultRuntimeOptions) } -func (c *Config) GetDefaultRuntimeOptions() interface{} { +func (c *Config) GetDefaultRuntimeOptions() any { runtimeNamesForConfig := engine.GetLowLevelRuntimes(c) for _, r := range runtimeNamesForConfig { options := c.GetSubtreeByPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", r}) @@ -42,7 +42,7 @@ func (c *Config) GetDefaultRuntimeOptions() interface{} { } } c.Logger.Warningf("Could not infer options from runtimes %v", runtimeNamesForConfig) - options, _ := toml.TreeFromMap(map[string]interface{}{ + options, _ := toml.TreeFromMap(map[string]any{ "runtime_type": c.RuntimeType, "runtime_root": "", "runtime_engine": "", @@ -51,7 +51,7 @@ func (c *Config) GetDefaultRuntimeOptions() interface{} { return options } -func (c *Config) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options interface{}) error { +func (c *Config) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options any) error { config := *c.Tree config.Set("version", c.Version) @@ -95,7 +95,7 @@ func (c *Config) getStringArrayValue(path []string) ([]string, error) { if !config.HasPath(path) { return nil, nil } - annotationsI, ok := config.GetPath(path).([]interface{}) + annotationsI, ok := config.GetPath(path).([]any) if !ok { return nil, fmt.Errorf("invalid annotations: %v", annotationsI) } @@ -143,7 +143,7 @@ func (c *Config) RemoveRuntime(name string) error { } runtimePath := []string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name} - for i := 0; i < len(runtimePath); i++ { + for i := range runtimePath { if runtimes, ok := config.GetPath(runtimePath[:len(runtimePath)-i]).(*toml.Tree); ok { if len(runtimes.Keys()) == 0 { config.DeletePath(runtimePath[:len(runtimePath)-i]) diff --git a/pkg/config/engine/containerd/config_drop_in.go b/pkg/config/engine/containerd/config_drop_in.go index 1dcd58320..76c502e08 100644 --- a/pkg/config/engine/containerd/config_drop_in.go +++ b/pkg/config/engine/containerd/config_drop_in.go @@ -20,6 +20,7 @@ package containerd import ( "fmt" "path/filepath" + "slices" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger" "github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine" @@ -204,11 +205,8 @@ func (c *topLevelConfig) ensureImports(dropInFilename string) { currentImports := c.getCurrentImports() requiredImport := c.importPattern(dropInFilename) - for _, currentImport := range currentImports { - // If the requiredImport is already present, then we need not update the config. - if currentImport == requiredImport { - return - } + if slices.Contains(currentImports, requiredImport) { + return } currentImports = append(currentImports, requiredImport) diff --git a/pkg/config/engine/containerd/config_v1.go b/pkg/config/engine/containerd/config_v1.go index def36bc00..a5b7602f7 100644 --- a/pkg/config/engine/containerd/config_v1.go +++ b/pkg/config/engine/containerd/config_v1.go @@ -37,11 +37,11 @@ func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool) error return c.AddRuntimeWithOptions(name, path, setAsDefault, defaultRuntimeOptions) } -func (c *ConfigV1) GetDefaultRuntimeOptions() interface{} { +func (c *ConfigV1) GetDefaultRuntimeOptions() any { return (*Config)(c).GetDefaultRuntimeOptions() } -func (c *ConfigV1) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options interface{}) error { +func (c *ConfigV1) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options any) error { if err := (*Config)(c).AddRuntimeWithOptions(name, path, setAsDefault && !c.UseLegacyConfig, options); err != nil { return err } @@ -104,7 +104,7 @@ func (c *ConfigV1) RemoveRuntime(name string) error { } runtimeConfigPath := []string{"plugins", "cri", "containerd", "runtimes", name} - for i := 0; i < len(runtimeConfigPath); i++ { + for i := range runtimeConfigPath { if runtimes, ok := config.GetPath(runtimeConfigPath[:len(runtimeConfigPath)-i]).(*toml.Tree); ok { if len(runtimes.Keys()) == 0 { config.DeletePath(runtimeConfigPath[:len(runtimeConfigPath)-i]) diff --git a/pkg/config/engine/crio/crio.go b/pkg/config/engine/crio/crio.go index f6819612d..5ce9862ab 100644 --- a/pkg/config/engine/crio/crio.go +++ b/pkg/config/engine/crio/crio.go @@ -102,7 +102,7 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { return c.AddRuntimeWithOptions(name, path, setAsDefault, defaultRuntimeOptions) } -func (c *Config) GetDefaultRuntimeOptions() interface{} { +func (c *Config) GetDefaultRuntimeOptions() any { runtimeNamesForConfig := engine.GetLowLevelRuntimes(c) for _, r := range runtimeNamesForConfig { options := c.GetSubtreeByPath([]string{"crio", "runtime", "runtimes", r}) @@ -115,7 +115,7 @@ func (c *Config) GetDefaultRuntimeOptions() interface{} { return nil } -func (c *Config) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options interface{}) error { +func (c *Config) AddRuntimeWithOptions(name string, path string, setAsDefault bool, options any) error { config := *c.Tree if options != nil { @@ -163,7 +163,7 @@ func (c *Config) RemoveRuntime(name string) error { runtimeClassPath := []string{"crio", "runtime", "runtimes", name} config.DeletePath(runtimeClassPath) - for i := 0; i < len(runtimeClassPath); i++ { + for i := range runtimeClassPath { remainingPath := runtimeClassPath[:len(runtimeClassPath)-i] if entry, ok := config.GetPath(remainingPath).(*toml.Tree); ok { if len(entry.Keys()) != 0 { diff --git a/pkg/config/engine/docker/docker.go b/pkg/config/engine/docker/docker.go index 2cae9e71f..53e09b67d 100644 --- a/pkg/config/engine/docker/docker.go +++ b/pkg/config/engine/docker/docker.go @@ -31,11 +31,11 @@ const ( // Config defines a docker config file. // TODO: This should not be public, but we need to access it from the tests in tools/container/docker -type Config map[string]interface{} +type Config map[string]any var _ engine.Interface = (*Config)(nil) -type dockerRuntime map[string]interface{} +type dockerRuntime map[string]any var _ engine.RuntimeConfig = (*dockerRuntime)(nil) @@ -73,13 +73,13 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { config := *c // Read the existing runtimes - runtimes := make(map[string]interface{}) + runtimes := make(map[string]any) if _, exists := config["runtimes"]; exists { - runtimes = config["runtimes"].(map[string]interface{}) + runtimes = config["runtimes"].(map[string]any) } // Add / update the runtime definitions - runtimes[name] = map[string]interface{}{ + runtimes[name] = map[string]any{ "path": path, "args": []string{}, } @@ -136,7 +136,7 @@ func (c *Config) RemoveRuntime(name string) error { } if _, exists := config["runtimes"]; exists { - runtimes := config["runtimes"].(map[string]interface{}) + runtimes := config["runtimes"].(map[string]any) delete(runtimes, name) @@ -202,11 +202,11 @@ func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) { cfg := *c - var runtimes map[string]interface{} + var runtimes map[string]any if _, ok := cfg["runtimes"]; ok { - runtimes = cfg["runtimes"].(map[string]interface{}) + runtimes = cfg["runtimes"].(map[string]any) if r, ok := runtimes[name]; ok { - dr := dockerRuntime(r.(map[string]interface{})) + dr := dockerRuntime(r.(map[string]any)) return &dr, nil } } diff --git a/pkg/config/engine/docker/docker_test.go b/pkg/config/engine/docker/docker_test.go index a279c7be2..9dd3f21d5 100644 --- a/pkg/config/engine/docker/docker_test.go +++ b/pkg/config/engine/docker/docker_test.go @@ -29,7 +29,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { config Config runtimeName string setAsDefault bool - expectedDefaultRuntimeName interface{} + expectedDefaultRuntimeName any }{ { setAsDefault: false, @@ -41,7 +41,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { expectedDefaultRuntimeName: "NAME", }, { - config: map[string]interface{}{ + config: map[string]any{ "default-runtime": "ALREADY_SET", }, runtimeName: "NAME", @@ -49,7 +49,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { expectedDefaultRuntimeName: "ALREADY_SET", }, { - config: map[string]interface{}{ + config: map[string]any{ "default-runtime": "ALREADY_SET", }, runtimeName: "NAME", @@ -61,7 +61,7 @@ func TestUpdateConfigDefaultRuntime(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) { if tc.config == nil { - tc.config = make(map[string]interface{}) + tc.config = make(map[string]any) } err := tc.config.AddRuntime(tc.runtimeName, "", tc.setAsDefault) require.NoError(t, err) @@ -76,21 +76,21 @@ func TestUpdateConfigRuntimes(t *testing.T) { testCases := []struct { config Config runtimes map[string]string - expectedConfig map[string]interface{} + expectedConfig map[string]any }{ { - config: map[string]interface{}{}, + config: map[string]any{}, runtimes: map[string]string{ "runtime1": "/test/runtime/dir/runtime1", "runtime2": "/test/runtime/dir/runtime2", }, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "runtime1": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "runtime1": map[string]any{ "path": "/test/runtime/dir/runtime1", "args": []string{}, }, - "runtime2": map[string]interface{}{ + "runtime2": map[string]any{ "path": "/test/runtime/dir/runtime2", "args": []string{}, }, @@ -98,9 +98,9 @@ func TestUpdateConfigRuntimes(t *testing.T) { }, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "runtime1": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "runtime1": map[string]any{ "path": "runtime1", "args": []string{}, }, @@ -110,13 +110,13 @@ func TestUpdateConfigRuntimes(t *testing.T) { "runtime1": "/test/runtime/dir/runtime1", "runtime2": "/test/runtime/dir/runtime2", }, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "runtime1": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "runtime1": map[string]any{ "path": "/test/runtime/dir/runtime1", "args": []string{}, }, - "runtime2": map[string]interface{}{ + "runtime2": map[string]any{ "path": "/test/runtime/dir/runtime2", "args": []string{}, }, @@ -124,9 +124,9 @@ func TestUpdateConfigRuntimes(t *testing.T) { }, }, { - config: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "not-nvidia": map[string]interface{}{ + config: map[string]any{ + "runtimes": map[string]any{ + "not-nvidia": map[string]any{ "path": "some-other-path", "args": []string{}, }, @@ -135,13 +135,13 @@ func TestUpdateConfigRuntimes(t *testing.T) { runtimes: map[string]string{ "runtime1": "/test/runtime/dir/runtime1", }, - expectedConfig: map[string]interface{}{ - "runtimes": map[string]interface{}{ - "not-nvidia": map[string]interface{}{ + expectedConfig: map[string]any{ + "runtimes": map[string]any{ + "not-nvidia": map[string]any{ "path": "some-other-path", "args": []string{}, }, - "runtime1": map[string]interface{}{ + "runtime1": map[string]any{ "path": "/test/runtime/dir/runtime1", "args": []string{}, }, @@ -149,7 +149,7 @@ func TestUpdateConfigRuntimes(t *testing.T) { }, }, { - config: map[string]interface{}{ + config: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ @@ -160,15 +160,15 @@ func TestUpdateConfigRuntimes(t *testing.T) { runtimes: map[string]string{ "runtime1": "/test/runtime/dir/runtime1", }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ "max-size": "100m", }, "storage-driver": "overlay2", - "runtimes": map[string]interface{}{ - "runtime1": map[string]interface{}{ + "runtimes": map[string]any{ + "runtime1": map[string]any{ "path": "/test/runtime/dir/runtime1", "args": []string{}, }, @@ -176,7 +176,7 @@ func TestUpdateConfigRuntimes(t *testing.T) { }, }, { - config: map[string]interface{}{ + config: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ @@ -184,7 +184,7 @@ func TestUpdateConfigRuntimes(t *testing.T) { }, "storage-driver": "overlay2", }, - expectedConfig: map[string]interface{}{ + expectedConfig: map[string]any{ "exec-opts": []string{"native.cgroupdriver=systemd"}, "log-driver": "json-file", "log-opts": map[string]string{ @@ -215,9 +215,9 @@ func TestUpdateConfigRuntimes(t *testing.T) { } func TestGetRuntimeConfig(t *testing.T) { - c := map[string]interface{}{ - "runtimes": map[string]interface{}{ - "nvidia": map[string]interface{}{ + c := map[string]any{ + "runtimes": map[string]any{ + "nvidia": map[string]any{ "path": "nvidia-container-runtime", "args": []string{}, }, diff --git a/pkg/config/ocihook/oci-hook.go b/pkg/config/ocihook/oci-hook.go index a64ec420d..bed0e4e36 100644 --- a/pkg/config/ocihook/oci-hook.go +++ b/pkg/config/ocihook/oci-hook.go @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "slices" "strings" ) @@ -59,11 +60,8 @@ func generateOciHook(executablePath string) podmanHook { dir := filepath.Dir(executablePath) var found bool - for _, pathPart := range pathParts { - if pathPart == dir { - found = true - break - } + if slices.Contains(pathParts, dir) { + found = true } if !found { pathParts = append(pathParts, dir) diff --git a/pkg/config/toml/source-map.go b/pkg/config/toml/source-map.go index 077bbb392..7b8cf3c70 100644 --- a/pkg/config/toml/source-map.go +++ b/pkg/config/toml/source-map.go @@ -16,7 +16,7 @@ package toml -type tomlMap map[string]interface{} +type tomlMap map[string]any var _ Loader = (*tomlFile)(nil) diff --git a/pkg/config/toml/source.go b/pkg/config/toml/source.go index 105a9743b..eed64b1b5 100644 --- a/pkg/config/toml/source.go +++ b/pkg/config/toml/source.go @@ -48,7 +48,7 @@ func FromFile(path string) Loader { // FromMap creates a TOML source for the specified map. // If an empty map is passed and empty tomly config is used. -func FromMap(m map[string]interface{}) Loader { +func FromMap(m map[string]any) Loader { if m == nil { return Empty } diff --git a/pkg/config/toml/toml.go b/pkg/config/toml/toml.go index 32b855168..b73c51c72 100644 --- a/pkg/config/toml/toml.go +++ b/pkg/config/toml/toml.go @@ -56,19 +56,19 @@ func (t *Tree) HasPath(keys []string) bool { return (*toml.Tree)(t).HasPath(keys) } -func (t *Tree) Get(key string) interface{} { +func (t *Tree) Get(key string) any { return toTreeFromRaw((*toml.Tree)(t).Get(key)) } -func (t *Tree) GetPath(keys []string) interface{} { +func (t *Tree) GetPath(keys []string) any { return toTreeFromRaw((*toml.Tree)(t).GetPath(keys)) } -func (t *Tree) SetPath(keys []string, value interface{}) { +func (t *Tree) SetPath(keys []string, value any) { (*toml.Tree)(t).SetPath(keys, toRawFromTree(value)) } -func (t *Tree) Set(key string, value interface{}) { +func (t *Tree) Set(key string, value any) { (*toml.Tree)(t).Set(key, toRawFromTree(value)) } @@ -84,7 +84,7 @@ func (t *Tree) String() string { return (*toml.Tree)(t).String() } -func (t *Tree) ToMap() map[string]interface{} { +func (t *Tree) ToMap() map[string]any { return (*toml.Tree)(t).ToMap() } @@ -92,21 +92,21 @@ func (t *Tree) Raw() *toml.Tree { return (*toml.Tree)(t) } -func toRawFromTree(value interface{}) interface{} { +func toRawFromTree(value any) any { if tree, ok := value.(*Tree); ok { return (*toml.Tree)(tree) } return value } -func toTreeFromRaw(value interface{}) interface{} { +func toTreeFromRaw(value any) any { if tree, ok := value.(*toml.Tree); ok { return (*Tree)(tree) } return value } -func TreeFromMap(m map[string]interface{}) (*Tree, error) { +func TreeFromMap(m map[string]any) (*Tree, error) { return new(func() (*toml.Tree, error) { return toml.TreeFromMap(m) }) @@ -130,11 +130,11 @@ func LoadFile(path string) (*Tree, error) { }) } -func LoadMap(m map[string]interface{}) (*Tree, error) { +func LoadMap(m map[string]any) (*Tree, error) { return TreeFromMap(m) } -func Marshal(v interface{}) ([]byte, error) { +func Marshal(v any) ([]byte, error) { return toml.Marshal(v) } diff --git a/pkg/nvcdi/driver-wsl.go b/pkg/nvcdi/driver-wsl.go index 3d9b8311c..9f6b901aa 100644 --- a/pkg/nvcdi/driver-wsl.go +++ b/pkg/nvcdi/driver-wsl.go @@ -53,7 +53,7 @@ func (l *wsllib) newWSLDriverDiscoverer() (discover.Discover, error) { } defer func() { if err := dxcore.Shutdown(); err != nil { - l.logger.Warningf("failed to shutdown dxcore: %w", err) + l.logger.Warningf("failed to shutdown dxcore: %v", err) } }() diff --git a/pkg/nvcdi/lib-wsl.go b/pkg/nvcdi/lib-wsl.go index ca5041c04..7ce74aca4 100644 --- a/pkg/nvcdi/lib-wsl.go +++ b/pkg/nvcdi/lib-wsl.go @@ -18,6 +18,7 @@ package nvcdi import ( "fmt" + "slices" "tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/specs-go" @@ -27,7 +28,10 @@ type wsllib nvcdilib var _ deviceSpecGeneratorFactory = (*wsllib)(nil) -func (l *wsllib) DeviceSpecGenerators(...string) (DeviceSpecGenerator, error) { +func (l *wsllib) DeviceSpecGenerators(ids ...string) (DeviceSpecGenerator, error) { + if slices.Contains(ids, "none") { + return emptyDeviceSpecGenerator("none"), nil + } return l, nil } diff --git a/pkg/nvcdi/transform/deduplicate.go b/pkg/nvcdi/transform/deduplicate.go index c7eace03f..e66a138f7 100644 --- a/pkg/nvcdi/transform/deduplicate.go +++ b/pkg/nvcdi/transform/deduplicate.go @@ -41,7 +41,6 @@ func (d dedupe) Transform(spec *specs.Spec) error { } var updatedDevices []specs.Device for _, device := range spec.Devices { - device := device if err := d.transformEdits(&device.ContainerEdits); err != nil { return err } diff --git a/pkg/nvcdi/transform/merged-device.go b/pkg/nvcdi/transform/merged-device.go index 480008f80..32f2af40c 100644 --- a/pkg/nvcdi/transform/merged-device.go +++ b/pkg/nvcdi/transform/merged-device.go @@ -111,7 +111,6 @@ func mergeDeviceSpecs(deviceSpecs []specs.Device, mergedDeviceName string) (*spe mergedEdits := edits.EmptyFactory.New() for _, d := range deviceSpecs { - d := d edit := cdi.ContainerEdits{ ContainerEdits: &d.ContainerEdits, } diff --git a/pkg/nvcdi/transform/remove.go b/pkg/nvcdi/transform/remove.go index bc0094855..029452feb 100644 --- a/pkg/nvcdi/transform/remove.go +++ b/pkg/nvcdi/transform/remove.go @@ -39,7 +39,6 @@ func (r remove) Transform(spec *specs.Spec) error { } for _, device := range spec.Devices { - device := device if err := r.transformEdits(&device.ContainerEdits); err != nil { return fmt.Errorf("failed to remove edits from device %q: %w", device.Name, err) } diff --git a/pkg/nvcdi/transform/root/container-root.go b/pkg/nvcdi/transform/root/container-root.go index b8d76f927..8ac260186 100644 --- a/pkg/nvcdi/transform/root/container-root.go +++ b/pkg/nvcdi/transform/root/container-root.go @@ -38,7 +38,6 @@ func (t containerRootTransformer) Transform(spec *specs.Spec) error { } for _, d := range spec.Devices { - d := d if err := t.applyToEdits(&d.ContainerEdits); err != nil { return fmt.Errorf("failed to apply root transform to device %s: %w", d.Name, err) } diff --git a/pkg/nvcdi/transform/root/host-root.go b/pkg/nvcdi/transform/root/host-root.go index 9d2b54d39..50b3c6dcb 100644 --- a/pkg/nvcdi/transform/root/host-root.go +++ b/pkg/nvcdi/transform/root/host-root.go @@ -38,7 +38,6 @@ func (t hostRootTransformer) Transform(spec *specs.Spec) error { } for _, d := range spec.Devices { - d := d if err := t.applyToEdits(&d.ContainerEdits); err != nil { return fmt.Errorf("failed to apply root transform to device %s: %w", d.Name, err) } diff --git a/pkg/nvcdi/transform/root/root.go b/pkg/nvcdi/transform/root/root.go index 36203fba0..96809519e 100644 --- a/pkg/nvcdi/transform/root/root.go +++ b/pkg/nvcdi/transform/root/root.go @@ -39,9 +39,21 @@ func New(opts ...Option) transform.Transformer { } func (t transformer) transformPath(path string) string { - if !strings.HasPrefix(path, t.root) { + // Ensure that the root ends with a path separator so that the prefix + // comparison below only matches at a path component boundary. Without this + // a root of /driver would also match a path such as /driver-backup/lib.so + // and transform it to {targetRoot}/-backup/lib.so. + root := t.root + if !strings.HasSuffix(root, string(filepath.Separator)) { + root += string(filepath.Separator) + } + + // The path is compared with a trailing separator too so that a path equal + // to the root is also matched and transformed to the target root. + pathWithSeparator := path + string(filepath.Separator) + if !strings.HasPrefix(pathWithSeparator, root) { return path } - return filepath.Join(t.targetRoot, strings.TrimPrefix(path, t.root)) + return filepath.Join(t.targetRoot, strings.TrimPrefix(pathWithSeparator, root)) } diff --git a/pkg/nvcdi/transform/root/root_test.go b/pkg/nvcdi/transform/root/root_test.go new file mode 100644 index 000000000..139960ee4 --- /dev/null +++ b/pkg/nvcdi/transform/root/root_test.go @@ -0,0 +1,94 @@ +/** +# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package root + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTransformPath(t *testing.T) { + testCases := []struct { + description string + root string + targetRoot string + path string + expectedPath string + }{ + { + description: "path under root is transformed", + root: "/run/nvidia/driver", + targetRoot: "/", + path: "/run/nvidia/driver/lib/libcuda.so", + expectedPath: "/lib/libcuda.so", + }, + { + description: "path equal to root is transformed", + root: "/run/nvidia/driver", + targetRoot: "/host", + path: "/run/nvidia/driver", + expectedPath: "/host", + }, + { + description: "path outside root is unchanged", + root: "/run/nvidia/driver", + targetRoot: "/host", + path: "/usr/lib/libcuda.so", + expectedPath: "/usr/lib/libcuda.so", + }, + { + description: "sibling of root with common prefix is unchanged", + root: "/run/nvidia/driver", + targetRoot: "/host", + path: "/run/nvidia/driver-backup/lib/libcuda.so", + expectedPath: "/run/nvidia/driver-backup/lib/libcuda.so", + }, + { + description: "partial path component match is unchanged", + root: "/run/nvidia/driver", + targetRoot: "/host", + path: "/run/nvidia/driverfoo", + expectedPath: "/run/nvidia/driverfoo", + }, + { + description: "root with trailing slash is transformed", + root: "/run/nvidia/driver/", + targetRoot: "/host", + path: "/run/nvidia/driver/lib/libcuda.so", + expectedPath: "/host/lib/libcuda.so", + }, + { + description: "root of / is transformed", + root: "/", + targetRoot: "/host", + path: "/usr/lib/libcuda.so", + expectedPath: "/host/usr/lib/libcuda.so", + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + tr := transformer{ + root: tc.root, + targetRoot: tc.targetRoot, + } + require.Equal(t, tc.expectedPath, tr.transformPath(tc.path)) + }) + } +} diff --git a/pkg/nvcdi/transform/sorter.go b/pkg/nvcdi/transform/sorter.go index 2fc53ac3e..3c834dfe2 100644 --- a/pkg/nvcdi/transform/sorter.go +++ b/pkg/nvcdi/transform/sorter.go @@ -44,7 +44,6 @@ func (d sorter) Transform(spec *specs.Spec) error { } var updatedDevices []specs.Device for _, device := range spec.Devices { - device := device if err := d.transformEdits(&device.ContainerEdits); err != nil { return err } diff --git a/tests/e2e/nvidia-cdi-refresh_test.go b/tests/e2e/nvidia-cdi-refresh_test.go index 21265dedf..0bee922ca 100644 --- a/tests/e2e/nvidia-cdi-refresh_test.go +++ b/tests/e2e/nvidia-cdi-refresh_test.go @@ -93,6 +93,24 @@ EOF fi ` + nvidiaCdiRefreshUdevRulesInstalledTemplate = ` + if [ ! -f /lib/udev/rules.d/99-nvidia-cdi-refresh.rules ]; then + echo "99-nvidia-cdi-refresh.rules is not installed" + exit 1 + fi + ` + + nvidiaCdiRefreshOrderingDropInInstalledTemplate = ` + if [ ! -f /lib/systemd/system/nvidia-cdi-refresh.service.d/10-container-engines.conf ]; then + echo "10-container-engines.conf is not installed" + exit 1 + fi + if ! systemctl show nvidia-cdi-refresh.service -p Before | grep -q docker.service; then + echo "nvidia-cdi-refresh.service is not ordered before docker.service" + exit 1 + fi + ` + nvidiaCdiRefreshFileExistsTemplate = ` # is /var/run/cdi/nvidia.yaml exists? and exit with 0 if it does not exist if [ ! -f /var/run/cdi/nvidia.yaml ]; then @@ -209,6 +227,16 @@ var _ = Describe("nvidia-cdi-refresh", Ordered, ContinueOnFailure, Label("system Expect(err).ToNot(HaveOccurred()) }) + It("should install the nvidia-cdi-refresh udev rules", func(ctx context.Context) { + _, _, err := systemdRunner.Run(nvidiaCdiRefreshUdevRulesInstalledTemplate) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should install the container engine ordering drop-in", func(ctx context.Context) { + _, _, err := systemdRunner.Run(nvidiaCdiRefreshOrderingDropInInstalledTemplate) + Expect(err).ToNot(HaveOccurred()) + }) + It("should generate the nvidia.yaml file", func(ctx context.Context) { _, _, err := systemdRunner.Run(nvidiaCdiRefreshFileExistsTemplate) Expect(err).ToNot(HaveOccurred()) @@ -258,6 +286,16 @@ var _ = Describe("nvidia-cdi-refresh", Ordered, ContinueOnFailure, Label("system Expect(err).ToNot(HaveOccurred()) }) + It("should install the nvidia-cdi-refresh udev rules", func(ctx context.Context) { + _, _, err := systemdRunner.Run(nvidiaCdiRefreshUdevRulesInstalledTemplate) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should install the container engine ordering drop-in", func(ctx context.Context) { + _, _, err := systemdRunner.Run(nvidiaCdiRefreshOrderingDropInInstalledTemplate) + Expect(err).ToNot(HaveOccurred()) + }) + It("should generate the nvidia.yaml file", func(ctx context.Context) { _, _, err := systemdRunner.Run(nvidiaCdiRefreshFileExistsTemplate) Expect(err).ToNot(HaveOccurred()) diff --git a/tests/e2e/nvidia-container-toolkit_test.go b/tests/e2e/nvidia-container-toolkit_test.go index 8ebd2af23..7bb265de8 100644 --- a/tests/e2e/nvidia-container-toolkit_test.go +++ b/tests/e2e/nvidia-container-toolkit_test.go @@ -25,7 +25,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "golang.org/x/mod/semver" + "github.com/Masterminds/semver/v3" ) // Integration tests for Docker runtime @@ -491,7 +491,7 @@ EOF`) It("should fail when using the nvidia-container-runtime-hook", Label("legacy"), func(ctx context.Context) { // Prior to Docker v29.2.0, the --gpus flag would inject the nvidia-container-runtime-hook. - if semver.Compare(dockerVersion, "v29.2.0") >= 0 { + if semver.MustParse(dockerVersion).Compare(semver.MustParse("v29.2.0")) >= 0 { Skip(fmt.Sprintf("This test requires Docker < v29.2.0. Found %s", dockerVersion)) } output, stderr, err := runner.Run("docker run --rm --runtime=runc --gpus=all firmware-test") @@ -503,7 +503,7 @@ EOF`) It("should not fail when the --gpus flag is handled as a CDI request", func(ctx context.Context) { // As of Docker v29.2.0, the --gpus flag is handled as a CDI // device request if CDI specs are available. - if semver.Compare(dockerVersion, "v29.2.0") < 0 { + if semver.MustParse(dockerVersion).Compare(semver.MustParse("v29.2.0")) < 0 { Skip(fmt.Sprintf("This test requires Docker >= v29.2.0. Found %s", dockerVersion)) } output, stderr, err := runner.Run("docker run --rm --runtime=runc --gpus=all firmware-test") diff --git a/tests/go.mod b/tests/go.mod index 4660ba76c..71b5cd6b0 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,6 +1,6 @@ module github.com/NVIDIA/nvidia-container-toolkit/tests -go 1.25.0 +go 1.26.0 require ( github.com/onsi/ginkgo/v2 v2.28.1