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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,37 @@ linters-settings:
- /var/lib/kubelet
```

#### Per-scope settings

`linters-settings` configures the source tree. The two images a published module
consists of are configured separately, under `remote`: they carry different files
and are linted by different rules, so each gets its own section.

```yaml
linters-settings: # dmt lint <dir>
openapi:
impact: error

remote:
bundle: # dmt lint remote <repo>:<tag>
documentation:
rules:
changelog:
impact: warn
release: # ... the same command, <repo>/release:<tag>
module:
rules:
release-layout:
impact: error
```

A `remote` section has the same shape as `global.linters-settings` — a linter
`impact` and per-rule impacts. `exclude-rules` are not read there.

The sections are independent: nothing from `linters-settings` or `global` reaches
a remote scope, and a section left out means the built-in severities rather than
the ones the source tree happens to be tuned to.

### Rule: mount-points

The `mount-points` rule validates that volume mounts in pod controllers match the declarations in `mount-points.yaml` files (and vice versa). It runs in two directions:
Expand Down Expand Up @@ -317,6 +348,40 @@ dmt lint ./my-module --values-file custom-values.yaml
dmt lint ./my-module --log-level debug
```

#### Lint Remote Command

```bash
dmt lint remote <repo>:<tag> [flags]
```

Lints the published images instead of a directory: the bundle at `<repo>:<tag>`
and the release at `<repo>/release:<tag>`.

**Flags:**
- `--login` / `--password`: Registry credentials

Credentials are resolved in this order: the flags, then the `DMT_REGISTRY_LOGIN` /
`DMT_REGISTRY_PASSWORD` environment variables, then the Docker config
(`~/.docker/config.json`), then anonymous access. Each field falls back on its own, so
the login can come from a flag and the password from a secret. In CI prefer the
environment variables — a password passed as a flag shows up in the process list and
in the job log.

The `lint` flags are inherited, so `--linter`, `--hide-warnings`, `--show-ignored` and
`--log-level` apply here too.

**Example:**
```bash
dmt lint remote registry.example.com/my-module:v0.0.1

# Lint only the module linter in both published images
dmt lint remote registry.example.com/my-module:v0.0.1 --linter module

# CI: credentials from the environment
DMT_REGISTRY_LOGIN=license-token DMT_REGISTRY_PASSWORD="$REGISTRY_TOKEN" \
dmt lint remote registry.example.com/my-module:v0.0.1
```

#### Bootstrap Command

