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  }