1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
/*
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
}
|