Skip to content

Commit 19b7de9

Browse files
authored
[dmt] Create changelog rule (#318)
Signed-off-by: Sinelnikov Michail <mikhail.sinelnikov@flant.com> Co-authored-by: Sinelnikov Michail <mikhail.sinelnikov@flant.com>
1 parent 5a9566c commit 19b7de9

File tree

7 files changed

+201
-1
lines changed

7 files changed

+201
-1
lines changed

internal/module/module.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ func mapModuleRules(linterSettings *pkg.LintersSettings, configSettings *config.
311311
rules.LicenseRule.SetLevel(globalRules.LicenseRule.Impact, fallbackImpact)
312312
rules.RequarementsRule.SetLevel(globalRules.RequarementsRule.Impact, fallbackImpact)
313313
rules.LegacyReleaseFileRule.SetLevel(globalRules.LegacyReleaseFileRule.Impact, fallbackImpact)
314+
rules.ChangelogRule.SetLevel(globalRules.ChangelogRule.Impact, fallbackImpact)
314315
}
315316

316317
// mapTemplatesRules configures Templates linter rules

pkg/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ type ModuleLinterRules struct {
220220
LicenseRule RuleConfig
221221
RequarementsRule RuleConfig
222222
LegacyReleaseFileRule RuleConfig
223+
ChangelogRule RuleConfig
223224
}
224225
type OSSRuleSettings struct {
225226
Disable bool

pkg/config/global/global.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ type ModuleLinterRules struct {
109109
LicenseRule RuleConfig `mapstructure:"license"`
110110
RequarementsRule RuleConfig `mapstructure:"requarements"`
111111
LegacyReleaseFileRule RuleConfig `mapstructure:"legacy-release-file"`
112+
ChangelogRule RuleConfig `mapstructure:"changelog"`
112113
}
113114

114115
type TemplatesLinterConfig struct {

pkg/linters/module/README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Module linter performs automated checks on Deckhouse modules to validate con
88

99
## Rules
1010

11-
The Module linter includes **7 validation rules**:
11+
The Module linter includes **8 validation rules**:
1212

1313
| Rule | Description | Configurable |
1414
|------|-------------|--------------|
@@ -19,6 +19,7 @@ The Module linter includes **7 validation rules**:
1919
| [**license**](#license) | Validates license headers in source files | ✅ Yes |
2020
| [**requirements**](#requirements) | Validates version requirements for features | ❌ No |
2121
| [**legacy-release-file**](#legacy-release-file) | Checks for deprecated `release.yaml` file | ❌ No |
22+
| [**changelog**](#changelog) | Validates changelog file presence and content | ❌ No |
2223

2324
---
2425

@@ -531,3 +532,52 @@ update:
531532
- from: "1.17"
532533
to: "1.20"
533534
```
535+
536+
---
537+
538+
### Changelog
539+
540+
Validates changelog.yaml file presence and content in modules.
541+
542+
**Purpose:** Ensures modules have a changelog file that documents changes and version history. This maintains transparency about module evolution and helps users understand what changes are included in each version.
543+
544+
**Checks:**
545+
- ✅ `changelog.yaml` file must exist in module root
546+
- ✅ File must not be empty (size > 0 bytes)
547+
548+
**Validation Logic:**
549+
- File `changelog.yaml` is required in the module root directory
550+
- File must contain content (not be empty)
551+
- Missing or empty files generate appropriate error messages
552+
553+
**Examples:**
554+
555+
```yaml
556+
# changelog.yaml - Valid changelog
557+
---
558+
features:
559+
Linting:
560+
Module:
561+
- Added new changelog validation rule to ensure proper changelog format
562+
- Enhanced module YAML file checking with additional validation rules
563+
- Improved license checking functionality with better error reporting
564+
- Added support for restricting UpdateMode to Auto in module configurations
565+
fixes:
566+
Linting:
567+
Module:
568+
- Fixed install output formatting issues
569+
- Corrected changelog rule validation logic
570+
- Resolved false positives in module YAML validation
571+
chore:
572+
Dependencies:
573+
- Updated helm.sh/helm/v3 dependency from 3.18.4 to 3.18.5
574+
Linting:
575+
- Refactored changelog validation code for better maintainability
576+
577+
```
578+
579+
**Error Examples:**
580+
```
581+
❌ changelog.yaml file is missing
582+
❌ changelog.yaml file is empty
583+
```

pkg/linters/module/module.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func (l *Module) Run(m *module.Module) {
5656
CheckFiles(m, errorList.WithMaxLevel(l.cfg.Rules.LicenseRule.GetLevel()))
5757
rules.NewRequirementsRule().CheckRequirements(m.GetPath(), errorList.WithMaxLevel(l.cfg.Rules.RequarementsRule.GetLevel()))
5858
rules.NewLegacyReleaseFileRule().CheckLegacyReleaseFile(m.GetPath(), errorList.WithMaxLevel(l.cfg.Rules.LegacyReleaseFileRule.GetLevel()))
59+
rules.NewChangelogRule().CheckChangelog(m.GetPath(), errorList.WithMaxLevel(l.cfg.Rules.ChangelogRule.GetLevel()))
5960
}
6061

6162
func (l *Module) Name() string {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rules
18+
19+
import (
20+
errs "errors"
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
25+
"github.com/deckhouse/dmt/pkg"
26+
"github.com/deckhouse/dmt/pkg/errors"
27+
)
28+
29+
const (
30+
ChangelogRuleName = "changelog"
31+
changelogFilename = "changelog.yaml"
32+
)
33+
34+
func NewChangelogRule() *ChangelogRule {
35+
return &ChangelogRule{
36+
RuleMeta: pkg.RuleMeta{
37+
Name: ChangelogRuleName,
38+
},
39+
}
40+
}
41+
42+
type ChangelogRule struct {
43+
pkg.RuleMeta
44+
}
45+
46+
func (r *ChangelogRule) CheckChangelog(modulePath string, errorList *errors.LintRuleErrorsList) {
47+
errorList = errorList.WithRule(r.GetName())
48+
49+
changelogPath := filepath.Join(modulePath, changelogFilename)
50+
err := checkFile(changelogPath)
51+
if err != nil {
52+
errorList.WithFilePath(changelogFilename).Error(err.Error())
53+
}
54+
}
55+
56+
func checkFile(filePath string) error {
57+
stat, err := os.Stat(filePath)
58+
if errs.Is(err, os.ErrNotExist) {
59+
return fmt.Errorf("changelog.yaml file does not exist: %w", err)
60+
}
61+
if err != nil {
62+
return fmt.Errorf("failed to stat changelog.yaml file: %w", err)
63+
}
64+
if stat.Size() == 0 {
65+
return fmt.Errorf("changelog.yaml file is empty")
66+
}
67+
68+
return nil
69+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rules
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
27+
"github.com/deckhouse/dmt/pkg/errors"
28+
)
29+
30+
func TestChangelogRule_CheckChangelog(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
fileContent string
34+
createFile bool
35+
expectErrors int
36+
}{
37+
{
38+
name: "changelog.yaml file missing",
39+
createFile: false,
40+
expectErrors: 1,
41+
},
42+
{
43+
name: "changelog.yaml file empty",
44+
fileContent: "",
45+
createFile: true,
46+
expectErrors: 1,
47+
},
48+
{
49+
name: "changelog.yaml file present and non-empty",
50+
fileContent: "# Changelog\n\n## v1.0.0\n- Initial release",
51+
createFile: true,
52+
expectErrors: 0,
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
tempDir := t.TempDir()
59+
if tt.createFile {
60+
err := os.WriteFile(filepath.Join(tempDir, changelogFilename), []byte(tt.fileContent), 0600)
61+
require.NoError(t, err)
62+
}
63+
64+
rule := NewChangelogRule()
65+
errorList := errors.NewLintRuleErrorsList()
66+
67+
rule.CheckChangelog(tempDir, errorList)
68+
69+
if tt.expectErrors > 0 {
70+
assert.True(t, errorList.ContainsErrors(), "Expected errors but got none")
71+
assert.Len(t, errorList.GetErrors(), tt.expectErrors)
72+
} else {
73+
assert.False(t, errorList.ContainsErrors(), "Expected no errors but got some")
74+
}
75+
})
76+
}
77+
}

0 commit comments

Comments
 (0)