Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add README and documentation #5

Merged
merged 19 commits into from Sep 19, 2020
19 changes: 19 additions & 0 deletions .circleci/config.yml
Expand Up @@ -16,6 +16,21 @@ jobs:
command: |
make lint

- run:
name: Ensure generated docs are up-to-date
command: |
make generated-docs
git diff --exit-code HEAD

test:
executor: custom
steps:
- checkout
- run:
name: Run unit tests
command: |
make test

workflows:
version: 2
build:
Expand All @@ -24,3 +39,7 @@ workflows:
filters:
tags:
only: /.*/
- test:
filters:
tags:
only: /.*/
1 change: 1 addition & 0 deletions .golangci.yml
Expand Up @@ -54,6 +54,7 @@ linters-settings:
- unnecessaryDefer
- importShadow
- emptyStringTest
- hugeParam
nolintlint:
allow-leading-space: false # require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
Expand Down
27 changes: 23 additions & 4 deletions Makefile
Expand Up @@ -60,14 +60,33 @@ staticcheck: $(STATICCHECK_BIN)
.PHONY: lint
lint: golangci-lint staticcheck

#############
## Compile ##
#############
####################
## Code generation #
####################

.PHONY: generated-docs
generated-docs: build
./bin/kube-linter templates list --format markdown > docs/generated/templates.md
./bin/kube-linter checks list --format markdown > docs/generated/checks.md

.PHONY: packr
packr: $(PACKR_BIN)
packr

#############
## Compile ##
#############


.PHONY: build
build: packr
go build -o kube-linter ./cmd/kubelinter
go build -o ./bin/kube-linter ./cmd/kube-linter

##########
## Test ##
##########

.PHONY: test
test: packr
go test ./...

33 changes: 33 additions & 0 deletions README.md
@@ -1 +1,34 @@
# kube-linter

kube-linter is a static analysis tool that checks Kubernetes YAML files to ensure the applications represented in them
adhere to best practices.

In detail, `kube-linter` is a binary that takes in paths to YAML files, and runs a list of checks
against them. If any lint errors are found, they are printed to standard error, and `kube-linter` returns a non-zero
exit code.

The list of checks that is run is configurable. `kube-linter` comes with several built-in checks, only some of which
are enabled by default. Users can also create custom checks.

## Install

If you have `go` installed, you can run `go get golang.stackrox.io/kube-linter/cmd/kube-linter`.

