From a6792ff4fd1d381ef4df170baafb127dc63525da Mon Sep 17 00:00:00 2001 From: David Gogl <1381862+kengou@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:25:15 +0100 Subject: [PATCH] feat(ci): latest ci and linter config (#14) * feat(ci) add lint and test during PR * feat(ci): check PR title * fix(docker) update docker action * fix(pkg) linter findings * add CODEOWNERS file * Update .golangci.yaml Co-authored-by: Sandro * fix(makefile) github repo link in fmt * feat(gh) add pull request template * fix pull request template --------- Co-authored-by: Sandro --- .github/CODEOWNERS | 19 ++++ .github/pull_request_template.md | 65 +++++++++++ .github/renovate.json | 42 +++++--- .github/workflows/ci-pr-title.yaml | 83 ++++++++++++++ .github/workflows/docker-build.yaml | 70 +++++++++--- .github/workflows/test.yaml | 41 +++++++ .gitignore | 38 ++++++- .golangci.yaml | 162 ++++++++++++++++++++++++++++ Makefile | 36 +++++++ pkg/resource/in.go | 4 +- pkg/resource/types.go | 11 +- 11 files changed, 534 insertions(+), 37 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci-pr-title.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .golangci.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2c94824 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,19 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +* @cloudoperators/Administrators + +/.github/ @cloudoperators/Administrators +/.github/workflows/ @cloudoperators/greenhouse-core +/.github/licenserc.yaml @cloudoperators/greenhouse-core +/.github/renovate.json @cloudoperators/greenhouse-core + +.gitignore @cloudoperators/greenhouse-core +/README.md @cloudoperators/greenhouse-core + +/cmd/ @cloudoperators/greenhouse-backend +/pkg/ @cloudoperators/greenhouse-backend +/test/ @cloudoperators/greenhouse-backend +/Dockerfile @cloudoperators/greenhouse-backend +/Makefile @cloudoperators/greenhouse-core +/.golangci.yaml @cloudoperators/greenhouse-backend +/go.* @cloudoperators/greenhouse-backend diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e1574dd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,65 @@ + + +## Description + + +## What type of PR is this? (check all applicable) + +- [ ] 🍕 Feature +- [ ] 🐛 Bug Fix +- [ ] 📝 Documentation Update +- [ ] 🎨 Style +- [ ] 🧑‍💻 Code Refactor +- [ ] 🔥 Performance Improvements +- [ ] ✅ Test +- [ ] 🤖 Build +- [ ] 🔁 CI +- [ ] 📦 Chore (Release) +- [ ] ⏩ Revert + +## Related Tickets & Documents + + + +## Added tests? + +- [ ] 👍 yes +- [ ] 🙅 no, because they aren't needed +- [ ] 🙋 no, because I need help +- [ ] Separate ticket for tests # (issue/pr) + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +## Added to documentation? + +- [ ] 📜 README.md +- [ ] 🤝 Documentation pages updated +- [ ] 🙅 no documentation needed +- [ ] (if applicable) generated OpenAPI docs for CRD changes + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] New and existing unit tests pass locally with my changes diff --git a/.github/renovate.json b/.github/renovate.json index d1a06ba..d3ae66e 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -7,6 +7,34 @@ "constraints": { "go": "1.22" }, + "customManagers": [ + { + "customType": "regex", + "description": "Bump golangci-lint version in the Makefile", + "fileMatch": [ + "^Makefile$" + ], + "matchStrings": [ + "GOLINT_VERSION\\s*\\?=\\s*(?.?(?:\\d+\\.){0,2}\\d+)" + ], + "datasourceTemplate": "github-tags", + "depNameTemplate": "golangci/golangci-lint", + "extractVersionTemplate": "^v(?.*)$" + }, + { + "customType": "regex", + "description": "Bump ginkolinter version in the Makefile", + "fileMatch": [ + "^Makefile$" + ], + "matchStrings": [ + "GINKGOLINTER_VERSION\\s*\\?=\\s*(?.?(?:\\d+\\.){0,2}\\d+)" + ], + "datasourceTemplate": "github-tags", + "depNameTemplate": "nunnatsa/ginkgolinter", + "extractVersionTemplate": "^v(?.*)$" + } + ], "packageRules": [ { "groupName": "github actions", @@ -86,17 +114,5 @@ "gomodTidy", "gomodUpdateImportPaths" ], - "separateMinorPatch": true, - "customManagers": [ - { - "customType": "regex", - "fileMatch": [ - "Makefile$", - "\\.sh$" - ], - "matchStrings": [ - "# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s.+?_(VERSION|version) *[?:]?= *\"?(?.+?)\"?\\s" - ] - } - ] + "separateMinorPatch": true } diff --git a/.github/workflows/ci-pr-title.yaml b/.github/workflows/ci-pr-title.yaml new file mode 100644 index 0000000..69a3c34 --- /dev/null +++ b/.github/workflows/ci-pr-title.yaml @@ -0,0 +1,83 @@ +name: CI Check Title + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + title-lint: + name: Validate PR title + runs-on: [default] + steps: + - name: CI Check Title + uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + wip: true + # Configure which types are allowed (newline-delimited). + # Default: https://github.com/commitizen/conventional-commit-types + types: | + build + chore + fix + feat + merge + publish + release + refactor + research + style + test + docs + # Configure which scopes are allowed (newline-delimited). + # These are regex patterns auto-wrapped in `^ $`. + scopes: | + build + config + charts + ci + core + deps + docs + actions + template + tests + ui + utils + version + webhook + ISSUE-\d+ + # Configure that a scope must always be provided. + requireScope: true + # Configure which scopes are disallowed in PR titles (newline-delimited). + # For instance by setting the value below, `chore(release): ...` (lowercase) + # and `ci(e2e,release): ...` (unknown scope) will be rejected. + # These are regex patterns auto-wrapped in `^ $`. + disallowScopes: | + release + [A-Z]+ + # Configure additional validation for the subject based on a regex. + # This example ensures the subject doesn't start with an uppercase character. + subjectPattern: ^(?![A-Z]).+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + doesn't start with an uppercase character. + # If the PR contains one of these newline-delimited labels, the + # validation is skipped. If you want to rerun the validation when + # labels change, you might want to use the `labeled` and `unlabeled` + # event triggers in your workflow. + ignoreLabels: | + bot + ignore-semantic-pull-request + # If you're using a format for the PR title that differs from the traditional Conventional + # Commits spec, you can use these options to customize the parsing of the type, scope and + # subject. The `headerPattern` should contain a regex where the capturing groups in parentheses + # correspond to the parts listed in `headerPatternCorrespondence`. + # See: https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser#headerpattern + headerPattern: '^(\w*)(?:\(([\w$.\-*/ ]*)\))?: (.*)$' + headerPatternCorrespondence: type, scope, subject diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index cf4b283..a8bc07b 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -1,34 +1,59 @@ -name: Docker Image CI +name: Build Docker images and push to registry on: push: branches: - - main - + - main + paths: + - cmd/** + - pkg/** + - Dockerfile + - go.mod + - go.sum + tags: + - v*.*.* + env: REGISTRY: ghcr.io - # github.repository as / IMAGE_NAME: ${{ github.repository }} - # Comma separated list of platforms to build the image for. - PLATFORMS: linux/amd64,linux/arm64 jobs: build: + name: Build runs-on: [ default ] + continue-on-error: true + permissions: + contents: read + packages: write + id-token: write + steps: - - name: checkout + - name: Checkout repository uses: actions/checkout@v4 + + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3.7.0 + with: + cosign-release: 'v2.2.3' - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to GitHub Container Registry + with: + driver-opts: | + image=moby/buildkit:latest + + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: ${{github.actor}} - password: ${{secrets.GITHUB_TOKEN}} - - name: Extract image metadata + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 with: @@ -41,16 +66,27 @@ jobs: org.opencontainers.image.url=https://github.com/cloudoperators/concourse-oci-helm-chart-resource org.opencontainers.image.source=https://github.com/cloudoperators/concourse-oci-helm-chart-resource org.opencontainers.image.documentation=https://github.com/cloudoperators/concourse-oci-helm-chart-resource/tree/main/README.md - - name: Build and push + + - name: Build and push Docker image + id: build-and-push uses: docker/build-push-action@v6 with: context: . - platforms: ${{ env.PLATFORMS }} - push: true + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} provenance: false - + platforms: | + linux/amd64 + linux/arm64 + + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + vulnerability-scan: permissions: contents: read @@ -68,14 +104,14 @@ jobs: id: vars run: echo "sha_short=sha-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.29.0 if: success() with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.sha_short }} ignore-unfixed: true format: 'sarif' output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' + severity: 'CRITICAL,HIGH,MEDIUM' - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..a31ca6d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,41 @@ +name: "Unit tests" +on: + pull_request: + paths: + - 'pkg/**' + - 'cmd/**' + - 'Dockerfile*' + - 'go.mod' + - 'go.sum' + - '.golangci.yaml' + +jobs: + lint: + runs-on: [ default ] + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + token: ${{ secrets.GITHUB_TOKEN }} + - name: golangci-lint + run: make lint + + build: + runs-on: [ default ] + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + token: ${{ secrets.GITHUB_TOKEN }} + - name: build + run: make build \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5656dee..50c667e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,38 @@ +# Vendor vendor/** -bin/** + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ +.vscode + +# symlinked main.go +/main.go + +# gorleaser artifacts +dist/** + +# Apple files +.DS_Store + +# act artifacts needed for testing workflows +.secrets +.env +act_*.json diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..7def2bf --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,162 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +run: + timeout: 3m # 1m by default + modules-download-mode: readonly + +output: + # Do not print lines of code with issue. + print-issued-lines: false + +issues: + exclude: + # It is idiomatic Go to reuse the name 'err' with ':=' for subsequent errors. + # Ref: https://go.dev/doc/effective_go#redeclaration + - 'declaration of "err" shadows declaration at' + exclude-rules: + - path: _test\.go + linters: + - bodyclose + - dupl + # '0' disables the following options. + max-issues-per-linter: 0 + max-same-issues: 0 + +linters-settings: + dupl: + # Tokens count to trigger issue, 150 by default. + threshold: 100 + errcheck: + # Report about assignment of errors to blank identifier. + check-blank: true + # Report about not checking of errors in type assertions. + check-type-assertions: true + exclude-functions: + - encoding/json.Marshal + forbidigo: + analyze-types: true # required for pkg: + forbid: + # ioutil package has been deprecated: https://github.com/golang/go/issues/42026 + - ^ioutil\..*$ + # Using http.DefaultServeMux is discouraged because it's a global variable that some packages silently and magically add handlers to (esp. net/http/pprof). + # Applications wishing to use http.ServeMux should obtain local instances through http.NewServeMux() instead of using the global default instance. + - ^http\.DefaultServeMux$ + - ^http\.Handle(?:Func)?$ + # Forbid usage of old and archived square/go-jose + - pkg: ^gopkg\.in/square/go-jose\.v2$ + msg: "gopk.in/square/go-jose is arcived and has CVEs. Replace it with gopkg.in/go-jose/go-jose.v2" + - pkg: ^github.com/coreos/go-oidc$ + msg: "github.com/coreos/go-oidc depends on gopkg.in/square/go-jose which has CVEs. Replace it with github.com/coreos/go-oidc/v3" + + - pkg: ^github.com/howeyc/gopass$ + msg: "github.com/howeyc/gopass is archived, use golang.org/x/term instead" + goconst: + ignore-tests: true + min-occurrences: 5 + gocritic: + enabled-checks: + - boolExprSimplify + - builtinShadow + - emptyStringTest + - evalOrder + - httpNoBody + - importShadow + - initClause + - methodExprCall + - paramTypeCombine + - preferFilepathJoin + - ptrToRefParam + - redundantSprint + - returnAfterHttpError + - stringConcatSimplify + - timeExprSimplify + - truncateCmp + - typeAssertChain + - typeUnparen + - unnamedResult + - unnecessaryBlock + - unnecessaryDefer + - weakCond + - yodaStyleExpr + goimports: + # Put local imports after 3rd-party packages. + local-prefixes: github.com/cloudoperators/concourse-oci-helm-chart-resource + gosec: + excludes: + # gosec wants us to set a short ReadHeaderTimeout to avoid Slowloris attacks, but doing so would expose us to Keep-Alive race conditions (see https://iximiuz.com/en/posts/reverse-proxy-http-keep-alive-and-502s/) + - G112 + # created file permissions are restricted by umask if necessary + - G306 + govet: + enable-all: true + disable: + - fieldalignment + nolintlint: + require-specific: true + misspell: + ignore-words: + - metis + stylecheck: + dot-import-whitelist: + - github.com/onsi/ginkgo/v2 + - github.com/onsi/gomega + usestdlibvars: + constant-kind: true + crypto-hash: true + default-rpc-path: true + http-method: true + http-status-code: true + sql-isolation-level: true + time-layout: true + time-month: true + time-weekday: true + tls-signature-scheme: true + whitespace: + # Enforce newlines (or comments) after multi-line function signatures. + multi-func: true + +linters: + # We use 'disable-all' and enable linters explicitly so that a newer version + # does not introduce new linters unexpectedly. + disable-all: true + enable: + - bodyclose + - containedctx + - copyloopvar + # - dupl + - dupword + - durationcheck + - errcheck + - errname + - errorlint + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - goconst + - gocritic + - gofmt + - goimports + - gosec + - gosimple + - govet + - ineffassign + - intrange + - misspell + - nilerr + - noctx + - nolintlint + - nosprintfhostport + - perfsprint + - predeclared + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tenv + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace diff --git a/Makefile b/Makefile index 7b122ed..cbb7507 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,19 @@ TAG ?= $(shell git rev-parse --short HEAD) IMG ?= ghcr.io/cloudoperators/concourse-oci-helm-chart-resource:$(TAG) +## Tool Binaries +GOIMPORTS ?= $(LOCALBIN)/goimports +GOLINT ?= $(LOCALBIN)/golangci-lint + +## Tool Versions +GOLINT_VERSION ?= 1.63.4 +GINKGOLINTER_VERSION ?= 0.18.4 + +## Location to install dependencies an GO binaries +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + .PHONY: all all: build @@ -20,3 +33,26 @@ docker-build: .PHONY: docker-push docker-push: docker-build docker push ${IMG} + +.PHONY: goimports +goimports: $(GOIMPORTS) +$(GOIMPORTS): $(LOCALBIN) + GOBIN=$(LOCALBIN) go install golang.org/x/tools/cmd/goimports@latest + +.PHONY: fmt +fmt: goimports + GOBIN=$(LOCALBIN) go fmt ./... + $(GOIMPORTS) -w -local github.com/cloudoperators/concourse-oci-helm-chart-resource . + +.PHONY: lint +lint: golint + $(GOLINT) run -v --timeout 5m + +.PHONY: check +check: fmt lint + +.PHONY: golint +golint: $(GOLINT) +$(GOLINT): $(LOCALBIN) + GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(GOLINT_VERSION) + GOBIN=$(LOCALBIN) go install github.com/nunnatsa/ginkgolinter/cmd/ginkgolinter@v$(GINKGOLINTER_VERSION) diff --git a/pkg/resource/in.go b/pkg/resource/in.go index 3ed6f48..5d773dc 100644 --- a/pkg/resource/in.go +++ b/pkg/resource/in.go @@ -19,7 +19,7 @@ import ( const ( mediaTypeHelmChartContentArchive = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" - mediaTypeHelmChartJson = "application/vnd.cncf.helm.chart.v2+json" + mediaTypeHelmChartJSON = "application/vnd.cncf.helm.chart.v2+json" ) type ( @@ -69,7 +69,7 @@ func Get(ctx context.Context, request GetRequest, outputDir string) (*GetRespons switch layer.MediaType { case mediaTypeHelmChartContentArchive: fileExtension = ".tgz" - case mediaTypeHelmChartJson: + case mediaTypeHelmChartJSON: fileExtension = ".json" default: continue diff --git a/pkg/resource/types.go b/pkg/resource/types.go index d0df76a..538bc22 100644 --- a/pkg/resource/types.go +++ b/pkg/resource/types.go @@ -3,7 +3,10 @@ package resource -import "fmt" +import ( + "errors" + "fmt" +) type Source struct { Registry string `json:"registry"` @@ -16,13 +19,13 @@ type Source struct { func (s *Source) Validate() error { if s.Registry == "" { - return fmt.Errorf("registry cannot be empty") + return errors.New("registry cannot be empty") } if s.Repository == "" { - return fmt.Errorf("repository cannot be empty") + return errors.New("repository cannot be empty") } if s.ChartName == "" { - return fmt.Errorf("chart_name cannot be empty") + return errors.New("chart_name cannot be empty") } return nil }