```bash
Expand Down
29 changes: 18 additions & 11 deletions cmd/dmt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"runtime"
Expand All @@ -34,14 +35,16 @@ import (
"github.com/deckhouse/dmt/internal/metrics"
"github.com/deckhouse/dmt/internal/version"
"github.com/deckhouse/dmt/pkg/config"
"github.com/deckhouse/dmt/pkg/scopes"
)

func main() {
execute()
}

func runLint(ctx context.Context, dir string) error {
// runLint is the whole of a lint run: everything but where the modules come from,
// which is the source's business. Both `dmt lint <dir>` and `dmt lint remote <ref>`
// enter here, so setup and teardown exist once.
func runLint(ctx context.Context, src manager.Source) error {
if flags.PprofFile != "" {
log.Info("Profiling enabled", slog.String("file", flags.PprofFile))

Expand Down Expand Up @@ -76,16 +79,20 @@ func runLint(ctx context.Context, dir string) error {

log.Info("DMT version", slog.String("version", version.Version), slog.String("commit", version.Commit), slog.String("date", version.Date))

dir := src.ConfigDir()

cfg, err := config.NewDefaultRootConfig(dir)
if err != nil {
log.Fatal("default root config", log.Err(err)) //nolint:gocritic
return fmt.Errorf("default root config: %w", err)
}

// init metrics storage, should be done before running manager
metrics.GetClient(dir)

mng := manager.NewManager(dir, cfg, scopes.Static)
mng.Run(ctx)
mng := manager.New(cfg, src)
defer mng.Close()

sourceErr := mng.Run(ctx)

if flags.Fix {
mng.ApplyFixes()
Expand All @@ -94,13 +101,13 @@ func runLint(ctx context.Context, dir string) error {
mng.PrintResult()
mng.PrintStatistics()

metrics.SetDmtInfo()
metrics.SetLinterWarningsMetrics(cfg.GlobalSettings)
metrics.SetDmtRuntimeDuration()
metrics.SetDmtRuntimeDurationSeconds()
metrics.Flush(ctx, mng.MetricsSections()...)

metricsClient := metrics.GetClient(dir)
metricsClient.Send(context.WithoutCancel(ctx))
// A source failure — a registry that would not answer — is reported after the
// findings, never instead of them: whatever was linted still has to be seen.
if sourceErr != nil {
return sourceErr
}

if mng.HasCriticalErrors() {
return errors.New("critical errors found")
Expand Down
39 changes: 37 additions & 2 deletions cmd/dmt/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import (
"github.com/deckhouse/dmt/internal/flags"
"github.com/deckhouse/dmt/internal/fsutils"
"github.com/deckhouse/dmt/internal/rendercmd"
"github.com/deckhouse/dmt/internal/sources/remote"
"github.com/deckhouse/dmt/internal/sources/static"
"github.com/deckhouse/dmt/internal/test"
"github.com/deckhouse/dmt/internal/version"
"github.com/deckhouse/dmt/pkg/config"
Expand Down Expand Up @@ -148,7 +150,40 @@ func execute() {
},
}

lintCmd.Flags().AddFlagSet(flags.InitLintFlagSet())
remoteCmd := &cobra.Command{
Use: "remote <repo>:<tag>",
Short: "lint the published images instead of a directory",
Long: `Lints a module as it was published: pulls the bundle image at <repo>:<tag> and
the release image at <repo>/release:<tag>, and runs the scopes that belong to
them. Severities come from the 'remote.bundle' and 'remote.release' sections of
the config next to the caller.`,
Example: " dmt lint remote registry.example.com/my-module:v0.0.1",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
// --fix comes down from `lint`, but there is nothing here to fix: the tree
// is an extracted image that is thrown away at the end of the run, and
// applying a fix would drop the finding from the report for nothing.
if flags.Fix {
return errors.New("--fix is not supported for remote lint")
}

src, err := remote.NewSource(args[0], &remote.Options{
Login: flags.RemoteLogin,
Password: flags.RemotePassword,
})
if err != nil {
return err
}

return runLint(cmd.Context(), src)
},
}
remoteCmd.Flags().AddFlagSet(flags.InitRemoteFlagSet())

// Persistent, so 'lint remote' inherits --log-level and friends.
lintCmd.PersistentFlags().AddFlagSet(flags.InitLintFlagSet())
lintCmd.AddCommand(remoteCmd)
bootstrapCmd.Flags().AddFlagSet(flags.InitBootstrapFlagSet())

testCmd := &cobra.Command{
Expand Down Expand Up @@ -296,7 +331,7 @@ func runLintMultiple(ctx context.Context, dirs []string) error {
log.Info("Processing directory", slog.String("directory", expandedDir))

// Run lint for this directory as a separate execution
if err := runLint(ctx, expandedDir); err != nil {
if err := runLint(ctx, static.NewSource(expandedDir)); err != nil {
log.Error("Error processing directory", slog.String("directory", expandedDir), log.Err(err))

hasErrors = true
Expand Down
14 changes: 12 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/Masterminds/sprig/v3 v3.3.0
github.com/bmatcuk/doublestar v1.3.4
github.com/deckhouse/deckhouse/pkg/log v0.2.1
github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260831072828-0356cb79de29
github.com/fatih/color v1.19.0
github.com/go-openapi/spec v0.22.4
github.com/gogo/protobuf v1.3.2
Expand Down Expand Up @@ -80,17 +81,18 @@ require (
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.4.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/dominikbraun/graph v0.23.0 // indirect
Expand Down Expand Up @@ -214,6 +216,7 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/wI2L/jsondiff v0.5.0 // indirect
github.com/werf/common-go v0.0.0-20251113140850-a1a98e909e9b // indirect
github.com/werf/kubedog v0.13.1-0.20260616105957-2c00b08fb99e // indirect
Expand Down Expand Up @@ -277,3 +280,10 @@ require (
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

// deckhouse/pkg/registry pulls docker/cli v29, whose types.AuthConfig no longer
// converts from docker/docker's registry.AuthConfig — which breaks oras.land/oras-go,
// reached through werf/nelm and unfixed on every v1.2.x. docker/cli is an indirect
// requirement of the registry client (go-containerregistry's Docker-config keychain),
// so holding it at the version the rest of the tree already builds against is enough.
replace github.com/docker/cli => github.com/docker/cli v27.1.1+incompatible
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
Expand All @@ -131,6 +133,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckhouse/deckhouse/pkg/log v0.2.1 h1:7SSD+QJPnziAO3l8ycgRwN5wlQeC312Tf7f2Wa/hWDg=
github.com/deckhouse/deckhouse/pkg/log v0.2.1/go.mod h1:pbAxTSDcPmwyl3wwKDcEB3qdxHnRxqTV+J0K+sha8bw=
github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260831072828-0356cb79de29 h1:Zy/Lj8Sj6ZQ0pXzAN0uyChezenIcgPVJUP8I/1OsR38=
github.com/deckhouse/deckhouse/pkg/registry v0.0.0-20260831072828-0356cb79de29/go.mod h1:KDf44MqEif8jAKCehKJqOg0k4sJcnetKJKDGd0IFQjI=
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
Expand All @@ -154,8 +158,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
Expand Down Expand Up @@ -398,8 +402,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
Expand Down Expand Up @@ -635,6 +639,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
github.com/wI2L/jsondiff v0.5.0 h1:RRMTi/mH+R2aXcPe1VYyvGINJqQfC3R+KSEakuU1Ikw=
Expand Down
14 changes: 14 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ var (
LinterName string
)

var (
RemoteLogin string
RemotePassword string
)

var (
PrintVersion bool
Version string
Expand Down Expand Up @@ -106,6 +111,15 @@ func InitLintFlagSet() *pflag.FlagSet {
return lint
}

func InitRemoteFlagSet() *pflag.FlagSet {
remote := pflag.NewFlagSet("remote", pflag.ContinueOnError)

remote.StringVar(&RemoteLogin, "login", "", "registry login (defaults to $DMT_REGISTRY_LOGIN, then the Docker config)")
remote.StringVar(&RemotePassword, "password", "", "registry password (defaults to $DMT_REGISTRY_PASSWORD, then the Docker config)")

return remote
}

func InitBootstrapFlagSet() *pflag.FlagSet {
bootstrap := pflag.NewFlagSet("bootstrap", pflag.ContinueOnError)

Expand Down
Loading
Loading