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 }