Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: ["main"]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Run tests with coverage
run: go test ./... -coverprofile=coverage.out

- name: Upload coverage to Codecov
if: env.CODECOV_TOKEN != ''
uses: codecov/codecov-action@v4
with:
token: ${{ env.CODECOV_TOKEN }}
files: coverage.out
fail_ci_if_error: true
verbose: true
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Binaries
docker-lint
/docker-lint
docker-lint.exe
*.exe
*.exe~
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Thank you for your interest in contributing to docker-lint!

### Prerequisites

- Go 1.21 or later
- Go 1.22 or later (matches the `go` directive in `go.mod`)
- Git

### Getting Started
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# docker-lint

[![CI](https://img.shields.io/github/actions/workflow/status/devblac/docker-lint/ci.yml?branch=main&logo=github)](https://github.com/devblac/docker-lint/actions/workflows/ci.yml) [![Codecov](https://codecov.io/gh/devblac/docker-lint/graph/badge.svg)](https://codecov.io/gh/devblac/docker-lint) [![Go Report Card](https://goreportcard.com/badge/github.com/devblac/docker-lint)](https://goreportcard.com/report/github.com/devblac/docker-lint) [![Go Reference](https://pkg.go.dev/badge/github.com/devblac/docker-lint.svg)](https://pkg.go.dev/github.com/devblac/docker-lint) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Release](https://img.shields.io/github/v/release/devblac/docker-lint?display_name=release&sort=semver)](https://github.com/devblac/docker-lint/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/devblac/docker-lint/ci.yml?branch=main&logo=github)](https://github.com/devblac/docker-lint/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/devblac/docker-lint)](https://goreportcard.com/report/github.com/devblac/docker-lint) [![Go Reference](https://pkg.go.dev/badge/github.com/devblac/docker-lint.svg)](https://pkg.go.dev/github.com/devblac/docker-lint) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Release](https://img.shields.io/github/v/release/devblac/docker-lint?display_name=release&sort=semver)](https://github.com/devblac/docker-lint/releases)

A minimal, production-grade CLI tool for statically analyzing Dockerfiles to detect common inefficiencies, anti-patterns, and security issues.

Expand All @@ -18,21 +18,23 @@ A minimal, production-grade CLI tool for statically analyzing Dockerfiles to det

### From Source

Requires Go 1.22 or later.

```bash
go install github.com/docker-lint/docker-lint/cmd/docker-lint@latest
go install github.com/devblac/docker-lint/cmd/docker-lint@latest
```

### Build from Repository

```bash
git clone https://github.com/docker-lint/docker-lint.git
git clone https://github.com/devblac/docker-lint.git
cd docker-lint
go build -o docker-lint ./cmd/docker-lint
```

### Binary Download

Download the latest release from the [Releases](https://github.com/docker-lint/docker-lint/releases) page.
Download the latest release from the [Releases](https://github.com/devblac/docker-lint/releases) page.

## Usage

Expand Down Expand Up @@ -179,6 +181,8 @@ Machine-readable JSON output for CI/CD integration:

## CI/CD Integration

The repository's CI workflow runs `go test ./... -cover`. Coverage uploads to Codecov are attempted only when a `CODECOV_TOKEN` secret is configured; otherwise the upload step is skipped while tests still gate the build.

### GitHub Actions

```yaml
Expand Down Expand Up @@ -218,7 +222,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing guidelines

```bash
# Clone the repository
git clone https://github.com/docker-lint/docker-lint.git
git clone https://github.com/devblac/docker-lint.git
cd docker-lint

# Install dependencies
Expand Down
143 changes: 143 additions & 0 deletions cmd/docker-lint/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

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

"github.com/devblac/docker-lint/internal/analyzer"
"github.com/devblac/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/formatter"
"github.com/devblac/docker-lint/internal/parser"
"github.com/devblac/docker-lint/internal/rules"
)

// version is set at build time using -ldflags. Defaults to "dev" when not set.
var version = "dev"

func main() {
var (
jsonOutput bool
quiet bool
strict bool
versionFlg bool
rulesFlag bool
ignoreCSV string
)

flag.BoolVar(&jsonOutput, "json", false, "Output findings as JSON")
flag.BoolVar(&jsonOutput, "j", false, "Output findings as JSON")

flag.BoolVar(&quiet, "quiet", false, "Suppress informational messages (show only warnings and errors)")
flag.BoolVar(&quiet, "q", false, "Suppress informational messages (show only warnings and errors)")

flag.BoolVar(&strict, "strict", false, "Treat warnings as errors")
flag.BoolVar(&strict, "s", false, "Treat warnings as errors")

flag.BoolVar(&versionFlg, "version", false, "Show version information")
flag.BoolVar(&versionFlg, "v", false, "Show version information")

flag.BoolVar(&rulesFlag, "rules", false, "List all available rules with descriptions")

flag.StringVar(&ignoreCSV, "ignore", "", "Comma-separated list of rule IDs to ignore")

flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [file]\n", os.Args[0])
flag.PrintDefaults()
}

flag.Parse()

if versionFlg {
fmt.Println(version)
return
}

if rulesFlag {
listRules()
return
}

args := flag.Args()
if len(args) > 1 {
fmt.Fprintln(os.Stderr, "too many arguments: only one Dockerfile path is supported")
os.Exit(2)
}

filename := "stdin"
var reader io.Reader = os.Stdin

if len(args) == 1 {
filename = args[0]
file, err := os.Open(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open file: %v\n", err)
os.Exit(2)
}
defer file.Close()
reader = file
}

dockerfile, err := parser.ParseReader(reader)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse Dockerfile: %v\n", err)
os.Exit(2)
}

ignoreRules := parseIgnoreList(ignoreCSV)

anlzr := analyzer.NewWithDefaults(analyzer.Config{IgnoreRules: ignoreRules})
findings := anlzr.Analyze(dockerfile)

var errorsCount, warningsCount int
for _, finding := range findings {
switch finding.Severity {
case ast.SeverityError:
errorsCount++
case ast.SeverityWarning:
warningsCount++
}
}

if jsonOutput {
jsonFormatter := formatter.NewJSONFormatter(filename, quiet)
if err := jsonFormatter.Format(findings, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "failed to format JSON output: %v\n", err)
os.Exit(2)
}
} else {
textFormatter := formatter.NewTextFormatter(filename, quiet)
if err := textFormatter.Format(findings, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "failed to format text output: %v\n", err)
os.Exit(2)
}
}

