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:
parent
49b935f77b
commit
7283eaba0d
4
Makefile
4
Makefile
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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)
|
133
frontend/dockerfile/linter/generate.go
Normal file
133
frontend/dockerfile/linter/generate.go
Normal 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}`))
|
||||
}
|
46
hack/dockerfiles/docs-dockerfile.Dockerfile
Normal file
46
hack/dockerfiles/docs-dockerfile.Dockerfile
Normal 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
|
Loading…
x
Reference in New Issue
Block a user