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  }