github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/cli/doctor.go (about)

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"time"
     8  
     9  	"github.com/spf13/cobra"
    10  
    11  	"github.com/tilt-dev/tilt/internal/analytics"
    12  	"github.com/tilt-dev/tilt/internal/container"
    13  	"github.com/tilt-dev/tilt/internal/docker"
    14  	"github.com/tilt-dev/tilt/internal/dockercompose"
    15  	"github.com/tilt-dev/tilt/pkg/logger"
    16  	"github.com/tilt-dev/tilt/pkg/model"
    17  )
    18  
    19  type doctorCmd struct {
    20  }
    21  
    22  func (c *doctorCmd) name() model.TiltSubcommand { return "doctor" }
    23  
    24  func (c *doctorCmd) register() *cobra.Command {
    25  	cmd := &cobra.Command{
    26  		Use:   "doctor",
    27  		Short: "Print diagnostic information about the Tilt environment, for filing bug reports",
    28  	}
    29  	addKubeContextFlag(cmd)
    30  	return cmd
    31  }
    32  
    33  func (c *doctorCmd) run(ctx context.Context, args []string) error {
    34  	analytics.Get(ctx).Incr("cmd.doctor", map[string]string{})
    35  	defer analytics.Get(ctx).Flush(time.Second)
    36  
    37  	fmt.Printf("Tilt: %s\n", buildStamp())
    38  	fmt.Printf("System: %s-%s\n", runtime.GOOS, runtime.GOARCH)
    39  
    40  	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    41  	defer cancel()
    42  
    43  	var localDocker, clusterDocker docker.Client
    44  	var localDockerErr, clusterDockerErr error
    45  	multipleClients := false
    46  	client, err := wireDockerCompositeClient(ctx)
    47  	if err == nil {
    48  		localDocker = client.DefaultLocalClient()
    49  		clusterDocker = client.DefaultClusterClient()
    50  		multipleClients = client.HasMultipleClients()
    51  	} else { // Figure out which client(s) had errors so we can show them
    52  		localDocker, localDockerErr = wireDockerLocalClient(ctx)
    53  		clusterDocker, clusterDockerErr = wireDockerClusterClient(ctx)
    54  		multipleClients = (localDockerErr != nil) != (clusterDockerErr != nil)
    55  	}
    56  
    57  	fmt.Println("---")
    58  	if multipleClients {
    59  		fmt.Println("Docker (cluster)")
    60  	} else {
    61  		fmt.Println("Docker")
    62  	}
    63  
    64  	if clusterDockerErr != nil {
    65  		printField("Host", nil, clusterDockerErr)
    66  	} else {
    67  		dockerEnv := clusterDocker.Env()
    68  		host := dockerEnv.DaemonHost()
    69  		if host == "" {
    70  			host = "[default]"
    71  		}
    72  		printField("Host", host, nil)
    73  
    74  		version, err := clusterDocker.ServerVersion(ctx)
    75  		printField("Server Version", version.Version, err)
    76  		printField("API Version", version.APIVersion, err)
    77  
    78  		builderVersion, err := clusterDocker.BuilderVersion(ctx)
    79  		printField("Builder", builderVersion, err)
    80  	}
    81  
    82  	if multipleClients {
    83  		fmt.Println("---")
    84  		fmt.Println("Docker (local)")
    85  
    86  		if localDockerErr != nil {
    87  			printField("Host", nil, localDockerErr)
    88  		} else {
    89  			dockerEnv := localDocker.Env()
    90  			host := dockerEnv.DaemonHost()
    91  			if host == "" {
    92  				host = "[default]"
    93  			}
    94  			printField("Host", host, nil)
    95  
    96  			version, err := localDocker.ServerVersion(ctx)
    97  			printField("Server Version", version.Version, err)
    98  			printField("Version", version.APIVersion, err)
    99  
   100  			builderVersion, err := localDocker.BuilderVersion(ctx)
   101  			printField("Builder", builderVersion, err)
   102  		}
   103  	}
   104  
   105  	// in theory, the env shouldn't matter since we're just calling the version subcommand,
   106  	// but to be safe, we'll try to use the actual local env if available
   107  	composeEnv := docker.LocalEnv{}
   108  	if localDockerErr == nil {
   109  		composeEnv = docker.LocalEnv(localDocker.Env())
   110  	}
   111  	dcCli := dockercompose.NewDockerComposeClient(composeEnv)
   112  	// errors getting the version aren't generally useful; in many cases it'll just mean that
   113  	// the command couldn't exec since Docker Compose isn't installed, for example, so they
   114  	// are just ignored and the field skipped
   115  	if composeVersion, composeBuild, err := dcCli.Version(ctx); err == nil {
   116  		composeField := composeVersion
   117  		if composeBuild != "" {
   118  			composeField += fmt.Sprintf(" (build %s)", composeBuild)
   119  		}
   120  		printField("Compose Version", composeField, nil)
   121  	}
   122  
   123  	fmt.Println("---")
   124  	fmt.Println("Kubernetes")
   125  
   126  	env, err := wireEnv(ctx)
   127  	printField("Env", env, err)
   128  
   129  	kContext, err := wireKubeContext(ctx)
   130  	printField("Context", kContext, err)
   131  	clusterName, err := wireClusterName(ctx)
   132  	if clusterName == "" {
   133  		clusterName = "Unknown"
   134  	}
   135  	printField("Cluster Name", clusterName, err)
   136  
   137  	ns, err := wireNamespace(ctx)
   138  	printField("Namespace", ns, err)
   139  
   140  	runtime, err := containerRuntime(ctx)
   141  	printField("Container Runtime", runtime, err)
   142  
   143  	kVersion, err := wireK8sVersion(ctx)
   144  	printField("Version", kVersion, err)
   145  
   146  	registryDisplay, err := clusterLocalRegistryDisplay(ctx)
   147  	printField("Cluster Local Registry", registryDisplay, err)
   148  
   149  	fmt.Println("---")
   150  	fmt.Println("Thanks for seeing the Tilt Doctor!")
   151  	fmt.Println("Please send the info above when filing bug reports. 💗")
   152  
   153  	fmt.Println("")
   154  	fmt.Println("The info below helps us understand how you're using Tilt so we can improve,")
   155  	fmt.Println("but is not required to ask for help.")
   156  
   157  	fmt.Println("---")
   158  	fmt.Println("Analytics Settings")
   159  	fmt.Println("--> (These results reflect your personal opt in/out status and may be overridden by an `analytics_settings` call in your Tiltfile)")
   160  
   161  	a := analytics.Get(ctx)
   162  	opt := a.UserOpt()
   163  	fmt.Printf("- User Mode: %s\n", opt)
   164  
   165  	fmt.Printf("- Machine: %s\n", a.MachineHash())
   166  	fmt.Printf("- Repo: %s\n", a.GitRepoHash())
   167  
   168  	return nil
   169  }
   170  
   171  func containerRuntime(ctx context.Context) (container.Runtime, error) {
   172  	kClient, err := wireK8sClient(ctx)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  	return kClient.ContainerRuntime(ctx), nil
   177  }
   178  
   179  func clusterLocalRegistryDisplay(ctx context.Context) (string, error) {
   180  	kClient, err := wireK8sClient(ctx)
   181  	if err != nil {
   182  		return "", err
   183  	}
   184  
   185  	// blackhole any warnings
   186  	newCtx := logger.WithLogger(ctx, logger.NewDeferredLogger(ctx))
   187  	registry := kClient.LocalRegistry(newCtx)
   188  	if container.IsEmptyRegistry(registry) {
   189  		return "none", nil
   190  	}
   191  	return fmt.Sprintf("%+v", registry), nil
   192  }
   193  
   194  func printField(name string, v interface{}, err error) {
   195  	if err != nil {
   196  		fmt.Printf("- %s: Error: %v\n", name, err)
   197  	} else {
   198  		fmt.Printf("- %s: %s\n", name, v)
   199  	}
   200  }