2023-03-13 12:33:10 +00:00
/ * *
# Copyright ( c ) 2022 , 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 config
import (
2023-08-08 11:43:45 +00:00
"errors"
"fmt"
2023-11-22 11:36:39 +00:00
"reflect"
2023-08-08 11:43:45 +00:00
"strconv"
"strings"
2023-12-01 01:10:10 +00:00
"github.com/urfave/cli/v2"
2023-03-13 12:33:10 +00:00
createdefault "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/config/create-default"
2023-08-08 11:43:45 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/config/flags"
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
2023-03-22 12:27:43 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
2023-03-13 12:33:10 +00:00
)
type command struct {
2023-03-22 12:27:43 +00:00
logger logger . Interface
2023-03-13 12:33:10 +00:00
}
2023-08-08 11:43:45 +00:00
// options stores the subcommand options
type options struct {
flags . Options
sets cli . StringSlice
}
2023-03-13 12:33:10 +00:00
// NewCommand constructs an config command with the specified logger
2023-03-22 12:27:43 +00:00
func NewCommand ( logger logger . Interface ) * cli . Command {
2023-03-13 12:33:10 +00:00
c := command {
logger : logger ,
}
return c . build ( )
}
// build
func ( m command ) build ( ) * cli . Command {
2023-08-08 11:43:45 +00:00
opts := options { }
2023-03-13 12:33:10 +00:00
// Create the 'config' command
c := cli . Command {
Name : "config" ,
Usage : "Interact with the NVIDIA Container Toolkit configuration" ,
2023-08-08 11:43:45 +00:00
Action : func ( ctx * cli . Context ) error {
return run ( ctx , & opts )
} ,
}
c . Flags = [ ] cli . Flag {
& cli . StringFlag {
Name : "config-file" ,
Aliases : [ ] string { "config" , "c" } ,
Usage : "Specify the config file to modify." ,
Value : config . GetConfigFilePath ( ) ,
Destination : & opts . Config ,
} ,
& cli . StringSliceFlag {
Name : "set" ,
Usage : "Set a config value using the pattern key=value. If value is empty, this is equivalent to specifying the same key in unset. This flag can be specified multiple times" ,
Destination : & opts . sets ,
} ,
& cli . BoolFlag {
Name : "in-place" ,
Aliases : [ ] string { "i" } ,
Usage : "Modify the config file in-place" ,
Destination : & opts . InPlace ,
} ,
& cli . StringFlag {
Name : "output" ,
Aliases : [ ] string { "o" } ,
Usage : "Specify the output file to write to; If not specified, the output is written to stdout" ,
Destination : & opts . Output ,
} ,
2023-03-13 12:33:10 +00:00
}
c . Subcommands = [ ] * cli . Command {
createdefault . NewCommand ( m . logger ) ,
}
return & c
}
2023-08-08 11:43:45 +00:00
func run ( c * cli . Context , opts * options ) error {
cfgToml , err := config . New (
config . WithConfigFile ( opts . Config ) ,
)
if err != nil {
return fmt . Errorf ( "unable to create config: %v" , err )
}
for _ , set := range opts . sets . Value ( ) {
2023-11-22 11:36:39 +00:00
key , value , err := setFlagToKeyValue ( set )
2023-08-08 11:43:45 +00:00
if err != nil {
return fmt . Errorf ( "invalid --set option %v: %w" , set , err )
}
2024-02-14 14:10:31 +00:00
if value == nil {
_ = cfgToml . Delete ( key )
} else {
cfgToml . Set ( key , value )
}
2023-08-08 11:43:45 +00:00
}
2023-08-23 07:13:38 +00:00
if err := opts . EnsureOutputFolder ( ) ; err != nil {
return fmt . Errorf ( "failed to create output directory: %v" , err )
}
output , err := opts . CreateOutput ( )
if err != nil {
return fmt . Errorf ( "failed to open output file: %v" , err )
}
defer output . Close ( )
2023-08-25 14:48:11 +00:00
if _ , err := cfgToml . Save ( output ) ; err != nil {
return fmt . Errorf ( "failed to save config: %v" , err )
2023-08-23 07:13:38 +00:00
}
2023-08-25 14:48:11 +00:00
2023-08-08 11:43:45 +00:00
return nil
}
var errInvalidConfigOption = errors . New ( "invalid config option" )
2023-11-22 11:36:39 +00:00
var errUndefinedField = errors . New ( "undefined field" )
2023-08-08 11:43:45 +00:00
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.
2023-11-22 11:36:39 +00:00
func setFlagToKeyValue ( setFlag string ) ( string , interface { } , error ) {
2023-08-08 11:43:45 +00:00
setParts := strings . SplitN ( setFlag , "=" , 2 )
key := setParts [ 0 ]
2023-11-22 11:36:39 +00:00
field , err := getField ( key )
if err != nil {
return key , nil , fmt . Errorf ( "%w: %w" , errInvalidConfigOption , err )
2023-08-08 11:43:45 +00:00
}
2023-11-22 11:36:39 +00:00
kind := field . Kind ( )
2023-08-08 11:43:45 +00:00
if len ( setParts ) != 2 {
2024-02-14 14:10:31 +00:00
if kind == reflect . Bool || ( kind == reflect . Pointer && field . Elem ( ) . Kind ( ) == reflect . Bool ) {
2023-11-22 11:36:39 +00:00
return key , true , nil
}
2023-08-08 11:43:45 +00:00
return key , nil , fmt . Errorf ( "%w: expected key=value; got %v" , errInvalidFormat , setFlag )
}
value := setParts [ 1 ]
2024-02-14 14:10:31 +00:00
if kind == reflect . Pointer && value != "nil" {
kind = field . Elem ( ) . Kind ( )
}
2023-11-22 11:36:39 +00:00
switch kind {
2024-02-14 14:10:31 +00:00
case reflect . Pointer :
return key , nil , nil
2023-11-22 11:36:39 +00:00
case reflect . Bool :
2023-08-08 11:43:45 +00:00
b , err := strconv . ParseBool ( value )
if err != nil {
return key , value , fmt . Errorf ( "%w: %w" , errInvalidFormat , err )
}
2024-02-14 14:10:31 +00:00
return key , b , nil
2023-11-22 11:36:39 +00:00
case reflect . String :
2023-08-08 11:43:45 +00:00
return key , value , nil
2023-11-22 11:36:39 +00:00
case reflect . Slice :
valueParts := strings . Split ( value , "," )
switch field . Elem ( ) . Kind ( ) {
case reflect . String :
return key , valueParts , nil
case reflect . Int :
var output [ ] int64
for _ , v := range valueParts {
vi , err := strconv . ParseInt ( v , 10 , 0 )
if err != nil {
return key , nil , fmt . Errorf ( "%w: %w" , errInvalidFormat , err )
}
output = append ( output , vi )
}
return key , output , nil
}
}
return key , nil , fmt . Errorf ( "unsupported type for %v (%v)" , setParts , kind )
}
func getField ( key string ) ( reflect . Type , error ) {
s , err := getStruct ( reflect . TypeOf ( config . Config { } ) , strings . Split ( key , "." ) ... )
if err != nil {
return nil , err
}
return s . Type , err
}
func getStruct ( current reflect . Type , paths ... string ) ( reflect . StructField , error ) {
if len ( paths ) < 1 {
return reflect . StructField { } , fmt . Errorf ( "%w: no fields selected" , errUndefinedField )
}
tomlField := paths [ 0 ]
for i := 0 ; i < current . NumField ( ) ; i ++ {
f := current . Field ( i )
v , ok := f . Tag . Lookup ( "toml" )
if ! ok {
continue
}
2024-02-14 14:10:31 +00:00
if strings . SplitN ( v , "," , 2 ) [ 0 ] != tomlField {
2023-11-22 11:36:39 +00:00
continue
}
if len ( paths ) == 1 {
return f , nil
}
return getStruct ( f . Type , paths [ 1 : ] ... )
2023-08-08 11:43:45 +00:00
}
2023-11-22 11:36:39 +00:00
return reflect . StructField { } , fmt . Errorf ( "%w: %q" , errUndefinedField , tomlField )
2023-08-08 11:43:45 +00:00
}