diff --git a/coverage.svg b/coverage.svg
index e200c27d..ce48b808 100644
--- a/coverage.svg
+++ b/coverage.svg
@@ -1 +1 @@
-
+
diff --git a/pkg/linters/module/rules/license.go b/pkg/linters/module/rules/license.go
index 091a698f..1f50367f 100644
--- a/pkg/linters/module/rules/license.go
+++ b/pkg/linters/module/rules/license.go
@@ -19,10 +19,8 @@ package rules
import (
errs "errors"
"io"
- "os"
"github.com/deckhouse/dmt/internal/fsutils"
- "github.com/deckhouse/dmt/internal/logger"
"github.com/deckhouse/dmt/internal/module"
"github.com/deckhouse/dmt/pkg"
"github.com/deckhouse/dmt/pkg/errors"
@@ -53,7 +51,7 @@ type LicenseRule struct {
func (r *LicenseRule) CheckFiles(mod *module.Module, errorList *errors.LintRuleErrorsList) {
errorList = errorList.WithRule(r.GetName())
- files := fsutils.GetFiles(mod.GetPath(), false, filterFiles)
+ files := fsutils.GetFiles(mod.GetPath(), true, filterFiles)
for _, fileName := range files {
name := fsutils.Rel(mod.GetPath(), fileName)
@@ -74,14 +72,6 @@ func (r *LicenseRule) CheckFiles(mod *module.Module, errorList *errors.LintRuleE
}
func filterFiles(rootPath, path string) bool {
- f, err := os.Stat(path)
- if err != nil {
- logger.DebugF("Error getting file info: %v", err)
- return false
- }
- if f.IsDir() {
- return false
- }
path = fsutils.Rel(rootPath, path)
if fileToCheckRe.MatchString(path) && !fileToSkipRe.MatchString(path) {
return true
diff --git a/pkg/linters/module/rules/license_library.go b/pkg/linters/module/rules/license_library.go
index a9d2d104..e8e4b5fa 100644
--- a/pkg/linters/module/rules/license_library.go
+++ b/pkg/linters/module/rules/license_library.go
@@ -20,10 +20,12 @@ import (
"errors"
"fmt"
"os"
+ "path/filepath"
"regexp"
+ "strings"
)
-var CELicenseRe = regexp.MustCompile(`(?s)[/#{!-]*(\s)*Copyright 202[1-9] Flant JSC[-!}\n#/]*
+var CELicenseRe = regexp.MustCompile(`(?s)[/#{!-]*(\s)*Copyright 202[1-9] Flant C?JSC[-!}\n#/]*
[/#{!-]*(\s)*Licensed under the Apache License, Version 2\.0 \(the "License"\);[-!}\n]*
[/#{!-]*(\s)*you may not use this file except in compliance with the License\.[-!}\n]*
[/#{!-]*(\s)*You may obtain a copy of the License at[-!}\n#/]*
@@ -34,6 +36,8 @@ var CELicenseRe = regexp.MustCompile(`(?s)[/#{!-]*(\s)*Copyright 202[1-9] Flant
[/#{!-]*(\s)*See the License for the specific language governing permissions and[-!}\n]*
[/#{!-]*(\s)*limitations under the License\.[-!}\n]*`)
+var EELicenseRe = regexp.MustCompile(`(?s)[/#{!-]*(\s)*Copyright 202[1-9] Flant JSC[\t ]*\n([\t ]*\n)*[#{!-]*(\s)*Licensed under the Deckhouse Platform Enterprise Edition \(EE\) license\. See https://github\.com/deckhouse/deckhouse/blob/main/ee/LICENSE;[-!}\n]*`)
+
var fileToCheckRe = regexp.MustCompile(
`\.go$|/[^.]+$|\.sh$|\.lua$|\.py$`,
)
@@ -48,7 +52,32 @@ var flantRe = regexp.MustCompile(`Flant|Deckhouse`)
const bufSize int = 1024
-// checkFileCopyright returns true if file is readable and has no copyright information in it.
+// LicenseType represents the type of license expected for a file
+type LicenseType int
+
+const (
+ LicenseTypeCE LicenseType = iota
+ LicenseTypeEE
+)
+
+// getLicenseType determines the expected license type based on the file path
+// Files in directories starting with "ee" should have EE license, others should have CE license
+func getLicenseType(filePath string) LicenseType {
+ // Split the path into components
+ pathComponents := strings.Split(filePath, string(filepath.Separator))
+
+ // Check if any directory in the path starts with "ee"
+ for _, component := range pathComponents {
+ if strings.EqualFold(component, "ee") {
+ return LicenseTypeEE
+ }
+ }
+
+ return LicenseTypeCE
+}
+
+// checkFileCopyright returns true if file is readable and has the correct copyright information.
+// It now checks for the appropriate license type based on the file path.
func checkFileCopyright(fName string) (bool, error) {
// Original script 'validate_copyright.sh' used 'head -n 10'.
// Here we just read first 1024 bytes.
@@ -62,9 +91,29 @@ func checkFileCopyright(fName string) (bool, error) {
return true, errors.New("generated code or other license")
}
- // Check Flant license if file contains keywords.
- if flantRe.Match(headBuf) {
- return true, nil
+ // Determine expected license type based on file path
+ licenseType := getLicenseType(fName)
+
+ // Check for the appropriate license type
+ switch licenseType {
+ case LicenseTypeCE:
+ // Check for CE license (Apache 2.0)
+ if CELicenseRe.Match(headBuf) {
+ return true, nil
+ }
+ // Check if file contains Flant keywords but no proper license
+ if flantRe.Match(headBuf) {
+ return false, errors.New("file contains Flant references but missing proper CE license header")
+ }
+ case LicenseTypeEE:
+ // Check for EE license
+ if EELicenseRe.Match(headBuf) {
+ return true, nil
+ }
+ // Check if file contains Flant keywords but no proper license
+ if flantRe.Match(headBuf) {
+ return false, errors.New("file contains Flant references but missing proper EE license header")
+ }
}
// Skip file with some other copyright
@@ -72,7 +121,8 @@ func checkFileCopyright(fName string) (bool, error) {
return true, errors.New("contains other license")
}
- return false, errors.New("no copyright or license information")
+ return false, fmt.Errorf("no copyright or license information found (expected %s license)",
+ map[LicenseType]string{LicenseTypeCE: "CE", LicenseTypeEE: "EE"}[licenseType])
}
func readFileHead(fName string, size int) ([]byte, error) {
diff --git a/pkg/linters/module/rules/license_library_test.go b/pkg/linters/module/rules/license_library_test.go
index ca9d3bda..cfd241bc 100644
--- a/pkg/linters/module/rules/license_library_test.go
+++ b/pkg/linters/module/rules/license_library_test.go
@@ -16,7 +16,185 @@ limitations under the License.
package rules
-import "testing"
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func Test_getLicenseType(t *testing.T) {
+ tests := []struct {
+ name string
+ filePath string
+ expected LicenseType
+ }{
+ {
+ name: "CE license for regular file",
+ filePath: "internal/module/module.go",
+ expected: LicenseTypeCE,
+ },
+ {
+ name: "CE license for file in regular directory",
+ filePath: "pkg/linters/module/rules/license.go",
+ expected: LicenseTypeCE,
+ },
+ {
+ name: "EE license for file in ee directory",
+ filePath: "ee/module/rules/license.go",
+ expected: LicenseTypeEE,
+ },
+ {
+ name: "EE license for file in nested ee directory",
+ filePath: "internal/ee/module/rules/license.go",
+ expected: LicenseTypeEE,
+ },
+ {
+ name: "EE license for file in EE directory (case insensitive)",
+ filePath: "EE/module/rules/license.go",
+ expected: LicenseTypeEE,
+ },
+ {
+ name: "CE license for file in eetools directory",
+ filePath: "eetools/module/rules/license.go",
+ expected: LicenseTypeCE,
+ },
+ {
+ name: "CE license for file with ee in middle of path",
+ filePath: "internal/feedback/module/rules/license.go",
+ expected: LicenseTypeCE,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := getLicenseType(tt.filePath)
+ if result != tt.expected {
+ t.Errorf("getLicenseType() = %v, want %v", result, tt.expected)
+ }
+ })
+ }
+}
+
+func Test_ee_license_re(t *testing.T) {
+ invalidCases := []struct {
+ title string
+ content string
+ }{
+ {
+ title: "No license",
+ content: `package main
+
+no license
+`,
+ },
+ {
+ title: "CE license instead of EE",
+ content: `/*
+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 main
+`,
+ },
+ }
+
+ for _, c := range invalidCases {
+ t.Run(c.title, func(t *testing.T) {
+ res := EELicenseRe.MatchString(c.content)
+ if res {
+ t.Errorf("should not detect EE license")
+ }
+ })
+ }
+
+ validCases := []struct {
+ title string
+ content string
+ }{
+ {
+ title: "EE license in Go multiline comment",
+ content: `/*
+Copyright 2025 Flant JSC
+Licensed under the Deckhouse Platform Enterprise Edition (EE) license. See https://github.com/deckhouse/deckhouse/blob/main/ee/LICENSE;
+*/
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ fmt.Printf("Hello, world!")
+ os.Exit(0)
+}
+`,
+ },
+ {
+ title: "EE license in Go single line comments",
+ content: `/*
+Copyright 2025 Flant JSC
+Licensed under the Deckhouse Platform Enterprise Edition (EE) license. See https://github.com/deckhouse/deckhouse/blob/main/ee/LICENSE;
+*/
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ fmt.Printf("Hello, world!")
+ os.Exit(0)
+}
+`,
+ },
+ {
+ title: "EE license in Bash comments",
+ content: `#!/bin/bash
+# Copyright 2025 Flant JSC
+# Licensed under the Deckhouse Platform Enterprise Edition (EE) license. See https://github.com/deckhouse/deckhouse/blob/main/ee/LICENSE;
+
+set -Eeo pipefail
+`,
+ },
+ {
+ title: "EE license in Lua comments",
+ content: `--[[
+Copyright 2025 Flant JSC
+Licensed under the Deckhouse Platform Enterprise Edition (EE) license. See https://github.com/deckhouse/deckhouse/blob/main/ee/LICENSE;
+--]]
+
+local a = require "table.nkeys"
+
+print("Hello")
+`,
+ },
+ }
+
+ for _, c := range validCases {
+ t.Run(c.title, func(t *testing.T) {
+ res := EELicenseRe.MatchString(c.content)
+ if !res {
+ t.Errorf("should detect EE license")
+ }
+ })
+ }
+}
func Test_copyright_re(t *testing.T) {
in := `package main
@@ -39,7 +217,7 @@ no license
content: `
#!/bin/bash
-# Copyright 2021 Flant JSC
+# 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.
@@ -61,7 +239,7 @@ set -Eeo pipefail
{
title: "Bash comment without previous spaces",
content: `#!/bin/bash
-# Copyright 2021 Flant JSC
+# 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.
@@ -83,7 +261,7 @@ set -Eeo pipefail
{
title: "Golang multiline comment without previous spaces",
content: `/*
-Copyright 2021 Flant JSC
+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.
@@ -116,7 +294,7 @@ func main() {
title: "Golang multiline comment with previous spaces",
content: `
/*
-Copyright 2021 Flant JSC
+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.
@@ -146,7 +324,7 @@ func main() {
{
title: "Golang multiple one line comments without previous spaces",
- content: `// Copyright 2021 Flant JSC
+ content: `// 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.
@@ -177,7 +355,7 @@ func main() {
{
title: "Golang multiple one line comments with previous spaces",
content: `
-// Copyright 2021 Flant JSC
+// 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.
@@ -207,7 +385,7 @@ func main() {
{
title: "Lua multiple one line comments without previous spaces",
content: `--[[
-Copyright 2021 Flant JSC
+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.
@@ -239,3 +417,149 @@ print("Hello")
})
}
}
+
+func Test_checkFileCopyright_Integration(t *testing.T) {
+ // Create temporary test files
+ tmpDir := t.TempDir()
+
+ // Test CE license file
+ ceFile := filepath.Join(tmpDir, "ce_file.go")
+ ceContent := `/*
+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 main
+
+func main() {
+ fmt.Println("Hello, CE!")
+}
+`
+ if err := os.WriteFile(ceFile, []byte(ceContent), 0600); err != nil {
+ t.Fatalf("Failed to create CE test file: %v", err)
+ }
+
+ // Test EE license file
+ eeDir := filepath.Join(tmpDir, "ee")
+ if err := os.MkdirAll(eeDir, 0755); err != nil {
+ t.Fatalf("Failed to create EE directory: %v", err)
+ }
+
+ eeFile := filepath.Join(eeDir, "ee_file.go")
+ eeContent := `/*
+Copyright 2025 Flant JSC
+
+Licensed under the Deckhouse Platform Enterprise Edition (EE) license. See https://github.com/deckhouse/deckhouse/blob/main/ee/LICENSE;
+*/
+
+package main
+
+func main() {
+ fmt.Println("Hello, EE!")
+}
+`
+ if err := os.WriteFile(eeFile, []byte(eeContent), 0600); err != nil {
+ t.Fatalf("Failed to create EE test file: %v", err)
+ }
+
+ // Test file without license
+ noLicenseFile := filepath.Join(tmpDir, "no_license.go")
+ noLicenseContent := `package main
+
+func main() {
+ fmt.Println("No license!")
+}
+`
+ if err := os.WriteFile(noLicenseFile, []byte(noLicenseContent), 0600); err != nil {
+ t.Fatalf("Failed to create no license test file: %v", err)
+ }
+
+ // Test file with wrong license type
+ wrongLicenseFile := filepath.Join(eeDir, "wrong_license.go")
+ wrongLicenseContent := `/*
+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 main
+
+func main() {
+ fmt.Println("Wrong license in EE directory!")
+}
+`
+ if err := os.WriteFile(wrongLicenseFile, []byte(wrongLicenseContent), 0600); err != nil {
+ t.Fatalf("Failed to create wrong license test file: %v", err)
+ }
+
+ tests := []struct {
+ name string
+ filePath string
+ expectOK bool
+ expectError string
+ }{
+ {
+ name: "CE file with correct license",
+ filePath: ceFile,
+ expectOK: true,
+ },
+ {
+ name: "EE file with correct license",
+ filePath: eeFile,
+ expectOK: true,
+ },
+ {
+ name: "File without license",
+ filePath: noLicenseFile,
+ expectOK: false,
+ expectError: "no copyright or license information found (expected CE license)",
+ },
+ {
+ name: "EE file with wrong license type",
+ filePath: wrongLicenseFile,
+ expectOK: false,
+ expectError: "file contains Flant references but missing proper EE license header",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ok, err := checkFileCopyright(tt.filePath)
+
+ if tt.expectOK && !ok {
+ t.Errorf("Expected file to be OK, but got error: %v", err)
+ }
+
+ if !tt.expectOK && ok {
+ t.Errorf("Expected file to have error, but it was OK")
+ }
+
+ if !tt.expectOK && err != nil && tt.expectError != "" {
+ if err.Error() != tt.expectError {
+ t.Errorf("Expected error '%s', but got '%s'", tt.expectError, err.Error())
+ }
+ }
+ })
+ }
+}