github.com/splunk/dan1-qbec@v0.7.3/internal/commands/common.go (about) 1 /* 2 Copyright 2019 Splunk Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package commands contains the implementation of all qbec commands. 18 package commands 19 20 import ( 21 "fmt" 22 "io" 23 "strings" 24 "sync" 25 26 "github.com/ghodss/yaml" 27 "github.com/pkg/errors" 28 "github.com/spf13/cobra" 29 "github.com/splunk/qbec/internal/model" 30 "github.com/splunk/qbec/internal/remote" 31 "github.com/splunk/qbec/internal/remote/k8smeta" 32 "github.com/splunk/qbec/internal/sio" 33 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/client-go/dynamic" 36 ) 37 38 // usageError indicates that the user supplied incorrect arguments or flags to the command. 39 type usageError struct { 40 error 41 } 42 43 // newUsageError returns a usage error 44 func newUsageError(msg string) error { 45 return &usageError{ 46 error: errors.New(msg), 47 } 48 } 49 50 // isUsageError returns if the supplied error was caused due to incorrect command usage. 51 func isUsageError(err error) bool { 52 _, ok := err.(*usageError) 53 return ok 54 } 55 56 // runtimeError indicates that there were runtime issues with execution. 57 type runtimeError struct { 58 error 59 } 60 61 // NewRuntimeError returns a runtime error 62 func NewRuntimeError(err error) error { 63 return &runtimeError{ 64 error: err, 65 } 66 } 67 68 // IsRuntimeError returns if the supplied error was a runtime error as opposed to an error arising out of user input. 69 func IsRuntimeError(err error) bool { 70 _, ok := err.(*runtimeError) 71 return ok 72 } 73 74 // wrapError passes through usage errors and wraps all other errors with a runtime marker. 75 func wrapError(err error) error { 76 if err == nil { 77 return nil 78 } 79 if isUsageError(err) { 80 return err 81 } 82 return NewRuntimeError(err) 83 } 84 85 // Client encapsulates all remote operations needed for the superset of all commands. 86 type Client interface { 87 DisplayName(o model.K8sMeta) string 88 IsNamespaced(kind schema.GroupVersionKind) (bool, error) 89 Get(obj model.K8sMeta) (*unstructured.Unstructured, error) 90 Sync(obj model.K8sLocalObject, opts remote.SyncOptions) (*remote.SyncResult, error) 91 ValidatorFor(gvk schema.GroupVersionKind) (k8smeta.Validator, error) 92 ListObjects(scope remote.ListQueryConfig) (remote.Collection, error) 93 Delete(obj model.K8sMeta, dryRun bool) (*remote.SyncResult, error) 94 ObjectKey(obj model.K8sMeta) string 95 ResourceInterface(obj schema.GroupVersionKind, namespace string) (dynamic.ResourceInterface, error) 96 } 97 98 // ConfigProvider provides standard configuration available to all commands 99 type ConfigProvider func() *Config 100 101 // Setup sets up all subcommands for the supplied root command. 102 func Setup(root *cobra.Command, cp ConfigProvider) { 103 root.AddCommand(newApplyCommand(cp)) 104 root.AddCommand(newValidateCommand(cp)) 105 root.AddCommand(newShowCommand(cp)) 106 root.AddCommand(newDiffCommand(cp)) 107 root.AddCommand(newDeleteCommand(cp)) 108 root.AddCommand(newComponentCommand(cp)) 109 root.AddCommand(newParamCommand(cp)) 110 root.AddCommand(newEnvCommand(cp)) 111 root.AddCommand(newInitCommand()) 112 } 113 114 type worker func(object model.K8sLocalObject) error 115 116 func runInParallel(objs []model.K8sLocalObject, worker worker, parallel int) error { 117 if parallel <= 0 { 118 parallel = 1 119 } 120 121 ch := make(chan model.K8sLocalObject, len(objs)) 122 for _, o := range objs { 123 ch <- o 124 } 125 close(ch) 126 127 var wg sync.WaitGroup 128 129 errs := make(chan error, parallel) 130 for i := 0; i < parallel; i++ { 131 wg.Add(1) 132 go func() { 133 defer wg.Done() 134 for o := range ch { 135 err := worker(o) 136 if err != nil { 137 errs <- errors.Wrap(err, fmt.Sprint(o)) 138 return 139 } 140 } 141 }() 142 } 143 wg.Wait() 144 close(errs) 145 146 errMsgs := []string{} 147 for e := range errs { 148 errMsgs = append(errMsgs, e.Error()) 149 } 150 if len(errMsgs) > 0 { 151 return errors.New(strings.Join(errMsgs, "\n")) 152 } 153 return nil 154 } 155 156 func printStats(w io.Writer, stats interface{}) { 157 summary := struct { 158 Stats interface{} `json:"stats"` 159 }{stats} 160 b, err := yaml.Marshal(summary) 161 if err != nil { 162 sio.Warnln("unable to print summary stats", err) 163 } 164 fmt.Fprintf(w, "---\n%s\n", b) 165 } 166 167 type lockWriter struct { 168 io.Writer 169 l sync.Mutex 170 } 171 172 func (lw *lockWriter) Write(buf []byte) (int, error) { 173 lw.l.Lock() 174 n, err := lw.Writer.Write(buf) 175 lw.l.Unlock() 176 return n, err 177 }