github.com/cilium/cilium@v1.16.2/tools/dev-doctor/rootcmd.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"text/tabwriter"
    12  
    13  	"github.com/blang/semver/v4"
    14  	"github.com/spf13/cobra"
    15  	"golang.org/x/mod/modfile"
    16  )
    17  
    18  var rootCmd = &cobra.Command{
    19  	Args:  cobra.NoArgs,
    20  	Short: "Check development setup",
    21  	Run:   rootCmdRun,
    22  }
    23  
    24  var (
    25  	backportingChecks *bool
    26  	nfsFirewallChecks *bool
    27  )
    28  
    29  // versionRegex determines the semantic version via regexp.
    30  const versionRegex string = `v?(\d+\.\d+(\.\d+)?\S*)`
    31  
    32  func init() {
    33  	flags := rootCmd.Flags()
    34  	backportingChecks = flags.Bool("backporting", false, "Run backporting checks")
    35  	nfsFirewallChecks = flags.Bool("nfs-firewall", false, "Run extra NFS firewall checks, requires root privileges")
    36  }
    37  
    38  func readGoModGoVersion(rootDir string) (*semver.Version, error) {
    39  	goModFile := "go.mod"
    40  	path := filepath.Join(rootDir, goModFile)
    41  	data, err := os.ReadFile(path)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	mod, err := modfile.Parse(goModFile, data, nil)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if mod.Go == nil {
    51  		return nil, fmt.Errorf("no go statement found in %s", path)
    52  	}
    53  	ver, err := semver.ParseTolerant(mod.Go.Version)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return &ver, nil
    58  }
    59  
    60  func rootCmdRun(cmd *cobra.Command, args []string) {
    61  	rootDir := goPath() + "/src/github.com/cilium/cilium"
    62  
    63  	// $GOPATH is optional to set with a module-based Go setup
    64  	// If we cannot find src path via `$GOPATH`, just look in
    65  	// the `make` dir for `go.mod`
    66  	if _, err := os.Stat(rootDir); os.IsNotExist(err) {
    67  		rootDir, _ = os.Getwd()
    68  	}
    69  
    70  	minGoVersion, err := readGoModGoVersion(rootDir)
    71  	if err != nil {
    72  		panic(fmt.Sprintf("cannot read go version from go.mod: %v", err))
    73  	}
    74  
    75  	checks := []check{
    76  		osArchCheck{},
    77  		unameCheck{},
    78  		rootDirCheck{
    79  			rootDir: rootDir,
    80  		},
    81  		&binaryCheck{
    82  			name:          "make",
    83  			ifNotFound:    checkError,
    84  			versionArgs:   []string{"--version"},
    85  			versionRegexp: regexp.MustCompile(`GNU\s+Make\s+` + versionRegex),
    86  		},
    87  		&binaryCheck{
    88  			name:          "go",
    89  			ifNotFound:    checkError,
    90  			versionArgs:   []string{"version"},
    91  			versionRegexp: regexp.MustCompile(`go version go` + versionRegex),
    92  			minVersion:    minGoVersion,
    93  		},
    94  		&binaryCheck{
    95  			name:          "tparse",
    96  			ifNotFound:    checkWarning,
    97  			versionArgs:   []string{"-v"},
    98  			versionRegexp: regexp.MustCompile(`tparse version: ` + versionRegex),
    99  			hint:          `Run "go install github.com/mfridman/tparse@latest"`,
   100  		},
   101  		&binaryCheck{
   102  			name:          "clang",
   103  			ifNotFound:    checkError,
   104  			versionArgs:   []string{"--version"},
   105  			versionRegexp: regexp.MustCompile(`clang version ` + versionRegex),
   106  			minVersion:    &semver.Version{Major: 17, Minor: 0, Patch: 0},
   107  		},
   108  		&binaryCheck{
   109  			name:          "docker-server",
   110  			command:       "docker",
   111  			ifNotFound:    checkWarning,
   112  			versionArgs:   []string{"version", "--format", "{{ .Server.Version }}"},
   113  			versionRegexp: regexp.MustCompile(versionRegex),
   114  		},
   115  		&binaryCheck{
   116  			name:          "docker-client",
   117  			command:       "docker",
   118  			ifNotFound:    checkWarning,
   119  			versionArgs:   []string{"version", "--format", "{{ .Client.Version }}"},
   120  			versionRegexp: regexp.MustCompile(versionRegex),
   121  		},
   122  		&binaryCheck{
   123  			name:          "docker-buildx",
   124  			command:       "docker",
   125  			ifNotFound:    checkWarning,
   126  			versionArgs:   []string{"buildx", "version"},
   127  			versionRegexp: regexp.MustCompile(`github\.com/docker/buildx ` + versionRegex),
   128  			hint:          "see https://docs.docker.com/buildx/working-with-buildx/",
   129  		},
   130  		&binaryCheck{
   131  			name:          "ginkgo",
   132  			ifNotFound:    checkWarning,
   133  			versionArgs:   []string{"version"},
   134  			versionRegexp: regexp.MustCompile(`Ginkgo Version ` + versionRegex),
   135  			minVersion:    &semver.Version{Major: 1, Minor: 4, Patch: 0},
   136  			maxVersion:    &semver.Version{Major: 2, Minor: 0, Patch: 0},
   137  			hint:          `Run "go install github.com/onsi/ginkgo/ginkgo@v1.16.5".`,
   138  		},
   139  		// FIXME add gomega check?
   140  		&binaryCheck{
   141  			name:          "golangci-lint",
   142  			ifNotFound:    checkWarning,
   143  			versionArgs:   []string{"version"},
   144  			versionRegexp: regexp.MustCompile(versionRegex),
   145  			minVersion:    &semver.Version{Major: 1, Minor: 27, Patch: 0},
   146  			hint:          "See https://golangci-lint.run/welcome/install/#local-installation.",
   147  		},
   148  		&binaryCheck{
   149  			name:          "docker",
   150  			ifNotFound:    checkError,
   151  			versionArgs:   []string{"--version"},
   152  			versionRegexp: regexp.MustCompile(`Docker version ` + versionRegex),
   153  		},
   154  		&binaryCheck{
   155  			name:          "helm",
   156  			ifNotFound:    checkWarning,
   157  			versionArgs:   []string{`version`, `--template`, `Version: {{.Version}}`},
   158  			versionRegexp: regexp.MustCompile(`Version: ` + versionRegex),
   159  			minVersion:    &semver.Version{Major: 3, Minor: 13, Patch: 0},
   160  		},
   161  		&binaryCheck{
   162  			name:          "vagrant",
   163  			ifNotFound:    checkInfo,
   164  			versionArgs:   []string{"--version"},
   165  			versionRegexp: regexp.MustCompile(`Vagrant ` + versionRegex),
   166  			minVersion:    &semver.Version{Major: 2, Minor: 0, Patch: 0},
   167  		},
   168  		&binaryCheck{
   169  			name:           "virtualbox",
   170  			alternateNames: []string{"VirtualBox"},
   171  			ifNotFound:     checkInfo,
   172  		},
   173  		&binaryCheck{
   174  			name:           "vboxheadless",
   175  			alternateNames: []string{"VBoxHeadless"},
   176  			ifNotFound:     checkInfo,
   177  			versionArgs:    []string{"--version"},
   178  			versionRegexp:  regexp.MustCompile(`Oracle VM VirtualBox Headless Interface ` + versionRegex),
   179  			hint:           "run \"VBoxHeadless --help\" to diagnose why vboxheadless failed to execute",
   180  		},
   181  		&binaryCheck{
   182  			name:          "pip3",
   183  			ifNotFound:    checkWarning,
   184  			versionArgs:   []string{"--version"},
   185  			versionRegexp: regexp.MustCompile(`pip ` + versionRegex),
   186  		},
   187  		&binaryCheck{
   188  			name:          "kind",
   189  			ifNotFound:    checkWarning,
   190  			versionArgs:   []string{"--version"},
   191  			versionRegexp: regexp.MustCompile(`kind version ` + versionRegex),
   192  			minVersion:    &semver.Version{Major: 0, Minor: 7, Patch: 0},
   193  			hint:          "See https://kind.sigs.k8s.io/docs/user/quick-start/#installation.",
   194  		},
   195  		&binaryCheck{
   196  			name:          "kubectl",
   197  			ifNotFound:    checkWarning,
   198  			versionArgs:   []string{"version", "--output=yaml", "--client=true"},
   199  			versionRegexp: regexp.MustCompile(`gitVersion: ` + versionRegex),
   200  			minVersion:    &semver.Version{Major: 1, Minor: 26, Patch: 0},
   201  			hint:          "See https://kubernetes.io/docs/tasks/tools/#kubectl.",
   202  		},
   203  		&binaryCheck{
   204  			name:          "cilium",
   205  			ifNotFound:    checkWarning,
   206  			versionArgs:   []string{"version", "--client"},
   207  			versionRegexp: regexp.MustCompile(`cilium-cli: [A-Za-z/-]*` + versionRegex),
   208  			hint:          "See https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#install-the-cilium-cli.",
   209  		},
   210  		dockerGroupCheck{},
   211  	}
   212  
   213  	if *backportingChecks {
   214  		checks = append(checks,
   215  			&binaryCheck{
   216  				name:       "jq",
   217  				ifNotFound: checkError,
   218  			},
   219  			&binaryCheck{
   220  				name:          "python3",
   221  				ifNotFound:    checkError,
   222  				versionArgs:   []string{"--version"},
   223  				versionRegexp: regexp.MustCompile(`Python\s+` + versionRegex),
   224  				minVersion:    &semver.Version{Major: 3, Minor: 6, Patch: 0},
   225  			},
   226  			&commandCheck{
   227  				name:             "pygithub",
   228  				command:          "python3",
   229  				args:             []string{"-c", "from github import Github"},
   230  				ifFailure:        checkWarning,
   231  				ifSuccessMessage: "pygithub installed",
   232  				hint:             `Run "pip3 install --user PyGithub".`,
   233  			},
   234  			&binaryCheck{
   235  				name:          "gh",
   236  				ifNotFound:    checkError,
   237  				versionArgs:   []string{"--version"},
   238  				versionRegexp: regexp.MustCompile(`gh\s+version\s+` + versionRegex),
   239  				minVersion:    &semver.Version{Major: 2, Minor: 14, Patch: 0},
   240  				hint:          `Download the latest version from https://cli.github.com`,
   241  			},
   242  			&envVarCheck{
   243  				name:            "GITHUB_TOKEN",
   244  				ifNotSetOrEmpty: checkInfo,
   245  			},
   246  		)
   247  	}
   248  
   249  	if *nfsFirewallChecks {
   250  		checks = append(checks,
   251  			etcNFSConfCheck{},
   252  			&iptablesRuleCheck{
   253  				rule: []string{"INPUT", "-p", "tcp", "-s", "192.168.61.0/24", "--dport", "111", "-j", "ACCEPT"},
   254  			},
   255  			&iptablesRuleCheck{
   256  				rule: []string{"INPUT", "-p", "tcp", "-s", "192.168.61.0/24", "--dport", "2049", "-j", "ACCEPT"},
   257  			},
   258  			&iptablesRuleCheck{
   259  				rule: []string{"INPUT", "-p", "tcp", "-s", "192.168.61.0/24", "--dport", "20048", "-j", "ACCEPT"},
   260  			},
   261  		)
   262  	}
   263  
   264  	worstResult := checkOK
   265  	resultWriter := tabwriter.NewWriter(os.Stdout, 3, 0, 3, ' ', 0)
   266  	fmt.Fprint(resultWriter, "RESULT\tCHECK\tMESSAGE\n")
   267  	hints := make([]string, 0, len(checks))
   268  	for _, check := range checks {
   269  		checkResult, message := check.Run()
   270  		fmt.Fprintf(resultWriter, "%s\t%s\t%s\n", checkResultStr[checkResult], check.Name(), message)
   271  		if checkResult > checkOK {
   272  			if hint := check.Hint(); hint != "" {
   273  				hints = append(hints, fmt.Sprintf("%s\t%s\n", check.Name(), hint))
   274  			}
   275  		}
   276  		if checkResult > worstResult {
   277  			worstResult = checkResult
   278  		}
   279  	}
   280  	resultWriter.Flush()
   281  
   282  	if len(hints) > 0 {
   283  		fmt.Println()
   284  		hintWriter := tabwriter.NewWriter(os.Stdout, 3, 0, 3, ' ', 0)
   285  		fmt.Fprint(hintWriter, "CHECK\tHINT\n")
   286  		for _, hint := range hints {
   287  			fmt.Fprint(hintWriter, hint)
   288  		}
   289  		hintWriter.Flush()
   290  	}
   291  
   292  	if worstResult > checkOK {
   293  		fmt.Println()
   294  		fmt.Println("See https://docs.cilium.io/en/latest/contributing/development/dev_setup/.")
   295  		if *backportingChecks {
   296  			fmt.Println("See https://docs.cilium.io/en/latest/contributing/release/backports/.")
   297  		}
   298  	}
   299  
   300  	if worstResult > checkWarning {
   301  		os.Exit(1)
   302  	}
   303  }