/* Copyright 2018 The Kubernetes 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 cli import ( "errors" "flag" "fmt" "io" "io/ioutil" "os" "sigs.k8s.io/structured-merge-diff/v4/typed" ) var ( ErrTooManyOperations = errors.New("exactly one of --merge, --compare, --validate or --fieldset must be provided") ErrNeedTwoArgs = errors.New("--merge and --compare require both --lhs and --rhs") ) type Options struct { schemaPath string typeName string output string // options determining the operation to perform listTypes bool validatePath string merge bool compare bool fieldset string // arguments for merge or compare lhsPath string rhsPath string } func (o *Options) AddFlags(fs *flag.FlagSet) { fs.StringVar(&o.schemaPath, "schema", "", "Path to the schema file for this operation. Required.") fs.StringVar(&o.typeName, "type-name", "", "Name of type in the schema to use. If empty, the first type in the schema will be used.") fs.StringVar(&o.output, "output", "-", "Output location (if the command has output). '-' means stdout.") // The three supported operations. We could make these into subcommands // and that would probably make more sense, but this is easy and this // binary is mostly just to enable a little exploration, so this is // fine for now. fs.BoolVar(&o.listTypes, "list-types", false, "List all the types in the schema and exit.") fs.StringVar(&o.validatePath, "validate", "", "Path to a file to perform a validation operation on.") fs.BoolVar(&o.merge, "merge", false, "Perform a merge operation between --lhs and --rhs") fs.BoolVar(&o.compare, "compare", false, "Perform a compare operation between --lhs and --rhs") fs.StringVar(&o.fieldset, "fieldset", "", "Path to a file for which we should build a fieldset.") fs.StringVar(&o.lhsPath, "lhs", "", "Path to a file containing the left hand side of the operation") fs.StringVar(&o.rhsPath, "rhs", "", "Path to a file containing the right hand side of the operation") } // resolve turns options in to an operation that can be executed. func (o *Options) Resolve() (Operation, error) { var base operationBase if o.schemaPath == "" { return nil, errors.New("a schema is required") } b, err := ioutil.ReadFile(o.schemaPath) if err != nil { return nil, fmt.Errorf("unable to read schema %q: %v", o.schemaPath, err) } base.parser, err = typed.NewParser(typed.YAMLObject(b)) if err != nil { return nil, fmt.Errorf("schema %q has errors:\n%v", o.schemaPath, err) } if o.typeName == "" { types := base.parser.Schema.Types if len(types) == 0 { return nil, errors.New("no types were given in the schema") } base.typeName = types[0].Name } else { base.typeName = o.typeName } // Count how many operations were requested c := map[bool]int{true: 1} count := c[o.merge] + c[o.compare] + c[o.validatePath != ""] + c[o.listTypes] + c[o.fieldset != ""] if count > 1 { return nil, ErrTooManyOperations } switch { case o.listTypes: return listTypes{base}, nil case o.validatePath != "": return validation{base, o.validatePath}, nil case o.merge: if o.lhsPath == "" || o.rhsPath == "" { return nil, ErrNeedTwoArgs } return merge{base, o.lhsPath, o.rhsPath}, nil case o.compare: if o.lhsPath == "" || o.rhsPath == "" { return nil, ErrNeedTwoArgs } return compare{base, o.lhsPath, o.rhsPath}, nil case o.fieldset != "": return fieldset{base, o.fieldset}, nil } return nil, errors.New("no operation requested") } func (o *Options) OpenOutput() (io.WriteCloser, error) { if o.output == "-" { return os.Stdout, nil } f, err := os.Create(o.output) if err != nil { return nil, fmt.Errorf("unable to open %q for writing: %v", o.output, err) } return f, nil }