diff --git a/internal/module/module.go b/internal/module/module.go index c3ce7c31..f6734e8d 100644 --- a/internal/module/module.go +++ b/internal/module/module.go @@ -395,6 +395,7 @@ func mapContainerExclusions(linterSettings *pkg.LintersSettings, configSettings configExcludes := &configSettings.Container.ExcludeRules excludes.ControllerSecurityContext = configExcludes.ControllerSecurityContext.Get() + excludes.NamespaceLabelsRule = configExcludes.NamespaceLabelsRule.Get() excludes.DNSPolicy = configExcludes.DNSPolicy.Get() excludes.PriorityClass = configExcludes.PriorityClass.Get() excludes.HostNetworkPorts = configExcludes.HostNetworkPorts.Get() diff --git a/pkg/config.go b/pkg/config.go index 7a28986b..52621565 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -345,6 +345,7 @@ type ContainerLinterRules struct { type ContainerExcludeRules struct { ControllerSecurityContext KindRuleExcludeList + NamespaceLabelsRule KindRuleExcludeList DNSPolicy KindRuleExcludeList PriorityClass KindRuleExcludeList diff --git a/pkg/config/global/global.go b/pkg/config/global/global.go index d6c969a9..3100ab54 100644 --- a/pkg/config/global/global.go +++ b/pkg/config/global/global.go @@ -46,7 +46,7 @@ type ContainerLinterConfig struct { type ContainerRules struct { RecommendedLabelsRule RuleConfig `mapstructure:"recommended-labels"` - NamespaceLabelsRule RuleConfig `mapstructure:"namespace-labels"` + NamespaceLabelsRule RuleConfig `mapstructure:"object-namespace-labels"` APIVersionRule RuleConfig `mapstructure:"api-version"` PriorityClassRule RuleConfig `mapstructure:"priority-class"` DNSPolicyRule RuleConfig `mapstructure:"dns-policy"` diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 0f4b7c75..92504005 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -57,6 +57,7 @@ type ContainerSettings struct { type ContainerExcludeRules struct { ControllerSecurityContext KindRuleExcludeList `mapstructure:"controller-security-context"` + NamespaceLabelsRule KindRuleExcludeList `mapstructure:"object-namespace-labels"` DNSPolicy KindRuleExcludeList `mapstructure:"dns-policy"` PriorityClass KindRuleExcludeList `mapstructure:"priority-class"` diff --git a/pkg/linters/container/README.md b/pkg/linters/container/README.md index bf5963ed..f9c6c095 100644 --- a/pkg/linters/container/README.md +++ b/pkg/linters/container/README.md @@ -24,7 +24,7 @@ Proper container configuration is critical for cluster stability, security, and | Rule | Description | Configurable | Default | |------|-------------|--------------|---------| | [object-recommended-labels](#object-recommended-labels) | Validates required labels (module, heritage) | ❌ | enabled | -| [object-namespace-labels](#object-namespace-labels) | Validates Prometheus watcher label on d8-* namespaces | ❌ | enabled | +| [object-namespace-labels](#object-namespace-labels) | Validates Prometheus watcher label on d8-* namespaces | ✅ | enabled | | [object-api-version](#object-api-version) | Validates API versions are not deprecated | ❌ | enabled | | [object-priority-class](#object-priority-class) | Validates PriorityClass is set and allowed | ❌ | enabled | | [dns-policy](#dns-policy) | Validates DNS policy for hostNetwork pods | ✅ | enabled | @@ -44,6 +44,8 @@ Proper container configuration is critical for cluster stability, security, and | [no-new-privileges](#no-new-privileges) | Validates containers don't allow privilege escalation | ✅ | enabled | | [seccomp-profile](#seccomp-profile) | Validates seccomp profile configuration | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### object-recommended-labels @@ -174,6 +176,22 @@ metadata: heritage: deckhouse ``` +**Configuration:** + +Exclude specific `d8-*` namespaces from this check when the Prometheus watcher label is intentionally omitted: + +```yaml +# .dmt.yaml +linters-settings: + container: + exclude-rules: + object-namespace-labels: + - kind: Namespace + name: d8-my-module +``` + +Each entry matches by Kubernetes object `kind` and `name`. When a namespace is excluded, the rule is not applied even if `PrometheusRule` resources exist in that namespace. + --- ### object-api-version @@ -344,7 +362,7 @@ spec: **Error:** ``` dnsPolicy must be `ClusterFirstWithHostNet` when hostNetwork is `true` -``` +``.dmtlint.yaml ✅ **Correct** - Proper DNS policy: @@ -443,7 +461,7 @@ spec: image: my-image ``` -✅ **Correct** - Non-root with deckhouse user: +✅ .dmtlint.yaml** - Non-root with deckhouse user: ```yaml spec: @@ -654,7 +672,7 @@ Container's SecurityContext has `ReadOnlyRootFilesystem: false`, but it must be ``` ✅ **Correct** - Read-only filesystem: - +.dmtlint.yaml ```yaml containers: - name: app @@ -724,7 +742,7 @@ spec: ``` Pod running in hostNetwork and it's container port doesn't fit the range [4200,4299] ``` - +.dmtlint.yaml ✅ **Correct** - Port in allowed range: ```yaml @@ -831,7 +849,7 @@ Using external registries bypasses Deckhouse's image verification, mirroring, an containers: - name: app image: docker.io/library/nginx:latest -``` +``.dmtlint.yaml **Error:** ``` @@ -954,7 +972,7 @@ Ephemeral storage for container is not defined in Resources.Requests ✅ **Correct** - Ephemeral storage defined: -```yaml +``.dmtlint.yaml containers: - name: app image: my-image @@ -1015,7 +1033,7 @@ containers: ``` Container ContainerSecurityContext is not defined ``` - +.dmtlint.yaml ✅ **Correct** - Security context defined: ```yaml @@ -1076,7 +1094,7 @@ containers: ``` **Error:** -``` +``.dmtlint.yaml Container uses port <= 1024 ``` @@ -1182,7 +1200,7 @@ containers: periodSeconds: 20 ``` -✅ **Correct** - Exec liveness probe: +✅ .dmtlint.yaml** - Exec liveness probe: ```yaml containers: @@ -1285,7 +1303,7 @@ containers: tcpSocket: port: 8080 initialDelaySeconds: 5 - periodSeconds: 10 + .dmtlint.yamldSeconds: 10 ``` ✅ **Correct** - GRPC readiness probe: @@ -1342,7 +1360,7 @@ containers: allowPrivilegeEscalation: true # ❌ Allows privilege escalation ``` -**Error:** +**.dmtlint.yaml ``` Container allows privilege escalation (allowPrivilegeEscalation is true) ``` @@ -1417,7 +1435,7 @@ containers: ``` **Error:** -``` +``.dmtlint.yaml Container has seccompProfile.type set to 'Unconfined' which disables seccomp filtering and poses security risks - use 'RuntimeDefault' instead ``` @@ -1436,7 +1454,7 @@ containers: ```yaml # .dmt.yaml -linters-settings: +li.dmtlint.yamltings: container: exclude-rules: seccomp-profile: @@ -1453,7 +1471,7 @@ The Container linter can be configured at both the module level and for individu Configure the overall impact level for the container linter: -```yaml +``.dmtlint.yaml # .dmt.yaml linters-settings: container: @@ -1511,7 +1529,7 @@ linters-settings: container: reserve-resources image-digest: - - kind: Deployment + .dmtlint.yamlind: Deployment name: okmeter container: okagent @@ -1568,10 +1586,10 @@ linters-settings: liveness-probe: - kind: Deployment - name: standby-holder-name + name: standb.dmtlint.yamlname container: reserve-resources - readiness-probe: + readiness-prob.dmtlint.yaml - kind: Deployment name: standby-holder-name container: reserve-resources diff --git a/pkg/linters/container/rules.go b/pkg/linters/container/rules.go index 03824ccb..d386bcb0 100644 --- a/pkg/linters/container/rules.go +++ b/pkg/linters/container/rules.go @@ -28,7 +28,8 @@ func (l *Container) applyContainerRules(object storage.StoreObject, storageMap m errorList = errorList.WithFilePath(object.GetPath()) rules.NewRecommendedLabelsRule().ObjectRecommendedLabels(object, errorList.WithMaxLevel(l.cfg.Rules.RecommendedLabelsRule.GetLevel())) - rules.NewNamespaceLabelsRule().ObjectNamespaceLabels(object, storageMap, errorList.WithMaxLevel(l.cfg.Rules.NamespaceLabelsRule.GetLevel())) + rules.NewNamespaceLabelsRule(l.cfg.ExcludeRules.NamespaceLabelsRule.Get()). + ObjectNamespaceLabels(object, storageMap, errorList.WithMaxLevel(l.cfg.Rules.NamespaceLabelsRule.GetLevel())) rules.NewAPIVersionRule().ObjectAPIVersion(object, errorList.WithMaxLevel(l.cfg.Rules.APIVersionRule.GetLevel())) rules.NewPriorityClassRule(l.cfg.ExcludeRules.PriorityClass.Get()).ObjectPriorityClass(object, errorList.WithMaxLevel(l.cfg.Rules.PriorityClassRule.GetLevel())) rules.NewDNSPolicyRule(l.cfg.ExcludeRules.DNSPolicy.Get()). diff --git a/pkg/linters/container/rules/namespace_labels.go b/pkg/linters/container/rules/namespace_labels.go index a370dbb3..01c02a22 100644 --- a/pkg/linters/container/rules/namespace_labels.go +++ b/pkg/linters/container/rules/namespace_labels.go @@ -28,16 +28,20 @@ const ( NamespaceLabelsRuleName = "object-namespace-labels" ) -func NewNamespaceLabelsRule() *NamespaceLabelsRule { +func NewNamespaceLabelsRule(excludeRules []pkg.KindRuleExclude) *NamespaceLabelsRule { return &NamespaceLabelsRule{ RuleMeta: pkg.RuleMeta{ Name: NamespaceLabelsRuleName, }, + KindRule: pkg.KindRule{ + ExcludeRules: excludeRules, + }, } } type NamespaceLabelsRule struct { pkg.RuleMeta + pkg.KindRule } func (r *NamespaceLabelsRule) ObjectNamespaceLabels(object storage.StoreObject, storageMap map[storage.ResourceIndex]storage.StoreObject, errorList *errors.LintRuleErrorsList) { @@ -49,6 +53,11 @@ func (r *NamespaceLabelsRule) ObjectNamespaceLabels(object storage.StoreObject, namespaceName := object.Unstructured.GetName() + if !r.Enabled(object.Unstructured.GetKind(), namespaceName) { + // TODO: add metrics + return + } + hasPrometheusRules := false for _, obj := range storageMap { diff --git a/pkg/linters/docs/README.md b/pkg/linters/docs/README.md index c2ff6b59..b8498249 100644 --- a/pkg/linters/docs/README.md +++ b/pkg/linters/docs/README.md @@ -15,6 +15,8 @@ Proper documentation is critical for Deckhouse modules as it helps users underst | [cyrillic-in-english](#cyrillic-in-english) | Validates English documentation doesn't contain cyrillic characters | ✅ | enabled | | [no-lang-key](#no-lang-key) | Validates documentation front matter doesn't contain `lang` key | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### readme @@ -91,7 +93,7 @@ This module provides... **Configuration:** To disable this rule for specific modules: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -172,7 +174,7 @@ my-module/ **Configuration:** To disable bilingual checks for specific files: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -287,7 +289,7 @@ Line 42: Check the документация for more details. **Configuration:** To exclude specific files from this check: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -377,7 +379,7 @@ webIfaces: **Configuration:** To exclude specific files from this check: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -397,7 +399,7 @@ The Documentation linter can be configured at both the module level and for indi ### Module-Level Settings Configure the overall impact level for the documentation linter: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -414,7 +416,7 @@ linters-settings: ### Path-Based Exclusions Exclude specific modules or files from validation: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -499,7 +501,7 @@ File: docs/CONFIGURATION.md ``` 3. **Exclude if translation is not needed (not recommended):** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: diff --git a/pkg/linters/hooks/README.md b/pkg/linters/hooks/README.md index 7685a8fc..2c136d7e 100644 --- a/pkg/linters/hooks/README.md +++ b/pkg/linters/hooks/README.md @@ -12,6 +12,8 @@ Hooks are Go or Python scripts that react to Kubernetes resource changes and imp |------|-------------|--------------|---------| | [ingress](#ingress) | Validates copy_custom_certificate hook presence for Ingress resources | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### ingress @@ -85,7 +87,7 @@ func init() { ``` **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -95,7 +97,7 @@ linters-settings: ``` To disable this rule: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -111,7 +113,7 @@ The Hooks linter can be configured at both the module level and for individual r ### Module-Level Settings Configure the overall impact level for the hooks linter: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -128,7 +130,7 @@ linters-settings: ### Rule-Level Settings Each rule can be individually configured: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -139,7 +141,7 @@ linters-settings: ``` ### Complete Configuration Example - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -152,10 +154,10 @@ linters-settings: disable: false ``` -### Configuration in Module Directory +### Configuration in M.dmtlint.yamlectory You can also place a `.dmt.yaml` configuration file directly in your module directory for module-specific settings: - +.dmtlint.yaml ```yaml # modules/my-module/.dmt.yaml linters-settings: @@ -199,7 +201,7 @@ Error: Ingress resource exists but module does not have copy_custom_certificate ``` 2. **If your Ingress doesn't need custom certificates:** Disable the rule for this module: - +.dmtlint.yaml ```yaml # modules/my-module/.dmt.yaml linters-settings: diff --git a/pkg/linters/images/README.md b/pkg/linters/images/README.md index cabdfe78..01c4b4af 100644 --- a/pkg/linters/images/README.md +++ b/pkg/linters/images/README.md @@ -17,6 +17,8 @@ The Images linter includes **4 validation rules**: | [**werf**](#werf) | Validates werf.yaml configuration | ✅ Yes | | [**patches**](#patches) | Validates patch file structure and documentation | ✅ Yes | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + --- ## Rule Details diff --git a/pkg/linters/module/README.md b/pkg/linters/module/README.md index 2d3fa90c..54541822 100644 --- a/pkg/linters/module/README.md +++ b/pkg/linters/module/README.md @@ -21,6 +21,8 @@ The Module linter includes **8 validation rules**: | [**package-yaml**](#package-yaml) | Validates `package.yaml` metadata and new requirements schema | ✅ Yes | | [**legacy-release-file**](#legacy-release-file) | Checks for deprecated `release.yaml` file | ❌ No | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + --- ## Rule Details diff --git a/pkg/linters/no-cyrillic/README.md b/pkg/linters/no-cyrillic/README.md index 74207b2d..124fbbcf 100644 --- a/pkg/linters/no-cyrillic/README.md +++ b/pkg/linters/no-cyrillic/README.md @@ -12,6 +12,8 @@ Source code, configuration files, and technical documentation should use English |------|-------------|--------------|---------| | [files](#files) | Validates source files don't contain cyrillic characters | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### files @@ -224,7 +226,7 @@ Visual pointer: ^^^^^^^^^^^^ Each `^` points to a cyrillic character, making it easy to find and replace them. **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -233,7 +235,7 @@ linters-settings: ``` To exclude specific files: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -245,7 +247,7 @@ linters-settings: ``` To exclude entire directories: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -258,7 +260,7 @@ linters-settings: ``` Combined example: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -280,7 +282,7 @@ The No-Cyrillic linter can be configured at the module level with path-based exc ### Module-Level Settings Configure the overall impact level for the no-cyrillic linter: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -297,7 +299,7 @@ linters-settings: ### File Exclusions Exclude specific files from cyrillic checking: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -317,7 +319,7 @@ linters-settings: ### Directory Exclusions Exclude entire directories from cyrillic checking: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -336,7 +338,7 @@ linters-settings: - All files within excluded directories are skipped ### Complete Configuration Example - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -361,10 +363,10 @@ linters-settings: - tools/migration/ ``` -### Configuration in Module Directory +### Configuration in M.dmtlint.yamlectory You can also place a `.dmt.yaml` configuration file directly in your module directory: - +.dmtlint.yaml ```yaml # modules/my-module/.dmt.yaml linters-settings: diff --git a/pkg/linters/openapi/README.md b/pkg/linters/openapi/README.md index 27a5dd02..84e4a710 100644 --- a/pkg/linters/openapi/README.md +++ b/pkg/linters/openapi/README.md @@ -16,6 +16,8 @@ Proper OpenAPI schema validation is critical for module configuration, ensuring | [deckhouse-crds](#deckhouse-crds) | Validates Deckhouse CRD structure and metadata | ✅ | enabled | | [bilingual](#bilingual) | Validates translation files (`doc-ru-`) exist for OpenAPI and CRD files | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### enum @@ -160,7 +162,7 @@ properties: ``` **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -169,7 +171,7 @@ linters-settings: ``` To exclude specific enum fields: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -181,7 +183,7 @@ linters-settings: ``` To exclude enum fields with array wildcards: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -281,7 +283,7 @@ properties: ``` **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -374,7 +376,7 @@ properties: **Configuration:** Define which names should be banned in enum values: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -593,7 +595,7 @@ spec: ``` **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -681,7 +683,7 @@ crds/ ``` **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -698,7 +700,7 @@ The OpenAPI linter can be configured at the module level with rule-specific excl ### Module-Level Settings Configure the overall impact level for the openapi linter: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -719,7 +721,7 @@ Each rule supports excluding specific schema paths or CRD names: #### Enum Rule Exclusions Exclude specific enum fields by their schema path: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -740,7 +742,7 @@ linters-settings: #### High Availability Exclusions Exclude specific highAvailability fields: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -754,7 +756,7 @@ linters-settings: #### Key Banned Names Configuration Define which property names are banned in enum values: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -771,7 +773,7 @@ linters-settings: #### CRD Exclusions Exclude specific CRDs from validation: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -784,7 +786,7 @@ linters-settings: ``` ### Complete Configuration Example - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -816,10 +818,10 @@ linters-settings: - experimental-resources.deckhouse.io ``` -### Configuration in Module Directory +### Configuration in M.dmtlint.yamlectory You can also place a `.dmt.yaml` configuration file directly in your module directory: - +.dmtlint.yaml ```yaml # modules/my-module/.dmt.yaml linters-settings: @@ -1147,7 +1149,7 @@ Error: enum 'properties.items[5].properties.type.enum' is invalid: value 'custom ``` 2. **Exclude specific path if needed:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: diff --git a/pkg/linters/rbac/README.md b/pkg/linters/rbac/README.md index dd3781b3..e79d1b0d 100644 --- a/pkg/linters/rbac/README.md +++ b/pkg/linters/rbac/README.md @@ -15,6 +15,8 @@ Proper RBAC configuration is critical for Kubernetes security, ensuring least-pr | [placement](#placement) | Validates RBAC resource placement and naming conventions | ✅ | enabled | | [wildcards](#wildcards) | Validates Roles/ClusterRoles don't use wildcard permissions | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmt.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### user-authz @@ -328,7 +330,7 @@ roleRef: - `log-shipper` in `d8-log-shipper` (when module is `loki`) - For log collection **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -687,7 +689,7 @@ rules: | Role | rbac-to-us.yaml | Any | `access-to--` | **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -892,7 +894,7 @@ If you find yourself wanting to use wildcards, consider: 4. **Review actual needs** - Often you don't need as much access as you think **Configuration:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -912,7 +914,7 @@ The RBAC linter can be configured at the module level with rule-specific exclusi ### Module-Level Settings Configure the overall impact level for the rbac linter: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -931,7 +933,7 @@ linters-settings: #### Binding Subject Exclusions Exclude specific ServiceAccount names from validation: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -947,7 +949,7 @@ linters-settings: #### Placement Exclusions Exclude specific RBAC resources from placement validation: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -965,7 +967,7 @@ linters-settings: #### Wildcards Exclusions Exclude specific Roles/ClusterRoles from wildcard validation: - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -981,7 +983,7 @@ linters-settings: ``` ### Complete Configuration Example - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -1012,10 +1014,10 @@ linters-settings: name: d8:my-module:admin ``` -### Configuration in Module Directory +### Configuration in M.dmtlint.yamlectory You can also place a `.dmt.yaml` configuration file directly in your module directory: - +.dmtlint.yaml ```yaml # modules/my-module/.dmt.yaml linters-settings: @@ -1072,7 +1074,7 @@ Error: ClusterRoleBinding bind to the wrong ServiceAccount (doesn't exist in the Ensure the name in subjects matches the ServiceAccount definition exactly. 3. **Exclude the ServiceAccount from validation:** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: @@ -1209,7 +1211,7 @@ Error: apiGroups, resources, verbs contains a wildcards. Replace them with an ex ``` 3. **Exclude if wildcards are absolutely necessary (not recommended):** - +.dmtlint.yaml ```yaml # .dmt.yaml linters-settings: diff --git a/pkg/linters/templates/README.md b/pkg/linters/templates/README.md index 2d804420..054d7f48 100644 --- a/pkg/linters/templates/README.md +++ b/pkg/linters/templates/README.md @@ -22,6 +22,8 @@ Proper template validation prevents runtime issues, ensures applications are pro | [werf](#werf) | Validates image names in `werf.yaml` do not contain underscores | ❌ | enabled | | [enabled-modules](#enabled-modules) | Detects usage of `.Values.global.enabledModules` in templates | ✅ | enabled | +"Configurable" means that this rule can be configured using the `.dmtlint.yaml` file, including customizing the rule's parameters and/or disabling the rule. + ## Rule Details ### vpa @@ -323,7 +325,7 @@ spec: **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -576,7 +578,7 @@ spec: **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -682,7 +684,7 @@ data: **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -863,7 +865,7 @@ spec: **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -996,7 +998,7 @@ spec: **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -1158,7 +1160,7 @@ monitoring/ **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: prometheus-rules: @@ -1275,7 +1277,7 @@ monitoring/ **Configuration:** ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: grafana-dashboards: @@ -1631,7 +1633,7 @@ Consider using (.Capabilities.APIVersions.Has "group/version/Kind") instead. The rule supports excluding specific files and directories (paths are relative to the module root): ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -1651,7 +1653,7 @@ The Templates linter can be configured at the module level with rule-specific se Configure the overall impact level and individual rule toggles: ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: # Overall impact level @@ -1670,7 +1672,7 @@ linters-settings: Each rule can override the overall impact level individually via the `rules` block: ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: rules: @@ -1701,7 +1703,7 @@ linters-settings: Configure exclusions for specific rules: ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -1749,7 +1751,7 @@ linters-settings: ### Complete Configuration Example ```yaml -# .dmt.yaml +# .dmtlint.yaml linters-settings: templates: # Global impact level @@ -1790,10 +1792,10 @@ linters-settings: ### Configuration in Module Directory -Place `.dmt.yaml` in your module directory for module-specific settings: +Place `.dmtlint.yaml` in your module directory for module-specific settings: ```yaml -# modules/my-module/.dmt.yaml +# modules/my-module/.dmtlint.yaml linters-settings: templates: impact: warning # More lenient for this module @@ -1849,7 +1851,7 @@ Error: No VPA is found for object 2. **Exclude the controller from VPA validation:** ```yaml - # .dmt.yaml + # .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -1934,7 +1936,7 @@ Error: No PodDisruptionBudget found for controller 2. **Exclude from PDB validation:** ```yaml - # .dmt.yaml + # .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -2034,7 +2036,7 @@ Object: namespace = d8-my-module 2. **Exclude namespace from validation:** ```yaml - # .dmt.yaml + # .dmtlint.yaml linters-settings: templates: exclude-rules: @@ -2064,7 +2066,7 @@ Error: Ingress annotation "nginx.ingress.kubernetes.io/configuration-snippet" do 2. **Exclude Ingress:** ```yaml - # .dmt.yaml + # .dmtlint.yaml linters-settings: templates: exclude-rules: diff --git a/test/e2e/README.md b/test/e2e/README.md index b4119939..677c861b 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -58,6 +58,9 @@ kind: lint # "lint" (default) or "conversions" module: module # subdir to lint, defaults to "module" expectClean: false # assert the run produced zero findings exhaustive: false # assert there are NO findings beyond those listed +expectPass: # rules that must not produce any matching findings + - linter: container + rule: object-namespace-labels expect: - linter: container # required, matched case-insensitively rule: env-variables-duplicates # optional @@ -74,6 +77,9 @@ Matching semantics: - `count` is the expected number of matching findings. `0` (or omitting it) means "at least one". - `expectClean: true` asserts the module produced no findings at all. +- `expectPass` lists rules that must not produce any matching findings. `linter` + is required; `rule`, `level` and `textContains` are optional filters using the + same matching rules as `expect`. - `exhaustive: true` asserts that every produced finding is matched by some entry in `expect` (use it to lock down the complete output of a fixture). diff --git a/test/e2e/framework.go b/test/e2e/framework.go index 23ba035f..e7cfaaf6 100644 --- a/test/e2e/framework.go +++ b/test/e2e/framework.go @@ -114,6 +114,8 @@ type CaseSpec struct { // same matching semantics as Expect (linter required; rule/level/textContains // optional); the case fails if any produced finding matches. ExpectAbsent []Finding `yaml:"expectAbsent"` + // ExpectPass lists rules that must not produce any matching findings. + ExpectPass []Finding `yaml:"expectPass"` // Exhaustive, when true, asserts that there are no findings beyond those // listed in Expect (every produced finding must be matched by some Finding). Exhaustive bool `yaml:"exhaustive"` @@ -349,6 +351,21 @@ func Match(spec *CaseSpec, findings []pkg.LinterError) MatchResult { } } + for _, pass := range spec.ExpectPass { + var hits int + + for i := range findings { + if findingMatches(pass, findings[i]) { + hits++ + } + } + + if hits > 0 { + res.Failures = append(res.Failures, + fmt.Sprintf("expected rule to pass for [%s], got %d finding(s)", pass, hits)) + } + } + return res } diff --git a/test/e2e/framework_test.go b/test/e2e/framework_test.go new file mode 100644 index 00000000..80ec4615 --- /dev/null +++ b/test/e2e/framework_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/deckhouse/dmt/pkg" +) + +func TestMatchExpectPass(t *testing.T) { + t.Parallel() + + findings := []pkg.LinterError{ + {LinterID: "container", RuleID: "object-recommended-labels", Level: pkg.Error, Text: `missing "module"`}, + } + + spec := &CaseSpec{ + Expect: []Finding{ + {Linter: "container", Rule: "object-recommended-labels"}, + }, + ExpectPass: []Finding{ + {Linter: "container", Rule: "object-namespace-labels"}, + }, + } + + require.True(t, Match(spec, findings).OK()) +} + +func TestMatchExpectPassFailsWhenFindingPresent(t *testing.T) { + t.Parallel() + + findings := []pkg.LinterError{ + {LinterID: "container", RuleID: "object-namespace-labels", Level: pkg.Error, Text: "missing label"}, + } + + spec := &CaseSpec{ + ExpectPass: []Finding{ + {Linter: "container", Rule: "object-namespace-labels"}, + }, + } + + result := Match(spec, findings) + require.False(t, result.OK()) + require.Contains(t, result.Failures[0], "expected rule to pass") +} + +func TestMatchExpectPassFiltersByLevelAndText(t *testing.T) { + t.Parallel() + + findings := []pkg.LinterError{ + {LinterID: "container", RuleID: "object-namespace-labels", Level: pkg.Ignored, Text: "ignored issue"}, + } + + spec := &CaseSpec{ + ExpectPass: []Finding{ + {Linter: "container", Rule: "object-namespace-labels", Level: "error"}, + }, + } + + require.True(t, Match(spec, findings).OK()) +} diff --git a/test/e2e/testdata/container/ignore-namespace-labels/expected.yaml b/test/e2e/testdata/container/ignore-namespace-labels/expected.yaml new file mode 100644 index 00000000..a3ca45cf --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/expected.yaml @@ -0,0 +1,7 @@ +description: > + A namespace with a name starting with "d8-" should be ignored by the + container linter on the object-namespace-labels rule. +module: module +expectPass: + - linter: container + rule: object-namespace-labels diff --git a/test/e2e/testdata/container/ignore-namespace-labels/module/.dmtlint.yaml b/test/e2e/testdata/container/ignore-namespace-labels/module/.dmtlint.yaml new file mode 100644 index 00000000..7d12533b --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/module/.dmtlint.yaml @@ -0,0 +1,10 @@ +linters-settings: + container: + exclude-rules: + resources: + - kind: Deployment + name: m-s-test + container: m-s-test + object-namespace-labels: + - kind: Namespace + name: d8-e2e-app \ No newline at end of file diff --git a/test/e2e/testdata/container/ignore-namespace-labels/module/module.yaml b/test/e2e/testdata/container/ignore-namespace-labels/module/module.yaml new file mode 100644 index 00000000..04d7ef2d --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/module/module.yaml @@ -0,0 +1,2 @@ +name: e2e-container-issues +namespace: e2e-container-issues diff --git a/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/config-values.yaml b/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/config-values.yaml new file mode 100644 index 00000000..03b0d8bf --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/config-values.yaml @@ -0,0 +1,2 @@ +type: object +properties: {} diff --git a/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/values.yaml b/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/values.yaml new file mode 100644 index 00000000..47180da5 --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/module/openapi/values.yaml @@ -0,0 +1,4 @@ +x-extend: + schema: config-values.yaml +type: object +properties: {} diff --git a/test/e2e/testdata/container/ignore-namespace-labels/module/templates/manifests.yaml b/test/e2e/testdata/container/ignore-namespace-labels/module/templates/manifests.yaml new file mode 100644 index 00000000..38b06bd6 --- /dev/null +++ b/test/e2e/testdata/container/ignore-namespace-labels/module/templates/manifests.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: d8-e2e-app + labels: + module: e2e-app + heritage: deckhouse + # Missing: prometheus.deckhouse.io/rules-watcher-enabled +--- +apiVersion: v1 +kind: PrometheusRule +metadata: + name: e2e-app + namespace: d8-e2e-app diff --git a/test/e2e/testdata/container/namespace-labels/expected.yaml b/test/e2e/testdata/container/namespace-labels/expected.yaml new file mode 100644 index 00000000..cd43569a --- /dev/null +++ b/test/e2e/testdata/container/namespace-labels/expected.yaml @@ -0,0 +1,9 @@ +description: > + A namespace with a name starting with "d8-" should be flagged by the + container linter on the object-namespace-labels rule. +module: module +expect: + - linter: container + rule: object-namespace-labels + level: error + textContains: 'Namespace object does not have the label "prometheus.deckhouse.io/rules-watcher-enabled"' \ No newline at end of file diff --git a/test/e2e/testdata/container/namespace-labels/module/module.yaml b/test/e2e/testdata/container/namespace-labels/module/module.yaml new file mode 100644 index 00000000..04d7ef2d --- /dev/null +++ b/test/e2e/testdata/container/namespace-labels/module/module.yaml @@ -0,0 +1,2 @@ +name: e2e-container-issues +namespace: e2e-container-issues diff --git a/test/e2e/testdata/container/namespace-labels/module/openapi/config-values.yaml b/test/e2e/testdata/container/namespace-labels/module/openapi/config-values.yaml new file mode 100644 index 00000000..03b0d8bf --- /dev/null +++ b/test/e2e/testdata/container/namespace-labels/module/openapi/config-values.yaml @@ -0,0 +1,2 @@ +type: object +properties: {} diff --git a/test/e2e/testdata/container/namespace-labels/module/openapi/values.yaml b/test/e2e/testdata/container/namespace-labels/module/openapi/values.yaml new file mode 100644 index 00000000..47180da5 --- /dev/null +++ b/test/e2e/testdata/container/namespace-labels/module/openapi/values.yaml @@ -0,0 +1,4 @@ +x-extend: + schema: config-values.yaml +type: object +properties: {} diff --git a/test/e2e/testdata/container/namespace-labels/module/templates/manifests.yaml b/test/e2e/testdata/container/namespace-labels/module/templates/manifests.yaml new file mode 100644 index 00000000..9fef5a6b --- /dev/null +++ b/test/e2e/testdata/container/namespace-labels/module/templates/manifests.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: d8-{{ .Chart.Name }} + labels: + module: e2e-app + heritage: deckhouse + # Missing: prometheus.deckhouse.io/rules-watcher-enabled +--- +apiVersion: v1 +kind: PrometheusRule +metadata: + name: e2e-app + namespace: d8-{{ .Chart.Name }}