diff --git a/.changelog/19.txt b/.changelog/19.txt new file mode 100644 index 0000000..0dd0354 --- /dev/null +++ b/.changelog/19.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +golang: Setup checks related to golang in gha/ci +``` diff --git a/.github/workflows/golang.yml b/.github/workflows/golang.yml new file mode 100644 index 0000000..253766e --- /dev/null +++ b/.github/workflows/golang.yml @@ -0,0 +1,72 @@ +name: Go + +on: + push: + branches: + - main + pull_request: + +# permissions: +# # Required: allow read access to the content for analysis. +# contents: read +# # Optional: allow read access to pull request. Use with `only-new-issues` option. +# # pull-requests: read +# # Optional: Allow write access to checks to allow the action to annotate code in the PR. +# checks: write + +jobs: + linters: + runs-on: ubuntu-latest + strategy: + matrix: + go: [ '^1.22' ] + name: Go ${{ matrix.go }} linters + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ matrix.go }} + check-latest: false + cache: false + id: go + - name: golangci-lint + uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0 + with: + version: v1.57 + # Optional: golangci-lint command line arguments. + # + # Note: By default, the `.golangci.yml` file should be at the root of the repository. + # The location of the configuration file can be changed by using `--config=` + # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # skip-cache: true + tests: + runs-on: ubuntu-latest + strategy: + matrix: + go: [ '^1.22' ] + name: Go ${{ matrix.go }} tests + # needs: [linters] + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ matrix.go }} + check-latest: false + cache: false + id: go + - name: Get dependencies + run: go get -v -t -d ./... + - name: Test + run: go test -v -coverprofile=coverage.out ./... diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..ad23ac4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,793 @@ +# This configuration file is used with different version of golangci-lint to avoid regressions: +# the linters can change between version, their configuration can change as well. + +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 5m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path + exclude-dirs: + - test/testdata_etc + - tooling + - vendor + - third_party + + # # which files to skip: they will be analyzed, but issues from them won't be reported; + # # can use regexp here: generated.*, regexp is applied on full path + exclude-files: + # - ".*\.my\.go$" + - lib/bad.go + +linters: + # please, do not use `enable-all`: it is deprecated and will be removed soon. + # Run `golangci-lint run --help` to see all available linters. + enable: + - bodyclose + - depguard + - dogsled + - dupl + - errcheck + - funlen + - gochecknoinits + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - nolintlint + - revive + - rowserrcheck + - exportloopref + - staticcheck + - stylecheck + - typecheck + - unconvert + # - unparam + - unused + - whitespace + +linters-settings: + #staticcheck: + + # # SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks + # # Default: ["*"] + # checks: + # - "all" + + # Specify which checks to disable. + # This overrides the "checks" setting above. + # See https://staticcheck.io/docs/configuration/options/#checks for available checks. + # disabled-checks: + # - "SA1019" + # - "ST1003" + + # Specify the severity level for each check. + # Available severity levels: "error", "warning", "info", "none". + # See https://staticcheck.io/docs/configuration/options/#severity for more details. + # severities: + # "ST1000": "warning" # Treat the "ST1000" check as a warning instead of an error + # "ST1003": "none" # Completely disable the "ST1003" check + + # Specify a list of packages to ignore. + # This is useful for ignoring external dependencies or generated code. + # packages: + # - "github.com/example/external-package" + # - "example.com/generated/code" + + # Specify a list of files to ignore. + # This is useful for ignoring specific files or directories. + # files: + # - "internal/generated/code.go" + # - "vendor/**/*.go" + + # Specify a list of functions to ignore. + # This is useful for ignoring specific functions that may trigger false positives. + # functions: + # - "github.com/example/package.IgnoredFunction" + # - "example.com/package.AnotherIgnoredFunction" + + #cyclop: + # # The maximal code complexity to report. + # # Default: 10 + # max-complexity: 30 + # # The maximal average package complexity. + # # If it's higher than 0.0 (float) the check is enabled + # # Default: 0.0 + # package-average: 10.0 + + dupl: + # tokens count to trigger issue, 150 by default + threshold: 100 + + errcheck: + # report about not checking of errors in type assertions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(x)`; + # default is false: such cases aren't reported by default. + check-blank: false + + #exhaustive: + # # Program elements to check for exhaustiveness. + # # Default: [ switch ] + # check: + # - switch + # - map + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 210 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 55 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + + gomnd: + # List of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. + # Default: ["argument", "case", "condition", "operation", "return", "assign"] + checks: + - argument + - case + - condition + - operation + - return + - assign + + # ignored-functions: [] + ignored-files: + - 'main.go' + - 'dashboards.go' + - 'sk8l.go' + - 'metrics.go' + + # gomodguard: + # blocked: + # # List of blocked modules. + # # Default: [] + + gosec: + # To select a subset of rules to run. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] - means include all rules + includes: [] + + govet: + enable-all: true + disable: + - shadow + # settings: + # shadow: + # # Whether to be strict about shadowing; can be noisy. + # # Default: false + # strict: false + + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 60 + + goconst: + # minimal length of string constant, 3 by default + min-len: 3 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 3 + + depguard: + rules: + # Name of a rule. + main: + # Used to determine the package matching priority. + # There are three different modes: `original`, `strict`, and `lax`. + # Default: "original" + list-mode: lax + # Packages that are not allowed where the value is a suggestion. + deny: + - pkg: "github.com/sirupsen/logrus" + desc: not allowed + - pkg: "github.com/pkg/errors" + desc: Should be replaced by standard lib errors package + + # depguard: + # list-type: whitelist + # packages: + # - github.com/danroux/sk8l/protos + # - github.com/prometheus/client_golang/prometheu + # - k8s.io/apimachinery/pkg/types + # - k8s.io/api/batch/v1 + # - k8s.io/api/core/v1 + # - github.com/dgraph-io/badger/v4 + + maligned: + # print struct with more effective memory layout or not, false by default + suggest-new: true + + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + locale: US + + lll: + # max line length, lines longer will be reported. Default is 120. + # ' ' is counted as 1 character by default, and can be changed with the tab-width option. + line-length: 150 + # tab width in spaces. Default to 1. + tab-width: 1 + + unparam: + # Inspect exported functions, default is false. Set to true if no external program/library imports your code. + # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: + # if it can't find external functions, it will think they are unused. + check-exported: false + + revive: + # Maximum number of open files at the same time. + # See https://github.com/mgechev/revive#command-line-flags + # Defaults to unlimited. + max-open-files: 2048 + # When set to false, ignores files with "GENERATED" header, similar to golint. + # See https://github.com/mgechev/revive#available-rules for details. + # Default: false + ignore-generated-header: true + # Sets the default severity. + # See https://github.com/mgechev/revive#configuration + # Default: warning + severity: warning + # Enable all available rules. + # Default: false + enable-all-rules: true + # Sets the default failure confidence. + # This means that linting errors with less than 0.8 confidence will be ignored. + # Default: 0.8 + confidence: 0.8 + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant + - name: add-constant + severity: error + disabled: true + # exclude: [""] + # arguments: + # - maxLitCount: "3" + # allowStrs: '""' + # allowInts: "0,1,2" + # allowFloats: "0.0,0.,1.0,1.,2.0,2." + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit + - name: argument-limit + severity: warning + disabled: false + exclude: [""] + # arguments: [4] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic + - name: atomic + severity: warning + exclude: [""] + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters + - name: banned-characters + severity: warning + disabled: true + exclude: [""] + # arguments: ["Ω", "Σ", "σ", "7"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return + - name: bare-return + severity: warning + exclude: [""] + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports + - name: blank-imports + severity: warning + exclude: [""] + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr + - name: bool-literal-in-expr + severity: warning + exclude: [""] + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc + - name: call-to-gc + severity: warning + exclude: [""] + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity + - name: cognitive-complexity + severity: warning + disabled: false + exclude: [""] + arguments: [60] # 7 + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#comment-spacings + # - name: comment-spacings + # severity: warning + # disabled: false + # exclude: [""] + # arguments: + # - mypragma + # - otherpragma + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming + - name: confusing-naming + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results + - name: confusing-results + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr + - name: constant-logical-expr + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument + - name: context-as-argument + severity: warning + disabled: false + exclude: [""] + # arguments: + # - allowTypesBefore: "*testing.T,*github.com/user/repo/testing.Harness" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type + - name: context-keys-type + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic + - name: cyclomatic + severity: warning + disabled: false + exclude: [""] + arguments: [20] # 10 + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace + - name: datarace + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit + - name: deep-exit + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer + - name: defer + severity: warning + disabled: false + exclude: [""] + # arguments: + # - ["call-chain", "loop"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports + - name: dot-imports + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports + - name: duplicated-imports + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return + - name: early-return + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "preserveScope" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block + - name: empty-block + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines + - name: empty-lines + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#enforce-map-style + - name: enforce-map-style + severity: warning + disabled: false + exclude: [""] + arguments: + - "any" + # - "make" + # - "literal" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#enforce-repeated-arg-type-style + - name: enforce-repeated-arg-type-style + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "short" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#enforce-slice-style + - name: enforce-slice-style + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "make" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming + - name: error-naming + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return + - name: error-return + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings + - name: error-strings + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf + - name: errorf + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported + - name: exported + severity: warning + disabled: true + exclude: [""] + # arguments: + # - "preserveScope" + # - "checkPrivateReceivers" + # - "sayRepetitiveInsteadOfStutters" + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header + # - name: file-header + # severity: warning + # disabled: false + # exclude: [""] + # arguments: + # - This is the text that must appear at the top of source files. + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter + - name: flag-parameter + severity: warning + disabled: true + exclude: [""] + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit + - name: function-result-limit + severity: warning + disabled: false + exclude: [""] + arguments: [4] + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length + - name: function-length + severity: warning + disabled: false + exclude: [""] + arguments: [55, 210] # (int,int) the maximum allowed statements and lines + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return + - name: get-return + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches + - name: identical-branches + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return + - name: if-return + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement + - name: increment-decrement + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow + - name: indent-error-flow + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "preserveScope" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-alias-naming + - name: import-alias-naming + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "^[a-z][a-z0-9]{0,}$" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blocklist + - name: imports-blocklist + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "crypto/md5" + # - "crypto/sha1" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing + - name: import-shadowing + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit + - name: line-length-limit + severity: error + disabled: false + exclude: [""] + arguments: [150] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-control-nesting + - name: max-control-nesting + severity: warning + disabled: false + exclude: [""] + arguments: [4] + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs + - name: max-public-structs + severity: warning + disabled: true + exclude: [""] + arguments: [3] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter + - name: modifies-parameter + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver + - name: modifies-value-receiver + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs + - name: nested-structs + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order + - name: optimize-operands-order + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments + - name: package-comments + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range + - name: range + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure + - name: range-val-in-closure + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address + - name: range-val-address + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming + - name: receiver-naming + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redundant-import-alias + - name: redundant-import-alias + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id + - name: redefines-builtin-id + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int + - name: string-of-int + severity: warning + disabled: false + exclude: [""] + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format + # - name: string-format + # severity: warning + # disabled: false + # exclude: [""] + # arguments: + # - - 'core.WriteError[1].Message' + # - '/^([^A-Z]|$)/' + # - must not start with a capital letter + # - - 'fmt.Errorf[0]' + # - '/(^|[^\.!?])$/' + # - must not end in punctuation + # - - panic + # - '/^[^\n]*$/' + # - must not contain line breaks + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + # - name: struct-tag + # arguments: + # - "json,inline" + # - "bson,outline,gnu" + # severity: warning + # disabled: false + # exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else + - name: superfluous-else + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "preserveScope" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal + - name: time-equal + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming + - name: time-naming + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming + - name: var-naming + severity: warning + disabled: false + exclude: [""] + # arguments: + # - ["ID"] # AllowList + # - ["VM"] # DenyList + # - - upperCaseConst: true + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration + - name: var-declaration + severity: warning + disabled: false + # exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion + - name: unconditional-recursion + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming + - name: unexported-naming + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return + - name: unexported-return + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error + - name: unhandled-error + severity: warning + disabled: false + exclude: [""] + # arguments: + # - "fmt.Printf" + # - "myFunction" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt + - name: unnecessary-stmt + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code + - name: unreachable-code + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + severity: warning + disabled: true + exclude: [""] + # arguments: + # - allowRegex: "^_" + # # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver + - name: unused-receiver + severity: error + disabled: true + exclude: [""] + # arguments: + # - allowRegex: "^_" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + severity: warning + disabled: false + exclude: [""] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value + - name: waitgroup-by-value + severity: warning + disabled: false + exclude: [""] + + unused: + # treat code as a program (not a library) and report unused exported identifiers; default is false. + check-exported: false + + nakedret: + # make an issue when you have 3 or more consecutive return statements without parenthesis. Default is maxed at 1. + max-func-lines: 1 + + prealloc: + # XXX: we don't recommend using this linter before doing performance profiling. + # For most programs usage of prealloc will be a premature optimization. + + # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. + # True by default. + simple: true + + # Report preallocation suggestions on range loops, but only if the range expression is constant. + # True by default. + range-loops: true + + # Report preallocation suggestions on for loops, but only for loops that have no returns/breaks/continues/gotos in them. + # False by default. + for-loops: false + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + - abcdef + + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gocyclo + - errcheck + - dupl + - gosec + + # Exclude known linters from partially hard-vendored code, + # these patterns will apply to all components. + - path: vendor/github.com/stretchr/testify/ + linters: + - gosec + + # Exclude some staticcheck messages + - linters: + - staticcheck + text: "SA9003" + + # Exclude lll issues for long lines with go:generate + # - linters: + # - lll + # # source: "^//\s*go:generate" + + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is true. + exclude-use-default: false + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 + +output: + show-stats: true diff --git a/dashboards.go b/dashboards.go index bf3fdb5..be2ed07 100644 --- a/dashboards.go +++ b/dashboards.go @@ -1,17 +1,19 @@ +// Sk8l package main import ( "fmt" + "log" "regexp" ) type DataSource struct { - Type, Uid string + UID, Type string } type Target struct { - Expr, LegendFormat string DataSource *DataSource + Expr, LegendFormat string } type GridPos struct { @@ -26,24 +28,24 @@ type Option struct { } type Override struct { - Id string `json:"id"` + ID string `json:"id"` Options string `json:"options"` } type Panel struct { - Title string - Targets []*Target - DataSource *DataSource - Type string GridPos *GridPos `json:"gridPos"` + DataSource *DataSource Options *Option Override *Override + Title string + Type string + Targets []*Target } var ( dataSource = &DataSource{ Type: "prometheus", - Uid: "${DS_PROMETHEUS}", + UID: "${DS_PROMETHEUS}", } durationRe = regexp.MustCompile(`duration_seconds$`) @@ -69,7 +71,7 @@ func generatePanels() []Panel { var totalsStats = []*Target{} for _, totalMetricName := range totalMetricNames { t := &Target{ - Expr: fmt.Sprintf("%s_%s", METRIC_PREFIX, totalMetricName), + Expr: fmt.Sprintf("%s_%s", MetricPrefix, totalMetricName), LegendFormat: "{{__name__}}", DataSource: dataSource, } @@ -83,7 +85,7 @@ func generatePanels() []Panel { } t := &Target{ - Expr: fmt.Sprintf("%s_%s", METRIC_PREFIX, totalStatName), + Expr: fmt.Sprintf("%s_%s", MetricPrefix, totalStatName), LegendFormat: legendFmt, DataSource: dataSource, } @@ -91,9 +93,9 @@ func generatePanels() []Panel { } var panels = []Panel{ - Panel{ + { Type: "row", - Title: fmt.Sprintf("sk8l: %s overview", K8_NAMESPACE), + Title: fmt.Sprintf("sk8l: %s overview", K8Namespace), DataSource: dataSource, GridPos: &GridPos{ H: 1, @@ -103,7 +105,7 @@ func generatePanels() []Panel { }, Targets: make([]*Target, 0), }, - Panel{ + { Title: "completed / registered / failed cronjobs totals", DataSource: dataSource, GridPos: &GridPos{ @@ -117,9 +119,9 @@ func generatePanels() []Panel { Calcs: "last", }, }, - Panel{ + { Type: "stat", - Title: fmt.Sprintf("sk8l: %s totals", K8_NAMESPACE), + Title: fmt.Sprintf("sk8l: %s totals", K8Namespace), DataSource: dataSource, GridPos: &GridPos{ H: 8, @@ -132,7 +134,7 @@ func generatePanels() []Panel { Calcs: "lastNotNull", }, Override: &Override{ - Id: "byName", + ID: "byName", Options: "failing cronjobs", }, }, @@ -142,21 +144,29 @@ func generatePanels() []Panel { individualPanelsGenerator := func(key, value any) bool { var row Panel - var target = &Target{} + var target *Target var failureMetricName string var cronjobDurations = make([]*Target, 0) var cronjobTotals = make([]*Target, 0) - metricNames := value.([]string) + metricNames, ok := value.([]string) - keyName := key.(string) + if !ok { + log.Println("Error: value.([]string)") + } + + keyName, ok := key.(string) + + if !ok { + log.Println("Error: key.(string)") + } i := len(individualPanels) var rowI, rowM, failureY uint16 rowI = uint16((i + 1) * 9) - rowM = uint16(rowI + 1) - failureY = uint16(rowM + 8) + rowM = rowI + 1 + failureY = rowM + 8 row = Panel{ Type: "row", diff --git a/k8s_api.go b/k8s_api.go index 72b15ef..3ca1162 100644 --- a/k8s_api.go +++ b/k8s_api.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "log" "slices" @@ -18,8 +17,8 @@ import ( ) type K8sClient struct { - namespace string ClientSet *kubernetes.Clientset + namespace string } // A ClientOption is used to configure a Client. @@ -63,13 +62,13 @@ func (kc *K8sClient) GetCronjob(cronjobNamespace, cronjobName string) *batchv1.C cronJob, err := kc.ClientSet.BatchV1().CronJobs(cronjobNamespace).Get(ctx, cronjobName, metav1.GetOptions{}) if errors.IsNotFound(err) { - log.Printf("Cronjob %s found in default namespace\n", cronjobName) + log.Printf("Cronjob not %s found in default namespace\n", cronjobName) } else if statusError, isStatus := err.(*errors.StatusError); isStatus { log.Printf("Error getting cronjob %v\n", statusError.ErrStatus.Message) } else if err != nil { panic(err.Error()) } else { - log.Printf("Found example-xxxxx cronjob in default namespace\n") + log.Printf("Found %s cronjob in %s namespace\n", cronjobName, cronjobNamespace) } return cronJob @@ -91,26 +90,6 @@ func (kc *K8sClient) WatchPods() watch.Interface { return watcher } -func (kc *K8sClient) GetJobPodsForJob(job *batchv1.Job) *corev1.PodList { - ctx := context.TODO() - - jobPods, err := kc.ClientSet.CoreV1().Pods(job.Namespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("job-name=%s", job.Name), - }) - - if errors.IsNotFound(err) { - log.Printf("Pods found in default namespace\n") - } else if statusError, isStatus := err.(*errors.StatusError); isStatus { - log.Printf("Error getting pod %v\n", statusError.ErrStatus.Message) - } else if err != nil { - panic(err.Error()) - } else { - log.Printf("There are %d jobPods in the cluster for job %s - %s\n", len(jobPods.Items), job.UID, job.Name) - } - - return jobPods -} - func (kc *K8sClient) GetPod(jobNamespace, podName string) *corev1.Pod { ctx := context.TODO() pod, err := kc.ClientSet.CoreV1().Pods(jobNamespace).Get(ctx, podName, metav1.GetOptions{}) @@ -125,7 +104,7 @@ func (kc *K8sClient) GetPod(jobNamespace, podName string) *corev1.Pod { } else if err != nil { panic(err.Error()) } else { - log.Printf("Found example-xxxxx pod in default namespace\n") + log.Printf("Found %s pod in %s namespace\n", podName, jobNamespace) } return pod diff --git a/main.go b/main.go index 9af8a8b..431389d 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/x509" "fmt" "log" "net" @@ -21,27 +22,39 @@ import ( healthgrpc "google.golang.org/grpc/health/grpc_health_v1" ) +const ( + ReadTimeoutSeconds = 10 + WriteTimeoutSeconds = 30 + ReadTimeout = time.Duration(ReadTimeoutSeconds) + WriteTimeout = time.Duration(WriteTimeoutSeconds) +) + var ( - K8_NAMESPACE = os.Getenv("K8_NAMESPACE") - API_PORT = os.Getenv("SK8L_SERVICE_PORT_SK8L_API") - API_HEALTH_PORT = os.Getenv("SK8L_SERVICE_PORT_SK8L_API_HEALTH") - METRICS_PORT = os.Getenv("SK8L_SERVICE_PORT_SK8L_API_METRICS") - certFile = filepath.Join("/etc", "sk8l-certs", "server-cert.pem") - certKeyFile = filepath.Join("/etc", "sk8l-certs", "server-key.pem") - caFile = filepath.Join("/etc", "sk8l-certs", "ca-cert.pem") - METRIC_PREFIX = fmt.Sprintf("sk8l_%s", K8_NAMESPACE) + K8Namespace = os.Getenv("K8_NAMESPACE") + APIPort = os.Getenv("SK8L_SERVICE_PORT_SK8L_API") + APIHealthPort = os.Getenv("SK8L_SERVICE_PORT_SK8L_API_HEALTH") + MetricsPort = os.Getenv("SK8L_SERVICE_PORT_SK8L_API_METRICS") + certFile = filepath.Join("/etc", "sk8l-certs", "server-cert.pem") + certKeyFile = filepath.Join("/etc", "sk8l-certs", "server-key.pem") + caFile = filepath.Join("/etc", "sk8l-certs", "ca-cert.pem") + MetricPrefix = fmt.Sprintf("sk8l_%s", K8Namespace) ) func main() { - serverTLSConfig, err := setupTLS(certFile, certKeyFile, caFile) - target := fmt.Sprintf("0.0.0.0:%s", API_PORT) + certPool := x509.NewCertPool() + serverTLSConfig, err := setupTLS(certFile, certKeyFile, caFile, certPool) + if err != nil { + log.Fatal("Error: setupTLS:", err) + } + + target := fmt.Sprintf("0.0.0.0:%s", APIPort) conn, err := net.Listen("tcp", target) if err != nil { log.Fatal("tlsListen error:", err) } - healthConn, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", API_HEALTH_PORT)) + healthConn, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", APIHealthPort)) if err != nil { log.Fatal("Health Probe Listen error:", err) @@ -54,7 +67,7 @@ func main() { probeS := grpc.NewServer() log.Printf("grpcS creds %v", creds) - k8sClient := NewK8sClient(WithNamespace(K8_NAMESPACE)) + k8sClient := NewK8sClient(WithNamespace(K8Namespace)) db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) @@ -73,10 +86,10 @@ func main() { http.Handle("/metrics", promhttp.Handler()) httpS := &http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%s", METRICS_PORT), + Addr: fmt.Sprintf("0.0.0.0:%s", MetricsPort), IdleTimeout: time.Minute, - ReadTimeout: 10 * time.Second, - WriteTimeout: 30 * time.Second, + ReadTimeout: ReadTimeout * time.Second, + WriteTimeout: WriteTimeout * time.Second, TLSConfig: serverTLSConfig, } logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) @@ -97,7 +110,6 @@ func main() { if err != nil { log.Fatal("httpS error", err) } - }() go func() { diff --git a/metrics.go b/metrics.go index 0b7ce31..8a0353f 100644 --- a/metrics.go +++ b/metrics.go @@ -95,7 +95,7 @@ func recordMetrics(ctx context.Context, svr *Sk8lServer) { } if err != nil { - log.Fatalf("%v.GetCronjobs(_) = _, %v", cronjobsClient, err) + log.Printf("Error: %v.GetCronjobs(_) = _, %v\n", cronjobsClient, err) } registeredCronjobs := len(cronjobsResponse.Cronjobs) @@ -111,9 +111,9 @@ func recordMetrics(ctx context.Context, svr *Sk8lServer) { durationMetricName := fmt.Sprintf("%s_duration_seconds", sanitizedCjName) metricNames = []string{ - fmt.Sprintf("%s_%s", METRIC_PREFIX, completionMetricName), - fmt.Sprintf("%s_%s", METRIC_PREFIX, failureMetricName), - fmt.Sprintf("%s_%s", METRIC_PREFIX, durationMetricName), + fmt.Sprintf("%s_%s", MetricPrefix, completionMetricName), + fmt.Sprintf("%s_%s", MetricPrefix, failureMetricName), + fmt.Sprintf("%s_%s", MetricPrefix, durationMetricName), } metricsNamesMap.Store(sanitizedCjName, metricNames) diff --git a/sk8l.go b/sk8l.go index 5da2711..74171db 100644 --- a/sk8l.go +++ b/sk8l.go @@ -21,7 +21,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/protoadapt" - // structpb "google.golang.org/protobuf/types/known/structpb" + // structpb "google.golang.org/protobuf/types/known/structpb". "google.golang.org/grpc" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -30,13 +30,17 @@ import ( ) const ( - jobPodsKeyFmt = "jobs_pods_for_job_%s" - cronjobsKeyFmt = "sk8l_cronjob_%s_%s" + jobPodsKeyFmt = "jobs_pods_for_job_%s" + cronjobsKeyFmt = "sk8l_cronjob_%s_%s" + badgerTTLSeconds = 15 + refreshSeconds = 10 ) var ( cronjobsCacheKey = []byte("sk8l_cronjobs") jobsCacheKey = []byte("sk8l_jobs") + badgerTTL = time.Duration(badgerTTLSeconds) + refreshInterval = time.Second * refreshSeconds ) type Sk8lServer struct { @@ -50,7 +54,10 @@ type Sk8lServer struct { type APICall (func() []byte) -func (s Sk8lServer) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { +func (s Sk8lServer) Check( + ctx context.Context, + req *grpc_health_v1.HealthCheckRequest, +) (*grpc_health_v1.HealthCheckResponse, error) { log.Default().Println("serving health") return &grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING}, nil } @@ -59,6 +66,7 @@ func (s Sk8lServer) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc_he response := &grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING} if err := stream.Send(response); err != nil { + log.Println("Error: Watch#stream.Send", err) return err } @@ -104,7 +112,7 @@ func (s *Sk8lServer) GetCronjobs(in *protos.CronjobsRequest, stream protos.Cronj return err } - time.Sleep(time.Second * 10) + time.Sleep(refreshInterval) } } @@ -119,7 +127,7 @@ func (s *Sk8lServer) GetCronjob(in *protos.CronjobRequest, stream protos.Cronjob return err } - time.Sleep(time.Second * 10) + time.Sleep(refreshInterval) } } @@ -153,15 +161,18 @@ func (s *Sk8lServer) GetCronjobPods(in *protos.CronjobPodsRequest, stream protos return err } - time.Sleep(time.Second * 10) + time.Sleep(refreshInterval) } } -func (s *Sk8lServer) GetCronjobYAML(ctx context.Context, in *protos.CronjobRequest) (*protos.CronjobYAMLResponse, error) { +func (s *Sk8lServer) GetCronjobYAML( + ctx context.Context, + in *protos.CronjobRequest, +) (*protos.CronjobYAMLResponse, error) { cronjob := s.K8sClient.GetCronjob(in.CronjobNamespace, in.CronjobName) - prettyJson, _ := json.MarshalIndent(cronjob, "", " ") + prettyJSON, _ := json.MarshalIndent(cronjob, "", " ") - y, _ := gyaml.JSONToYAML(prettyJson) + y, _ := gyaml.JSONToYAML(prettyJSON) response := &protos.CronjobYAMLResponse{ Cronjob: string(y), @@ -172,9 +183,9 @@ func (s *Sk8lServer) GetCronjobYAML(ctx context.Context, in *protos.CronjobReque func (s *Sk8lServer) GetJobYAML(ctx context.Context, in *protos.JobRequest) (*protos.JobYAMLResponse, error) { job := s.K8sClient.GetJob(in.JobNamespace, in.JobName) - prettyJson, _ := json.MarshalIndent(job, "", " ") + prettyJSON, _ := json.MarshalIndent(job, "", " ") - y, _ := gyaml.JSONToYAML(prettyJson) + y, _ := gyaml.JSONToYAML(prettyJSON) response := &protos.JobYAMLResponse{ Job: string(y), @@ -185,9 +196,9 @@ func (s *Sk8lServer) GetJobYAML(ctx context.Context, in *protos.JobRequest) (*pr func (s *Sk8lServer) GetPodYAML(ctx context.Context, in *protos.PodRequest) (*protos.PodYAMLResponse, error) { pod := s.K8sClient.GetPod(in.PodNamespace, in.PodName) - prettyJson, _ := json.MarshalIndent(pod, "", " ") + prettyJSON, _ := json.MarshalIndent(pod, "", " ") - y, _ := gyaml.JSONToYAML(prettyJson) + y, _ := gyaml.JSONToYAML(prettyJSON) response := &protos.PodYAMLResponse{ Pod: string(y), @@ -196,13 +207,16 @@ func (s *Sk8lServer) GetPodYAML(ctx context.Context, in *protos.PodRequest) (*pr return response, nil } -func (s *Sk8lServer) GetDashboardAnnotations(context.Context, *protos.DashboardAnnotationsRequest) (*protos.DashboardAnnotationsResponse, error) { +func (s *Sk8lServer) GetDashboardAnnotations( + context.Context, + *protos.DashboardAnnotationsRequest, +) (*protos.DashboardAnnotationsResponse, error) { panels := generatePanels() var tmplFile = "annotations.tmpl" t := template.New(tmplFile) // t = t.Funcs(template.FuncMap{"StringsJoin": strings.Join}) - t = t.Funcs(template.FuncMap{"marshal": func(v interface{}) string { + t = t.Funcs(template.FuncMap{"marshal": func(v any) string { a, _ := json.Marshal(v) return string(a) }, @@ -231,12 +245,14 @@ func (s *Sk8lServer) getAndStore(key []byte, apiCall APICall) ([]byte, error) { if errors.Is(err, badger.ErrKeyNotFound) { err = s.DB.Update(func(txn *badger.Txn) error { apiResult := apiCall() - entry := badger.NewEntry(key, apiResult).WithTTL(time.Second * 15) - err := txn.SetEntry(entry) + entry := badger.NewEntry(key, apiResult).WithTTL(time.Second * badgerTTL) + err = txn.SetEntry(entry) + if err != nil { + log.Println("Error: getAndStore#txn.SetEntry", err) + } valueResponse = append([]byte{}, apiResult...) return err }) - } else { err = item.Value(func(val []byte) error { valueResponse = append([]byte{}, val...) @@ -280,8 +296,8 @@ func (s *Sk8lServer) findCronjobs() *batchv1.CronJobList { value, err := s.get(cronjobsCacheKey) if err != nil { - if errors.Is(err, badger.ErrKeyNotFound) { - + if !errors.Is(err, badger.ErrKeyNotFound) { + log.Println("Error: findCronjobs#s.get", err) } } @@ -290,7 +306,7 @@ func (s *Sk8lServer) findCronjobs() *batchv1.CronJobList { err = proto.Unmarshal(value, cronjobListV2) if err != nil { - log.Fatalln("findCronjobs", err) + log.Println("Error: findCronjobs#proto.Unmarshal", err) } return cronjobList @@ -310,7 +326,7 @@ func (s *Sk8lServer) findCronjob(cronjobNamespace, cronjobName string) *batchv1. cronjobValue, err := s.getAndStore(key, gCjCall) if err != nil { - + log.Println("Error: findCronjob#s.getAndStore", err) } cronjob := &batchv1.CronJob{} @@ -318,7 +334,7 @@ func (s *Sk8lServer) findCronjob(cronjobNamespace, cronjobName string) *batchv1. err = proto.Unmarshal(cronjobValue, cronjobV2) if err != nil { - + log.Println("Error: findCronjob#proton.Unmarshal", err) } return cronjob @@ -334,7 +350,7 @@ func (s *Sk8lServer) findJobs() *protos.MappedJobs { value, err := s.getAndStore(jobsCacheKey, jobsCall) if err != nil { - + log.Println("Error: findJobs#s.getAndStore", err) } mappedJobs := &protos.MappedJobs{} @@ -342,7 +358,7 @@ func (s *Sk8lServer) findJobs() *protos.MappedJobs { err = proto.Unmarshal(value, mappedJobs) if err != nil { - + log.Println("Error: findJobs#proton.Unmarshal", err) } return mappedJobs @@ -354,22 +370,36 @@ func (s *Sk8lServer) findJobPodsForJob(job *batchv1.Job) *corev1.PodList { collection := &corev1.PodList{} collectionV2 := protoadapt.MessageV2Of(collection) - s.DB.View(func(txn *badger.Txn) error { + err := s.DB.View(func(txn *badger.Txn) error { current, err := txn.Get(key) if errors.Is(err, badger.ErrKeyNotFound) { return nil } - current.Value(func(val []byte) error { - proto.Unmarshal(val, collectionV2) + err = current.Value(func(val []byte) error { + err = proto.Unmarshal(val, collectionV2) + + if err != nil { + log.Println("findJobPodsForJob#proto.Unmarshal", err) + return err + } return nil }) + if err != nil { + log.Println("Error: findJobPodsForJob#current.Value", err) + return err + } + return nil }) + if err != nil { + log.Println("Error: findJobPodsForJob#DB.View", err) + } + podItems := []corev1.Pod{} podMap := make(map[string][]corev1.Pod) @@ -398,15 +428,20 @@ func (s *Sk8lServer) findJobPodsForJob(job *batchv1.Job) *corev1.PodList { return podList } -// Revisit this. JobConditions are not being used yet anywhere. PodResponse.TerminationReasons.TerminationDetails -> ContainerStateTerminated -func jobFailed(job *batchv1.Job, jobPodsResponses []*protos.PodResponse) (bool, *batchv1.JobCondition, []*batchv1.JobCondition) { +// Revisit this. JobConditions are not being used yet anywhere. +// PodResponse.TerminationReasons.TerminationDetails -> ContainerStateTerminated. +func jobFailed( + job *batchv1.Job, + jobPodsResponses []*protos.PodResponse, +) (bool, *batchv1.JobCondition, []*batchv1.JobCondition) { var jobFailed bool var failureCondition *batchv1.JobCondition n := len(job.Status.Conditions) jobConditions := make([]*batchv1.JobCondition, 0, n) - for _, jobCondition := range job.Status.Conditions { - if jobFailed != true { + for i := range job.Status.Conditions { + jobCondition := job.Status.Conditions[i] + if !jobFailed { if jobCondition.Type == batchv1.JobFailed { jobFailed = true failureCondition = &jobCondition @@ -477,7 +512,11 @@ func (s *Sk8lServer) collectCronjobs() { for { event, more := <-x.ResultChan() if more { - eventCronjob := event.Object.(*batchv1.CronJob) + eventCronjob, ok := event.Object.(*batchv1.CronJob) + + if !ok { + log.Println("Error: event.Object.(*batchv1.CronJob)") + } err := s.DB.Update(func(txn *badger.Txn) error { item, err := txn.Get(cronjobsCacheKey) @@ -488,8 +527,12 @@ func (s *Sk8lServer) collectCronjobs() { cjListV2 := protoadapt.MessageV2Of(cjList) result, _ := proto.Marshal(cjListV2) + entry := badger.NewEntry(cronjobsCacheKey, result) - err := txn.SetEntry(entry) + err = txn.SetEntry(entry) + if err != nil { + log.Println("Error: collectCronjobs#txn.SetEntry", err) + } return err } @@ -497,7 +540,11 @@ func (s *Sk8lServer) collectCronjobs() { storedCjList := &batchv1.CronJobList{} storedCjListV2 := protoadapt.MessageV2Of(storedCjList) - proto.Unmarshal(stored, storedCjListV2) + err = proto.Unmarshal(stored, storedCjListV2) + + if err != nil { + log.Println("Error: collectCronjobs#proto.Unmarshal", err) + } switch event.Type { case "ADDED": @@ -510,10 +557,16 @@ func (s *Sk8lServer) collectCronjobs() { updateStoredCronjobList(storedCjList, eventCronjob) } - result, _ := proto.Marshal(storedCjListV2) + result, err := proto.Marshal(storedCjListV2) + if err != nil { + log.Println("Error: collectCronjobs#proto.Marshal", err) + } entry := badger.NewEntry(cronjobsCacheKey, result) - err := txn.SetEntry(entry) + err = txn.SetEntry(entry) + if err != nil { + log.Println("Error: collectCronjobs#txn.SetEntry", err) + } return err }) @@ -523,7 +576,6 @@ func (s *Sk8lServer) collectCronjobs() { if err != nil { panic(err) } - } else { x = s.K8sClient.WatchCronjobs() log.Println("Cronjob watching: Received all Cronjobs. Opening again") @@ -539,7 +591,11 @@ func (s *Sk8lServer) collectPods() { for { event, more := <-x.ResultChan() if more { - eventPod := event.Object.(*corev1.Pod) + eventPod, ok := event.Object.(*corev1.Pod) + + if !ok { + log.Println("Error: event.Object.(*corev1.Pod)") + } fKey := fmt.Sprintf(jobPodsKeyFmt, eventPod.Labels["job-name"]) key := []byte(fKey) @@ -551,22 +607,38 @@ func (s *Sk8lServer) collectPods() { } podListV2 := protoadapt.MessageV2Of(podList) - result, _ := proto.Marshal(podListV2) + result, err := proto.Marshal(podListV2) + + if err != nil { + log.Println("Error: collectPods#proto.Marshal", err) + } + entry := badger.NewEntry(key, result) - err := txn.SetEntry(entry) + err = txn.SetEntry(entry) return err } err = item.Value(func(val []byte) error { podList := &corev1.PodList{} podListV2 := protoadapt.MessageV2Of(podList) - proto.Unmarshal(val, podListV2) + err = proto.Unmarshal(val, podListV2) + + if err != nil { + log.Println("Error: collectPods#proto.Unmarshal", err) + } podList.Items = append(podList.Items, *eventPod) - result, _ := proto.Marshal(podListV2) + result, err := proto.Marshal(podListV2) + + if err != nil { + log.Println("Error: collectPods#proto.Marshal", err) + } entry := badger.NewEntry(key, result) - err := txn.SetEntry(entry) + err = txn.SetEntry(entry) + if err != nil { + log.Println("Error: collectCronjobs#txn.SetEntry", err) + } return err }) @@ -584,7 +656,10 @@ func (s *Sk8lServer) collectPods() { }() } -func (s *Sk8lServer) allAndRunningJobsAnPods(jobs []*batchv1.Job, cronjobUID types.UID) ([]*protos.JobResponse, []*protos.PodResponse, []*protos.JobResponse, []*protos.PodResponse) { +func (s *Sk8lServer) allAndRunningJobsAnPods( + jobs []*batchv1.Job, + cronjobUID types.UID, +) ([]*protos.JobResponse, []*protos.PodResponse, []*protos.JobResponse, []*protos.PodResponse) { jn := len(jobs) allJobsForCronJob := make([]*protos.JobResponse, 0, jn) allJobPodsForCronjob := make([]*protos.PodResponse, 0) @@ -641,7 +716,10 @@ func (s *Sk8lServer) jobsForCronjob(jobsMapped *protos.MappedJobs, cronjobName s } func (s *Sk8lServer) cronJobResponse(cronJob batchv1.CronJob, jobsForCronjob []*batchv1.Job) *protos.CronjobResponse { - allJobsForCronJob, jobPodsForCronJob, runningJobs, runningJobPods := s.allAndRunningJobsAnPods(jobsForCronjob, cronJob.UID) + allJobsForCronJob, jobPodsForCronJob, runningJobs, runningJobPods := s.allAndRunningJobsAnPods( + jobsForCronjob, + cronJob.UID, + ) lastDuration := getLastDuration(allJobsForCronJob) currentDuration := getCurrentDuration(runningJobs) @@ -679,7 +757,11 @@ func (s *Sk8lServer) cronJobResponse(cronJob batchv1.CronJob, jobsForCronjob []* return cjr } -func collectTerminatedAndFailedContainers(pod *corev1.Pod, statuses []corev1.ContainerStatus, terminationReasons *[]*protos.TerminationReason) ([]*protos.ContainerResponse, []*protos.ContainerResponse) { +func collectTerminatedAndFailedContainers( + pod *corev1.Pod, + statuses []corev1.ContainerStatus, + terminationReasons *[]*protos.TerminationReason, +) ([]*protos.ContainerResponse, []*protos.ContainerResponse) { terminatedContainers := make([]*protos.ContainerResponse, 0) failedContainers := make([]*protos.ContainerResponse, 0) @@ -741,12 +823,26 @@ func collectTerminatedAndFailedContainers(pod *corev1.Pod, statuses []corev1.Con return terminatedContainers, failedContainers } -func terminatedAndFailedContainersResponses(pod *corev1.Pod) (*protos.TerminatedContainers, *protos.TerminatedContainers) { +func terminatedAndFailedContainersResponses( + pod *corev1.Pod, +) (*protos.TerminatedContainers, *protos.TerminatedContainers) { terminatedReasons := make([]*protos.TerminationReason, 0) - terminatedEphContainers, failedEphContainers := collectTerminatedAndFailedContainers(pod, pod.Status.EphemeralContainerStatuses, &terminatedReasons) - terminatedInitContainers, failedInitContainers := collectTerminatedAndFailedContainers(pod, pod.Status.InitContainerStatuses, &terminatedReasons) - terminatedContainers, failedContainers := collectTerminatedAndFailedContainers(pod, pod.Status.ContainerStatuses, &terminatedReasons) + terminatedEphContainers, failedEphContainers := collectTerminatedAndFailedContainers( + pod, + pod.Status.EphemeralContainerStatuses, + &terminatedReasons, + ) + terminatedInitContainers, failedInitContainers := collectTerminatedAndFailedContainers( + pod, + pod.Status.InitContainerStatuses, + &terminatedReasons, + ) + terminatedContainers, failedContainers := collectTerminatedAndFailedContainers( + pod, + pod.Status.ContainerStatuses, + &terminatedReasons, + ) terminatedContainersResponse := &protos.TerminatedContainers{ InitContainers: terminatedInitContainers, @@ -817,9 +913,9 @@ func jobSucceded(job *batchv1.Job) bool { return job.Status.CompletionTime != nil } -func buildLastTimes(cronJob batchv1.CronJob) (string, string) { - var lastSuccessfulTime string - var lastScheduleTime string +func buildLastTimes(cronJob batchv1.CronJob) (lastSuccessfulTime string, lastScheduleTime string) { + // var lastSuccessfulTime string + // var lastScheduleTime string if cronJob.Status.LastSuccessfulTime != nil { lastSuccessfulTime = cronJob.Status.LastSuccessfulTime.UTC().Format(time.RFC3339) } @@ -846,7 +942,10 @@ func buildCronJobCommand(cronJob batchv1.CronJob) map[string]*protos.ContainerCo var command bytes.Buffer for _, container := range cronJob.Spec.JobTemplate.Spec.Template.Spec.InitContainers { for _, ccmd := range container.Command { - command.WriteString(fmt.Sprintf("%s ", ccmd)) + _, err := command.WriteString(fmt.Sprintf("%s ", ccmd)) + if err != nil { + log.Println("Error: buildCronJobCommand#command.WriteString") + } } initContainersCommands = append(initContainersCommands, command.String()) command.Reset() @@ -855,7 +954,10 @@ func buildCronJobCommand(cronJob batchv1.CronJob) map[string]*protos.ContainerCo containersCommands := make([]string, 0, n) for _, container := range cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers { for _, ccmd := range container.Command { - command.WriteString(fmt.Sprintf("%s ", ccmd)) + _, err := command.WriteString(fmt.Sprintf("%s ", ccmd)) + if err != nil { + log.Println("Error: buildCronJobCommand#command.WriteString") + } } containersCommands = append(containersCommands, command.String()) command.Reset() @@ -864,7 +966,10 @@ func buildCronJobCommand(cronJob batchv1.CronJob) map[string]*protos.ContainerCo ephemeralContainersinersCommands := make([]string, 0, n) for _, container := range cronJob.Spec.JobTemplate.Spec.Template.Spec.EphemeralContainers { for _, ccmd := range container.Command { - command.WriteString(fmt.Sprintf("%s ", ccmd)) + _, err := command.WriteString(fmt.Sprintf("%s ", ccmd)) + if err != nil { + log.Println("Error: buildCronJobCommand#command.WriteString") + } } ephemeralContainersinersCommands = append(ephemeralContainersinersCommands, command.String()) command.Reset() @@ -883,7 +988,7 @@ func buildCronJobCommand(cronJob batchv1.CronJob) map[string]*protos.ContainerCo return commands } -func updateStoredCronjobList(storedCjList *batchv1.CronJobList, eventCronjob *batchv1.CronJob) *batchv1.CronJobList { +func updateStoredCronjobList(storedCjList *batchv1.CronJobList, eventCronjob *batchv1.CronJob) { cjList := &batchv1.CronJobList{} // to avoid duplicates if the process is restarted and on "MODIFIED" to get the updated version of the resource for _, cronjob := range storedCjList.Items { @@ -892,8 +997,6 @@ func updateStoredCronjobList(storedCjList *batchv1.CronJobList, eventCronjob *ba } } storedCjList.Items = cjList.Items - - return storedCjList } func getCurrentDuration(runningJobsForCronJob []*protos.JobResponse) int64 { diff --git a/testdata/ca-cert.pem b/testdata/ca-cert.pem new file mode 100644 index 0000000..3d36526 --- /dev/null +++ b/testdata/ca-cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIULMSdf2cBl8Bfu8fRF64qCopL6towDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJMTAuOTYuMC4wMB4XDTI0MDQwMzEzMjgzM1oXDTM0MDQw +MTEzMjgzM1owFDESMBAGA1UEAwwJMTAuOTYuMC4wMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAkxX1O/Y+r4SiKqK5XdqXmFduu7/bxLC3GY3ZGdpsiiDV +YNwcdc+jsRgxdNNGgdZa7n/Z1bj+h5HI70K2RbQdyqC6CgVLsKTQYWQ+lfAtoJNH +8r8mcnPX1kx5dMRNrdtoMYg221XlDdv8Lo/tk48IXY0I91hcp2AxWY5R0S9J0pEU ++6RSjCpbAcu0jkQxeZcWhLPbbDsuZUBrcqJz4c0sMiFg/fFJg7OBYN+mGKwTmoek +ovZQImwzHRw/9+dFq2u+Mv+DLN7DpIxck0XvVkNk1cGadlsIb+LO1f6b0PTj2FKI +VdC5DPVL8kBNQ720XOKd69/4+wtXw4l+fl6vib3WM8YvehjDSewahFPZGwzuC4Ii +hA9RrtHALzpXVLDqtraI/Xmp/zAMwvNzaQ8e1DwDSj6U9ys4yuQbFDkEennEhJL7 +DFQ4iqH8DgiuJpy60vvOtPN5OzExnW3hHIDsmcQfQwb6yccHn5qRRmBSkP5XpsLs +Jjc87Jnmlyz4uDb7jkBJ7oAAOMkPIo8iSaadMh7Sh7Q3rNJhePSU3MhMeWjJEd7P +U+6xLfV3BsZMghlbPxIU+sy/DovvcF0mN+F0azIyjEgPVxDIRxYjb3H85vmOefLE +oxWcSHLKX1/OtyH/7mPruBvKGYjLYVERPMOqkJlqG4r/EQH4giz9xzhvHBOErXEC +AwEAAaNTMFEwHQYDVR0OBBYEFBI2rPlN23PLSfbHcM+G3JpQcLXSMB8GA1UdIwQY +MBaAFBI2rPlN23PLSfbHcM+G3JpQcLXSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAGzwwKzbD8lmxIwNOTNEhL2DgMNySEwlzCQkZOMkWpo1yMOF +WRGuSqaKZ1aMaNRp+9qwS/D+XCsX1CsKhTNTnnaFNq37RmpyTOmM0PrWs883MQRt +RoFQ++gHW5ZkGIHvZ45gwsy6fECukGMsgt7hkZHejs5xW3dF46NpVPchDvfhAcAS +8EXnkbHpH546ldwAiX2crm8pSbvIb9v6qh8t2amYykGU+7OZxFRSMAxaM7USorbG +zX0XPrjPsb3Erm6U3wKgmtKvp7MWXKNmxHKC7HTR79ogR+rrYPiEYvgYXbxmIES6 +aUHAEQ/FKiOF4SJW2hkyeI32+wibHCuipHfrTYZCHq405cSUoUl226lyn+1izEKv +u5COLOp33jr2P0V6ET3dORHxmjSdvqiGeSs9LkorzuMdo25PFcBhfa6wDe+EqoJR +LOnhSY3Raa2IwiBUxQe/I9KByI+J6sZDJkCkUCZq3x1i+h/hms3iDOGVUSf7sVDt +FYjX/gbIvFYZO2I5eJmEXKIP7SSy+nL1LrdVrO+BFthaWC85uKRhBBCmccRoyh7T +mYivUZ2iMeATt/gCUhHlJH+AG3xwIABHG0M4tEW1YY97/7Fw5VuT1l1i83eO8Xey +VqNuzRr1y6PPpurm70eDXorauk3p+DcMVqJCDbDY8gpJLLIeMU+CyGxZl9nZ +-----END CERTIFICATE----- diff --git a/testdata/ca-key.key b/testdata/ca-key.key new file mode 100644 index 0000000..cc6125e --- /dev/null +++ b/testdata/ca-key.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +ApkQ6cka2GVGpAg2EmrGo8pT44OBnQ== +-----END PRIVATE KEY----- diff --git a/testdata/invalid-cert.key b/testdata/invalid-cert.key new file mode 100644 index 0000000..a49bd01 --- /dev/null +++ b/testdata/invalid-cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDpl+zVFIM74puF +LIuFJqkEK9i6DDcUB+V20bCwAJ+6lB5s6EXJTwFIWy+jMQz31hJeRTGf2GP3Irsp +qRQgLGNzjGok2QnBt/daprdWyOCpnTviSHOQkgVAsc0jEgvakMhCvt8l80FTvWxo +qx4jtjJO0mFyk/UdghhwkowkwTWMeubYneX/IpryXjSo10SBU60qmfDp7KbiDKNy +IKfO2sZPd2btdNtTGIIp/OxtORc/J0L8NQXueTNS7abXqeiZvwq/xMbNgh1OF5g8 +OO8cfPZsBujTp4U9g5Ha3fqevRwKu/p8ssxgjIXVSf/A9R/PXp0sZmtcLNf2YSe8 +Lo+fO5UHAgMBAAECggEAWHMp/s8LGDBoogcpd0wZwJYXtO7uJhwNU633wNUjpkLy +uFtFHYJYYn0Ar0Lh/bAl+WMq9LM7uvcANriqgbo9GCIpiPVTv/H4a+9nyak7hI4g +gEDd3XalHYeVvPtW2LZs1fUvSs/uwxYdSA7PVUdmijkYJAoJTNiXh3y+Kyarr5qa +qMXConsFnEmXozRN3OK5AkHdG2j4SHFSzh7PRrxFTfYvDSppFvw4O8ZI6LdiQLtN +pIb5ULTZEqy/sna2MyZCUpCtZuXH7RlQsruosy+TiO3cweaGRttQkNmXfk486Lcn +pMuKME02gMmK8Z65p1Fqz5mYAJ2PyhyplkVu8aaZuQKBgQDs9GAB6F2SwChCEVY+ +61elV+UdBzBzzFPT1diJXNZxlM6q1y7Ib4EEDFLJvpr6vXW/TiPQJYWMr1Ywmfem +ing4vPzSQMIK6hGH+8LPddAZh4emPIAMklhFh3rdphecAC6f2X1B2Y4nSZQMhBde +tRPnBUm4CPlHFkY6tQMjQSbKvwKBgQD8XmQJLEb1Jjyc1KtADkZepNOgvZn3+Bpr +b63BKUmmWmeRTBfoDWevd+OpV/7EyD1dx7zbUNbfBtr2L0UoeND2x4UiAyGU7Vl8 +qzA+KvTHjbgHIse3Fm4gea3UUirhYSvBK4uWkWC5sFPPdbDfA43qJqy2cDWxKG/v +FjXYdYYvuQKBgQDlif2PG4e550eYaK8BmH15AGJD8njvVBpIdTmkJzjdImfpezYo +mEuEnmN8Z5Y+G1Z7EbkDo7VxiVCXGC+dNzoqzHilPHI8nG6LKkmXKuuCL5YqZSUu +CH5WLF3LZWAtkMeZtdfu+E8Ko+41CaiR6Bv1iVvWWWsRf/RcXolBl5V6hQKBgArv +rDYZjRmiho3lSWaFN9dyHRmiHcH5Jmvxcv4j4+UplcNeIMsViaC4+UylW4z7gG1H +8o8ueaFAksyIT97pHWUUUFig+huSDYaxhG2tPt5G7eqhPzZX6n3NyBKjXOhXZaYB +VRM5SY/CbF9ZIkkHxmaXNnTReAItVmxt8fdnYGFBAoGBAIep/tFRYWxdKD/oor62 +KuqGAv+rTpAiuUt/ume0O+X2cgCtTkifghY1UN4X+5yMhvQekebHbPjyaG4+dulm +np7+0Y2ecZn6rp0GayKZQ6S5DrqNrxOEhvJC1AiqEeIiCwN8nZQdsAG767HNbUgw +vACIVEKep+QcS/zh27lg1m0W +-----END PRIVATE KEY----- diff --git a/testdata/invalid-cert.pem b/testdata/invalid-cert.pem new file mode 100644 index 0000000..8dcbe5f --- /dev/null +++ b/testdata/invalid-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUAgI7Uo0qAsE3gUO+KpNOfzrlU7QwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA0MDExMzQ1MDlaFw0yNDA0 +MDIxMzQ1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDpl+zVFIM74puFLIuFJqkEK9i6DDcUB+V20bCwAJ+6 +lB5s6EXJTwFIWy+jMQz31hJeRTGf2GP3IrspqRQgLGNzjGok2QnBt/daprdWyOCp +nTviSHOQkgVAsc0jEgvakMhCvt8l80FTvWxoqx4jtjJO0mFyk/UdghhwkowkwTWM +eubYneX/IpryXjSo10SBU60qmfDp7KbiDKNyIKfO2sZPd2btdNtTGIIp/OxtORc/ +J0L8NQXueTNS7abXqeiZvwq/xMbNgh1OF5g8OO8cfPZsBujTp4U9g5Ha3fqevRwK +u/p8ssxgjIXVSf/A9R/PXp0sZmtcLNf2YSe8Lo+fO5UHAgMBAAGjUzBRMB0GA1Ud +DgQWBBT2ltlHx6jMoZcvFqphI/FLWWewuTAfBgNVHSMEGDAWgBT2ltlHx6jMoZcv +FqphI/FLWWewuTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCY +olMxnGyfPKurn9KHsw9RVf27fLKeJV1sJwMJ2Ye+j2m22WfWnOjkCeixjaPngexS +LmgbWXs0U7lYfRGscNSsAGoA2xrfOY9MWYfy37/JnXA5yWGH2nFtP6mYvpS34l6D +hboy1bPzAPjtUdUbX8U+p7AbcA/ppHXnoY9gc6uw+j7J9J7ereeKEeauL+02F8eP +l9LKloXGYE1SqoWcssvVL11htYGcaqJAgkR3cpGEdFUORLe4QKlwZlzCS65gJRBu +7XAfmDyYh010+Stj7o30BfTkIXal8rW2Pn8ktuHRfTTL88PiS966Pj2jCtW+tN9x +tDcbi7lNlLD3m8CzA1zk +-----END CERTIFICATE----- diff --git a/testdata/server-cert.pem b/testdata/server-cert.pem new file mode 100644 index 0000000..a3e2235 --- /dev/null +++ b/testdata/server-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFOzCCAyMCFFSsBRYvhv0wzFx2jlAeGMuI36T1MA0GCSqGSIb3DQEBCwUAMBQx +EjAQBgNVBAMMCTEwLjk2LjAuMDAeFw0yNDA0MDMxMzI4MzhaFw0yNDA3MDIxMzI4 +MzhaMIGfMQswCQYDVQQGEwJGUjEWMBQGA1UECAwNSWxlIGRlIEZyYW5jZTEOMAwG +A1UEBwwFUGFyaXMxEzARBgNVBAoMClNlcnZlciBUTFMxDzANBgNVBAsMBlNlcnZl +cjEkMCIGA1UEAwwbc2s4bC5zazhsLnBvZC5jbHVzdGVyLmxvY2FsMRwwGgYJKoZI +hvcNAQkBFg10bHNAZ21haWwuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEArL+CYuxbCBjAUWuP7K4jISFWXaLJehh1QLSWnnwXRd75XoAjLlU5TFaA +ClCFYD/f+uhaoOGZgi06mur7lkMlYj7wcD9ezsn0rznIGPnSfhpH1HN0JK/lGi/V +p+pZ41322iCLrO6x1HtVY7UJKHTaQYQJH9Nrh/YOh0hBqFaBAC+2E56ChgfRhC11 +C/5QTqbnB/dGRo2mhX3h4ijKLJOTxqBJQ+7B6xkyMbcg0t1vmQkydOR0Kt+E2sj6 +hm9SmCg05dRMugwLonrt2LCATvAbqiBMIjDSjUDH2qOEpaMN94kOvn3v3UiDS4aB +uimyhYN24jT0nvEgb5nzs3p+XHVT3Pxkibsnrmec/1IlLLSfCP5W1f3/CrcJHyYS +BRSo4rMf+mDKjEcDNPgCgO5ReXlEoXsd2uATI4W/vAPiu6V00VOD7XxtuhlLzhqZ +XeNbnOAS3AE2HaGIkvl4MeQOShqnQIaadL5r/9OLZlxPhWgD7D6+HbYJobUSM7Vy +VqrBhRyqQ0fqM+I7UiETT4WcwKn6abVuVpYb4YpXOdcqOQ4mbVi39zEt3pdCt53H +ipPL4HK1MlTEiItApM7Auy8oAP96Fh8SF3+x8LLm2mO9mEC8FzaXcRMTrDpXbhH3 +GW6ONZS5JBexILfHjWeT8IQ9UZM32JeE7ak/MaEsr2D/OQEqum0CAwEAATANBgkq +hkiG9w0BAQsFAAOCAgEABMPw/5e8wrblr6JsAdzC5p3dLt4YrYzZRsz6cOTwF25v +iFzzbEZBVIhbsIEQM7qzrw/gl739rUxU65ZMrElZzpyyOT0ODl6CnFBUAy6KRZna +QhfShAlRWaj8WknR2aZB3Tdw7gf+qEwHYjizhJQyLvVvR35FRLpz3vx3AFTuvA+m +7VcGQEczdMEcIV/V+rw0NS55N/Bsg1dW02wHdCGh3wNYck2D9zkuHbebW5ez0CPM +6haUyYB61+htisnIni46szgil4kO5AIRplSMwRshPiCFioQEzhbxHpy4i9veikwt +C+V+nGZigjiiMdwR4MXJYASk26zvx2p6o0IpQj3ab/k6pT91mDFId3JVDeEIlvas +jYyFvOG6rEnMqxmHFy1F5ywbyMZXXr2WXKspdykk5l8N917nLTwLcogAe91NZYcB +nh0KJ7okPItEu+ULkUob1sKDW/IpjADynVH4CLQn7weRCWvEjpW4eZwa7ilP99oK +svF3bS5chDTbZjWrJMH6bcjeY9Pax6UbFFCKzAKS8+F+rQHVgPLqc0Jdcf2x+2t+ +55uECIa+kFhDf//nYlirLnFRlcoc0zSCmnsFItlolrY+V1atYJRFZbf9QtTlzWl8 +Zo+BBk5qiOofnPygk0lYX0QYE+lSPxkRqEjnWPUPpN0S9f6H2A0lxOxrpX3VLow= +-----END CERTIFICATE----- diff --git a/testdata/server-key.key b/testdata/server-key.key new file mode 100644 index 0000000..925d9d8 --- /dev/null +++ b/testdata/server-key.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +ozBmlfz5iuBOo4wz5UjHkaPZ7DrwjkQ= +-----END PRIVATE KEY----- diff --git a/testdata/server-req.pem b/testdata/server-req.pem new file mode 100644 index 0000000..76f8cab --- /dev/null +++ b/testdata/server-req.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE5TCCAs0CAQAwgZ8xCzAJBgNVBAYTAkZSMRYwFAYDVQQIDA1JbGUgZGUgRnJh +bmNlMQ4wDAYDVQQHDAVQYXJpczETMBEGA1UECgwKU2VydmVyIFRMUzEPMA0GA1UE +CwwGU2VydmVyMSQwIgYDVQQDDBtzazhsLnNrOGwucG9kLmNsdXN0ZXIubG9jYWwx +HDAaBgkqhkiG9w0BCQEWDXRsc0BnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCsv4Ji7FsIGMBRa4/sriMhIVZdosl6GHVAtJaefBdF3vle +gCMuVTlMVoAKUIVgP9/66Fqg4ZmCLTqa6vuWQyViPvBwP17OyfSvOcgY+dJ+GkfU +c3Qkr+UaL9Wn6lnjXfbaIIus7rHUe1VjtQkodNpBhAkf02uH9g6HSEGoVoEAL7YT +noKGB9GELXUL/lBOpucH90ZGjaaFfeHiKMosk5PGoElD7sHrGTIxtyDS3W+ZCTJ0 +5HQq34TayPqGb1KYKDTl1Ey6DAuieu3YsIBO8BuqIEwiMNKNQMfao4Slow33iQ6+ +fe/dSINLhoG6KbKFg3biNPSe8SBvmfOzen5cdVPc/GSJuyeuZ5z/UiUstJ8I/lbV +/f8KtwkfJhIFFKjisx/6YMqMRwM0+AKA7lF5eUShex3a4BMjhb+8A+K7pXTRU4Pt +fG26GUvOGpld41uc4BLcATYdoYiS+Xgx5A5KGqdAhpp0vmv/04tmXE+FaAPsPr4d +tgmhtRIztXJWqsGFHKpDR+oz4jtSIRNPhZzAqfpptW5Wlhvhilc51yo5DiZtWLf3 +MS3el0K3nceKk8vgcrUyVMSIi0CkzsC7LygA/3oWHxIXf7HwsubaY72YQLwXNpdx +ExOsOlduEfcZbo41lLkkF7Egt8eNZ5PwhD1RkzfYl4TtqT8xoSyvYP85ASq6bQID +AQABoAAwDQYJKoZIhvcNAQELBQADggIBAFPs9D8JU/Glz5hmHJEBnQpExA38Vjyi +SBniTHd6Ws7tzvM2x4MbYA5pflaPIrs277c1g2k4RfIn3fIzYbkkC98BieNqPJI1 +l6XdFko+Q5w9XEh++8y0p2yPg95T7MGV0fgcsfNMtTTqzsxBcvLbGr61cWlKLAp+ +u7bHpE0lPXb9sVEFjABZmY3trvYMC8ofj/781R66sjkstrEcLSJ65TiYENeYJ4Iu +4itDkA+LW+hUiPdNKQ5vLDBdW30mrPbYYvFnBPT9SEBY1o1vzY49F7VBFT5NIBEz +vebNH+h3mOi1+dQRrnS+kTCVPoGrgHOzCfzzN2RUZWTojxZafe8hL4KIR5YZN71L +tBiO3rlB6QY094DqB5qYM4S0MwKp/246ohyrm48s0aKDwV6qvYcxgquiNXi5U+vm +RohkyOc7vf0RtrCjt84jie3FhDFCpWc+qQL/65KanTxbLnFKy27ltdoZLEwHQszQ +AXRIJrfkw/L84op/rF0oMdXer+qx3f/jND9WejVGQDD/A0F3yAiDTRhjn0NizSoB +/7B36RSTJr0zx+P00Q+KvyksD52Kk3egBycTN2z8yQK7TLjudUZ/8z2Ddj2/3TRd +qO30TM6V+tXnm+9zp7Rp5ftKDGMG4qifN5DKT/V/FOUk63j/b2TsJMHVs87NeM26 +pxlx4P3AT7fT +-----END CERTIFICATE REQUEST----- diff --git a/tls.go b/tls.go index 797564c..0e02b77 100644 --- a/tls.go +++ b/tls.go @@ -3,12 +3,41 @@ package main import ( "crypto/tls" "crypto/x509" + "errors" "fmt" - "io/ioutil" + "os" + "path/filepath" ) -func setupTLS(certFile, certKeyFile, caFile string) (*tls.Config, error) { - tlsConfig := &tls.Config{} +type CertPool interface { + AppendCertsFromPEM(caBytes []byte) bool +} + +var ( + ErrFailedToAppend = errors.New("failed to append Root certificate") + ErrTLS = errors.New("TLS Error") + ErrCertPool = errors.New("certPool Error") + ErrCACertFile = errors.New("caCertFile Error") +) + +type CertError struct { + Err error + CertFile string + Msg string +} + +func (ce *CertError) Error() string { + return fmt.Sprintf("CertError: %s - %s (%s)", ce.Err.Error(), ce.Msg, ce.CertFile) +} + +func (ce *CertError) Unwrap() error { + return fmt.Errorf(" %w: %s", ce.Err, ce.Msg) +} + +func setupTLS(certFile, certKeyFile, caFile string, certPool CertPool) (*tls.Config, error) { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS13, + } var err error tlsConfig.Certificates = make([]tls.Certificate, 1) @@ -18,22 +47,47 @@ func setupTLS(certFile, certKeyFile, caFile string) (*tls.Config, error) { ) if err != nil { - return nil, err + cErr := &CertError{ + CertFile: certFile, + Msg: err.Error(), + Err: ErrTLS, + } + return nil, cErr } - caBytes, err := ioutil.ReadFile(caFile) + caBytes, err := os.ReadFile(filepath.Clean(caFile)) + + if err != nil { + cErr := &CertError{ + CertFile: certFile, + Msg: err.Error(), + Err: ErrCACertFile, + } + return nil, cErr + } + + ok := certPool.AppendCertsFromPEM(caBytes) + + if !ok { + cErr := &CertError{ + CertFile: certFile, + Err: ErrFailedToAppend, + } + return nil, cErr + } - certPool := x509.NewCertPool() - ok := certPool.AppendCertsFromPEM([]byte(caBytes)) + certPoolAsX509CertPool, ok := certPool.(*x509.CertPool) if !ok { - return nil, fmt.Errorf( - "failed to parse root certificate: %q", certFile, - ) + cErr := &CertError{ + CertFile: certFile, + Err: ErrCertPool, + } + return nil, cErr } - tlsConfig.ClientCAs = certPool - tlsConfig.RootCAs = certPool + tlsConfig.ClientCAs = certPoolAsX509CertPool + tlsConfig.RootCAs = certPoolAsX509CertPool tlsConfig.ServerName = "0.0.0.0" return tlsConfig, nil diff --git a/tls_test.go b/tls_test.go new file mode 100644 index 0000000..24316a1 --- /dev/null +++ b/tls_test.go @@ -0,0 +1,128 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "testing" +) + +type CustomCertPool struct { + certPool *x509.CertPool +} + +func (c *CustomCertPool) AppendCertsFromPEM(caBytes []byte) bool { + return false +} + +func TestSetupTLS(t *testing.T) { + testCases := []struct { + certPool CertPool + expectedError error + expectedErrMsg string + name string + certFile string + keyFile string + caFile string + expectedSN string + expectedMinVer uint16 + expectedCAs bool + }{ + { + name: "ValidFiles", + certFile: "testdata/server-cert.pem", + keyFile: "testdata/server-key.key", + caFile: "testdata/ca-cert.pem", + certPool: x509.NewCertPool(), + expectedError: nil, + expectedErrMsg: "", + expectedMinVer: tls.VersionTLS13, + expectedCAs: true, + expectedSN: "0.0.0.0", + }, + { + name: "InvalidCA", + certFile: "testdata/invalid-cert.pem", + keyFile: "testdata/server-key.key", + caFile: "testdata/ca-cert.pem", + certPool: &x509.CertPool{}, + expectedError: ErrTLS, + expectedErrMsg: "CertError: TLS Error - tls: private key does not match public key (testdata/invalid-cert.pem)", + expectedMinVer: 0, + expectedCAs: false, + expectedSN: "", + }, + { + name: "FailedToAppend", + certFile: "testdata/server-cert.pem", + keyFile: "testdata/server-key.key", + caFile: "testdata/ca-cert.pem", + certPool: &CustomCertPool{certPool: x509.NewCertPool()}, + expectedError: ErrFailedToAppend, + expectedErrMsg: "CertError: failed to append Root certificate - (testdata/server-cert.pem)", + expectedMinVer: 0, + expectedCAs: false, + expectedSN: "", + }, + { + name: "MissingCACert", + certFile: "testdata/server-cert.pem", + keyFile: "testdata/server-key.key", + caFile: "testdata/no-ca-cert.pem", + certPool: x509.NewCertPool(), + expectedError: ErrCACertFile, + expectedErrMsg: "CertError: caCertFile Error - open testdata/no-ca-cert.pem: no such file or directory (testdata/server-cert.pem)", + expectedMinVer: 0, + expectedCAs: false, + expectedSN: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tlsConfig, err := setupTLS(tc.certFile, tc.keyFile, tc.caFile, tc.certPool) + + if err != nil { + if !errors.Is(err, tc.expectedError) { + t.Errorf("expected error to be %v, got %v", tc.expectedError, err) + } + + if tc.expectedError != nil { + certError, ok := err.(*CertError) + if !ok { + t.Errorf("unexpected error type: got %T, want *CertError", err) + return + } + + if !errors.Is(certError.Err, tc.expectedError) { + t.Errorf("unexpected error type: got %v, want %v", certError.Err, tc.expectedError) + } + + if err.Error() != tc.expectedErrMsg { + t.Errorf("unexpected error message: got %s, want %s", err.Error(), tc.expectedErrMsg) + } + } + } else { + if tlsConfig.MinVersion != tc.expectedMinVer { + t.Error("Unexpected MinVersion value") + } + + if tlsConfig.ClientCAs == nil || tlsConfig.RootCAs == nil { + if tc.expectedCAs { + t.Error("Expected ClientCAs and RootCAs to be non-nil") + } + } else { + if tlsConfig.ClientCAs != tc.certPool || tlsConfig.RootCAs != tc.certPool { + if tc.expectedCAs { + t.Error("ClientCAs and RootCAs should match the passed certPool") + } + } + } + + if tlsConfig.ServerName != tc.expectedSN { + t.Error("Unexpected ServerName value") + } + } + }) + } +} diff --git a/version.go b/version.go index 93fc24d..6d29038 100644 --- a/version.go +++ b/version.go @@ -1,6 +1,6 @@ package main -// Set with LDFLAGS +// Set with LDFLAGS. var version = "unset" func Version() string {