diff --git a/cmd/cdi/cmd/cdi-api.go b/cmd/cdi/cmd/cdi-api.go index 199f6e12..20790266 100644 --- a/cmd/cdi/cmd/cdi-api.go +++ b/cmd/cdi/cmd/cdi-api.go @@ -23,7 +23,7 @@ import ( "strings" oci "github.com/opencontainers/runtime-spec/specs-go" - gen "github.com/opencontainers/runtime-tools/generate" + "tags.cncf.io/container-device-interface/internal/ociedit" "tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/parser" ) @@ -227,12 +227,12 @@ func collectCDIDevicesFromOCISpec(spec *oci.Spec) []string { } devices := spec.Linux.Devices - g := gen.NewFromSpec(spec) - g.ClearLinuxDevices() + editor := ociedit.NewSpecEditor(spec) + editor.ClearLinuxDevices() for _, d := range devices { if !parser.IsQualifiedName(d.Path) { - g.AddDevice(d) + editor.AddDevice(d) continue } cdiDevs = append(cdiDevs, d.Path) diff --git a/cmd/cdi/cmd/cdi-api_test.go b/cmd/cdi/cmd/cdi-api_test.go new file mode 100644 index 00000000..af95514e --- /dev/null +++ b/cmd/cdi/cmd/cdi-api_test.go @@ -0,0 +1,78 @@ +/* + Copyright 2026 The CDI Authors + + 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 cmd + +import ( + "testing" + + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/require" +) + +func TestCollectCDIDevicesFromOCISpec(t *testing.T) { + for _, tc := range []struct { + name string + spec *oci.Spec + devices []string + linuxDevs []oci.LinuxDevice + emptyDevice bool + }{ + { + name: "collect CDI devices and keep non-CDI devices", + spec: &oci.Spec{ + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + {Path: "vendor.com/device=dev0"}, + {Path: "/dev/null", Type: "c", Major: 1, Minor: 3}, + {Path: "/dev/null", Type: "c", Major: 1, Minor: 5}, + {Path: "vendor.com/device=dev1"}, + }, + }, + }, + devices: []string{ + "vendor.com/device=dev0", + "vendor.com/device=dev1", + }, + linuxDevs: []oci.LinuxDevice{ + {Path: "/dev/null", Type: "c", Major: 1, Minor: 5}, + }, + }, + { + name: "all CDI devices leave an empty device slice", + spec: &oci.Spec{ + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + {Path: "vendor.com/device=dev0"}, + }, + }, + }, + devices: []string{"vendor.com/device=dev0"}, + linuxDevs: []oci.LinuxDevice{}, + emptyDevice: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + devices := collectCDIDevicesFromOCISpec(tc.spec) + + require.Equal(t, tc.devices, devices) + require.Equal(t, tc.linuxDevs, tc.spec.Linux.Devices) + if tc.emptyDevice && tc.spec.Linux.Devices == nil { + require.NotNil(t, tc.spec.Linux.Devices) + } + }) + } +} diff --git a/cmd/cdi/go.mod b/cmd/cdi/go.mod index 4994a31c..cc5a317e 100644 --- a/cmd/cdi/go.mod +++ b/cmd/cdi/go.mod @@ -5,8 +5,8 @@ go 1.21 require ( github.com/fsnotify/fsnotify v1.5.1 github.com/opencontainers/runtime-spec v1.3.0 - github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 github.com/spf13/cobra v1.6.0 + github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.4.0 tags.cncf.io/container-device-interface v1.1.0 @@ -15,8 +15,9 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/moby/sys/capability v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/cmd/cdi/go.sum b/cmd/cdi/go.sum index 5d7a9d7e..9c2ce7cf 100644 --- a/cmd/cdi/go.sum +++ b/cmd/cdi/go.sum @@ -1,5 +1,3 @@ -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -8,25 +6,13 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= -github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 h1:tAKu3NkKWZYpqBSOJKwTxT1wIGueiF7gcmcNgr5pNTY= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= -github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -48,6 +34,7 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/cmd/validate/go.sum b/cmd/validate/go.sum index 39f8589d..99998750 100644 --- a/cmd/validate/go.sum +++ b/cmd/validate/go.sum @@ -5,12 +5,8 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= -github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 h1:tAKu3NkKWZYpqBSOJKwTxT1wIGueiF7gcmcNgr5pNTY= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/go.mod b/go.mod index 9f1d07c6..8e7cc556 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.21 require ( github.com/fsnotify/fsnotify v1.5.1 github.com/opencontainers/runtime-spec v1.3.0 - github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 github.com/stretchr/testify v1.7.0 golang.org/x/sys v0.19.0 gopkg.in/yaml.v3 v3.0.1 @@ -15,8 +14,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/moby/sys/capability v0.4.0 // indirect - github.com/opencontainers/selinux v1.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/mod v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index 9c10a46a..ce65d94d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,34 +5,15 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= -github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 h1:tAKu3NkKWZYpqBSOJKwTxT1wIGueiF7gcmcNgr5pNTY= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= -github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/ociedit/spec_editor.go b/internal/ociedit/spec_editor.go new file mode 100644 index 00000000..47fad61f --- /dev/null +++ b/internal/ociedit/spec_editor.go @@ -0,0 +1,256 @@ +/* + Copyright 2026 The CDI Authors + + 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 ociedit + +import ( + "slices" + "strings" + + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// SpecEditor is the internal boundary between CDI edits and OCI spec mutation. +// It deliberately models only the operations CDI needs, so a future external +// generator can be wired in without leaking that dependency elsewhere. +type SpecEditor interface { + AddMultipleProcessEnv([]string) + RemoveDevice(string) + AddDevice(oci.LinuxDevice) + AddLinuxResourcesDevice(bool, string, *int64, *int64, string) + SetLinuxNetDevice(string, *oci.LinuxNetDevice) + RemoveMount(string) + AddMount(oci.Mount) + Mounts() []oci.Mount + SetMounts([]oci.Mount) + AddPreStartHook(oci.Hook) + AddPostStartHook(oci.Hook) + AddPostStopHook(oci.Hook) + AddCreateRuntimeHook(oci.Hook) + AddCreateContainerHook(oci.Hook) + AddStartContainerHook(oci.Hook) + SetLinuxIntelRdt(*oci.LinuxIntelRdt) + AddProcessAdditionalGID(uint32) + ClearLinuxDevices() +} + +// NewSpecEditor returns CDI's native OCI spec editor. +func NewSpecEditor(spec *oci.Spec) SpecEditor { + envCache := map[string]int{} + if spec != nil && spec.Process != nil { + envCache = createEnvCacheMap(spec.Process.Env) + } + return &nativeSpecEditor{ + spec: spec, + envMap: envCache, + } +} + +type nativeSpecEditor struct { + spec *oci.Spec + envMap map[string]int +} + +func createEnvCacheMap(env []string) map[string]int { + envMap := make(map[string]int, len(env)) + for i, val := range env { + val, _, _ = strings.Cut(val, "=") + envMap[val] = i + } + return envMap +} + +func (e *nativeSpecEditor) initConfig() { + if e.spec == nil { + e.spec = &oci.Spec{} + } +} + +func (e *nativeSpecEditor) initProcess() { + e.initConfig() + if e.spec.Process == nil { + e.spec.Process = &oci.Process{} + } +} + +func (e *nativeSpecEditor) initHooks() { + e.initConfig() + if e.spec.Hooks == nil { + e.spec.Hooks = &oci.Hooks{} + } +} + +func (e *nativeSpecEditor) initLinux() { + e.initConfig() + if e.spec.Linux == nil { + e.spec.Linux = &oci.Linux{} + } +} + +func (e *nativeSpecEditor) initLinuxResources() { + e.initLinux() + if e.spec.Linux.Resources == nil { + e.spec.Linux.Resources = &oci.LinuxResources{} + } +} + +func (e *nativeSpecEditor) initLinuxNetDevices() { + e.initLinux() + if e.spec.Linux.NetDevices == nil { + e.spec.Linux.NetDevices = map[string]oci.LinuxNetDevice{} + } +} + +func (e *nativeSpecEditor) AddMultipleProcessEnv(envs []string) { + e.initProcess() + + for _, val := range envs { + split := strings.SplitN(val, "=", 2) + e.addEnv(val, split[0]) + } +} + +func (e *nativeSpecEditor) addEnv(env, key string) { + if idx, ok := e.envMap[key]; ok { + e.spec.Process.Env[idx] = env + return + } + + e.spec.Process.Env = append(e.spec.Process.Env, env) + e.envMap[key] = len(e.spec.Process.Env) - 1 +} + +func (e *nativeSpecEditor) RemoveDevice(path string) { + if e.spec == nil || e.spec.Linux == nil || e.spec.Linux.Devices == nil { + return + } + + for i, device := range e.spec.Linux.Devices { + if device.Path == path { + e.spec.Linux.Devices = append(e.spec.Linux.Devices[:i], e.spec.Linux.Devices[i+1:]...) + return + } + } +} + +func (e *nativeSpecEditor) AddDevice(device oci.LinuxDevice) { + e.initLinux() + + for i, dev := range e.spec.Linux.Devices { + if dev.Path == device.Path { + e.spec.Linux.Devices[i] = device + return + } + } + + e.spec.Linux.Devices = append(e.spec.Linux.Devices, device) +} + +func (e *nativeSpecEditor) AddLinuxResourcesDevice(allow bool, devType string, major, minor *int64, access string) { + e.initLinuxResources() + e.spec.Linux.Resources.Devices = append(e.spec.Linux.Resources.Devices, oci.LinuxDeviceCgroup{ + Allow: allow, + Type: devType, + Major: major, + Minor: minor, + Access: access, + }) +} + +func (e *nativeSpecEditor) SetLinuxNetDevice(hostIf string, netDev *oci.LinuxNetDevice) { + if netDev == nil { + return + } + + e.initLinuxNetDevices() + e.spec.Linux.NetDevices[hostIf] = *netDev +} + +func (e *nativeSpecEditor) RemoveMount(dest string) { + e.initConfig() + + for i, mount := range e.spec.Mounts { + if mount.Destination == dest { + e.spec.Mounts = append(e.spec.Mounts[:i], e.spec.Mounts[i+1:]...) + return + } + } +} + +func (e *nativeSpecEditor) AddMount(mnt oci.Mount) { + e.initConfig() + e.spec.Mounts = append(e.spec.Mounts, mnt) +} + +func (e *nativeSpecEditor) Mounts() []oci.Mount { + e.initConfig() + return e.spec.Mounts +} + +func (e *nativeSpecEditor) SetMounts(mounts []oci.Mount) { + e.initConfig() + e.spec.Mounts = mounts +} + +func (e *nativeSpecEditor) AddPreStartHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.Prestart = append(e.spec.Hooks.Prestart, hook) //nolint:staticcheck // CDI still supports OCI prestart hooks. +} + +func (e *nativeSpecEditor) AddPostStartHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.Poststart = append(e.spec.Hooks.Poststart, hook) +} + +func (e *nativeSpecEditor) AddPostStopHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.Poststop = append(e.spec.Hooks.Poststop, hook) +} + +func (e *nativeSpecEditor) AddCreateRuntimeHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.CreateRuntime = append(e.spec.Hooks.CreateRuntime, hook) +} + +func (e *nativeSpecEditor) AddCreateContainerHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.CreateContainer = append(e.spec.Hooks.CreateContainer, hook) +} + +func (e *nativeSpecEditor) AddStartContainerHook(hook oci.Hook) { + e.initHooks() + e.spec.Hooks.StartContainer = append(e.spec.Hooks.StartContainer, hook) +} + +func (e *nativeSpecEditor) SetLinuxIntelRdt(rdt *oci.LinuxIntelRdt) { + e.initLinux() + e.spec.Linux.IntelRdt = rdt +} + +func (e *nativeSpecEditor) AddProcessAdditionalGID(gid uint32) { + e.initProcess() + if slices.Contains(e.spec.Process.User.AdditionalGids, gid) { + return + } + e.spec.Process.User.AdditionalGids = append(e.spec.Process.User.AdditionalGids, gid) +} + +func (e *nativeSpecEditor) ClearLinuxDevices() { + if e.spec == nil || e.spec.Linux == nil || e.spec.Linux.Devices == nil { + return + } + e.spec.Linux.Devices = []oci.LinuxDevice{} +} diff --git a/pkg/cdi/container-edits.go b/pkg/cdi/container-edits.go index 387219f5..3f368aa8 100644 --- a/pkg/cdi/container-edits.go +++ b/pkg/cdi/container-edits.go @@ -25,7 +25,7 @@ import ( "strings" oci "github.com/opencontainers/runtime-spec/specs-go" - ocigen "github.com/opencontainers/runtime-tools/generate" + "tags.cncf.io/container-device-interface/internal/ociedit" cdi "tags.cncf.io/container-device-interface/specs-go" ) @@ -80,9 +80,9 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { return nil } - specgen := ocigen.NewFromSpec(spec) + editor := ociedit.NewSpecEditor(spec) if len(e.Env) > 0 { - specgen.AddMultipleProcessEnv(e.Env) + editor.AddMultipleProcessEnv(e.Env) } for _, d := range e.DeviceNodes { @@ -104,8 +104,8 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { } } - specgen.RemoveDevice(dev.Path) - specgen.AddDevice(dev) + editor.RemoveDevice(dev.Path) + editor.AddDevice(dev) if dev.Type == "b" || dev.Type == "c" { access := d.Permissions @@ -115,15 +115,13 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { case NoPermissions: access = "" } - specgen.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, access) + editor.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, access) } } if len(e.NetDevices) > 0 { - // specgen is currently missing functionality to set Linux NetDevices, - // so we use a locally rolled function for now. for _, dev := range e.NetDevices { - specgenAddLinuxNetDevice(&specgen, dev.HostInterfaceName, (&LinuxNetDevice{dev}).toOCI()) + editor.SetLinuxNetDevice(dev.HostInterfaceName, (&LinuxNetDevice{dev}).toOCI()) } } @@ -131,76 +129,51 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { for _, m := range e.Mounts { mnt := &Mount{m} - specgen.RemoveMount(m.ContainerPath) + editor.RemoveMount(m.ContainerPath) if !specHasUserNamespace(spec) { - specgen.AddMount(mnt.toOCI()) + editor.AddMount(mnt.toOCI()) } else { - specgen.AddMount(mnt.toOCI(withIDMapForBindMount())) + editor.AddMount(mnt.toOCI(withIDMapForBindMount())) } } - sortMounts(&specgen) + sortMounts(editor) } for _, h := range e.Hooks { ociHook := (&Hook{h}).toOCI() switch h.HookName { case PrestartHook: - specgen.AddPreStartHook(ociHook) + editor.AddPreStartHook(ociHook) case PoststartHook: - specgen.AddPostStartHook(ociHook) + editor.AddPostStartHook(ociHook) case PoststopHook: - specgen.AddPostStopHook(ociHook) - // TODO: Maybe runtime-tools/generate should be updated with these... + editor.AddPostStopHook(ociHook) case CreateRuntimeHook: - ensureOCIHooks(spec) - spec.Hooks.CreateRuntime = append(spec.Hooks.CreateRuntime, ociHook) + editor.AddCreateRuntimeHook(ociHook) case CreateContainerHook: - ensureOCIHooks(spec) - spec.Hooks.CreateContainer = append(spec.Hooks.CreateContainer, ociHook) + editor.AddCreateContainerHook(ociHook) case StartContainerHook: - ensureOCIHooks(spec) - spec.Hooks.StartContainer = append(spec.Hooks.StartContainer, ociHook) + editor.AddStartContainerHook(ociHook) default: return fmt.Errorf("unknown hook name %q", h.HookName) } } if e.IntelRdt != nil { - // The specgen is missing functionality to set all parameters so we - // just piggy-back on it to initialize all structs and the copy over. - specgen.SetLinuxIntelRdtClosID(e.IntelRdt.ClosID) - spec.Linux.IntelRdt = (&IntelRdt{e.IntelRdt}).toOCI() + editor.SetLinuxIntelRdt((&IntelRdt{e.IntelRdt}).toOCI()) } for _, additionalGID := range e.AdditionalGIDs { if additionalGID == 0 { continue } - specgen.AddProcessAdditionalGid(additionalGID) + editor.AddProcessAdditionalGID(additionalGID) } return nil } -func specgenAddLinuxNetDevice(specgen *ocigen.Generator, hostIf string, netDev *oci.LinuxNetDevice) { - if specgen == nil || netDev == nil { - return - } - ensureLinuxNetDevices(specgen.Config) - specgen.Config.Linux.NetDevices[hostIf] = *netDev -} - -// Ensure OCI Spec Linux NetDevices map is not nil. -func ensureLinuxNetDevices(spec *oci.Spec) { - if spec.Linux == nil { - spec.Linux = &oci.Linux{} - } - if spec.Linux.NetDevices == nil { - spec.Linux.NetDevices = map[string]oci.LinuxNetDevice{} - } -} - // Validate container edits. func (e *ContainerEdits) Validate() error { if e == nil || e.ContainerEdits == nil { @@ -437,19 +410,11 @@ func (i *IntelRdt) Validate() error { return nil } -// Ensure OCI Spec hooks are not nil so we can add hooks. -func ensureOCIHooks(spec *oci.Spec) { - if spec.Hooks == nil { - spec.Hooks = &oci.Hooks{} - } -} - // sortMounts sorts the mounts in the given OCI Spec. -func sortMounts(specgen *ocigen.Generator) { - mounts := specgen.Mounts() - specgen.ClearMounts() +func sortMounts(editor ociedit.SpecEditor) { + mounts := editor.Mounts() sort.Stable(orderedMounts(mounts)) - specgen.Config.Mounts = mounts + editor.SetMounts(mounts) } // orderedMounts defines how to sort an OCI Spec Mount slice. diff --git a/pkg/cdi/container-edits_test.go b/pkg/cdi/container-edits_test.go index 2240f3d1..5c20dbee 100644 --- a/pkg/cdi/container-edits_test.go +++ b/pkg/cdi/container-edits_test.go @@ -397,6 +397,47 @@ func TestApplyContainerEdits(t *testing.T) { }, }, }, + { + name: "empty spec, duplicate env vars use last value", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + Env: []string{ + "FOO=old", + "FOO=new", + }, + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "FOO=new", + }, + }, + }, + }, + { + name: "non-empty spec, env var with existing key is replaced", + spec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "FOO=old", + "BAR=old", + }, + }, + }, + edits: &cdi.ContainerEdits{ + Env: []string{ + "FOO=new", + }, + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "FOO=new", + "BAR=old", + }, + }, + }, + }, { name: "empty spec, device", spec: &oci.Spec{}, @@ -788,6 +829,26 @@ func TestApplyContainerEdits(t *testing.T) { }, }, }, + { + name: "existing additional GIDs are not duplicated", + spec: &oci.Spec{ + Process: &oci.Process{ + User: oci.User{ + AdditionalGids: []uint32{4}, + }, + }, + }, + edits: &cdi.ContainerEdits{ + AdditionalGIDs: []uint32{4, 5}, + }, + result: &oci.Spec{ + Process: &oci.Process{ + User: oci.User{ + AdditionalGids: []uint32{4, 5}, + }, + }, + }, + }, { name: "duplicate GIDs are ignored", spec: &oci.Spec{}, diff --git a/schema/go.mod b/schema/go.mod index e5370e9d..68041f8b 100644 --- a/schema/go.mod +++ b/schema/go.mod @@ -13,9 +13,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/moby/sys/capability v0.4.0 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect - github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/schema/go.sum b/schema/go.sum index d28c5582..8c58ee6f 100644 --- a/schema/go.sum +++ b/schema/go.sum @@ -1,5 +1,3 @@ -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,22 +5,10 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= -github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 h1:tAKu3NkKWZYpqBSOJKwTxT1wIGueiF7gcmcNgr5pNTY= -github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= -github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=