if errorsCount > 0 || (strict && warningsCount > 0) {
os.Exit(1)
}
}

func parseIgnoreList(csv string) []string {
if csv == "" {
return nil
}

parts := strings.Split(csv, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

func listRules() {
for _, rule := range rules.DefaultRegistry.All() {
fmt.Printf("%s\t[%s]\t%s - %s\n", rule.ID(), rule.Severity().String(), rule.Name(), rule.Description())
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/docker-lint/docker-lint
module github.com/devblac/docker-lint

go 1.22.0

Expand Down
4 changes: 2 additions & 2 deletions internal/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package analyzer
import (
"sort"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/docker-lint/docker-lint/internal/rules"
"github.com/devblac/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/rules"
)

// Config holds configuration options for the analyzer.
Expand Down
4 changes: 2 additions & 2 deletions internal/analyzer/analyzer_property_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"reflect"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/docker-lint/docker-lint/internal/rules"
"github.com/devblac/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/rules"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
Expand Down
6 changes: 3 additions & 3 deletions internal/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"strings"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/docker-lint/docker-lint/internal/parser"
"github.com/docker-lint/docker-lint/internal/rules"
"github.com/devblac/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/parser"
"github.com/devblac/docker-lint/internal/rules"
)

func TestAnalyzer_Analyze_BasicFindings(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/formatter/formatter_property_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"encoding/json"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
Expand Down
2 changes: 1 addition & 1 deletion internal/formatter/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

func TestTextFormatter_Format(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/formatter/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"io"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// JSONFinding represents a single finding in JSON output format.
Expand Down
2 changes: 1 addition & 1 deletion internal/formatter/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"io"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// TextFormatter formats findings as human-readable text.
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"sort"
"strings"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// Format converts a Dockerfile AST back to text representation.
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"strings"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

func TestFormatNilDockerfile(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"regexp"
"strings"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// ParseError represents a parsing error with location information.
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/parser_property_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"reflect"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// TestParseAllInstructionTypes tests parsing of valid Dockerfiles with all instruction types.
Expand Down
2 changes: 1 addition & 1 deletion internal/rules/base_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package rules
import (
"strings"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

// largeBaseImages is a set of known large base images that have smaller alternatives.
Expand Down
2 changes: 1 addition & 1 deletion internal/rules/base_image_property_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"reflect"
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
Expand Down
2 changes: 1 addition & 1 deletion internal/rules/base_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package rules
import (
"testing"

"github.com/docker-lint/docker-lint/internal/ast"
"github.com/devblac/docker-lint/internal/ast"
)

func TestBaseImageRulesRegistered(t *testing.T) {
Expand Down
Loading