diff --git a/.github/workflows/golang.yaml b/.github/workflows/golang.yaml index 95ae3aa..465c4d0 100644 --- a/.github/workflows/golang.yaml +++ b/.github/workflows/golang.yaml @@ -9,25 +9,21 @@ jobs: name: GoLang Basics steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of Sonar scan - - name: FS Permissions - # workaround for permissions with contaner attempting to create directories - run: chmod 777 -R "$(pwd)" - name: Dep run: make dep - name: Lint run: make lint - - name: Coverage Setup - # workaround for permissions with container attempting to create directory - run: mkdir .coverage && chmod 777 .coverage + - name: Coverage Setup + run: mkdir -p .coverage/unit - name: Unit Tests run: make test - name: Integration Tests run: make integration - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@v5 + uses: SonarSource/sonarqube-scan-action@v7 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # run on PRs and once we merge to main, as we need baseline runs for main in Sonar diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..fdb813e --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,89 @@ +version: "2" +run: + build-tags: + - integration + issues-exit-code: 1 + tests: true + timeout: 5m +output: + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true +linters: + default: none + enable: + - depguard + - errcheck + - gochecknoinits + - goconst + - gocyclo + - gosec + - govet + - ineffassign + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - unconvert + - unparam + - unused + settings: + depguard: + rules: + main: + deny: + - pkg: github.com/davecgh/go-spew/spew + desc: not allowed to use spew + govet: + enable: + - shadow # Check for possible unintended shadowing of variables. + misspell: + locale: US + prealloc: + for-loops: true + revive: + rules: + - name: package-comments + disabled: true + unparam: + check-exported: false + exclusions: + generated: lax + rules: + - path: (.+)\.go$ + text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked + - path: (.+)\.go$ + text: (possible misuse of unsafe.Pointer|should have signature) + - path: (.+)\.go$ + text: ineffective break statement. Did you mean to break out of the outer loop + - path: (.+)\.go$ + text: Use of unsafe calls should be audited + - path: (.+)\.go$ + text: Subprocess launch(ed with variable|ing should be audited) + - path: (.+)\.go$ + text: G104 + - path: (.+)\.go$ + text: (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less) + - path: (.+)\.go$ + text: Potential file inclusion via variable + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: false + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + diff --git a/Dockerfile b/Dockerfile index 6c9e81c..bf019c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,7 @@ -FROM golang:latest AS BUILDER -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -a -o /opt/app main.go +# syntax=docker/dockerfile:1 -################################## +# Build a local Go toolchain image +FROM golang:1.24 AS go +USER root +# Intentionally empty: this stage serves as a runnable Go toolchain container -FROM alpine:latest as CERTS -RUN apk --no-cache add tzdata zip ca-certificates -WORKDIR /usr/share/zoneinfo -# -0 means no compression. Needed because go's -# tz loader doesn't handle compressed data. -RUN zip -r -0 /zoneinfo.zip . - -################################### - -FROM scratch -COPY --from=BUILDER /opt/app . -# the timezone data: -COPY --from=CERTS /zoneinfo.zip / -# the tls certificates: -COPY --from=CERTS /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -ENV ZONEINFO /zoneinfo.zip -ENTRYPOINT ["app"] diff --git a/Makefile b/Makefile index 38ce214..6aab82d 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,41 @@ +.PHONY: docker-build-go docker-build-lint docker-build dep lint coverage test + TAG := $(shell git rev-parse --short HEAD) DIR := $(shell pwd -L) -SDCLI_VERSION :=v1.5 -SDCLI=docker run --rm -v "$(DIR):$(DIR)" -w "$(DIR)" asecurityteam/sdcli:$(SDCLI_VERSION) - -dep: - $(SDCLI) go dep - -lint: - $(SDCLI) go lint - -test: - $(SDCLI) go test - -integration: - $(SDCLI) go integration - -coverage: - $(SDCLI) go coverage +LOCAL_GO_IMAGE ?= serverfull-go +LOCAL_LINT_IMAGE ?= serverfull-golangci-lint +GODOCKER = docker run --rm -v "$(DIR):$(DIR)" -w "$(DIR)" $(LOCAL_GO_IMAGE) +LINTDOCKER = docker run --rm -v "$(DIR):$(DIR)" -w "$(DIR)" $(LOCAL_LINT_IMAGE) + +COVERAGE_DIR := .coverage +UNIT_COVERAGE_DIR := $(COVERAGE_DIR)/unit +UNIT_COVERAGE_FILE := $(UNIT_COVERAGE_DIR)/unit.cover.out + +docker-build-go: + docker build --target go -t $(LOCAL_GO_IMAGE) . + +docker-build-lint: + docker build --target lint -t $(LOCAL_LINT_IMAGE) -f linter.Dockerfile . + +docker-build: docker-build-go docker-build-lint + +dep: docker-build-go + $(GODOCKER) go mod vendor + +lint: docker-build-lint + $(LINTDOCKER) golangci-lint run --config .golangci.yaml ./... -v + +coverage-setup: + mkdir -p $(UNIT_COVERAGE_DIR) + touch $(UNIT_COVERAGE_FILE) + +test: coverage-setup docker-build-go + $(GODOCKER) go test -coverprofile=$(UNIT_COVERAGE_FILE) -v -race ./... + +integration: ; + +coverage: docker-build-go + $(GODOCKER) go tool cover -func=$(UNIT_COVERAGE_FILE) doc: ; diff --git a/invokeapi.go b/invokeapi.go index e61e8de..1206e45 100644 --- a/invokeapi.go +++ b/invokeapi.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "reflect" "strings" @@ -103,7 +103,7 @@ func (h *Invoke) ServeHTTP(w http.ResponseWriter, r *http.Request) { fnType = invocationTypeRequestResponse // This is the default value in AWS. } ctx := r.Context() - b, errRead := ioutil.ReadAll(r.Body) + b, errRead := io.ReadAll(r.Body) if errRead != nil { w.WriteHeader(http.StatusBadRequest) // Matches JSON parsing errors for the body _ = json.NewEncoder(w).Encode(responseFromError(errRead)) diff --git a/linter.Dockerfile b/linter.Dockerfile new file mode 100644 index 0000000..95d1b39 --- /dev/null +++ b/linter.Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 + +# Build a local golangci-lint image +FROM golangci/golangci-lint:v2.6 AS lint +USER root +# Intentionally empty: this stage serves as a runnable golangci-lint container + + diff --git a/tests/doc.go b/tests/doc.go index bfec3c9..3cdacaf 100644 --- a/tests/doc.go +++ b/tests/doc.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration // Package tests is where integration tests for a project should be placed. // Integration tests include any of those that require external resources. diff --git a/tests/embedded_lambda_test.go b/tests/embedded_lambda_test.go index a92920c..e87cb04 100644 --- a/tests/embedded_lambda_test.go +++ b/tests/embedded_lambda_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package tests @@ -156,7 +155,7 @@ func convertStack(s []uintptr) []*messages.InvokeResponse_Error_StackFrame { func formatFrame(inputFrame runtime.Frame) *messages.InvokeResponse_Error_StackFrame { path := inputFrame.File - line := int32(inputFrame.Line) + line := int32(inputFrame.Line) // nolint:gosec // G115: Line number is always non-negative label := inputFrame.Function // Strip GOPATH from path by counting the number of seperators in label & path diff --git a/tests/port_test.go b/tests/port_test.go index 04b10f2..216aa18 100644 --- a/tests/port_test.go +++ b/tests/port_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package tests diff --git a/tests/router_test.go b/tests/router_test.go index d4d20c4..aec2bf6 100644 --- a/tests/router_test.go +++ b/tests/router_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package tests diff --git a/tests/start_test.go b/tests/start_test.go index 677eaa8..77ec4dc 100644 --- a/tests/start_test.go +++ b/tests/start_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package tests @@ -7,7 +6,7 @@ import ( "context" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/rpc" "os" @@ -77,7 +76,7 @@ func TestStart(t *testing.T) { } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - b, _ := ioutil.ReadAll(resp.Body) + b, _ := io.ReadAll(resp.Body) t.Log(resp.StatusCode) t.Log(string(b)) continue @@ -112,7 +111,7 @@ func TestStart(t *testing.T) { } defer resp.Body.Close() if resp.StatusCode != http.StatusInternalServerError { - b, _ := ioutil.ReadAll(resp.Body) + b, _ := io.ReadAll(resp.Body) t.Log(resp.StatusCode) t.Log(string(b)) continue