From be1bc25196203f51ca12a4c644fb39edf253b020 Mon Sep 17 00:00:00 2001 From: Andrew Peabody Date: Wed, 30 Aug 2023 10:50:47 -0700 Subject: [PATCH] feat: add lint scripts workflow (#330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andrew Peabody Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .github/workflows/scripts.yaml | 34 ++++++++++++++ .golangci.yaml | 50 +++++++++++++++++++++ scripts/artifacthub/hub.go | 73 ++++++++++++++++++++++++------- scripts/artifacthub/hub_test.go | 13 +++--- scripts/validate/validate_test.go | 6 ++- scripts/website/generate.go | 72 +++++++++++++++++++++++------- 6 files changed, 208 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/scripts.yaml create mode 100644 .golangci.yaml diff --git a/.github/workflows/scripts.yaml b/.github/workflows/scripts.yaml new file mode 100644 index 000000000..8991c9f9d --- /dev/null +++ b/.github/workflows/scripts.yaml @@ -0,0 +1,34 @@ +name: scripts +on: + pull_request: + branches: + - master + paths: + - ".github/workflows/scripts.yaml" + - "scripts/**" +permissions: + contents: read + +concurrency: + group: '$${{ github.workflow }}-$${{ github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + folder: [artifacthub, require-sync, validate, website] + steps: + - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version: '1.20' + cache: false + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - name: golangci-lint + uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0 + with: + version: v1.54.2 + working-directory: scripts/${{ matrix.folder }} diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 000000000..9a848d631 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,50 @@ +run: + timeout: 5m + +linters-settings: + gocritic: + enabled-tags: + - performance + gosec: + excludes: + - G108 + importas: + no-unaliased: true + alias: + - pkg: "github.com/open-policy-agent/frameworks/constraint/pkg/client" + alias: constraintclient + lll: + line-length: 200 + + misspell: + locale: US + staticcheck: + # Select the Go version to target. The default is '1.13'. + go: "1.20" + +linters: + disable-all: true + enable: + - errcheck + - errorlint + - exportloopref + - forcetypeassert + - gci + - gocritic + - goconst + - godot + - gofmt + - gofumpt + - goimports + - gosec + - gosimple + - govet + - importas + - ineffassign + - misspell + - revive # replacement for golint + - staticcheck + - typecheck + - unconvert + - unused + - whitespace diff --git a/scripts/artifacthub/hub.go b/scripts/artifacthub/hub.go index c7e0140c9..8cbb5975c 100644 --- a/scripts/artifacthub/hub.go +++ b/scripts/artifacthub/hub.go @@ -83,17 +83,17 @@ type DefaultClient struct{} // Get is a method of DefaultClient that makes the actual HTTP GET request. func (c DefaultClient) Get(url string) (*http.Response, error) { - return http.Get(url) + return http.Get(url) //nolint } const ( - // entryPoint is the directory entry point for artifact hub + // entryPoint is the directory entry point for artifact hub. ahEntryPoint = "artifacthub" - // directory entry point for library + // directory entry point for library. entryPoint = "library" - // raw github source URL + // raw github source URL. sourceURL = "https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/" ) @@ -151,7 +151,7 @@ func main() { } func createVersionDirectory(rootDir, basePath, githubSourceRelativePath string, constraintTemplate map[string]interface{}) { - version := fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"]) + version := getConstraintTemplateVersion(constraintTemplate) // create directory if not exists destination := filepath.Join(rootDir, ahEntryPoint, basePath, version) @@ -192,11 +192,11 @@ func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath, github } artifactHubMetadata = &ArtifactHubMetadata{ - Version: fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"]), - Name: fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["name"]), - DisplayName: fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/title"]), + Version: getConstraintTemplateVersion(constraintTemplate), + Name: getConstraintTemplateName(constraintTemplate), + DisplayName: getConstraintTemplateTitle(constraintTemplate), CreatedAt: currentDateTime.Format(time.RFC3339), - Description: fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["description"]), + Description: getConstraintTemplateDescription(constraintTemplate), License: "Apache-2.0", HomeURL: "https://open-policy-agent.github.io/gatekeeper-library/website/" + sourceDirectory, Keywords: []string{ @@ -211,7 +211,7 @@ func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath, github }, Install: fmt.Sprintf("### Usage\n```shell\nkubectl apply -f %s\n```", sourceURL+filepath.Join(ahBasePath, "template.yaml")), Readme: fmt.Sprintf(`# %s -%s`, constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/title"], constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["description"]), +%s`, getConstraintTemplateTitle(constraintTemplate), getConstraintTemplateDescription(constraintTemplate)), } } else { // when metadata file already exists, check version to make sure it's updated if constraint template is changed @@ -230,7 +230,7 @@ func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath, github panic(err) } - err = os.WriteFile(filepath.Join(destinationPath, "artifacthub-pkg.yml"), artifactHubMetadataBytes, 0644) + err = os.WriteFile(filepath.Join(destinationPath, "artifacthub-pkg.yml"), artifactHubMetadataBytes, 0o600) if err != nil { fmt.Println("error while writing artifact hub metadata") panic(err) @@ -242,7 +242,7 @@ func checkVersion(httpClient HTTPClient, artifactHubMetadata *ArtifactHubMetadat githubTemplateURL := sourceURL + githubSourceRelativePath resp, err := httpClient.Get(githubTemplateURL) if err != nil { - return fmt.Errorf("error while getting constraint template from github: %v", err) + return fmt.Errorf("error while getting constraint template from github: %w", err) } if resp.StatusCode == http.StatusNotFound { fmt.Printf("constraint template %s not found in github. It is likely that constraint template is being updated locally and not merged to github yet.\n", githubSourceRelativePath) @@ -265,7 +265,7 @@ func checkVersion(httpClient HTTPClient, artifactHubMetadata *ArtifactHubMetadat githubConstraintTemplateHash := getConstraintTemplateHash(githubConstraintTemplate) if artifactHubMetadata.Digest != githubConstraintTemplateHash { // compare version - if artifactHubMetadata.Version == githubConstraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"].(string) { + if artifactHubMetadata.Version == getConstraintTemplateVersion(githubConstraintTemplate) { // panic if version is same but hash is different return fmt.Errorf("looks like template.yaml is updated but the version is not. Please update the 'metadata.gatekeeper.sh/version' annotation in the template.yaml source") } @@ -308,7 +308,7 @@ func getMetadataIfExist(metadataFilePath string) *ArtifactHubMetadata { return nil } -// copyDirectory copies a whole directory recursively +// copyDirectory copies a whole directory recursively. func copyDirectory(src string, dst string) error { var err error var directoryFileInfo []fs.DirEntry @@ -345,7 +345,7 @@ func copyDirectory(src string, dst string) error { return nil } -// copyFile copies a single file from src to dst +// copyFile copies a single file from src to dst. func copyFile(src, dst string) error { var err error var sourceFile *os.File @@ -371,3 +371,46 @@ func copyFile(src, dst string) error { } return os.Chmod(dst, sourceFileInfo.Mode()) } + +func getConstraintTemplateMetadata(constraintTemplate map[string]interface{}) map[string]interface{} { + metadata, ok := constraintTemplate["metadata"].(map[string]interface{}) + if !ok { + panic("error while retrieving constraintTemplate metadata") + } + return metadata +} + +func getConstraintTemplateAnnotations(constraintTemplate map[string]interface{}) map[string]interface{} { + metadata := getConstraintTemplateMetadata(constraintTemplate) + + annotations, ok := metadata["annotations"].(map[string]interface{}) + if !ok { + panic("error while retrieving constraintTemplate annotations") + } + + return annotations +} + +func getConstraintTemplateName(constraintTemplate map[string]interface{}) string { + metadata := getConstraintTemplateMetadata(constraintTemplate) + + return fmt.Sprintf("%s", metadata["name"]) +} + +func getConstraintTemplateVersion(constraintTemplate map[string]interface{}) string { + annotations := getConstraintTemplateAnnotations(constraintTemplate) + + return fmt.Sprintf("%s", annotations["metadata.gatekeeper.sh/version"]) +} + +func getConstraintTemplateTitle(constraintTemplate map[string]interface{}) string { + annotations := getConstraintTemplateAnnotations(constraintTemplate) + + return fmt.Sprintf("%s", annotations["metadata.gatekeeper.sh/title"]) +} + +func getConstraintTemplateDescription(constraintTemplate map[string]interface{}) string { + annotations := getConstraintTemplateAnnotations(constraintTemplate) + + return fmt.Sprintf("%s", annotations["description"]) +} diff --git a/scripts/artifacthub/hub_test.go b/scripts/artifacthub/hub_test.go index d080c6157..79db063d6 100644 --- a/scripts/artifacthub/hub_test.go +++ b/scripts/artifacthub/hub_test.go @@ -12,7 +12,7 @@ import ( ) const ( - expectedHash = "dc888d5c05f7e0421a47adfe2d4e59b5264d6e56ec0b3392fe9b3d224bd61a3e" + expectedHash = "dc888d5c05f7e0421a47adfe2d4e59b5264d6e56ec0b3392fe9b3d224bd61a3e" //nolint ) func TestGetConstraintTemplateHash(t *testing.T) { @@ -81,7 +81,6 @@ func TestGetConstraintTemplateHash(t *testing.T) { } func TestGetMetadataIfExist(t *testing.T) { - testCases := []struct { name string metadataFilePath string @@ -120,7 +119,9 @@ func TestCopyDirectory(t *testing.T) { // create a file in the src directory srcFilePath := srcDirPath + "/test.txt" - os.WriteFile(srcFilePath, []byte("test"), 0o644) + if os.WriteFile(srcFilePath, []byte("test"), 0o600) != nil { + t.Errorf("error writing file") + } testCases := []struct { name string @@ -172,7 +173,7 @@ type MockClient struct { } // Get is a method of MockClient that returns the pre-configured response and error. -func (c MockClient) Get(url string) (*http.Response, error) { +func (c MockClient) Get(_ string) (*http.Response, error) { return c.Resp, c.Err } @@ -244,7 +245,7 @@ func TestCheckVersion(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - githubConstraintTemplateBytes, err := yaml.Marshal(tc.githubConstraintTemplate) + githubConstraintTemplateBytes, _ := yaml.Marshal(tc.githubConstraintTemplate) // Create a mock client with a pre-configured response and error. mockResp := &http.Response{ @@ -256,7 +257,7 @@ func TestCheckVersion(t *testing.T) { Err: tc.httpError, } - err = checkVersion(mockClient, tc.artifactHubMetadata, "path/to/constraint/template.yaml") + err := checkVersion(mockClient, tc.artifactHubMetadata, "path/to/constraint/template.yaml") if tc.expectedErrorMessage != "" { if err == nil { diff --git a/scripts/validate/validate_test.go b/scripts/validate/validate_test.go index 7552e924e..246a5cafd 100644 --- a/scripts/validate/validate_test.go +++ b/scripts/validate/validate_test.go @@ -50,14 +50,16 @@ func TestValidateDocsDirStructure(t *testing.T) { // Create the directory structure docsDirPath := filepath.Join(tmpDir, docsDirEntry) - err = os.MkdirAll(docsDirPath, 0755) + err = os.MkdirAll(docsDirPath, 0o755) if err != nil { t.Fatalf("Error creating docs dir: %v", err) } for _, item := range tc.dirStructure { path := filepath.Join(docsDirPath, item) if filepath.Ext(path) == "" { - err = os.Mkdir(path, 0755) + if os.Mkdir(path, 0o755) != nil { + t.Fatalf("Error creating directory: %v", path) + } } else { f, err := os.Create(path) if err != nil { diff --git a/scripts/website/generate.go b/scripts/website/generate.go index 3fab6d687..62496ed03 100644 --- a/scripts/website/generate.go +++ b/scripts/website/generate.go @@ -13,15 +13,15 @@ import ( ) const ( - // raw github source URL + // raw github source URL. sourceURL = "https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/" - // directory entry point for parsing + // directory entry point for parsing. entryPoint = "library" mutationEntryPoint = "mutation" sidebarPath = "website/sidebars.js" - // regex patterns + // regex patterns. pspReadmeLinkPattern = `\[([^\[\]]+)\]\(([^(]+)\)` generalPattern = `(\s*)(type:\s+'category',\s+label:\s+'General',\s+collapsed:\s+true,\s+items:\s*\[\s)(\s*)([^\]]*,)` pspPattern = `(\s*)(type:\s+'category',\s+label:\s+'Pod Security Policy',\s+collapsed:\s+true,\s+items:\s*\[\s)(\s*)([^\]]*,)` @@ -29,7 +29,7 @@ const ( ) // Suite ... -// ToDo (nilekh): Get this struct from the Gatekeeper repo +// ToDo (nilekh): Get this struct from the Gatekeeper repo. type Suite struct { Kind string `yaml:"kind"` APIVersion string `yaml:"apiVersion"` @@ -67,7 +67,10 @@ func main() { // create website validation directory if not exists if _, err := os.Stat(filepath.Join(rootDir, "website/docs/validation")); os.IsNotExist(err) { - os.Mkdir(filepath.Join(rootDir, "website/docs/validation"), 0755) + if os.Mkdir(filepath.Join(rootDir, "website/docs/validation"), 0o755) != nil { + fmt.Println("error while creating directory") + panic(err) + } } validationSidebarItems := make(map[string][]string) @@ -153,15 +156,15 @@ func main() { "%TEMPLATE%", string(constraintTemplateContent), "%RAWURL%", constraintTemplateRawURL, "%EXAMPLES%", allExamples, - "%TITLE%", fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/title"]), - "%DESCRIPTION%", fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["description"]), + "%TITLE%", getConstraintTemplateTitle(constraintTemplate), + "%DESCRIPTION%", getConstraintTemplateDescription(constraintTemplate), "%FILENAME%", dir.Name(), ) err = os.WriteFile( filepath.Join(rootDir, "website/docs/validation", fmt.Sprintf("%s.md", dir.Name())), []byte(replacer.Replace(string(templateContent))), - 0644, + 0o600, ) if err != nil { fmt.Println("error while writing file") @@ -183,7 +186,10 @@ func main() { // create website mutation directory if not exists if _, err := os.Stat(filepath.Join(rootDir, "website/docs/mutation-examples")); os.IsNotExist(err) { - os.Mkdir(filepath.Join(rootDir, "website/docs/mutation-examples"), 0755) + if os.Mkdir(filepath.Join(rootDir, "website/docs/mutation-examples"), 0o755) != nil { + fmt.Println("error while creating directory") + panic(err) + } } for _, entry := range mutationDirEntry { @@ -229,7 +235,7 @@ func main() { replacer := strings.NewReplacer( "%RAWURL%", sourceURL+filepath.Join(mutationEntryPoint, entry.Name(), dir.Name(), "samples", file.Name()), - "%EXAMPLES%", fmt.Sprintf("%s", fileContentBytes), + "%EXAMPLES%", string(fileContentBytes), "%TITLE%", dir.Name(), "%FILENAME%", dir.Name(), ) @@ -237,7 +243,7 @@ func main() { err := os.WriteFile( filepath.Join(rootDir, "website/docs/mutation-examples", fmt.Sprintf("%s.md", dir.Name())), []byte(replacer.Replace(string(mutationTemplateContent))), - 0644, + 0o600, ) if err != nil { fmt.Println("error while writing ", file.Name()) @@ -249,7 +255,7 @@ func main() { } } - //update README.md + // update README.md fmt.Println("Updating README.md") readmeTemplateContent, err := os.ReadFile(filepath.Join(rootDir, "scripts/website", "readme-template.md")) if err != nil { @@ -266,14 +272,14 @@ func main() { err = os.WriteFile( filepath.Join(rootDir, "website/docs/intro.md"), []byte(strings.Replace(string(readmeTemplateContent), "%CONTENT%", string(readmeContent), 1)), - 0644, + 0o600, ) if err != nil { fmt.Println("error while updating README.md") panic(err) } - //update PSP README.md + // update PSP README.md fmt.Println("Updating PSP README.md") pspReadmeTemplateContent, err := os.ReadFile(filepath.Join(rootDir, "scripts/website", "pspreadme-template.md")) if err != nil { @@ -303,7 +309,7 @@ func main() { err = os.WriteFile( filepath.Join(rootDir, "website/docs/pspintro.md"), []byte(strings.Replace(string(pspReadmeTemplateContent), "%CONTENT%", string(pspReadmeContent), 1)), - 0644, + 0o600, ) if err != nil { fmt.Println("error while updating psp README.md") @@ -367,7 +373,7 @@ func main() { ) // write the updated content to the file - err = os.WriteFile(filepath.Join(rootDir, sidebarPath), []byte(updatedSidebar), 0644) + err = os.WriteFile(filepath.Join(rootDir, sidebarPath), []byte(updatedSidebar), 0o600) if err != nil { log.Fatal(err) } @@ -388,7 +394,7 @@ func getRegexReplacedString(content string, pattern string, replacement []string item, ) } - + updatedContent := fmt.Sprintf("%s%s%s", matches[1], matches[2], @@ -397,3 +403,35 @@ func getRegexReplacedString(content string, pattern string, replacement []string return re.ReplaceAllString(content, updatedContent) } + +// TODO: Use shared pkg. +func getConstraintTemplateMetadata(constraintTemplate map[string]interface{}) map[string]interface{} { + metadata, ok := constraintTemplate["metadata"].(map[string]interface{}) + if !ok { + panic("error while retrieving constraintTemplate metadata") + } + return metadata +} + +func getConstraintTemplateAnnotations(constraintTemplate map[string]interface{}) map[string]interface{} { + metadata := getConstraintTemplateMetadata(constraintTemplate) + + annotations, ok := metadata["annotations"].(map[string]interface{}) + if !ok { + panic("error while retrieving constraintTemplate annotations") + } + + return annotations +} + +func getConstraintTemplateTitle(constraintTemplate map[string]interface{}) string { + annotations := getConstraintTemplateAnnotations(constraintTemplate) + + return fmt.Sprintf("%s", annotations["metadata.gatekeeper.sh/title"]) +} + +func getConstraintTemplateDescription(constraintTemplate map[string]interface{}) string { + annotations := getConstraintTemplateAnnotations(constraintTemplate) + + return fmt.Sprintf("%s", annotations["description"]) +}