Skip to content

Commit

Permalink
feat: added ability to lint Starlark packages (#1360)
Browse files Browse the repository at this point in the history
## Description:
Adds a way to lint starlark files

## Is this change user facing?
YES

## References (if applicable):
Closes #1228
  • Loading branch information
h4ck3rk3y committed Sep 21, 2023
1 parent e33bfe6 commit f4a072c
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 0 deletions.
1 change: 1 addition & 0 deletions cli/cli/command_str_consts/command_str_consts.go
Expand Up @@ -49,6 +49,7 @@ const (
FilesStoreServiceCmdStr = "storeservice"
FilesRenderTemplate = "rendertemplate"
KurtosisDumpCmdStr = "dump"
KurtosisLintCmdStr = "lint"
PortalCmdStr = "portal"
PortalStartCmdStr = "start"
PortalStatusCmdStr = "status"
Expand Down
138 changes: 138 additions & 0 deletions cli/cli/commands/lint/lint.go
@@ -0,0 +1,138 @@
package lint

import (
"context"
_ "embed"
"fmt"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/stacktrace"
"github.com/sirupsen/logrus"
"os"
"os/exec"
)

const (
fileOrDirToLintArgKey = "file-or-dir"
fileOrDirToLintArgKeyIsOptional = true
fileOrDirToLintArgKeyIsGreedy = true

formatFlagKey = "format"
formatFlagShortKey = "f"
formatFlagDefaultValue = "false"

pyBlackDockerImage = "pyfound/black:23.9.1"
dockerRunCmd = "run"
removeContainerOnExit = "--rm"
dockerBinary = "docker"
lintVolumeName = "/lint"
dockerVolumeFlag = "-v"
dockerWorkDirFlag = "--workdir"
blackBinaryName = "black"
includeFlagForBlack = "--include"
presentWorkingDirectory = "."
checkFlagForBlack = "--check"
allStarlarkFilesMatch = "\\.star?$"
dirVolumeSeparator = ":"

linterFailedAsThingsNeedToBeReformattedExitCode = 1
linterFailedWithInternalErrorsExitCode = 123
)

var fileOrDirToLintDefaultValue = []string{"."}

var dockerRunPrefix = []string{dockerRunCmd, removeContainerOnExit, dockerVolumeFlag}
var dockerRunSuffix = []string{dockerWorkDirFlag, lintVolumeName, pyBlackDockerImage, blackBinaryName, presentWorkingDirectory, includeFlagForBlack, allStarlarkFilesMatch}

// LintCmd we only fill in the required struct fields, hence the others remain nil
// nolint: exhaustruct
var LintCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.KurtosisLintCmdStr,
ShortDescription: "Lints the Kurtosis package or file",
LongDescription: "Lints the Kurtosis package or file",

Args: []*args.ArgConfig{
{
Key: fileOrDirToLintArgKey,
DefaultValue: fileOrDirToLintDefaultValue,
IsOptional: fileOrDirToLintArgKeyIsOptional,
IsGreedy: fileOrDirToLintArgKeyIsGreedy,
ValidationFunc: validateFileOrDirToLintArg,
},
},

Flags: []*flags.FlagConfig{
{
Key: formatFlagKey,
Usage: "Use this flag to edit files in place instead of just verifying whether the formatting is correct",
Shorthand: formatFlagShortKey,
Type: flags.FlagType_Bool,
Default: formatFlagDefaultValue,
},
},
RunFunc: run,
}

func run(_ context.Context, flags *flags.ParsedFlags, args *args.ParsedArgs) error {
fileOrDirToLintArg, err := args.GetGreedyArg(fileOrDirToLintArgKey)
if err != nil {
return stacktrace.Propagate(err, "an error occurred getting the value of argument with key '%v'", fileOrDirToLintArgKey)
}

formatFlag, err := flags.GetBool(formatFlagKey)
if err != nil {
return stacktrace.Propagate(err, "an error occurred getting the value of flag '%v'", formatFlag)
}
if !formatFlag {
dockerRunSuffix = append(dockerRunSuffix, checkFlagForBlack)
}

logrus.Infof("This depends on '%v'; first run may take a while as we might have to download it", pyBlackDockerImage)

if _, err := exec.LookPath(dockerBinary); err != nil {
return stacktrace.Propagate(err, "'%v' uses '%v' underneath in order to use the '%v' image but it couldn't find '%v' in path", command_str_consts.KurtosisLintCmdStr, dockerBinary, pyBlackDockerImage, dockerBinary)
}

for _, fileOrDirToLint := range fileOrDirToLintArg {
logrus.Infof("Linting '%v'", fileOrDirToLint)
commandArgs := append(dockerRunPrefix, fileOrDirToLint+dirVolumeSeparator+lintVolumeName)
commandArgs = append(commandArgs, dockerRunSuffix...)
cmd := exec.Command(dockerBinary, commandArgs...)
cmdOutput, err := cmd.CombinedOutput()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
switch exitError.ExitCode() {
case linterFailedAsThingsNeedToBeReformattedExitCode:
fmt.Println(string(cmdOutput))
return stacktrace.NewError("linting failed, this means that there are some files that need to be formatted, run this command with the '--%v' flag", formatFlagKey)
case linterFailedWithInternalErrorsExitCode:
fmt.Println(string(cmdOutput))
return stacktrace.NewError("linting failed with an internal error please look at the output to see why; usually this happens if there's a mix of spaces & tabs")
default:
return stacktrace.Propagate(err, "linting failed with an unexpected exit code '%v'; This is a bug in Kurtosis", exitError.ExitCode())
}
}
return stacktrace.Propagate(err, "Linting failed and we couldn't get an exit code out of the err; This is a bug in Kurtosis")
}
fmt.Println(string(cmdOutput))
}

