1
0
mirror of https://github.com/moby/buildkit.git synced 2025-04-18 18:04:03 +03:00

dockerfile: generate lint rules documentation

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2024-06-04 12:36:41 +02:00 committed by Tonis Tiigi
parent 49b935f77b
commit 7283eaba0d
No known key found for this signature in database
GPG Key ID: AFA9DE5F8AB7AF39
6 changed files with 302 additions and 1 deletions

View File

@ -113,6 +113,10 @@ doctoc:
docs:
$(BUILDX_CMD) bake docs
.PHONY: docs-dockerfile
docs-dockerfile:
$(BUILDX_CMD) bake docs-dockerfile
.PHONY: mod-outdated
mod-outdated:
$(BUILDX_CMD) bake mod-outdated

View File

@ -126,7 +126,7 @@ target "integration-tests" {
}
group "validate" {
targets = ["lint", "validate-vendor", "validate-doctoc", "validate-generated-files", "validate-archutil", "validate-shfmt", "validate-docs"]
targets = ["lint", "validate-vendor", "validate-doctoc", "validate-generated-files", "validate-archutil", "validate-shfmt", "validate-docs", "validate-docs-dockerfile"]
}
target "lint" {
@ -211,6 +211,13 @@ target "validate-docs" {
output = ["type=cacheonly"]
}
target "validate-docs-dockerfile" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/docs-dockerfile.Dockerfile"
target = "validate"
output = ["type=cacheonly"]
}
target "vendor" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
@ -260,6 +267,13 @@ target "docs" {
output = ["./docs"]
}
target "docs-dockerfile" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/docs-dockerfile.Dockerfile"
target = "update"
output = ["./frontend/dockerfile/docs/rules"]
}
target "mod-outdated" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"

View File

@ -0,0 +1,55 @@
---
title: FileConsistentCommandCasing
description: All commands within the Dockerfile should use the same casing (either upper or lower)
---
Example warning:
```text
Command 'foo' should match the case of the command majority (uppercase)
```
Instructions within a Dockerfile should have consistent casing through out the
entire files. Instructions are not case-sensitive, but the convention is to use
uppercase for instruction keywords to make it easier to distinguish keywords
from arguments.
Whether you prefer instructions to be uppercase or lowercase, you should make
sure you use consistent casing to help improve readability of the Dockerfile.
## Examples
❌ Bad: mixing uppercase and lowercase
```dockerfile
FROM alpine:latest AS builder
run apk --no-cache add build-base
FROM builder AS build1
copy source1.cpp source.cpp
```
✅ Good: all uppercase
```dockerfile
FROM alpine:latest AS builder
RUN apk --no-cache add build-base
FROM builder AS build1
COPY source1.cpp source.cpp
```
✅ Good: all lowercase
```dockerfile
from alpine:latest as builder
run apk --no-cache add build-base
from builder as build1
copy source1.cpp source.cpp
```
## Related errors
- [`FromAsCasing`](./from-as-casing.md)

View File

@ -0,0 +1,49 @@
Example warning:
```text
Command 'foo' should match the case of the command majority (uppercase)
```
Instructions within a Dockerfile should have consistent casing through out the
entire files. Instructions are not case-sensitive, but the convention is to use
uppercase for instruction keywords to make it easier to distinguish keywords
from arguments.
Whether you prefer instructions to be uppercase or lowercase, you should make
sure you use consistent casing to help improve readability of the Dockerfile.
## Examples
❌ Bad: mixing uppercase and lowercase
```dockerfile
FROM alpine:latest AS builder
run apk --no-cache add build-base
FROM builder AS build1
copy source1.cpp source.cpp
```
✅ Good: all uppercase
```dockerfile
FROM alpine:latest AS builder
RUN apk --no-cache add build-base
FROM builder AS build1
COPY source1.cpp source.cpp
```
✅ Good: all lowercase
```dockerfile
from alpine:latest as builder
run apk --no-cache add build-base
from builder as build1
copy source1.cpp source.cpp
```
## Related errors
- [`FromAsCasing`](./from-as-casing.md)

View File

