github.com/splunk/dan1-qbec@v0.7.3/internal/remote/query.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 remote 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/pkg/errors" 27 "github.com/splunk/qbec/internal/model" 28 "github.com/splunk/qbec/internal/sio" 29 apiErrors "k8s.io/apimachinery/pkg/api/errors" 30 "k8s.io/apimachinery/pkg/api/meta" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/client-go/dynamic" 35 ) 36 37 type queryConfig struct { 38 scope ListQueryConfig 39 resourceProvider func(gvk schema.GroupVersionKind, namespace string) (dynamic.ResourceInterface, error) 40 namespacedTypes []schema.GroupVersionKind 41 clusterTypes []schema.GroupVersionKind 42 verbosity int 43 } 44 45 type objectLister struct { 46 queryConfig 47 } 48 49 func (o *objectLister) listObjectsOfType(gvk schema.GroupVersionKind, namespace string) ([]*basicObject, error) { 50 startTime := time.Now() 51 defer func() { 52 if o.verbosity > 0 { 53 sio.Debugf("list objects: type=%s,namespace=%q took %v\n", gvk, namespace, time.Since(startTime).Round(time.Millisecond)) 54 } 55 }() 56 xface, err := o.resourceProvider(gvk, namespace) 57 if err != nil { 58 return nil, errors.Wrap(err, fmt.Sprintf("get resource interface for %s", gvk)) 59 } 60 ls := fmt.Sprintf("%s=%s,%s=%s", model.QbecNames.ApplicationLabel, o.scope.Application, model.QbecNames.EnvironmentLabel, o.scope.Environment) 61 if o.scope.Tag == "" { 62 ls = fmt.Sprintf("%s,!%s", ls, model.QbecNames.TagLabel) 63 } else { 64 ls = fmt.Sprintf("%s,%s=%s", ls, model.QbecNames.TagLabel, o.scope.Tag) 65 } 66 list, err := xface.List(metav1.ListOptions{ 67 LabelSelector: ls, 68 IncludeUninitialized: true, 69 }) 70 if err != nil { 71 if apiErrors.IsForbidden(err) { 72 sio.Warnf("not authorized to list %s, error ignored\n", gvk) 73 return nil, nil 74 } 75 return nil, err 76 } 77 objs, err := meta.ExtractList(list) 78 if err != nil { 79 return nil, errors.Wrap(err, fmt.Sprintf("extract items for %s", gvk)) 80 } 81 var ret []*basicObject 82 83 outer: 84 for _, obj := range objs { 85 un, ok := obj.(*unstructured.Unstructured) 86 if !ok { 87 return nil, fmt.Errorf("dunno how to process object of type %v", reflect.TypeOf(obj)) 88 } 89 90 // check if the object has been created by a controller and, if so, skip it 91 refs := un.GetOwnerReferences() 92 for _, ref := range refs { 93 if ref.Controller != nil && *ref.Controller { 94 if o.verbosity > 0 { 95 sio.Debugf("ignore %s %s since it was created by a controller\n", gvk, un.GetName()) 96 } 97 continue outer 98 } 99 } 100 101 labels := un.GetLabels() 102 if labels == nil { 103 labels = map[string]string{} 104 } 105 anns := un.GetAnnotations() 106 if anns == nil { 107 anns = map[string]string{} 108 } 109 mm := &basicObject{ 110 objectKey: objectKey{ 111 gvk: un.GroupVersionKind(), 112 namespace: un.GetNamespace(), 113 name: un.GetName(), 114 }, 115 app: labels[model.QbecNames.ApplicationLabel], 116 component: anns[model.QbecNames.ComponentAnnotation], 117 env: labels[model.QbecNames.EnvironmentLabel], 118 } 119 ret = append(ret, mm) 120 } 121 return ret, nil 122 } 123 124 type errorContext struct { 125 gvk schema.GroupVersionKind 126 namespace string 127 err error 128 } 129 130 type errorCollection struct { 131 errors []errorContext 132 } 133 134 func (e *errorCollection) add(ec errorContext) { 135 e.errors = append(e.errors, ec) 136 } 137 138 func (e *errorCollection) Error() string { 139 var lines []string 140 for _, e := range e.errors { 141 lines = append(lines, fmt.Sprintf("list %s %s: %v", e.gvk, e.namespace, e.err)) 142 } 143 return "list errors:" + strings.Join(lines, "\n\t") 144 } 145 146 func (o *objectLister) serverObjects(coll *collection) error { 147 var l sync.Mutex 148 var errs errorCollection 149 add := func(gvk schema.GroupVersionKind, ns string, objects []*basicObject, err error) { 150 l.Lock() 151 defer l.Unlock() 152 if err != nil { 153 errs.add(errorContext{gvk: gvk, namespace: ns, err: err}) 154 } 155 for _, o := range objects { 156 coll.objects[o.objectKey] = o 157 } 158 } 159 160 var workers []func() 161 addQueries := func(types []schema.GroupVersionKind, ns string) { 162 for _, gvk := range types { 163 if o.scope.KindFilter != nil && !o.scope.KindFilter.ShouldInclude(gvk.Kind) { 164 continue 165 } 166 localType := gvk 167 workers = append(workers, func() { 168 ret, err := o.listObjectsOfType(localType, ns) 169 add(localType, ns, ret, err) 170 }) 171 } 172 } 173 174 if len(o.scope.Namespaces) > 0 { 175 switch { 176 case o.scope.DisableAllNsQueries || len(o.scope.Namespaces) == 1: 177 for _, ns := range o.scope.Namespaces { 178 addQueries(o.namespacedTypes, ns) 179 } 180 default: 181 addQueries(o.namespacedTypes, "") 182 } 183 } 184 185 if o.scope.ClusterObjects { 186 addQueries(o.clusterTypes, "") 187 } 188 189 concurrency := o.scope.Concurrency 190 if concurrency <= 0 { 191 concurrency = 5 192 } 193 194 ch := make(chan func(), len(workers)) 195 for _, w := range workers { 196 ch <- w 197 } 198 close(ch) 199 200 var wg sync.WaitGroup 201 wg.Add(concurrency) 202 for i := 0; i < concurrency; i++ { 203 go func() { 204 defer wg.Done() 205 for fn := range ch { 206 fn() 207 } 208 }() 209 } 210 211 wg.Wait() 212 213 if len(errs.errors) > 0 { 214 return &errs 215 } 216 return nil 217 }