`kube-linter` binaries can be downloaded from [the Releases page](https://github.com/stackrox/kube-linter/releases).
Download the `kube-linter` binary, and add it to your PATH.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO a readme in an open source repository should always include custom build instructions and contributing guidelines (or references to these)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added instructions for building from source. Contributing guidelines, will do in a follow-up -- we still need to iron those out.


## Usage

To lint directories or files, simply run `./kube-linter lint files_or_dirs ...`. If a directory is passed, all files
with `.yaml` or `.yml` extensions are parsed, and Kubernetes objects are loaded from them. If a file is passed,
it is parsed irrespective of extension.

Users can pass a config file using the `--config` file to control which checks are executed, and to configure custom checks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--config flag

An example config file is provided in `config.yaml.example`.

See the [documentation](./docs) for more details.

# WARNING: Breaking changes possible

kube-linter is currently in a very early stage of development. There may be breaking changes to the command usage, flags
and config file formats.
2 changes: 2 additions & 0 deletions bin/.gitignore
@@ -0,0 +1,2 @@
*
!.gitignore
File renamed without changes.
19 changes: 19 additions & 0 deletions config.yaml.example
@@ -0,0 +1,19 @@
# customChecks defines custom checks.
customChecks:
- name: "required-label-app"
template: "required-label"
params:
key: "app"
checks:
# if doNotAutoAddDefaults is true, default checks are not automatically added.
# Otherwise, they are.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove, this is pretty obvious based on the previous line

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think doNotAutoAddDefaults is backwards, should be autoAddDefaults that defaults to true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's also just a bit too verbose

doNotAutoAddDefaults: false

# include explicitly adds checks, by name. You can reference any of the built-in checks.
# Note that customChecks defined above are included automatically.
include:
- "required-label-owner"
# exclude explicitly excludes checks, by name. exclude has the highest priority: if a check is
# in exclude, then it is not considered, even if it is in include as well.
exclude:
- "privileged"
38 changes: 38 additions & 0 deletions docs/README.md
@@ -0,0 +1,38 @@

# Documentation

Welcome to the `kube-linter` documentation. Read on for more detailed information about using and configuring the tool.

## Exporing the CLI

You can run `kube-linter --help` to see a list of supported commands and flags. For each subcommand, you can
run `kube-linter <subcommand> --help` to see detailed help text and flags for it.

## Running the linter

To lint directories or files, simply run `./kube-linter lint files_or_dirs ...`. If a directory is passed, all files
with `.yaml` or `.yml` extensions are parsed, and Kubernetes objects are loaded from them. If a file is passed,
it is parsed irrespective of extension.

Users can pass a config file using the `--config` file to control which checks are executed, and to configure custom checks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flag again (is there some way to not duplicate the contents between this and the top-level readme)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. And I just removed the duplicate content from the top-level README in favour of just linking.

An example config file is provided [here](../config.yaml.example).

## Built-in checks

`kube-linter` comes with a list of built-in checks, which you can find [here](generated/checks.md). Only some
built-in checks are enabled by default -- others must be explicitly enabled in the config.

## Custom checks

### Check Templates

In `kube-linter`, checks are concrete realizations of check templates. A check template describes a class of check -- it
contains logic (written in Go code) that would execute the check, and lays out (zero or more) parameters that it takes.

The list of supported check templates, along with their metadata, can be found [here](generated/templates.md).

### Custom checks

All checks in `kube-linter` are defined by referencing a check template, passing parameters to it, and adding additional
check specific metadata (like check name and description). Users can configure custom checks the same way built-in checks
are configured, and add them to the config file. The built-in checks are specified [here](../internal/builtinchecks).
8 changes: 8 additions & 0 deletions docs/generated/checks.md
@@ -0,0 +1,8 @@
The following table enumerates built-in checks:


| Name | Enabled by default | Description | Template | Parameters |
--- | --- | --- | --- | --- |
|`env-var-secret`|No|Alert on objects using a secret in an environment variable|env-var|- `name`: `.*secret.*` <br />|
|`privileged-container`|Yes|Alert on deployments with containers running in privileged mode|privileged|none|
|`required-label-owner`|No|Alert on objects without the 'owner' label|required-label|- `key`: `owner` <br />|
8 changes: 8 additions & 0 deletions docs/generated/templates.md
@@ -0,0 +1,8 @@
The following table enumerates supported check templates:


| Name | Description | Supported Objects | Parameters |
--- | --- | --- | --- |
|`env-var`|Flag environment variables that match the provided patterns|DeploymentLike|- `name` (required): A regex for the env var name <br />- `value`: A regex for the env var value <br />|
|`privileged`|Flag privileged containers|DeploymentLike|none|
|`required-label`|Flag objects not carrying at least one label matching the provided patterns|Any|- `key` (required): A regex for the key of the required label <br />- `value`: A regex for the value of the required label <br />|
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -10,6 +10,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
honnef.co/go/tools v0.0.1-2020.1.5
Expand Down
25 changes: 19 additions & 6 deletions internal/builtinchecks/built_in_checks.go
Expand Up @@ -14,18 +14,31 @@ var (

// LoadInto loads built-in checks into the registry.
func LoadInto(registry checkregistry.CheckRegistry) error {
checks, err := List()
if err != nil {
return err
}
for _, chk := range checks {
if err := registry.Register(&chk); err != nil {
return errors.Wrapf(err, "registering default check %s", chk.Name)
}
}
return nil
}

// List lists built-in checks.
func List() ([]check.Check, error) {
var out []check.Check
for _, fileName := range box.List() {
contents, err := box.Find(fileName)
if err != nil {
return errors.Wrapf(err, "loading default check from %s", fileName)
return nil, errors.Wrapf(err, "loading default check from %s", fileName)
}
var chk check.Check
if err := yaml.Unmarshal(contents, &chk); err != nil {
return errors.Wrapf(err, "unmarshaling default check from %s", fileName)
}
if err := registry.Register(&chk); err != nil {
return errors.Wrapf(err, "registering default check from %s", fileName)
return nil, errors.Wrapf(err, "unmarshaling default check from %s", fileName)
}
out = append(out, chk)
}
return nil
return out, nil
}
1 change: 1 addition & 0 deletions internal/check/template.go
Expand Up @@ -25,6 +25,7 @@ type ObjectKindsDesc struct {
// A Template is a template for a check.
type Template struct {
Name string
Description string
SupportedObjectKinds ObjectKindsDesc
Parameters []ParameterDesc
Instantiate func(params map[string]string) (Func, error)
Expand Down
90 changes: 90 additions & 0 deletions internal/command/checks/command.go
@@ -0,0 +1,90 @@
package checks

import (
"fmt"
"io"
"os"
"strings"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.stackrox.io/kube-linter/internal/builtinchecks"
"golang.stackrox.io/kube-linter/internal/check"
"golang.stackrox.io/kube-linter/internal/command/common"
"golang.stackrox.io/kube-linter/internal/defaultchecks"
"golang.stackrox.io/kube-linter/internal/ternary"
)

var (
dashes = func() string {
var sb strings.Builder
for i := 0; i < 30; i++ {
sb.WriteRune('-')
}
return sb.String()
}()

formatsToRenderFuncs = map[string]func([]check.Check, io.Writer){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly irrelevant (see below), but the brace following the closing parens without space in between looks weird - doesn't the linter flag this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That brace is actually consistent with go fmt since it applies to the map, and not the func (although I agree it pattern matches with braces around a func and looks weird).

common.PlainFormat: func(checks []check.Check, out io.Writer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these to separate functions instead of inlining them. I would also encourage you to use Go templates for formatting. Especially the markdown formatting (a) looks ugly in code and (b) is extremely limited as you cannot add any custom styling, notes, etc.
(You might need to add either a custom function or translate the checks into a separate type to realize the "enabled by default" flag)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Also moved to templates.

for i, check := range checks {
fmt.Fprintf(out, "Name: %s\nDescription: %s\nTemplate: %s\nParameters: %v\nEnabled by default: %v\n",
check.Name, check.Description, check.Template, check.Params, defaultchecks.List.Contains(check.Name))
if i != len(checks)-1 {
fmt.Fprintf(out, "\n%s\n\n", dashes)
}
}
},
common.MarkdownFormat: func(checks []check.Check, out io.Writer) {
fmt.Fprintf(out, "The following table enumerates built-in checks:\n")
fmt.Fprintf(out, "\n\n| Name | Enabled by default | Description | Template | Parameters |\n --- | --- | --- | --- | --- | \n")
for _, check := range checks {
var params string
if len(check.Params) == 0 {
params = "none"
} else {
var sb strings.Builder
for key, value := range check.Params {
sb.WriteString(fmt.Sprintf("- `%s`: `%s` <br />", key, value))
}
params = sb.String()
}
fmt.Fprintf(out, "|`%s`|%s|%s|%s|%s|\n",
check.Name,
ternary.String(defaultchecks.List.Contains(check.Name), "Yes", "No"),
check.Description, check.Template, params)
}
},
}
)

func listCommand() *cobra.Command {
format := common.FormatWrapper{Format: common.PlainFormat}
c := &cobra.Command{
Use: "list",
Short: "list built-in checks",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
checks, err := builtinchecks.List()
if err != nil {
return err
}
renderFunc := formatsToRenderFuncs[format.Format]
if renderFunc == nil {
return errors.Errorf("unknown format: %q", format.Format)
}
renderFunc(checks, os.Stdout)
return nil
},
}
c.Flags().Var(&format, "format", "output format")
return c
}

// Command defines the root of the checks command.
func Command() *cobra.Command {
c := &cobra.Command{
Use: "checks",
}
c.AddCommand(listCommand())
return c
}
17 changes: 17 additions & 0 deletions internal/command/checks/command_test.go
@@ -0,0 +1,17 @@
package checks

import (
"testing"

"github.com/stretchr/testify/assert"
"golang.stackrox.io/kube-linter/internal/command/common"
"golang.stackrox.io/kube-linter/internal/set"
)

func TestAllFormatsSupported(t *testing.T) {
supportedFormats := set.NewStringSet()
for format := range formatsToRenderFuncs {
supportedFormats.Add(format)
}
assert.ElementsMatch(t, supportedFormats.AsSlice(), common.AllSupportedFormats.AsSlice())
}