istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/istioctl/kube.go (about)

     1  //  Copyright Istio Authors
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package istioctl
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	"istio.io/istio/istioctl/cmd"
    25  	"istio.io/istio/pilot/pkg/config/kube/crd"
    26  	"istio.io/istio/pkg/log"
    27  	"istio.io/istio/pkg/test"
    28  	"istio.io/istio/pkg/test/framework/resource"
    29  	"istio.io/istio/pkg/test/scopes"
    30  )
    31  
    32  // We cannot invoke the istioctl library concurrently due to the number of global variables
    33  // https://github.com/istio/istio/issues/37324
    34  var invokeMutex sync.Mutex
    35  
    36  type kubeComponent struct {
    37  	config     Config
    38  	kubeconfig string
    39  }
    40  
    41  // Filenamer is an interface to avoid importing kubecluster package, instead build our own interface
    42  // to extract kube context
    43  type Filenamer interface {
    44  	Filename() string
    45  }
    46  
    47  func newKube(ctx resource.Context, config Config) (Instance, error) {
    48  	fn, ok := ctx.Clusters().GetOrDefault(config.Cluster).(Filenamer)
    49  	if !ok {
    50  		return nil, fmt.Errorf("cluster does not support fetching kube config")
    51  	}
    52  	n := &kubeComponent{
    53  		config:     config,
    54  		kubeconfig: fn.Filename(),
    55  	}
    56  
    57  	return n, nil
    58  }
    59  
    60  // Invoke implements WaitForConfigs
    61  func (c *kubeComponent) WaitForConfig(defaultNamespace string, configs string) error {
    62  	cfgs, _, err := crd.ParseInputs(configs)
    63  	if err != nil {
    64  		return fmt.Errorf("failed to parse input: %v", err)
    65  	}
    66  	for _, cfg := range cfgs {
    67  		ns := cfg.Namespace
    68  		if ns == "" {
    69  			ns = defaultNamespace
    70  		}
    71  		// TODO(https://github.com/istio/istio/issues/37148) increase timeout. Right now it fails often, so
    72  		// set it to low timeout to reduce impact
    73  		if out, stderr, err := c.Invoke([]string{"x", "wait", "-v", "--timeout=5s", cfg.GroupVersionKind.Kind, cfg.Name + "." + ns}); err != nil {
    74  			return fmt.Errorf("wait: %v\nout: %v\nerr: %v", err, out, stderr)
    75  		}
    76  	}
    77  	return nil
    78  }
    79  
    80  // Invoke implements Instance
    81  func (c *kubeComponent) Invoke(args []string) (string, string, error) {
    82  	cmdArgs := append([]string{
    83  		"--kubeconfig",
    84  		c.kubeconfig,
    85  	}, args...)
    86  
    87  	var out bytes.Buffer
    88  	var err bytes.Buffer
    89  
    90  	start := time.Now()
    91  
    92  	invokeMutex.Lock()
    93  	rootCmd := cmd.GetRootCmd(cmdArgs)
    94  	rootCmd.SetOut(&out)
    95  	rootCmd.SetErr(&err)
    96  	// istioctl will overwrite logs which we don't want.
    97  	// It happens to do this via PersistentPreRunE, which we can disable.
    98  	// We add an additional check in case someone refactors this away, to ensure we don't wipe out non-logging code.
    99  	if fmt.Sprintf("%p", rootCmd.PersistentPreRunE) != fmt.Sprintf("%p", cmd.ConfigureLogging) {
   100  		log.Fatalf("istioctl PersistentPreRunE is not configuring logging")
   101  	}
   102  	rootCmd.PersistentPreRunE = nil
   103  	fErr := rootCmd.Execute()
   104  	invokeMutex.Unlock()
   105  
   106  	scopes.Framework.Infof("istioctl (%v): completed after %.4fs", args, time.Since(start).Seconds())
   107  
   108  	if err.String() != "" {
   109  		scopes.Framework.Infof("istioctl error: %v", strings.TrimSpace(err.String()))
   110  	}
   111  	return out.String(), err.String(), fErr
   112  }
   113  
   114  // InvokeOrFail implements Instance
   115  func (c *kubeComponent) InvokeOrFail(t test.Failer, args []string) (string, string) {
   116  	output, stderr, err := c.Invoke(args)
   117  	if err != nil {
   118  		t.Logf("Unwanted exception for 'istioctl %s': %v", strings.Join(args, " "), err)
   119  		t.Logf("Output:\n%v", output)
   120  		t.Logf("Error:\n%v", stderr)
   121  		t.FailNow()
   122  	}
   123  	return output, stderr
   124  }