return nil
}

func validateFileOrDirToLintArg(_ context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error {
fileOrDirToLintArg, err := args.GetGreedyArg(fileOrDirToLintArgKey)
if err != nil {
return stacktrace.Propagate(err, "an error occurred getting the value of argument with key '%v'", fileOrDirToLintArgKey)
}

for _, fileOrDirToLint := range fileOrDirToLintArg {
if _, err := os.Stat(fileOrDirToLint); err != nil {
return stacktrace.Propagate(err, "an error occurred validating whether supplied path '%v' was valid", fileOrDirToLint)
}
}

return nil
}
2 changes: 2 additions & 0 deletions cli/cli/commands/root.go
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/gateway"
_import "github.com/kurtosis-tech/kurtosis/cli/cli/commands/import"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/kurtosis_context"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/lint"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/lsp"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/port"
"github.com/kurtosis-tech/kurtosis/cli/cli/commands/portal"
Expand Down Expand Up @@ -120,6 +121,7 @@ func init() {
RootCmd.AddCommand(files.FilesCmd)
RootCmd.AddCommand(gateway.GatewayCmd)
RootCmd.AddCommand(lsp.NewLspCommand())
RootCmd.AddCommand(lint.LintCmd.MustGetCobraCommand())
RootCmd.AddCommand(port.PortCmd)
RootCmd.AddCommand(portal.PortalCmd)
RootCmd.AddCommand(run.StarlarkRunCmd.MustGetCobraCommand())
Expand Down
33 changes: 33 additions & 0 deletions docs/docs/cli-reference/lint.md
@@ -0,0 +1,33 @@
---
title: lint
sidebar_label: lint
slug: /lint
---

The following command can be used to lint Starlark files in the given package

To get running quickly, simply run

```bash
kurtosis lint .
```

This will lint all the Starlark files in the given package

Instead of just finding linting issues if you want to format the files as well use the `--format` flag

```bash
kurtosis lint . --format
```

You can also lint a specific file via

```bash
kurtosis lint main.star
```

Or to lint multiple files or directories at the same time

```bash
kurtosis lint this.star that.star also-this.star my-favorite-directory/
```

0 comments on commit f4a072c

Please sign in to comment.