@ -0,0 +1,133 @@
//go:build ignore
// +build ignore
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path"
"regexp"
"strings"
"text/template"
"github.com/pkg/errors"
)
type Rule struct {
Name string
Description string
}
const tmplStr = `---
title: {{.Rule.Name}}
description: {{.Rule.Description}}
---
{{.Content}}
`
var destDir string
func main() {
if len(os.Args) < 2 {
panic("Please provide a destination directory")
}
destDir = os.Args[1]
log.Printf("Destination directory: %s\n", destDir)
if err := run(destDir); err != nil {
panic(err)
}
}
func run(destDir string) error {
if err := os.MkdirAll(destDir, 0700); err != nil {
return err
}
rules, err := listRules()
if err != nil {
return err
}
tmpl, err := template.New("rule").Parse(tmplStr)
if err != nil {
return err
}
for _, rule := range rules {
if ok, err := genRuleDoc(rule, tmpl); err != nil {
return errors.Wrapf(err, "Error generating docs for %s", rule.Name)
} else if ok {
log.Printf("Docs generated for %s\n", rule.Name)
}
}
return nil
}
func genRuleDoc(rule Rule, tmpl *template.Template) (bool, error) {
mdfilename := fmt.Sprintf("docs/%s.md", rule.Name)
content, err := os.ReadFile(mdfilename)
if err != nil {
return false, err
}
outputfile, err := os.Create(path.Join(destDir, fmt.Sprintf("%s.md", camelToKebab(rule.Name))))
if err != nil {
return false, err
}
defer outputfile.Close()
if err = tmpl.Execute(outputfile, struct {
Rule Rule
Content string
}{
Rule: rule,
Content: string(content),
}); err != nil {
return false, err
}
return true, nil
}
func listRules() ([]Rule, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, "ruleset.go", nil, parser.ParseComments)
if err != nil {
return nil, err
}
var rules []Rule
ast.Inspect(node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.GenDecl:
for _, spec := range x.Specs {
if vSpec, ok := spec.(*ast.ValueSpec); ok {
rule := Rule{}
if cl, ok := vSpec.Values[0].(*ast.CompositeLit); ok {
for _, elt := range cl.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
switch kv.Key.(*ast.Ident).Name {
case "Name":
if basicLit, ok := kv.Value.(*ast.BasicLit); ok {
rule.Name = strings.Trim(basicLit.Value, `"`)
}
case "Description":
if basicLit, ok := kv.Value.(*ast.BasicLit); ok {
rule.Description = strings.Trim(basicLit.Value, `"`)
}
}
}
}
}
rules = append(rules, rule)
}
}
}
return true
})
return rules, nil
}
func camelToKebab(s string) string {
var re = regexp.MustCompile(`([a-z])([A-Z])`)
return strings.ToLower(re.ReplaceAllString(s, `${1}-${2}`))
}

View File

@ -0,0 +1,46 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.21
ARG ALPINE_VERSION=3.20
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golatest
FROM golatest AS docsgen
WORKDIR /src
ENV CGO_ENABLED=0
RUN --mount=target=. \
--mount=target=/root/.cache,type=cache \
--mount=target=/go/pkg/mod,type=cache \
go build -mod=vendor -o /out/docsgen ./frontend/dockerfile/linter/generate.go
FROM alpine AS gen
RUN apk add --no-cache rsync git
WORKDIR /src
COPY --from=docsgen /out/docsgen /usr/bin
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -ex
rsync -a /context/. .
cd frontend/dockerfile/linter
docsgen ./dist
mkdir /out
cp -r dist/* /out
EOT
FROM scratch AS update
COPY --from=gen /out /
FROM gen AS validate
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
git add -A
rm -rf frontend/dockerfile/docs/rules/*
cp -rf /out/* ./frontend/dockerfile/docs/rules/
if [ -n "$(git status --porcelain -- frontend/dockerfile/docs/rules/)" ]; then
echo >&2 'ERROR: Dockerfile docs result differs. Please update with "make docs-dockerfile"'
git status --porcelain -- frontend/dockerfile/docs/rules/
exit 1
fi
EOT