diff --git a/cmd/gpuop-cfg/internal/images/clusterpolicy.go b/cmd/gpuop-cfg/internal/images/clusterpolicy.go new file mode 100644 index 000000000..70649059b --- /dev/null +++ b/cmd/gpuop-cfg/internal/images/clusterpolicy.go @@ -0,0 +1,60 @@ +/** +# 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 images + +import ( + "fmt" + + v1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1" +) + +type OperandImage struct { + Name string + Image string +} + +func FromClusterPolicy(spec *v1.ClusterPolicySpec) ([]OperandImage, error) { + type operand struct { + name string + spec interface{} + } + + operands := []operand{ + {"Driver", &spec.Driver}, + {"Toolkit", &spec.Toolkit}, + {"DevicePlugin", &spec.DevicePlugin}, + {"DCGMExporter", &spec.DCGMExporter}, + {"DCGM", &spec.DCGM}, + {"GPUFeatureDiscovery", &spec.GPUFeatureDiscovery}, + {"MIGManager", &spec.MIGManager}, + {"GPUDirectStorage", spec.GPUDirectStorage}, + {"VFIOManager", &spec.VFIOManager}, + {"SandboxDevicePlugin", &spec.SandboxDevicePlugin}, + {"VGPUDeviceManager", &spec.VGPUDeviceManager}, + } + + var images []OperandImage + for _, op := range operands { + path, err := v1.ImagePath(op.spec) + if err != nil { + return nil, fmt.Errorf("failed to construct image path for %s: %v", op.name, err) + } + images = append(images, OperandImage{Name: op.name, Image: path}) + } + + return images, nil +} diff --git a/cmd/gpuop-cfg/internal/images/csv.go b/cmd/gpuop-cfg/internal/images/csv.go new file mode 100644 index 000000000..76deec93c --- /dev/null +++ b/cmd/gpuop-cfg/internal/images/csv.go @@ -0,0 +1,44 @@ +/** +# 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 images + +import ( + "strings" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" +) + +func FromCSV(csv *v1alpha1.ClusterServiceVersion) []string { + var images []string + + for _, image := range csv.Spec.RelatedImages { + images = append(images, image.Image) + } + + deployment := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0] + ctr := deployment.Spec.Template.Spec.Containers[0] + images = append(images, ctr.Image) + + for _, env := range ctr.Env { + if !strings.HasSuffix(env.Name, "_IMAGE") { + continue + } + images = append(images, env.Value) + } + + return images +} diff --git a/cmd/gpuop-cfg/list-images/list-images.go b/cmd/gpuop-cfg/list-images/list-images.go new file mode 100644 index 000000000..bccb444e3 --- /dev/null +++ b/cmd/gpuop-cfg/list-images/list-images.go @@ -0,0 +1,134 @@ +/** +# 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 listimages + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/sirupsen/logrus" + cli "github.com/urfave/cli/v3" + "sigs.k8s.io/yaml" + + v1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1" + "github.com/NVIDIA/gpu-operator/cmd/gpuop-cfg/internal/images" +) + +type options struct { + input string +} + +func NewCommand(_ *logrus.Logger) *cli.Command { + listImages := cli.Command{ + Name: "list-images", + Usage: "List container images referenced in GPU Operator configuration files", + } + + listImages.Commands = []*cli.Command{ + buildCSV(), + buildClusterPolicy(), + } + + return &listImages +} + +func buildCSV() *cli.Command { + opts := options{} + + c := cli.Command{ + Name: "csv", + Usage: "List images from a ClusterServiceVersion manifest", + Action: func(ctx context.Context, cmd *cli.Command) error { + contents, err := getContents(opts.input) + if err != nil { + return fmt.Errorf("failed to read file: %v", err) + } + + spec := &v1alpha1.ClusterServiceVersion{} + if err := yaml.Unmarshal(contents, spec); err != nil { + return fmt.Errorf("failed to unmarshal csv: %v", err) + } + + for _, image := range images.FromCSV(spec) { + fmt.Println(image) + } + return nil + }, + } + + c.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "input", + Usage: "Specify the input file. If this is '-' the file is read from STDIN", + Value: "-", + Destination: &opts.input, + }, + } + + return &c +} + +func buildClusterPolicy() *cli.Command { + opts := options{} + + c := cli.Command{ + Name: "clusterpolicy", + Usage: "List images from a ClusterPolicy manifest", + Action: func(ctx context.Context, cmd *cli.Command) error { + contents, err := getContents(opts.input) + if err != nil { + return fmt.Errorf("failed to read file: %v", err) + } + + spec := &v1.ClusterPolicy{} + if err := yaml.Unmarshal(contents, spec); err != nil { + return fmt.Errorf("failed to unmarshal clusterpolicy: %v", err) + } + + operandImages, err := images.FromClusterPolicy(&spec.Spec) + if err != nil { + return err + } + + for _, op := range operandImages { + fmt.Println(op.Image) + } + return nil + }, + } + + c.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "input", + Usage: "Specify the input file. If this is '-' the file is read from STDIN", + Value: "-", + Destination: &opts.input, + }, + } + + return &c +} + +func getContents(input string) ([]byte, error) { + if input == "-" { + return io.ReadAll(os.Stdin) + } + return os.ReadFile(input) +} diff --git a/cmd/gpuop-cfg/main.go b/cmd/gpuop-cfg/main.go index 8b69c5000..2c989d148 100644 --- a/cmd/gpuop-cfg/main.go +++ b/cmd/gpuop-cfg/main.go @@ -23,6 +23,7 @@ import ( log "github.com/sirupsen/logrus" cli "github.com/urfave/cli/v3" + listimages "github.com/NVIDIA/gpu-operator/cmd/gpuop-cfg/list-images" "github.com/NVIDIA/gpu-operator/cmd/gpuop-cfg/validate" ) @@ -66,6 +67,7 @@ func main() { // Define the subcommands c.Commands = []*cli.Command{ validate.NewCommand(logger), + listimages.NewCommand(logger), } err := c.Run(context.Background(), os.Args) diff --git a/cmd/gpuop-cfg/validate/clusterpolicy/images.go b/cmd/gpuop-cfg/validate/clusterpolicy/images.go index f91c2461d..69520d7bd 100644 --- a/cmd/gpuop-cfg/validate/clusterpolicy/images.go +++ b/cmd/gpuop-cfg/validate/clusterpolicy/images.go @@ -24,132 +24,25 @@ import ( "github.com/regclient/regclient/types/ref" v1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1" + "github.com/NVIDIA/gpu-operator/cmd/gpuop-cfg/internal/images" ) func validateImages(ctx context.Context, spec *v1.ClusterPolicySpec) error { - // Driver - path, err := v1.ImagePath(&spec.Driver) + operandImages, err := images.FromClusterPolicy(spec) if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) + return err } - // For driver, we must append the os-tag - path += "-ubuntu22.04" - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // Toolkit - path, err = v1.ImagePath(&spec.Toolkit) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // Device Plugin - path, err = v1.ImagePath(&spec.DevicePlugin) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // DCGMExporter - path, err = v1.ImagePath(&spec.DCGMExporter) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // DCGM - path, err = v1.ImagePath(&spec.DCGM) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // GPUFeatureDiscovery - path, err = v1.ImagePath(&spec.GPUFeatureDiscovery) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // MIGManager - path, err = v1.ImagePath(&spec.MIGManager) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // GPUDirectStorage - path, err = v1.ImagePath(spec.GPUDirectStorage) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - // For GDS driver, we must append the os-tag - path += "-ubuntu22.04" - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // VFIOManager - path, err = v1.ImagePath(&spec.VFIOManager) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // SandboxDevicePlugin - path, err = v1.ImagePath(&spec.SandboxDevicePlugin) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) - } - - // VGPUDeviceManager - path, err = v1.ImagePath(&spec.VGPUDeviceManager) - if err != nil { - return fmt.Errorf("failed to construct the image path: %v", err) - } - - err = validateImage(ctx, path) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", path, err) + for _, op := range operandImages { + path := op.Image + // For Driver and GPUDirectStorage, we must append the os-tag + if op.Name == "Driver" || op.Name == "GPUDirectStorage" { + path += "-ubuntu22.04" + } + err = validateImage(ctx, path) + if err != nil { + return fmt.Errorf("failed to validate image %s: %v", path, err) + } } return nil diff --git a/cmd/gpuop-cfg/validate/csv/images.go b/cmd/gpuop-cfg/validate/csv/images.go index 3d8e4f532..833e8c27f 100644 --- a/cmd/gpuop-cfg/validate/csv/images.go +++ b/cmd/gpuop-cfg/validate/csv/images.go @@ -19,41 +19,21 @@ package csv import ( "context" "fmt" - "strings" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/regclient/regclient" "github.com/regclient/regclient/types/ref" + + "github.com/NVIDIA/gpu-operator/cmd/gpuop-cfg/internal/images" ) func validateImages(ctx context.Context, csv *v1alpha1.ClusterServiceVersion) error { - // validate all 'relatedImages' - images := csv.Spec.RelatedImages - for _, image := range images { - err := validateImage(ctx, image.Image) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", image.Name, err) - } - } - - // get the gpu-operator deployment spec - deployment := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0] - ctr := deployment.Spec.Template.Spec.Containers[0] + imagePaths := images.FromCSV(csv) - // validate the gpu-operator image - err := validateImage(ctx, ctr.Image) - if err != nil { - return fmt.Errorf("failed to validate image %s: %v", ctr.Image, err) - } - - // validate all operand images configured as env vars - for _, env := range ctr.Env { - if !strings.HasSuffix(env.Name, "_IMAGE") { - continue - } - err = validateImage(ctx, env.Value) + for _, path := range imagePaths { + err := validateImage(ctx, path) if err != nil { - return fmt.Errorf("failed to validate image %s: %v", env.Name, err) + return fmt.Errorf("failed to validate image %s: %v", path, err) } }