diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 176612ae8..baa356165 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,10 +13,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f15404847..b479ecef8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,10 +35,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 # âšī¸ Command-line programs to run using the OS shell. # đ https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7b751dd55..144dad16c 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -40,10 +40,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -62,10 +62,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 8d7d886c4..8bd6d0a11 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -14,13 +14,13 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: # ensures we fetch tag history for the repository fetch-depth: 0 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6ba334f4e..5ed24b19b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,13 +13,13 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: # ensures we fetch tag history for the repository fetch-depth: 0 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index ab18a35ac..61f82e8c6 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -12,10 +12,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -23,7 +23,7 @@ jobs: check-latest: true - name: golangci-lint - uses: reviewdog/action-golangci-lint@8e1117c7d327bbfb1eb7ec8dc2d895d13e6e17c3 # v2.6.0 + uses: reviewdog/action-golangci-lint@00311c26a97213f93f2fd3a3524d66762e956ae0 # v2.6.1 with: github_token: ${{ secrets.github_token }} golangci_lint_flags: "--config=.golangci.yml --timeout=5m" @@ -36,10 +36,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -47,7 +47,7 @@ jobs: check-latest: true - name: golangci-lint - uses: reviewdog/action-golangci-lint@8e1117c7d327bbfb1eb7ec8dc2d895d13e6e17c3 # v2.6.0 + uses: reviewdog/action-golangci-lint@00311c26a97213f93f2fd3a3524d66762e956ae0 # v2.6.1 with: github_token: ${{ secrets.github_token }} golangci_lint_flags: "--config=.golangci.yml --timeout=5m" diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 57789b101..6e73989b2 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -13,10 +13,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec185448b..52c9d17ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,10 +13,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -28,7 +28,7 @@ jobs: make test - name: coverage - uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 # v4.0.2 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} file: coverage.out \ No newline at end of file diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml index 462f9be92..84ffec5ac 100644 --- a/.github/workflows/validate-pr-title.yml +++ b/.github/workflows/validate-pr-title.yml @@ -13,5 +13,7 @@ jobs: steps: - name: validate title + env: + TITLE: ${{ github.event.pull_request.title }} run: | - echo "${{ github.event.pull_request.title }}" | grep -Eq '^(feat|fix|chore|refactor|enhance|test|docs)(\(.*\)|):\s.+$' && (echo "Pass"; exit 0) || (echo "Incorrect Format. Please see https://go-vela.github.io/docs/community/contributing_guidelines/#development-workflow"; exit 1) + echo "$TITLE" | grep -Eq '^(feat|fix|chore|refactor|enhance|test|docs)(\(.*\)|)!?:\s.+$' && (echo "Pass"; exit 0) || (echo "Incorrect Format. Please see https://go-vela.github.io/docs/community/contributing_guidelines/#development-workflow"; exit 1) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ec175ea5b..ad53c2bf8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -13,10 +13,10 @@ jobs: steps: - name: clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: # use version from go.mod file go-version-file: 'go.mod' @@ -36,9 +36,12 @@ jobs: # Check that go fmt ./... produces a zero diff; clean up any changes afterwards. go fmt ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) # Check that go fix ./... produces a zero diff; clean up any changes afterwards. - go fix ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) + # + # Renable this after https://github.com/golang/go/commit/7fd62ba821b1044e8e4077df052b0a1232672d57 + # has been released. + # go fix ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) - name: validate spec run: | sudo make spec-install - make spec \ No newline at end of file + make spec diff --git a/.golangci.yml b/.golangci.yml index 48151b3e9..df7592d27 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,7 +8,10 @@ # outputs it results from the linters it executes. output: # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - format: colored-line-number + formats: + - format: json + path: stderr + - format: colored-line-number # print lines of code with issue, default is true print-issued-lines: true @@ -32,6 +35,18 @@ linters-settings: lines: 160 statements: 70 + # https://github.com/daixiang0/gci + # ensure import order is consistent + # gci write --custom-order -s standard -s default -s blank -s dot -s "prefix(github.com/go-vela)" . + gci: + custom-order: true + sections: + - standard + - default + - blank + - dot + - prefix(github.com/go-vela) + # https://github.com/denis-tingaikin/go-header goheader: template: |- @@ -43,10 +58,9 @@ linters-settings: # https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint nolintlint: - allow-leading-space: true # allow non-"machine-readable" format (ie. with leading space) - allow-unused: false # allow nolint directives that don't address a linting issue - require-explanation: true # require an explanation for nolint directives - require-specific: true # require nolint directives to be specific about which linter is being skipped + allow-unused: false # allow nolint directives that don't address a linting issue + require-explanation: true # require an explanation for nolint directives + require-specific: true # require nolint directives to be specific about which linter is being skipped # This section provides the configuration for which linters # golangci will execute. Several of them were disabled by @@ -57,46 +71,43 @@ linters: # enable a specific set of linters to run enable: - - bidichk # checks for dangerous unicode character sequences - - bodyclose # checks whether HTTP response body is closed successfully - - contextcheck # check the function whether use a non-inherited context - - deadcode # finds unused code - - dupl # code clone detection - - errcheck # checks for unchecked errors - - errorlint # find misuses of errors - - exportloopref # check for exported loop vars - - funlen # detects long functions - - goconst # finds repeated strings that could be replaced by a constant - - gocyclo # computes and checks the cyclomatic complexity of functions - - godot # checks if comments end in a period - - gofmt # checks whether code was gofmt-ed - - goheader # checks is file header matches to pattern - - goimports # fixes imports and formats code in same style as gofmt - - gomoddirectives # manage the use of 'replace', 'retract', and 'excludes' directives in go.mod - - goprintffuncname # checks that printf-like functions are named with f at the end - - gosec # inspects code for security problems - - gosimple # linter that specializes in simplifying a code - - govet # reports suspicious constructs, ex. Printf calls whose arguments don't align with the format string - - ineffassign # detects when assignments to existing variables aren't used - - makezero # finds slice declarations with non-zero initial length - - misspell # finds commonly misspelled English words in comments - - nakedret # finds naked returns in functions greater than a specified function length - - nilerr # finds the code that returns nil even if it checks that the error is not nil - - noctx # noctx finds sending http request without context.Context - - nolintlint # reports ill-formed or insufficient nolint directives - - revive # linter for go - - staticcheck # applies static analysis checks, go vet on steroids - - structcheck # finds unused struct fields - - stylecheck # replacement for golint - - tenv # analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 - - typecheck # parses and type-checks go code, like the front-end of a go compiler - - unconvert # remove unnecessary type conversions - - unparam # reports unused function parameters - - unused # checks for unused constants, variables, functions and types - - varcheck # finds unused global variables and constants - - whitespace # detects leading and trailing whitespace - - wsl # forces code to use empty lines - + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + - dupl # code clone detection + - errcheck # checks for unchecked errors + - errorlint # find misuses of errors + - exportloopref # check for exported loop vars + - funlen # detects long functions + - gci # consistent import ordering + - goconst # finds repeated strings that could be replaced by a constant + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - gofmt # checks whether code was gofmt-ed + - goheader # checks is file header matches to pattern + - gomoddirectives # manage the use of 'replace', 'retract', and 'excludes' directives in go.mod + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects code for security problems + - gosimple # linter that specializes in simplifying a code + - govet # reports suspicious constructs, ex. Printf calls whose arguments don't align with the format string + - ineffassign # detects when assignments to existing variables aren't used + - makezero # finds slice declarations with non-zero initial length + - misspell # finds commonly misspelled English words in comments + - nakedret # finds naked returns in functions greater than a specified function length + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - noctx # noctx finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - revive # linter for go + - staticcheck # applies static analysis checks, go vet on steroids + - stylecheck # replacement for golint + - tenv # analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + - typecheck # parses and type-checks go code, like the front-end of a go compiler + - unconvert # remove unnecessary type conversions + - unparam # reports unused function parameters + - unused # checks for unused constants, variables, functions and types + - whitespace # detects leading and trailing whitespace + - wsl # forces code to use empty lines + # static list of linters we know golangci can run but we've # chosen to leave disabled for now # - asciicheck - non-critical @@ -109,13 +120,13 @@ linters: # - exhaustivestruct - style preference # - forbidigo - unused # - forcetypeassert - unused - # - gci - use goimports # - gochecknoinits - unused # - gochecknoglobals - global variables allowed - # - gocognit - unused complexity metric + # - gocognit - unused complexity metric # - gocritic - style preference # - godox - to be used in the future # - goerr113 - to be used in the future + # - goimports - use gci # - golint - archived, replaced with revive # - gofumpt - use gofmt # - gomnd - get too many false-positives @@ -123,7 +134,7 @@ linters: # - ifshort - use both styles # - ireturn - allow interfaces to be returned # - importas - want flexibility with naming - # - lll - not too concerned about line length + # - lll - not too concerned about line length # - interfacer - archived # - nestif - non-critical # - nilnil - style preference @@ -132,7 +143,7 @@ linters: # - paralleltest - false-positives # - prealloc - don't use # - predeclared - unused - # - promlinter - style preference + # - promlinter - style preference # - rowserrcheck - unused # - scopelint - deprecated - replaced with exportloopref # - sqlclosecheck - unused diff --git a/Dockerfile b/Dockerfile index 1a9604914..8b3860b37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -FROM alpine:3.19.1@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b as certs +FROM alpine:3.20.0@sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb4f25238ebd as certs RUN apk add --update --no-cache ca-certificates diff --git a/Dockerfile-alpine b/Dockerfile-alpine index d1512ab9e..21dd79e8f 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -FROM alpine:3.19.1@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b +FROM alpine:3.20.0@sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb4f25238ebd RUN apk add --update --no-cache ca-certificates diff --git a/Makefile b/Makefile index 0df0c3fe9..83527e308 100644 --- a/Makefile +++ b/Makefile @@ -336,3 +336,14 @@ lint: @echo @echo "### Linting Go Code" @golangci-lint run ./... + +# The `lintfix` target is intended to lint the +# Go source code with golangci-lint and apply +# any fixes that can be automatically applied. +# +# Usage: `make lintfix` +.PHONY: lintfix +lintfix: + @echo + @echo "### Fixing Go code with linter" + @golangci-lint run ./... --fix diff --git a/api/admin/build.go b/api/admin/build.go index a56cdbd16..525179256 100644 --- a/api/admin/build.go +++ b/api/admin/build.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore similar code package admin import ( @@ -9,18 +8,17 @@ import ( "strconv" "time" - "github.com/go-vela/server/database" - "github.com/go-vela/server/util" - - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" ) // swagger:operation GET /api/v1/admin/builds/queue admin AllBuildsQueue // -// Get all of the running and pending builds in the database +// Get running and pending builds // // --- // produces: @@ -39,20 +37,25 @@ import ( // schema: // type: array // items: -// "$ref": "#/definitions/BuildQueue" +// "$ref": "#/definitions/QueueBuild" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve all running and pending builds from the database +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// AllBuildsQueue represents the API handler to -// captures all running and pending builds stored in the database. +// AllBuildsQueue represents the API handler to get running and pending builds. func AllBuildsQueue(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Debug("platform admin: reading running and pending builds") + // capture middleware values ctx := c.Request.Context() - logrus.Info("Admin: reading running and pending builds") - // default timestamp to 24 hours ago if user did not provide it as query parameter after := c.DefaultQuery("after", strconv.FormatInt(time.Now().UTC().Add(-24*time.Hour).Unix(), 10)) @@ -71,7 +74,7 @@ func AllBuildsQueue(c *gin.Context) { // swagger:operation PUT /api/v1/admin/build admin AdminUpdateBuild // -// Update a build in the database +// Update a build // // --- // produces: @@ -79,7 +82,7 @@ func AllBuildsQueue(c *gin.Context) { // parameters: // - in: body // name: body -// description: Payload containing build to update +// description: The build object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Build" @@ -87,38 +90,46 @@ func AllBuildsQueue(c *gin.Context) { // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the build in the database +// description: Successfully updated the build // schema: // "$ref": "#/definitions/Build" -// '404': -// description: Unable to update the build in the database +// '400': +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the build in the database +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateBuild represents the API handler to -// update any build stored in the database. +// UpdateBuild represents the API handler to update a build. func UpdateBuild(c *gin.Context) { - logrus.Info("Admin: updating build in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating build") + // capture body from API request - input := new(library.Build) + input := new(types.Build) err := c.Bind(input) if err != nil { retErr := fmt.Errorf("unable to decode JSON for build %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "build": input.GetNumber(), + "build_id": input.GetID(), + "repo": util.EscapeValue(input.GetRepo().GetName()), + "repo_id": input.GetRepo().GetID(), + "org": util.EscapeValue(input.GetRepo().GetOrg()), + }).Debug("platform admin: attempting to update build") + // send API call to update the build b, err := database.FromContext(c).UpdateBuild(ctx, input) if err != nil { @@ -129,5 +140,13 @@ func UpdateBuild(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + "org": b.GetRepo().GetOrg(), + }).Info("platform admin: updated build") + c.JSON(http.StatusOK, b) } diff --git a/api/admin/clean.go b/api/admin/clean.go index 5078a217b..da3949568 100644 --- a/api/admin/clean.go +++ b/api/admin/clean.go @@ -8,19 +8,18 @@ import ( "strconv" "time" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types" "github.com/go-vela/types/constants" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/admin/clean admin AdminCleanResources // -// Update pending build resources to error status before a given time +// Update stale build resources to error status // // --- // produces: @@ -28,7 +27,7 @@ import ( // parameters: // - in: query // name: before -// description: filter pending resources created before a certain time +// description: Filter stale resources created before a certain time // required: true // type: integer // - in: body @@ -43,28 +42,27 @@ import ( // '200': // description: Successfully updated pending resources with error message // schema: -// type: string +// type: string // '400': -// description: Unable to update resources â bad request +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" // '401': -// description: Unable to update resources â unauthorized +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update resources +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CleanResources represents the API handler to -// update any user stored in the database. +// CleanResources represents the API handler to update stale resources. func CleanResources(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() - logrus.Infof("platform admin %s: updating pending resources in database", u.GetName()) + l.Debug("platform admin: cleaning resources") // default error message msg := "build cleaned by platform admin" @@ -83,7 +81,7 @@ func CleanResources(c *gin.Context) { // if a message is provided, set the error message to that if input.Message != nil { - msg = *input.Message + msg = util.EscapeValue(*input.Message) } // capture before query parameter, default to max build timeout @@ -106,7 +104,7 @@ func CleanResources(c *gin.Context) { return } - logrus.Infof("platform admin %s: cleaned %d builds in database", u.GetName(), builds) + l.Debugf("platform admin: cleaned %d builds", builds) // clean executables executables, err := database.FromContext(c).CleanBuildExecutables(ctx) @@ -118,6 +116,8 @@ func CleanResources(c *gin.Context) { return } + l.Debugf("platform admin: cleaned %d executables", executables) + // clean services services, err := database.FromContext(c).CleanServices(ctx, msg, before) if err != nil { @@ -128,7 +128,7 @@ func CleanResources(c *gin.Context) { return } - logrus.Infof("platform admin %s: cleaned %d services in database", u.GetName(), services) + l.Debugf("platform admin: cleaned %d services", services) // clean steps steps, err := database.FromContext(c).CleanSteps(ctx, msg, before) @@ -140,7 +140,7 @@ func CleanResources(c *gin.Context) { return } - logrus.Infof("platform admin %s: cleaned %d steps in database", u.GetName(), steps) + l.Debugf("platform admin: cleaned %d steps", steps) c.JSON(http.StatusOK, fmt.Sprintf("%d builds cleaned. %d executables cleaned. %d services cleaned. %d steps cleaned.", builds, executables, services, steps)) } diff --git a/api/admin/deployment.go b/api/admin/deployment.go index deeac0045..f205c90d1 100644 --- a/api/admin/deployment.go +++ b/api/admin/deployment.go @@ -10,20 +10,23 @@ import ( // swagger:operation PUT /api/v1/admin/deployment admin AdminUpdateDeployment // -// Get All (Not Implemented) +// Update a deployment (Not Implemented) // // --- // produces: // - application/json // parameters: // responses: +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '501': // description: This endpoint is not implemented // schema: // type: string -// UpdateDeployment represents the API handler to -// update any deployment stored in the database. +// UpdateDeployment represents the API handler to update a deployment. func UpdateDeployment(c *gin.Context) { c.JSON(http.StatusNotImplemented, "The server does not support the functionality required to fulfill the request.") } diff --git a/api/admin/hook.go b/api/admin/hook.go index 4336a0744..a7d0ef303 100644 --- a/api/admin/hook.go +++ b/api/admin/hook.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore similar code package admin import ( @@ -8,15 +7,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/admin/hook admin AdminUpdateHook // -// Update a hook in the database +// Update a hook // // --- // produces: @@ -24,7 +24,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing hook to update +// description: The hook object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Webhook" @@ -32,26 +32,30 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the hook in the database +// description: Successfully updated the hook // schema: // "$ref": "#/definitions/Webhook" -// '404': -// description: Unable to update the hook in the database +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '400': +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" -// '501': -// description: Unable to update the hook in the database +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateHook represents the API handler to -// update any hook stored in the database. +// UpdateHook represents the API handler to update a hook. func UpdateHook(c *gin.Context) { - logrus.Info("Admin: updating hook in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating hook") + // capture body from API request input := new(library.Hook) @@ -59,11 +63,15 @@ func UpdateHook(c *gin.Context) { if err != nil { retErr := fmt.Errorf("unable to decode JSON for hook %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "hook_id": input.GetID(), + }).Debug("platform admin: attempting to update hook") + // send API call to update the hook h, err := database.FromContext(c).UpdateHook(ctx, input) if err != nil { @@ -74,5 +82,9 @@ func UpdateHook(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "hook_id": h.GetID(), + }).Info("platform admin: hook updated") + c.JSON(http.StatusOK, h) } diff --git a/api/admin/repo.go b/api/admin/repo.go index 11814a8a0..16a26fef0 100644 --- a/api/admin/repo.go +++ b/api/admin/repo.go @@ -1,24 +1,22 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore similar code package admin import ( "fmt" "net/http" - "github.com/go-vela/server/database" - "github.com/go-vela/server/util" - - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" ) // swagger:operation PUT /api/v1/admin/repo admin AdminUpdateRepo // -// Update a repo in the database +// Update a repository // // --- // produces: @@ -26,7 +24,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing repo to update +// description: The repository object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Repo" @@ -34,38 +32,48 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the repo in the database +// description: Successfully updated the repo // schema: // "$ref": "#/definitions/Repo" -// '404': -// description: Unable to update the repo in the database +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" -// '501': -// description: Unable to update the repo in the database +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateRepo represents the API handler to -// update any repo stored in the database. +// UpdateRepo represents the API handler to update a repo. func UpdateRepo(c *gin.Context) { - logrus.Info("Admin: updating repo in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating repo") + // capture body from API request - input := new(library.Repo) + input := new(types.Repo) err := c.Bind(input) if err != nil { retErr := fmt.Errorf("unable to decode JSON for repo %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "org": util.EscapeValue(input.GetOrg()), + "repo": util.EscapeValue(input.GetName()), + "repo_id": input.GetID(), + }).Debug("platform admin: attempting to update repo") + // send API call to update the repo r, err := database.FromContext(c).UpdateRepo(ctx, input) if err != nil { @@ -76,5 +84,11 @@ func UpdateRepo(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + }).Info("platform admin: repo updated") + c.JSON(http.StatusOK, r) } diff --git a/api/admin/rotate_keys.go b/api/admin/rotate_keys.go new file mode 100644 index 000000000..4fba00616 --- /dev/null +++ b/api/admin/rotate_keys.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 + +package admin + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" +) + +// swagger:operation POST /api/v1/admin/rotate_oidc_keys admin AdminRotateOIDCKeys +// +// Rotate RSA Keys +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully rotated OIDC provider keys +// schema: +// type: string +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// RotateOIDCKeys represents the API handler to +// rotate RSA keys in the OIDC provider service. +func RotateOIDCKeys(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Info("platform admin: rotating keys for OIDC provider") + + // capture middleware values + ctx := c.Request.Context() + + err := database.FromContext(c).RotateKeys(ctx) + if err != nil { + retErr := fmt.Errorf("unable to rotate keys: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, "keys rotated successfully") +} diff --git a/api/admin/secret.go b/api/admin/secret.go index 9c00c830c..f287e4227 100644 --- a/api/admin/secret.go +++ b/api/admin/secret.go @@ -1,24 +1,22 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore similar code package admin import ( "fmt" "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/admin/secret admin AdminUpdateSecret // -// Update a secret in the database +// Update a secret // // --- // produces: @@ -26,7 +24,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing secret to update +// description: The secret object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Secret" @@ -34,26 +32,30 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the secret in the database +// description: Successfully updated the secret // schema: // "$ref": "#/definitions/Secret" -// '404': -// description: Unable to update the secret in the database +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" -// '501': -// description: Unable to update the secret in the database +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateSecret represents the API handler to -// update any secret stored in the database. +// UpdateSecret represents the API handler to update a secret. func UpdateSecret(c *gin.Context) { - logrus.Info("Admin: updating secret in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating secret") + // capture body from API request input := new(library.Secret) @@ -61,11 +63,20 @@ func UpdateSecret(c *gin.Context) { if err != nil { retErr := fmt.Errorf("unable to decode JSON for secret %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "secret_id": input.GetID(), + "secret_org": util.EscapeValue(input.GetOrg()), + "secret_repo": util.EscapeValue(input.GetRepo()), + "secret_type": util.EscapeValue(input.GetType()), + "secret_name": util.EscapeValue(input.GetName()), + "secret_team": util.EscapeValue(input.GetTeam()), + }).Debug("platform admin: attempting to update secret") + // send API call to update the secret s, err := database.FromContext(c).UpdateSecret(ctx, input) if err != nil { @@ -76,5 +87,14 @@ func UpdateSecret(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "secret_id": s.GetID(), + "secret_org": s.GetOrg(), + "secret_repo": s.GetRepo(), + "secret_type": s.GetType(), + "secret_name": s.GetName(), + "secret_team": s.GetTeam(), + }).Info("platform admin: secret updated") + c.JSON(http.StatusOK, s) } diff --git a/api/admin/service.go b/api/admin/service.go index 190d4f902..13d33d199 100644 --- a/api/admin/service.go +++ b/api/admin/service.go @@ -7,18 +7,17 @@ import ( "fmt" "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/admin/service admin AdminUpdateService // -// Update a hook in the database +// Update a service // // --- // produces: @@ -26,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing service to update +// description: The service object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Service" @@ -34,27 +33,31 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the service in the database +// description: Successfully updated the service // type: json // schema: // "$ref": "#/definitions/Service" -// '404': -// description: Unable to update the service in the database +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '400': +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the service in the database +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateService represents the API handler to -// update any service stored in the database. +// UpdateService represents the API handler to update a service. func UpdateService(c *gin.Context) { - logrus.Info("Admin: updating service in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating service") + // capture body from API request input := new(library.Service) @@ -62,11 +65,16 @@ func UpdateService(c *gin.Context) { if err != nil { retErr := fmt.Errorf("unable to decode JSON for service %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "service_id": input.GetID(), + "service": util.EscapeValue(input.GetName()), + }).Debug("platform admin: attempting to update service") + // send API call to update the service s, err := database.FromContext(c).UpdateService(ctx, input) if err != nil { @@ -77,5 +85,10 @@ func UpdateService(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "service_id": s.GetID(), + "service": s.GetName(), + }).Info("platform admin: updated service") + c.JSON(http.StatusOK, s) } diff --git a/api/admin/settings.go b/api/admin/settings.go new file mode 100644 index 000000000..dfb8cb338 --- /dev/null +++ b/api/admin/settings.go @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: Apache-2.0 + +package admin + +import ( + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/compiler/native" + "github.com/go-vela/server/database" + "github.com/go-vela/server/internal/image" + "github.com/go-vela/server/queue" + cliMiddleware "github.com/go-vela/server/router/middleware/cli" + sMiddleware "github.com/go-vela/server/router/middleware/settings" + uMiddleware "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// swagger:operation GET /api/v1/admin/settings admin GetSettings +// +// Get platform settings +// +// --- +// produces: +// - application/json +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved settings +// type: json +// schema: +// "$ref": "#/definitions/Platform" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" + +// GetSettings represents the API handler to get platform settings. +func GetSettings(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Debug("platform admin: reading platform settings") + + // capture middleware values + s := sMiddleware.FromContext(c) + + // check captured value because we aren't retrieving settings from the database + // instead we are retrieving the auto-refreshed middleware value + if s == nil { + retErr := fmt.Errorf("platform settings not found") + + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + c.JSON(http.StatusOK, s) +} + +// swagger:operation PUT /api/v1/admin/settings admin UpdateSettings +// +// Update platform settings +// +// --- +// produces: +// - application/json +// parameters: +// - in: body +// name: body +// description: The settings object with the fields to be updated +// required: true +// schema: +// "$ref": "#/definitions/Platform" +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully updated platform settings +// type: json +// schema: +// "$ref": "#/definitions/Platform" +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// UpdateSettings represents the API handler to update the +// platform settings singleton. +func UpdateSettings(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + s := sMiddleware.FromContext(c) + u := uMiddleware.FromContext(c) + ctx := c.Request.Context() + + l.Debug("platform admin: updating platform settings") + + // check captured value because we aren't retrieving settings from the database + // instead we are retrieving the auto-refreshed middleware value + if s == nil { + retErr := fmt.Errorf("platform settings not found") + + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + // duplicate settings to not alter the shared pointer + _s := new(settings.Platform) + _s.FromSettings(s) + + // ensure we update the singleton record + _s.SetID(1) + + // capture body from API request + input := new(settings.Platform) + + err := c.Bind(input) + if err != nil { + retErr := fmt.Errorf("unable to decode JSON for platform settings: %w", err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + if input.Compiler != nil { + if input.CloneImage != nil { + // validate clone image + cloneImage := *input.CloneImage + + _, err = image.ParseWithError(cloneImage) + if err != nil { + retErr := fmt.Errorf("invalid clone image %s for platform settings: %w", cloneImage, err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + _s.SetCloneImage(cloneImage) + + l.Infof("platform admin: updating clone image to %s", cloneImage) + } + + if input.TemplateDepth != nil { + _s.SetTemplateDepth(*input.TemplateDepth) + + l.Infof("platform admin: updating template depth to %d", *input.TemplateDepth) + } + + if input.StarlarkExecLimit != nil { + _s.SetStarlarkExecLimit(*input.StarlarkExecLimit) + + l.Infof("platform admin: updating starlark exec limit to %d", *input.StarlarkExecLimit) + } + } + + if input.Queue != nil { + if input.Queue.Routes != nil { + _s.SetRoutes(input.GetRoutes()) + } + + l.Infof("platform admin: updating queue routes to: %s", input.GetRoutes()) + } + + if input.RepoAllowlist != nil { + _s.SetRepoAllowlist(input.GetRepoAllowlist()) + + l.Infof("platform admin: updating repo allowlist to: %s", input.GetRepoAllowlist()) + } + + if input.ScheduleAllowlist != nil { + _s.SetScheduleAllowlist(input.GetScheduleAllowlist()) + + l.Infof("platform admin: updating schedule allowlist to: %s", input.GetScheduleAllowlist()) + } + + _s.SetUpdatedBy(u.GetName()) + + // send API call to update the settings + _s, err = database.FromContext(c).UpdateSettings(ctx, _s) + if err != nil { + retErr := fmt.Errorf("unable to update platform settings: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, _s) +} + +// swagger:operation DELETE /api/v1/admin/settings admin RestoreSettings +// +// Restore platform settings to the environment defaults +// +// --- +// produces: +// - application/json +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully restored default platform settings +// type: json +// schema: +// "$ref": "#/definitions/Platform" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// RestoreSettings represents the API handler to +// restore platform settings to the environment defaults. +func RestoreSettings(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Debug("platform admin: restoring platform settings") + + // capture middleware values + ctx := c.Request.Context() + cliCtx := cliMiddleware.FromContext(c) + s := sMiddleware.FromContext(c) + u := uMiddleware.FromContext(c) + + // check captured value because we aren't retrieving settings from the database + // instead we are retrieving the auto-refreshed middleware value + if s == nil { + retErr := fmt.Errorf("platform settings not found") + + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + compiler, err := native.FromCLIContext(cliCtx) + if err != nil { + retErr := fmt.Errorf("unable to restore platform settings: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + queue, err := queue.FromCLIContext(cliCtx) + if err != nil { + retErr := fmt.Errorf("unable to restore platform settings: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // initialize a new settings record + _s := settings.FromCLIContext(cliCtx) + + _s.SetUpdatedAt(time.Now().UTC().Unix()) + _s.SetUpdatedBy(u.GetName()) + + // read in defaults supplied from the cli runtime + compilerSettings := compiler.GetSettings() + _s.SetCompiler(compilerSettings) + + queueSettings := queue.GetSettings() + _s.SetQueue(queueSettings) + + // send API call to update the settings + s, err = database.FromContext(c).UpdateSettings(ctx, _s) + if err != nil { + retErr := fmt.Errorf("unable to update (restore) platform settings: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, s) +} diff --git a/api/admin/step.go b/api/admin/step.go index 9fd909414..b29fc57e0 100644 --- a/api/admin/step.go +++ b/api/admin/step.go @@ -1,23 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 +//nolint:dupl // ignore similar code with user.go package admin import ( "fmt" "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/admin/step admin AdminUpdateStep // -// Update a step in the database +// Update a step // // --- // produces: @@ -25,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing step to update +// description: The step object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Step" @@ -33,36 +33,47 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the step in the database +// description: Successfully updated the step // schema: // "$ref": "#/definitions/Step" -// '404': -// description: Unable to update the step in the database +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '400': +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the step in the database +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateStep represents the API handler to -// update any step stored in the database. +// UpdateStep represents the API handler to update a step. func UpdateStep(c *gin.Context) { - logrus.Info("Admin: updating step in database") + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + ctx := c.Request.Context() + + l.Debug("platform admin: updating step") // capture body from API request input := new(library.Step) - ctx := c.Request.Context() err := c.Bind(input) if err != nil { retErr := fmt.Errorf("unable to decode JSON for step %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "step_id": input.GetID(), + "step": util.EscapeValue(input.GetName()), + }).Debug("platform admin: attempting to update step") + // send API call to update the step s, err := database.FromContext(c).UpdateStep(ctx, input) if err != nil { @@ -73,5 +84,10 @@ func UpdateStep(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "step_id": s.GetID(), + "step": s.GetName(), + }).Info("platform admin: updated step") + c.JSON(http.StatusOK, s) } diff --git a/api/admin/user.go b/api/admin/user.go index 416cd2e09..3f6d1b1ca 100644 --- a/api/admin/user.go +++ b/api/admin/user.go @@ -7,18 +7,17 @@ import ( "fmt" "net/http" - "github.com/go-vela/server/database" - "github.com/go-vela/server/util" - - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" ) // swagger:operation PUT /api/v1/admin/user admin AdminUpdateUser // -// Update a user in the database +// Update a user // // --- // produces: @@ -26,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing user to update +// description: The user object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/User" @@ -34,40 +33,49 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully updated the user in the database +// description: Successfully updated the user // schema: // "$ref": "#/definitions/User" -// '404': -// description: Unable to update the user in the database +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '400': +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the user in the database +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateUser represents the API handler to -// update any user stored in the database. +// UpdateUser represents the API handler to update a user. func UpdateUser(c *gin.Context) { - logrus.Info("Admin: updating user in database") - // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("platform admin: updating user") + // capture body from API request - input := new(library.User) + input := new(types.User) err := c.Bind(input) if err != nil { retErr := fmt.Errorf("unable to decode JSON for user %d: %w", input.GetID(), err) - util.HandleError(c, http.StatusNotFound, retErr) + util.HandleError(c, http.StatusBadRequest, retErr) return } + l.WithFields(logrus.Fields{ + "target_user_id": input.GetID(), + "target_user": util.EscapeValue(input.GetName()), + }).Debug("platform admin: attempting to update user") + // send API call to update the user - u, err := database.FromContext(c).UpdateUser(ctx, input) + tu, err := database.FromContext(c).UpdateUser(ctx, input) if err != nil { retErr := fmt.Errorf("unable to update user %d: %w", input.GetID(), err) @@ -76,5 +84,10 @@ func UpdateUser(c *gin.Context) { return } - c.JSON(http.StatusOK, u) + l.WithFields(logrus.Fields{ + "target_user_id": tu.GetID(), + "target_user": tu.GetName(), + }).Info("platform admin: updated user") + + c.JSON(http.StatusOK, tu) } diff --git a/api/admin/worker.go b/api/admin/worker.go index 1966d7d25..59ed71a5e 100644 --- a/api/admin/worker.go +++ b/api/admin/worker.go @@ -6,17 +6,16 @@ import ( "fmt" "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/internal/token" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) -// swagger:operation POST /api/v1/admin/workers/{worker}/register-token admin RegisterToken +// swagger:operation POST /api/v1/admin/workers/{worker}/register admin RegisterToken // // Get a worker registration token // @@ -40,15 +39,17 @@ import ( // description: Unauthorized // schema: // "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" // RegisterToken represents the API handler to // generate a registration token for onboarding a worker. func RegisterToken(c *gin.Context) { - // retrieve user from context - u := user.Retrieve(c) - - logrus.Infof("Platform admin %s: generating registration token", u.GetName()) + l := c.MustGet("logger").(*logrus.Entry) + // capture middleware values host := util.PathParameter(c, "worker") tm := c.MustGet("token-manager").(*token.Manager) @@ -58,6 +59,8 @@ func RegisterToken(c *gin.Context) { TokenDuration: tm.WorkerRegisterTokenDuration, } + l.Debug("platform admin: generating worker registration token") + rt, err := tm.MintToken(rmto) if err != nil { retErr := fmt.Errorf("unable to generate registration token: %w", err) @@ -67,5 +70,7 @@ func RegisterToken(c *gin.Context) { return } + l.Infof("platform admin: generated worker registration token for %s", host) + c.JSON(http.StatusOK, library.Token{Token: &rt}) } diff --git a/api/auth/get_token.go b/api/auth/get_token.go index 16b4334ae..07df70b0c 100644 --- a/api/auth/get_token.go +++ b/api/auth/get_token.go @@ -7,6 +7,9 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/scm" @@ -24,15 +27,15 @@ import ( // parameters: // - in: query // name: code -// description: the code received after identity confirmation +// description: The code received after identity confirmation // type: string // - in: query // name: state -// description: a random string +// description: A random string // type: string // - in: query // name: redirect_uri -// description: the url where the user will be sent after authorization +// description: The URL where the user will be sent after authorization // type: string // responses: // '200': @@ -45,7 +48,7 @@ import ( // '307': // description: Redirected for authentication // '401': -// description: Unable to authenticate +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '503': @@ -59,8 +62,9 @@ import ( func GetAuthToken(c *gin.Context) { var err error - tm := c.MustGet("token-manager").(*token.Manager) // capture middleware values + tm := c.MustGet("token-manager").(*token.Manager) + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() // capture the OAuth state if present @@ -101,7 +105,7 @@ func GetAuthToken(c *gin.Context) { // create a new user account if len(u.GetName()) == 0 || err != nil { // create the user account - u := new(library.User) + u := new(types.User) u.SetName(newUser.GetName()) u.SetToken(newUser.GetToken()) u.SetActive(true) @@ -121,7 +125,7 @@ func GetAuthToken(c *gin.Context) { u.SetRefreshToken(rt) // send API call to create the user in the database - _, err = database.FromContext(c).CreateUser(ctx, u) + ur, err := database.FromContext(c).CreateUser(ctx, u) if err != nil { retErr := fmt.Errorf("unable to create user %s: %w", u.GetName(), err) @@ -130,6 +134,11 @@ func GetAuthToken(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "user": ur.GetName(), + "user_id": ur.GetID(), + }).Info("new user created") + // return the jwt access token c.JSON(http.StatusOK, library.Token{Token: &at}) @@ -154,7 +163,7 @@ func GetAuthToken(c *gin.Context) { u.SetRefreshToken(rt) // send API call to update the user in the database - _, err = database.FromContext(c).UpdateUser(ctx, u) + ur, err := database.FromContext(c).UpdateUser(ctx, u) if err != nil { retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err) @@ -163,6 +172,11 @@ func GetAuthToken(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "user": ur.GetName(), + "user_id": ur.GetID(), + }).Info("user updated - new token") + // return the user with their jwt access token c.JSON(http.StatusOK, library.Token{Token: &at}) } diff --git a/api/auth/login.go b/api/auth/login.go index 041d844f3..f2bf5adc5 100644 --- a/api/auth/login.go +++ b/api/auth/login.go @@ -8,27 +8,28 @@ import ( "net/url" "github.com/gin-gonic/gin" - "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/internal" + "github.com/go-vela/server/util" ) // swagger:operation GET /login authenticate GetLogin // -// Log into the Vela api +// Log into the Vela API // // --- // parameters: // - in: query // name: type -// description: the login type ("cli" or "web") +// description: The login type ("cli" or "web") // type: string // enum: // - web // - cli // - in: query // name: port -// description: the port number when type=cli +// description: The port number when type=cli // type: integer // responses: // '307': @@ -38,7 +39,8 @@ import ( // process a user logging in to Vela. func Login(c *gin.Context) { // load the metadata - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) // capture query params t := util.FormParameter(c, "type") @@ -50,18 +52,20 @@ func Login(c *gin.Context) { // default path (headless mode) path := "/authenticate" + l.Info("logging in user") + // handle web and cli logins switch t { case "web": r = fmt.Sprintf("%s/authenticate/%s", m.Vela.Address, t) - logrus.Debugf("web login request, setting redirect to: %s", r) + l.Infof("web login request, setting redirect to: %s", r) case "cli": // port must be supplied if len(p) > 0 { r = fmt.Sprintf("%s/authenticate/%s/%s", m.Vela.Address, t, p) - logrus.Debugf("cli login request, setting redirect to: %s", r) + l.Infof("cli login request, setting redirect to: %s", r) } } diff --git a/api/auth/logout.go b/api/auth/logout.go index 454d2b9ed..9acb575cd 100644 --- a/api/auth/logout.go +++ b/api/auth/logout.go @@ -7,20 +7,19 @@ import ( "net/http" "net/url" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - - "github.com/go-vela/types" "github.com/go-vela/types/constants" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // swagger:operation GET /logout authenticate GetLogout // -// Log out of the Vela api +// Log out of the Vela API // // --- // produces: @@ -30,6 +29,10 @@ import ( // description: Successfully logged out // schema: // type: string +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '503': // description: Logout did not succeed // schema: @@ -41,26 +44,20 @@ import ( // refresh token cookie. func Logout(c *gin.Context) { // grab the metadata to help deal with the cookie - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) // capture middleware values u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }) - - logger.Infof("logging out user %s", u.GetName()) + l.Debug("logging out user") // parse the address for the backend server // so we can set it for the cookie domain addr, err := url.Parse(m.Vela.Address) if err != nil { // silently fail - logger.Error("unable to parse Vela address during logout") + l.Error("unable to parse Vela address during logout") } // set the same samesite attribute we used to create the cookie @@ -83,6 +80,8 @@ func Logout(c *gin.Context) { return } + l.Info("updated user - logged out") + // return 200 for successful logout c.JSON(http.StatusOK, "ok") } diff --git a/api/auth/post_token.go b/api/auth/post_token.go index 2f68af691..d5fc345c9 100644 --- a/api/auth/post_token.go +++ b/api/auth/post_token.go @@ -7,6 +7,8 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/scm" @@ -35,7 +37,7 @@ import ( // schema: // "$ref": "#/definitions/Token" // '401': -// description: Unable to authenticate +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '503': @@ -43,11 +45,11 @@ import ( // schema: // "$ref": "#/definitions/Error" -// PostAuthToken represents the API handler to -// process a user logging in using PAT to Vela from -// the API. +// PostAuthToken represents the API handler to process +// a user logging in using PAT to Vela from the API. func PostAuthToken(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() // attempt to get user from source @@ -60,16 +62,23 @@ func PostAuthToken(c *gin.Context) { return } + l.Infof("SCM user %s authenticated using PAT", u.GetName()) + // check if the user exists u, err = database.FromContext(c).GetUserForName(ctx, u.GetName()) if err != nil { - retErr := fmt.Errorf("user %s not found", u.GetName()) + retErr := fmt.Errorf("unable to authenticate: user %s not found", u.GetName()) util.HandleError(c, http.StatusUnauthorized, retErr) return } + l.WithFields(logrus.Fields{ + "user": u.GetName(), + "user_id": u.GetID(), + }).Info("user successfully authenticated via SCM PAT") + // We don't need refresh token for this scenario // We only need access token and are configured based on the config defined tm := c.MustGet("token-manager").(*token.Manager) @@ -80,14 +89,14 @@ func PostAuthToken(c *gin.Context) { TokenType: constants.UserAccessTokenType, TokenDuration: tm.UserAccessTokenDuration, } - at, err := tm.MintToken(amto) + at, err := tm.MintToken(amto) if err != nil { retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err) util.HandleError(c, http.StatusServiceUnavailable, retErr) } - // return the user with their jwt access token + // return jwt access token c.JSON(http.StatusOK, library.Token{Token: &at}) } diff --git a/api/auth/redirect.go b/api/auth/redirect.go index 473c7a612..83022abc5 100644 --- a/api/auth/redirect.go +++ b/api/auth/redirect.go @@ -7,9 +7,10 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/internal" + "github.com/go-vela/server/util" ) // swagger:operation GET /authenticate/web authenticate GetAuthenticateTypeWeb @@ -24,11 +25,11 @@ import ( // parameters: // - in: query // name: code -// description: the code received after identity confirmation +// description: The code received after identity confirmation // type: string // - in: query // name: state -// description: a random string +// description: A random string // type: string // responses: // '307': @@ -47,15 +48,15 @@ import ( // - in: path // name: port // required: true -// description: the port number +// description: The port number // type: integer // - in: query // name: code -// description: the code received after identity confirmation +// description: The code received after identity confirmation // type: string // - in: query // name: state -// description: a random string +// description: A random string // type: string // responses: // '307': @@ -68,9 +69,10 @@ import ( // This will only handle non-headless flows (ie. web or cli). func GetAuthRedirect(c *gin.Context) { // load the metadata - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) - logrus.Info("redirecting for final auth flow destination") + l.Debug("redirecting for final auth flow destination") // capture the path elements t := util.PathParameter(c, "type") diff --git a/api/auth/refresh.go b/api/auth/refresh.go index 65a7626b7..69d61bb1e 100644 --- a/api/auth/refresh.go +++ b/api/auth/refresh.go @@ -7,6 +7,8 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/auth" "github.com/go-vela/server/util" @@ -35,6 +37,10 @@ import ( // RefreshAccessToken will return a new access token if the provided // refresh token via cookie is valid. func RefreshAccessToken(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Info("refreshing access token") + // capture the refresh token // TODO: move this into token package and do it internally // since we are already passsing context diff --git a/api/auth/validate.go b/api/auth/validate.go index 6485fe9f9..40bab2c38 100644 --- a/api/auth/validate.go +++ b/api/auth/validate.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/util" ) @@ -34,8 +36,11 @@ import ( // ValidateServerToken will validate if a token was issued by the server // if it is provided in the auth header. func ValidateServerToken(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) + l.Info("validating server token") + if !strings.EqualFold(cl.Subject, "vela-server") { retErr := fmt.Errorf("token is not a valid server token") diff --git a/api/auth/validate_oauth.go b/api/auth/validate_oauth.go index 5a31c0069..25325a353 100644 --- a/api/auth/validate_oauth.go +++ b/api/auth/validate_oauth.go @@ -7,13 +7,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/scm" "github.com/go-vela/server/util" ) // swagger:operation GET /validate-oauth authenticate ValidateOAuthToken // -// Validate that a user oauth token was created by Vela +// Validate that a user OAuth token was created by Vela // // --- // produces: @@ -29,18 +31,21 @@ import ( // '200': // description: Successfully validated // schema: -// "$ref": "#/definitions/Token" +// type: string // '401': -// description: Unable to validate +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // ValidateOAuthToken represents the API handler to -// validate that a user oauth token was created by Vela. +// validate that a user OAuth token was created by Vela. func ValidateOAuthToken(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Info("validating oauth token") + token := c.Request.Header.Get("Token") if len(token) == 0 { retErr := fmt.Errorf("unable to validate oauth token: no token provided in header") diff --git a/api/badge.go b/api/badge.go index df4fcd97a..9aed49600 100644 --- a/api/badge.go +++ b/api/badge.go @@ -6,17 +6,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // swagger:operation GET /badge/{org}/{repo}/status.svg base GetBadge // -// Get a badge for the repo +// Get a build status badge for a repository // // --- // produces: @@ -24,37 +24,39 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org the repo belongs to +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo to get the badge for +// description: Name of the repository // required: true // type: string // responses: // '200': -// description: Successfully retrieved a status Badge +// description: Successfully retrieved the build status badge // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // GetBadge represents the API handler to // return a build status badge. func GetBadge(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) ctx := c.Request.Context() branch := util.QueryParameter(c, "branch", r.GetBranch()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - }).Infof("creating latest build badge for repo %s on branch %s", r.GetFullName(), branch) + l.Debugf("creating latest build badge for repo %s on branch %s", r.GetFullName(), branch) // send API call to capture the last build for the repo and branch b, err := database.FromContext(c).LastBuildForRepo(ctx, r, branch) diff --git a/api/build/approve.go b/api/build/approve.go index 037160282..86c491c04 100644 --- a/api/build/approve.go +++ b/api/build/approve.go @@ -9,20 +9,21 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/queue" + "github.com/go-vela/server/queue/models" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/approve builds ApproveBuild // -// Sign off on a build to run from an outside contributor +// Approve a build to run // // --- // produces: @@ -30,17 +31,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to retrieve +// description: Build number // required: true // type: integer // security: @@ -50,41 +51,33 @@ import ( // description: Request processed but build was skipped // schema: // type: string -// '201': -// description: Successfully created the build -// type: json -// schema: -// "$ref": "#/definitions/Build" // '400': -// description: Unable to create the build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to create the build +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CreateBuild represents the API handler to approve a build to run in the configured backend. +// ApproveBuild represents the API handler to approve a build to run. func ApproveBuild(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) + l.Debugf("approving build %d", b.GetID()) // verify build is in correct status if !strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) { @@ -102,37 +95,25 @@ func ApproveBuild(c *gin.Context) { return } - logger.Debugf("user %s approved build %s/%d for execution", u.GetName(), r.GetFullName(), b.GetNumber()) - - // send API call to capture the repo owner - owner, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - // set fields b.SetStatus(constants.StatusPending) b.SetApprovedAt(time.Now().Unix()) b.SetApprovedBy(u.GetName()) // update the build in the db - _, err = database.FromContext(c).UpdateBuild(ctx, b) + _, err := database.FromContext(c).UpdateBuild(ctx, b) if err != nil { - logrus.Errorf("Failed to update build %d during publish to queue for %s: %v", b.GetNumber(), r.GetFullName(), err) + l.Errorf("failed to update build during publish to queue: %v", err) } + l.Info("build updated - user approved build execution") + // publish the build to the queue - go PublishToQueue( + go Enqueue( ctx, queue.FromGinContext(c), database.FromContext(c), - b, - r, - owner, + models.ToItem(b), b.GetHost(), ) diff --git a/api/build/auto_cancel.go b/api/build/auto_cancel.go index 0f200498c..d1ba091fe 100644 --- a/api/build/auto_cancel.go +++ b/api/build/auto_cancel.go @@ -12,16 +12,29 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" ) // AutoCancel is a helper function that checks to see if any pending or running // builds for the repo can be replaced by the current build. -func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library.Repo, cancelOpts *pipeline.CancelOptions) (bool, error) { +func AutoCancel(c *gin.Context, b *types.Build, rB *types.Build, cancelOpts *pipeline.CancelOptions) (bool, error) { + l := c.MustGet("logger").(*logrus.Entry) + + // in this path, the middleware doesn't inject build, + // so we need to set it manually + l = l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + }) + + l.Debug("checking if builds should be auto canceled") + // if build is the current build, continue if rB.GetID() == b.GetID() { return false, nil @@ -43,6 +56,11 @@ func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library. return false, err } + l.WithFields(logrus.Fields{ + "build": rB.GetNumber(), + "build_id": rB.GetID(), + }).Info("build updated - build canceled") + // remove executable from table _, err = database.FromContext(c).PopBuildExecutable(c, rB.GetID()) if err != nil { @@ -50,7 +68,7 @@ func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library. } case strings.EqualFold(status, constants.StatusRunning) && cancelOpts.Running: // call cancelRunning routine for builds already running on worker - err := cancelRunning(c, rB, r) + err := cancelRunning(c, rB) if err != nil { return false, err } @@ -66,6 +84,11 @@ func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library. // if this call fails, we still canceled the build, so return true return true, err } + + l.WithFields(logrus.Fields{ + "build": rB.GetNumber(), + "build_id": rB.GetID(), + }).Info("build updated - build canceled") } return true, nil @@ -73,8 +96,10 @@ func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library. // cancelRunning is a helper function that determines the executor currently running a build and sends an API call // to that executor's worker to cancel the build. -func cancelRunning(c *gin.Context, b *library.Build, r *library.Repo) error { - e := new([]library.Executor) +func cancelRunning(c *gin.Context, b *types.Build) error { + l := c.MustGet("logger").(*logrus.Entry) + + e := new([]types.Executor) // retrieve the worker w, err := database.FromContext(c).GetWorkerForHostname(c, b.GetHost()) if err != nil { @@ -131,7 +156,7 @@ func cancelRunning(c *gin.Context, b *library.Build, r *library.Repo) error { for _, executor := range *e { // check each executor on the worker running the build to see if it's running the build we want to cancel - if strings.EqualFold(executor.Repo.GetFullName(), r.GetFullName()) && *executor.GetBuild().Number == b.GetNumber() { + if executor.Build.GetID() == b.GetID() { // prepare the request to the worker client := http.DefaultClient client.Timeout = 30 * time.Second @@ -169,6 +194,8 @@ func cancelRunning(c *gin.Context, b *library.Build, r *library.Repo) error { } defer resp.Body.Close() + l.Debugf("sent cancel request to worker %s (executor %d) for build %d", w.GetHostname(), executor.GetID(), b.GetID()) + // Read Response Body respBody, err := io.ReadAll(resp.Body) if err != nil { @@ -187,7 +214,7 @@ func cancelRunning(c *gin.Context, b *library.Build, r *library.Repo) error { // isCancelable is a helper function that determines whether a `target` build should be auto-canceled // given a current build that intends to supersede it. -func isCancelable(target *library.Build, current *library.Build) bool { +func isCancelable(target *types.Build, current *types.Build) bool { switch target.GetEvent() { case constants.EventPush: // target is cancelable if current build is also a push event and the branches are the same @@ -204,7 +231,7 @@ func isCancelable(target *library.Build, current *library.Build) bool { // ShouldAutoCancel is a helper function that determines whether or not a build should be eligible to // auto cancel currently running / pending builds. -func ShouldAutoCancel(opts *pipeline.CancelOptions, b *library.Build, defaultBranch string) bool { +func ShouldAutoCancel(opts *pipeline.CancelOptions, b *types.Build, defaultBranch string) bool { // if the build is pending approval, it should always be eligible to auto cancel if strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) { return true diff --git a/api/build/auto_cancel_test.go b/api/build/auto_cancel_test.go index 23879b923..15420b04d 100644 --- a/api/build/auto_cancel_test.go +++ b/api/build/auto_cancel_test.go @@ -5,8 +5,8 @@ package build import ( "testing" + "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" ) @@ -25,17 +25,17 @@ func Test_isCancelable(t *testing.T) { tests := []struct { name string - target *library.Build - current *library.Build + target *types.Build + current *types.Build want bool }{ { name: "Wrong Event", - target: &library.Build{ + target: &types.Build{ Event: &tagEvent, Branch: &branchDev, }, - current: &library.Build{ + current: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, @@ -43,11 +43,11 @@ func Test_isCancelable(t *testing.T) { }, { name: "Cancelable Push", - target: &library.Build{ + target: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, - current: &library.Build{ + current: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, @@ -55,11 +55,11 @@ func Test_isCancelable(t *testing.T) { }, { name: "Push Branch Mismatch", - target: &library.Build{ + target: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, - current: &library.Build{ + current: &types.Build{ Event: &pushEvent, Branch: &branchPatch, }, @@ -67,11 +67,11 @@ func Test_isCancelable(t *testing.T) { }, { name: "Event Mismatch", - target: &library.Build{ + target: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, - current: &library.Build{ + current: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchPatch, @@ -80,13 +80,13 @@ func Test_isCancelable(t *testing.T) { }, { name: "Cancelable Pull", - target: &library.Build{ + target: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchPatch, EventAction: &actionOpened, }, - current: &library.Build{ + current: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchPatch, @@ -96,13 +96,13 @@ func Test_isCancelable(t *testing.T) { }, { name: "Pull Head Ref Mismatch", - target: &library.Build{ + target: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchPatch, EventAction: &actionSync, }, - current: &library.Build{ + current: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchDev, @@ -112,13 +112,13 @@ func Test_isCancelable(t *testing.T) { }, { name: "Pull Ineligible Action", - target: &library.Build{ + target: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchPatch, EventAction: &actionEdited, }, - current: &library.Build{ + current: &types.Build{ Event: &pullEvent, Branch: &branchDev, HeadRef: &branchDev, @@ -154,7 +154,7 @@ func Test_ShouldAutoCancel(t *testing.T) { tests := []struct { name string opts *pipeline.CancelOptions - build *library.Build + build *types.Build branch string want bool }{ @@ -165,7 +165,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: true, }, - build: &library.Build{ + build: &types.Build{ Event: &tagEvent, Branch: &branchPatch, }, @@ -179,7 +179,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: false, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pushEvent, Branch: &branchPatch, }, @@ -193,7 +193,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pushEvent, Branch: &branchPatch, }, @@ -207,7 +207,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: true, }, - build: &library.Build{ + build: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, @@ -221,7 +221,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pushEvent, Branch: &branchDev, }, @@ -235,7 +235,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pullEvent, Branch: &branchDev, EventAction: &actionSync, @@ -250,7 +250,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: true, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pullEvent, Branch: &branchDev, EventAction: &actionOpened, @@ -265,7 +265,7 @@ func Test_ShouldAutoCancel(t *testing.T) { Pending: false, DefaultBranch: false, }, - build: &library.Build{ + build: &types.Build{ Event: &pullEvent, Branch: &branchDev, EventAction: &actionOpened, diff --git a/api/build/cancel.go b/api/build/cancel.go index 0126f819c..79d186799 100644 --- a/api/build/cancel.go +++ b/api/build/cancel.go @@ -8,43 +8,42 @@ import ( "fmt" "io" "net/http" - "strings" "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/executors" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/cancel builds CancelBuild // -// Cancel a running build +// Cancel a build // // --- // produces: // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to cancel +// description: Build number // required: true // type: integer // security: @@ -53,43 +52,39 @@ import ( // '200': // description: Successfully canceled the build // schema: -// type: string +// "$ref": "#/definitions/Build" // '400': -// description: Unable to cancel build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to cancel build +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to cancel build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CancelBuild represents the API handler to cancel a running build. +// CancelBuild represents the API handler to cancel a build. // //nolint:funlen // ignore statement count func CancelBuild(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) e := executors.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) user := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": user.GetName(), - }).Infof("canceling build %s", entry) + l.Debugf("canceling build %s", entry) switch b.GetStatus() { case constants.StatusRunning: @@ -104,7 +99,7 @@ func CancelBuild(c *gin.Context) { for _, executor := range e { // check each executor on the worker running the build to see if it's running the build we want to cancel - if strings.EqualFold(executor.Repo.GetFullName(), r.GetFullName()) && *executor.GetBuild().Number == b.GetNumber() { + if executor.Build.GetID() == b.GetID() { // prepare the request to the worker client := http.DefaultClient client.Timeout = 30 * time.Second @@ -178,6 +173,11 @@ func CancelBuild(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + }).Info("build updated - build canceled") + c.JSON(resp.StatusCode, b) return @@ -207,6 +207,11 @@ func CancelBuild(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + }).Info("build updated - build canceled") + // remove build executable for clean up _, err = database.FromContext(c).PopBuildExecutable(ctx, b.GetID()) if err != nil { diff --git a/api/build/clean.go b/api/build/clean.go index 96a186821..5c70b5164 100644 --- a/api/build/clean.go +++ b/api/build/clean.go @@ -7,17 +7,28 @@ import ( "fmt" "time" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // cleanBuild is a helper function to kill the build // without execution. This will kill all resources, -// like steps and services, for the build in the -// configured backend. -func CleanBuild(ctx context.Context, database database.Interface, b *library.Build, services []*library.Service, steps []*library.Step, e error) { +// like steps and services, for the build. +func CleanBuild(ctx context.Context, database database.Interface, b *types.Build, services []*library.Service, steps []*library.Step, e error) { + l := logrus.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }) + + l.Debug("cleaning build") + // update fields in build object b.SetError(fmt.Sprintf("unable to publish to queue: %s", e.Error())) b.SetStatus(constants.StatusError) @@ -26,9 +37,11 @@ func CleanBuild(ctx context.Context, database database.Interface, b *library.Bui // send API call to update the build b, err := database.UpdateBuild(ctx, b) if err != nil { - logrus.Errorf("unable to kill build %d: %v", b.GetNumber(), err) + l.Errorf("unable to kill build %d: %v", b.GetNumber(), err) } + l.Info("build updated - build cleaned") + for _, s := range services { // update fields in service object s.SetStatus(constants.StatusKilled) @@ -37,8 +50,13 @@ func CleanBuild(ctx context.Context, database database.Interface, b *library.Bui // send API call to update the service _, err := database.UpdateService(ctx, s) if err != nil { - logrus.Errorf("unable to kill service %s for build %d: %v", s.GetName(), b.GetNumber(), err) + l.Errorf("unable to kill service %s for build %d: %v", s.GetName(), b.GetNumber(), err) } + + l.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + }).Info("service updated - service cleaned") } for _, s := range steps { @@ -49,7 +67,12 @@ func CleanBuild(ctx context.Context, database database.Interface, b *library.Bui // send API call to update the step _, err := database.UpdateStep(ctx, s) if err != nil { - logrus.Errorf("unable to kill step %s for build %d: %v", s.GetName(), b.GetNumber(), err) + l.Errorf("unable to kill step %s for build %d: %v", s.GetName(), b.GetNumber(), err) } + + l.WithFields(logrus.Fields{ + "step": s.GetName(), + "step_id": s.GetID(), + }).Info("step updated - step cleaned") } } diff --git a/api/build/compile_publish.go b/api/build/compile_publish.go new file mode 100644 index 000000000..a4e458c7a --- /dev/null +++ b/api/build/compile_publish.go @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/compiler" + "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" + "github.com/go-vela/server/queue" + "github.com/go-vela/server/queue/models" + "github.com/go-vela/server/scm" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// CompileAndPublishConfig is a struct that contains information for the CompileAndPublish function. +type CompileAndPublishConfig struct { + Build *types.Build + Metadata *internal.Metadata + BaseErr string + Source string + Comment string + Labels []string + Retries int +} + +// CompileAndPublish is a helper function to generate the queue items for a build. It takes a form +// as well as the database, scm, compiler, and queue services as arguments. It is used in webhook handling, +// schedule processing, and API build creation. +// +//nolint:funlen,gocyclo // ignore function length due to comments, error handling, and general complexity of function +func CompileAndPublish( + c context.Context, + cfg CompileAndPublishConfig, + database database.Interface, + scm scm.Service, + compiler compiler.Engine, + queue queue.Service, +) (*pipeline.Build, *models.Item, int, error) { + logger := logrus.WithFields(logrus.Fields{ + "org": cfg.Build.GetRepo().GetOrg(), + "repo": cfg.Build.GetRepo().GetName(), + "repo_id": cfg.Build.GetRepo().GetID(), + "build": cfg.Build.GetNumber(), + "build_id": cfg.Build.GetID(), + }) + + logger.Debug("generating queue items") + + // assign variables from form for readibility + r := cfg.Build.GetRepo() + u := cfg.Build.GetRepo().GetOwner() + b := cfg.Build + baseErr := cfg.BaseErr + + // confirm current repo owner has at least write access to repo (needed for status update later) + _, err := scm.RepoAccess(c, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) + if err != nil { + retErr := fmt.Errorf("unable to publish build to queue: repository owner %s no longer has write access to repository %s", u.GetName(), r.GetFullName()) + + return nil, nil, http.StatusUnauthorized, retErr + } + + // get pull request number from build if event is pull_request or issue_comment + var prNum int + if strings.EqualFold(b.GetEvent(), constants.EventPull) || strings.EqualFold(b.GetEvent(), constants.EventComment) { + prNum, err = getPRNumberFromBuild(b) + if err != nil { + retErr := fmt.Errorf("%s: failed to get pull request number for %s: %w", baseErr, r.GetFullName(), err) + + return nil, nil, http.StatusBadRequest, retErr + } + } + + // if the event is issue_comment and the issue is a pull request, + // call SCM for more data not provided in webhook payload + if strings.EqualFold(cfg.Source, "webhook") && strings.EqualFold(b.GetEvent(), constants.EventComment) { + commit, branch, baseref, headref, err := scm.GetPullRequest(c, r, prNum) + if err != nil { + retErr := fmt.Errorf("%s: failed to get pull request info for %s: %w", baseErr, r.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + b.SetCommit(commit) + b.SetBranch(strings.ReplaceAll(branch, "refs/heads/", "")) + b.SetBaseRef(baseref) + b.SetHeadRef(headref) + } + + // if the source is from a schedule, fetch the commit sha from schedule branch (same as build branch at this moment) + if strings.EqualFold(cfg.Source, "schedule") { + // send API call to capture the commit sha for the branch + _, commit, err := scm.GetBranch(c, r, b.GetBranch()) + if err != nil { + retErr := fmt.Errorf("failed to get commit for repo %s on %s branch: %w", r.GetFullName(), r.GetBranch(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + b.SetCommit(commit) + } + + // create SQL filters for querying pending and running builds for repo + filters := map[string]interface{}{ + "status": []string{constants.StatusPending, constants.StatusRunning}, + } + + // send API call to capture the number of pending or running builds for the repo + builds, err := database.CountBuildsForRepo(c, r, filters) + if err != nil { + retErr := fmt.Errorf("%s: unable to get count of builds for repo %s", baseErr, r.GetFullName()) + + return nil, nil, http.StatusInternalServerError, retErr + } + + logger.Debugf("currently %d builds running on repo %s", builds, r.GetFullName()) + + // check if the number of pending and running builds exceeds the limit for the repo + if builds >= r.GetBuildLimit() { + retErr := fmt.Errorf("%s: repo %s has exceeded the concurrent build limit of %d", baseErr, r.GetFullName(), r.GetBuildLimit()) + + return nil, nil, http.StatusTooManyRequests, retErr + } + + // update fields in build object + // this is necessary in case source is restart and the build is prepopulated with these values + b.SetID(0) + b.SetCreated(time.Now().UTC().Unix()) + b.SetEnqueued(0) + b.SetStarted(0) + b.SetFinished(0) + b.SetStatus(constants.StatusPending) + b.SetError("") + b.SetHost("") + b.SetRuntime("") + b.SetDistribution("") + + // variable to store changeset files + var files []string + + // check if the build event is not issue_comment or pull_request + if !strings.EqualFold(b.GetEvent(), constants.EventComment) && + !strings.EqualFold(b.GetEvent(), constants.EventPull) && + !strings.EqualFold(b.GetEvent(), constants.EventDelete) { + // send API call to capture list of files changed for the commit + files, err = scm.Changeset(c, r, b.GetCommit()) + if err != nil { + retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, r.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + } + + // check if the build event is a pull_request + if strings.EqualFold(b.GetEvent(), constants.EventPull) && prNum > 0 { + // send API call to capture list of files changed for the pull request + files, err = scm.ChangesetPR(c, r, prNum) + if err != nil { + retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, r.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + } + + var ( + // variable to store the raw pipeline configuration + pipelineFile []byte + // variable to store executable pipeline + p *pipeline.Build + // variable to store pipeline configuration + pipeline *library.Pipeline + // variable to store the pipeline type for the repository + pipelineType = r.GetPipelineType() + // variable to store updated repository record + repo *types.Repo + ) + + // implement a loop to process asynchronous operations with a retry limit + // + // Some operations taken during the webhook workflow can lead to race conditions + // failing to successfully process the request. This logic ensures we attempt our + // best efforts to handle these cases gracefully. + for i := 0; i < cfg.Retries; i++ { + logger.Debugf("compilation loop - attempt %d", i+1) + // check if we're on the first iteration of the loop + if i > 0 { + // incrementally sleep in between retries + time.Sleep(time.Duration(i) * time.Second) + } + + // send database call to attempt to capture the pipeline if we already processed it before + pipeline, err = database.GetPipelineForRepo(c, b.GetCommit(), r) + if err != nil { // assume the pipeline doesn't exist in the database yet + // send API call to capture the pipeline configuration file + pipelineFile, err = scm.ConfigBackoff(c, u, r, b.GetCommit()) + if err != nil { + retErr := fmt.Errorf("%s: unable to get pipeline configuration for %s: %w", baseErr, r.GetFullName(), err) + + return nil, nil, http.StatusNotFound, retErr + } + } else { + pipelineFile = pipeline.GetData() + } + + // send API call to capture repo for the counter (grabbing repo again to ensure counter is correct) + repo, err = database.GetRepoForOrg(c, r.GetOrg(), r.GetName()) + if err != nil { + retErr := fmt.Errorf("%s: unable to get repo %s: %w", baseErr, r.GetFullName(), err) + + // check if the retry limit has been exceeded + if i < cfg.Retries-1 { + logger.WithError(retErr).Warningf("retrying #%d", i+1) + + // continue to the next iteration of the loop + continue + } + + return nil, nil, http.StatusInternalServerError, retErr + } + + // update DB record of repo (repo) with any changes captured from webhook payload (r) + repo.SetTopics(r.GetTopics()) + repo.SetBranch(r.GetBranch()) + + // update the build numbers based off repo counter + inc := repo.GetCounter() + 1 + repo.SetCounter(inc) + b.SetNumber(inc) + + // populate the build link if a web address is provided + if len(cfg.Metadata.Vela.WebAddress) > 0 { + b.SetLink( + fmt.Sprintf("%s/%s/%d", cfg.Metadata.Vela.WebAddress, repo.GetFullName(), b.GetNumber()), + ) + } + + // ensure we use the expected pipeline type when compiling + // + // The pipeline type for a repo can change at any time which can break compiling + // existing pipelines in the system for that repo. To account for this, we update + // the repo pipeline type to match what was defined for the existing pipeline + // before compiling. After we're done compiling, we reset the pipeline type. + if len(pipeline.GetType()) > 0 { + repo.SetPipelineType(pipeline.GetType()) + } + + var compiled *library.Pipeline + // parse and compile the pipeline configuration file + p, compiled, err = compiler. + Duplicate(). + WithBuild(b). + WithComment(cfg.Comment). + WithCommit(b.GetCommit()). + WithFiles(files). + WithMetadata(cfg.Metadata). + WithRepo(repo). + WithUser(u). + WithLabels(cfg.Labels). + Compile(pipelineFile) + if err != nil { + // format the error message with extra information + err = fmt.Errorf("unable to compile pipeline configuration for %s: %w", repo.GetFullName(), err) + + // log the error for traceability + logger.Error(err.Error()) + + return nil, nil, http.StatusInternalServerError, fmt.Errorf("%s: %w", baseErr, err) + } + + // reset the pipeline type for the repo + // + // The pipeline type for a repo can change at any time which can break compiling + // existing pipelines in the system for that repo. To account for this, we update + // the repo pipeline type to match what was defined for the existing pipeline + // before compiling. After we're done compiling, we reset the pipeline type. + repo.SetPipelineType(pipelineType) + + // skip the build if pipeline compiled to only the init and clone steps + skip := SkipEmptyBuild(p) + if skip != "" { + // set build to successful status + b.SetStatus(constants.StatusSkipped) + + // send API call to set the status on the commit + err = scm.Status(c, u, b, repo.GetOrg(), repo.GetName()) + if err != nil { + logger.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err) + } + + return nil, + &models.Item{ + Build: b, + }, + http.StatusOK, + errors.New(skip) + } + + // check if the pipeline did not already exist in the database + if pipeline == nil { + pipeline = compiled + pipeline.SetRepoID(repo.GetID()) + pipeline.SetCommit(b.GetCommit()) + pipeline.SetRef(b.GetRef()) + + // send API call to create the pipeline + pipeline, err = database.CreatePipeline(c, pipeline) + if err != nil { + retErr := fmt.Errorf("%s: failed to create pipeline for %s: %w", baseErr, repo.GetFullName(), err) + + // check if the retry limit has been exceeded + if i < cfg.Retries-1 { + logger.WithError(retErr).Warningf("retrying #%d", i+1) + + // continue to the next iteration of the loop + continue + } + + return nil, nil, http.StatusInternalServerError, retErr + } + + logger.WithFields(logrus.Fields{ + "pipeline": pipeline.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Info("pipeline created") + } + + b.SetPipelineID(pipeline.GetID()) + + // create the objects from the pipeline in the database + // TODO: + // - if a build gets created and something else fails midway, + // the next loop will attempt to create the same build, + // using the same Number and thus create a constraint + // conflict; consider deleting the partially created + // build object in the database + err = PlanBuild(c, database, scm, p, b, repo) + if err != nil { + retErr := fmt.Errorf("%s: %w", baseErr, err) + + // check if the retry limit has been exceeded + if i < cfg.Retries-1 { + logger.WithError(retErr).Warningf("retrying #%d", i+1) + + // reset fields set by cleanBuild for retry + b.SetError("") + b.SetStatus(constants.StatusPending) + b.SetFinished(0) + + // continue to the next iteration of the loop + continue + } + + return nil, nil, http.StatusInternalServerError, retErr + } + + // break the loop because everything was successful + break + } // end of retry loop + + // send API call to update repo for ensuring counter is incremented + repo, err = database.UpdateRepo(c, repo) + if err != nil { + retErr := fmt.Errorf("%s: failed to update repo %s: %w", baseErr, repo.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + logger.WithFields(logrus.Fields{ + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Info("repo updated - counter incremented") + + // return error if pipeline didn't get populated + if p == nil { + retErr := fmt.Errorf("%s: failed to set pipeline for %s: %w", baseErr, repo.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + // return error if build didn't get populated + if b == nil { + retErr := fmt.Errorf("%s: failed to set build for %s: %w", baseErr, repo.GetFullName(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + // send API call to capture the triggered build + b, err = database.GetBuildForRepo(c, repo, b.GetNumber()) + if err != nil { + retErr := fmt.Errorf("%s: failed to get new build %s/%d: %w", baseErr, repo.GetFullName(), b.GetNumber(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + // determine queue route + route, err := queue.Route(&p.Worker) + if err != nil { + retErr := fmt.Errorf("unable to set route for build %d for %s: %w", b.GetNumber(), r.GetFullName(), err) + + // error out the build + CleanBuild(c, database, b, nil, nil, retErr) + + return nil, nil, http.StatusBadRequest, retErr + } + + // temporarily set host to the route before it gets picked up by a worker + b.SetHost(route) + + // publish the pipeline.Build to the build_executables table to be requested by a worker + err = PublishBuildExecutable(c, database, p, b) + if err != nil { + retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", repo.GetFullName(), b.GetNumber(), err) + + return nil, nil, http.StatusInternalServerError, retErr + } + + return p, models.ToItem(b), http.StatusCreated, nil +} + +// getPRNumberFromBuild is a helper function to +// extract the pull request number from a Build. +func getPRNumberFromBuild(b *types.Build) (int, error) { + // parse out pull request number from base ref + // + // pattern: refs/pull/1/head + var parts []string + if strings.HasPrefix(b.GetRef(), "refs/pull/") { + parts = strings.Split(b.GetRef(), "/") + } + + // just being safe to avoid out of range index errors + if len(parts) < 3 { + return 0, fmt.Errorf("invalid ref: %s", b.GetRef()) + } + + // return the results of converting number to string + return strconv.Atoi(parts[2]) +} diff --git a/api/build/create.go b/api/build/create.go index 88fa52908..8fef779c7 100644 --- a/api/build/create.go +++ b/api/build/create.go @@ -5,29 +5,23 @@ package build import ( "fmt" "net/http" - "strconv" - "strings" - "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/queue" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds builds CreateBuild // -// Create a build in the configured backend +// Create a build // // --- // produces: @@ -35,17 +29,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: body // name: body -// description: Payload containing the build to create +// description: Build object to create // required: true // schema: // "$ref": "#/definitions/Build" @@ -53,51 +47,47 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Request processed but build was skipped +// description: Successfully received request but build was skipped // schema: // type: string // '201': -// description: Successfully created the build +// description: Successfully created the build from request // type: json // schema: // "$ref": "#/definitions/Build" // '400': -// description: Unable to create the build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to create the build +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '429': +// description: Concurrent build limit reached for repository // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CreateBuild represents the API handler to create a build in the configured backend. -// -//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity +// CreateBuild represents the API handler to create a build. func CreateBuild(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) - o := org.Retrieve(c) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - - logger.Infof("creating new build for repo %s", r.GetFullName()) + l.Debugf("creating new build for repo %s", r.GetFullName()) // capture body from API request - input := new(library.Build) + input := new(types.Build) err := c.Bind(input) if err != nil { @@ -108,11 +98,10 @@ func CreateBuild(c *gin.Context) { return } + input.SetRepo(r) + // verify the build has a valid event and the repo allows that event type - if (input.GetEvent() == constants.EventPush && !r.GetAllowPush()) || - (input.GetEvent() == constants.EventPull && !r.GetAllowPull()) || - (input.GetEvent() == constants.EventTag && !r.GetAllowTag()) || - (input.GetEvent() == constants.EventDeploy && !r.GetAllowDeploy()) { + if !r.GetAllowEvents().Allowed(input.GetEvent(), input.GetEventAction()) { retErr := fmt.Errorf("unable to create new build: %s does not have %s events enabled", r.GetFullName(), input.GetEvent()) util.HandleError(c, http.StatusBadRequest, retErr) @@ -120,286 +109,50 @@ func CreateBuild(c *gin.Context) { return } - // send API call to capture the repo owner - u, err = database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // create SQL filters for querying pending and running builds for repo - filters := map[string]interface{}{ - "status": []string{constants.StatusPending, constants.StatusRunning}, - } - - // send API call to capture the number of pending or running builds for the repo - builds, err := database.FromContext(c).CountBuildsForRepo(ctx, r, filters) - if err != nil { - retErr := fmt.Errorf("unable to create new build: unable to get count of builds for repo %s", r.GetFullName()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // check if the number of pending and running builds exceeds the limit for the repo - if builds >= r.GetBuildLimit() { - retErr := fmt.Errorf("unable to create new build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // update fields in build object - input.SetRepoID(r.GetID()) - input.SetStatus(constants.StatusPending) - input.SetCreated(time.Now().UTC().Unix()) - - // set the parent equal to the current repo counter - input.SetParent(r.GetCounter()) - // check if the parent is set to 0 - if input.GetParent() == 0 { - // parent should be "1" if it's the first build ran - input.SetParent(1) - } - - // update the build numbers based off repo counter - inc := r.GetCounter() + 1 - r.SetCounter(inc) - input.SetNumber(inc) - - // populate the build link if a web address is provided - if len(m.Vela.WebAddress) > 0 { - input.SetLink( - fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), input.GetNumber()), - ) - } - - // variable to store changeset files - var files []string - // check if the build event is not issue_comment or pull_request - if !strings.EqualFold(input.GetEvent(), constants.EventComment) && - !strings.EqualFold(input.GetEvent(), constants.EventPull) { - // send API call to capture list of files changed for the commit - files, err = scm.FromContext(c).Changeset(ctx, u, r, input.GetCommit()) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - } - - // check if the build event is a pull_request - if strings.EqualFold(input.GetEvent(), constants.EventPull) { - // capture number from build - number, err := getPRNumberFromBuild(input) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to get pull_request number for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to capture list of files changed for the pull request - files, err = scm.FromContext(c).ChangesetPR(ctx, u, r, number) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } + // create config + config := CompileAndPublishConfig{ + Build: input, + Metadata: m, + BaseErr: "unable to create build", + Source: "create", + Retries: 1, } - var ( - // variable to store the raw pipeline configuration - config []byte - // variable to store executable pipeline - p *pipeline.Build - // variable to store pipeline configuration - pipeline *library.Pipeline - // variable to store the pipeline type for the repository - pipelineType = r.GetPipelineType() + _, item, code, err := CompileAndPublish( + c, + config, + database.FromContext(c), + scm.FromContext(c), + compiler.FromContext(c), + queue.FromContext(c), ) - // send API call to attempt to capture the pipeline - pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, input.GetCommit(), r) - if err != nil { // assume the pipeline doesn't exist in the database yet - // send API call to capture the pipeline configuration file - config, err = scm.FromContext(c).ConfigBackoff(ctx, u, r, input.GetCommit()) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to get pipeline configuration for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusNotFound, retErr) - - return - } - } else { - config = pipeline.GetData() - } - - // ensure we use the expected pipeline type when compiling - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - if len(pipeline.GetType()) > 0 { - r.SetPipelineType(pipeline.GetType()) - } - - var compiled *library.Pipeline - // parse and compile the pipeline configuration file - p, compiled, err = compiler.FromContext(c). - Duplicate(). - WithBuild(input). - WithFiles(files). - WithMetadata(m). - WithRepo(r). - WithUser(u). - Compile(config) - if err != nil { - retErr := fmt.Errorf("unable to compile pipeline configuration for %s/%d: %w", r.GetFullName(), input.GetNumber(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - // reset the pipeline type for the repo - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - r.SetPipelineType(pipelineType) - - // skip the build if only the init or clone steps are found - skip := SkipEmptyBuild(p) - if skip != "" { - // set build to successful status - input.SetStatus(constants.StatusSuccess) - - // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, input, r.GetOrg(), r.GetName()) - if err != nil { - logger.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), input.GetNumber(), err) - } - - c.JSON(http.StatusOK, skip) + // check if build was skipped + if err != nil && code == http.StatusOK { + c.JSON(http.StatusOK, err.Error()) return } - // check if the pipeline did not already exist in the database - // - //nolint:dupl // ignore duplicate code - if pipeline == nil { - pipeline = compiled - pipeline.SetRepoID(r.GetID()) - pipeline.SetCommit(input.GetCommit()) - pipeline.SetRef(input.GetRef()) - - // send API call to create the pipeline - pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to create pipeline for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - } - - input.SetPipelineID(pipeline.GetID()) - - // create the objects from the pipeline in the database - err = PlanBuild(ctx, database.FromContext(c), scm.FromContext(c), p, input, r) if err != nil { - util.HandleError(c, http.StatusInternalServerError, err) + util.HandleError(c, code, err) return } - // send API call to update repo for ensuring counter is incremented - r, err = database.FromContext(c).UpdateRepo(ctx, r) - if err != nil { - retErr := fmt.Errorf("unable to create new build: failed to update repo %s: %w", r.GetFullName(), err) + l.WithFields(logrus.Fields{ + "build": item.Build.GetNumber(), + "build_id": item.Build.GetID(), + }).Info("build created") - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // send API call to capture the created build - input, _ = database.FromContext(c).GetBuildForRepo(ctx, r, input.GetNumber()) - - c.JSON(http.StatusCreated, input) - - // send API call to set the status on the commit except for scheduled build - if input.GetEvent() != constants.EventSchedule { - err = scm.FromContext(c).Status(ctx, u, input, r.GetOrg(), r.GetName()) - if err != nil { - logger.Errorf("unable to set commit status for build %s/%d: %v", r.GetFullName(), input.GetNumber(), err) - } - } - - // determine queue route - route, err := queue.FromGinContext(c).Route(&p.Worker) - if err != nil { - logrus.Errorf("unable to set route for build %d for %s: %v", input.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, database.FromContext(c), input, nil, nil, err) - - return - } - - // temporarily set host to the route before it gets picked up by a worker - input.SetHost(route) - - err = PublishBuildExecutable(ctx, database.FromContext(c), p, input) - if err != nil { - retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), input.GetNumber(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } + c.JSON(http.StatusCreated, item.Build) // publish the build to the queue - go PublishToQueue( + go Enqueue( ctx, queue.FromGinContext(c), database.FromContext(c), - input, - r, - u, - route, + item, + item.Build.GetHost(), ) } - -// getPRNumberFromBuild is a helper function to -// extract the pull request number from a Build. -func getPRNumberFromBuild(b *library.Build) (int, error) { - // parse out pull request number from base ref - // - // pattern: refs/pull/1/head - var parts []string - if strings.HasPrefix(b.GetRef(), "refs/pull/") { - parts = strings.Split(b.GetRef(), "/") - } - - // just being safe to avoid out of range index errors - if len(parts) < 3 { - return 0, fmt.Errorf("invalid ref: %s", b.GetRef()) - } - - // return the results of converting number to string - return strconv.Atoi(parts[2]) -} diff --git a/api/build/delete.go b/api/build/delete.go index c1ce1de82..c7824e3f7 100644 --- a/api/build/delete.go +++ b/api/build/delete.go @@ -7,18 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build} builds DeleteBuild // -// Delete a build in the configured backend +// Delete a build // // --- // produces: @@ -26,17 +25,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to delete +// description: Build number // required: true // type: integer // security: @@ -47,35 +46,34 @@ import ( // schema: // type: string // '400': -// description: Unable to delete the build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // DeleteBuild represents the API handler to remove -// a build for a repo from the configured backend. +// a build for a repo. func DeleteBuild(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting build %s", entry) + l.Debugf("deleting build %s", entry) // send API call to remove the build err := database.FromContext(c).DeleteBuild(ctx, b) diff --git a/api/build/enqueue.go b/api/build/enqueue.go new file mode 100644 index 000000000..aa87f4473 --- /dev/null +++ b/api/build/enqueue.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "context" + "encoding/json" + "time" + + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" + "github.com/go-vela/server/queue" + "github.com/go-vela/server/queue/models" +) + +// Enqueue is a helper function that pushes a queue item (build, repo, user) to the queue. +func Enqueue(ctx context.Context, queue queue.Service, db database.Interface, item *models.Item, route string) { + l := logrus.WithFields(logrus.Fields{ + "build": item.Build.GetNumber(), + "build_id": item.Build.GetID(), + "org": item.Build.GetRepo().GetOrg(), + "repo": item.Build.GetRepo().GetName(), + "repo_id": item.Build.GetRepo().GetID(), + }) + + l.Debug("adding item to queue") + + byteItem, err := json.Marshal(item) + if err != nil { + l.Errorf("failed to convert item to json: %v", err) + + // error out the build + CleanBuild(ctx, db, item.Build, nil, nil, err) + + return + } + + l.Debugf("pushing item for build to queue route %s", route) + + // push item on to the queue + err = queue.Push(context.Background(), route, byteItem) + if err != nil { + l.Errorf("retrying; failed to publish build: %v", err) + + err = queue.Push(context.Background(), route, byteItem) + if err != nil { + l.Errorf("failed to publish build: %v", err) + + // error out the build + CleanBuild(ctx, db, item.Build, nil, nil, err) + + return + } + } + + // update fields in build object + item.Build.SetEnqueued(time.Now().UTC().Unix()) + + // update the build in the db to reflect the time it was enqueued + _, err = db.UpdateBuild(ctx, item.Build) + if err != nil { + l.Errorf("failed to update build during publish to queue: %v", err) + } + + l.Info("updated build as enqueued") +} diff --git a/api/build/executable.go b/api/build/executable.go index 351314043..19f66c3a6 100644 --- a/api/build/executable.go +++ b/api/build/executable.go @@ -9,20 +9,20 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/executable builds GetBuildExecutable // -// Get a build executable in the configured backend +// Get a build executable // // --- // produces: @@ -30,17 +30,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to retrieve +// description: Build number // required: true // type: integer // security: @@ -50,39 +50,34 @@ import ( // description: Successfully retrieved the build executable // type: json // schema: -// "$ref": "#/definitions/Build" +// "$ref": "#/definitions/BuildExecutable" // '400': -// description: Bad request +// description: Invalid request payload or path // schema: // "$ref": "#/definitions/Error" // '401': // description: Unauthorized // schema: // "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Could not retrieve build executable +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetBuildExecutable represents the API handler to capture -// a build executable for a repo from the configured backend. +// GetBuildExecutable represents the API handler to get +// a build executable for a repository. func GetBuildExecutable(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - cl := claims.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "subject": cl.Subject, - }).Infof("reading build executable %s/%d", r.GetFullName(), b.GetNumber()) + l.Debugf("reading build executable %s/%d", r.GetFullName(), b.GetNumber()) // send database call to pop the requested build executable from the table bExecutable, err := database.FromContext(c).PopBuildExecutable(ctx, b.GetID()) @@ -98,11 +93,11 @@ func GetBuildExecutable(c *gin.Context) { // PublishBuildExecutable marshals a pipeline.Build into bytes and pushes that data to the build_executables table to be // requested by a worker whenever the build has been picked up. -func PublishBuildExecutable(ctx context.Context, db database.Interface, p *pipeline.Build, b *library.Build) error { +func PublishBuildExecutable(ctx context.Context, db database.Interface, p *pipeline.Build, b *types.Build) error { // marshal pipeline build into byte data to add to the build executable object byteExecutable, err := json.Marshal(p) if err != nil { - logrus.Errorf("Failed to marshal build executable: %v", err) + logrus.Errorf("failed to marshal build executable: %v", err) // error out the build CleanBuild(ctx, db, b, nil, nil, err) @@ -118,7 +113,7 @@ func PublishBuildExecutable(ctx context.Context, db database.Interface, p *pipel // send database call to create a build executable err = db.CreateBuildExecutable(ctx, bExecutable) if err != nil { - logrus.Errorf("Failed to publish build executable to database: %v", err) + logrus.Errorf("failed to publish build executable to database: %v", err) // error out the build CleanBuild(ctx, db, b, nil, nil, err) @@ -126,5 +121,9 @@ func PublishBuildExecutable(ctx context.Context, db database.Interface, p *pipel return err } + logrus.WithFields(logrus.Fields{ + "build_executable_id": bExecutable.GetBuildID(), + }).Info("created build executable") + return nil } diff --git a/api/build/get.go b/api/build/get.go index 135ca1bb4..773cec777 100644 --- a/api/build/get.go +++ b/api/build/get.go @@ -6,16 +6,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build} builds GetBuild // -// Get a build in the configured backend +// Get a build // // --- // produces: @@ -23,17 +22,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to retrieve +// description: Build number // required: true // type: integer // security: @@ -44,25 +43,28 @@ import ( // type: json // schema: // "$ref": "#/definitions/Build" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" -// GetBuild represents the API handler to capture -// a build for a repo from the configured backend. +// GetBuild represents the API handler to get +// a build for a repository. func GetBuild(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading build %s/%d", r.GetFullName(), b.GetNumber()) + l.Debugf("reading build %s/%d", r.GetFullName(), b.GetNumber()) c.JSON(http.StatusOK, b) } diff --git a/api/build/get_id.go b/api/build/get_id.go index 44cfbdbea..2a9e4201e 100644 --- a/api/build/get_id.go +++ b/api/build/get_id.go @@ -8,17 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/search/builds/{id} builds GetBuildByID // -// Get a single build by its id in the configured backend +// Get a build by id // // --- // produces: @@ -26,7 +26,7 @@ import ( // parameters: // - in: path // name: id -// description: build id +// description: Build ID // required: true // type: number // security: @@ -37,30 +37,28 @@ import ( // schema: // "$ref": "#/definitions/Build" // '400': -// description: Unable to retrieve the build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetBuildByID represents the API handler to capture a -// build by its id from the configured backend. +// GetBuildByID represents the API handler to get a +// build by its id. func GetBuildByID(c *gin.Context) { - // Variables that will hold the library types of the build and repo - var ( - b *library.Build - r *library.Repo - ) - // Capture user from middleware + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() // Parse build ID from path id, err := strconv.ParseInt(c.Param("id"), 10, 64) - if err != nil { retErr := fmt.Errorf("unable to parse build id: %w", err) @@ -69,16 +67,10 @@ func GetBuildByID(c *gin.Context) { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": id, - "user": u.GetName(), - }).Infof("reading build %d", id) + l.Debugf("reading build %d", id) // Get build from database - b, err = database.FromContext(c).GetBuild(ctx, id) + b, err := database.FromContext(c).GetBuild(ctx, id) if err != nil { retErr := fmt.Errorf("unable to get build: %w", err) @@ -87,21 +79,11 @@ func GetBuildByID(c *gin.Context) { return } - // Get repo from database using repo ID field from build - r, err = database.FromContext(c).GetRepo(ctx, b.GetRepoID()) - if err != nil { - retErr := fmt.Errorf("unable to get repo: %w", err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - // Capture user access from SCM. We do this in order to ensure user has access and is not // just retrieving any build using a random id number. - perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), b.GetRepo().GetOrg(), b.GetRepo().GetName()) if err != nil { - logrus.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) + l.Errorf("unable to get user %s access level for repo %s", u.GetName(), b.GetRepo().GetFullName()) } // Ensure that user has at least read access to repo to return the build diff --git a/api/build/graph.go b/api/build/graph.go index dde6e5343..5fb3ea1ee 100644 --- a/api/build/graph.go +++ b/api/build/graph.go @@ -9,19 +9,19 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" ) // Graph contains nodes, and relationships between nodes, or edges. @@ -90,7 +90,7 @@ const ( // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/graph builds GetBuildGraph // -// Get directed a-cyclical graph for a build in the configured backend +// Get directed a-cyclical graph for a build // // --- // produces: @@ -98,12 +98,12 @@ const ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -119,48 +119,41 @@ const ( // type: json // schema: // "$ref": "#/definitions/Graph" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" // '401': -// description: Unable to retrieve graph for the build â unauthorized +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve graph for the build â not found +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve graph for the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetBuildGraph represents the API handler to capture a -// directed a-cyclical graph for a build from the configured backend. +// GetBuildGraph represents the API handler to get a +// directed a-cyclical graph for a build. // //nolint:funlen,goconst,gocyclo // ignore function length and constants func GetBuildGraph(c *gin.Context) { // capture middleware values + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) - m := c.MustGet("metadata").(*types.Metadata) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - logger := logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) baseErr := "unable to retrieve graph" - logger.Infof("constructing graph for build %s", entry) - - logger.Info("retrieving pipeline configuration") + l.Debugf("constructing graph for build %s and retrieving pipeline configuration", entry) var config []byte @@ -194,7 +187,7 @@ func GetBuildGraph(c *gin.Context) { // check if the build event is not pull_request if !strings.EqualFold(b.GetEvent(), constants.EventPull) { // send API call to capture list of files changed for the commit - files, err = scm.FromContext(c).Changeset(ctx, u, r, b.GetCommit()) + files, err = scm.FromContext(c).Changeset(ctx, r, b.GetCommit()) if err != nil { retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, r.GetFullName(), err) @@ -205,7 +198,7 @@ func GetBuildGraph(c *gin.Context) { } } - logger.Info("compiling pipeline configuration") + l.Debug("compiling pipeline configuration") // parse and compile the pipeline configuration file p, _, err := compiler.FromContext(c). @@ -221,7 +214,7 @@ func GetBuildGraph(c *gin.Context) { // format the error message with extra information err = fmt.Errorf("unable to compile pipeline configuration for %s: %w", r.GetFullName(), err) - logger.Error(err.Error()) + l.Error(err.Error()) retErr := fmt.Errorf("%s: %w", baseErr, err) @@ -233,7 +226,7 @@ func GetBuildGraph(c *gin.Context) { if p == nil { retErr := fmt.Errorf("unable to compile pipeline configuration for %s: pipeline is nil", r.GetFullName()) - logger.Error(retErr) + l.Error(retErr) util.HandleError(c, http.StatusInternalServerError, retErr) @@ -326,7 +319,7 @@ func GetBuildGraph(c *gin.Context) { return } - logger.Info("generating build graph") + l.Debug("generating build graph") // create nodes from pipeline stages nodes := make(map[int]*node) diff --git a/api/build/id_request_token.go b/api/build/id_request_token.go new file mode 100644 index 000000000..77869a44c --- /dev/null +++ b/api/build/id_request_token.go @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/internal/token" + "github.com/go-vela/server/router/middleware/build" + "github.com/go-vela/server/util" + "github.com/go-vela/types/library" +) + +// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/id_request_token builds GetIDRequestToken +// +// Get a Vela OIDC request token for a build +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: org +// description: Name of the organization +// required: true +// type: string +// - in: path +// name: repo +// description: Name of the repository +// required: true +// type: string +// - in: path +// name: build +// description: Build number +// required: true +// type: integer +// - in: query +// name: image +// description: Add image to token claims +// type: string +// - in: query +// name: request +// description: Add request input to token claims +// type: string +// - in: query +// name: commands +// description: Add commands input to token claims +// type: boolean +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved ID Request token +// schema: +// "$ref": "#/definitions/Token" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// GetIDRequestToken represents the API handler to generate and return an ID request token. +func GetIDRequestToken(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + b := build.Retrieve(c) + + l.Infof("generating ID request token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber()) + + image := c.Query("image") + if len(image) == 0 { + retErr := errors.New("no step 'image' provided in query parameters") + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + request := c.Query("request") + if len(request) == 0 { + retErr := errors.New("no 'request' provided in query parameters") + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + commands := false + + var err error + + if len(c.Query("commands")) > 0 { + commands, err = strconv.ParseBool(c.Query("commands")) + if err != nil { + retErr := fmt.Errorf("unable to parse 'commands' query parameter as boolean %s: %w", c.Query("commands"), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + } + + // retrieve token manager from context + tm := c.MustGet("token-manager").(*token.Manager) + + exp := (time.Duration(b.GetRepo().GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration + + // set mint token options + idmto := &token.MintTokenOpts{ + Build: b, + Repo: b.GetRepo().GetFullName(), + TokenType: constants.IDRequestTokenType, + TokenDuration: exp, + Image: util.Sanitize(image), + Request: util.Sanitize(request), + Commands: commands, + } + + // mint token + idrt, err := tm.MintToken(idmto) + if err != nil { + retErr := fmt.Errorf("unable to generate ID request token: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, library.Token{Token: &idrt}) +} diff --git a/api/build/id_token.go b/api/build/id_token.go new file mode 100644 index 000000000..e049dcebd --- /dev/null +++ b/api/build/id_token.go @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database" + "github.com/go-vela/server/internal/token" + "github.com/go-vela/server/router/middleware/build" + "github.com/go-vela/server/router/middleware/claims" + "github.com/go-vela/server/util" + "github.com/go-vela/types/library" +) + +// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/id_token builds GetIDToken +// +// Get a Vela OIDC token for a build +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: org +// description: Name of the organization +// required: true +// type: string +// - in: path +// name: repo +// description: Name of the repository +// required: true +// type: string +// - in: path +// name: build +// description: Build number +// required: true +// type: integer +// - in: query +// name: audience +// description: Add audience to token claims +// type: array +// items: +// type: string +// collectionFormat: multi +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved ID token +// schema: +// "$ref": "#/definitions/Token" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// GetIDToken represents the API handler to generate a id token. +func GetIDToken(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + b := build.Retrieve(c) + cl := claims.Retrieve(c) + ctx := c.Request.Context() + + l.Infof("generating ID token for build %s/%d", b.GetRepo().GetFullName(), b.GetNumber()) + + // retrieve token manager from context + tm := c.MustGet("token-manager").(*token.Manager) + + // set mint token options + idmto := &token.MintTokenOpts{ + Build: b, + Repo: b.GetRepo().GetFullName(), + TokenType: constants.IDTokenType, + TokenDuration: tm.IDTokenDuration, + Image: cl.Image, + Request: cl.Request, + Commands: cl.Commands, + } + + // if audience is provided, include that in claims + audience := []string{} + + if len(c.QueryArray("audience")) > 0 { + for _, a := range c.QueryArray("audience") { + if len(a) > 0 { + audience = append(audience, util.Sanitize(a)) + } + } + } + + if len(audience) == 0 { + retErr := fmt.Errorf("unable to generate ID token: %s", "no audience provided") + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + idmto.Audience = audience + + // mint token + idt, err := tm.MintIDToken(ctx, idmto, database.FromContext(c)) + if err != nil { + retErr := fmt.Errorf("unable to generate ID token: %w", err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, library.Token{Token: &idt}) +} diff --git a/api/build/list_org.go b/api/build/list_org.go index 673879bc4..339baa0df 100644 --- a/api/build/list_org.go +++ b/api/build/list_org.go @@ -8,20 +8,21 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/builds builds ListBuildsForOrg // -// Get a list of builds by org in the configured backend +// Get all builds for an organization // // --- // produces: @@ -29,7 +30,7 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: query @@ -84,39 +85,38 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of builds +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of builds +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListBuildsForOrg represents the API handler to capture a -// list of builds associated with an org from the configured backend. +// ListBuildsForOrg represents the API handler to get a +// list of builds associated with an organization. func ListBuildsForOrg(c *gin.Context) { // variables that will hold the build list, build list filters and total count var ( filters = map[string]interface{}{} - b []*library.Build + b []*types.Build t int64 ) // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "user": u.GetName(), - }).Infof("listing builds for org %s", o) + l.Debugf("listing builds for org %s", o) // capture the branch name parameter branch := c.Query("branch") @@ -191,7 +191,7 @@ func ListBuildsForOrg(c *gin.Context) { // See if the user is an org admin to bypass individual permission checks perm, err := scm.FromContext(c).OrgAccess(ctx, u, o) if err != nil { - logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o) + l.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } // Only show public repos to non-admins if perm != "admin" { @@ -200,7 +200,6 @@ func ListBuildsForOrg(c *gin.Context) { // send API call to capture the list of builds for the org (and event type if passed in) b, t, err = database.FromContext(c).ListBuildsForOrg(ctx, o, filters, page, perPage) - if err != nil { retErr := fmt.Errorf("unable to list builds for org %s: %w", o, err) diff --git a/api/build/list_repo.go b/api/build/list_repo.go index a1a51d023..b4771eefe 100644 --- a/api/build/list_repo.go +++ b/api/build/list_repo.go @@ -9,20 +9,19 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds builds ListBuildsForRepo // -// Get builds from the configured backend +// Get all builds for a repository // // --- // produces: @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: query @@ -82,12 +81,12 @@ import ( // default: 10 // - in: query // name: before -// description: filter builds created before a certain time +// description: Filter builds created before a certain time // type: integer // default: 1 // - in: query // name: after -// description: filter builds created after a certain time +// description: Filter builds created after a certain time // type: integer // default: 0 // security: @@ -104,41 +103,41 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of builds +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of builds +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListBuildsForRepo represents the API handler to capture a -// list of builds for a repo from the configured backend. +// ListBuildsForRepo represents the API handler to get a +// list of builds for a repository. func ListBuildsForRepo(c *gin.Context) { // variables that will hold the build list, build list filters and total count var ( filters = map[string]interface{}{} - b []*library.Build + b []*types.Build t int64 ) // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("listing builds for repo %s", r.GetFullName()) + l.Debugf("listing builds for repo %s", r.GetFullName()) // capture the branch name parameter branch := c.Query("branch") diff --git a/api/build/plan.go b/api/build/plan.go index 9d49eaffd..e8ed0b643 100644 --- a/api/build/plan.go +++ b/api/build/plan.go @@ -7,21 +7,22 @@ import ( "fmt" "time" - "github.com/go-vela/server/scm" + "github.com/sirupsen/logrus" "github.com/go-vela/server/api/service" "github.com/go-vela/server/api/step" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/types/library" + "github.com/go-vela/server/scm" "github.com/go-vela/types/pipeline" ) // PlanBuild is a helper function to plan the build for // execution. This creates all resources, like steps -// and services, for the build in the configured backend. +// and services, for the build. // TODO: // - return build and error. -func PlanBuild(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *library.Build, r *library.Repo) error { +func PlanBuild(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build, r *types.Repo) error { // update fields in build object b.SetCreated(time.Now().UTC().Unix()) @@ -41,6 +42,11 @@ func PlanBuild(ctx context.Context, database database.Interface, scm scm.Service return fmt.Errorf("unable to create new build for %s: %w", r.GetFullName(), err) } + logrus.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + }).Info("build created") + // plan all services for the build services, err := service.PlanServices(ctx, database, p, b) if err != nil { diff --git a/api/build/publish.go b/api/build/publish.go deleted file mode 100644 index f843b1461..000000000 --- a/api/build/publish.go +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package build - -import ( - "context" - "encoding/json" - "time" - - "github.com/go-vela/server/database" - "github.com/go-vela/server/queue" - "github.com/go-vela/types" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" -) - -// PublishToQueue is a helper function that publishes a queue item (build, repo, user) to the queue. -func PublishToQueue(ctx context.Context, queue queue.Service, db database.Interface, b *library.Build, r *library.Repo, u *library.User, route string) { - // convert build, repo, and user into queue item - item := types.ToItem(b, r, u) - - logrus.Infof("Converting queue item to json for build %d for %s", b.GetNumber(), r.GetFullName()) - - byteItem, err := json.Marshal(item) - if err != nil { - logrus.Errorf("Failed to convert item to json for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, db, b, nil, nil, err) - - return - } - - logrus.Infof("Establishing route for build %d for %s", b.GetNumber(), r.GetFullName()) - - logrus.Infof("Publishing item for build %d for %s to queue %s", b.GetNumber(), r.GetFullName(), route) - - // push item on to the queue - err = queue.Push(context.Background(), route, byteItem) - if err != nil { - logrus.Errorf("Retrying; Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - err = queue.Push(context.Background(), route, byteItem) - if err != nil { - logrus.Errorf("Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, db, b, nil, nil, err) - - return - } - } - - // update fields in build object - b.SetEnqueued(time.Now().UTC().Unix()) - - // update the build in the db to reflect the time it was enqueued - _, err = db.UpdateBuild(ctx, b) - if err != nil { - logrus.Errorf("Failed to update build %d during publish to queue for %s: %v", b.GetNumber(), r.GetFullName(), err) - } -} diff --git a/api/build/restart.go b/api/build/restart.go index 782ab313a..aed5fccd4 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -6,29 +6,26 @@ import ( "fmt" "net/http" "strings" - "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/queue" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build} builds RestartBuild // -// Restart a build in the configured backend +// Restart a build // // --- // produces: @@ -36,68 +33,69 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to restart +// description: Build number // required: true // type: integer // security: // - ApiKeyAuth: [] // responses: // '200': -// description: Request processed but build was skipped +// description: Successfully received request but build was skipped // schema: // type: string // '201': -// description: Successfully restarted the build +// description: Successfully created the build from request +// type: json // schema: // "$ref": "#/definitions/Build" // '400': -// description: Unable to restart the build +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to restart the build +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '429': +// description: Concurrent build limit reached for repository // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to restart the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// RestartBuild represents the API handler to restart an existing build in the configured backend. -// -//nolint:funlen // ignore statement count +// RestartBuild represents the API handler to restart an existing build. func RestartBuild(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) + scm := scm.FromContext(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) + l.Debugf("restarting build %d", b.GetNumber()) + // a build that is in a pending approval state cannot be restarted if strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) { retErr := fmt.Errorf("unable to restart build %s/%d: cannot restart a build pending approval", r.GetFullName(), b.GetNumber()) @@ -106,293 +104,63 @@ func RestartBuild(c *gin.Context) { return } - logger.Infof("restarting build %s", entry) - - // send API call to capture the repo owner - u, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // create SQL filters for querying pending and running builds for repo - filters := map[string]interface{}{ - "status": []string{constants.StatusPending, constants.StatusRunning}, - } - - // send API call to capture the number of pending or running builds for the repo - builds, err := database.FromContext(c).CountBuildsForRepo(ctx, r, filters) - if err != nil { - retErr := fmt.Errorf("unable to restart build: unable to get count of builds for repo %s", r.GetFullName()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // check if the number of pending and running builds exceeds the limit for the repo - if builds >= r.GetBuildLimit() { - retErr := fmt.Errorf("unable to restart build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // update fields in build object - b.SetID(0) - b.SetCreated(time.Now().UTC().Unix()) - b.SetEnqueued(0) - b.SetStarted(0) - b.SetFinished(0) - b.SetStatus(constants.StatusPending) - b.SetError("") - b.SetHost("") - b.SetRuntime("") - b.SetDistribution("") + // set sender to the user who initiated the restart b.SetSender(cl.Subject) - // update the PR event action if action was never set - // for backwards compatibility with pre-0.14 releases. - if b.GetEvent() == constants.EventPull && b.GetEventAction() == "" { - // technically, the action could have been opened or synchronize. - // will not affect behavior of the pipeline since we did not - // support actions for builds where this would be the case. - b.SetEventAction(constants.ActionOpened) - } - - // set the parent equal to the restarted build number - b.SetParent(b.GetNumber()) - // update the build numbers based off repo counter - inc := r.GetCounter() + 1 - r.SetCounter(inc) - b.SetNumber(inc) - - // populate the build link if a web address is provided - if len(m.Vela.WebAddress) > 0 { - b.SetLink( - fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), b.GetNumber()), - ) - } - - // variable to store changeset files - var files []string - // check if the build event is not issue_comment or pull_request - if !strings.EqualFold(b.GetEvent(), constants.EventComment) && - !strings.EqualFold(b.GetEvent(), constants.EventPull) { - // send API call to capture list of files changed for the commit - files, err = scm.FromContext(c).Changeset(ctx, u, r, b.GetCommit()) - if err != nil { - retErr := fmt.Errorf("unable to restart build: failed to get changeset for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - } - - // check if the build event is a pull_request - if strings.EqualFold(b.GetEvent(), constants.EventPull) { - // capture number from build - number, err := getPRNumberFromBuild(b) - if err != nil { - retErr := fmt.Errorf("unable to restart build: failed to get pull_request number for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to capture list of files changed for the pull request - files, err = scm.FromContext(c).ChangesetPR(ctx, u, r, number) - if err != nil { - retErr := fmt.Errorf("unable to restart build: failed to get changeset for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - } - - // variables to store pipeline configuration - var ( - // variable to store the raw pipeline configuration - config []byte - // variable to store executable pipeline - p *pipeline.Build - // variable to store pipeline configuration - pipeline *library.Pipeline - // variable to store the pipeline type for the repository - pipelineType = r.GetPipelineType() - ) - - // send API call to attempt to capture the pipeline - pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, b.GetCommit(), r) - if err != nil { // assume the pipeline doesn't exist in the database yet (before pipeline support was added) - // send API call to capture the pipeline configuration file - config, err = scm.FromContext(c).ConfigBackoff(ctx, u, r, b.GetCommit()) - if err != nil { - retErr := fmt.Errorf("unable to get pipeline configuration for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusNotFound, retErr) - - return - } - } else { - config = pipeline.GetData() - } - - // ensure we use the expected pipeline type when compiling - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - if len(pipeline.GetType()) > 0 { - r.SetPipelineType(pipeline.GetType()) - } - - var compiled *library.Pipeline - // parse and compile the pipeline configuration file - p, compiled, err = compiler.FromContext(c). - Duplicate(). - WithBuild(b). - WithCommit(b.GetCommit()). - WithFiles(files). - WithMetadata(m). - WithRepo(r). - WithUser(u). - Compile(config) + // fetch scm user id + senderID, err := scm.GetUserID(ctx, u.GetName(), r.GetOwner().GetToken()) if err != nil { - retErr := fmt.Errorf("unable to compile pipeline configuration for %s: %w", entry, err) + retErr := fmt.Errorf("unable to get SCM user id for %s: %w", u.GetName(), err) util.HandleError(c, http.StatusInternalServerError, retErr) return } - // reset the pipeline type for the repo - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - r.SetPipelineType(pipelineType) - - // skip the build if only the init or clone steps are found - skip := SkipEmptyBuild(p) - if skip != "" { - // set build to successful status - b.SetStatus(constants.StatusSkipped) - - // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, b, r.GetOrg(), r.GetName()) - if err != nil { - logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err) - } - - c.JSON(http.StatusOK, skip) - - return - } - - // check if the pipeline did not already exist in the database - if pipeline == nil { - pipeline = compiled - pipeline.SetRepoID(r.GetID()) - pipeline.SetCommit(b.GetCommit()) - pipeline.SetRef(b.GetRef()) - - // send API call to create the pipeline - pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline) - if err != nil { - retErr := fmt.Errorf("unable to create pipeline for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - } - - b.SetPipelineID(pipeline.GetID()) - - // create the objects from the pipeline in the database - err = PlanBuild(ctx, database.FromContext(c), scm.FromContext(c), p, b, r) - if err != nil { - util.HandleError(c, http.StatusInternalServerError, err) - - return - } - - // send API call to update repo for ensuring counter is incremented - r, err = database.FromContext(c).UpdateRepo(ctx, r) - if err != nil { - retErr := fmt.Errorf("unable to restart build: failed to update repo %s: %w", r.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // send API call to capture the restarted build - b, _ = database.FromContext(c).GetBuildForRepo(ctx, r, b.GetNumber()) - - c.JSON(http.StatusCreated, b) - - // if the event is a deployment, update the build list - if strings.EqualFold(b.GetEvent(), constants.EventDeploy) { - d, err := database.FromContext(c).GetDeploymentForRepo(c, r, b.GetDeployNumber()) - if err != nil { - logger.Errorf("unable to set get deployment for build %s: %v", entry, err) - } - build := append(d.GetBuilds(), b) + b.SetSenderSCMID(senderID) - d.SetBuilds(build) + // parent to the previous build + b.SetParent(b.GetNumber()) - _, err = database.FromContext(c).UpdateDeployment(ctx, d) - if err != nil { - logger.Errorf("unable to set update deployment for build %s: %v", entry, err) - } - } + l.Debugf("generating queue items for build %s", entry) - // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, b, r.GetOrg(), r.GetName()) - if err != nil { - logger.Errorf("unable to set commit status for build %s: %v", entry, err) + // restart form + config := CompileAndPublishConfig{ + Build: b, + Metadata: m, + BaseErr: "unable to restart build", + Source: "restart", + Retries: 1, } - // determine queue route - route, err := queue.FromContext(c).Route(&p.Worker) + // generate queue items + _, item, code, err := CompileAndPublish( + c, + config, + database.FromContext(c), + scm, + compiler.FromContext(c), + queue.FromContext(c), + ) if err != nil { - logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, database.FromContext(c), b, nil, nil, err) + util.HandleError(c, code, err) return } - // temporarily set host to the route before it gets picked up by a worker - b.SetHost(route) + l.WithFields(logrus.Fields{ + "new_build": item.Build.GetNumber(), + "new_build_id": item.Build.GetID(), + }).Info("build created via restart") - err = PublishBuildExecutable(ctx, database.FromContext(c), p, b) - if err != nil { - retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } + c.JSON(http.StatusCreated, item.Build) // publish the build to the queue - go PublishToQueue( + go Enqueue( ctx, queue.FromGinContext(c), database.FromContext(c), - b, - r, - u, - route, + item, + item.Build.GetHost(), ) } diff --git a/api/build/skip.go b/api/build/skip.go index 0987e99f6..8e8965c57 100644 --- a/api/build/skip.go +++ b/api/build/skip.go @@ -8,8 +8,6 @@ import ( // SkipEmptyBuild checks if the build should be skipped due to it // not containing any steps besides init or clone. -// -//nolint:goconst // ignore init and clone constants func SkipEmptyBuild(p *pipeline.Build) string { if len(p.Stages) == 1 { if p.Stages[0].Name == "init" { diff --git a/api/build/token.go b/api/build/token.go index 7365a72d4..b2126bb84 100644 --- a/api/build/token.go +++ b/api/build/token.go @@ -9,15 +9,15 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/token builds GetBuildToken @@ -29,13 +29,13 @@ import ( // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path @@ -51,7 +51,11 @@ import ( // schema: // "$ref": "#/definitions/Token" // '400': -// description: Bad request +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '409': @@ -59,27 +63,19 @@ import ( // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to generate build token +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // GetBuildToken represents the API handler to generate a build token. func GetBuildToken(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) cl := claims.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": cl.Subject, - }).Infof("generating build token for build %s/%d", r.GetFullName(), b.GetNumber()) + l.Infof("generating build token for build %s/%d", r.GetFullName(), b.GetNumber()) // if build is not in a pending state, then a build token should not be needed - conflict if !strings.EqualFold(b.GetStatus(), constants.StatusPending) { @@ -98,7 +94,7 @@ func GetBuildToken(c *gin.Context) { // set mint token options bmto := &token.MintTokenOpts{ Hostname: cl.Subject, - BuildID: b.GetID(), + Build: b, Repo: r.GetFullName(), TokenType: constants.WorkerBuildTokenType, TokenDuration: exp, diff --git a/api/build/update.go b/api/build/update.go index c9fb30aa8..eabe94981 100644 --- a/api/build/update.go +++ b/api/build/update.go @@ -7,21 +7,21 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build} builds UpdateBuild // -// Updates a build in the configured backend +// Update a build // // --- // produces: @@ -29,22 +29,22 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path // name: build -// description: Build number to update +// description: Build number // required: true // type: integer // - in: body // name: body -// description: Payload containing the build to update +// description: The build object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Build" @@ -55,39 +55,38 @@ import ( // description: Successfully updated the build // schema: // "$ref": "#/definitions/Build" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to update the build +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateBuild represents the API handler to update -// a build for a repo in the configured backend. +// a build for a repo. func UpdateBuild(c *gin.Context) { // capture middleware values - cl := claims.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": cl.Subject, - }).Infof("updating build %s", entry) + l.Debugf("updating build %s", entry) // capture body from API request - input := new(library.Build) + input := new(types.Build) err := c.Bind(input) if err != nil { @@ -168,24 +167,29 @@ func UpdateBuild(c *gin.Context) { b.GetStatus() == constants.StatusCanceled || b.GetStatus() == constants.StatusKilled || b.GetStatus() == constants.StatusError) && b.GetEvent() != constants.EventSchedule { - // send API call to capture the repo owner - u, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - logrus.Errorf("unable to get owner for build %s: %v", entry, err) - } - // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, b, r.GetOrg(), r.GetName()) + err = scm.FromContext(c).Status(ctx, r.GetOwner(), b, r.GetOrg(), r.GetName()) if err != nil { - logrus.Errorf("unable to set commit status for build %s: %v", entry, err) + l.Errorf("unable to set commit status for build %s: %v", entry, err) } } } // UpdateComponentStatuses updates all components (steps and services) for a build to a given status. -func UpdateComponentStatuses(c *gin.Context, b *library.Build, status string) error { +func UpdateComponentStatuses(c *gin.Context, b *types.Build, status string) error { + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l = l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }) + + l.Debug("updating component statuses") + // retrieve the steps for the build from the step table steps := []*library.Step{} page := 1 @@ -218,6 +222,11 @@ func UpdateComponentStatuses(c *gin.Context, b *library.Build, status string) er if err != nil { return err } + + l.WithFields(logrus.Fields{ + "step": step.GetNumber(), + "step_id": step.GetID(), + }).Infof("step status updated") } // retrieve the services for the build from the service table @@ -251,6 +260,11 @@ func UpdateComponentStatuses(c *gin.Context, b *library.Build, status string) er if err != nil { return err } + + l.WithFields(logrus.Fields{ + "service": service.GetNumber(), + "service_id": service.GetID(), + }).Info("service status updated") } return nil diff --git a/api/dashboard/create.go b/api/dashboard/create.go new file mode 100644 index 000000000..104a59074 --- /dev/null +++ b/api/dashboard/create.go @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// swagger:operation POST /api/v1/dashboards dashboards CreateDashboard +// +// Create a dashboard +// +// --- +// produces: +// - application/json +// parameters: +// - in: body +// name: body +// description: Dashboard object to create +// required: true +// schema: +// "$ref": "#/definitions/Dashboard" +// security: +// - ApiKeyAuth: [] +// responses: +// '201': +// description: Successfully created dashboard +// schema: +// "$ref": "#/definitions/Dashboard" +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// CreateDashboard represents the API handler to +// create a dashboard. +func CreateDashboard(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + u := user.Retrieve(c) + + // capture body from API request + input := new(types.Dashboard) + + err := c.Bind(input) + if err != nil { + retErr := fmt.Errorf("unable to decode JSON for new dashboard: %w", err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // ensure dashboard name is defined + if input.GetName() == "" { + util.HandleError(c, http.StatusBadRequest, fmt.Errorf("dashboard name must be set")) + + return + } + + l.Debugf("creating new dashboard %s", input.GetName()) + + d := new(types.Dashboard) + + // update fields in dashboard object + d.SetCreatedBy(u.GetName()) + d.SetName(input.GetName()) + d.SetCreatedAt(time.Now().UTC().Unix()) + d.SetUpdatedAt(time.Now().UTC().Unix()) + d.SetUpdatedBy(u.GetName()) + + // validate admins to ensure they are all active users + admins, err := createAdminSet(c, u, input.GetAdmins()) + if err != nil { + util.HandleError(c, http.StatusBadRequest, err) + + return + } + + d.SetAdmins(admins) + + // validate repos to ensure they are all enabled + err = validateRepoSet(c, input.GetRepos()) + if err != nil { + util.HandleError(c, http.StatusBadRequest, err) + + return + } + + d.SetRepos(input.GetRepos()) + + // create dashboard in database + d, err = database.FromContext(c).CreateDashboard(c, d) + if err != nil { + retErr := fmt.Errorf("unable to create new dashboard %s: %w", d.GetName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + l.WithFields(logrus.Fields{ + "dashboard": d.GetName(), + "dashboard_id": d.GetID(), + }).Info("dashboard created") + + // add dashboard to claims' user's dashboard set + u.SetDashboards(append(u.GetDashboards(), d.GetID())) + + // update user in database + _, err = database.FromContext(c).UpdateUser(c, u) + if err != nil { + retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + l.Infof("user updated with new dashboard %s", d.GetName()) + + c.JSON(http.StatusCreated, d) +} + +// createAdminSet takes a slice of users, cleanses it of duplicates and throws an error +// when a user is inactive or not found in the database. It returns a sanitized slice of admins. +func createAdminSet(c context.Context, caller *types.User, users []*types.User) ([]*types.User, error) { + // add user creating the dashboard to admin list + admins := []*types.User{caller.Crop()} + + dupMap := make(map[string]bool) + + // validate supplied admins are actual users + for _, u := range users { + if u.GetName() == caller.GetName() || dupMap[u.GetName()] { + continue + } + + dbUser, err := database.FromContext(c).GetUserForName(c, u.GetName()) + if err != nil || !dbUser.GetActive() { + return nil, fmt.Errorf("unable to create dashboard: %s is not an active user", u.GetName()) + } + + admins = append(admins, dbUser.Crop()) + + dupMap[dbUser.GetName()] = true + } + + return admins, nil +} + +// validateRepoSet is a helper function that confirms all dashboard repos exist and are enabled +// in the database while also confirming the IDs match when saving. +func validateRepoSet(c context.Context, repos []*types.DashboardRepo) error { + for _, repo := range repos { + // verify format (org/repo) + parts := strings.Split(repo.GetName(), "/") + if len(parts) != 2 { + return fmt.Errorf("unable to create dashboard: %s is not a valid repo", repo.GetName()) + } + + // fetch repo from database + dbRepo, err := database.FromContext(c).GetRepoForOrg(c, parts[0], parts[1]) + if err != nil || !dbRepo.GetActive() { + return fmt.Errorf("unable to create dashboard: could not get repo %s: %w", repo.GetName(), err) + } + + // override ID field if provided to match the database ID + repo.SetID(dbRepo.GetID()) + } + + return nil +} diff --git a/api/dashboard/delete.go b/api/dashboard/delete.go new file mode 100644 index 000000000..05b14e86b --- /dev/null +++ b/api/dashboard/delete.go @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/dashboard" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// swagger:operation DELETE /api/v1/dashboards/{dashboard} dashboards DeleteDashboard +// +// Delete a dashboard +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: dashboard +// description: Dashboard ID +// required: true +// type: string +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully deleted dashboard +// schema: +// type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// DeleteDashboard represents the API handler to remove a dashboard. +func DeleteDashboard(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + d := dashboard.Retrieve(c) + u := user.Retrieve(c) + + l.Debugf("deleting dashboard %s", d.GetID()) + + if !isAdmin(d, u) { + retErr := fmt.Errorf("unable to delete dashboard %s: user is not an admin", d.GetID()) + + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + + err := database.FromContext(c).DeleteDashboard(c, d) + if err != nil { + retErr := fmt.Errorf("error while deleting dashboard %s: %w", d.GetID(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, fmt.Sprintf("dashboard %s deleted", d.GetName())) +} + +// isAdmin is a helper function that iterates through the dashboard admins +// and confirms if the user is in the slice. +func isAdmin(d *types.Dashboard, u *types.User) bool { + for _, admin := range d.GetAdmins() { + if admin.GetID() == u.GetID() { + return true + } + } + + return false +} diff --git a/api/dashboard/doc.go b/api/dashboard/doc.go new file mode 100644 index 000000000..eb9f60c2d --- /dev/null +++ b/api/dashboard/doc.go @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package dashboard provides the dashboard handlers for the Vela API. +// +// Usage: +// +// import "github.com/go-vela/server/api/dashboard" +package dashboard diff --git a/api/dashboard/get.go b/api/dashboard/get.go new file mode 100644 index 000000000..c009e4127 --- /dev/null +++ b/api/dashboard/get.go @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/dashboard" + "github.com/go-vela/server/util" +) + +// swagger:operation GET /api/v1/dashboards/{dashboard} dashboards GetDashboard +// +// Get a dashboard +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: dashboard +// description: Dashboard id to retrieve +// required: true +// type: string +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved dashboard +// type: json +// schema: +// "$ref": "#/definitions/Dashboard" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// GetDashboard represents the API handler to get +// a dashboard for a repository. +func GetDashboard(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + d := dashboard.Retrieve(c) + + var err error + + l.Debugf("reading dashboard %s", d.GetID()) + + // initialize DashCard and set dashboard to the dashboard info pulled from database + dashboard := new(types.DashCard) + dashboard.Dashboard = d + + // build RepoPartials referenced in the dashboard + dashboard.Repos, err = buildRepoPartials(c, d.GetRepos()) + if err != nil { + util.HandleError(c, http.StatusInternalServerError, err) + + return + } + + c.JSON(http.StatusOK, dashboard) +} + +// buildRepoPartials is a helper function which takes the dashboard repo list +// and builds a list of RepoPartials with information about the associated +// repository and its latest five builds. +func buildRepoPartials(c context.Context, repos []*types.DashboardRepo) ([]types.RepoPartial, error) { + var result []types.RepoPartial + + for _, r := range repos { + repo := types.RepoPartial{} + + // fetch repo from database + dbRepo, err := database.FromContext(c).GetRepo(c, r.GetID()) + if err != nil { + return nil, fmt.Errorf("unable to get repo %s for dashboard: %w", r.GetName(), err) + } + + // set values for RepoPartial + repo.Org = dbRepo.GetOrg() + repo.Name = dbRepo.GetName() + repo.Counter = dbRepo.GetCounter() + repo.Active = dbRepo.GetActive() + + // list last 5 builds for repo given the branch and event filters + builds, err := database.FromContext(c).ListBuildsForDashboardRepo(c, dbRepo, r.GetBranches(), r.GetEvents()) + if err != nil { + return nil, fmt.Errorf("unable to list builds for repo %s in dashboard: %w", dbRepo.GetFullName(), err) + } + + bPartials := []types.BuildPartial{} + + // populate BuildPartials with info from builds list + for _, build := range builds { + bPartial := types.BuildPartial{ + Number: build.GetNumber(), + Status: build.GetStatus(), + Started: build.GetStarted(), + Finished: build.GetFinished(), + Sender: build.GetSender(), + Branch: build.GetBranch(), + Event: build.GetEvent(), + Link: build.GetLink(), + } + + bPartials = append(bPartials, bPartial) + } + + repo.Builds = bPartials + + result = append(result, repo) + } + + return result, nil +} diff --git a/api/dashboard/list_user.go b/api/dashboard/list_user.go new file mode 100644 index 000000000..d2e8e300a --- /dev/null +++ b/api/dashboard/list_user.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// swagger:operation GET /api/v1/user/dashboards dashboards ListUserDashboards +// +// Get all dashboards for the current user +// +// --- +// produces: +// - application/json +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved user dashboards +// schema: +// type: array +// items: +// "$ref": "#/definitions/DashCard" +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// ListUserDashboards represents the API handler to capture a list +// of dashboards for a user. +func ListUserDashboards(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + u := user.Retrieve(c) + + l.Debugf("listing dashboards for user %s", u.GetName()) + + var dashCards []types.DashCard + + // iterate through user dashboards and build a list of DashCards + for _, dashboard := range u.GetDashboards() { + dashCard := types.DashCard{} + + d, err := database.FromContext(c).GetDashboard(c, dashboard) + if err != nil { + // check if the query returned a record not found error + if errors.Is(err, gorm.ErrRecordNotFound) { + d = new(types.Dashboard) + d.SetID(dashboard) + + dashCard.Dashboard = d + // if user dashboard has been deleted, append empty dashboard + // to set and continue + dashCards = append(dashCards, dashCard) + + continue + } + + retErr := fmt.Errorf("unable to get dashboard %s: %w", dashboard, err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + dashCard.Dashboard = d + + dashCard.Repos, err = buildRepoPartials(c, d.GetRepos()) + if err != nil { + util.HandleError(c, http.StatusInternalServerError, err) + + return + } + + dashCards = append(dashCards, dashCard) + } + + c.JSON(http.StatusOK, dashCards) +} diff --git a/api/dashboard/update.go b/api/dashboard/update.go new file mode 100644 index 000000000..aec6affa4 --- /dev/null +++ b/api/dashboard/update.go @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/dashboard" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// swagger:operation PUT /api/v1/dashboards/{dashboard} dashboards UpdateDashboard +// +// Update a dashboard +// +// --- +// produces: +// - application/json +// parameters: +// - name: dashboard +// in: path +// description: ID of the dashboard +// required: true +// type: string +// - name: body +// in: body +// description: The dashboard object with the fields to be updated +// required: true +// schema: +// $ref: '#/definitions/Dashboard' +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully updated dashboard +// schema: +// "$ref": "#/definitions/Dashboard" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// UpdateDashboard represents the API handler to update a dashboard. +func UpdateDashboard(c *gin.Context) { + // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) + d := dashboard.Retrieve(c) + u := user.Retrieve(c) + + l.Debugf("updating dashboard %s", d.GetID()) + + if !isAdmin(d, u) { + retErr := fmt.Errorf("unable to update dashboard %s: user is not an admin", d.GetID()) + + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + + // capture body from API request + input := new(types.Dashboard) + + err := c.Bind(input) + if err != nil { + retErr := fmt.Errorf("unable to decode JSON for dashboard %s: %w", d.GetID(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + if input.GetName() != "" { + // update name if defined + d.SetName(input.GetName()) + } + + // validate admin set if supplied + if len(input.GetAdmins()) > 0 { + admins, err := createAdminSet(c, u, input.GetAdmins()) + if err != nil { + util.HandleError(c, http.StatusBadRequest, err) + + return + } + + d.SetAdmins(admins) + } + + // set the updated by field using claims + d.SetUpdatedBy(u.GetName()) + + // validate repo set if supplied + if len(input.GetRepos()) > 0 { + // validate supplied repo list + err = validateRepoSet(c, input.GetRepos()) + if err != nil { + util.HandleError(c, http.StatusBadRequest, err) + + return + } + + d.SetRepos(input.GetRepos()) + } + + // update the dashboard within the database + d, err = database.FromContext(c).UpdateDashboard(c, d) + if err != nil { + retErr := fmt.Errorf("unable to update dashboard %s: %w", input.GetID(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, d) +} diff --git a/api/deployment/create.go b/api/deployment/create.go index 4f5d0faf1..fa980e5e1 100644 --- a/api/deployment/create.go +++ b/api/deployment/create.go @@ -8,19 +8,19 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/deployments/{org}/{repo} deployments CreateDeployment // -// Create a deployment for the configured backend +// Create a deployment // // --- // produces: @@ -28,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: @@ -44,31 +44,32 @@ import ( // schema: // "$ref": "#/definitions/Deployment" // '400': -// description: Unable to create the deployment +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the deployment +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateDeployment represents the API handler to -// create a deployment in the configured backend. +// create a deployment. func CreateDeployment(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("creating new deployment for repo %s", r.GetFullName()) + l.Debugf("creating new deployment for repo %s", r.GetFullName()) // capture body from API request input := new(library.Deployment) @@ -120,5 +121,9 @@ func CreateDeployment(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "deployment_id": d.GetID(), + }).Info("deployment created") + c.JSON(http.StatusCreated, d) } diff --git a/api/deployment/get.go b/api/deployment/get.go index 72456e79a..5c978cf64 100644 --- a/api/deployment/get.go +++ b/api/deployment/get.go @@ -8,18 +8,18 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/deployments/{org}/{repo}/{deployment} deployments GetDeployment // -// Get a deployment from the configured backend +// Get a deployment // // --- // produces: @@ -27,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -48,19 +48,26 @@ import ( // schema: // "$ref": "#/definitions/Deployment" // '400': -// description: Unable to retrieve the deployment +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the deployment +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetDeployment represents the API handler to -// capture a deployment from the configured backend. +// GetDeployment represents the API handler to get a deployment. func GetDeployment(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) deployment := util.PathParameter(c, "deployment") @@ -68,14 +75,7 @@ func GetDeployment(c *gin.Context) { entry := fmt.Sprintf("%s/%s", r.GetFullName(), deployment) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading deployment %s", entry) + l.Debugf("reading deployment %s", entry) number, err := strconv.Atoi(deployment) if err != nil { diff --git a/api/deployment/list.go b/api/deployment/list.go index 8b8c24798..34f4c74a2 100644 --- a/api/deployment/list.go +++ b/api/deployment/list.go @@ -8,18 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/deployments/{org}/{repo} deployments ListDeployments // -// Get a list of deployments for the configured backend +// Get all deployments for a repository // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: query @@ -60,33 +59,32 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of deployments +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of deployments +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListDeployments represents the API handler to capture -// a list of deployments from the configured backend. +// ListDeployments represents the API handler to get a list of deployments. func ListDeployments(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading deployments for repo %s", r.GetFullName()) + + l.Debugf("reading deployments for repo %s", r.GetFullName()) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/health.go b/api/health.go index 71c8eacf4..12497408d 100644 --- a/api/health.go +++ b/api/health.go @@ -10,7 +10,7 @@ import ( // swagger:operation GET /health base Health // -// Check if the Vela API is available +// Check the Vela API health // // --- // produces: diff --git a/api/hook/create.go b/api/hook/create.go index 569e43664..67eb86789 100644 --- a/api/hook/create.go +++ b/api/hook/create.go @@ -8,39 +8,38 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/hooks/{org}/{repo} webhook CreateHook // -// Create a webhook for the configured backend +// Create a hook // // --- // produces: // - application/json // parameters: -// - in: body -// name: body -// description: Webhook payload that we expect from the user or VCS -// required: true -// schema: -// "$ref": "#/definitions/Webhook" // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string +// - in: body +// name: body +// description: Hook object from the user or VCS to create +// required: true +// schema: +// "$ref": "#/definitions/Webhook" // security: // - ApiKeyAuth: [] // responses: @@ -49,31 +48,30 @@ import ( // schema: // "$ref": "#/definitions/Webhook" // '400': -// description: The webhook was unable to be created +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: The webhook was unable to be created +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CreateHook represents the API handler to create -// a webhook in the configured backend. +// CreateHook represents the API handler to create a webhook. func CreateHook(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("creating new hook for repo %s", r.GetFullName()) + l.Debugf("creating new hook for repo %s", r.GetFullName()) // capture body from API request input := new(library.Hook) @@ -121,5 +119,10 @@ func CreateHook(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "hook": h.GetNumber(), + "hook_id": h.GetID(), + }).Info("hook created") + c.JSON(http.StatusCreated, h) } diff --git a/api/hook/delete.go b/api/hook/delete.go index 86ef22312..a67f35263 100644 --- a/api/hook/delete.go +++ b/api/hook/delete.go @@ -5,20 +5,19 @@ package hook import ( "fmt" "net/http" - "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/hook" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/hooks/{org}/{repo}/{hook} webhook DeleteHook // -// Delete a webhook for the configured backend +// Delete a hook // // --- // produces: @@ -26,12 +25,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -47,63 +46,38 @@ import ( // schema: // type: string // '400': -// description: The webhook was unable to be deleted +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: The webhook was unable to be deleted +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: The webhook was unable to be deleted +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteHook represents the API handler to remove -// a webhook from the configured backend. +// DeleteHook represents the API handler to remove a webhook. func DeleteHook(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) - hook := util.PathParameter(c, "hook") + h := hook.Retrieve(c) ctx := c.Request.Context() - entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "hook": hook, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting hook %s", entry) - - number, err := strconv.Atoi(hook) - if err != nil { - retErr := fmt.Errorf("invalid hook parameter provided: %s", hook) - - util.HandleError(c, http.StatusBadRequest, retErr) + entry := fmt.Sprintf("%s/%d", r.GetFullName(), h.GetNumber()) - return - } - - // send API call to capture the webhook - h, err := database.FromContext(c).GetHookForRepo(ctx, r, number) - if err != nil { - retErr := fmt.Errorf("unable to get hook %s: %w", hook, err) - - util.HandleError(c, http.StatusNotFound, retErr) - - return - } + l.Debugf("deleting hook %s", entry) // send API call to remove the webhook - err = database.FromContext(c).DeleteHook(ctx, h) + err := database.FromContext(c).DeleteHook(ctx, h) if err != nil { - retErr := fmt.Errorf("unable to delete hook %s: %w", hook, err) + retErr := fmt.Errorf("unable to delete hook %s: %w", entry, err) util.HandleError(c, http.StatusInternalServerError, retErr) diff --git a/api/hook/doc.go b/api/hook/doc.go new file mode 100644 index 000000000..828826073 --- /dev/null +++ b/api/hook/doc.go @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package hook provides the hook handlers for the Vela API. +// +// Usage: +// +// import "github.com/go-vela/server/api/hook" +package hook diff --git a/api/hook/get.go b/api/hook/get.go index d3ff18793..d31e6e659 100644 --- a/api/hook/get.go +++ b/api/hook/get.go @@ -3,22 +3,18 @@ package hook import ( - "fmt" "net/http" - "strconv" "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" - "github.com/go-vela/server/util" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/router/middleware/hook" + "github.com/go-vela/server/router/middleware/repo" ) // swagger:operation GET /api/v1/hooks/{org}/{repo}/{hook} webhook GetHook // -// Retrieve a webhook for the configured backend +// Get a hook // // --- // produces: @@ -26,12 +22,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -47,54 +43,30 @@ import ( // schema: // "$ref": "#/definitions/Webhook" // '400': -// description: Unable to retrieve the webhook +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the webhook +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetHook represents the API handler to capture a -// webhook from the configured backend. +// GetHook represents the API handler to get a hook. func GetHook(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) - hook := util.PathParameter(c, "hook") - ctx := c.Request.Context() - - entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "hook": hook, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading hook %s", entry) - - number, err := strconv.Atoi(hook) - if err != nil { - retErr := fmt.Errorf("invalid hook parameter provided: %s", hook) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // send API call to capture the webhook - h, err := database.FromContext(c).GetHookForRepo(ctx, r, number) - if err != nil { - retErr := fmt.Errorf("unable to get hook %s: %w", entry, err) - - util.HandleError(c, http.StatusInternalServerError, retErr) + h := hook.Retrieve(c) - return - } + l.Debugf("reading hook %s/%d", r.GetFullName(), h.GetNumber()) c.JSON(http.StatusOK, h) } diff --git a/api/hook/list.go b/api/hook/list.go index 902cd33cc..d2e65d1ac 100644 --- a/api/hook/list.go +++ b/api/hook/list.go @@ -8,18 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/hooks/{org}/{repo} webhook ListHooks // -// Retrieve the webhooks for the configured backend +// Get all hooks for a repository // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: query @@ -60,34 +59,34 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve webhooks +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve webhooks +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListHooks represents the API handler to capture a list -// of webhooks from the configured backend. +// ListHooks represents the API handler to get all hooks +// for a repository. func ListHooks(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading hooks for repo %s", r.GetFullName()) + l.Debugf("reading hooks for repo %s", r.GetFullName()) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/hook/redeliver.go b/api/hook/redeliver.go index 50f68e6eb..13ec3f57a 100644 --- a/api/hook/redeliver.go +++ b/api/hook/redeliver.go @@ -5,21 +5,20 @@ package hook import ( "fmt" "net/http" - "strconv" "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/router/middleware/hook" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/hooks/{org}/{repo}/{hook}/redeliver webhook RedeliverHook // -// Redeliver a webhook from the SCM +// Redeliver a hook // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -46,17 +45,21 @@ import ( // '200': // description: Successfully redelivered the webhook // schema: -// "$ref": "#/definitions/Webhook" +// type: string // '400': -// description: The webhook was unable to be redelivered +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: The webhook was unable to be redelivered +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: The webhook was unable to be redelivered +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -64,44 +67,16 @@ import ( // a webhook from the SCM. func RedeliverHook(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) - hook := util.PathParameter(c, "hook") - ctx := c.Request.Context() - - entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "hook": hook, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("redelivering hook %s", entry) - - number, err := strconv.Atoi(hook) - if err != nil { - retErr := fmt.Errorf("invalid hook parameter provided: %s", hook) + h := hook.Retrieve(c) - util.HandleError(c, http.StatusBadRequest, retErr) + entry := fmt.Sprintf("%s/%d", r.GetFullName(), h.GetNumber()) - return - } - - // send API call to capture the webhook - h, err := database.FromContext(c).GetHookForRepo(ctx, r, number) - if err != nil { - retErr := fmt.Errorf("unable to get hook %s: %w", entry, err) - - util.HandleError(c, http.StatusNotFound, retErr) - - return - } + l.Debugf("redelivering hook %s", entry) - err = scm.FromContext(c).RedeliverWebhook(c, u, r, h) + err := scm.FromContext(c).RedeliverWebhook(c, u, r, h) if err != nil { retErr := fmt.Errorf("unable to redeliver hook %s: %w", entry, err) diff --git a/api/hook/update.go b/api/hook/update.go index 74954dde1..75492e45a 100644 --- a/api/hook/update.go +++ b/api/hook/update.go @@ -5,21 +5,20 @@ package hook import ( "fmt" "net/http" - "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/hook" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/hooks/{org}/{repo}/{hook} webhook UpdateHook // -// Update a webhook for the configured backend +// Update a hook // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -54,39 +53,33 @@ import ( // schema: // "$ref": "#/definitions/Webhook" // '400': -// description: The webhook was unable to be updated +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: The webhook was unable to be updated +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: The webhook was unable to be updated +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateHook represents the API handler to update -// a webhook in the configured backend. +// UpdateHook represents the API handler to update a hook. func UpdateHook(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) - hook := util.PathParameter(c, "hook") + h := hook.Retrieve(c) ctx := c.Request.Context() - entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook) + entry := fmt.Sprintf("%s/%d", r.GetFullName(), h.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "hook": hook, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("updating hook %s", entry) + l.Debugf("updating hook %s", entry) // capture body from API request input := new(library.Hook) @@ -100,25 +93,6 @@ func UpdateHook(c *gin.Context) { return } - number, err := strconv.Atoi(hook) - if err != nil { - retErr := fmt.Errorf("invalid hook parameter provided: %s", hook) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // send API call to capture the webhook - h, err := database.FromContext(c).GetHookForRepo(ctx, r, number) - if err != nil { - retErr := fmt.Errorf("unable to get hook %s: %w", entry, err) - - util.HandleError(c, http.StatusNotFound, retErr) - - return - } - // update webhook fields if provided if input.GetCreated() > 0 { // update created if set diff --git a/api/jwks.go b/api/jwks.go new file mode 100644 index 000000000..166ccc15d --- /dev/null +++ b/api/jwks.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" + "github.com/go-vela/server/util" +) + +// swagger:operation GET /_services/token/.well-known/jwks token GetJWKS +// +// Get the JWKS for the Vela OIDC service +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved the Vela JWKS +// schema: +// "$ref": "#/definitions/JWKSet" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" + +// GetJWKS represents the API handler for requests to public keys in the Vela OpenID service. +func GetJWKS(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + + l.Debug("reading JWKS") + + // retrieve JWKs from the database + keys, err := database.FromContext(c).ListJWKs(c) + if err != nil { + retErr := fmt.Errorf("unable to get key set: %w", err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, keys) +} diff --git a/api/log/create_service.go b/api/log/create_service.go index fb188aead..736f1fca5 100644 --- a/api/log/create_service.go +++ b/api/log/create_service.go @@ -8,15 +8,14 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services CreateServiceLog @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -60,37 +59,35 @@ import ( // '201': // description: Successfully created the service logs // '400': -// description: Unable to create the service logs +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the service logs +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateServiceLog represents the API handler to create -// the logs for a service in the configured backend. +// the logs for a service. func CreateServiceLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("creating logs for service %s", entry) + l.Debugf("creating logs for service %s", entry) // capture body from API request input := new(library.Log) @@ -119,5 +116,10 @@ func CreateServiceLog(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + }).Info("logs created for service") + c.JSON(http.StatusCreated, nil) } diff --git a/api/log/create_step.go b/api/log/create_step.go index 19c8030ef..2a92abab7 100644 --- a/api/log/create_step.go +++ b/api/log/create_step.go @@ -8,15 +8,14 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps CreateStepLog @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -60,37 +59,35 @@ import ( // '201': // description: Successfully created the logs for step // '400': -// description: Unable to create the logs for a step +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the logs for a step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateStepLog represents the API handler to create -// the logs for a step in the configured backend. +// the logs for a step. func CreateStepLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("creating logs for step %s", entry) + l.Debugf("creating logs for step %s", entry) // capture body from API request input := new(library.Log) @@ -119,5 +116,10 @@ func CreateStepLog(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "step": s.GetName(), + "step_id": s.GetID(), + }).Info("logs created for step") + c.JSON(http.StatusCreated, nil) } diff --git a/api/log/delete_service.go b/api/log/delete_service.go index 788fe1f8d..598874def 100644 --- a/api/log/delete_service.go +++ b/api/log/delete_service.go @@ -8,14 +8,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services DeleteServiceLog @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -53,37 +52,39 @@ import ( // description: Successfully deleted the service logs // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the service logs +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // DeleteServiceLog represents the API handler to remove -// the logs for a service from the configured backend. +// the logs for a service. func DeleteServiceLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("deleting logs for service %s", entry) + l.Debugf("deleting logs for service %s", entry) // send API call to capture the service logs - l, err := database.FromContext(c).GetLogForService(ctx, s) + sl, err := database.FromContext(c).GetLogForService(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err) @@ -93,7 +94,7 @@ func DeleteServiceLog(c *gin.Context) { } // send API call to remove the log - err = database.FromContext(c).DeleteLog(ctx, l) + err = database.FromContext(c).DeleteLog(ctx, sl) if err != nil { retErr := fmt.Errorf("unable to delete logs for service %s: %w", entry, err) diff --git a/api/log/delete_step.go b/api/log/delete_step.go index 7209637ff..28b8b6cd5 100644 --- a/api/log/delete_step.go +++ b/api/log/delete_step.go @@ -8,14 +8,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps DeleteStepLog @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -53,37 +52,39 @@ import ( // description: Successfully deleted the logs for the step // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the logs for the step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // DeleteStepLog represents the API handler to remove -// the logs for a step from the configured backend. +// the logs for a step. func DeleteStepLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("deleting logs for step %s", entry) + l.Debugf("deleting logs for step %s", entry) // send API call to capture the step logs - l, err := database.FromContext(c).GetLogForStep(ctx, s) + sl, err := database.FromContext(c).GetLogForStep(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err) @@ -93,7 +94,7 @@ func DeleteStepLog(c *gin.Context) { } // send API call to remove the log - err = database.FromContext(c).DeleteLog(ctx, l) + err = database.FromContext(c).DeleteLog(ctx, sl) if err != nil { retErr := fmt.Errorf("unable to delete logs for step %s: %w", entry, err) diff --git a/api/log/get_service.go b/api/log/get_service.go index 62500eb3c..de223da08 100644 --- a/api/log/get_service.go +++ b/api/log/get_service.go @@ -8,19 +8,18 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services GetServiceLog // -// Retrieve the logs for a service +// Get the logs for a service // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -53,37 +52,38 @@ import ( // description: Successfully retrieved the service logs // schema: // "$ref": "#/definitions/Log" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the service logs +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetServiceLog represents the API handler to capture -// the logs for a service from the configured backend. +// GetServiceLog represents the API handler to get the logs for a service. func GetServiceLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("reading logs for service %s", entry) + l.Debugf("reading logs for service %s", entry) // send API call to capture the service logs - l, err := database.FromContext(c).GetLogForService(ctx, s) + sl, err := database.FromContext(c).GetLogForService(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err) @@ -92,5 +92,5 @@ func GetServiceLog(c *gin.Context) { return } - c.JSON(http.StatusOK, l) + c.JSON(http.StatusOK, sl) } diff --git a/api/log/get_step.go b/api/log/get_step.go index 2e2798918..1bf6c4191 100644 --- a/api/log/get_step.go +++ b/api/log/get_step.go @@ -8,19 +8,18 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps GetStepLog // -// Retrieve the logs for a step +// Get the logs for a step // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -54,37 +53,38 @@ import ( // type: json // schema: // "$ref": "#/definitions/Log" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the logs for a step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetStepLog represents the API handler to capture -// the logs for a step from the configured backend. +// GetStepLog represents the API handler to get the logs for a step. func GetStepLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("reading logs for step %s", entry) + l.Debugf("reading logs for step %s", entry) // send API call to capture the step logs - l, err := database.FromContext(c).GetLogForStep(ctx, s) + sl, err := database.FromContext(c).GetLogForStep(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err) @@ -93,5 +93,5 @@ func GetStepLog(c *gin.Context) { return } - c.JSON(http.StatusOK, l) + c.JSON(http.StatusOK, sl) } diff --git a/api/log/list_build.go b/api/log/list_build.go index 6cb2e5130..72d4ce7ab 100644 --- a/api/log/list_build.go +++ b/api/log/list_build.go @@ -8,19 +8,18 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/logs builds ListLogsForBuild // -// List logs for a build in the configured backend +// Get all logs for a build // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -61,32 +60,34 @@ import ( // type: array // items: // "$ref": "#/definitions/Log" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve logs for the build +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListLogsForBuild represents the API handler to capture a -// list of logs for a build from the configured backend. +// ListLogsForBuild represents the API handler to get a list of logs for a build. func ListLogsForBuild(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("listing logs for build %s", entry) + l.Debugf("listing logs for build %s", entry) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) @@ -111,7 +112,7 @@ func ListLogsForBuild(c *gin.Context) { perPage = util.MaxInt(1, util.MinInt(100, perPage)) // send API call to capture the list of logs for the build - l, t, err := database.FromContext(c).ListLogsForBuild(ctx, b, page, perPage) + bl, t, err := database.FromContext(c).ListLogsForBuild(ctx, b, page, perPage) if err != nil { retErr := fmt.Errorf("unable to list logs for build %s: %w", entry, err) @@ -129,5 +130,5 @@ func ListLogsForBuild(c *gin.Context) { // set pagination headers pagination.SetHeaderLink(c) - c.JSON(http.StatusOK, l) + c.JSON(http.StatusOK, bl) } diff --git a/api/log/update_service.go b/api/log/update_service.go index 7e1ca5a5b..dd8b8329d 100644 --- a/api/log/update_service.go +++ b/api/log/update_service.go @@ -8,20 +8,19 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services UpdateServiceLog // -// Update the logs for a service +// Update service logs for a build // // --- // deprecated: true @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -50,7 +49,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the log to update +// description: The log object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Log" @@ -59,43 +58,39 @@ import ( // responses: // '200': // description: Successfully updated the service logs -// schema: -// "$ref": "#/definitions/Log" // '400': -// description: Unable to updated the service logs +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to updates the service logs +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateServiceLog represents the API handler to update -// the logs for a service in the configured backend. +// the logs for a service. func UpdateServiceLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("updating logs for service %s", entry) + l.Debugf("updating logs for service %s", entry) // send API call to capture the service logs - l, err := database.FromContext(c).GetLogForService(ctx, s) + sl, err := database.FromContext(c).GetLogForService(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err) @@ -119,11 +114,11 @@ func UpdateServiceLog(c *gin.Context) { // update log fields if provided if len(input.GetData()) > 0 { // update data if set - l.SetData(input.GetData()) + sl.SetData(input.GetData()) } // send API call to update the log - err = database.FromContext(c).UpdateLog(ctx, l) + err = database.FromContext(c).UpdateLog(ctx, sl) if err != nil { retErr := fmt.Errorf("unable to update logs for service %s: %w", entry, err) diff --git a/api/log/update_step.go b/api/log/update_step.go index d6f0370ec..ff19da7da 100644 --- a/api/log/update_step.go +++ b/api/log/update_step.go @@ -8,20 +8,19 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps UpdateStepLog // -// Update the logs for a step +// Update step logs for a build // // --- // deprecated: true @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -50,7 +49,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the log to update +// description: The log object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Log" @@ -59,43 +58,39 @@ import ( // responses: // '200': // description: Successfully updated the logs for step -// schema: -// "$ref": "#/definitions/Log" // '400': -// description: Unable to update the logs for a step +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the logs for a step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateStepLog represents the API handler to update -// the logs for a step in the configured backend. +// the logs for a step. func UpdateStepLog(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("updating logs for step %s", entry) + l.Debugf("updating logs for step %s", entry) // send API call to capture the step logs - l, err := database.FromContext(c).GetLogForStep(ctx, s) + sl, err := database.FromContext(c).GetLogForStep(ctx, s) if err != nil { retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err) @@ -119,11 +114,11 @@ func UpdateStepLog(c *gin.Context) { // update log fields if provided if len(input.GetData()) > 0 { // update data if set - l.SetData(input.GetData()) + sl.SetData(input.GetData()) } // send API call to update the log - err = database.FromContext(c).UpdateLog(ctx, l) + err = database.FromContext(c).UpdateLog(ctx, sl) if err != nil { retErr := fmt.Errorf("unable to update logs for step %s: %w", entry, err) diff --git a/api/metrics.go b/api/metrics.go index b562f7a1d..e23fa1d62 100644 --- a/api/metrics.go +++ b/api/metrics.go @@ -7,13 +7,14 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" - "github.com/go-vela/server/queue" - "github.com/go-vela/types/constants" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" + "github.com/go-vela/server/queue" + "github.com/go-vela/types/constants" ) // MetricsQueryParameters holds query parameter information pertaining to requested metrics. @@ -100,7 +101,7 @@ var ( // swagger:operation GET /metrics base BaseMetrics // -// Retrieve metrics from the Vela api +// Get Vela API metrics // // --- // produces: @@ -113,7 +114,7 @@ var ( // default: false // - in: query // name: repo_count -// description: Indicates a request for repo count +// description: Indicates a request for repository count // type: boolean // default: false // - in: query diff --git a/api/oi_config.go b/api/oi_config.go new file mode 100644 index 000000000..2e600577d --- /dev/null +++ b/api/oi_config.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" +) + +// swagger:operation GET /_services/token/.well-known/openid-configuration token GetOpenIDConfig +// +// Get the Vela OIDC service configuration +// +// --- +// produces: +// - application/json +// parameters: +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved the Vela OpenID Configuration +// schema: +// "$ref": "#/definitions/OpenIDConfig" + +// GetOpenIDConfig represents the API handler for requests for configurations in the Vela OpenID service. +func GetOpenIDConfig(c *gin.Context) { + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) + + l.Debug("reading OpenID configuration") + + config := types.OpenIDConfig{ + Issuer: fmt.Sprintf("%s/_services/token", m.Vela.Address), + JWKSAddress: fmt.Sprintf("%s/%s", m.Vela.Address, "_services/token/.well-known/jwks"), + SupportedClaims: []string{ + "sub", + "exp", + "iat", + "iss", + "aud", + "build_number", + "build_id", + "repo", + "token_type", + "actor", + "actor_scm_id", + "commands", + "image", + "request", + "event", + "sha", + "ref", + }, + Algorithms: []string{ + jwt.SigningMethodRS256.Name, + }, + } + + c.JSON(http.StatusOK, config) +} diff --git a/api/pipeline/compile.go b/api/pipeline/compile.go index d829ab755..a7c24af80 100644 --- a/api/pipeline/compile.go +++ b/api/pipeline/compile.go @@ -8,33 +8,34 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" - "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/pipeline" + "github.com/go-vela/server/internal" + pMiddleware "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types" - "github.com/sirupsen/logrus" + "github.com/go-vela/types/pipeline" ) // swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/compile pipelines CompilePipeline // -// Get, expand and compile a pipeline from the configured backend +// Get, expand and compile a pipeline // // --- // produces: -// - application/x-yaml +// - application/yaml // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path @@ -58,11 +59,19 @@ import ( // schema: // "$ref": "#/definitions/PipelineBuild" // '400': -// description: Unable to validate the pipeline configuration +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the pipeline configuration +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -70,23 +79,15 @@ import ( // expand and compile a pipeline configuration. func CompilePipeline(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) - o := org.Retrieve(c) - p := pipeline.Retrieve(c) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) + p := pMiddleware.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("compiling pipeline %s", entry) + l.Debugf("compiling pipeline %s", entry) // ensure we use the expected pipeline type when compiling r.SetPipelineType(p.GetType()) @@ -94,8 +95,10 @@ func CompilePipeline(c *gin.Context) { // create the compiler object compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u) + ruleData := prepareRuleData(c) + // compile the pipeline - pipeline, _, err := compiler.CompileLite(p.GetData(), true) + pipeline, _, err := compiler.CompileLite(p.GetData(), ruleData, true) if err != nil { retErr := fmt.Errorf("unable to compile pipeline %s: %w", entry, err) @@ -106,3 +109,46 @@ func CompilePipeline(c *gin.Context) { writeOutput(c, pipeline) } + +// prepareRuleData is a helper function to prepare the rule data from the query parameters. +func prepareRuleData(c *gin.Context) *pipeline.RuleData { + // capture the branch name parameter + branch := c.Query("branch") + // capture the comment parameter + comment := c.Query("comment") + // capture the event type parameter + event := c.Query("event") + // capture the repo parameter + ruleDataRepo := c.Query("repo") + // capture the status type parameter + status := c.Query("status") + // capture the tag parameter + tag := c.Query("tag") + // capture the target parameter + target := c.Query("target") + // capture the path parameter + pathSet := c.QueryArray("path") + + // if any ruledata query params were provided, create ruledata struct + if len(branch) > 0 || + len(comment) > 0 || + len(event) > 0 || + len(pathSet) > 0 || + len(ruleDataRepo) > 0 || + len(status) > 0 || + len(tag) > 0 || + len(target) > 0 { + return &pipeline.RuleData{ + Branch: branch, + Comment: comment, + Event: event, + Path: pathSet, + Repo: ruleDataRepo, + Status: status, + Tag: tag, + Target: target, + } + } + + return nil +} diff --git a/api/pipeline/compile_test.go b/api/pipeline/compile_test.go new file mode 100644 index 000000000..e1ebd89d4 --- /dev/null +++ b/api/pipeline/compile_test.go @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 + +package pipeline + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/google/go-cmp/cmp" + + "github.com/go-vela/types/pipeline" +) + +// TestPrepareRuleData tests the prepareRuleData function. +func TestPrepareRuleData(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + parameters map[string]string + want *pipeline.RuleData + }{ + { + name: "all params provided", + parameters: map[string]string{ + "branch": "main", + "comment": "Test comment", + "event": "push", + "repo": "my-repo", + "status": "success", + "tag": "v1.0.0", + "target": "production", + "path": "README.md", + }, + want: &pipeline.RuleData{ + Branch: "main", + Comment: "Test comment", + Event: "push", + Repo: "my-repo", + Status: "success", + Tag: "v1.0.0", + Target: "production", + Path: []string{"README.md"}, + }, + }, + { + name: "multiple path params", + parameters: map[string]string{ + "path": "README.md", + "branch": "main", + }, + want: &pipeline.RuleData{ + Branch: "main", + Path: []string{"README.md", "src/main.go"}, + }, + }, + { + name: "no params provided", + parameters: map[string]string{}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + + q := req.URL.Query() + for key, value := range tt.parameters { + q.Add(key, value) + } + + // add additional path parameter for multiple path test + if strings.EqualFold(tt.name, "multiple path params") { + q.Add("path", "src/main.go") + } + + req.URL.RawQuery = q.Encode() + + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + + got := prepareRuleData(ctx) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("prepareRuleData() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/api/pipeline/create.go b/api/pipeline/create.go index 240b59c57..2dc5d5067 100644 --- a/api/pipeline/create.go +++ b/api/pipeline/create.go @@ -7,18 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/pipelines/{org}/{repo} pipelines CreatePipeline // -// Create a pipeline in the configured backend +// Create a pipeline // // --- // produces: @@ -26,17 +25,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: body // name: body -// description: Payload containing the pipeline to create +// description: Pipeline object to create // required: true // schema: // "$ref": "#/definitions/Pipeline" @@ -49,37 +48,31 @@ import ( // schema: // "$ref": "#/definitions/Pipeline" // '400': -// description: Unable to create the pipeline +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to create the pipeline +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the pipeline +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreatePipeline represents the API handler to -// create a pipeline in the configured backend. +// create a pipeline. func CreatePipeline(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - - logger.Infof("creating new pipeline for repo %s", r.GetFullName()) + l.Debugf("creating new pipeline for repo %s", r.GetFullName()) // capture body from API request input := new(library.Pipeline) @@ -106,5 +99,10 @@ func CreatePipeline(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "pipeline": p.GetCommit(), + "pipeline_id": p.GetID(), + }).Info("pipeline created for repo") + c.JSON(http.StatusCreated, p) } diff --git a/api/pipeline/delete.go b/api/pipeline/delete.go index d6ca9936e..0af419b78 100644 --- a/api/pipeline/delete.go +++ b/api/pipeline/delete.go @@ -7,18 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines DeletePipeline // -// Delete a pipeline from the configured backend +// Delete a pipeline // // --- // produces: @@ -26,12 +25,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -47,35 +46,33 @@ import ( // schema: // type: string // '400': -// description: Unable to delete the pipeline +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the pipeline +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeletePipeline represents the API handler to remove -// a pipeline for a repo from the configured backend. +// DeletePipeline represents the API handler to remove a pipeline for a repository. func DeletePipeline(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) p := pipeline.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting pipeline %s", entry) + l.Debugf("deleting pipeline %s", entry) // send API call to remove the build err := database.FromContext(c).DeletePipeline(ctx, p) diff --git a/api/pipeline/expand.go b/api/pipeline/expand.go index 8fb19303f..be77aba50 100644 --- a/api/pipeline/expand.go +++ b/api/pipeline/expand.go @@ -8,33 +8,33 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/expand pipelines ExpandPipeline // -// Get and expand a pipeline from the configured backend +// Expand a pipeline // // --- // produces: -// - application/x-yaml +// - application/yaml // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path @@ -59,11 +59,19 @@ import ( // schema: // "$ref": "#/definitions/PipelineBuild" // '400': -// description: Unable to expand the pipeline configuration +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the pipeline configuration +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -71,23 +79,15 @@ import ( // expand a pipeline configuration. func ExpandPipeline(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) - o := org.Retrieve(c) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) p := pipeline.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("expanding templates for pipeline %s", entry) + l.Debugf("expanding templates for pipeline %s", entry) // ensure we use the expected pipeline type when compiling r.SetPipelineType(p.GetType()) @@ -95,8 +95,10 @@ func ExpandPipeline(c *gin.Context) { // create the compiler object compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u) + ruleData := prepareRuleData(c) + // expand the templates in the pipeline - pipeline, _, err := compiler.CompileLite(p.GetData(), false) + pipeline, _, err := compiler.CompileLite(p.GetData(), ruleData, false) if err != nil { retErr := fmt.Errorf("unable to expand pipeline %s: %w", entry, err) diff --git a/api/pipeline/get.go b/api/pipeline/get.go index 8e8b9cf16..e0c5b17af 100644 --- a/api/pipeline/get.go +++ b/api/pipeline/get.go @@ -6,16 +6,15 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/org" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines GetPipeline // -// Get a pipeline from the configured backend +// Get a pipeline // // --- // produces: @@ -23,12 +22,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -44,25 +43,31 @@ import ( // type: json // schema: // "$ref": "#/definitions/Pipeline" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error +// schema: +// "$ref": "#/definitions/Error" -// GetPipeline represents the API handler to capture -// a pipeline for a repo from the configured backend. +// GetPipeline represents the API handler to get a pipeline for a repo. func GetPipeline(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) p := pipeline.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading pipeline %s/%s", r.GetFullName(), p.GetCommit()) + l.Debugf("reading pipeline %s/%s", r.GetFullName(), p.GetCommit()) c.JSON(http.StatusOK, p) } diff --git a/api/pipeline/list.go b/api/pipeline/list.go index ebb83ad54..4203c987f 100644 --- a/api/pipeline/list.go +++ b/api/pipeline/list.go @@ -8,18 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/pipelines/{org}/{repo} pipelines ListPipelines // -// List pipelines from the configured backend +// Get all pipelines for a repository // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: query @@ -60,34 +59,34 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of pipelines +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of pipelines +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListPipelines represents the API handler to capture a list -// of pipelines for a repo from the configured backend. +// ListPipelines represents the API handler to get a list +// of pipelines for a repository. func ListPipelines(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("listing pipelines for repo %s", r.GetFullName()) + l.Debugf("listing pipelines for repo %s", r.GetFullName()) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/pipeline/output.go b/api/pipeline/output.go index b7e260ed3..e9d49483f 100644 --- a/api/pipeline/output.go +++ b/api/pipeline/output.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/server/util" ) diff --git a/api/pipeline/template.go b/api/pipeline/template.go index 9716b6328..8dc9742b8 100644 --- a/api/pipeline/template.go +++ b/api/pipeline/template.go @@ -8,38 +8,38 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" "github.com/go-vela/server/compiler/registry/github" - "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/library" "github.com/go-vela/types/yaml" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/pipelines/{org}/{repo}/{pipeline}/templates pipelines GetTemplates // -// Get a map of templates utilized by a pipeline from the configured backend +// Get pipeline templates // // --- // produces: -// - application/x-yaml +// - application/yaml // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path @@ -61,13 +61,23 @@ import ( // '200': // description: Successfully retrieved the map of pipeline templates // schema: -// "$ref": "#/definitions/Template" +// type: array +// items: +// "$ref": "#/definitions/Template" // '400': -// description: Unable to retrieve the pipeline configuration templates +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the pipeline configuration templates +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -75,7 +85,8 @@ import ( // map of templates utilized by a pipeline configuration. func GetTemplates(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) p := pipeline.Retrieve(c) r := repo.Retrieve(c) @@ -84,15 +95,7 @@ func GetTemplates(c *gin.Context) { entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading templates from pipeline %s", entry) + l.Debugf("reading templates from pipeline %s", entry) // create the compiler object compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u) @@ -105,14 +108,6 @@ func GetTemplates(c *gin.Context) { return } - // send API call to capture the repo owner - user, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - util.HandleError(c, http.StatusBadRequest, fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err)) - - return - } - baseErr := fmt.Sprintf("unable to set template links for %s", entry) templates := make(map[string]*library.Template) @@ -144,7 +139,7 @@ func GetTemplates(c *gin.Context) { } // retrieve link to template file from github - link, err := scm.FromContext(c).GetHTMLURL(ctx, user, src.Org, src.Repo, src.Name, src.Ref) + link, err := scm.FromContext(c).GetHTMLURL(ctx, r.GetOwner(), src.Org, src.Repo, src.Name, src.Ref) if err != nil { util.HandleError(c, http.StatusBadRequest, fmt.Errorf("%s: unable to get html url for %s/%s/%s/@%s: %w", baseErr, src.Org, src.Repo, src.Name, src.Ref, err)) diff --git a/api/pipeline/update.go b/api/pipeline/update.go index 075d00265..babd29494 100644 --- a/api/pipeline/update.go +++ b/api/pipeline/update.go @@ -7,19 +7,18 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines UpdatePipeline // -// Update a pipeline in the configured backend +// Update a pipeline // // --- // produces: @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -42,7 +41,7 @@ import ( // type: string // - in: body // name: body -// description: Payload containing the pipeline to update +// description: The pipeline object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Pipeline" @@ -53,36 +52,35 @@ import ( // description: Successfully updated the pipeline // schema: // "$ref": "#/definitions/Pipeline" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to update the pipeline +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the pipeline +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdatePipeline represents the API handler to update -// a pipeline for a repo in the configured backend. +// a pipeline for a repo. func UpdatePipeline(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) p := pipeline.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("updating pipeline %s", entry) + l.Debugf("updating pipeline %s", entry) // capture body from API request input := new(library.Pipeline) diff --git a/api/pipeline/validate.go b/api/pipeline/validate.go index 333e7f0d6..6ff069347 100644 --- a/api/pipeline/validate.go +++ b/api/pipeline/validate.go @@ -7,33 +7,33 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/compiler" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/validate pipelines ValidatePipeline // -// Get, expand and validate a pipeline from the configured backend +// Get, expand and validate a pipeline // // --- // produces: -// - application/x-yaml +// - application/yaml // - application/json // parameters: // - in: path -// name: repo -// description: Name of the repo +// name: org +// description: Name of the organization // required: true // type: string // - in: path -// name: org -// description: Name of the org +// name: repo +// description: Name of the repository // required: true // type: string // - in: path @@ -57,11 +57,19 @@ import ( // schema: // type: string // '400': -// description: Unable to validate the pipeline configuration +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the pipeline configuration +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -69,23 +77,15 @@ import ( // expand and validate a pipeline configuration. func ValidatePipeline(c *gin.Context) { // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) - o := org.Retrieve(c) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) p := pipeline.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p.GetCommit(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("validating pipeline %s", entry) + l.Debugf("validating pipeline %s", entry) // ensure we use the expected pipeline type when compiling r.SetPipelineType(p.GetType()) @@ -93,8 +93,10 @@ func ValidatePipeline(c *gin.Context) { // create the compiler object compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u) + ruleData := prepareRuleData(c) + // validate the pipeline - pipeline, _, err := compiler.CompileLite(p.GetData(), false) + pipeline, _, err := compiler.CompileLite(p.GetData(), ruleData, false) if err != nil { retErr := fmt.Errorf("unable to validate pipeline %s: %w", entry, err) diff --git a/api/queue/doc.go b/api/queue/doc.go new file mode 100644 index 000000000..e794792cf --- /dev/null +++ b/api/queue/doc.go @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package queue provides the queue handlers for the Vela API. +// +// Usage: +// +// import "github.com/go-vela/server/api/queue" +package queue diff --git a/api/queue/queue.go b/api/queue/queue.go index 78d4c170b..2c5698bb6 100644 --- a/api/queue/queue.go +++ b/api/queue/queue.go @@ -6,9 +6,9 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/library" ) // swagger:operation POST /api/v1/queue/info queue Info @@ -33,11 +33,9 @@ import ( // Info represents the API handler to // retrieve queue credentials as part of worker onboarding. func Info(c *gin.Context) { - cl := claims.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Info("requesting queue credentials with registration token") + l.Info("requesting queue credentials with registration token") // extract the public key that was packed into gin context k := c.MustGet("public-key").(string) diff --git a/api/repo/chown.go b/api/repo/chown.go index 8061b2de6..a30eab938 100644 --- a/api/repo/chown.go +++ b/api/repo/chown.go @@ -7,17 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation PATCH /api/v1/repos/{org}/{repo}/chown repos ChownRepo // -// Change the owner of the webhook for a repo +// Change the owner of a repository // // --- // produces: @@ -25,46 +25,51 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully changed the owner for the repo +// description: Successfully changed the owner for the repository // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to change the owner for the repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // ChownRepo represents the API handler to change -// the owner of a repo in the configured backend. +// the owner of a repo. func ChownRepo(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("changing owner of repo %s to %s", r.GetFullName(), u.GetName()) + l.Debugf("changing owner of repo %s to %s", r.GetFullName(), u.GetName()) // update repo owner - r.SetUserID(u.GetID()) + r.SetOwner(u) // send API call to update the repo _, err := database.FromContext(c).UpdateRepo(ctx, r) @@ -76,5 +81,7 @@ func ChownRepo(c *gin.Context) { return } + l.Infof("updated repo - changed owner to %s", u.GetName()) + c.JSON(http.StatusOK, fmt.Sprintf("repo %s changed owner to %s", r.GetFullName(), u.GetName())) } diff --git a/api/repo/create.go b/api/repo/create.go index 9fe69dfad..25719f2b4 100644 --- a/api/repo/create.go +++ b/api/repo/create.go @@ -9,20 +9,23 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/actions" "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/settings" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/go-vela/types/library/actions" - "github.com/google/uuid" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos repos CreateRepo // -// Create a repo in the configured backend +// Create a repository // // --- // produces: @@ -30,7 +33,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing the repo to create +// description: Repo object to create // required: true // schema: // "$ref": "#/definitions/Repo" @@ -42,7 +45,11 @@ import ( // schema: // "$ref": "#/definitions/Repo" // '400': -// description: Unable to create the repo +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '403': @@ -54,7 +61,7 @@ import ( // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // '503': @@ -62,14 +69,15 @@ import ( // schema: // "$ref": "#/definitions/Error" -// CreateRepo represents the API handler to -// create a repo in the configured backend. +// CreateRepo represents the API handler to create a repository. // //nolint:funlen,gocyclo // ignore function length and cyclomatic complexity func CreateRepo(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) - allowlist := c.Value("allowlist").([]string) + s := settings.FromContext(c) + defaultBuildLimit := c.Value("defaultBuildLimit").(int64) defaultTimeout := c.Value("defaultTimeout").(int64) maxBuildLimit := c.Value("maxBuildLimit").(int64) @@ -80,7 +88,7 @@ func CreateRepo(c *gin.Context) { ctx := c.Request.Context() // capture body from API request - input := new(library.Repo) + input := new(types.Repo) err := c.Bind(input) if err != nil { @@ -91,14 +99,7 @@ func CreateRepo(c *gin.Context) { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": input.GetOrg(), - "repo": input.GetName(), - "user": u.GetName(), - }).Infof("creating new repo %s", input.GetFullName()) + l.Debugf("creating new repo %s", input.GetFullName()) // get repo information from the source r, _, err := scm.FromContext(c).GetRepo(ctx, u, input) @@ -111,7 +112,7 @@ func CreateRepo(c *gin.Context) { } // update fields in repo object - r.SetUserID(u.GetID()) + r.SetOwner(u) // set the active field based off the input provided if input.Active == nil { @@ -182,34 +183,6 @@ func CreateRepo(c *gin.Context) { r.SetAllowEvents(defaultAllowedEvents(defaultRepoEvents, defaultRepoEventsMask)) } - // -- DEPRECATED SECTION -- - // set default events if no events are passed in - if !input.GetAllowPull() && !input.GetAllowPush() && - !input.GetAllowDeploy() && !input.GetAllowTag() && - !input.GetAllowComment() { - for _, event := range defaultRepoEvents { - switch event { - case constants.EventPull: - r.SetAllowPull(true) - case constants.EventPush: - r.SetAllowPush(true) - case constants.EventDeploy: - r.SetAllowDeploy(true) - case constants.EventTag: - r.SetAllowTag(true) - case constants.EventComment: - r.SetAllowComment(true) - } - } - } else { - r.SetAllowComment(input.GetAllowComment()) - r.SetAllowDeploy(input.GetAllowDeploy()) - r.SetAllowPull(input.GetAllowPull()) - r.SetAllowPush(input.GetAllowPush()) - r.SetAllowTag(input.GetAllowTag()) - } - // -- END DEPRECATED SECTION -- - if len(input.GetPipelineType()) == 0 { r.SetPipelineType(constants.PipelineTypeYAML) } else { @@ -223,6 +196,7 @@ func CreateRepo(c *gin.Context) { return } + r.SetPipelineType(input.GetPipelineType()) } @@ -243,7 +217,7 @@ func CreateRepo(c *gin.Context) { ) // ensure repo is allowed to be activated - if !util.CheckAllowlist(r, allowlist) { + if !util.CheckAllowlist(r, s.GetRepoAllowlist()) { retErr := fmt.Errorf("unable to activate repo: %s is not on allowlist", r.GetFullName()) util.HandleError(c, http.StatusForbidden, retErr) @@ -275,11 +249,7 @@ func CreateRepo(c *gin.Context) { // make sure our record of the repo allowed events matches what we send to SCM // what the dbRepo has should override default events on enable - r.SetAllowComment(dbRepo.GetAllowComment()) - r.SetAllowDeploy(dbRepo.GetAllowDeploy()) - r.SetAllowPull(dbRepo.GetAllowPull()) - r.SetAllowPush(dbRepo.GetAllowPush()) - r.SetAllowTag(dbRepo.GetAllowTag()) + r.SetAllowEvents(dbRepo.GetAllowEvents()) } // check if we should create the webhook @@ -307,13 +277,15 @@ func CreateRepo(c *gin.Context) { // if the repo exists but is inactive if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() { // update the repo owner - dbRepo.SetUserID(u.GetID()) + dbRepo.SetOwner(u) // update the default branch dbRepo.SetBranch(r.GetBranch()) // activate the repo dbRepo.SetActive(true) // send API call to update the repo + // NOTE: not logging modification out separately + // although we are CREATING a repo in this path r, err = database.FromContext(c).UpdateRepo(ctx, dbRepo) if err != nil { retErr := fmt.Errorf("unable to set repo %s to active: %w", dbRepo.GetFullName(), err) @@ -322,6 +294,12 @@ func CreateRepo(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + }).Infof("repo %s activated", r.GetFullName()) } else { // send API call to create the repo r, err = database.FromContext(c).CreateRepo(ctx, r) @@ -332,6 +310,12 @@ func CreateRepo(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + }).Infof("repo %s created", r.GetFullName()) } // create init hook in the DB after repo has been added in order to capture its ID @@ -347,6 +331,10 @@ func CreateRepo(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "hook": h.GetID(), + }).Infof("hook %d created for repo %s", h.GetID(), r.GetFullName()) } c.JSON(http.StatusCreated, r) @@ -355,12 +343,12 @@ func CreateRepo(c *gin.Context) { // defaultAllowedEvents is a helper function that generates an Events struct that results // from an admin-provided `sliceDefaults` or an admin-provided `maskDefaults`. If the admin // supplies a mask, that will be the default. Otherwise, it will be the legacy event list. -func defaultAllowedEvents(sliceDefaults []string, maskDefaults int64) *library.Events { +func defaultAllowedEvents(sliceDefaults []string, maskDefaults int64) *types.Events { if maskDefaults > 0 { - return library.NewEventsFromMask(maskDefaults) + return types.NewEventsFromMask(maskDefaults) } - events := new(library.Events) + events := new(types.Events) for _, event := range sliceDefaults { switch event { diff --git a/api/repo/delete.go b/api/repo/delete.go index 445a9af92..0d8be5d84 100644 --- a/api/repo/delete.go +++ b/api/repo/delete.go @@ -7,18 +7,18 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo} repos DeleteRepo // -// Delete a repo in the configured backend +// Delete a repository // // --- // produces: @@ -26,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: @@ -41,32 +41,32 @@ import ( // description: Successfully deleted the repo // schema: // type: string -// '500': -// description: Unable to deleted the repo +// '400': +// description: Invalid request payload or path // schema: // "$ref": "#/definitions/Error" -// '510': -// description: Unable to deleted the repo +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteRepo represents the API handler to remove -// a repo from the configured backend. +// DeleteRepo represents the API handler to remove a repository. func DeleteRepo(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting repo %s", r.GetFullName()) + l.Debugf("deleting repo %s", r.GetFullName()) // send API call to remove the webhook err := scm.FromContext(c).Disable(ctx, u, r.GetOrg(), r.GetName()) diff --git a/api/repo/get.go b/api/repo/get.go index d8b1fdebb..ab0254f7e 100644 --- a/api/repo/get.go +++ b/api/repo/get.go @@ -6,15 +6,14 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/router/middleware/repo" ) // swagger:operation GET /api/v1/repos/{org}/{repo} repos GetRepo // -// Get a repo in the configured backend +// Get a repository // // --- // produces: @@ -22,12 +21,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: @@ -37,23 +36,26 @@ import ( // description: Successfully retrieved the repo // schema: // "$ref": "#/definitions/Repo" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Repo" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Repo" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Repo" -// GetRepo represents the API handler to -// capture a repo from the configured backend. +// GetRepo represents the API handler to get a repository. func GetRepo(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading repo %s", r.GetFullName()) + + l.Debug("reading repo") c.JSON(http.StatusOK, r) } diff --git a/api/repo/list.go b/api/repo/list.go index 7b7f788f6..0e408e41d 100644 --- a/api/repo/list.go +++ b/api/repo/list.go @@ -8,16 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos repos ListRepos // -// Get all repos in the configured backend +// Get all repositories // // --- // produces: @@ -48,30 +49,30 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the repo +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListRepos represents the API handler to capture a list -// of repos for a user from the configured backend. +// ListRepos represents the API handler to get a list +// of repositories for a user. func ListRepos(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("listing repos for user %s", u.GetName()) + l.Debugf("listing repos for user %s", u.GetName()) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/repo/list_org.go b/api/repo/list_org.go index e2b5b411b..2152006c9 100644 --- a/api/repo/list_org.go +++ b/api/repo/list_org.go @@ -8,6 +8,8 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/org" @@ -15,12 +17,11 @@ import ( "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org} repos ListReposForOrg // -// Get all repos for the provided org in the configured backend +// Get all repositories for an organization // // --- // produces: @@ -30,7 +31,7 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: query @@ -69,32 +70,31 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the org +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the org +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListReposForOrg represents the API handler to capture a list -// of repos for an org from the configured backend. +// ListReposForOrg represents the API handler to get a list +// of repositories for an organization. func ListReposForOrg(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "user": u.GetName(), - }).Infof("listing repos for org %s", o) + l.Debugf("listing repos for org %s", o) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) @@ -137,7 +137,7 @@ func ListReposForOrg(c *gin.Context) { // See if the user is an org admin to bypass individual permission checks perm, err := scm.FromContext(c).OrgAccess(ctx, u, o) if err != nil { - logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o) + l.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } // Only show public repos to non-admins if perm != "admin" { diff --git a/api/repo/repair.go b/api/repo/repair.go index 4a792283d..27e720c37 100644 --- a/api/repo/repair.go +++ b/api/repo/repair.go @@ -4,22 +4,23 @@ package repo import ( "fmt" + "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + wh "github.com/go-vela/server/api/webhook" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" - "github.com/sirupsen/logrus" - "net/http" ) // swagger:operation PATCH /api/v1/repos/{org}/{repo}/repair repos RepairRepo // -// Remove and recreate the webhook for a repo +// Repair a hook for a repository in Vela and the configured SCM // // --- // produces: @@ -27,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: @@ -42,8 +43,20 @@ import ( // description: Successfully repaired the repo // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to repair the repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -51,21 +64,13 @@ import ( // and then create a webhook for a repo. func RepairRepo(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("repairing repo %s", r.GetFullName()) + l.Debugf("repairing repo %s", r.GetFullName()) // check if we should create the webhook if c.Value("webhookvalidation").(bool) { @@ -117,6 +122,10 @@ func RepairRepo(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "hook": hook.GetID(), + }).Info("new webhook created") } // get repo information from the source @@ -167,6 +176,8 @@ func RepairRepo(c *gin.Context) { return } + + l.Infof("repo %s updated - set to active", r.GetFullName()) } c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName())) diff --git a/api/repo/update.go b/api/repo/update.go index ba21996d3..db3342326 100644 --- a/api/repo/update.go +++ b/api/repo/update.go @@ -9,21 +9,21 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/google/uuid" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/repos/{org}/{repo} repos UpdateRepo // -// Update a repo in the configured backend +// Update a repository // // --- // produces: @@ -31,17 +31,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: body // name: body -// description: Payload containing the repo to update +// description: The repository object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Repo" @@ -53,11 +53,15 @@ import ( // schema: // "$ref": "#/definitions/Repo" // '400': -// description: Unable to update the repo +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // '503': @@ -65,13 +69,12 @@ import ( // schema: // "$ref": "#/definitions/Error" -// UpdateRepo represents the API handler to update -// a repo in the configured backend. +// UpdateRepo represents the API handler to update a repo. // //nolint:funlen,gocyclo // ignore function length func UpdateRepo(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) maxBuildLimit := c.Value("maxBuildLimit").(int64) @@ -79,17 +82,10 @@ func UpdateRepo(c *gin.Context) { defaultRepoEventsMask := c.Value("defaultRepoEventsMask").(int64) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("updating repo %s", r.GetFullName()) + l.Debug("updating repo") // capture body from API request - input := new(library.Repo) + input := new(types.Repo) err := c.Bind(input) if err != nil { @@ -191,51 +187,6 @@ func UpdateRepo(c *gin.Context) { eventsChanged = true } - // -- DEPRECATED SECTION -- - if input.AllowPull != nil { - // update allow_pull if set - r.SetAllowPull(input.GetAllowPull()) - - eventsChanged = true - } - - if input.AllowPush != nil { - // update allow_push if set - r.SetAllowPush(input.GetAllowPush()) - - eventsChanged = true - } - - if input.AllowDeploy != nil { - // update allow_deploy if set - r.SetAllowDeploy(input.GetAllowDeploy()) - - eventsChanged = true - } - - if input.AllowTag != nil { - // update allow_tag if set - r.SetAllowTag(input.GetAllowTag()) - - eventsChanged = true - } - - if input.AllowComment != nil { - // update allow_comment if set - r.SetAllowComment(input.GetAllowComment()) - - eventsChanged = true - } - - // set default events if no events are enabled - if !r.GetAllowPull() && !r.GetAllowPush() && - !r.GetAllowDeploy() && !r.GetAllowTag() && - !r.GetAllowComment() { - r.SetAllowPull(true) - r.SetAllowPush(true) - } - // -- END DEPRECATED SECTION -- - // set default events if no events are enabled if r.GetAllowEvents().ToDatabase() == 0 { r.SetAllowEvents(defaultAllowedEvents(defaultRepoEvents, defaultRepoEventsMask)) @@ -294,26 +245,15 @@ func UpdateRepo(c *gin.Context) { return } - // if user is platform admin, fetch the repo owner token to make changes to webhook + // if user is platform admin, use repo owner token to make changes to webhook if u.GetAdmin() { // capture admin name for logging admn := u.GetName() - u, err = database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get repo owner of %s for platform admin webhook update: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - // log admin override update repo hook - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("platform admin %s updating repo webhook events for repo %s", admn, r.GetFullName()) + l.Debugf("platform admin %s updating repo webhook events for repo %s", admn, r.GetFullName()) + + u = r.GetOwner() } // update webhook with new events _, err = scm.FromContext(c).Update(ctx, u, r, lastHook.GetWebhookID()) diff --git a/api/schedule/create.go b/api/schedule/create.go index 88df46a75..36741832b 100644 --- a/api/schedule/create.go +++ b/api/schedule/create.go @@ -9,17 +9,19 @@ import ( "github.com/adhocore/gronx" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/repo" + "github.com/go-vela/server/router/middleware/settings" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/schedules/{org}/{repo} schedules CreateSchedule // -// Create a schedule in the configured backend +// Create a schedule // // --- // produces: @@ -27,17 +29,17 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: body // name: body -// description: Payload containing the schedule to create +// description: Schedule object to create // required: true // schema: // "$ref": "#/definitions/Schedule" @@ -49,19 +51,27 @@ import ( // schema: // "$ref": "#/definitions/Schedule" // '400': -// description: Unable to create the schedule +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '403': // description: Unable to create the schedule // schema: // "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '409': // description: Unable to create the schedule // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the schedule +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // '503': @@ -70,17 +80,19 @@ import ( // "$ref": "#/definitions/Error" // CreateSchedule represents the API handler to -// create a schedule in the configured backend. +// create a schedule. func CreateSchedule(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) r := repo.Retrieve(c) ctx := c.Request.Context() - allowlist := c.Value("allowlistschedule").([]string) + s := settings.FromContext(c) + minimumFrequency := c.Value("scheduleminimumfrequency").(time.Duration) // capture body from API request - input := new(library.Schedule) + input := new(api.Schedule) err := c.Bind(input) if err != nil { @@ -108,17 +120,10 @@ func CreateSchedule(c *gin.Context) { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("creating new schedule %s", input.GetName()) + l.Debugf("creating new schedule %s", input.GetName()) // ensure repo is allowed to create new schedules - if !util.CheckAllowlist(r, allowlist) { + if !util.CheckAllowlist(r, s.GetScheduleAllowlist()) { retErr := fmt.Errorf("unable to create schedule %s: %s is not on allowlist", input.GetName(), r.GetFullName()) util.HandleError(c, http.StatusForbidden, retErr) @@ -126,29 +131,29 @@ func CreateSchedule(c *gin.Context) { return } - s := new(library.Schedule) + schedule := new(api.Schedule) // update fields in schedule object - s.SetCreatedBy(u.GetName()) - s.SetRepoID(r.GetID()) - s.SetName(input.GetName()) - s.SetEntry(input.GetEntry()) - s.SetCreatedAt(time.Now().UTC().Unix()) - s.SetUpdatedAt(time.Now().UTC().Unix()) - s.SetUpdatedBy(u.GetName()) + schedule.SetCreatedBy(u.GetName()) + schedule.SetRepo(r) + schedule.SetName(input.GetName()) + schedule.SetEntry(input.GetEntry()) + schedule.SetCreatedAt(time.Now().UTC().Unix()) + schedule.SetUpdatedAt(time.Now().UTC().Unix()) + schedule.SetUpdatedBy(u.GetName()) if input.GetBranch() == "" { - s.SetBranch(r.GetBranch()) + schedule.SetBranch(r.GetBranch()) } else { - s.SetBranch(input.GetBranch()) + schedule.SetBranch(input.GetBranch()) } // set the active field based off the input provided if input.Active == nil { // default active field to true - s.SetActive(true) + schedule.SetActive(true) } else { - s.SetActive(input.GetActive()) + schedule.SetActive(input.GetActive()) } // send API call to capture the schedule from the database @@ -177,7 +182,7 @@ func CreateSchedule(c *gin.Context) { dbSchedule.SetActive(true) // send API call to update the schedule - s, err = database.FromContext(c).UpdateSchedule(ctx, dbSchedule, true) + schedule, err = database.FromContext(c).UpdateSchedule(ctx, dbSchedule, true) if err != nil { retErr := fmt.Errorf("unable to set schedule %s to active: %w", dbSchedule.GetName(), err) @@ -185,9 +190,14 @@ func CreateSchedule(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "schedule": schedule.GetName(), + "schedule_id": schedule.GetID(), + }).Infof("schedule %s updated - activated", schedule.GetName()) } else { // send API call to create the schedule - s, err = database.FromContext(c).CreateSchedule(ctx, s) + schedule, err = database.FromContext(c).CreateSchedule(ctx, schedule) if err != nil { retErr := fmt.Errorf("unable to create new schedule %s: %w", r.GetName(), err) @@ -195,9 +205,14 @@ func CreateSchedule(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "schedule": schedule.GetName(), + "schedule_id": schedule.GetID(), + }).Infof("schedule %s created", schedule.GetName()) } - c.JSON(http.StatusCreated, s) + c.JSON(http.StatusCreated, schedule) } // validateEntry validates the entry for a minimum frequency. diff --git a/api/schedule/delete.go b/api/schedule/delete.go index 3e7eea10d..1cf2da000 100644 --- a/api/schedule/delete.go +++ b/api/schedule/delete.go @@ -7,18 +7,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/schedule" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/{schedule} schedules DeleteSchedule // -// Delete a schedule in the configured backend +// Delete a schedule // // --- // produces: @@ -26,12 +24,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -46,33 +44,31 @@ import ( // description: Successfully deleted the schedule // schema: // type: string -// '500': -// description: Unable to delete the schedule +// '400': +// description: Invalid request payload or path // schema: // "$ref": "#/definitions/Error" -// '510': -// description: Unable to delete the schedule +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteSchedule represents the API handler to remove -// a schedule from the configured backend. +// DeleteSchedule represents the API handler to remove a schedule. func DeleteSchedule(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) s := schedule.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting schedule %s", s.GetName()) + l.Debugf("deleting schedule %s", s.GetName()) err := database.FromContext(c).DeleteSchedule(ctx, s) if err != nil { diff --git a/api/schedule/doc.go b/api/schedule/doc.go new file mode 100644 index 000000000..c6736d529 --- /dev/null +++ b/api/schedule/doc.go @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package schedule provides the schedule handlers for the Vela API. +// +// Usage: +// +// import "github.com/go-vela/server/api/schedule" +package schedule diff --git a/api/schedule/get.go b/api/schedule/get.go index 902b06b21..287debf42 100644 --- a/api/schedule/get.go +++ b/api/schedule/get.go @@ -6,16 +6,14 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/schedule" - "github.com/go-vela/server/router/middleware/user" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/router/middleware/schedule" ) // swagger:operation GET /api/v1/schedules/{org}/{repo}/{schedule} schedules GetSchedule // -// Get a schedule in the configured backend +// Get a schedule // // --- // produces: @@ -23,12 +21,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -43,25 +41,26 @@ import ( // description: Successfully retrieved the schedule // schema: // "$ref": "#/definitions/Schedule" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" -// GetSchedule represents the API handler to -// capture a schedule from the configured backend. +// GetSchedule represents the API handler to get a schedule. func GetSchedule(c *gin.Context) { // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) s := schedule.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - "schedule": s.GetName(), - }).Infof("reading schedule %s", s.GetName()) + l.Debugf("reading schedule %s", s.GetName()) c.JSON(http.StatusOK, s) } diff --git a/api/schedule/list.go b/api/schedule/list.go index cd47a56e1..22b1fd43a 100644 --- a/api/schedule/list.go +++ b/api/schedule/list.go @@ -8,16 +8,17 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/schedules/{org}/{repo} schedules ListSchedules // -// Get all schedules in the configured backend +// Get all schedules for a repository // // --- // produces: @@ -27,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: query @@ -58,31 +59,33 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the schedules +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the schedules +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListSchedules represents the API handler to capture a list -// of schedules for a repo from the configured backend. +// ListSchedules represents the API handler to get a list of schedules for a repository. func ListSchedules(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "repo": r.GetName(), - "org": r.GetOrg(), - }).Infof("listing schedules for repo %s", r.GetFullName()) + l.Debugf("listing schedules for repo %s", r.GetFullName()) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/schedule/update.go b/api/schedule/update.go index d1ff1e44f..3aedde1a1 100644 --- a/api/schedule/update.go +++ b/api/schedule/update.go @@ -8,18 +8,18 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/schedule" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/schedules/{org}/{repo}/{schedule} schedules UpdateSchedule // -// Update a schedule for the configured backend +// Update a schedule // // --- // produces: @@ -27,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -42,7 +42,7 @@ import ( // type: string // - in: body // name: body -// description: Payload containing the schedule to update +// description: The schedule object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Schedule" @@ -54,40 +54,36 @@ import ( // schema: // "$ref": "#/definitions/Schedule" // '400': -// description: Unable to update the schedule +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to update the schedule +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the schedule +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateSchedule represents the API handler to update -// a schedule in the configured backend. +// UpdateSchedule represents the API handler to update a schedule. func UpdateSchedule(c *gin.Context) { // capture middleware values - r := repo.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) s := schedule.Retrieve(c) - ctx := c.Request.Context() u := user.Retrieve(c) + ctx := c.Request.Context() scheduleName := util.PathParameter(c, "schedule") minimumFrequency := c.Value("scheduleminimumfrequency").(time.Duration) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "schedule": scheduleName, - "repo": r.GetName(), - "org": r.GetOrg(), - }).Infof("updating schedule %s", scheduleName) + l.Debugf("updating schedule %s", scheduleName) // capture body from API request - input := new(library.Schedule) + input := new(api.Schedule) err := c.Bind(input) if err != nil { diff --git a/api/scm/sync.go b/api/scm/sync.go index 2033c9a49..66522510a 100644 --- a/api/scm/sync.go +++ b/api/scm/sync.go @@ -8,18 +8,19 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation PATCH /api/v1/scm/repos/{org}/{repo}/sync scm SyncRepo // -// Sync up scm service and database in the context of a specific repo +// Sync a repository with the scm service // // --- // produces: @@ -27,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // security: @@ -45,15 +46,23 @@ import ( // '204': // description: Successful request resulting in no change // '301': -// description: Repo has moved permanently +// description: Repo has moved permanently (from SCM) +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '403': -// description: User has been forbidden access to repository +// description: User has been forbidden access to repository (from SCM) +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to synchronize repo +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -64,25 +73,16 @@ import ( // subscribed events with allowed events. func SyncRepo(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - - logger.Infof("syncing repo %s", r.GetFullName()) + l.Debugf("syncing repo %s", r.GetFullName()) // retrieve repo from source code manager service _, respCode, err := scm.FromContext(c).GetRepo(ctx, u, r) - // if there is an error retrieving repo, we know it is deleted: set to inactive if err != nil { if respCode == http.StatusNotFound { @@ -99,6 +99,8 @@ func SyncRepo(c *gin.Context) { return } + l.Infof("repo %s has been updated - set to inactive", r.GetFullName()) + // exit with success as hook sync will be unnecessary c.JSON(http.StatusOK, r) @@ -116,7 +118,7 @@ func SyncRepo(c *gin.Context) { // we cannot use our normal permissions check due to the possibility the repo was deleted perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), o, r.GetName()) if err != nil { - logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o) + l.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } if !strings.EqualFold(perm, "admin") { @@ -157,6 +159,8 @@ func SyncRepo(c *gin.Context) { return } + l.Infof("repo %s has been updated - set to inactive", r.GetFullName()) + c.JSON(http.StatusOK, r) return diff --git a/api/scm/sync_org.go b/api/scm/sync_org.go index fb0c425f3..e8efee734 100644 --- a/api/scm/sync_org.go +++ b/api/scm/sync_org.go @@ -7,18 +7,19 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PATCH /api/v1/scm/orgs/{org}/sync scm SyncReposForOrg // -// Sync up repos from scm service and database in a specified org +// Sync repositories from scm service and database in a specified organization // // --- // produces: @@ -26,7 +27,7 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // security: @@ -41,15 +42,23 @@ import ( // '204': // description: Successful request resulting in no change // '301': -// description: One repo in the org has moved permanently +// description: One repository in the organiation has moved permanently (from SCM) +// schema: +// "$ref": "#/definitions/Error" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '403': -// description: User has been forbidden access to at least one repository in org +// description: User has been forbidden access to at least one repository in organiation (from SCM) // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to synchronize org repositories +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -60,24 +69,17 @@ import ( // subscribed events with allowed events. func SyncReposForOrg(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "user": u.GetName(), - }) - - logger.Infof("syncing repos for org %s", o) + l.Debugf("syncing repos for org %s", o) // see if the user is an org admin perm, err := scm.FromContext(c).OrgAccess(ctx, u, o) if err != nil { - logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o) + l.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } // only allow org-wide syncing if user is admin of org @@ -99,7 +101,7 @@ func SyncReposForOrg(c *gin.Context) { return } - repos := []*library.Repo{} + repos := []*types.Repo{} page := 0 // capture all repos belonging to a certain org in database for orgRepos := int64(0); orgRepos < t; orgRepos += 100 { @@ -117,7 +119,7 @@ func SyncReposForOrg(c *gin.Context) { page++ } - var results []*library.Repo + var results []*types.Repo // iterate through captured repos and check if they are in GitHub for _, repo := range repos { @@ -134,6 +136,8 @@ func SyncReposForOrg(c *gin.Context) { return } + l.Infof("repo %s has been updated - set to inactive", repo.GetFullName()) + results = append(results, repo) } else { retErr := fmt.Errorf("error while retrieving repo %s from %s: %w", repo.GetFullName(), scm.FromContext(c).Driver(), err) @@ -174,6 +178,8 @@ func SyncReposForOrg(c *gin.Context) { return } + l.Infof("repo %s has been updated - set to inactive", repo.GetFullName()) + results = append(results, repo) continue diff --git a/api/secret/create.go b/api/secret/create.go index 8423bc8cb..6c4c502b1 100644 --- a/api/secret/create.go +++ b/api/secret/create.go @@ -9,6 +9,8 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/secret" @@ -16,10 +18,8 @@ import ( "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/library/actions" - "github.com/sirupsen/logrus" ) -// // swagger:operation POST /api/v1/secrets/{engine}/{type}/{org}/{name} secrets CreateSecret // // Create a secret @@ -44,17 +44,17 @@ import ( // type: string // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: name -// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret +// description: Name of the repository if a repository secret, team name if a shared secret, or '*' if an organization secret // required: true // type: string // - in: body // name: body -// description: Payload containing the secret to create +// description: Secret object to create // required: true // schema: // "$ref": "#/definitions/Secret" @@ -66,20 +66,25 @@ import ( // schema: // "$ref": "#/definitions/Secret" // '400': -// description: Unable to create the secret +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the secret +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateSecret represents the API handler to -// create a secret in the configured backend. +// create a secret. // //nolint:funlen // suppress long function error func CreateSecret(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) e := util.PathParameter(c, "engine") t := util.PathParameter(c, "type") @@ -91,25 +96,24 @@ func CreateSecret(c *gin.Context) { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { - // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "team": n, - "type": t, - "user": u.GetName(), - } + // update log fields for shared secret + delete(fields, "secret_repo") + fields["secret_team"] = n } + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logger := l.WithFields(fields) + if strings.EqualFold(t, constants.SecretOrg) { // retrieve org name from SCM // @@ -168,10 +172,7 @@ func CreateSecret(c *gin.Context) { } } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(fields).Infof("creating new secret %s for %s service", entry, e) + logger.Debugf("creating new secret %s for %s service", entry, e) // capture body from API request input := new(library.Secret) @@ -225,15 +226,18 @@ func CreateSecret(c *gin.Context) { input.SetAllowEvents(e) } - if len(input.GetEvents()) == 0 { - // set default events to enable for the secret - input.SetEvents([]string{constants.EventPush, constants.EventTag, constants.EventDeploy}) - } - if input.AllowCommand == nil { input.SetAllowCommand(true) } + // default to not allow substitution for shared secrets + if strings.EqualFold(input.GetType(), constants.SecretShared) && input.AllowSubstitution == nil { + input.SetAllowSubstitution(false) + input.SetAllowCommand(false) + } else if input.AllowSubstitution == nil { + input.SetAllowSubstitution(true) + } + // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update the team instead of repo @@ -251,5 +255,23 @@ func CreateSecret(c *gin.Context) { return } + // update log fields from create response + fields = logrus.Fields{ + "secret_id": s.GetID(), + "secret_name": s.GetName(), + "secret_org": s.GetOrg(), + "secret_repo": s.GetRepo(), + "secret_type": s.GetType(), + } + + // check if secret is a shared secret + if strings.EqualFold(t, constants.SecretShared) { + // update log fields for shared secret + delete(fields, "secret_repo") + fields["secret_team"] = s.GetTeam() + } + + l.WithFields(fields).Infof("created secret %s for %s service", entry, e) + c.JSON(http.StatusOK, s.Sanitize()) } diff --git a/api/secret/delete.go b/api/secret/delete.go index 5257b82a9..c038c1a08 100644 --- a/api/secret/delete.go +++ b/api/secret/delete.go @@ -8,17 +8,16 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/user" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/secret" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) -// // swagger:operation DELETE /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets DeleteSecret // -// Delete a secret from the configured backend +// Delete a secret // // --- // produces: @@ -40,12 +39,12 @@ import ( // type: string // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: name -// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret +// description: Name of the repository if a repository secret, team name if a shared secret, or '*' if an organzation secret // required: true // type: string // - in: path @@ -60,15 +59,19 @@ import ( // description: Successfully deleted the secret // schema: // type: string +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the secret +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // DeleteSecret deletes a secret from the provided secrets service. func DeleteSecret(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) e := util.PathParameter(c, "engine") t := util.PathParameter(c, "type") o := util.PathParameter(c, "org") @@ -80,31 +83,26 @@ func DeleteSecret(c *gin.Context) { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "secret": s, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_name": s, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "secret": s, - "team": n, - "type": t, - "user": u.GetName(), - } + delete(fields, "secret_repo") + fields["secret_team"] = n } // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(fields).Infof("deleting secret %s from %s service", entry, e) + logger := l.WithFields(fields) + + logger.Debugf("deleting secret %s from %s service", entry, e) // send API call to remove the secret err := secret.FromContext(c, e).Delete(ctx, t, o, n, s) @@ -116,5 +114,7 @@ func DeleteSecret(c *gin.Context) { return } + logger.Infof("secret %s deleted from %s service", entry, e) + c.JSON(http.StatusOK, fmt.Sprintf("secret %s deleted from %s service", entry, e)) } diff --git a/api/secret/get.go b/api/secret/get.go index 74cad449f..72a84fc18 100644 --- a/api/secret/get.go +++ b/api/secret/get.go @@ -8,18 +8,17 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/secret" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) -// // swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets GetSecret // -// Retrieve a secret from the configured backend +// Get a secret // // --- // produces: @@ -41,12 +40,12 @@ import ( // type: string // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: name -// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret +// description: Name of the repository if a repository secret, team name if a shared secret, or '*' if an organization secret // required: true // type: string // - in: path @@ -61,16 +60,20 @@ import ( // description: Successfully retrieved the secret // schema: // "$ref": "#/definitions/Secret" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the secret +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // GetSecret gets a secret from the provided secrets service. func GetSecret(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) - u := user.Retrieve(c) e := util.PathParameter(c, "engine") t := util.PathParameter(c, "type") o := util.PathParameter(c, "org") @@ -82,31 +85,26 @@ func GetSecret(c *gin.Context) { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "secret": s, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_name": s, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "secret": s, - "team": n, - "type": t, - "user": u.GetName(), - } + delete(fields, "secret_repo") + fields["secret_team"] = n } // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(fields).Infof("reading secret %s from %s service", entry, e) + logger := l.WithFields(fields) + + logger.Debugf("reading secret %s from %s service", entry, e) // send API call to capture the secret secret, err := secret.FromContext(c, e).Get(ctx, t, o, n, s) @@ -125,5 +123,7 @@ func GetSecret(c *gin.Context) { return } + logger.Infof("retrieved secret %s from %s service", entry, e) + c.JSON(http.StatusOK, secret.Sanitize()) } diff --git a/api/secret/list.go b/api/secret/list.go index a3870597f..793b594f7 100644 --- a/api/secret/list.go +++ b/api/secret/list.go @@ -9,6 +9,8 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" @@ -16,13 +18,11 @@ import ( "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) -// // swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name} secrets ListSecrets // -// Retrieve a list of secrets from the configured backend +// Get all organization or shared secrets // // --- // produces: @@ -44,12 +44,12 @@ import ( // type: string // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: name -// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret +// description: Name of the repository if a repository secret, team name if a shared secret, or '*' if an organization secret // required: true // type: string // - in: query @@ -77,21 +77,25 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of secrets +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of secrets +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListSecrets represents the API handler to capture -// a list of secrets from the configured backend. +// ListSecrets represents the API handler to get a list of secrets. func ListSecrets(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) e := util.PathParameter(c, "engine") t := util.PathParameter(c, "type") @@ -118,29 +122,25 @@ func ListSecrets(c *gin.Context) { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "team": n, - "type": t, - "user": u.GetName(), - } + delete(fields, "secret_repo") + fields["secret_team"] = n } // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(fields).Infof("listing secrets %s from %s service", entry, e) + logger := l.WithFields(fields) + + logger.Debugf("listing secrets %s from %s service", entry, e) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) @@ -205,5 +205,7 @@ func ListSecrets(c *gin.Context) { secrets = append(secrets, tmp.Sanitize()) } + logger.Infof("successfully listed secrets %s from %s service", entry, e) + c.JSON(http.StatusOK, secrets) } diff --git a/api/secret/update.go b/api/secret/update.go index 86870d03d..0d7d62072 100644 --- a/api/secret/update.go +++ b/api/secret/update.go @@ -9,18 +9,19 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/secret" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // // swagger:operation PUT /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets UpdateSecret // -// Update a secret on the configured backend +// Update a secret // // --- // produces: @@ -42,12 +43,12 @@ import ( // type: string // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: name -// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret +// description: Name of the repository if a repository secret, team name if a shared secret, or '*' if an organization secret // required: true // type: string // - in: path @@ -69,17 +70,22 @@ import ( // schema: // "$ref": "#/definitions/Secret" // '400': -// description: Unable to update the secret +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the secret +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateSecret updates a secret for the provided secrets service. func UpdateSecret(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) e := util.PathParameter(c, "engine") t := util.PathParameter(c, "type") @@ -92,31 +98,24 @@ func UpdateSecret(c *gin.Context) { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "secret": s, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_name": s, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "secret": s, - "team": n, - "type": t, - "user": u.GetName(), - } + delete(fields, "secret_repo") + fields["secret_team"] = n } // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(fields).Infof("updating secret %s for %s service", entry, e) + l.WithFields(fields).Debugf("updating secret %s for %s service", entry, e) // capture body from API request input := new(library.Secret) @@ -143,15 +142,15 @@ func UpdateSecret(c *gin.Context) { input.SetImages(util.Unique(input.GetImages())) } - if len(input.GetEvents()) > 0 { - input.SetEvents(util.Unique(input.GetEvents())) - } - if input.AllowCommand != nil { // update allow_command if set input.SetAllowCommand(input.GetAllowCommand()) } + if input.AllowSubstitution != nil { + input.SetAllowSubstitution(input.GetAllowSubstitution()) + } + // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update the team instead of repo @@ -169,5 +168,7 @@ func UpdateSecret(c *gin.Context) { return } + l.WithFields(fields).Info("secret updated") + c.JSON(http.StatusOK, secret.Sanitize()) } diff --git a/api/service/create.go b/api/service/create.go index e43f66a0f..4e3ee47c7 100644 --- a/api/service/create.go +++ b/api/service/create.go @@ -8,20 +8,19 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services services CreateService // -// Create a service for a build in the configured backend +// Create a service for a build // // --- // produces: @@ -29,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -44,7 +43,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the service to create +// description: Service object to create // required: true // schema: // "$ref": "#/definitions/Service" @@ -56,35 +55,34 @@ import ( // schema: // "$ref": "#/definitions/Service" // '400': -// description: Unable to create the service +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the service +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateService represents the API handler to create -// a service for a build in the configured backend. +// a service for a build. func CreateService(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("creating new service for build %s", entry) + l.Debugf("creating new service for build %s", entry) // capture body from API request input := new(library.Service) @@ -120,5 +118,10 @@ func CreateService(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + }).Infof("service %s created for build %s", s.GetName(), entry) + c.JSON(http.StatusCreated, s) } diff --git a/api/service/delete.go b/api/service/delete.go index ff885e440..5bd8269fd 100644 --- a/api/service/delete.go +++ b/api/service/delete.go @@ -7,20 +7,19 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services DeleteService // -// Delete a service for a build in the configured backend +// Delete a service for a build // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -53,34 +52,35 @@ import ( // description: Successfully deleted the service // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to delete the service +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteService represents the API handler to remove -// a service for a build from the configured backend. +// DeleteService represents the API handler to remove a service for a build. func DeleteService(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("deleting service %s", entry) + l.Debugf("deleting service %s", entry) // send API call to remove the service err := database.FromContext(c).DeleteService(ctx, s) diff --git a/api/service/get.go b/api/service/get.go index 5ac4503b8..9538d3300 100644 --- a/api/service/get.go +++ b/api/service/get.go @@ -6,18 +6,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" - "github.com/sirupsen/logrus" ) // // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services GetService // -// Get a service for a build in the configured backend +// Get a service for a build // // --- // produces: @@ -25,12 +24,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -51,34 +50,31 @@ import ( // schema: // "$ref": "#/definitions/Service" // '400': -// description: Unable to retrieve the service +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the service +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetService represents the API handler to capture a -// service for a build from the configured backend. +// GetService represents the API handler to get a service for a build. func GetService(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) + l.Debugf("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) c.JSON(http.StatusOK, s) } diff --git a/api/service/list.go b/api/service/list.go index d5e37cfef..efc4aa5a0 100644 --- a/api/service/list.go +++ b/api/service/list.go @@ -8,19 +8,18 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services services ListServices // -// Get a list of all services for a build in the configured backend +// Get all services for a build // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -66,38 +65,36 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of services +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of services +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListServices represents the API handler to capture a list -// of services for a build from the configured backend. +// ListServices represents the API handler to get a list of services for a build. func ListServices(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading services for build %s", entry) + l.Debugf("reading services for build %s", entry) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/service/plan.go b/api/service/plan.go index 790c9b538..bc8408ddb 100644 --- a/api/service/plan.go +++ b/api/service/plan.go @@ -7,6 +7,9 @@ import ( "fmt" "time" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" @@ -15,8 +18,8 @@ import ( // PlanServices is a helper function to plan all services // in the build for execution. This creates the services -// for the build in the configured backend. -func PlanServices(ctx context.Context, database database.Interface, p *pipeline.Build, b *library.Build) ([]*library.Service, error) { +// for the build. +func PlanServices(ctx context.Context, database database.Interface, p *pipeline.Build, b *types.Build) ([]*library.Service, error) { // variable to store planned services services := []*library.Service{} @@ -25,7 +28,7 @@ func PlanServices(ctx context.Context, database database.Interface, p *pipeline. // create the service object s := new(library.Service) s.SetBuildID(b.GetID()) - s.SetRepoID(b.GetRepoID()) + s.SetRepoID(b.GetRepo().GetID()) s.SetName(service.Name) s.SetImage(service.Image) s.SetNumber(service.Number) @@ -38,6 +41,14 @@ func PlanServices(ctx context.Context, database database.Interface, p *pipeline. return services, fmt.Errorf("unable to create service %s: %w", s.GetName(), err) } + logrus.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }).Info("service created") + // populate environment variables from service library // // https://pkg.go.dev/github.com/go-vela/types/library#Service.Environment @@ -50,7 +61,7 @@ func PlanServices(ctx context.Context, database database.Interface, p *pipeline. l := new(library.Log) l.SetServiceID(s.GetID()) l.SetBuildID(b.GetID()) - l.SetRepoID(b.GetRepoID()) + l.SetRepoID(b.GetRepo().GetID()) l.SetData([]byte{}) // send API call to create the service logs @@ -58,6 +69,15 @@ func PlanServices(ctx context.Context, database database.Interface, p *pipeline. if err != nil { return services, fmt.Errorf("unable to create service logs for service %s: %w", s.GetName(), err) } + + logrus.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + "log_id": l.GetID(), // it won't have an ID here, because CreateLog doesn't return the created log + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }).Info("log for service created") } return services, nil diff --git a/api/service/update.go b/api/service/update.go index c391e6a86..a7e047a60 100644 --- a/api/service/update.go +++ b/api/service/update.go @@ -7,21 +7,20 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // // swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services UpdateService // -// Update a service for a build in the configured backend +// Update a service for a build // // --- // produces: @@ -29,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -49,7 +48,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the service to update +// description: The service object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Service" @@ -61,37 +60,35 @@ import ( // schema: // "$ref": "#/definitions/Service" // '400': -// description: Unable to update the service +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the service +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateService represents the API handler to update -// a service for a build in the configured backend. +// a service for a build. func UpdateService(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := service.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "service": s.GetNumber(), - "user": u.GetName(), - }).Infof("updating service %s", entry) + l.Debugf("updating service %s", entry) // capture body from API request input := new(library.Service) diff --git a/api/step/create.go b/api/step/create.go index f8b9ef2f8..b9a113de4 100644 --- a/api/step/create.go +++ b/api/step/create.go @@ -8,15 +8,14 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps steps CreateStep @@ -29,12 +28,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -44,7 +43,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the step to create +// description: Step object to create // required: true // schema: // "$ref": "#/definitions/Step" @@ -56,35 +55,34 @@ import ( // schema: // "$ref": "#/definitions/Step" // '400': -// description: Unable to create the step +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateStep represents the API handler to create -// a step for a build in the configured backend. +// a step for a build. func CreateStep(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("creating new step for build %s", entry) + l.Debugf("creating new step for build %s", entry) // capture body from API request input := new(library.Step) @@ -120,5 +118,10 @@ func CreateStep(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "step": s.GetName(), + "step_id": s.GetID(), + }).Infof("step %s created for build %s", s.GetName(), entry) + c.JSON(http.StatusCreated, s) } diff --git a/api/step/delete.go b/api/step/delete.go index 574e873c7..b59f9b1d0 100644 --- a/api/step/delete.go +++ b/api/step/delete.go @@ -7,14 +7,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps DeleteStep @@ -27,12 +26,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -52,34 +51,35 @@ import ( // description: Successfully deleted the step // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Successfully deleted the step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteStep represents the API handler to remove -// a step for a build from the configured backend. +// DeleteStep represents the API handler to remove a step for a build. func DeleteStep(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("deleting step %s", entry) + l.Debugf("deleting step %s", entry) // send API call to remove the step err := database.FromContext(c).DeleteStep(ctx, s) diff --git a/api/step/get.go b/api/step/get.go index 51f1894c6..627937371 100644 --- a/api/step/get.go +++ b/api/step/get.go @@ -6,17 +6,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps GetStep // -// Retrieve a step for a build +// Get a step for a build // // --- // produces: @@ -24,12 +23,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -49,27 +48,28 @@ import ( // description: Successfully retrieved the step // schema: // "$ref": "#/definitions/Step" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" -// GetStep represents the API handler to capture a -// step for a build from the configured backend. +// GetStep represents the API handler to get a step for a build. func GetStep(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) + l.Debugf("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) c.JSON(http.StatusOK, s) } diff --git a/api/step/list.go b/api/step/list.go index 42c45cac8..194235f5c 100644 --- a/api/step/list.go +++ b/api/step/list.go @@ -8,19 +8,18 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps steps ListSteps // -// Retrieve a list of steps for a build +// Get all steps for a build // // --- // produces: @@ -28,12 +27,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -66,38 +65,36 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of steps +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of steps +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListSteps represents the API handler to capture a list -// of steps for a build from the configured backend. +// ListSteps represents the API handler to get a list of steps for a build. func ListSteps(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("listing steps for build %s", entry) + l.Debugf("listing steps for build %s", entry) // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/step/plan.go b/api/step/plan.go index e1c446c44..bfacc5136 100644 --- a/api/step/plan.go +++ b/api/step/plan.go @@ -7,9 +7,11 @@ import ( "fmt" "time" - "github.com/go-vela/server/scm" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" + "github.com/go-vela/server/scm" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" @@ -17,8 +19,8 @@ import ( // PlanSteps is a helper function to plan all steps // in the build for execution. This creates the steps -// for the build in the configured backend. -func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *library.Build, r *library.Repo) ([]*library.Step, error) { +// for the build. +func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service, p *pipeline.Build, b *types.Build, r *types.Repo) ([]*library.Step, error) { // variable to store planned steps steps := []*library.Step{} @@ -49,16 +51,17 @@ func PlanSteps(ctx context.Context, database database.Interface, scm scm.Service return steps, nil } -func planStep(ctx context.Context, database database.Interface, scm scm.Service, b *library.Build, r *library.Repo, c *pipeline.Container, stage string) (*library.Step, error) { +func planStep(ctx context.Context, database database.Interface, scm scm.Service, b *types.Build, r *types.Repo, c *pipeline.Container, stage string) (*library.Step, error) { // create the step object s := new(library.Step) s.SetBuildID(b.GetID()) - s.SetRepoID(b.GetRepoID()) + s.SetRepoID(b.GetRepo().GetID()) s.SetNumber(c.Number) s.SetName(c.Name) s.SetImage(c.Image) s.SetStage(stage) s.SetStatus(constants.StatusPending) + s.SetReportAs(c.ReportAs) s.SetCreated(time.Now().UTC().Unix()) if c.ReportStatus { @@ -77,6 +80,14 @@ func planStep(ctx context.Context, database database.Interface, scm scm.Service, return nil, fmt.Errorf("unable to create step %s: %w", s.GetName(), err) } + logrus.WithFields(logrus.Fields{ + "step": s.GetName(), + "step_id": s.GetID(), + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }).Info("step created") + // populate environment variables from step library // // https://pkg.go.dev/github.com/go-vela/types/library#step.Environment @@ -89,7 +100,7 @@ func planStep(ctx context.Context, database database.Interface, scm scm.Service, l := new(library.Log) l.SetStepID(s.GetID()) l.SetBuildID(b.GetID()) - l.SetRepoID(b.GetRepoID()) + l.SetRepoID(b.GetRepo().GetID()) l.SetData([]byte{}) // send API call to create the step logs @@ -98,5 +109,22 @@ func planStep(ctx context.Context, database database.Interface, scm scm.Service, return nil, fmt.Errorf("unable to create logs for step %s: %w", s.GetName(), err) } + logrus.WithFields(logrus.Fields{ + "step": s.GetName(), + "step_id": s.GetID(), + "log_id": l.GetID(), // it won't have an ID here + "org": b.GetRepo().GetOrg(), + "repo": b.GetRepo().GetName(), + "repo_id": b.GetRepo().GetID(), + }).Info("log for step created") + + if len(s.GetReportAs()) > 0 { + // send API call to set the status on the commit + err = scm.StepStatus(ctx, b.GetRepo().GetOwner(), b, s, b.GetRepo().GetOrg(), b.GetRepo().GetName()) + if err != nil { + logrus.Errorf("unable to set commit status for build: %v", err) + } + } + return s, nil } diff --git a/api/step/update.go b/api/step/update.go index 49bb82e9b..7d3ebcddd 100644 --- a/api/step/update.go +++ b/api/step/update.go @@ -6,18 +6,17 @@ import ( "fmt" "net/http" - "github.com/go-vela/server/scm" - "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/step" - "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/scm" "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps UpdateStep @@ -30,12 +29,12 @@ import ( // parameters: // - in: path // name: org -// description: Name of the org +// description: Name of the organization // required: true // type: string // - in: path // name: repo -// description: Name of the repo +// description: Name of the repository // required: true // type: string // - in: path @@ -50,7 +49,7 @@ import ( // type: integer // - in: body // name: body -// description: Payload containing the step to update +// description: The step object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Step" @@ -62,37 +61,35 @@ import ( // schema: // "$ref": "#/definitions/Step" // '400': -// description: Unable to update the step +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the step +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateStep represents the API handler to update -// a step for a build in the configured backend. +// a step for a build. func UpdateStep(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) s := step.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "repo": r.GetName(), - "step": s.GetNumber(), - "user": u.GetName(), - }).Infof("updating step %s", entry) + l.Debugf("updating step %s", entry) // capture body from API request input := new(library.Step) @@ -171,4 +168,20 @@ func UpdateStep(c *gin.Context) { } c.JSON(http.StatusOK, s) + + // check if the build is in a "final" state + // and if build is not a scheduled event + if (s.GetStatus() == constants.StatusSuccess || + s.GetStatus() == constants.StatusFailure || + s.GetStatus() == constants.StatusCanceled || + s.GetStatus() == constants.StatusKilled || + s.GetStatus() == constants.StatusError) && + (b.GetEvent() != constants.EventSchedule) && + (len(s.GetReportAs()) > 0) { + // send API call to set the status on the commit + err = scm.FromContext(c).StepStatus(ctx, r.GetOwner(), b, s, r.GetOrg(), r.GetName()) + if err != nil { + l.Errorf("unable to set commit status for build %s: %v", entry, err) + } + } } diff --git a/api/types/actions/comment.go b/api/types/actions/comment.go new file mode 100644 index 000000000..552aa3800 --- /dev/null +++ b/api/types/actions/comment.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import "github.com/go-vela/types/constants" + +// Comment is the API representation of the various actions associated +// with the comment event webhook from the SCM. +type Comment struct { + Created *bool `json:"created"` + Edited *bool `json:"edited"` +} + +// FromMask returns the Comment type resulting from the provided integer mask. +func (a *Comment) FromMask(mask int64) *Comment { + a.SetCreated(mask&constants.AllowCommentCreate > 0) + a.SetEdited(mask&constants.AllowCommentEdit > 0) + + return a +} + +// ToMask returns the integer mask of the values for the Comment set. +func (a *Comment) ToMask() int64 { + mask := int64(0) + + if a.GetCreated() { + mask = mask | constants.AllowCommentCreate + } + + if a.GetEdited() { + mask = mask | constants.AllowCommentEdit + } + + return mask +} + +// GetCreated returns the Created field from the provided Comment. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Comment) GetCreated() bool { + // return zero value if Events type or Created field is nil + if a == nil || a.Created == nil { + return false + } + + return *a.Created +} + +// GetEdited returns the Edited field from the provided Comment. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Comment) GetEdited() bool { + // return zero value if Events type or Edited field is nil + if a == nil || a.Edited == nil { + return false + } + + return *a.Edited +} + +// SetCreated sets the Comment Created field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (a *Comment) SetCreated(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.Created = &v +} + +// SetEdited sets the Comment Edited field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (a *Comment) SetEdited(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.Edited = &v +} diff --git a/api/types/actions/comment_test.go b/api/types/actions/comment_test.go new file mode 100644 index 000000000..1ffc465b8 --- /dev/null +++ b/api/types/actions/comment_test.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "reflect" + "testing" + + "github.com/go-vela/types/constants" +) + +func TestTypes_Comment_Getters(t *testing.T) { + // setup tests + tests := []struct { + actions *Comment + want *Comment + }{ + { + actions: testComment(), + want: testComment(), + }, + { + actions: new(Comment), + want: new(Comment), + }, + } + + // run tests + for _, test := range tests { + if test.actions.GetCreated() != test.want.GetCreated() { + t.Errorf("GetCreated is %v, want %v", test.actions.GetCreated(), test.want.GetCreated()) + } + + if test.actions.GetEdited() != test.want.GetEdited() { + t.Errorf("GetEdited is %v, want %v", test.actions.GetEdited(), test.want.GetEdited()) + } + } +} + +func TestTypes_Comment_Setters(t *testing.T) { + // setup types + var a *Comment + + // setup tests + tests := []struct { + actions *Comment + want *Comment + }{ + { + actions: testComment(), + want: testComment(), + }, + { + actions: a, + want: new(Comment), + }, + } + + // run tests + for _, test := range tests { + test.actions.SetCreated(test.want.GetCreated()) + test.actions.SetEdited(test.want.GetEdited()) + + if test.actions.GetCreated() != test.want.GetCreated() { + t.Errorf("SetCreated is %v, want %v", test.actions.GetCreated(), test.want.GetCreated()) + } + + if test.actions.GetEdited() != test.want.GetEdited() { + t.Errorf("SetEdited is %v, want %v", test.actions.GetEdited(), test.want.GetEdited()) + } + } +} + +func TestTypes_Comment_FromMask(t *testing.T) { + // setup types + mask := testMask() + + want := testComment() + + // run test + got := new(Comment).FromMask(mask) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromMask is %v, want %v", got, want) + } +} + +func TestTypes_Comment_ToMask(t *testing.T) { + // setup types + actions := testComment() + + want := int64(constants.AllowCommentCreate) + + // run test + got := actions.ToMask() + + if want != got { + t.Errorf("ToMask is %v, want %v", got, want) + } +} + +func testComment() *Comment { + comment := new(Comment) + comment.SetCreated(true) + comment.SetEdited(false) + + return comment +} diff --git a/api/types/actions/deploy.go b/api/types/actions/deploy.go new file mode 100644 index 000000000..5dd7a4242 --- /dev/null +++ b/api/types/actions/deploy.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +//nolint:dupl // similar code to schedule.go +package actions + +import "github.com/go-vela/types/constants" + +// Deploy is the API representation of the various actions associated +// with the deploy event webhook from the SCM. +type Deploy struct { + Created *bool `json:"created"` +} + +// FromMask returns the Deploy type resulting from the provided integer mask. +func (a *Deploy) FromMask(mask int64) *Deploy { + a.SetCreated(mask&constants.AllowDeployCreate > 0) + + return a +} + +// ToMask returns the integer mask of the values for the Deploy set. +func (a *Deploy) ToMask() int64 { + mask := int64(0) + + if a.GetCreated() { + mask = mask | constants.AllowDeployCreate + } + + return mask +} + +// GetCreated returns the Created field from the provided Deploy. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Deploy) GetCreated() bool { + // return zero value if Deploy type or Created field is nil + if a == nil || a.Created == nil { + return false + } + + return *a.Created +} + +// SetCreated sets the Deploy Created field. +// +// When the provided Deploy type is nil, it +// will set nothing and immediately return. +func (a *Deploy) SetCreated(v bool) { + // return if Deploy type is nil + if a == nil { + return + } + + a.Created = &v +} diff --git a/api/types/actions/deploy_test.go b/api/types/actions/deploy_test.go new file mode 100644 index 000000000..373407c7f --- /dev/null +++ b/api/types/actions/deploy_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "reflect" + "testing" + + "github.com/go-vela/types/constants" +) + +func TestTypes_Deploy_Getters(t *testing.T) { + // setup tests + tests := []struct { + actions *Deploy + want *Deploy + }{ + { + actions: testDeploy(), + want: testDeploy(), + }, + { + actions: new(Deploy), + want: new(Deploy), + }, + } + + // run tests + for _, test := range tests { + if test.actions.GetCreated() != test.want.GetCreated() { + t.Errorf("GetCreated is %v, want %v", test.actions.GetCreated(), test.want.GetCreated()) + } + } +} + +func TestTypes_Deploy_Setters(t *testing.T) { + // setup types + var a *Deploy + + // setup tests + tests := []struct { + actions *Deploy + want *Deploy + }{ + { + actions: testDeploy(), + want: testDeploy(), + }, + { + actions: a, + want: new(Deploy), + }, + } + + // run tests + for _, test := range tests { + test.actions.SetCreated(test.want.GetCreated()) + + if test.actions.GetCreated() != test.want.GetCreated() { + t.Errorf("SetCreated is %v, want %v", test.actions.GetCreated(), test.want.GetCreated()) + } + } +} + +func TestTypes_Deploy_FromMask(t *testing.T) { + // setup types + mask := testMask() + + want := testDeploy() + + // run test + got := new(Deploy).FromMask(mask) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromMask is %v, want %v", got, want) + } +} + +func TestTypes_Deploy_ToMask(t *testing.T) { + // setup types + actions := testDeploy() + + want := int64(constants.AllowDeployCreate) + + // run test + got := actions.ToMask() + + if want != got { + t.Errorf("ToMask is %v, want %v", got, want) + } +} + +func testDeploy() *Deploy { + deploy := new(Deploy) + deploy.SetCreated(true) + + return deploy +} diff --git a/api/types/actions/pull.go b/api/types/actions/pull.go new file mode 100644 index 000000000..fd35fe7eb --- /dev/null +++ b/api/types/actions/pull.go @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import "github.com/go-vela/types/constants" + +// Pull is the API representation of the various actions associated +// with the pull_request event webhook from the SCM. +type Pull struct { + Opened *bool `json:"opened"` + Edited *bool `json:"edited"` + Synchronize *bool `json:"synchronize"` + Reopened *bool `json:"reopened"` + Labeled *bool `json:"labeled"` + Unlabeled *bool `json:"unlabeled"` +} + +// FromMask returns the Pull type resulting from the provided integer mask. +func (a *Pull) FromMask(mask int64) *Pull { + a.SetOpened(mask&constants.AllowPullOpen > 0) + a.SetSynchronize(mask&constants.AllowPullSync > 0) + a.SetEdited(mask&constants.AllowPullEdit > 0) + a.SetReopened(mask&constants.AllowPullReopen > 0) + a.SetLabeled(mask&constants.AllowPullLabel > 0) + a.SetUnlabeled(mask&constants.AllowPullUnlabel > 0) + + return a +} + +// ToMask returns the integer mask of the values for the Pull set. +func (a *Pull) ToMask() int64 { + mask := int64(0) + + if a.GetOpened() { + mask = mask | constants.AllowPullOpen + } + + if a.GetSynchronize() { + mask = mask | constants.AllowPullSync + } + + if a.GetEdited() { + mask = mask | constants.AllowPullEdit + } + + if a.GetReopened() { + mask = mask | constants.AllowPullReopen + } + + if a.GetLabeled() { + mask = mask | constants.AllowPullLabel + } + + if a.GetUnlabeled() { + mask = mask | constants.AllowPullUnlabel + } + + return mask +} + +// GetOpened returns the Opened field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetOpened() bool { + // return zero value if Pull type or Opened field is nil + if a == nil || a.Opened == nil { + return false + } + + return *a.Opened +} + +// GetSynchronize returns the Synchronize field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetSynchronize() bool { + // return zero value if Pull type or Synchronize field is nil + if a == nil || a.Synchronize == nil { + return false + } + + return *a.Synchronize +} + +// GetEdited returns the Edited field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetEdited() bool { + // return zero value if Pull type or Edited field is nil + if a == nil || a.Edited == nil { + return false + } + + return *a.Edited +} + +// GetReopened returns the Reopened field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetReopened() bool { + // return zero value if Pull type or Reopened field is nil + if a == nil || a.Reopened == nil { + return false + } + + return *a.Reopened +} + +// GetLabeled returns the Labeled field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetLabeled() bool { + // return zero value if Pull type or Labeled field is nil + if a == nil || a.Labeled == nil { + return false + } + + return *a.Labeled +} + +// GetUnlabeled returns the Unlabeled field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetUnlabeled() bool { + // return zero value if Pull type or Unlabeled field is nil + if a == nil || a.Unlabeled == nil { + return false + } + + return *a.Unlabeled +} + +// SetOpened sets the Pull Opened field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetOpened(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Opened = &v +} + +// SetSynchronize sets the Pull Synchronize field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetSynchronize(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Synchronize = &v +} + +// SetEdited sets the Pull Edited field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetEdited(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Edited = &v +} + +// SetReopened sets the Pull Reopened field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetReopened(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Reopened = &v +} + +// SetLabeled sets the Pull Labeled field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetLabeled(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Labeled = &v +} + +// SetUnlabeled sets the Pull Unlabeled field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetUnlabeled(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Unlabeled = &v +} diff --git a/api/types/actions/pull_test.go b/api/types/actions/pull_test.go new file mode 100644 index 000000000..0ff17d6ad --- /dev/null +++ b/api/types/actions/pull_test.go @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "reflect" + "testing" + + "github.com/go-vela/types/constants" +) + +func TestActions_Pull_Getters(t *testing.T) { + // setup tests + tests := []struct { + actions *Pull + want *Pull + }{ + { + actions: testPull(), + want: testPull(), + }, + { + actions: new(Pull), + want: new(Pull), + }, + } + + // run tests + for _, test := range tests { + if test.actions.GetOpened() != test.want.GetOpened() { + t.Errorf("GetOpened is %v, want %v", test.actions.GetOpened(), test.want.GetOpened()) + } + + if test.actions.GetSynchronize() != test.want.GetSynchronize() { + t.Errorf("GetSynchronize is %v, want %v", test.actions.GetSynchronize(), test.want.GetSynchronize()) + } + + if test.actions.GetEdited() != test.want.GetEdited() { + t.Errorf("GetEdited is %v, want %v", test.actions.GetEdited(), test.want.GetEdited()) + } + + if test.actions.GetReopened() != test.want.GetReopened() { + t.Errorf("GetReopened is %v, want %v", test.actions.GetReopened(), test.want.GetReopened()) + } + + if test.actions.GetLabeled() != test.want.GetLabeled() { + t.Errorf("GetLabeled is %v, want %v", test.actions.GetLabeled(), test.want.GetLabeled()) + } + + if test.actions.GetUnlabeled() != test.want.GetUnlabeled() { + t.Errorf("GetUnlabeled is %v, want %v", test.actions.GetUnlabeled(), test.want.GetUnlabeled()) + } + } +} + +func TestActions_Pull_Setters(t *testing.T) { + // setup types + var a *Pull + + // setup tests + tests := []struct { + actions *Pull + want *Pull + }{ + { + actions: testPull(), + want: testPull(), + }, + { + actions: a, + want: new(Pull), + }, + } + + // run tests + for _, test := range tests { + test.actions.SetOpened(test.want.GetOpened()) + test.actions.SetSynchronize(test.want.GetSynchronize()) + test.actions.SetEdited(test.want.GetEdited()) + test.actions.SetReopened(test.want.GetReopened()) + test.actions.SetLabeled(test.want.GetLabeled()) + test.actions.SetUnlabeled(test.want.GetUnlabeled()) + + if test.actions.GetOpened() != test.want.GetOpened() { + t.Errorf("SetOpened is %v, want %v", test.actions.GetOpened(), test.want.GetOpened()) + } + + if test.actions.GetSynchronize() != test.want.GetSynchronize() { + t.Errorf("SetSynchronize is %v, want %v", test.actions.GetSynchronize(), test.want.GetSynchronize()) + } + + if test.actions.GetEdited() != test.want.GetEdited() { + t.Errorf("SetEdited is %v, want %v", test.actions.GetEdited(), test.want.GetEdited()) + } + + if test.actions.GetReopened() != test.want.GetReopened() { + t.Errorf("SetReopened is %v, want %v", test.actions.GetReopened(), test.want.GetReopened()) + } + + if test.actions.GetLabeled() != test.want.GetLabeled() { + t.Errorf("SetLabeled is %v, want %v", test.actions.GetLabeled(), test.want.GetLabeled()) + } + + if test.actions.GetUnlabeled() != test.want.GetUnlabeled() { + t.Errorf("SetUnlabeled is %v, want %v", test.actions.GetUnlabeled(), test.want.GetUnlabeled()) + } + } +} + +func TestActions_Pull_FromMask(t *testing.T) { + // setup types + mask := testMask() + + want := testPull() + + // run test + got := new(Pull).FromMask(mask) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromMask is %v, want %v", got, want) + } +} + +func TestActions_Pull_ToMask(t *testing.T) { + // setup types + actions := testPull() + + want := int64(constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen | constants.AllowPullUnlabel) + + // run test + got := actions.ToMask() + + if want != got { + t.Errorf("ToMask is %v, want %v", got, want) + } +} + +func testPull() *Pull { + pr := new(Pull) + pr.SetOpened(true) + pr.SetSynchronize(true) + pr.SetEdited(false) + pr.SetReopened(true) + pr.SetLabeled(false) + pr.SetUnlabeled(true) + + return pr +} diff --git a/api/types/actions/push.go b/api/types/actions/push.go new file mode 100644 index 000000000..6d5b24a6f --- /dev/null +++ b/api/types/actions/push.go @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import "github.com/go-vela/types/constants" + +// Push is the API representation of the various actions associated +// with the push event webhook from the SCM. +type Push struct { + Branch *bool `json:"branch"` + Tag *bool `json:"tag"` + DeleteBranch *bool `json:"delete_branch"` + DeleteTag *bool `json:"delete_tag"` +} + +// FromMask returns the Push type resulting from the provided integer mask. +func (a *Push) FromMask(mask int64) *Push { + a.SetBranch(mask&constants.AllowPushBranch > 0) + a.SetTag(mask&constants.AllowPushTag > 0) + a.SetDeleteBranch(mask&constants.AllowPushDeleteBranch > 0) + a.SetDeleteTag(mask&constants.AllowPushDeleteTag > 0) + + return a +} + +// ToMask returns the integer mask of the values for the Push set. +func (a *Push) ToMask() int64 { + mask := int64(0) + + if a.GetBranch() { + mask = mask | constants.AllowPushBranch + } + + if a.GetTag() { + mask = mask | constants.AllowPushTag + } + + if a.GetDeleteBranch() { + mask = mask | constants.AllowPushDeleteBranch + } + + if a.GetDeleteTag() { + mask = mask | constants.AllowPushDeleteTag + } + + return mask +} + +// GetBranch returns the Branch field from the provided Push. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Push) GetBranch() bool { + // return zero value if Push type or Branch field is nil + if a == nil || a.Branch == nil { + return false + } + + return *a.Branch +} + +// GetTag returns the Tag field from the provided Push. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Push) GetTag() bool { + // return zero value if Push type or Tag field is nil + if a == nil || a.Tag == nil { + return false + } + + return *a.Tag +} + +// GetDeleteBranch returns the DeleteBranch field from the provided Push. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Push) GetDeleteBranch() bool { + // return zero value if Push type or DeleteBranch field is nil + if a == nil || a.DeleteBranch == nil { + return false + } + + return *a.DeleteBranch +} + +// GetDeleteTag returns the DeleteTag field from the provided Push. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Push) GetDeleteTag() bool { + // return zero value if Push type or DeleteTag field is nil + if a == nil || a.DeleteTag == nil { + return false + } + + return *a.DeleteTag +} + +// SetBranch sets the Push Branch field. +// +// When the provided Push type is nil, it +// will set nothing and immediately return. +func (a *Push) SetBranch(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.Branch = &v +} + +// SetTag sets the Push Tag field. +// +// When the provided Push type is nil, it +// will set nothing and immediately return. +func (a *Push) SetTag(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.Tag = &v +} + +// SetDeleteBranch sets the Push DeleteBranch field. +// +// When the provided Push type is nil, it +// will set nothing and immediately return. +func (a *Push) SetDeleteBranch(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.DeleteBranch = &v +} + +// SetDeleteTag sets the Push DeleteTag field. +// +// When the provided Push type is nil, it +// will set nothing and immediately return. +func (a *Push) SetDeleteTag(v bool) { + // return if Events type is nil + if a == nil { + return + } + + a.DeleteTag = &v +} diff --git a/api/types/actions/push_test.go b/api/types/actions/push_test.go new file mode 100644 index 000000000..259f7db2b --- /dev/null +++ b/api/types/actions/push_test.go @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "reflect" + "testing" + + "github.com/go-vela/types/constants" +) + +func TestTypes_Push_Getters(t *testing.T) { + // setup tests + tests := []struct { + actions *Push + want *Push + }{ + { + actions: testPush(), + want: testPush(), + }, + { + actions: new(Push), + want: new(Push), + }, + } + + // run tests + for _, test := range tests { + if test.actions.GetBranch() != test.want.GetBranch() { + t.Errorf("GetBranch is %v, want %v", test.actions.GetBranch(), test.want.GetBranch()) + } + + if test.actions.GetTag() != test.want.GetTag() { + t.Errorf("GetTag is %v, want %v", test.actions.GetTag(), test.want.GetTag()) + } + } +} + +func TestTypes_Push_Setters(t *testing.T) { + // setup types + var a *Push + + // setup tests + tests := []struct { + actions *Push + want *Push + }{ + { + actions: testPush(), + want: testPush(), + }, + { + actions: a, + want: new(Push), + }, + } + + // run tests + for _, test := range tests { + test.actions.SetBranch(test.want.GetBranch()) + test.actions.SetTag(test.want.GetTag()) + test.actions.SetDeleteBranch(test.want.GetDeleteBranch()) + test.actions.SetDeleteTag(test.want.GetDeleteTag()) + + if test.actions.GetBranch() != test.want.GetBranch() { + t.Errorf("SetBranch is %v, want %v", test.actions.GetBranch(), test.want.GetBranch()) + } + + if test.actions.GetTag() != test.want.GetTag() { + t.Errorf("SetTag is %v, want %v", test.actions.GetTag(), test.want.GetTag()) + } + + if test.actions.GetDeleteBranch() != test.want.GetDeleteBranch() { + t.Errorf("SetDeleteBranch is %v, want %v", test.actions.GetDeleteBranch(), test.want.GetDeleteBranch()) + } + + if test.actions.GetDeleteTag() != test.want.GetDeleteTag() { + t.Errorf("SetDeleteTag is %v, want %v", test.actions.GetDeleteTag(), test.want.GetDeleteTag()) + } + } +} + +func TestTypes_Push_FromMask(t *testing.T) { + // setup types + mask := testMask() + + want := testPush() + + // run test + got := new(Push).FromMask(mask) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromMask is %v, want %v", got, want) + } +} + +func TestTypes_Push_ToMask(t *testing.T) { + // setup types + actions := testPush() + + want := int64(constants.AllowPushBranch | constants.AllowPushTag | constants.AllowPushDeleteBranch | constants.AllowPushDeleteTag) + + // run test + got := actions.ToMask() + + if want != got { + t.Errorf("ToMask is %v, want %v", got, want) + } +} + +func testPush() *Push { + push := new(Push) + push.SetBranch(true) + push.SetTag(true) + push.SetDeleteBranch(true) + push.SetDeleteTag(true) + + return push +} + +func testMask() int64 { + return int64( + constants.AllowPushBranch | + constants.AllowPushTag | + constants.AllowPushDeleteBranch | + constants.AllowPushDeleteTag | + constants.AllowPullOpen | + constants.AllowPullSync | + constants.AllowPullReopen | + constants.AllowPullUnlabel | + constants.AllowDeployCreate | + constants.AllowCommentCreate | + constants.AllowSchedule, + ) +} diff --git a/api/types/actions/schedule.go b/api/types/actions/schedule.go new file mode 100644 index 000000000..bc4588d2f --- /dev/null +++ b/api/types/actions/schedule.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +//nolint:dupl // similar code to deploy.go +package actions + +import "github.com/go-vela/types/constants" + +// Schedule is the API representation of the various actions associated +// with the schedule event. +type Schedule struct { + Run *bool `json:"run"` +} + +// FromMask returns the Schedule type resulting from the provided integer mask. +func (a *Schedule) FromMask(mask int64) *Schedule { + a.SetRun(mask&constants.AllowSchedule > 0) + + return a +} + +// ToMask returns the integer mask of the values for the Schedule set. +func (a *Schedule) ToMask() int64 { + mask := int64(0) + + if a.GetRun() { + mask = mask | constants.AllowSchedule + } + + return mask +} + +// GetRun returns the Run field from the provided Schedule. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Schedule) GetRun() bool { + // return zero value if Schedule type or Run field is nil + if a == nil || a.Run == nil { + return false + } + + return *a.Run +} + +// SetRun sets the Schedule Run field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (a *Schedule) SetRun(v bool) { + // return if Schedule type is nil + if a == nil { + return + } + + a.Run = &v +} diff --git a/api/types/actions/schedule_test.go b/api/types/actions/schedule_test.go new file mode 100644 index 000000000..8a06b0d75 --- /dev/null +++ b/api/types/actions/schedule_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "reflect" + "testing" + + "github.com/go-vela/types/constants" +) + +func TestTypes_Schedule_Getters(t *testing.T) { + // setup tests + tests := []struct { + actions *Schedule + want *Schedule + }{ + { + actions: testSchedule(), + want: testSchedule(), + }, + { + actions: new(Schedule), + want: new(Schedule), + }, + } + + // run tests + for _, test := range tests { + if test.actions.GetRun() != test.want.GetRun() { + t.Errorf("GetRun is %v, want %v", test.actions.GetRun(), test.want.GetRun()) + } + } +} + +func TestTypes_Schedule_Setters(t *testing.T) { + // setup types + var a *Schedule + + // setup tests + tests := []struct { + actions *Schedule + want *Schedule + }{ + { + actions: testSchedule(), + want: testSchedule(), + }, + { + actions: a, + want: new(Schedule), + }, + } + + // run tests + for _, test := range tests { + test.actions.SetRun(test.want.GetRun()) + + if test.actions.GetRun() != test.want.GetRun() { + t.Errorf("SetRun is %v, want %v", test.actions.GetRun(), test.want.GetRun()) + } + } +} + +func TestTypes_Schedule_FromMask(t *testing.T) { + // setup types + mask := testMask() + + want := testSchedule() + + // run test + got := new(Schedule).FromMask(mask) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromMask is %v, want %v", got, want) + } +} + +func TestTypes_Schedule_ToMask(t *testing.T) { + // setup types + actions := testSchedule() + + want := int64(constants.AllowSchedule) + + // run test + got := actions.ToMask() + + if want != got { + t.Errorf("ToMask is %v, want %v", got, want) + } +} + +func testSchedule() *Schedule { + schedule := new(Schedule) + schedule.SetRun(true) + + return schedule +} diff --git a/api/types/build.go b/api/types/build.go new file mode 100644 index 000000000..e44c7e21f --- /dev/null +++ b/api/types/build.go @@ -0,0 +1,1248 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "strings" + "time" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/raw" +) + +// Build is the API types representation of a build for a pipeline. +// +// swagger:model Build +type Build struct { + ID *int64 `json:"id,omitempty"` + Repo *Repo `json:"repo,omitempty"` + PipelineID *int64 `json:"pipeline_id,omitempty"` + Number *int `json:"number,omitempty"` + Parent *int `json:"parent,omitempty"` + Event *string `json:"event,omitempty"` + EventAction *string `json:"event_action,omitempty"` + Status *string `json:"status,omitempty"` + Error *string `json:"error,omitempty"` + Enqueued *int64 `json:"enqueued,omitempty"` + Created *int64 `json:"created,omitempty"` + Started *int64 `json:"started,omitempty"` + Finished *int64 `json:"finished,omitempty"` + Deploy *string `json:"deploy,omitempty"` + DeployNumber *int64 `json:"deploy_number,omitempty"` + DeployPayload *raw.StringSliceMap `json:"deploy_payload,omitempty"` + Clone *string `json:"clone,omitempty"` + Source *string `json:"source,omitempty"` + Title *string `json:"title,omitempty"` + Message *string `json:"message,omitempty"` + Commit *string `json:"commit,omitempty"` + Sender *string `json:"sender,omitempty"` + SenderSCMID *string `json:"sender_scm_id,omitempty"` + Author *string `json:"author,omitempty"` + Email *string `json:"email,omitempty"` + Link *string `json:"link,omitempty"` + Branch *string `json:"branch,omitempty"` + Ref *string `json:"ref,omitempty"` + BaseRef *string `json:"base_ref,omitempty"` + HeadRef *string `json:"head_ref,omitempty"` + Host *string `json:"host,omitempty"` + Runtime *string `json:"runtime,omitempty"` + Distribution *string `json:"distribution,omitempty"` + ApprovedAt *int64 `json:"approved_at,omitempty"` + ApprovedBy *string `json:"approved_by,omitempty"` +} + +// Duration calculates and returns the total amount of +// time the build ran for in a human-readable format. +func (b *Build) Duration() string { + // check if the build doesn't have a started timestamp + if b.GetStarted() == 0 { + return "..." + } + + // capture started unix timestamp from the build + started := time.Unix(b.GetStarted(), 0) + + // check if the build doesn't have a finished timestamp + if b.GetFinished() == 0 { + // return the duration in a human-readable form by + // subtracting the build started time from the + // current time rounded to the nearest second + return time.Since(started).Round(time.Second).String() + } + + // capture finished unix timestamp from the build + finished := time.Unix(b.GetFinished(), 0) + + // calculate the duration by subtracting the build + // started time from the build finished time + duration := finished.Sub(started) + + // return the duration in a human-readable form + return duration.String() +} + +// Environment returns a list of environment variables +// provided from the fields of the Build type. +func (b *Build) Environment(workspace, channel string) map[string]string { + envs := map[string]string{ + "VELA_BUILD_APPROVED_AT": ToString(b.GetApprovedAt()), + "VELA_BUILD_APPROVED_BY": ToString(b.GetApprovedBy()), + "VELA_BUILD_AUTHOR": ToString(b.GetAuthor()), + "VELA_BUILD_AUTHOR_EMAIL": ToString(b.GetEmail()), + "VELA_BUILD_BASE_REF": ToString(b.GetBaseRef()), + "VELA_BUILD_BRANCH": ToString(b.GetBranch()), + "VELA_BUILD_CHANNEL": ToString(channel), + "VELA_BUILD_CLONE": ToString(b.GetClone()), + "VELA_BUILD_COMMIT": ToString(b.GetCommit()), + "VELA_BUILD_CREATED": ToString(b.GetCreated()), + "VELA_BUILD_DISTRIBUTION": ToString(b.GetDistribution()), + "VELA_BUILD_ENQUEUED": ToString(b.GetEnqueued()), + "VELA_BUILD_EVENT": ToString(b.GetEvent()), + "VELA_BUILD_EVENT_ACTION": ToString(b.GetEventAction()), + "VELA_BUILD_HOST": ToString(b.GetHost()), + "VELA_BUILD_LINK": ToString(b.GetLink()), + "VELA_BUILD_MESSAGE": ToString(b.GetMessage()), + "VELA_BUILD_NUMBER": ToString(b.GetNumber()), + "VELA_BUILD_PARENT": ToString(b.GetParent()), + "VELA_BUILD_REF": ToString(b.GetRef()), + "VELA_BUILD_RUNTIME": ToString(b.GetRuntime()), + "VELA_BUILD_SENDER": ToString(b.GetSender()), + "VELA_BUILD_SENDER_SCM_ID": ToString(b.GetSenderSCMID()), + "VELA_BUILD_STARTED": ToString(b.GetStarted()), + "VELA_BUILD_SOURCE": ToString(b.GetSource()), + "VELA_BUILD_STATUS": ToString(b.GetStatus()), + "VELA_BUILD_TITLE": ToString(b.GetTitle()), + "VELA_BUILD_WORKSPACE": ToString(workspace), + + // deprecated environment variables + "BUILD_AUTHOR": ToString(b.GetAuthor()), + "BUILD_AUTHOR_EMAIL": ToString(b.GetEmail()), + "BUILD_BASE_REF": ToString(b.GetBaseRef()), + "BUILD_BRANCH": ToString(b.GetBranch()), + "BUILD_CHANNEL": ToString(channel), + "BUILD_CLONE": ToString(b.GetClone()), + "BUILD_COMMIT": ToString(b.GetCommit()), + "BUILD_CREATED": ToString(b.GetCreated()), + "BUILD_ENQUEUED": ToString(b.GetEnqueued()), + "BUILD_EVENT": ToString(b.GetEvent()), + "BUILD_HOST": ToString(b.GetHost()), + "BUILD_LINK": ToString(b.GetLink()), + "BUILD_MESSAGE": ToString(b.GetMessage()), + "BUILD_NUMBER": ToString(b.GetNumber()), + "BUILD_PARENT": ToString(b.GetParent()), + "BUILD_REF": ToString(b.GetRef()), + "BUILD_SENDER": ToString(b.GetSender()), + "BUILD_STARTED": ToString(b.GetStarted()), + "BUILD_SOURCE": ToString(b.GetSource()), + "BUILD_STATUS": ToString(b.GetStatus()), + "BUILD_TITLE": ToString(b.GetTitle()), + "BUILD_WORKSPACE": ToString(workspace), + } + + // check if the Build event is comment + if strings.EqualFold(b.GetEvent(), constants.EventComment) { + // capture the pull request number + number := ToString(strings.SplitN(b.GetRef(), "/", 4)[2]) + + // add the pull request number to the list + envs["BUILD_PULL_REQUEST_NUMBER"] = number + envs["VELA_BUILD_PULL_REQUEST"] = number + envs["VELA_PULL_REQUEST"] = number + envs["VELA_PULL_REQUEST_SOURCE"] = b.GetHeadRef() + envs["VELA_PULL_REQUEST_TARGET"] = b.GetBaseRef() + } + + // check if the Build event is deployment + if strings.EqualFold(b.GetEvent(), constants.EventDeploy) { + // capture the deployment target + target := ToString(b.GetDeploy()) + + // add the deployment target to the list + envs["VELA_BUILD_TARGET"] = target + envs["VELA_DEPLOYMENT"] = target + envs["BUILD_TARGET"] = target + envs["VELA_DEPLOYMENT_NUMBER"] = ToString(b.GetDeployNumber()) + + // handle when deployment event is for a tag + if strings.HasPrefix(b.GetRef(), "refs/tags/") { + // capture the tag reference + tag := ToString(strings.SplitN(b.GetRef(), "refs/tags/", 2)[1]) + + // add the tag reference to the list + envs["BUILD_TAG"] = tag + envs["VELA_BUILD_TAG"] = tag + } + + // add payload data to the list + for key, value := range b.GetDeployPayload() { + envs[fmt.Sprintf("DEPLOYMENT_PARAMETER_%s", strings.ToUpper(key))] = value + } + } + + // check if the Build event is pull_request + if strings.EqualFold(b.GetEvent(), constants.EventPull) { + // capture the pull request number + number := ToString(strings.SplitN(b.GetRef(), "/", 4)[2]) + + // add the pull request number to the list + envs["BUILD_PULL_REQUEST_NUMBER"] = number + envs["VELA_BUILD_PULL_REQUEST"] = number + envs["VELA_PULL_REQUEST"] = number + envs["VELA_PULL_REQUEST_SOURCE"] = b.GetHeadRef() + envs["VELA_PULL_REQUEST_TARGET"] = b.GetBaseRef() + } + + // check if the Build event is tag + if strings.EqualFold(b.GetEvent(), constants.EventTag) { + // capture the tag reference + tag := ToString(strings.SplitN(b.GetRef(), "refs/tags/", 2)[1]) + + // add the tag reference to the list + envs["BUILD_TAG"] = tag + envs["VELA_BUILD_TAG"] = tag + } + + // check if the Build event is delete:tag + if strings.EqualFold(b.GetEvent(), constants.EventDelete) && strings.EqualFold(b.GetEventAction(), constants.ActionTag) { + // capture the tag reference, which has been stored in the Branch variable due to issues that arose + // when the Ref is set to the deleted tag + tag := b.GetBranch() + + // add the tag reference to the list + envs["BUILD_TAG"] = tag + envs["VELA_BUILD_TAG"] = tag + } + + return envs +} + +// GetID returns the ID field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetID() int64 { + // return zero value if Build type or ID field is nil + if b == nil || b.ID == nil { + return 0 + } + + return *b.ID +} + +// GetRepo returns the Repo field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetRepo() *Repo { + // return zero value if Build type or Repo field is nil + if b == nil || b.Repo == nil { + return new(Repo) + } + + return b.Repo +} + +// GetPipelineID returns the PipelineID field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetPipelineID() int64 { + // return zero value if Build type or PipelineID field is nil + if b == nil || b.PipelineID == nil { + return 0 + } + + return *b.PipelineID +} + +// GetNumber returns the Number field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetNumber() int { + // return zero value if Build type or Number field is nil + if b == nil || b.Number == nil { + return 0 + } + + return *b.Number +} + +// GetParent returns the Parent field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetParent() int { + // return zero value if Build type or Parent field is nil + if b == nil || b.Parent == nil { + return 0 + } + + return *b.Parent +} + +// GetEvent returns the Event field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetEvent() string { + // return zero value if Build type or Event field is nil + if b == nil || b.Event == nil { + return "" + } + + return *b.Event +} + +// GetEventAction returns the EventAction field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetEventAction() string { + // return zero value if Build type or EventAction field is nil + if b == nil || b.EventAction == nil { + return "" + } + + return *b.EventAction +} + +// GetStatus returns the Status field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetStatus() string { + // return zero value if Build type or Status field is nil + if b == nil || b.Status == nil { + return "" + } + + return *b.Status +} + +// GetError returns the Error field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetError() string { + // return zero value if Build type or Error field is nil + if b == nil || b.Error == nil { + return "" + } + + return *b.Error +} + +// GetEnqueued returns the Enqueued field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetEnqueued() int64 { + // return zero value if Build type or Enqueued field is nil + if b == nil || b.Enqueued == nil { + return 0 + } + + return *b.Enqueued +} + +// GetCreated returns the Created field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetCreated() int64 { + // return zero value if Build type or Created field is nil + if b == nil || b.Created == nil { + return 0 + } + + return *b.Created +} + +// GetStarted returns the Started field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetStarted() int64 { + // return zero value if Build type or Started field is nil + if b == nil || b.Started == nil { + return 0 + } + + return *b.Started +} + +// GetFinished returns the Finished field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetFinished() int64 { + // return zero value if Build type or Finished field is nil + if b == nil || b.Finished == nil { + return 0 + } + + return *b.Finished +} + +// GetDeploy returns the Deploy field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetDeploy() string { + // return zero value if Build type or Deploy field is nil + if b == nil || b.Deploy == nil { + return "" + } + + return *b.Deploy +} + +// GetDeployNumber returns the DeployNumber field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetDeployNumber() int64 { + // return zero value if Build type or Deploy field is nil + if b == nil || b.DeployNumber == nil { + return 0 + } + + return *b.DeployNumber +} + +// GetDeployPayload returns the DeployPayload field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetDeployPayload() raw.StringSliceMap { + // return zero value if Build type or Deploy field is nil + if b == nil || b.DeployPayload == nil { + return raw.StringSliceMap{} + } + + return *b.DeployPayload +} + +// GetClone returns the Clone field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetClone() string { + // return zero value if Build type or Clone field is nil + if b == nil || b.Clone == nil { + return "" + } + + return *b.Clone +} + +// GetSource returns the Source field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetSource() string { + // return zero value if Build type or Source field is nil + if b == nil || b.Source == nil { + return "" + } + + return *b.Source +} + +// GetTitle returns the Title field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetTitle() string { + // return zero value if Build type or Title field is nil + if b == nil || b.Title == nil { + return "" + } + + return *b.Title +} + +// GetMessage returns the Message field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetMessage() string { + // return zero value if Build type or Message field is nil + if b == nil || b.Message == nil { + return "" + } + + return *b.Message +} + +// GetCommit returns the Commit field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetCommit() string { + // return zero value if Build type or Commit field is nil + if b == nil || b.Commit == nil { + return "" + } + + return *b.Commit +} + +// GetSender returns the Sender field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetSender() string { + // return zero value if Build type or Sender field is nil + if b == nil || b.Sender == nil { + return "" + } + + return *b.Sender +} + +// GetSenderSCMID returns the SenderSCMID field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetSenderSCMID() string { + // return zero value if Build type or SenderSCMID field is nil + if b == nil || b.SenderSCMID == nil { + return "" + } + + return *b.SenderSCMID +} + +// GetAuthor returns the Author field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetAuthor() string { + // return zero value if Build type or Author field is nil + if b == nil || b.Author == nil { + return "" + } + + return *b.Author +} + +// GetEmail returns the Email field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetEmail() string { + // return zero value if Build type or Email field is nil + if b == nil || b.Email == nil { + return "" + } + + return *b.Email +} + +// GetLink returns the Link field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetLink() string { + // return zero value if Build type or Link field is nil + if b == nil || b.Link == nil { + return "" + } + + return *b.Link +} + +// GetBranch returns the Branch field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetBranch() string { + // return zero value if Build type or Branch field is nil + if b == nil || b.Branch == nil { + return "" + } + + return *b.Branch +} + +// GetRef returns the Ref field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetRef() string { + // return zero value if Build type or Ref field is nil + if b == nil || b.Ref == nil { + return "" + } + + return *b.Ref +} + +// GetBaseRef returns the BaseRef field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetBaseRef() string { + // return zero value if Build type or BaseRef field is nil + if b == nil || b.BaseRef == nil { + return "" + } + + return *b.BaseRef +} + +// GetHeadRef returns the HeadRef field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetHeadRef() string { + // return zero value if Build type or HeadRef field is nil + if b == nil || b.HeadRef == nil { + return "" + } + + return *b.HeadRef +} + +// GetHost returns the Host field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetHost() string { + // return zero value if Build type or Host field is nil + if b == nil || b.Host == nil { + return "" + } + + return *b.Host +} + +// GetRuntime returns the Runtime field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetRuntime() string { + // return zero value if Build type or Runtime field is nil + if b == nil || b.Runtime == nil { + return "" + } + + return *b.Runtime +} + +// GetDistribution returns the Distribution field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetDistribution() string { + // return zero value if Build type or Distribution field is nil + if b == nil || b.Distribution == nil { + return "" + } + + return *b.Distribution +} + +// GetApprovedAt returns the ApprovedAt field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetApprovedAt() int64 { + // return zero value if Build type or ApprovedAt field is nil + if b == nil || b.ApprovedAt == nil { + return 0 + } + + return *b.ApprovedAt +} + +// GetApprovedBy returns the ApprovedBy field. +// +// When the provided Build type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *Build) GetApprovedBy() string { + // return zero value if Build type or ApprovedBy field is nil + if b == nil || b.ApprovedBy == nil { + return "" + } + + return *b.ApprovedBy +} + +// SetID sets the ID field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetID(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.ID = &v +} + +// SetRepo sets the Repo field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetRepo(v *Repo) { + // return if Build type is nil + if b == nil { + return + } + + b.Repo = v +} + +// SetPipelineID sets the PipelineID field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetPipelineID(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.PipelineID = &v +} + +// SetNumber sets the Number field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetNumber(v int) { + // return if Build type is nil + if b == nil { + return + } + + b.Number = &v +} + +// SetParent sets the Parent field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetParent(v int) { + // return if Build type is nil + if b == nil { + return + } + + b.Parent = &v +} + +// SetEvent sets the Event field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetEvent(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Event = &v +} + +// SetEventAction sets the EventAction field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetEventAction(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.EventAction = &v +} + +// SetStatus sets the Status field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetStatus(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Status = &v +} + +// SetError sets the Error field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetError(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Error = &v +} + +// SetEnqueued sets the Enqueued field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetEnqueued(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.Enqueued = &v +} + +// SetCreated sets the Created field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetCreated(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.Created = &v +} + +// SetStarted sets the Started field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetStarted(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.Started = &v +} + +// SetFinished sets the Finished field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetFinished(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.Finished = &v +} + +// SetDeploy sets the Deploy field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetDeploy(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Deploy = &v +} + +// SetDeployNumber sets the DeployNumber field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetDeployNumber(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.DeployNumber = &v +} + +// SetDeployPayload sets the DeployPayload field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetDeployPayload(v raw.StringSliceMap) { + // return if Build type is nil + if b == nil { + return + } + + b.DeployPayload = &v +} + +// SetClone sets the Clone field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetClone(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Clone = &v +} + +// SetSource sets the Source field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetSource(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Source = &v +} + +// SetTitle sets the Title field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetTitle(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Title = &v +} + +// SetMessage sets the Message field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetMessage(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Message = &v +} + +// SetCommit sets the Commit field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetCommit(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Commit = &v +} + +// SetSender sets the Sender field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetSender(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Sender = &v +} + +// SetSenderSCMID sets the SenderSCMID field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetSenderSCMID(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.SenderSCMID = &v +} + +// SetAuthor sets the Author field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetAuthor(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Author = &v +} + +// SetEmail sets the Email field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetEmail(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Email = &v +} + +// SetLink sets the Link field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetLink(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Link = &v +} + +// SetBranch sets the Branch field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetBranch(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Branch = &v +} + +// SetRef sets the Ref field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetRef(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Ref = &v +} + +// SetBaseRef sets the BaseRef field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetBaseRef(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.BaseRef = &v +} + +// SetHeadRef sets the HeadRef field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetHeadRef(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.HeadRef = &v +} + +// SetHost sets the Host field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetHost(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Host = &v +} + +// SetRuntime sets the Runtime field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetRuntime(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Runtime = &v +} + +// SetDistribution sets the Distribution field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetDistribution(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.Distribution = &v +} + +// SetApprovedAt sets the ApprovedAt field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetApprovedAt(v int64) { + // return if Build type is nil + if b == nil { + return + } + + b.ApprovedAt = &v +} + +// SetApprovedBy sets the ApprovedBy field. +// +// When the provided Build type is nil, it +// will set nothing and immediately return. +func (b *Build) SetApprovedBy(v string) { + // return if Build type is nil + if b == nil { + return + } + + b.ApprovedBy = &v +} + +// String implements the Stringer interface for the Build type. +// +//nolint:dupl // this is duplicated in the test +func (b *Build) String() string { + return fmt.Sprintf(`{ + ApprovedAt: %d, + ApprovedBy: %s, + Author: %s, + BaseRef: %s, + Branch: %s, + Clone: %s, + Commit: %s, + Created: %d, + Deploy: %s, + DeployNumber: %d, + DeployPayload: %s, + Distribution: %s, + Email: %s, + Enqueued: %d, + Error: %s, + Event: %s, + EventAction: %s, + Finished: %d, + HeadRef: %s, + Host: %s, + ID: %d, + Link: %s, + Message: %s, + Number: %d, + Parent: %d, + PipelineID: %d, + Ref: %s, + Repo: %s, + Runtime: %s, + Sender: %s, + SenderSCMID: %s, + Source: %s, + Started: %d, + Status: %s, + Title: %s, +}`, + b.GetApprovedAt(), + b.GetApprovedBy(), + b.GetAuthor(), + b.GetBaseRef(), + b.GetBranch(), + b.GetClone(), + b.GetCommit(), + b.GetCreated(), + b.GetDeploy(), + b.GetDeployNumber(), + b.GetDeployPayload(), + b.GetDistribution(), + b.GetEmail(), + b.GetEnqueued(), + b.GetError(), + b.GetEvent(), + b.GetEventAction(), + b.GetFinished(), + b.GetHeadRef(), + b.GetHost(), + b.GetID(), + b.GetLink(), + b.GetMessage(), + b.GetNumber(), + b.GetParent(), + b.GetPipelineID(), + b.GetRef(), + b.GetRepo().GetFullName(), + b.GetRuntime(), + b.GetSender(), + b.GetSenderSCMID(), + b.GetSource(), + b.GetStarted(), + b.GetStatus(), + b.GetTitle(), + ) +} + +// TODO: remove this when Deployment is moved from types and uses Build type instead of library type. +func (b *Build) ToLibrary() *library.Build { + return &library.Build{ + ID: b.ID, + RepoID: b.GetRepo().ID, + PipelineID: b.PipelineID, + Number: b.Number, + Parent: b.Parent, + Event: b.Event, + EventAction: b.EventAction, + Status: b.Status, + Error: b.Error, + Enqueued: b.Enqueued, + Created: b.Created, + Started: b.Started, + Finished: b.Finished, + Deploy: b.Deploy, + DeployNumber: b.DeployNumber, + DeployPayload: b.DeployPayload, + Clone: b.Clone, + Source: b.Source, + Title: b.Title, + Message: b.Message, + Commit: b.Commit, + Sender: b.Sender, + Author: b.Author, + Email: b.Email, + Link: b.Link, + Branch: b.Branch, + Ref: b.Ref, + BaseRef: b.BaseRef, + HeadRef: b.HeadRef, + Host: b.Host, + Runtime: b.Runtime, + Distribution: b.Distribution, + ApprovedAt: b.ApprovedAt, + ApprovedBy: b.ApprovedBy, + } +} diff --git a/api/types/build_test.go b/api/types/build_test.go new file mode 100644 index 000000000..dd6d70483 --- /dev/null +++ b/api/types/build_test.go @@ -0,0 +1,942 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + "github.com/go-vela/types/raw" +) + +func TestTypes_Build_Duration(t *testing.T) { + // setup types + unfinished := testBuild() + unfinished.SetFinished(0) + + // setup tests + tests := []struct { + build *Build + want string + }{ + { + build: testBuild(), + want: "1s", + }, + { + build: unfinished, + want: time.Since(time.Unix(unfinished.GetStarted(), 0)).Round(time.Second).String(), + }, + { + build: new(Build), + want: "...", + }, + } + + // run tests + for _, test := range tests { + got := test.build.Duration() + + if got != test.want { + t.Errorf("Duration is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Build_Environment(t *testing.T) { + // setup types + _comment := testBuild() + _comment.SetEvent("comment") + _comment.SetEventAction("created") + _comment.SetRef("refs/pulls/1/head") + _comment.SetHeadRef("dev") + _comment.SetBaseRef("main") + + _deploy := testBuild() + _deploy.SetEvent("deployment") + _deploy.SetDeploy("production") + _deploy.SetDeployNumber(0) + _deploy.SetDeployPayload(map[string]string{ + "foo": "test1", + "bar": "test2", + }) + + _deployTag := testBuild() + _deployTag.SetRef("refs/tags/v0.1.0") + _deployTag.SetEvent("deployment") + _deployTag.SetDeploy("production") + _deployTag.SetDeployNumber(0) + _deployTag.SetDeployPayload(map[string]string{ + "foo": "test1", + "bar": "test2", + }) + + _pull := testBuild() + _pull.SetEvent("pull_request") + _pull.SetEventAction("opened") + _pull.SetRef("refs/pulls/1/head") + + _tag := testBuild() + _tag.SetEvent("tag") + _tag.SetRef("refs/tags/v0.1.0") + + // setup tests + tests := []struct { + build *Build + want map[string]string + }{ + { + build: testBuild(), + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "push", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/heads/main", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "push", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/heads/main", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + }, + }, + { + build: _comment, + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "main", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "comment", + "VELA_BUILD_EVENT_ACTION": "created", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_PULL_REQUEST": "1", + "VELA_BUILD_REF": "refs/pulls/1/head", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "VELA_PULL_REQUEST": "1", + "VELA_PULL_REQUEST_SOURCE": "dev", + "VELA_PULL_REQUEST_TARGET": "main", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "main", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "comment", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_PULL_REQUEST_NUMBER": "1", + "BUILD_REF": "refs/pulls/1/head", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + }, + }, + { + build: _deploy, + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "deployment", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/heads/main", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TARGET": "production", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "VELA_DEPLOYMENT": "production", + "VELA_DEPLOYMENT_NUMBER": "0", + "BUILD_TARGET": "production", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "deployment", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/heads/main", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + "DEPLOYMENT_PARAMETER_FOO": "test1", + "DEPLOYMENT_PARAMETER_BAR": "test2", + }, + }, + { + build: _deployTag, + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "deployment", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/tags/v0.1.0", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TAG": "v0.1.0", + "VELA_BUILD_TARGET": "production", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "VELA_DEPLOYMENT": "production", + "VELA_DEPLOYMENT_NUMBER": "0", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "deployment", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/tags/v0.1.0", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TAG": "v0.1.0", + "BUILD_TARGET": "production", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + "DEPLOYMENT_PARAMETER_FOO": "test1", + "DEPLOYMENT_PARAMETER_BAR": "test2", + }, + }, + { + build: _pull, + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "pull_request", + "VELA_BUILD_EVENT_ACTION": "opened", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_PULL_REQUEST": "1", + "VELA_BUILD_REF": "refs/pulls/1/head", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "VELA_PULL_REQUEST": "1", + "VELA_PULL_REQUEST_SOURCE": "changes", + "VELA_PULL_REQUEST_TARGET": "", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "pull_request", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_PULL_REQUEST_NUMBER": "1", + "BUILD_REF": "refs/pulls/1/head", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + }, + }, + { + build: _tag, + want: map[string]string{ + "VELA_BUILD_APPROVED_AT": "1563474076", + "VELA_BUILD_APPROVED_BY": "OctoCat", + "VELA_BUILD_AUTHOR": "OctoKitty", + "VELA_BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "VELA_BUILD_BASE_REF": "", + "VELA_BUILD_BRANCH": "main", + "VELA_BUILD_CHANNEL": "TODO", + "VELA_BUILD_CLONE": "https://github.com/github/octocat.git", + "VELA_BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_CREATED": "1563474076", + "VELA_BUILD_DISTRIBUTION": "linux", + "VELA_BUILD_ENQUEUED": "1563474077", + "VELA_BUILD_EVENT": "tag", + "VELA_BUILD_EVENT_ACTION": "", + "VELA_BUILD_HOST": "example.company.com", + "VELA_BUILD_LINK": "https://example.company.com/github/octocat/1", + "VELA_BUILD_MESSAGE": "First commit...", + "VELA_BUILD_NUMBER": "1", + "VELA_BUILD_PARENT": "1", + "VELA_BUILD_REF": "refs/tags/v0.1.0", + "VELA_BUILD_RUNTIME": "docker", + "VELA_BUILD_SENDER": "OctoKitty", + "VELA_BUILD_SENDER_SCM_ID": "123", + "VELA_BUILD_STARTED": "1563474078", + "VELA_BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "VELA_BUILD_STATUS": "running", + "VELA_BUILD_TAG": "v0.1.0", + "VELA_BUILD_TITLE": "push received from https://github.com/github/octocat", + "VELA_BUILD_WORKSPACE": "TODO", + "BUILD_AUTHOR": "OctoKitty", + "BUILD_AUTHOR_EMAIL": "OctoKitty@github.com", + "BUILD_BASE_REF": "", + "BUILD_BRANCH": "main", + "BUILD_CHANNEL": "TODO", + "BUILD_CLONE": "https://github.com/github/octocat.git", + "BUILD_COMMIT": "48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_CREATED": "1563474076", + "BUILD_ENQUEUED": "1563474077", + "BUILD_EVENT": "tag", + "BUILD_HOST": "example.company.com", + "BUILD_LINK": "https://example.company.com/github/octocat/1", + "BUILD_MESSAGE": "First commit...", + "BUILD_NUMBER": "1", + "BUILD_PARENT": "1", + "BUILD_REF": "refs/tags/v0.1.0", + "BUILD_SENDER": "OctoKitty", + "BUILD_STARTED": "1563474078", + "BUILD_SOURCE": "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", + "BUILD_STATUS": "running", + "BUILD_TAG": "v0.1.0", + "BUILD_TITLE": "push received from https://github.com/github/octocat", + "BUILD_WORKSPACE": "TODO", + }, + }, + } + + // run test + for _, test := range tests { + got := test.build.Environment("TODO", "TODO") + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("(Environment: -want +got):\n%s", diff) + } + } +} + +func TestTypes_Build_Getters(t *testing.T) { + // setup tests + tests := []struct { + build *Build + want *Build + }{ + { + build: testBuild(), + want: testBuild(), + }, + { + build: new(Build), + want: new(Build), + }, + } + + // run tests + for _, test := range tests { + if test.build.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.build.GetID(), test.want.GetID()) + } + + if !reflect.DeepEqual(test.build.GetRepo(), test.want.GetRepo()) { + t.Errorf("GetRepo is %v, want %v", test.build.GetRepo(), test.want.GetRepo()) + } + + if test.build.GetPipelineID() != test.want.GetPipelineID() { + t.Errorf("GetPipelineID is %v, want %v", test.build.GetPipelineID(), test.want.GetPipelineID()) + } + + if test.build.GetNumber() != test.want.GetNumber() { + t.Errorf("GetNumber is %v, want %v", test.build.GetNumber(), test.want.GetNumber()) + } + + if test.build.GetParent() != test.want.GetParent() { + t.Errorf("GetParent is %v, want %v", test.build.GetParent(), test.want.GetParent()) + } + + if test.build.GetEvent() != test.want.GetEvent() { + t.Errorf("GetEvent is %v, want %v", test.build.GetEvent(), test.want.GetEvent()) + } + + if test.build.GetEventAction() != test.want.GetEventAction() { + t.Errorf("GetEventAction is %v, want %v", test.build.GetEventAction(), test.want.GetEventAction()) + } + + if test.build.GetStatus() != test.want.GetStatus() { + t.Errorf("GetStatus is %v, want %v", test.build.GetStatus(), test.want.GetStatus()) + } + + if test.build.GetError() != test.want.GetError() { + t.Errorf("GetError is %v, want %v", test.build.GetError(), test.want.GetError()) + } + + if test.build.GetEnqueued() != test.want.GetEnqueued() { + t.Errorf("GetEnqueued is %v, want %v", test.build.GetEnqueued(), test.want.GetEnqueued()) + } + + if test.build.GetCreated() != test.want.GetCreated() { + t.Errorf("GetCreated is %v, want %v", test.build.GetCreated(), test.want.GetCreated()) + } + + if test.build.GetStarted() != test.want.GetStarted() { + t.Errorf("GetStarted is %v, want %v", test.build.GetStarted(), test.want.GetStarted()) + } + + if test.build.GetFinished() != test.want.GetFinished() { + t.Errorf("GetFinished is %v, want %v", test.build.GetFinished(), test.want.GetFinished()) + } + + if test.build.GetDeploy() != test.want.GetDeploy() { + t.Errorf("GetDeploy is %v, want %v", test.build.GetDeploy(), test.want.GetDeploy()) + } + + if test.build.GetDeployNumber() != test.want.GetDeployNumber() { + t.Errorf("GetDeployNumber is %v, want %v", test.build.GetDeployNumber(), test.want.GetDeployNumber()) + } + + if !reflect.DeepEqual(test.build.GetDeployPayload(), test.want.GetDeployPayload()) { + t.Errorf("GetDeployPayload is %v, want %v", test.build.GetDeployPayload(), test.want.GetDeployPayload()) + } + + if test.build.GetClone() != test.want.GetClone() { + t.Errorf("GetClone is %v, want %v", test.build.GetClone(), test.want.GetClone()) + } + + if test.build.GetSource() != test.want.GetSource() { + t.Errorf("GetSource is %v, want %v", test.build.GetSource(), test.want.GetSource()) + } + + if test.build.GetTitle() != test.want.GetTitle() { + t.Errorf("GetTitle is %v, want %v", test.build.GetTitle(), test.want.GetTitle()) + } + + if test.build.GetMessage() != test.want.GetMessage() { + t.Errorf("GetMessage is %v, want %v", test.build.GetMessage(), test.want.GetMessage()) + } + + if test.build.GetCommit() != test.want.GetCommit() { + t.Errorf("GetCommit is %v, want %v", test.build.GetCommit(), test.want.GetCommit()) + } + + if test.build.GetSender() != test.want.GetSender() { + t.Errorf("GetSender is %v, want %v", test.build.GetSender(), test.want.GetSender()) + } + + if test.build.GetAuthor() != test.want.GetAuthor() { + t.Errorf("GetAuthor is %v, want %v", test.build.GetAuthor(), test.want.GetAuthor()) + } + + if test.build.GetEmail() != test.want.GetEmail() { + t.Errorf("GetEmail is %v, want %v", test.build.GetEmail(), test.want.GetEmail()) + } + + if test.build.GetLink() != test.want.GetLink() { + t.Errorf("GetLink is %v, want %v", test.build.GetLink(), test.want.GetLink()) + } + + if test.build.GetBranch() != test.want.GetBranch() { + t.Errorf("GetBranch is %v, want %v", test.build.GetBranch(), test.want.GetBranch()) + } + + if test.build.GetRef() != test.want.GetRef() { + t.Errorf("GetRef is %v, want %v", test.build.GetRef(), test.want.GetRef()) + } + + if test.build.GetBaseRef() != test.want.GetBaseRef() { + t.Errorf("GetBaseRef is %v, want %v", test.build.GetBaseRef(), test.want.GetBaseRef()) + } + + if test.build.GetHeadRef() != test.want.GetHeadRef() { + t.Errorf("GetHeadRef is %v, want %v", test.build.GetHeadRef(), test.want.GetHeadRef()) + } + + if test.build.GetHost() != test.want.GetHost() { + t.Errorf("GetHost is %v, want %v", test.build.GetHost(), test.want.GetHost()) + } + + if test.build.GetRuntime() != test.want.GetRuntime() { + t.Errorf("GetRuntime is %v, want %v", test.build.GetRuntime(), test.want.GetRuntime()) + } + + if test.build.GetDistribution() != test.want.GetDistribution() { + t.Errorf("GetDistribution is %v, want %v", test.build.GetDistribution(), test.want.GetDistribution()) + } + + if test.build.GetApprovedAt() != test.want.GetApprovedAt() { + t.Errorf("GetApprovedAt is %v, want %v", test.build.GetApprovedAt(), test.want.GetApprovedAt()) + } + + if test.build.GetApprovedBy() != test.want.GetApprovedBy() { + t.Errorf("GetApprovedBy is %v, want %v", test.build.GetApprovedBy(), test.want.GetApprovedBy()) + } + } +} + +func TestTypes_Build_Setters(t *testing.T) { + // setup types + var b *Build + + // setup tests + tests := []struct { + build *Build + want *Build + }{ + { + build: testBuild(), + want: testBuild(), + }, + { + build: b, + want: new(Build), + }, + } + + // run tests + for _, test := range tests { + test.build.SetID(test.want.GetID()) + test.build.SetRepo(test.want.GetRepo()) + test.build.SetPipelineID(test.want.GetPipelineID()) + test.build.SetNumber(test.want.GetNumber()) + test.build.SetParent(test.want.GetParent()) + test.build.SetEvent(test.want.GetEvent()) + test.build.SetEventAction(test.want.GetEventAction()) + test.build.SetStatus(test.want.GetStatus()) + test.build.SetError(test.want.GetError()) + test.build.SetEnqueued(test.want.GetEnqueued()) + test.build.SetCreated(test.want.GetCreated()) + test.build.SetStarted(test.want.GetStarted()) + test.build.SetFinished(test.want.GetFinished()) + test.build.SetDeploy(test.want.GetDeploy()) + test.build.SetDeployNumber(test.want.GetDeployNumber()) + test.build.SetDeployPayload(test.want.GetDeployPayload()) + test.build.SetClone(test.want.GetClone()) + test.build.SetSource(test.want.GetSource()) + test.build.SetTitle(test.want.GetTitle()) + test.build.SetMessage(test.want.GetMessage()) + test.build.SetCommit(test.want.GetCommit()) + test.build.SetSender(test.want.GetSender()) + test.build.SetSenderSCMID(test.want.GetSenderSCMID()) + test.build.SetAuthor(test.want.GetAuthor()) + test.build.SetEmail(test.want.GetEmail()) + test.build.SetLink(test.want.GetLink()) + test.build.SetBranch(test.want.GetBranch()) + test.build.SetRef(test.want.GetRef()) + test.build.SetBaseRef(test.want.GetBaseRef()) + test.build.SetHeadRef(test.want.GetHeadRef()) + test.build.SetHost(test.want.GetHost()) + test.build.SetRuntime(test.want.GetRuntime()) + test.build.SetDistribution(test.want.GetDistribution()) + test.build.SetApprovedAt(test.want.GetApprovedAt()) + test.build.SetApprovedBy(test.want.GetApprovedBy()) + + if test.build.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.build.GetID(), test.want.GetID()) + } + + if !reflect.DeepEqual(test.build.GetRepo(), test.want.GetRepo()) { + t.Errorf("SetRepo is %v, want %v", test.build.GetRepo(), test.want.GetRepo()) + } + + if test.build.GetPipelineID() != test.want.GetPipelineID() { + t.Errorf("SetPipelineID is %v, want %v", test.build.GetPipelineID(), test.want.GetPipelineID()) + } + + if test.build.GetNumber() != test.want.GetNumber() { + t.Errorf("SetNumber is %v, want %v", test.build.GetNumber(), test.want.GetNumber()) + } + + if test.build.GetParent() != test.want.GetParent() { + t.Errorf("SetParent is %v, want %v", test.build.GetParent(), test.want.GetParent()) + } + + if test.build.GetEvent() != test.want.GetEvent() { + t.Errorf("SetEvent is %v, want %v", test.build.GetEvent(), test.want.GetEvent()) + } + + if test.build.GetEventAction() != test.want.GetEventAction() { + t.Errorf("SetEventAction is %v, want %v", test.build.GetEventAction(), test.want.GetEventAction()) + } + + if test.build.GetStatus() != test.want.GetStatus() { + t.Errorf("SetStatus is %v, want %v", test.build.GetStatus(), test.want.GetStatus()) + } + + if test.build.GetError() != test.want.GetError() { + t.Errorf("SetError is %v, want %v", test.build.GetError(), test.want.GetError()) + } + + if test.build.GetEnqueued() != test.want.GetEnqueued() { + t.Errorf("SetEnqueued is %v, want %v", test.build.GetEnqueued(), test.want.GetEnqueued()) + } + + if test.build.GetCreated() != test.want.GetCreated() { + t.Errorf("SetCreated is %v, want %v", test.build.GetCreated(), test.want.GetCreated()) + } + + if test.build.GetStarted() != test.want.GetStarted() { + t.Errorf("SetStarted is %v, want %v", test.build.GetStarted(), test.want.GetStarted()) + } + + if test.build.GetFinished() != test.want.GetFinished() { + t.Errorf("SetFinished is %v, want %v", test.build.GetFinished(), test.want.GetFinished()) + } + + if test.build.GetDeploy() != test.want.GetDeploy() { + t.Errorf("SetDeploy is %v, want %v", test.build.GetDeploy(), test.want.GetDeploy()) + } + + if test.build.GetDeployNumber() != test.want.GetDeployNumber() { + t.Errorf("SetDeployNumber is %v, want %v", test.build.GetDeployNumber(), test.want.GetDeployNumber()) + } + + if !reflect.DeepEqual(test.build.GetDeployPayload(), test.want.GetDeployPayload()) { + t.Errorf("GetDeployPayload is %v, want %v", test.build.GetDeployPayload(), test.want.GetDeployPayload()) + } + + if test.build.GetClone() != test.want.GetClone() { + t.Errorf("SetClone is %v, want %v", test.build.GetClone(), test.want.GetClone()) + } + + if test.build.GetSource() != test.want.GetSource() { + t.Errorf("SetSource is %v, want %v", test.build.GetSource(), test.want.GetSource()) + } + + if test.build.GetTitle() != test.want.GetTitle() { + t.Errorf("SetTitle is %v, want %v", test.build.GetTitle(), test.want.GetTitle()) + } + + if test.build.GetMessage() != test.want.GetMessage() { + t.Errorf("SetMessage is %v, want %v", test.build.GetMessage(), test.want.GetMessage()) + } + + if test.build.GetCommit() != test.want.GetCommit() { + t.Errorf("SetCommit is %v, want %v", test.build.GetCommit(), test.want.GetCommit()) + } + + if test.build.GetSender() != test.want.GetSender() { + t.Errorf("SetSender is %v, want %v", test.build.GetSender(), test.want.GetSender()) + } + + if test.build.GetSenderSCMID() != test.want.GetSenderSCMID() { + t.Errorf("SetSenderSCMID is %v, want %v", test.build.GetSenderSCMID(), test.want.GetSenderSCMID()) + } + + if test.build.GetAuthor() != test.want.GetAuthor() { + t.Errorf("SetAuthor is %v, want %v", test.build.GetAuthor(), test.want.GetAuthor()) + } + + if test.build.GetEmail() != test.want.GetEmail() { + t.Errorf("SetEmail is %v, want %v", test.build.GetEmail(), test.want.GetEmail()) + } + + if test.build.GetLink() != test.want.GetLink() { + t.Errorf("SetLink is %v, want %v", test.build.GetLink(), test.want.GetLink()) + } + + if test.build.GetBranch() != test.want.GetBranch() { + t.Errorf("SetBranch is %v, want %v", test.build.GetBranch(), test.want.GetBranch()) + } + + if test.build.GetRef() != test.want.GetRef() { + t.Errorf("SetRef is %v, want %v", test.build.GetRef(), test.want.GetRef()) + } + + if test.build.GetBaseRef() != test.want.GetBaseRef() { + t.Errorf("SetBaseRef is %v, want %v", test.build.GetBaseRef(), test.want.GetBaseRef()) + } + + if test.build.GetHeadRef() != test.want.GetHeadRef() { + t.Errorf("SetHeadRef is %v, want %v", test.build.GetHeadRef(), test.want.GetHeadRef()) + } + + if test.build.GetHost() != test.want.GetHost() { + t.Errorf("SetHost is %v, want %v", test.build.GetHost(), test.want.GetHost()) + } + + if test.build.GetRuntime() != test.want.GetRuntime() { + t.Errorf("SetRuntime is %v, want %v", test.build.GetRuntime(), test.want.GetRuntime()) + } + + if test.build.GetDistribution() != test.want.GetDistribution() { + t.Errorf("SetDistribution is %v, want %v", test.build.GetDistribution(), test.want.GetDistribution()) + } + + if test.build.GetApprovedAt() != test.want.GetApprovedAt() { + t.Errorf("SetApprovedAt is %v, want %v", test.build.GetApprovedAt(), test.want.GetApprovedAt()) + } + + if test.build.GetApprovedBy() != test.want.GetApprovedBy() { + t.Errorf("SetApprovedBy is %v, want %v", test.build.GetApprovedBy(), test.want.GetApprovedBy()) + } + } +} + +func TestTypes_Build_String(t *testing.T) { + // setup types + b := testBuild() + + want := fmt.Sprintf(`{ + ApprovedAt: %d, + ApprovedBy: %s, + Author: %s, + BaseRef: %s, + Branch: %s, + Clone: %s, + Commit: %s, + Created: %d, + Deploy: %s, + DeployNumber: %d, + DeployPayload: %s, + Distribution: %s, + Email: %s, + Enqueued: %d, + Error: %s, + Event: %s, + EventAction: %s, + Finished: %d, + HeadRef: %s, + Host: %s, + ID: %d, + Link: %s, + Message: %s, + Number: %d, + Parent: %d, + PipelineID: %d, + Ref: %s, + Repo: %s, + Runtime: %s, + Sender: %s, + SenderSCMID: %s, + Source: %s, + Started: %d, + Status: %s, + Title: %s, +}`, + b.GetApprovedAt(), + b.GetApprovedBy(), + b.GetAuthor(), + b.GetBaseRef(), + b.GetBranch(), + b.GetClone(), + b.GetCommit(), + b.GetCreated(), + b.GetDeploy(), + b.GetDeployNumber(), + b.GetDeployPayload(), + b.GetDistribution(), + b.GetEmail(), + b.GetEnqueued(), + b.GetError(), + b.GetEvent(), + b.GetEventAction(), + b.GetFinished(), + b.GetHeadRef(), + b.GetHost(), + b.GetID(), + b.GetLink(), + b.GetMessage(), + b.GetNumber(), + b.GetParent(), + b.GetPipelineID(), + b.GetRef(), + b.GetRepo().GetFullName(), + b.GetRuntime(), + b.GetSender(), + b.GetSenderSCMID(), + b.GetSource(), + b.GetStarted(), + b.GetStatus(), + b.GetTitle(), + ) + + // run test + got := b.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testBuild is a test helper function to create a Build +// type with all fields set to a fake value. +func testBuild() *Build { + b := new(Build) + + b.SetID(1) + b.SetRepo(testRepo()) + b.SetPipelineID(1) + b.SetNumber(1) + b.SetParent(1) + b.SetEvent("push") + b.SetStatus("running") + b.SetError("") + b.SetEnqueued(1563474077) + b.SetCreated(1563474076) + b.SetStarted(1563474078) + b.SetFinished(1563474079) + b.SetDeploy("") + b.SetDeployNumber(0) + b.SetDeployPayload(raw.StringSliceMap{"foo": "test1"}) + b.SetClone("https://github.com/github/octocat.git") + b.SetSource("https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetTitle("push received from https://github.com/github/octocat") + b.SetMessage("First commit...") + b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetSender("OctoKitty") + b.SetSenderSCMID("123") + b.SetAuthor("OctoKitty") + b.SetEmail("OctoKitty@github.com") + b.SetLink("https://example.company.com/github/octocat/1") + b.SetBranch("main") + b.SetRef("refs/heads/main") + b.SetBaseRef("") + b.SetHeadRef("changes") + b.SetHost("example.company.com") + b.SetRuntime("docker") + b.SetDistribution("linux") + b.SetApprovedAt(1563474076) + b.SetApprovedBy("OctoCat") + + return b +} diff --git a/api/types/dashboard.go b/api/types/dashboard.go new file mode 100644 index 000000000..08954350f --- /dev/null +++ b/api/types/dashboard.go @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" +) + +// RepoPartial is an API type that holds all relevant information +// for a repository attached to a dashboard. +type RepoPartial struct { + Org string `json:"org,omitempty"` + Name string `json:"name,omitempty"` + Counter int `json:"counter,omitempty"` + Active bool `json:"active,omitempty"` + Builds []BuildPartial `json:"builds,omitempty"` +} + +// BuildPartial is an API type that holds all relevant information +// for a build attached to a RepoPartial. +type BuildPartial struct { + Number int `json:"number,omitempty"` + Started int64 `json:"started,omitempty"` + Finished int64 `json:"finished,omitempty"` + Sender string `json:"sender,omitempty"` + Status string `json:"status,omitempty"` + Event string `json:"event,omitempty"` + Branch string `json:"branch,omitempty"` + Link string `json:"link,omitempty"` +} + +// DashCard is an API type that holds the dashboard information as +// well as a list of RepoPartials attached to the dashboard. +// +// swagger:model DashCard +type DashCard struct { + Dashboard *Dashboard `json:"dashboard,omitempty"` + Repos []RepoPartial `json:"repos,omitempty"` +} + +// Dashboard is the API representation of a dashboard. +// +// swagger:model Dashboard +type Dashboard struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + CreatedAt *int64 `json:"created_at,omitempty"` + CreatedBy *string `json:"created_by,omitempty"` + UpdatedAt *int64 `json:"updated_at,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` + Admins *[]*User `json:"admins,omitempty"` + Repos *[]*DashboardRepo `json:"repos,omitempty"` +} + +// GetID returns the ID field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetID() string { + // return zero value if Dashboard type or ID field is nil + if d == nil || d.ID == nil { + return "" + } + + return *d.ID +} + +// GetName returns the Name field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetName() string { + // return zero value if Dashboard type or Name field is nil + if d == nil || d.Name == nil { + return "" + } + + return *d.Name +} + +// GetCreatedAt returns the CreatedAt field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetCreatedAt() int64 { + // return zero value if Dashboard type or CreatedAt field is nil + if d == nil || d.CreatedAt == nil { + return 0 + } + + return *d.CreatedAt +} + +// GetCreatedBy returns the CreatedBy field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetCreatedBy() string { + // return zero value if Dashboard type or CreatedBy field is nil + if d == nil || d.CreatedBy == nil { + return "" + } + + return *d.CreatedBy +} + +// GetUpdatedAt returns the UpdatedAt field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetUpdatedAt() int64 { + // return zero value if Dashboard type or UpdatedAt field is nil + if d == nil || d.UpdatedAt == nil { + return 0 + } + + return *d.UpdatedAt +} + +// GetUpdatedBy returns the UpdatedBy field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetUpdatedBy() string { + // return zero value if Dashboard type or UpdatedBy field is nil + if d == nil || d.UpdatedBy == nil { + return "" + } + + return *d.UpdatedBy +} + +// GetAdmins returns the Admins field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetAdmins() []*User { + // return zero value if Dashboard type or Admins field is nil + if d == nil || d.Admins == nil { + return []*User{} + } + + return *d.Admins +} + +// GetRepos returns the Repos field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *Dashboard) GetRepos() []*DashboardRepo { + // return zero value if Dashboard type or Repos field is nil + if d == nil || d.Repos == nil { + return []*DashboardRepo{} + } + + return *d.Repos +} + +// SetID sets the ID field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetID(v string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.ID = &v +} + +// SetName sets the Name field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetName(v string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Name = &v +} + +// SetCreatedAt sets the CreatedAt field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetCreatedAt(v int64) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.CreatedAt = &v +} + +// SetCreatedBy sets the CreatedBy field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetCreatedBy(v string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.CreatedBy = &v +} + +// SetUpdatedAt sets the UpdatedAt field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetUpdatedAt(v int64) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.UpdatedAt = &v +} + +// SetUpdatedBy sets the UpdatedBy field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetUpdatedBy(v string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.UpdatedBy = &v +} + +// SetAdmins sets the Admins field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetAdmins(v []*User) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Admins = &v +} + +// SetRepos sets the Repos field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *Dashboard) SetRepos(v []*DashboardRepo) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Repos = &v +} + +// String implements the Stringer interface for the Dashboard type. +func (d *Dashboard) String() string { + return fmt.Sprintf(`{ + Name: %s, + ID: %s, + Admins: %v, + CreatedAt: %d, + CreatedBy: %s, + UpdatedAt: %d, + UpdatedBy: %s, + Repos: %v, +}`, + d.GetName(), + d.GetID(), + d.GetAdmins(), + d.GetCreatedAt(), + d.GetCreatedBy(), + d.GetUpdatedAt(), + d.GetUpdatedBy(), + d.GetRepos(), + ) +} diff --git a/api/types/dashboard_repo.go b/api/types/dashboard_repo.go new file mode 100644 index 000000000..4e1faa2bc --- /dev/null +++ b/api/types/dashboard_repo.go @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" +) + +// DashboardRepo is the API representation of a repo belonging to a Dashboard. +// +// swagger:model DashboardRepo +type DashboardRepo struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Branches *[]string `json:"branches,omitempty"` + Events *[]string `json:"events,omitempty"` +} + +// GetID returns the ID field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *DashboardRepo) GetID() int64 { + // return zero value if Dashboard type or ID field is nil + if d == nil || d.ID == nil { + return 0 + } + + return *d.ID +} + +// GetName returns the Name field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *DashboardRepo) GetName() string { + // return zero value if Dashboard type or ID field is nil + if d == nil || d.Name == nil { + return "" + } + + return *d.Name +} + +// GetBranches returns the Branches field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *DashboardRepo) GetBranches() []string { + // return zero value if Dashboard type or Branches field is nil + if d == nil || d.Branches == nil { + return []string{} + } + + return *d.Branches +} + +// GetEvents returns the Events field. +// +// When the provided Dashboard type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (d *DashboardRepo) GetEvents() []string { + // return zero value if Dashboard type or Events field is nil + if d == nil || d.Events == nil { + return []string{} + } + + return *d.Events +} + +// SetID sets the ID field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *DashboardRepo) SetID(v int64) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.ID = &v +} + +// SetName sets the Name field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *DashboardRepo) SetName(v string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Name = &v +} + +// SetBranches sets the Branches field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *DashboardRepo) SetBranches(v []string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Branches = &v +} + +// SetEvents sets the Events field. +// +// When the provided Dashboard type is nil, it +// will set nothing and immediately return. +func (d *DashboardRepo) SetEvents(v []string) { + // return if Dashboard type is nil + if d == nil { + return + } + + d.Events = &v +} + +// String implements the Stringer interface for the Dashboard type. +func (d *DashboardRepo) String() string { + return fmt.Sprintf(`{ + Name: %s, + ID: %d, + Branches: %v, + Events: %v, +}`, + d.GetName(), + d.GetID(), + d.GetBranches(), + d.GetEvents(), + ) +} diff --git a/api/types/dashboard_repo_test.go b/api/types/dashboard_repo_test.go new file mode 100644 index 000000000..892b698e3 --- /dev/null +++ b/api/types/dashboard_repo_test.go @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_DashboardRepo_Getters(t *testing.T) { + // setup tests + tests := []struct { + dashboardRepo *DashboardRepo + want *DashboardRepo + }{ + { + dashboardRepo: testDashboardRepo(), + want: testDashboardRepo(), + }, + { + dashboardRepo: new(DashboardRepo), + want: new(DashboardRepo), + }, + } + + // run tests + for _, test := range tests { + if test.dashboardRepo.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.dashboardRepo.GetID(), test.want.GetID()) + } + + if test.dashboardRepo.GetName() != test.want.GetName() { + t.Errorf("GetName is %v, want %v", test.dashboardRepo.GetName(), test.want.GetName()) + } + + if !reflect.DeepEqual(test.dashboardRepo.GetBranches(), test.want.GetBranches()) { + t.Errorf("GetBranches is %v, want %v", test.dashboardRepo.GetBranches(), test.want.GetBranches()) + } + + if !reflect.DeepEqual(test.dashboardRepo.GetEvents(), test.want.GetEvents()) { + t.Errorf("GetEvents is %v, want %v", test.dashboardRepo.GetEvents(), test.want.GetEvents()) + } + } +} + +func TestTypes_DashboardRepo_Setters(t *testing.T) { + // setup types + var d *DashboardRepo + + // setup tests + tests := []struct { + dashboardRepo *DashboardRepo + want *DashboardRepo + }{ + { + dashboardRepo: testDashboardRepo(), + want: testDashboardRepo(), + }, + { + dashboardRepo: d, + want: new(DashboardRepo), + }, + } + + // run tests + for _, test := range tests { + test.dashboardRepo.SetID(test.want.GetID()) + test.dashboardRepo.SetName(test.want.GetName()) + test.dashboardRepo.SetBranches(test.want.GetBranches()) + test.dashboardRepo.SetEvents(test.want.GetEvents()) + + if test.dashboardRepo.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.dashboardRepo.GetID(), test.want.GetID()) + } + + if test.dashboardRepo.GetName() != test.want.GetName() { + t.Errorf("SetName is %v, want %v", test.dashboardRepo.GetName(), test.want.GetName()) + } + + if !reflect.DeepEqual(test.dashboardRepo.GetBranches(), test.want.GetBranches()) { + t.Errorf("SetBranches is %v, want %v", test.dashboardRepo.GetBranches(), test.want.GetBranches()) + } + + if !reflect.DeepEqual(test.dashboardRepo.GetEvents(), test.want.GetEvents()) { + t.Errorf("SetEvents is %v, want %v", test.dashboardRepo.GetEvents(), test.want.GetEvents()) + } + } +} + +func TestTypes_DashboardRepo_String(t *testing.T) { + // setup types + d := testDashboardRepo() + + want := fmt.Sprintf(`{ + Name: %s, + ID: %d, + Branches: %v, + Events: %v, +}`, + d.GetName(), + d.GetID(), + d.GetBranches(), + d.GetEvents(), + ) + + // run test + got := d.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testDashboardRepo is a test helper function to create a DashboardRepo +// type with all fields set to a fake value. +func testDashboardRepo() *DashboardRepo { + d := new(DashboardRepo) + + d.SetName("go-vela/server") + d.SetID(1) + d.SetBranches([]string{"main"}) + d.SetEvents([]string{"push", "tag"}) + + return d +} diff --git a/api/types/dashboard_test.go b/api/types/dashboard_test.go new file mode 100644 index 000000000..29f8a283c --- /dev/null +++ b/api/types/dashboard_test.go @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_Dashboard_Getters(t *testing.T) { + // setup tests + tests := []struct { + dashboard *Dashboard + want *Dashboard + }{ + { + dashboard: testDashboard(), + want: testDashboard(), + }, + { + dashboard: new(Dashboard), + want: new(Dashboard), + }, + } + + // run tests + for _, test := range tests { + if test.dashboard.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.dashboard.GetID(), test.want.GetID()) + } + + if test.dashboard.GetName() != test.want.GetName() { + t.Errorf("GetName is %v, want %v", test.dashboard.GetName(), test.want.GetName()) + } + + if !reflect.DeepEqual(test.dashboard.GetAdmins(), test.want.GetAdmins()) { + t.Errorf("GetAdmins is %v, want %v", test.dashboard.GetAdmins(), test.want.GetAdmins()) + } + + if test.dashboard.GetCreatedAt() != test.want.GetCreatedAt() { + t.Errorf("GetCreatedAt is %v, want %v", test.dashboard.GetCreatedAt(), test.want.GetCreatedAt()) + } + + if test.dashboard.GetCreatedBy() != test.want.GetCreatedBy() { + t.Errorf("GetCreatedBy is %v, want %v", test.dashboard.GetCreatedBy(), test.want.GetCreatedBy()) + } + + if test.dashboard.GetUpdatedAt() != test.want.GetUpdatedAt() { + t.Errorf("GetUpdatedAt is %v, want %v", test.dashboard.GetUpdatedAt(), test.want.GetUpdatedAt()) + } + + if test.dashboard.GetUpdatedBy() != test.want.GetUpdatedBy() { + t.Errorf("GetUpdatedBy is %v, want %v", test.dashboard.GetUpdatedBy(), test.want.GetUpdatedBy()) + } + + if !reflect.DeepEqual(test.dashboard.GetRepos(), test.want.GetRepos()) { + t.Errorf("GetRepos is %v, want %v", test.dashboard.GetRepos(), test.want.GetRepos()) + } + } +} + +func TestTypes_Dashboard_Setters(t *testing.T) { + // setup types + var d *Dashboard + + // setup tests + tests := []struct { + dashboard *Dashboard + want *Dashboard + }{ + { + dashboard: testDashboard(), + want: testDashboard(), + }, + { + dashboard: d, + want: new(Dashboard), + }, + } + + // run tests + for _, test := range tests { + test.dashboard.SetID(test.want.GetID()) + test.dashboard.SetName(test.want.GetName()) + test.dashboard.SetAdmins(test.want.GetAdmins()) + test.dashboard.SetCreatedAt(test.want.GetCreatedAt()) + test.dashboard.SetCreatedBy(test.want.GetCreatedBy()) + test.dashboard.SetUpdatedAt(test.want.GetUpdatedAt()) + test.dashboard.SetUpdatedBy(test.want.GetUpdatedBy()) + test.dashboard.SetRepos(test.want.GetRepos()) + + if test.dashboard.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.dashboard.GetID(), test.want.GetID()) + } + + if test.dashboard.GetName() != test.want.GetName() { + t.Errorf("SetName is %v, want %v", test.dashboard.GetName(), test.want.GetName()) + } + + if !reflect.DeepEqual(test.dashboard.GetAdmins(), test.want.GetAdmins()) { + t.Errorf("SetAdmins is %v, want %v", test.dashboard.GetAdmins(), test.want.GetAdmins()) + } + + if test.dashboard.GetCreatedAt() != test.want.GetCreatedAt() { + t.Errorf("SetCreatedAt is %v, want %v", test.dashboard.GetCreatedAt(), test.want.GetCreatedAt()) + } + + if test.dashboard.GetCreatedBy() != test.want.GetCreatedBy() { + t.Errorf("SetCreatedBy is %v, want %v", test.dashboard.GetCreatedBy(), test.want.GetCreatedBy()) + } + + if test.dashboard.GetUpdatedAt() != test.want.GetUpdatedAt() { + t.Errorf("SetUpdatedAt is %v, want %v", test.dashboard.GetUpdatedAt(), test.want.GetUpdatedAt()) + } + + if test.dashboard.GetUpdatedBy() != test.want.GetUpdatedBy() { + t.Errorf("SetUpdatedBy is %v, want %v", test.dashboard.GetUpdatedBy(), test.want.GetUpdatedBy()) + } + + if !reflect.DeepEqual(test.dashboard.GetRepos(), test.want.GetRepos()) { + t.Errorf("SetRepos is %v, want %v", test.dashboard.GetRepos(), test.want.GetRepos()) + } + } +} + +func TestTypes_Dashboard_String(t *testing.T) { + // setup types + d := testDashboard() + + want := fmt.Sprintf(`{ + Name: %s, + ID: %s, + Admins: %v, + CreatedAt: %d, + CreatedBy: %s, + UpdatedAt: %d, + UpdatedBy: %s, + Repos: %v, +}`, + d.GetName(), + d.GetID(), + d.GetAdmins(), + d.GetCreatedAt(), + d.GetCreatedBy(), + d.GetUpdatedAt(), + d.GetUpdatedBy(), + d.GetRepos(), + ) + + // run test + got := d.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testDashboard is a test helper function to create a Dashboard +// type with all fields set to a fake value. +func testDashboard() *Dashboard { + d := new(Dashboard) + + d.SetID("123-abc") + d.SetName("vela") + d.SetAdmins([]*User{testUser()}) + d.SetCreatedAt(1) + d.SetCreatedBy("octocat") + d.SetUpdatedAt(2) + d.SetUpdatedBy("octokitty") + d.SetRepos([]*DashboardRepo{testDashboardRepo()}) + + return d +} diff --git a/api/types/events.go b/api/types/events.go new file mode 100644 index 000000000..8bf27db8c --- /dev/null +++ b/api/types/events.go @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + + "github.com/go-vela/server/api/types/actions" + "github.com/go-vela/types/constants" +) + +// Events is the API representation of the various events that generate a +// webhook from the SCM. +type Events struct { + Push *actions.Push `json:"push"` + PullRequest *actions.Pull `json:"pull_request"` + Deployment *actions.Deploy `json:"deployment"` + Comment *actions.Comment `json:"comment"` + Schedule *actions.Schedule `json:"schedule"` +} + +// NewEventsFromMask is an instatiation function for the Events type that +// takes in an event mask integer value and populates the nested Events struct. +func NewEventsFromMask(mask int64) *Events { + pushActions := new(actions.Push).FromMask(mask) + pullActions := new(actions.Pull).FromMask(mask) + deployActions := new(actions.Deploy).FromMask(mask) + commentActions := new(actions.Comment).FromMask(mask) + scheduleActions := new(actions.Schedule).FromMask(mask) + + e := new(Events) + + e.SetPush(pushActions) + e.SetPullRequest(pullActions) + e.SetDeployment(deployActions) + e.SetComment(commentActions) + e.SetSchedule(scheduleActions) + + return e +} + +// NewEventsFromSlice is an instantiation function for the Events type that +// takes in a slice of event strings and populates the nested Events struct. +func NewEventsFromSlice(events []string) (*Events, error) { + mask := int64(0) + + // iterate through all events provided + for _, event := range events { + switch event { + // push actions + case constants.EventPush, constants.EventPush + ":branch": + mask = mask | constants.AllowPushBranch + case constants.EventTag, constants.EventPush + ":" + constants.EventTag: + mask = mask | constants.AllowPushTag + case constants.EventDelete + ":" + constants.ActionBranch: + mask = mask | constants.AllowPushDeleteBranch + case constants.EventDelete + ":" + constants.ActionTag: + mask = mask | constants.AllowPushDeleteTag + case constants.EventDelete: + mask = mask | constants.AllowPushDeleteBranch | constants.AllowPushDeleteTag + + // pull_request actions + case constants.EventPull, constants.EventPullAlternate: + mask = mask | constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen + case constants.EventPull + ":" + constants.ActionOpened: + mask = mask | constants.AllowPullOpen + case constants.EventPull + ":" + constants.ActionEdited: + mask = mask | constants.AllowPullEdit + case constants.EventPull + ":" + constants.ActionSynchronize: + mask = mask | constants.AllowPullSync + case constants.EventPull + ":" + constants.ActionReopened: + mask = mask | constants.AllowPullReopen + case constants.EventPull + ":" + constants.ActionLabeled: + mask = mask | constants.AllowPullLabel + case constants.EventPull + ":" + constants.ActionUnlabeled: + mask = mask | constants.AllowPullUnlabel + + // deployment actions + case constants.EventDeploy, constants.EventDeployAlternate, constants.EventDeploy + ":" + constants.ActionCreated: + mask = mask | constants.AllowDeployCreate + + // comment actions + case constants.EventComment: + mask = mask | constants.AllowCommentCreate | constants.AllowCommentEdit + case constants.EventComment + ":" + constants.ActionCreated: + mask = mask | constants.AllowCommentCreate + case constants.EventComment + ":" + constants.ActionEdited: + mask = mask | constants.AllowCommentEdit + + // schedule actions + case constants.EventSchedule, constants.EventSchedule + ":" + constants.ActionRun: + mask = mask | constants.AllowSchedule + + default: + return nil, fmt.Errorf("invalid event provided: %s", event) + } + } + + return NewEventsFromMask(mask), nil +} + +// Allowed determines whether or not an event + action is allowed based on whether +// its event:action is set to true in the Events struct. +func (e *Events) Allowed(event, action string) bool { + allowed := false + + // if there is an action, create `event:action` comparator string + if len(action) > 0 { + event = event + ":" + action + } + + switch event { + case constants.EventPush: + allowed = e.GetPush().GetBranch() + case constants.EventPull + ":" + constants.ActionOpened: + allowed = e.GetPullRequest().GetOpened() + case constants.EventPull + ":" + constants.ActionSynchronize: + allowed = e.GetPullRequest().GetSynchronize() + case constants.EventPull + ":" + constants.ActionEdited: + allowed = e.GetPullRequest().GetEdited() + case constants.EventPull + ":" + constants.ActionReopened: + allowed = e.GetPullRequest().GetReopened() + case constants.EventPull + ":" + constants.ActionLabeled: + allowed = e.GetPullRequest().GetLabeled() + case constants.EventPull + ":" + constants.ActionUnlabeled: + allowed = e.GetPullRequest().GetUnlabeled() + case constants.EventTag: + allowed = e.GetPush().GetTag() + case constants.EventComment + ":" + constants.ActionCreated: + allowed = e.GetComment().GetCreated() + case constants.EventComment + ":" + constants.ActionEdited: + allowed = e.GetComment().GetEdited() + case constants.EventDeploy + ":" + constants.ActionCreated: + allowed = e.GetDeployment().GetCreated() + case constants.EventSchedule: + allowed = e.GetSchedule().GetRun() + case constants.EventDelete + ":" + constants.ActionBranch: + allowed = e.GetPush().GetDeleteBranch() + case constants.EventDelete + ":" + constants.ActionTag: + allowed = e.GetPush().GetDeleteTag() + } + + return allowed +} + +// List is an Events method that generates a comma-separated list of event:action +// combinations that are allowed for the repo. +func (e *Events) List() []string { + eventSlice := []string{} + + if e.GetPush().GetBranch() { + eventSlice = append(eventSlice, constants.EventPush) + } + + if e.GetPullRequest().GetOpened() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionOpened) + } + + if e.GetPullRequest().GetSynchronize() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionSynchronize) + } + + if e.GetPullRequest().GetEdited() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionEdited) + } + + if e.GetPullRequest().GetReopened() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionReopened) + } + + if e.GetPullRequest().GetLabeled() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionLabeled) + } + + if e.GetPullRequest().GetUnlabeled() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionUnlabeled) + } + + if e.GetPush().GetTag() { + eventSlice = append(eventSlice, constants.EventTag) + } + + if e.GetDeployment().GetCreated() { + eventSlice = append(eventSlice, constants.EventDeploy) + } + + if e.GetComment().GetCreated() { + eventSlice = append(eventSlice, constants.EventComment+":"+constants.ActionCreated) + } + + if e.GetComment().GetEdited() { + eventSlice = append(eventSlice, constants.EventComment+":"+constants.ActionEdited) + } + + if e.GetSchedule().GetRun() { + eventSlice = append(eventSlice, constants.EventSchedule) + } + + if e.GetPush().GetDeleteBranch() { + eventSlice = append(eventSlice, constants.EventDelete+":"+constants.ActionBranch) + } + + if e.GetPush().GetDeleteTag() { + eventSlice = append(eventSlice, constants.EventDelete+":"+constants.ActionTag) + } + + return eventSlice +} + +// ToDatabase is an Events method that converts a nested Events struct into an integer event mask. +func (e *Events) ToDatabase() int64 { + return 0 | + e.GetPush().ToMask() | + e.GetPullRequest().ToMask() | + e.GetComment().ToMask() | + e.GetDeployment().ToMask() | + e.GetSchedule().ToMask() +} + +// GetPush returns the Push field from the provided Events. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (e *Events) GetPush() *actions.Push { + // return zero value if Events type or Push field is nil + if e == nil || e.Push == nil { + return new(actions.Push) + } + + return e.Push +} + +// GetPullRequest returns the PullRequest field from the provided Events. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (e *Events) GetPullRequest() *actions.Pull { + // return zero value if Events type or PullRequest field is nil + if e == nil || e.PullRequest == nil { + return new(actions.Pull) + } + + return e.PullRequest +} + +// GetDeployment returns the Deployment field from the provided Events. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (e *Events) GetDeployment() *actions.Deploy { + // return zero value if Events type or Deployment field is nil + if e == nil || e.Deployment == nil { + return new(actions.Deploy) + } + + return e.Deployment +} + +// GetComment returns the Comment field from the provided Events. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (e *Events) GetComment() *actions.Comment { + // return zero value if Events type or Comment field is nil + if e == nil || e.Comment == nil { + return new(actions.Comment) + } + + return e.Comment +} + +// GetSchedule returns the Schedule field from the provided Events. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (e *Events) GetSchedule() *actions.Schedule { + // return zero value if Events type or Schedule field is nil + if e == nil || e.Schedule == nil { + return new(actions.Schedule) + } + + return e.Schedule +} + +// SetPush sets the Events Push field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (e *Events) SetPush(v *actions.Push) { + // return if Events type is nil + if e == nil { + return + } + + e.Push = v +} + +// SetPullRequest sets the Events PullRequest field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (e *Events) SetPullRequest(v *actions.Pull) { + // return if Events type is nil + if e == nil { + return + } + + e.PullRequest = v +} + +// SetDeployment sets the Events Deployment field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (e *Events) SetDeployment(v *actions.Deploy) { + // return if Events type is nil + if e == nil { + return + } + + e.Deployment = v +} + +// SetComment sets the Events Comment field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (e *Events) SetComment(v *actions.Comment) { + // return if Events type is nil + if e == nil { + return + } + + e.Comment = v +} + +// SetSchedule sets the Events Schedule field. +// +// When the provided Events type is nil, it +// will set nothing and immediately return. +func (e *Events) SetSchedule(v *actions.Schedule) { + // return if Events type is nil + if e == nil { + return + } + + e.Schedule = v +} diff --git a/api/types/events_test.go b/api/types/events_test.go new file mode 100644 index 000000000..68f6f5709 --- /dev/null +++ b/api/types/events_test.go @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/go-vela/server/api/types/actions" + "github.com/go-vela/types/constants" +) + +func TestTypes_Events_Getters(t *testing.T) { + // setup types + eventsOne, eventsTwo := testEvents() + + // setup tests + tests := []struct { + events *Events + want *Events + }{ + { + events: eventsOne, + want: eventsOne, + }, + { + events: eventsTwo, + want: eventsTwo, + }, + { + events: new(Events), + want: new(Events), + }, + } + + // run tests + for _, test := range tests { + if !reflect.DeepEqual(test.events.GetPush(), test.want.GetPush()) { + t.Errorf("GetPush is %v, want %v", test.events.GetPush(), test.want.GetPush()) + } + + if !reflect.DeepEqual(test.events.GetPullRequest(), test.want.GetPullRequest()) { + t.Errorf("GetPullRequest is %v, want %v", test.events.GetPush(), test.want.GetPush()) + } + + if !reflect.DeepEqual(test.events.GetDeployment(), test.want.GetDeployment()) { + t.Errorf("GetDeployment is %v, want %v", test.events.GetPush(), test.want.GetPush()) + } + + if !reflect.DeepEqual(test.events.GetComment(), test.want.GetComment()) { + t.Errorf("GetComment is %v, want %v", test.events.GetPush(), test.want.GetPush()) + } + + if !reflect.DeepEqual(test.events.GetSchedule(), test.want.GetSchedule()) { + t.Errorf("GetSchedule is %v, want %v", test.events.GetSchedule(), test.want.GetSchedule()) + } + } +} + +func TestTypes_Events_Setters(t *testing.T) { + // setup types + var e *Events + + eventsOne, eventsTwo := testEvents() + + // setup tests + tests := []struct { + events *Events + want *Events + }{ + { + events: eventsOne, + want: eventsOne, + }, + { + events: eventsTwo, + want: eventsTwo, + }, + { + events: e, + want: new(Events), + }, + } + + // run tests + for _, test := range tests { + test.events.SetPush(test.want.GetPush()) + test.events.SetPullRequest(test.want.GetPullRequest()) + test.events.SetDeployment(test.want.GetDeployment()) + test.events.SetComment(test.want.GetComment()) + test.events.SetSchedule(test.want.GetSchedule()) + + if !reflect.DeepEqual(test.events.GetPush(), test.want.GetPush()) { + t.Errorf("SetPush is %v, want %v", test.events.GetPush(), test.want.GetPush()) + } + + if !reflect.DeepEqual(test.events.GetPullRequest(), test.want.GetPullRequest()) { + t.Errorf("SetPullRequest is %v, want %v", test.events.GetPullRequest(), test.want.GetPullRequest()) + } + + if !reflect.DeepEqual(test.events.GetDeployment(), test.want.GetDeployment()) { + t.Errorf("SetDeployment is %v, want %v", test.events.GetDeployment(), test.want.GetDeployment()) + } + + if !reflect.DeepEqual(test.events.GetComment(), test.want.GetComment()) { + t.Errorf("SetComment is %v, want %v", test.events.GetComment(), test.want.GetComment()) + } + + if !reflect.DeepEqual(test.events.GetSchedule(), test.want.GetSchedule()) { + t.Errorf("SetSchedule is %v, want %v", test.events.GetSchedule(), test.want.GetSchedule()) + } + } +} + +func TestTypes_Events_List(t *testing.T) { + // setup types + eventsOne, eventsTwo := testEvents() + + wantOne := []string{ + "push", + "pull_request:opened", + "pull_request:synchronize", + "pull_request:reopened", + "pull_request:unlabeled", + "tag", + "comment:created", + "schedule", + "delete:branch", + } + + wantTwo := []string{ + "pull_request:edited", + "pull_request:labeled", + "deployment", + "comment:edited", + "delete:tag", + } + + // run test + gotOne := eventsOne.List() + + if diff := cmp.Diff(wantOne, gotOne); diff != "" { + t.Errorf("(List: -want +got):\n%s", diff) + } + + gotTwo := eventsTwo.List() + + if diff := cmp.Diff(wantTwo, gotTwo); diff != "" { + t.Errorf("(List Inverse: -want +got):\n%s", diff) + } +} + +func TestTypes_Events_NewEventsFromMask_ToDatabase(t *testing.T) { + // setup mask + maskOne := int64( + constants.AllowPushBranch | + constants.AllowPushTag | + constants.AllowPushDeleteBranch | + constants.AllowPullOpen | + constants.AllowPullSync | + constants.AllowPullReopen | + constants.AllowPullUnlabel | + constants.AllowCommentCreate | + constants.AllowSchedule, + ) + + maskTwo := int64( + constants.AllowPushDeleteTag | + constants.AllowPullEdit | + constants.AllowCommentEdit | + constants.AllowPullLabel | + constants.AllowDeployCreate, + ) + + wantOne, wantTwo := testEvents() + + // run test + gotOne := NewEventsFromMask(maskOne) + + if diff := cmp.Diff(wantOne, gotOne); diff != "" { + t.Errorf("(NewEventsFromMask: -want +got):\n%s", diff) + } + + gotTwo := NewEventsFromMask(maskTwo) + + if diff := cmp.Diff(wantTwo, gotTwo); diff != "" { + t.Errorf("(NewEventsFromMask Inverse: -want +got):\n%s", diff) + } + + // ensure ToDatabase maps back to masks + if gotOne.ToDatabase() != maskOne { + t.Errorf("ToDatabase returned %d, want %d", gotOne.ToDatabase(), maskOne) + } + + if gotTwo.ToDatabase() != maskTwo { + t.Errorf("ToDatabase returned %d, want %d", gotTwo.ToDatabase(), maskTwo) + } +} + +func Test_NewEventsFromSlice(t *testing.T) { + // setup types + tBool := true + fBool := false + + e1, e2 := testEvents() + + // setup tests + tests := []struct { + name string + events []string + want *Events + failure bool + }{ + { + name: "action specific events to e1", + events: []string{"push:branch", "push:tag", "delete:branch", "pull_request:opened", "pull_request:synchronize", "pull_request:reopened", "comment:created", "schedule:run", "pull_request:unlabeled"}, + want: e1, + failure: false, + }, + { + name: "action specific events to e2", + events: []string{"delete:tag", "pull_request:edited", "deployment:created", "comment:edited", "pull_request:labeled"}, + want: e2, + failure: false, + }, + { + name: "general events", + events: []string{"push", "pull", "deploy", "comment", "schedule", "tag", "delete"}, + want: &Events{ + Push: &actions.Push{ + Branch: &tBool, + Tag: &tBool, + DeleteBranch: &tBool, + DeleteTag: &tBool, + }, + PullRequest: &actions.Pull{ + Opened: &tBool, + Reopened: &tBool, + Edited: &fBool, + Synchronize: &tBool, + Labeled: &fBool, + Unlabeled: &fBool, + }, + Deployment: &actions.Deploy{ + Created: &tBool, + }, + Comment: &actions.Comment{ + Created: &tBool, + Edited: &tBool, + }, + Schedule: &actions.Schedule{ + Run: &tBool, + }, + }, + failure: false, + }, + { + name: "double events", + events: []string{"push", "push:branch", "pull_request", "pull_request:opened"}, + want: &Events{ + Push: &actions.Push{ + Branch: &tBool, + Tag: &fBool, + DeleteBranch: &fBool, + DeleteTag: &fBool, + }, + PullRequest: &actions.Pull{ + Opened: &tBool, + Reopened: &tBool, + Edited: &fBool, + Synchronize: &tBool, + Labeled: &fBool, + Unlabeled: &fBool, + }, + Deployment: &actions.Deploy{ + Created: &fBool, + }, + Comment: &actions.Comment{ + Created: &fBool, + Edited: &fBool, + }, + Schedule: &actions.Schedule{ + Run: &fBool, + }, + }, + failure: false, + }, + { + name: "empty events", + events: []string{}, + want: NewEventsFromMask(0), + }, + { + name: "invalid events", + events: []string{"foo:bar"}, + want: nil, + failure: true, + }, + } + + // run tests + for _, test := range tests { + got, err := NewEventsFromSlice(test.events) + + if test.failure { + if err == nil { + t.Errorf("NewEventsFromSlice should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("NewEventsFromSlice returned err: %v", err) + } + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("PopulateEvents failed for %s mismatch (-want +got):\n%s", test.name, diff) + } + } +} + +func TestTypes_Events_Allowed(t *testing.T) { + // setup types + eventsOne, eventsTwo := testEvents() + + // setup tests + tests := []struct { + event string + action string + want bool + }{ + {event: "push", want: true}, + {event: "tag", want: true}, + {event: "pull_request", action: "opened", want: true}, + {event: "pull_request", action: "synchronize", want: true}, + {event: "pull_request", action: "edited", want: false}, + {event: "pull_request", action: "reopened", want: true}, + {event: "pull_request", action: "labeled", want: false}, + {event: "pull_request", action: "unlabeled", want: true}, + {event: "deployment", action: "created", want: false}, + {event: "comment", action: "created", want: true}, + {event: "comment", action: "edited", want: false}, + {event: "schedule", want: true}, + {event: "delete", action: "branch", want: true}, + {event: "delete", action: "tag", want: false}, + } + + for _, test := range tests { + gotOne := eventsOne.Allowed(test.event, test.action) + gotTwo := eventsTwo.Allowed(test.event, test.action) + + if gotOne != test.want { + t.Errorf("Allowed for %s/%s is %v, want %v", test.event, test.action, gotOne, test.want) + } + + if gotTwo == test.want { + t.Errorf("Allowed Inverse for %s/%s is %v, want %v", test.event, test.action, gotTwo, !test.want) + } + } +} + +// testEvents is a helper test function that returns an Events struct and its inverse for unit test coverage. +func testEvents() (*Events, *Events) { + tBool := true + fBool := false + + e1 := &Events{ + Push: &actions.Push{ + Branch: &tBool, + Tag: &tBool, + DeleteBranch: &tBool, + DeleteTag: &fBool, + }, + PullRequest: &actions.Pull{ + Opened: &tBool, + Synchronize: &tBool, + Edited: &fBool, + Reopened: &tBool, + Labeled: &fBool, + Unlabeled: &tBool, + }, + Deployment: &actions.Deploy{ + Created: &fBool, + }, + Comment: &actions.Comment{ + Created: &tBool, + Edited: &fBool, + }, + Schedule: &actions.Schedule{ + Run: &tBool, + }, + } + + e2 := &Events{ + Push: &actions.Push{ + Branch: &fBool, + Tag: &fBool, + DeleteBranch: &fBool, + DeleteTag: &tBool, + }, + PullRequest: &actions.Pull{ + Opened: &fBool, + Synchronize: &fBool, + Edited: &tBool, + Reopened: &fBool, + Labeled: &tBool, + Unlabeled: &fBool, + }, + Deployment: &actions.Deploy{ + Created: &tBool, + }, + Comment: &actions.Comment{ + Created: &fBool, + Edited: &tBool, + }, + Schedule: &actions.Schedule{ + Run: &fBool, + }, + } + + return e1, e2 +} diff --git a/api/types/executor.go b/api/types/executor.go new file mode 100644 index 000000000..45bbfce2e --- /dev/null +++ b/api/types/executor.go @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "strings" + + "github.com/go-vela/types/pipeline" +) + +// Executor is the API representation of an executor for a worker. +// +// swagger:model Executor +type Executor struct { + ID *int64 `json:"id,omitempty"` + Host *string `json:"host,omitempty"` + Runtime *string `json:"runtime,omitempty"` + Distribution *string `json:"distribution,omitempty"` + Build *Build `json:"build,omitempty"` + Pipeline *pipeline.Build `json:"pipeline,omitempty"` +} + +// GetID returns the ID field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetID() int64 { + // return zero value if Executor type or ID field is nil + if e == nil || e.ID == nil { + return 0 + } + + return *e.ID +} + +// GetHost returns the Host field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetHost() string { + // return zero value if Executor type or Host field is nil + if e == nil || e.Host == nil { + return "" + } + + return *e.Host +} + +// GetRuntime returns the Runtime field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetRuntime() string { + // return zero value if Executor type or Runtime field is nil + if e == nil || e.Runtime == nil { + return "" + } + + return *e.Runtime +} + +// GetDistribution returns the Distribution field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetDistribution() string { + // return zero value if Executor type or Distribution field is nil + if e == nil || e.Distribution == nil { + return "" + } + + return *e.Distribution +} + +// GetBuild returns the Build field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetBuild() Build { + // return zero value if Executor type or Build field is nil + if e == nil || e.Build == nil { + return Build{} + } + + return *e.Build +} + +// GetPipeline returns the Pipeline field. +// +// When the provided Executor type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (e *Executor) GetPipeline() pipeline.Build { + // return zero value if Executor type or Pipeline field is nil + if e == nil || e.Pipeline == nil { + return pipeline.Build{} + } + + return *e.Pipeline +} + +// SetID sets the ID field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetID(v int64) { + // return if Executor type is nil + if e == nil { + return + } + + e.ID = &v +} + +// SetHost sets the Host field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetHost(v string) { + // return if Executor type is nil + if e == nil { + return + } + + e.Host = &v +} + +// SetRuntime sets the Runtime field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetRuntime(v string) { + // return if Executor type is nil + if e == nil { + return + } + + e.Runtime = &v +} + +// SetDistribution sets the Distribution field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetDistribution(v string) { + // return if Executor type is nil + if e == nil { + return + } + + e.Distribution = &v +} + +// SetBuild sets the Build field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetBuild(v Build) { + // return if Executor type is nil + if e == nil { + return + } + + e.Build = &v +} + +// SetPipeline sets the pipeline Build field. +// +// When the provided Executor type is nil, it +// will set nothing and immediately return. +func (e *Executor) SetPipeline(v pipeline.Build) { + // return if Executor type is nil + if e == nil { + return + } + + e.Pipeline = &v +} + +// String implements the Stringer interface for the Executor type. +func (e *Executor) String() string { + return fmt.Sprintf(`{ + Build: %s, + Distribution: %s, + Host: %s, + ID: %d, + Runtime: %s, + Pipeline: %v, +}`, + strings.ReplaceAll(e.Build.String(), " ", " "), + e.GetDistribution(), + e.GetHost(), + e.GetID(), + e.GetRuntime(), + e.GetPipeline(), + ) +} diff --git a/api/types/executor_test.go b/api/types/executor_test.go new file mode 100644 index 000000000..e47472b8a --- /dev/null +++ b/api/types/executor_test.go @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/go-vela/types/pipeline" +) + +func TestTypes_Executor_Getters(t *testing.T) { + // setup tests + tests := []struct { + executor *Executor + want *Executor + }{ + { + executor: testExecutor(), + want: testExecutor(), + }, + { + executor: new(Executor), + want: new(Executor), + }, + } + + // run tests + for _, test := range tests { + if test.executor.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.executor.GetID(), test.want.GetID()) + } + + if test.executor.GetHost() != test.want.GetHost() { + t.Errorf("GetHost is %v, want %v", test.executor.GetHost(), test.want.GetHost()) + } + + if test.executor.GetRuntime() != test.want.GetRuntime() { + t.Errorf("GetRuntime is %v, want %v", test.executor.GetRuntime(), test.want.GetRuntime()) + } + + if test.executor.GetDistribution() != test.want.GetDistribution() { + t.Errorf("GetDistribution is %v, want %v", test.executor.GetDistribution(), test.want.GetDistribution()) + } + + if !reflect.DeepEqual(test.executor.GetBuild(), test.want.GetBuild()) { + t.Errorf("GetBuild is %v, want %v", test.executor.GetBuild(), test.want.GetBuild()) + } + + if !reflect.DeepEqual(test.executor.GetPipeline(), test.want.GetPipeline()) { + t.Errorf("GetPipeline is %v, want %v", test.executor.GetPipeline(), test.want.GetPipeline()) + } + } +} + +func TestTypes_Executor_Setters(t *testing.T) { + // setup types + var e *Executor + + // setup tests + tests := []struct { + executor *Executor + want *Executor + }{ + { + executor: testExecutor(), + want: testExecutor(), + }, + { + executor: e, + want: new(Executor), + }, + } + + // run tests + for _, test := range tests { + test.executor.SetID(test.want.GetID()) + test.executor.SetHost(test.want.GetHost()) + test.executor.SetRuntime(test.want.GetRuntime()) + test.executor.SetDistribution(test.want.GetDistribution()) + test.executor.SetBuild(test.want.GetBuild()) + test.executor.SetPipeline(test.want.GetPipeline()) + + if test.executor.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.executor.GetID(), test.want.GetID()) + } + + if test.executor.GetHost() != test.want.GetHost() { + t.Errorf("SetHost is %v, want %v", test.executor.GetHost(), test.want.GetHost()) + } + + if test.executor.GetRuntime() != test.want.GetRuntime() { + t.Errorf("SetRuntime is %v, want %v", test.executor.GetRuntime(), test.want.GetRuntime()) + } + + if test.executor.GetDistribution() != test.want.GetDistribution() { + t.Errorf("SetDistribution is %v, want %v", test.executor.GetDistribution(), test.want.GetDistribution()) + } + + if !reflect.DeepEqual(test.executor.GetBuild(), test.want.GetBuild()) { + t.Errorf("SetBuild is %v, want %v", test.executor.GetBuild(), test.want.GetBuild()) + } + + if !reflect.DeepEqual(test.executor.GetPipeline(), test.want.GetPipeline()) { + t.Errorf("SetPipeline is %v, want %v", test.executor.GetPipeline(), test.want.GetPipeline()) + } + } +} + +func TestTypes_Executor_String(t *testing.T) { + // setup types + e := testExecutor() + + want := fmt.Sprintf(`{ + Build: %s, + Distribution: %s, + Host: %s, + ID: %d, + Runtime: %s, + Pipeline: %v, +}`, + strings.ReplaceAll(e.Build.String(), " ", " "), + e.GetDistribution(), + e.GetHost(), + e.GetID(), + e.GetRuntime(), + e.GetPipeline(), + ) + + // run test + got := e.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testExecutor is a test helper function to create a Executor +// type with all fields set to a fake value. +func testExecutor() *Executor { + e := new(Executor) + + e.SetID(1) + e.SetHost("company.example.com") + e.SetRuntime("docker") + e.SetDistribution("linux") + e.SetBuild(*testBuild()) + e.SetPipeline(pipeline.Build{ + Version: "1", + ID: "github_octocat_1", + Steps: pipeline.ContainerSlice{ + { + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + }, + { + ID: "step_github_octocat_1_clone", + Directory: "/home/github/octocat", + Image: "target/vela-git:v0.3.0", + Name: "clone", + Number: 2, + Pull: "always", + }, + { + ID: "step_github_octocat_1_echo", + Commands: []string{"echo hello"}, + Directory: "/home/github/octocat", + Image: "alpine:latest", + Name: "echo", + Number: 3, + Pull: "always", + }, + }, + }) + + return e +} diff --git a/api/types/oidc.go b/api/types/oidc.go new file mode 100644 index 000000000..50b106fb0 --- /dev/null +++ b/api/types/oidc.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "github.com/golang-jwt/jwt/v5" + "github.com/lestrrat-go/jwx/v2/jwk" +) + +// OpenIDConfig is a struct that represents the OpenID Connect configuration. +// +// swagger:model OpenIDConfig +type OpenIDConfig struct { + Issuer string `json:"issuer"` + JWKSAddress string `json:"jwks_uri"` + SupportedClaims []string `json:"supported_claims"` + Algorithms []string `json:"id_token_signing_alg_values_supported"` +} + +// OpenIDClaims struct is an extension of the JWT standard claims. It +// includes information relevant to OIDC services. +type OpenIDClaims struct { + BuildNumber int `json:"build_number,omitempty"` + BuildID int64 `json:"build_id,omitempty"` + Actor string `json:"actor,omitempty"` + ActorSCMID string `json:"actor_scm_id,omitempty"` + Repo string `json:"repo,omitempty"` + TokenType string `json:"token_type,omitempty"` + Image string `json:"image,omitempty"` + Request string `json:"request,omitempty"` + Commands bool `json:"commands,omitempty"` + Event string `json:"event,omitempty"` + Ref string `json:"ref,omitempty"` + SHA string `json:"sha,omitempty"` + jwt.RegisteredClaims +} + +// JWKSet is a wrapper of lestrrat-go/jwx/jwk.Set for API Swagger gen. +// +// swagger:model JWKSet +type JWKSet struct { + jwk.Set +} diff --git a/api/types/queue_build.go b/api/types/queue_build.go new file mode 100644 index 000000000..8b39c3bc7 --- /dev/null +++ b/api/types/queue_build.go @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "fmt" + +// QueueBuild is the API representation of the builds in the queue. +// +// swagger:model QueueBuild +type QueueBuild struct { + Status *string `json:"status,omitempty"` + Number *int32 `json:"number,omitempty"` + Created *int64 `json:"created,omitempty"` + FullName *string `json:"full_name,omitempty"` +} + +// GetStatus returns the Status field. +// +// When the provided QueueBuild type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *QueueBuild) GetStatus() string { + // return zero value if QueueBuild type or Status field is nil + if b == nil || b.Status == nil { + return "" + } + + return *b.Status +} + +// GetNumber returns the Number field. +// +// When the provided QueueBuild type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *QueueBuild) GetNumber() int32 { + // return zero value if QueueBuild type or Number field is nil + if b == nil || b.Number == nil { + return 0 + } + + return *b.Number +} + +// GetCreated returns the Created field. +// +// When the provided QueueBuild type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *QueueBuild) GetCreated() int64 { + // return zero value if QueueBuild type or Created field is nil + if b == nil || b.Created == nil { + return 0 + } + + return *b.Created +} + +// GetFullName returns the FullName field. +// +// When the provided QueueBuild type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (b *QueueBuild) GetFullName() string { + // return zero value if QueueBuild type or FullName field is nil + if b == nil || b.FullName == nil { + return "" + } + + return *b.FullName +} + +// SetStatus sets the Status field. +// +// When the provided QueueBuild type is nil, it +// will set nothing and immediately return. +func (b *QueueBuild) SetStatus(v string) { + // return if QueueBuild type is nil + if b == nil { + return + } + + b.Status = &v +} + +// SetNumber sets the Number field. +// +// When the provided QueueBuild type is nil, it +// will set nothing and immediately return. +func (b *QueueBuild) SetNumber(v int32) { + // return if QueueBuild type is nil + if b == nil { + return + } + + b.Number = &v +} + +// SetCreated sets the Created field. +// +// When the provided QueueBuild type is nil, it +// will set nothing and immediately return. +func (b *QueueBuild) SetCreated(v int64) { + // return if QueueBuild type is nil + if b == nil { + return + } + + b.Created = &v +} + +// SetFullName sets the FullName field. +// +// When the provided QueueBuild type is nil, it +// will set nothing and immediately return. +func (b *QueueBuild) SetFullName(v string) { + // return if QueueBuild type is nil + if b == nil { + return + } + + b.FullName = &v +} + +// String implements the Stringer interface for the QueueBuild type. +func (b *QueueBuild) String() string { + return fmt.Sprintf(`{ + Created: %d, + FullName: %s, + Number: %d, + Status: %s, +}`, + b.GetCreated(), + b.GetFullName(), + b.GetNumber(), + b.GetStatus(), + ) +} diff --git a/api/types/queue_build_test.go b/api/types/queue_build_test.go new file mode 100644 index 000000000..6351b8624 --- /dev/null +++ b/api/types/queue_build_test.go @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_QueueBuild_Getters(t *testing.T) { + // setup tests + tests := []struct { + buildQueue *QueueBuild + want *QueueBuild + }{ + { + buildQueue: testQueueBuild(), + want: testQueueBuild(), + }, + { + buildQueue: new(QueueBuild), + want: new(QueueBuild), + }, + } + + // run tests + for _, test := range tests { + if test.buildQueue.GetNumber() != test.want.GetNumber() { + t.Errorf("GetNumber is %v, want %v", test.buildQueue.GetNumber(), test.want.GetNumber()) + } + + if test.buildQueue.GetStatus() != test.want.GetStatus() { + t.Errorf("GetStatus is %v, want %v", test.buildQueue.GetStatus(), test.want.GetStatus()) + } + + if test.buildQueue.GetCreated() != test.want.GetCreated() { + t.Errorf("GetCreated is %v, want %v", test.buildQueue.GetCreated(), test.want.GetCreated()) + } + + if test.buildQueue.GetFullName() != test.want.GetFullName() { + t.Errorf("GetFullName is %v, want %v", test.buildQueue.GetFullName(), test.want.GetFullName()) + } + } +} + +func TestTypes_QueueBuild_Setters(t *testing.T) { + // setup types + var b *QueueBuild + + // setup tests + tests := []struct { + buildQueue *QueueBuild + want *QueueBuild + }{ + { + buildQueue: testQueueBuild(), + want: testQueueBuild(), + }, + { + buildQueue: b, + want: new(QueueBuild), + }, + } + + // run tests + for _, test := range tests { + test.buildQueue.SetNumber(test.want.GetNumber()) + test.buildQueue.SetStatus(test.want.GetStatus()) + test.buildQueue.SetCreated(test.want.GetCreated()) + test.buildQueue.SetFullName(test.want.GetFullName()) + + if test.buildQueue.GetNumber() != test.want.GetNumber() { + t.Errorf("SetNumber is %v, want %v", test.buildQueue.GetNumber(), test.want.GetNumber()) + } + + if test.buildQueue.GetStatus() != test.want.GetStatus() { + t.Errorf("SetStatus is %v, want %v", test.buildQueue.GetStatus(), test.want.GetStatus()) + } + + if test.buildQueue.GetCreated() != test.want.GetCreated() { + t.Errorf("SetCreated is %v, want %v", test.buildQueue.GetCreated(), test.want.GetCreated()) + } + + if test.buildQueue.GetFullName() != test.want.GetFullName() { + t.Errorf("SetFullName is %v, want %v", test.buildQueue.GetFullName(), test.want.GetFullName()) + } + } +} + +func TestTypes_QueueBuild_String(t *testing.T) { + // setup types + b := testQueueBuild() + + want := fmt.Sprintf(`{ + Created: %d, + FullName: %s, + Number: %d, + Status: %s, +}`, + b.GetCreated(), + b.GetFullName(), + b.GetNumber(), + b.GetStatus(), + ) + + // run test + got := b.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testQueueBuild is a test helper function to create a QueueBuild +// type with all fields set to a fake value. +func testQueueBuild() *QueueBuild { + b := new(QueueBuild) + + b.SetNumber(1) + b.SetStatus("running") + b.SetCreated(1563474076) + b.SetFullName("github/octocat") + + return b +} diff --git a/api/types/repo.go b/api/types/repo.go new file mode 100644 index 000000000..01e9f03ea --- /dev/null +++ b/api/types/repo.go @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "strings" +) + +// Repo is the API representation of a repo. +// +// swagger:model Repo +type Repo struct { + ID *int64 `json:"id,omitempty"` + Owner *User `json:"owner,omitempty"` + Hash *string `json:"-"` + Org *string `json:"org,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Link *string `json:"link,omitempty"` + Clone *string `json:"clone,omitempty"` + Branch *string `json:"branch,omitempty"` + Topics *[]string `json:"topics,omitempty"` + BuildLimit *int64 `json:"build_limit,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Counter *int `json:"counter,omitempty"` + Visibility *string `json:"visibility,omitempty"` + Private *bool `json:"private,omitempty"` + Trusted *bool `json:"trusted,omitempty"` + Active *bool `json:"active,omitempty"` + AllowEvents *Events `json:"allow_events,omitempty"` + PipelineType *string `json:"pipeline_type,omitempty"` + PreviousName *string `json:"previous_name,omitempty"` + ApproveBuild *string `json:"approve_build,omitempty"` + InstallID *int64 `json:"install_id,omitempty"` +} + +// Environment returns a list of environment variables +// provided from the fields of the Repo type. +func (r *Repo) Environment() map[string]string { + return map[string]string{ + "VELA_REPO_ACTIVE": ToString(r.GetActive()), + "VELA_REPO_ALLOW_EVENTS": strings.Join(r.GetAllowEvents().List()[:], ","), + "VELA_REPO_BRANCH": ToString(r.GetBranch()), + "VELA_REPO_TOPICS": strings.Join(r.GetTopics()[:], ","), + "VELA_REPO_BUILD_LIMIT": ToString(r.GetBuildLimit()), + "VELA_REPO_CLONE": ToString(r.GetClone()), + "VELA_REPO_FULL_NAME": ToString(r.GetFullName()), + "VELA_REPO_LINK": ToString(r.GetLink()), + "VELA_REPO_NAME": ToString(r.GetName()), + "VELA_REPO_ORG": ToString(r.GetOrg()), + "VELA_REPO_PRIVATE": ToString(r.GetPrivate()), + "VELA_REPO_TIMEOUT": ToString(r.GetTimeout()), + "VELA_REPO_TRUSTED": ToString(r.GetTrusted()), + "VELA_REPO_VISIBILITY": ToString(r.GetVisibility()), + "VELA_REPO_PIPELINE_TYPE": ToString(r.GetPipelineType()), + "VELA_REPO_APPROVE_BUILD": ToString(r.GetApproveBuild()), + "VELA_REPO_OWNER": ToString(r.GetOwner().GetName()), + + // deprecated environment variables + "REPOSITORY_ACTIVE": ToString(r.GetActive()), + "REPOSITORY_ALLOW_EVENTS": strings.Join(r.GetAllowEvents().List()[:], ","), + "REPOSITORY_BRANCH": ToString(r.GetBranch()), + "REPOSITORY_CLONE": ToString(r.GetClone()), + "REPOSITORY_FULL_NAME": ToString(r.GetFullName()), + "REPOSITORY_LINK": ToString(r.GetLink()), + "REPOSITORY_NAME": ToString(r.GetName()), + "REPOSITORY_ORG": ToString(r.GetOrg()), + "REPOSITORY_PRIVATE": ToString(r.GetPrivate()), + "REPOSITORY_TIMEOUT": ToString(r.GetTimeout()), + "REPOSITORY_TRUSTED": ToString(r.GetTrusted()), + "REPOSITORY_VISIBILITY": ToString(r.GetVisibility()), + } +} + +// GetID returns the ID field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetID() int64 { + // return zero value if Repo type or ID field is nil + if r == nil || r.ID == nil { + return 0 + } + + return *r.ID +} + +// GetOwner returns the Owner field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetOwner() *User { + // return zero value if Repo type or Owner field is nil + if r == nil || r.Owner == nil { + return new(User) + } + + return r.Owner +} + +// GetHash returns the Hash field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetHash() string { + // return zero value if Repo type or Hash field is nil + if r == nil || r.Hash == nil { + return "" + } + + return *r.Hash +} + +// GetOrg returns the Org field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetOrg() string { + // return zero value if Repo type or Org field is nil + if r == nil || r.Org == nil { + return "" + } + + return *r.Org +} + +// GetName returns the Name field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetName() string { + // return zero value if Repo type or Name field is nil + if r == nil || r.Name == nil { + return "" + } + + return *r.Name +} + +// GetFullName returns the FullName field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetFullName() string { + // return zero value if Repo type or FullName field is nil + if r == nil || r.FullName == nil { + return "" + } + + return *r.FullName +} + +// GetLink returns the Link field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetLink() string { + // return zero value if Repo type or Link field is nil + if r == nil || r.Link == nil { + return "" + } + + return *r.Link +} + +// GetClone returns the Clone field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetClone() string { + // return zero value if Repo type or Clone field is nil + if r == nil || r.Clone == nil { + return "" + } + + return *r.Clone +} + +// GetBranch returns the Branch field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetBranch() string { + // return zero value if Repo type or Branch field is nil + if r == nil || r.Branch == nil { + return "" + } + + return *r.Branch +} + +// GetTopics returns the Topics field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetTopics() []string { + // return zero value if Repo type or Topics field is nil + if r == nil || r.Topics == nil { + return []string{} + } + + return *r.Topics +} + +// GetBuildLimit returns the BuildLimit field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetBuildLimit() int64 { + // return zero value if Repo type or BuildLimit field is nil + if r == nil || r.BuildLimit == nil { + return 0 + } + + return *r.BuildLimit +} + +// GetTimeout returns the Timeout field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetTimeout() int64 { + // return zero value if Repo type or Timeout field is nil + if r == nil || r.Timeout == nil { + return 0 + } + + return *r.Timeout +} + +// GetCounter returns the Counter field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetCounter() int { + // return zero value if Repo type or Counter field is nil + if r == nil || r.Counter == nil { + return 0 + } + + return *r.Counter +} + +// GetVisibility returns the Visibility field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetVisibility() string { + // return zero value if Repo type or Visibility field is nil + if r == nil || r.Visibility == nil { + return "" + } + + return *r.Visibility +} + +// GetPrivate returns the Private field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetPrivate() bool { + // return zero value if Repo type or Private field is nil + if r == nil || r.Private == nil { + return false + } + + return *r.Private +} + +// GetTrusted returns the Trusted field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetTrusted() bool { + // return zero value if Repo type or Trusted field is nil + if r == nil || r.Trusted == nil { + return false + } + + return *r.Trusted +} + +// GetActive returns the Active field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetActive() bool { + // return zero value if Repo type or Active field is nil + if r == nil || r.Active == nil { + return false + } + + return *r.Active +} + +// GetAllowEvents returns the AllowEvents field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetAllowEvents() *Events { + // return zero value if Repo type or AllowPull field is nil + if r == nil || r.AllowEvents == nil { + return new(Events) + } + + return r.AllowEvents +} + +// GetPipelineType returns the PipelineType field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetPipelineType() string { + // return zero value if Repo type or PipelineType field is nil + if r == nil || r.PipelineType == nil { + return "" + } + + return *r.PipelineType +} + +// GetPreviousName returns the PreviousName field. +// +// When the provided Repo type is nil, or the field within +// Â the type is nil, it returns the zero value for the field. +func (r *Repo) GetPreviousName() string { + // return zero value if Repo type or PreviousName field is nil + if r == nil || r.PreviousName == nil { + return "" + } + + return *r.PreviousName +} + +// GetApproveBuild returns the ApproveBuild field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetApproveBuild() string { + // return zero value if Repo type or ApproveBuild field is nil + if r == nil || r.ApproveBuild == nil { + return "" + } + + return *r.ApproveBuild +} + +// GetInstallID returns the InstallID field. +// +// When the provided Repo type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (r *Repo) GetInstallID() int64 { + // return zero value if Repo type or InstallID field is nil + if r == nil || r.InstallID == nil { + return 0 + } + + return *r.InstallID +} + +// SetID sets the ID field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetID(v int64) { + // return if Repo type is nil + if r == nil { + return + } + + r.ID = &v +} + +// SetOwner sets the Owner field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetOwner(v *User) { + // return if Repo type is nil + if r == nil { + return + } + + r.Owner = v +} + +// SetHash sets the Hash field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetHash(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Hash = &v +} + +// SetOrg sets the Org field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetOrg(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Org = &v +} + +// SetName sets the Name field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetName(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Name = &v +} + +// SetFullName sets the FullName field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetFullName(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.FullName = &v +} + +// SetLink sets the Link field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetLink(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Link = &v +} + +// SetClone sets the Clone field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetClone(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Clone = &v +} + +// SetBranch sets the Branch field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetBranch(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Branch = &v +} + +// SetTopics sets the Topics field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetTopics(v []string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Topics = &v +} + +// SetBuildLimit sets the BuildLimit field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetBuildLimit(v int64) { + // return if Repo type is nil + if r == nil { + return + } + + r.BuildLimit = &v +} + +// SetTimeout sets the Timeout field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetTimeout(v int64) { + // return if Repo type is nil + if r == nil { + return + } + + r.Timeout = &v +} + +// SetCounter sets the Counter field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetCounter(v int) { + // return if Repo type is nil + if r == nil { + return + } + + r.Counter = &v +} + +// SetVisibility sets the Visibility field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetVisibility(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.Visibility = &v +} + +// SetPrivate sets the Private field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetPrivate(v bool) { + // return if Repo type is nil + if r == nil { + return + } + + r.Private = &v +} + +// SetTrusted sets the Trusted field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetTrusted(v bool) { + // return if Repo type is nil + if r == nil { + return + } + + r.Trusted = &v +} + +// SetActive sets the Active field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetActive(v bool) { + // return if Repo type is nil + if r == nil { + return + } + + r.Active = &v +} + +// SetAllowEvents sets the AllowEvents field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetAllowEvents(v *Events) { + // return if Repo type is nil + if r == nil { + return + } + + r.AllowEvents = v +} + +// SetPipelineType sets the PipelineType field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetPipelineType(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.PipelineType = &v +} + +// SetPreviousName sets the PreviousName field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetPreviousName(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.PreviousName = &v +} + +// SetApproveBuild sets the ApproveBuild field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetApproveBuild(v string) { + // return if Repo type is nil + if r == nil { + return + } + + r.ApproveBuild = &v +} + +// SetInstallID sets the InstallID field. +// +// When the provided Repo type is nil, it +// will set nothing and immediately return. +func (r *Repo) SetInstallID(v int64) { + // return if Repo type is nil + if r == nil { + return + } + + r.InstallID = &v +} + +// String implements the Stringer interface for the Repo type. +func (r *Repo) String() string { + return fmt.Sprintf(`{ + Active: %t, + AllowEvents: %s, + ApproveBuild: %s, + Branch: %s, + BuildLimit: %d, + Clone: %s, + Counter: %d, + FullName: %s, + ID: %d, + Link: %s, + Name: %s, + Org: %s, + Owner: %v, + PipelineType: %s, + PreviousName: %s, + Private: %t, + Timeout: %d, + Topics: %s, + Trusted: %t, + Visibility: %s +}`, + r.GetActive(), + r.GetAllowEvents().List(), + r.GetApproveBuild(), + r.GetBranch(), + r.GetBuildLimit(), + r.GetClone(), + r.GetCounter(), + r.GetFullName(), + r.GetID(), + r.GetLink(), + r.GetName(), + r.GetOrg(), + r.GetOwner(), + r.GetPipelineType(), + r.GetPreviousName(), + r.GetPrivate(), + r.GetTimeout(), + r.GetTopics(), + r.GetTrusted(), + r.GetVisibility(), + ) +} diff --git a/api/types/repo_test.go b/api/types/repo_test.go new file mode 100644 index 000000000..2cbe84639 --- /dev/null +++ b/api/types/repo_test.go @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/go-vela/types/constants" +) + +func TestTypes_Repo_Environment(t *testing.T) { + // setup types + want := map[string]string{ + "VELA_REPO_ACTIVE": "true", + "VELA_REPO_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", + "VELA_REPO_BRANCH": "main", + "VELA_REPO_TOPICS": "cloud,security", + "VELA_REPO_BUILD_LIMIT": "10", + "VELA_REPO_CLONE": "https://github.com/github/octocat.git", + "VELA_REPO_FULL_NAME": "github/octocat", + "VELA_REPO_LINK": "https://github.com/github/octocat", + "VELA_REPO_NAME": "octocat", + "VELA_REPO_ORG": "github", + "VELA_REPO_PRIVATE": "false", + "VELA_REPO_TIMEOUT": "30", + "VELA_REPO_TRUSTED": "false", + "VELA_REPO_VISIBILITY": "public", + "VELA_REPO_PIPELINE_TYPE": "", + "VELA_REPO_APPROVE_BUILD": "never", + "VELA_REPO_OWNER": "octocat", + "REPOSITORY_ACTIVE": "true", + "REPOSITORY_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", + "REPOSITORY_BRANCH": "main", + "REPOSITORY_CLONE": "https://github.com/github/octocat.git", + "REPOSITORY_FULL_NAME": "github/octocat", + "REPOSITORY_LINK": "https://github.com/github/octocat", + "REPOSITORY_NAME": "octocat", + "REPOSITORY_ORG": "github", + "REPOSITORY_PRIVATE": "false", + "REPOSITORY_TIMEOUT": "30", + "REPOSITORY_TRUSTED": "false", + "REPOSITORY_VISIBILITY": "public", + } + + // run test + got := testRepo().Environment() + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("(Environment: -want +got):\n%s", diff) + } +} + +func TestTypes_Repo_Getters(t *testing.T) { + // setup tests + tests := []struct { + repo *Repo + want *Repo + }{ + { + repo: testRepo(), + want: testRepo(), + }, + { + repo: new(Repo), + want: new(Repo), + }, + } + + // run tests + for _, test := range tests { + if test.repo.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.repo.GetID(), test.want.GetID()) + } + + if !reflect.DeepEqual(test.repo.GetOwner(), test.want.GetOwner()) { + t.Errorf("GetOwner is %v, want %v", test.repo.GetOwner(), test.want.GetOwner()) + } + + if test.repo.GetHash() != test.want.GetHash() { + t.Errorf("GetHash is %v, want %v", test.repo.GetHash(), test.want.GetHash()) + } + + if test.repo.GetOrg() != test.want.GetOrg() { + t.Errorf("GetOrg is %v, want %v", test.repo.GetOrg(), test.want.GetOrg()) + } + + if test.repo.GetName() != test.want.GetName() { + t.Errorf("GetName is %v, want %v", test.repo.GetName(), test.want.GetName()) + } + + if test.repo.GetFullName() != test.want.GetFullName() { + t.Errorf("GetFullName is %v, want %v", test.repo.GetFullName(), test.want.GetFullName()) + } + + if test.repo.GetLink() != test.want.GetLink() { + t.Errorf("GetLink is %v, want %v", test.repo.GetLink(), test.want.GetLink()) + } + + if test.repo.GetClone() != test.want.GetClone() { + t.Errorf("GetClone is %v, want %v", test.repo.GetClone(), test.want.GetClone()) + } + + if test.repo.GetBranch() != test.want.GetBranch() { + t.Errorf("GetBranch is %v, want %v", test.repo.GetBranch(), test.want.GetBranch()) + } + + if !reflect.DeepEqual(test.repo.GetTopics(), test.want.GetTopics()) { + t.Errorf("GetTopics is %v, want %v", test.repo.GetTopics(), test.want.GetTopics()) + } + + if test.repo.GetBuildLimit() != test.want.GetBuildLimit() { + t.Errorf("GetBuildLimit is %v, want %v", test.repo.GetBuildLimit(), test.want.GetBuildLimit()) + } + + if test.repo.GetTimeout() != test.want.GetTimeout() { + t.Errorf("GetTimeout is %v, want %v", test.repo.GetTimeout(), test.want.GetTimeout()) + } + + if test.repo.GetVisibility() != test.want.GetVisibility() { + t.Errorf("GetVisibility is %v, want %v", test.repo.GetVisibility(), test.want.GetVisibility()) + } + + if test.repo.GetPrivate() != test.want.GetPrivate() { + t.Errorf("GetPrivate is %v, want %v", test.repo.GetPrivate(), test.want.GetPrivate()) + } + + if test.repo.GetTrusted() != test.want.GetTrusted() { + t.Errorf("GetTrusted is %v, want %v", test.repo.GetTrusted(), test.want.GetTrusted()) + } + + if test.repo.GetActive() != test.want.GetActive() { + t.Errorf("GetActive is %v, want %v", test.repo.GetActive(), test.want.GetActive()) + } + + if !reflect.DeepEqual(test.repo.GetAllowEvents(), test.want.GetAllowEvents()) { + t.Errorf("GetRepo is %v, want %v", test.repo.GetAllowEvents(), test.want.GetAllowEvents()) + } + + if test.repo.GetPipelineType() != test.want.GetPipelineType() { + t.Errorf("GetPipelineType is %v, want %v", test.repo.GetPipelineType(), test.want.GetPipelineType()) + } + + if !reflect.DeepEqual(test.repo.GetPreviousName(), test.want.GetPreviousName()) { + t.Errorf("GetPreviousName is %v, want %v", test.repo.GetPreviousName(), test.want.GetPreviousName()) + } + + if test.repo.GetApproveBuild() != test.want.GetApproveBuild() { + t.Errorf("GetApproveForkBuild is %v, want %v", test.repo.GetApproveBuild(), test.want.GetApproveBuild()) + } + } +} + +func TestTypes_Repo_Setters(t *testing.T) { + // setup types + var r *Repo + + // setup tests + tests := []struct { + repo *Repo + want *Repo + }{ + { + repo: testRepo(), + want: testRepo(), + }, + { + repo: r, + want: new(Repo), + }, + } + + // run tests + for _, test := range tests { + test.repo.SetID(test.want.GetID()) + test.repo.SetOwner(test.want.GetOwner()) + test.repo.SetHash(test.want.GetHash()) + test.repo.SetOrg(test.want.GetOrg()) + test.repo.SetName(test.want.GetName()) + test.repo.SetFullName(test.want.GetFullName()) + test.repo.SetLink(test.want.GetLink()) + test.repo.SetClone(test.want.GetClone()) + test.repo.SetBranch(test.want.GetBranch()) + test.repo.SetTopics(test.want.GetTopics()) + test.repo.SetBuildLimit(test.want.GetBuildLimit()) + test.repo.SetTimeout(test.want.GetTimeout()) + test.repo.SetCounter(test.want.GetCounter()) + test.repo.SetVisibility(test.want.GetVisibility()) + test.repo.SetPrivate(test.want.GetPrivate()) + test.repo.SetTrusted(test.want.GetTrusted()) + test.repo.SetActive(test.want.GetActive()) + test.repo.SetAllowEvents(test.want.GetAllowEvents()) + test.repo.SetPipelineType(test.want.GetPipelineType()) + test.repo.SetPreviousName(test.want.GetPreviousName()) + test.repo.SetApproveBuild(test.want.GetApproveBuild()) + + if test.repo.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.repo.GetID(), test.want.GetID()) + } + + if !reflect.DeepEqual(test.repo.GetOwner(), test.want.GetOwner()) { + t.Errorf("SetOwner is %v, want %v", test.repo.GetOwner(), test.want.GetOwner()) + } + + if test.repo.GetHash() != test.want.GetHash() { + t.Errorf("SetHash is %v, want %v", test.repo.GetHash(), test.want.GetHash()) + } + + if test.repo.GetOrg() != test.want.GetOrg() { + t.Errorf("SetOrg is %v, want %v", test.repo.GetOrg(), test.want.GetOrg()) + } + + if test.repo.GetName() != test.want.GetName() { + t.Errorf("SetName is %v, want %v", test.repo.GetName(), test.want.GetName()) + } + + if test.repo.GetFullName() != test.want.GetFullName() { + t.Errorf("SetFullName is %v, want %v", test.repo.GetFullName(), test.want.GetFullName()) + } + + if test.repo.GetLink() != test.want.GetLink() { + t.Errorf("SetLink is %v, want %v", test.repo.GetLink(), test.want.GetLink()) + } + + if test.repo.GetClone() != test.want.GetClone() { + t.Errorf("SetClone is %v, want %v", test.repo.GetClone(), test.want.GetClone()) + } + + if test.repo.GetBranch() != test.want.GetBranch() { + t.Errorf("SetBranch is %v, want %v", test.repo.GetBranch(), test.want.GetBranch()) + } + + if !reflect.DeepEqual(test.repo.GetTopics(), test.want.GetTopics()) { + t.Errorf("SetTopics is %v, want %v", test.repo.GetTopics(), test.want.GetTopics()) + } + + if test.repo.GetBuildLimit() != test.want.GetBuildLimit() { + t.Errorf("SetBuildLimit is %v, want %v", test.repo.GetBuildLimit(), test.want.GetBuildLimit()) + } + + if test.repo.GetTimeout() != test.want.GetTimeout() { + t.Errorf("SetTimeout is %v, want %v", test.repo.GetTimeout(), test.want.GetTimeout()) + } + + if test.repo.GetVisibility() != test.want.GetVisibility() { + t.Errorf("SetVisibility is %v, want %v", test.repo.GetVisibility(), test.want.GetVisibility()) + } + + if test.repo.GetPrivate() != test.want.GetPrivate() { + t.Errorf("SetPrivate is %v, want %v", test.repo.GetPrivate(), test.want.GetPrivate()) + } + + if test.repo.GetTrusted() != test.want.GetTrusted() { + t.Errorf("SetTrusted is %v, want %v", test.repo.GetTrusted(), test.want.GetTrusted()) + } + + if test.repo.GetActive() != test.want.GetActive() { + t.Errorf("SetActive is %v, want %v", test.repo.GetActive(), test.want.GetActive()) + } + + if !reflect.DeepEqual(test.repo.GetAllowEvents(), test.want.GetAllowEvents()) { + t.Errorf("GetRepo is %v, want %v", test.repo.GetAllowEvents(), test.want.GetAllowEvents()) + } + + if test.repo.GetPipelineType() != test.want.GetPipelineType() { + t.Errorf("SetPipelineType is %v, want %v", test.repo.GetPipelineType(), test.want.GetPipelineType()) + } + + if !reflect.DeepEqual(test.repo.GetPreviousName(), test.want.GetPreviousName()) { + t.Errorf("SetPreviousName is %v, want %v", test.repo.GetPreviousName(), test.want.GetPreviousName()) + } + + if test.repo.GetApproveBuild() != test.want.GetApproveBuild() { + t.Errorf("SetApproveForkBuild is %v, want %v", test.repo.GetApproveBuild(), test.want.GetApproveBuild()) + } + } +} + +func TestTypes_Repo_String(t *testing.T) { + // setup types + r := testRepo() + + want := fmt.Sprintf(`{ + Active: %t, + AllowEvents: %s, + ApproveBuild: %s, + Branch: %s, + BuildLimit: %d, + Clone: %s, + Counter: %d, + FullName: %s, + ID: %d, + Link: %s, + Name: %s, + Org: %s, + Owner: %v, + PipelineType: %s, + PreviousName: %s, + Private: %t, + Timeout: %d, + Topics: %s, + Trusted: %t, + Visibility: %s +}`, + r.GetActive(), + r.GetAllowEvents().List(), + r.GetApproveBuild(), + r.GetBranch(), + r.GetBuildLimit(), + r.GetClone(), + r.GetCounter(), + r.GetFullName(), + r.GetID(), + r.GetLink(), + r.GetName(), + r.GetOrg(), + r.GetOwner(), + r.GetPipelineType(), + r.GetPreviousName(), + r.GetPrivate(), + r.GetTimeout(), + r.GetTopics(), + r.GetTrusted(), + r.GetVisibility(), + ) + + // run test + got := r.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testRepo is a test helper function to create a Repo +// type with all fields set to a fake value. +func testRepo() *Repo { + r := new(Repo) + + e, _ := testEvents() + + r.SetID(1) + r.SetOwner(testUser()) + r.SetOrg("github") + r.SetName("octocat") + r.SetFullName("github/octocat") + r.SetLink("https://github.com/github/octocat") + r.SetClone("https://github.com/github/octocat.git") + r.SetBranch("main") + r.SetTopics([]string{"cloud", "security"}) + r.SetBuildLimit(10) + r.SetTimeout(30) + r.SetCounter(0) + r.SetVisibility("public") + r.SetPrivate(false) + r.SetTrusted(false) + r.SetActive(true) + r.SetAllowEvents(e) + r.SetPipelineType("") + r.SetPreviousName("") + r.SetApproveBuild(constants.ApproveNever) + + return r +} diff --git a/api/types/schedule.go b/api/types/schedule.go new file mode 100644 index 000000000..2fb174779 --- /dev/null +++ b/api/types/schedule.go @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" +) + +// Schedule is the API representation of a schedule for a repo. +// +// swagger:model Schedule +type Schedule struct { + ID *int64 `json:"id,omitempty"` + Repo *Repo `json:"repo,omitempty"` + Active *bool `json:"active,omitempty"` + Name *string `json:"name,omitempty"` + Entry *string `json:"entry,omitempty"` + CreatedAt *int64 `json:"created_at,omitempty"` + CreatedBy *string `json:"created_by,omitempty"` + UpdatedAt *int64 `json:"updated_at,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` + ScheduledAt *int64 `json:"scheduled_at,omitempty"` + Branch *string `json:"branch,omitempty"` + Error *string `json:"error,omitempty"` + NextRun *int64 `json:"next_run,omitempty"` +} + +// GetID returns the ID field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetID() int64 { + // return zero value if Schedule type or ID field is nil + if s == nil || s.ID == nil { + return 0 + } + + return *s.ID +} + +// GetRepo returns the Repo field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetRepo() *Repo { + // return zero value if Schedule type or RepoID field is nil + if s == nil || s.Repo == nil { + return new(Repo) + } + + return s.Repo +} + +// GetActive returns the Active field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetActive() bool { + // return zero value if Schedule type or Active field is nil + if s == nil || s.Active == nil { + return false + } + + return *s.Active +} + +// GetName returns the Name field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetName() string { + // return zero value if Schedule type or Name field is nil + if s == nil || s.Name == nil { + return "" + } + + return *s.Name +} + +// GetEntry returns the Entry field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetEntry() string { + // return zero value if Schedule type or Entry field is nil + if s == nil || s.Entry == nil { + return "" + } + + return *s.Entry +} + +// GetCreatedAt returns the CreatedAt field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetCreatedAt() int64 { + // return zero value if Schedule type or CreatedAt field is nil + if s == nil || s.CreatedAt == nil { + return 0 + } + + return *s.CreatedAt +} + +// GetCreatedBy returns the CreatedBy field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetCreatedBy() string { + // return zero value if Schedule type or CreatedBy field is nil + if s == nil || s.CreatedBy == nil { + return "" + } + + return *s.CreatedBy +} + +// GetUpdatedAt returns the UpdatedAt field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetUpdatedAt() int64 { + // return zero value if Schedule type or UpdatedAt field is nil + if s == nil || s.UpdatedAt == nil { + return 0 + } + + return *s.UpdatedAt +} + +// GetUpdatedBy returns the UpdatedBy field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetUpdatedBy() string { + // return zero value if Schedule type or UpdatedBy field is nil + if s == nil || s.UpdatedBy == nil { + return "" + } + + return *s.UpdatedBy +} + +// GetScheduledAt returns the ScheduledAt field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetScheduledAt() int64 { + // return zero value if Schedule type or ScheduledAt field is nil + if s == nil || s.ScheduledAt == nil { + return 0 + } + + return *s.ScheduledAt +} + +// GetBranch returns the Branch field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetBranch() string { + // return zero value if Schedule type or ScheduledAt field is nil + if s == nil || s.Branch == nil { + return "" + } + + return *s.Branch +} + +// GetError returns the Error field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetError() string { + // return zero value if Schedule type or Error field is nil + if s == nil || s.Error == nil { + return "" + } + + return *s.Error +} + +// GetNextRun returns the NextRun field. +// +// When the provided Schedule type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (s *Schedule) GetNextRun() int64 { + // return zero value if Schedule type or NextRun field is nil + if s == nil || s.NextRun == nil { + return 0 + } + + return *s.NextRun +} + +// SetID sets the ID field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetID(id int64) { + // return if Schedule type is nil + if s == nil { + return + } + + s.ID = &id +} + +// SetRepo sets the Repo field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetRepo(v *Repo) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Repo = v +} + +// SetActive sets the Active field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetActive(active bool) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Active = &active +} + +// SetName sets the Name field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetName(name string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Name = &name +} + +// SetEntry sets the Entry field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetEntry(entry string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Entry = &entry +} + +// SetCreatedAt sets the CreatedAt field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetCreatedAt(createdAt int64) { + // return if Schedule type is nil + if s == nil { + return + } + + s.CreatedAt = &createdAt +} + +// SetCreatedBy sets the CreatedBy field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetCreatedBy(createdBy string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.CreatedBy = &createdBy +} + +// SetUpdatedAt sets the UpdatedAt field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetUpdatedAt(updatedAt int64) { + // return if Schedule type is nil + if s == nil { + return + } + + s.UpdatedAt = &updatedAt +} + +// SetUpdatedBy sets the UpdatedBy field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetUpdatedBy(updatedBy string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.UpdatedBy = &updatedBy +} + +// SetScheduledAt sets the ScheduledAt field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetScheduledAt(scheduledAt int64) { + // return if Schedule type is nil + if s == nil { + return + } + + s.ScheduledAt = &scheduledAt +} + +// SetBranch sets the Branch field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetBranch(branch string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Branch = &branch +} + +// SetError sets the Error field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetError(err string) { + // return if Schedule type is nil + if s == nil { + return + } + + s.Error = &err +} + +// SetNextRun sets the NextRun field. +// +// When the provided Schedule type is nil, it +// will set nothing and immediately return. +func (s *Schedule) SetNextRun(nextRun int64) { + // return if Schedule type is nil + if s == nil { + return + } + + s.NextRun = &nextRun +} + +// String implements the Stringer interface for the Schedule type. +func (s *Schedule) String() string { + return fmt.Sprintf(`{ + Active: %t, + CreatedAt: %d, + CreatedBy: %s, + Entry: %s, + ID: %d, + Name: %s, + Repo: %v, + ScheduledAt: %d, + UpdatedAt: %d, + UpdatedBy: %s, + Branch: %s, + Error: %s, + NextRun: %d, +}`, + s.GetActive(), + s.GetCreatedAt(), + s.GetCreatedBy(), + s.GetEntry(), + s.GetID(), + s.GetName(), + s.GetRepo(), + s.GetScheduledAt(), + s.GetUpdatedAt(), + s.GetUpdatedBy(), + s.GetBranch(), + s.GetError(), + s.GetNextRun(), + ) +} diff --git a/api/types/schedule_test.go b/api/types/schedule_test.go new file mode 100644 index 000000000..4d6becdbf --- /dev/null +++ b/api/types/schedule_test.go @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "strings" + "testing" + "time" +) + +func TestTypes_Schedule_Getters(t *testing.T) { + tests := []struct { + name string + schedule *Schedule + want *Schedule + }{ + { + name: "schedule with fields", + schedule: testSchedule(), + want: testSchedule(), + }, + { + name: "schedule with empty fields", + schedule: new(Schedule), + want: new(Schedule), + }, + { + name: "empty schedule", + schedule: nil, + want: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.schedule.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.schedule.GetID(), test.want.GetID()) + } + + if test.schedule.GetRepo().GetID() != test.want.GetRepo().GetID() { + t.Errorf("GetRepoID is %v, want %v", test.schedule.GetRepo().GetID(), test.want.GetRepo().GetID()) + } + + if test.schedule.GetActive() != test.want.GetActive() { + t.Errorf("GetActive is %v, want %v", test.schedule.GetActive(), test.want.GetActive()) + } + + if test.schedule.GetName() != test.want.GetName() { + t.Errorf("GetName is %v, want %v", test.schedule.GetName(), test.want.GetName()) + } + + if test.schedule.GetEntry() != test.want.GetEntry() { + t.Errorf("GetEntry is %v, want %v", test.schedule.GetEntry(), test.want.GetEntry()) + } + + if test.schedule.GetCreatedAt() != test.want.GetCreatedAt() { + t.Errorf("GetCreatedAt is %v, want %v", test.schedule.GetCreatedAt(), test.want.GetCreatedAt()) + } + + if test.schedule.GetCreatedBy() != test.want.GetCreatedBy() { + t.Errorf("GetCreatedBy is %v, want %v", test.schedule.GetCreatedBy(), test.want.GetCreatedBy()) + } + + if test.schedule.GetUpdatedAt() != test.want.GetUpdatedAt() { + t.Errorf("GetUpdatedAt is %v, want %v", test.schedule.GetUpdatedAt(), test.want.GetUpdatedAt()) + } + + if test.schedule.GetUpdatedBy() != test.want.GetUpdatedBy() { + t.Errorf("GetUpdatedBy is %v, want %v", test.schedule.GetUpdatedBy(), test.want.GetUpdatedBy()) + } + + if test.schedule.GetScheduledAt() != test.want.GetScheduledAt() { + t.Errorf("GetScheduledAt is %v, want %v", test.schedule.GetScheduledAt(), test.want.GetScheduledAt()) + } + + if test.schedule.GetBranch() != test.want.GetBranch() { + t.Errorf("GetBranch is %v, want %v", test.schedule.GetBranch(), test.want.GetBranch()) + } + + if test.schedule.GetError() != test.want.GetError() { + t.Errorf("GetError is %v, want %v", test.schedule.GetError(), test.want.GetError()) + } + + if test.schedule.GetNextRun() != test.want.GetNextRun() { + t.Errorf("GetNextRun is %v, want %v", test.schedule.GetNextRun(), test.want.GetNextRun()) + } + }) + } +} + +func TestTypes_Schedule_Setters(t *testing.T) { + tests := []struct { + name string + schedule *Schedule + want *Schedule + }{ + { + name: "schedule with fields", + schedule: testSchedule(), + want: testSchedule(), + }, + { + name: "schedule with empty fields", + schedule: new(Schedule), + want: new(Schedule), + }, + { + name: "empty schedule", + schedule: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.schedule.SetID(test.want.GetID()) + test.schedule.SetRepo(test.want.GetRepo()) + test.schedule.SetActive(test.want.GetActive()) + test.schedule.SetName(test.want.GetName()) + test.schedule.SetEntry(test.want.GetEntry()) + test.schedule.SetCreatedAt(test.want.GetCreatedAt()) + test.schedule.SetCreatedBy(test.want.GetCreatedBy()) + test.schedule.SetUpdatedAt(test.want.GetUpdatedAt()) + test.schedule.SetUpdatedBy(test.want.GetUpdatedBy()) + test.schedule.SetScheduledAt(test.want.GetScheduledAt()) + test.schedule.SetBranch(test.want.GetBranch()) + test.schedule.SetError(test.want.GetError()) + test.schedule.SetNextRun(test.want.GetNextRun()) + + if test.schedule.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.schedule.GetID(), test.want.GetID()) + } + + if test.schedule.GetRepo().GetID() != test.want.GetRepo().GetID() { + t.Errorf("SetRepoID is %v, want %v", test.schedule.GetRepo().GetID(), test.want.GetRepo().GetID()) + } + + if test.schedule.GetActive() != test.want.GetActive() { + t.Errorf("SetActive is %v, want %v", test.schedule.GetActive(), test.want.GetActive()) + } + + if test.schedule.GetName() != test.want.GetName() { + t.Errorf("SetName is %v, want %v", test.schedule.GetName(), test.want.GetName()) + } + + if test.schedule.GetEntry() != test.want.GetEntry() { + t.Errorf("SetEntry is %v, want %v", test.schedule.GetEntry(), test.want.GetEntry()) + } + + if test.schedule.GetCreatedAt() != test.want.GetCreatedAt() { + t.Errorf("SetCreatedAt is %v, want %v", test.schedule.GetCreatedAt(), test.want.GetCreatedAt()) + } + + if test.schedule.GetCreatedBy() != test.want.GetCreatedBy() { + t.Errorf("SetCreatedBy is %v, want %v", test.schedule.GetCreatedBy(), test.want.GetCreatedBy()) + } + + if test.schedule.GetUpdatedAt() != test.want.GetUpdatedAt() { + t.Errorf("SetUpdatedAt is %v, want %v", test.schedule.GetUpdatedAt(), test.want.GetUpdatedAt()) + } + + if test.schedule.GetUpdatedBy() != test.want.GetUpdatedBy() { + t.Errorf("SetUpdatedBy is %v, want %v", test.schedule.GetUpdatedBy(), test.want.GetUpdatedBy()) + } + + if test.schedule.GetScheduledAt() != test.want.GetScheduledAt() { + t.Errorf("SetScheduledAt is %v, want %v", test.schedule.GetScheduledAt(), test.want.GetScheduledAt()) + } + + if test.schedule.GetBranch() != test.want.GetBranch() { + t.Errorf("SetBranch is %v, want %v", test.schedule.GetBranch(), test.want.GetBranch()) + } + + if test.schedule.GetError() != test.want.GetError() { + t.Errorf("SetError is %v, want %v", test.schedule.GetError(), test.want.GetError()) + } + + if test.schedule.GetNextRun() != test.want.GetNextRun() { + t.Errorf("SetNextRun is %v, want %v", test.schedule.GetNextRun(), test.want.GetNextRun()) + } + }) + } +} + +func TestTypes_Schedule_String(t *testing.T) { + s := testSchedule() + + want := fmt.Sprintf(`{ + Active: %t, + CreatedAt: %d, + CreatedBy: %s, + Entry: %s, + ID: %d, + Name: %s, + Repo: %v, + ScheduledAt: %d, + UpdatedAt: %d, + UpdatedBy: %s, + Branch: %s, + Error: %s, + NextRun: %d, +}`, + s.GetActive(), + s.GetCreatedAt(), + s.GetCreatedBy(), + s.GetEntry(), + s.GetID(), + s.GetName(), + s.GetRepo(), + s.GetScheduledAt(), + s.GetUpdatedAt(), + s.GetUpdatedBy(), + s.GetBranch(), + s.GetError(), + s.GetNextRun(), + ) + + got := s.String() + if !strings.EqualFold(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testSchedule is a test helper function to create a Schedule type with all fields set to a fake value. +func testSchedule() *Schedule { + s := new(Schedule) + s.SetID(1) + s.SetRepo(testRepo()) + s.SetActive(true) + s.SetName("nightly") + s.SetEntry("0 0 * * *") + s.SetCreatedAt(time.Now().UTC().Unix()) + s.SetCreatedBy("user1") + s.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix()) + s.SetUpdatedBy("user2") + s.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix()) + s.SetBranch("main") + s.SetError("unable to trigger build for schedule nightly: unknown character") + s.SetNextRun(time.Now().Add((time.Hour * 2) + 24).UTC().Unix()) + + return s +} diff --git a/api/types/settings/compiler.go b/api/types/settings/compiler.go new file mode 100644 index 000000000..147cade94 --- /dev/null +++ b/api/types/settings/compiler.go @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import "fmt" + +type Compiler struct { + CloneImage *string `json:"clone_image,omitempty" yaml:"clone_image,omitempty"` + TemplateDepth *int `json:"template_depth,omitempty" yaml:"template_depth,omitempty"` + StarlarkExecLimit *uint64 `json:"starlark_exec_limit,omitempty" yaml:"starlark_exec_limit,omitempty"` +} + +// GetCloneImage returns the CloneImage field. +// +// When the provided Compiler type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (cs *Compiler) GetCloneImage() string { + // return zero value if Settings type or CloneImage field is nil + if cs == nil || cs.CloneImage == nil { + return "" + } + + return *cs.CloneImage +} + +// GetTemplateDepth returns the TemplateDepth field. +// +// When the provided Compiler type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (cs *Compiler) GetTemplateDepth() int { + // return zero value if Settings type or TemplateDepth field is nil + if cs == nil || cs.TemplateDepth == nil { + return 0 + } + + return *cs.TemplateDepth +} + +// GetStarlarkExecLimit returns the StarlarkExecLimit field. +// +// When the provided Compiler type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (cs *Compiler) GetStarlarkExecLimit() uint64 { + // return zero value if Compiler type or StarlarkExecLimit field is nil + if cs == nil || cs.StarlarkExecLimit == nil { + return 0 + } + + return *cs.StarlarkExecLimit +} + +// SetCloneImage sets the CloneImage field. +// +// When the provided Compiler type is nil, it +// will set nothing and immediately return. +func (cs *Compiler) SetCloneImage(v string) { + // return if Compiler type is nil + if cs == nil { + return + } + + cs.CloneImage = &v +} + +// SetTemplateDepth sets the TemplateDepth field. +// +// When the provided Compiler type is nil, it +// will set nothing and immediately return. +func (cs *Compiler) SetTemplateDepth(v int) { + // return if Compiler type is nil + if cs == nil { + return + } + + cs.TemplateDepth = &v +} + +// SetStarlarkExecLimit sets the StarlarkExecLimit field. +// +// When the provided Compiler type is nil, it +// will set nothing and immediately return. +func (cs *Compiler) SetStarlarkExecLimit(v uint64) { + // return if Compiler type is nil + if cs == nil { + return + } + + cs.StarlarkExecLimit = &v +} + +// String implements the Stringer interface for the Compiler type. +func (cs *Compiler) String() string { + return fmt.Sprintf(`{ + CloneImage: %s, + TemplateDepth: %d, + StarlarkExecLimit: %d, +}`, + cs.GetCloneImage(), + cs.GetTemplateDepth(), + cs.GetStarlarkExecLimit(), + ) +} + +// CompilerMockEmpty returns an empty Compiler type. +func CompilerMockEmpty() Compiler { + cs := Compiler{} + cs.SetCloneImage("") + cs.SetTemplateDepth(0) + cs.SetStarlarkExecLimit(0) + + return cs +} diff --git a/api/types/settings/compiler_test.go b/api/types/settings/compiler_test.go new file mode 100644 index 000000000..a224a9cb7 --- /dev/null +++ b/api/types/settings/compiler_test.go @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_Compiler_Getters(t *testing.T) { + // setup tests + tests := []struct { + compiler *Compiler + want *Compiler + }{ + { + compiler: testCompilerSettings(), + want: testCompilerSettings(), + }, + { + compiler: new(Compiler), + want: new(Compiler), + }, + } + + // run tests + for _, test := range tests { + if !reflect.DeepEqual(test.compiler.GetCloneImage(), test.want.GetCloneImage()) { + t.Errorf("GetCloneImage is %v, want %v", test.compiler.GetCloneImage(), test.want.GetCloneImage()) + } + + if !reflect.DeepEqual(test.compiler.GetTemplateDepth(), test.want.GetTemplateDepth()) { + t.Errorf("GetTemplateDepth is %v, want %v", test.compiler.GetTemplateDepth(), test.want.GetTemplateDepth()) + } + + if !reflect.DeepEqual(test.compiler.GetStarlarkExecLimit(), test.want.GetStarlarkExecLimit()) { + t.Errorf("GetStarlarkExecLimit is %v, want %v", test.compiler.GetStarlarkExecLimit(), test.want.GetStarlarkExecLimit()) + } + } +} + +func TestTypes_Compiler_Setters(t *testing.T) { + // setup types + var cs *Compiler + + // setup tests + tests := []struct { + compiler *Compiler + want *Compiler + }{ + { + compiler: testCompilerSettings(), + want: testCompilerSettings(), + }, + { + compiler: cs, + want: new(Compiler), + }, + } + + // run tests + for _, test := range tests { + test.compiler.SetCloneImage(test.want.GetCloneImage()) + + if !reflect.DeepEqual(test.compiler.GetCloneImage(), test.want.GetCloneImage()) { + t.Errorf("SetCloneImage is %v, want %v", test.compiler.GetCloneImage(), test.want.GetCloneImage()) + } + + test.compiler.SetTemplateDepth(test.want.GetTemplateDepth()) + + if !reflect.DeepEqual(test.compiler.GetTemplateDepth(), test.want.GetTemplateDepth()) { + t.Errorf("SetTemplateDepth is %v, want %v", test.compiler.GetTemplateDepth(), test.want.GetTemplateDepth()) + } + + test.compiler.SetStarlarkExecLimit(test.want.GetStarlarkExecLimit()) + + if !reflect.DeepEqual(test.compiler.GetStarlarkExecLimit(), test.want.GetStarlarkExecLimit()) { + t.Errorf("SetStarlarkExecLimit is %v, want %v", test.compiler.GetStarlarkExecLimit(), test.want.GetStarlarkExecLimit()) + } + } +} + +func TestTypes_Compiler_String(t *testing.T) { + // setup types + cs := testCompilerSettings() + + want := fmt.Sprintf(`{ + CloneImage: %s, + TemplateDepth: %d, + StarlarkExecLimit: %d, +}`, + cs.GetCloneImage(), + cs.GetTemplateDepth(), + cs.GetStarlarkExecLimit(), + ) + + // run test + got := cs.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testCompilerSettings is a test helper function to create a Compiler +// type with all fields set to a fake value. +func testCompilerSettings() *Compiler { + cs := new(Compiler) + + cs.SetCloneImage("target/vela-git:latest") + cs.SetTemplateDepth(1) + cs.SetStarlarkExecLimit(100) + + return cs +} diff --git a/api/types/settings/platform.go b/api/types/settings/platform.go new file mode 100644 index 000000000..ff7c58ef3 --- /dev/null +++ b/api/types/settings/platform.go @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +// Platform is the API representation of platform settingps. +// +// swagger:model Platform +type Platform struct { + ID *int64 `json:"id"` + *Compiler `json:"compiler,omitempty" yaml:"compiler,omitempty"` + *Queue `json:"queue,omitempty" yaml:"queue,omitempty"` + RepoAllowlist *[]string `json:"repo_allowlist,omitempty" yaml:"repo_allowlist,omitempty"` + ScheduleAllowlist *[]string `json:"schedule_allowlist,omitempty" yaml:"schedule_allowlist,omitempty"` + CreatedAt *int64 `json:"created_at,omitempty" yaml:"created_at,omitempty"` + UpdatedAt *int64 `json:"updated_at,omitempty" yaml:"updated_at,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty" yaml:"updated_by,omitempty"` +} + +// FromCLIContext returns a new Platform record from a cli context. +func FromCLIContext(c *cli.Context) *Platform { + ps := new(Platform) + + // set repos permitted to be added + ps.SetRepoAllowlist(c.StringSlice("vela-repo-allowlist")) + + // set repos permitted to use schedules + ps.SetScheduleAllowlist(c.StringSlice("vela-schedule-allowlist")) + + return ps +} + +// GetID returns the ID field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetID() int64 { + // return zero value if Platform type or ID field is nil + if ps == nil || ps.ID == nil { + return 0 + } + + return *ps.ID +} + +// GetCompiler returns the Compiler field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetCompiler() Compiler { + // return zero value if Platform type or Compiler field is nil + if ps == nil || ps.Compiler == nil { + return Compiler{} + } + + return *ps.Compiler +} + +// GetQueue returns the Queue field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetQueue() Queue { + // return zero value if Platform type or Queue field is nil + if ps == nil || ps.Queue == nil { + return Queue{} + } + + return *ps.Queue +} + +// GetRepoAllowlist returns the RepoAllowlist field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetRepoAllowlist() []string { + // return zero value if Platform type or RepoAllowlist field is nil + if ps == nil || ps.RepoAllowlist == nil { + return []string{} + } + + return *ps.RepoAllowlist +} + +// GetScheduleAllowlist returns the ScheduleAllowlist field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetScheduleAllowlist() []string { + // return zero value if Platform type or ScheduleAllowlist field is nil + if ps == nil || ps.ScheduleAllowlist == nil { + return []string{} + } + + return *ps.ScheduleAllowlist +} + +// GetCreatedAt returns the CreatedAt field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetCreatedAt() int64 { + // return zero value if Platform type or CreatedAt field is nil + if ps == nil || ps.CreatedAt == nil { + return 0 + } + + return *ps.CreatedAt +} + +// GetUpdatedAt returns the UpdatedAt field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetUpdatedAt() int64 { + // return zero value if Platform type or UpdatedAt field is nil + if ps == nil || ps.UpdatedAt == nil { + return 0 + } + + return *ps.UpdatedAt +} + +// GetUpdatedBy returns the UpdatedBy field. +// +// When the provided Platform type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (ps *Platform) GetUpdatedBy() string { + // return zero value if Platform type or UpdatedBy field is nil + if ps == nil || ps.UpdatedBy == nil { + return "" + } + + return *ps.UpdatedBy +} + +// SetID sets the ID field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetID(v int64) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.ID = &v +} + +// SetCompiler sets the Compiler field. +// +// When the provided Compiler type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetCompiler(cs Compiler) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.Compiler = &cs +} + +// SetQueue sets the Queue field. +// +// When the provided Queue type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetQueue(qs Queue) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.Queue = &qs +} + +// SetRepoAllowlist sets the RepoAllowlist field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetRepoAllowlist(v []string) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.RepoAllowlist = &v +} + +// SetScheduleAllowlist sets the RepoAllowlist field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetScheduleAllowlist(v []string) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.ScheduleAllowlist = &v +} + +// SetCreatedAt sets the CreatedAt field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetCreatedAt(v int64) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.CreatedAt = &v +} + +// SetUpdatedAt sets the UpdatedAt field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetUpdatedAt(v int64) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.UpdatedAt = &v +} + +// SetUpdatedBy sets the UpdatedBy field. +// +// When the provided Platform type is nil, it +// will set nothing and immediately return. +func (ps *Platform) SetUpdatedBy(v string) { + // return if Platform type is nil + if ps == nil { + return + } + + ps.UpdatedBy = &v +} + +// FromSettings takes another settings record and updates the internal fields, +// used when the updating settings and refreshing the record shared across the server. +func (ps *Platform) FromSettings(_ps *Platform) { + if ps == nil { + return + } + + if _ps == nil { + return + } + + ps.SetCompiler(_ps.GetCompiler()) + ps.SetQueue(_ps.GetQueue()) + ps.SetRepoAllowlist(_ps.GetRepoAllowlist()) + ps.SetScheduleAllowlist(_ps.GetScheduleAllowlist()) + + ps.SetCreatedAt(_ps.GetCreatedAt()) + ps.SetUpdatedAt(_ps.GetUpdatedAt()) + ps.SetUpdatedBy(_ps.GetUpdatedBy()) +} + +// String implements the Stringer interface for the Platform type. +func (ps *Platform) String() string { + cs := ps.GetCompiler() + qs := ps.GetQueue() + + return fmt.Sprintf(`{ + ID: %d, + Compiler: %v, + Queue: %v, + RepoAllowlist: %v, + ScheduleAllowlist: %v, + CreatedAt: %d, + UpdatedAt: %d, + UpdatedBy: %s, +}`, + ps.GetID(), + cs.String(), + qs.String(), + ps.GetRepoAllowlist(), + ps.GetScheduleAllowlist(), + ps.GetCreatedAt(), + ps.GetUpdatedAt(), + ps.GetUpdatedBy(), + ) +} + +// PlatformMockEmpty returns an empty Platform type. +func PlatformMockEmpty() Platform { + ps := Platform{} + + ps.SetCompiler(CompilerMockEmpty()) + ps.SetQueue(QueueMockEmpty()) + + ps.SetRepoAllowlist([]string{}) + ps.SetScheduleAllowlist([]string{}) + + return ps +} diff --git a/api/types/settings/platform_test.go b/api/types/settings/platform_test.go new file mode 100644 index 000000000..059c7b574 --- /dev/null +++ b/api/types/settings/platform_test.go @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "fmt" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestTypes_Platform_Getters(t *testing.T) { + // setup tests + tests := []struct { + platform *Platform + want *Platform + }{ + { + platform: testPlatformSettings(), + want: testPlatformSettings(), + }, + { + platform: new(Platform), + want: new(Platform), + }, + } + + // run tests + for _, test := range tests { + if !reflect.DeepEqual(test.platform.GetCompiler(), test.want.GetCompiler()) { + t.Errorf("GetCompiler is %v, want %v", test.platform.GetCompiler(), test.want.GetCompiler()) + } + + if !reflect.DeepEqual(test.platform.GetQueue(), test.want.GetQueue()) { + t.Errorf("GetQueue is %v, want %v", test.platform.GetQueue(), test.want.GetQueue()) + } + + if !reflect.DeepEqual(test.platform.GetRepoAllowlist(), test.want.GetRepoAllowlist()) { + t.Errorf("GetRepoAllowlist is %v, want %v", test.platform.GetRepoAllowlist(), test.want.GetRepoAllowlist()) + } + + if !reflect.DeepEqual(test.platform.GetScheduleAllowlist(), test.want.GetScheduleAllowlist()) { + t.Errorf("GetScheduleAllowlist is %v, want %v", test.platform.GetScheduleAllowlist(), test.want.GetScheduleAllowlist()) + } + } +} + +func TestTypes_Platform_Setters(t *testing.T) { + // setup types + var ps *Platform + + // setup tests + tests := []struct { + platform *Platform + want *Platform + }{ + { + platform: testPlatformSettings(), + want: testPlatformSettings(), + }, + { + platform: ps, + want: new(Platform), + }, + } + + // run tests + for _, test := range tests { + test.platform.SetCompiler(test.want.GetCompiler()) + + if !reflect.DeepEqual(test.platform.GetCompiler(), test.want.GetCompiler()) { + t.Errorf("SetCompiler is %v, want %v", test.platform.GetCompiler(), test.want.GetCompiler()) + } + + test.platform.SetQueue(test.want.GetQueue()) + + if !reflect.DeepEqual(test.platform.GetQueue(), test.want.GetQueue()) { + t.Errorf("SetQueue is %v, want %v", test.platform.GetQueue(), test.want.GetQueue()) + } + + test.platform.SetRepoAllowlist(test.want.GetRepoAllowlist()) + + if !reflect.DeepEqual(test.platform.GetRepoAllowlist(), test.want.GetRepoAllowlist()) { + t.Errorf("SetRepoAllowlist is %v, want %v", test.platform.GetRepoAllowlist(), test.want.GetRepoAllowlist()) + } + + test.platform.SetScheduleAllowlist(test.want.GetScheduleAllowlist()) + + if !reflect.DeepEqual(test.platform.GetScheduleAllowlist(), test.want.GetScheduleAllowlist()) { + t.Errorf("SetScheduleAllowlist is %v, want %v", test.platform.GetScheduleAllowlist(), test.want.GetScheduleAllowlist()) + } + } +} + +func TestTypes_Platform_Update(t *testing.T) { + // setup types + s := testPlatformSettings() + + // update fields + sUpdate := testPlatformSettings() + sUpdate.SetCompiler(Compiler{}) + sUpdate.SetQueue(Queue{}) + sUpdate.SetRepoAllowlist([]string{"foo"}) + sUpdate.SetScheduleAllowlist([]string{"bar"}) + + // setup tests + tests := []struct { + platform *Platform + want *Platform + }{ + { + platform: s, + want: testPlatformSettings(), + }, + { + platform: s, + want: sUpdate, + }, + } + + // run tests + for _, test := range tests { + test.platform.FromSettings(test.want) + + if diff := cmp.Diff(test.want, test.platform); diff != "" { + t.Errorf("(Update: -want +got):\n%s", diff) + } + } +} + +func TestTypes_Platform_String(t *testing.T) { + // setup types + s := testPlatformSettings() + cs := s.GetCompiler() + qs := s.GetQueue() + + want := fmt.Sprintf(`{ + ID: %d, + Compiler: %v, + Queue: %v, + RepoAllowlist: %v, + ScheduleAllowlist: %v, + CreatedAt: %d, + UpdatedAt: %d, + UpdatedBy: %s, +}`, + s.GetID(), + cs.String(), + qs.String(), + s.GetRepoAllowlist(), + s.GetScheduleAllowlist(), + s.GetCreatedAt(), + s.GetUpdatedAt(), + s.GetUpdatedBy(), + ) + + // run test + got := s.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testPlatformSettings is a test helper function to create a Platform +// type with all fields set to a fake value. +func testPlatformSettings() *Platform { + // setup platform + s := new(Platform) + s.SetID(1) + s.SetCreatedAt(1) + s.SetUpdatedAt(1) + s.SetUpdatedBy("vela-server") + s.SetRepoAllowlist([]string{"foo", "bar"}) + s.SetScheduleAllowlist([]string{"*"}) + + // setup types + // setup compiler + cs := new(Compiler) + + cs.SetCloneImage("target/vela-git:latest") + cs.SetTemplateDepth(1) + cs.SetStarlarkExecLimit(100) + + // setup queue + qs := new(Queue) + + qs.SetRoutes([]string{"vela"}) + + s.SetCompiler(*cs) + s.SetQueue(*qs) + + return s +} diff --git a/api/types/settings/queue.go b/api/types/settings/queue.go new file mode 100644 index 000000000..e813b18ae --- /dev/null +++ b/api/types/settings/queue.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import "fmt" + +type Queue struct { + Routes *[]string `json:"routes,omitempty" yaml:"routes,omitempty"` +} + +// GetRoutes returns the Routes field. +// +// When the provided Queue type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (qs *Queue) GetRoutes() []string { + // return zero value if Queue type or Routes field is nil + if qs == nil || qs.Routes == nil { + return []string{} + } + + return *qs.Routes +} + +// SetRoutes sets the Routes field. +// +// When the provided Queue type is nil, it +// will set nothing and immediately return. +func (qs *Queue) SetRoutes(v []string) { + // return if Queue type is nil + if qs == nil { + return + } + + qs.Routes = &v +} + +// String implements the Stringer interface for the Queue type. +func (qs *Queue) String() string { + return fmt.Sprintf(`{ + Routes: %v, +}`, + qs.GetRoutes(), + ) +} + +// QueueMockEmpty returns an empty Queue type. +func QueueMockEmpty() Queue { + qs := Queue{} + qs.SetRoutes([]string{}) + + return qs +} diff --git a/api/types/settings/queue_test.go b/api/types/settings/queue_test.go new file mode 100644 index 000000000..30389b881 --- /dev/null +++ b/api/types/settings/queue_test.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_Queue_Getters(t *testing.T) { + // setup tests + tests := []struct { + queue *Queue + want *Queue + }{ + { + queue: testQueueSettings(), + want: testQueueSettings(), + }, + { + queue: new(Queue), + want: new(Queue), + }, + } + + // run tests + for _, test := range tests { + if !reflect.DeepEqual(test.queue.GetRoutes(), test.want.GetRoutes()) { + t.Errorf("GetRoutes is %v, want %v", test.queue.GetRoutes(), test.want.GetRoutes()) + } + } +} + +func TestTypes_Queue_Setters(t *testing.T) { + // setup types + var qs *Queue + + // setup tests + tests := []struct { + queue *Queue + want *Queue + }{ + { + queue: testQueueSettings(), + want: testQueueSettings(), + }, + { + queue: qs, + want: new(Queue), + }, + } + + // run tests + for _, test := range tests { + test.queue.SetRoutes(test.want.GetRoutes()) + + if !reflect.DeepEqual(test.queue.GetRoutes(), test.want.GetRoutes()) { + t.Errorf("SetRoutes is %v, want %v", test.queue.GetRoutes(), test.want.GetRoutes()) + } + } +} + +func TestTypes_Queue_String(t *testing.T) { + // setup types + qs := testQueueSettings() + + want := fmt.Sprintf(`{ + Routes: %s, +}`, + qs.GetRoutes(), + ) + + // run test + got := qs.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testQueueSettings is a test helper function to create a Queue +// type with all fields set to a fake value. +func testQueueSettings() *Queue { + qs := new(Queue) + + qs.SetRoutes([]string{"vela"}) + + return qs +} diff --git a/api/types/string.go b/api/types/string.go new file mode 100644 index 000000000..33ddacb59 --- /dev/null +++ b/api/types/string.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "encoding/base64" + "strconv" + "strings" + + "github.com/buildkite/yaml" + json "github.com/ghodss/yaml" +) + +// ToString is a helper function to convert +// the provided interface value to a string. +func ToString(v interface{}) string { + switch v := v.(type) { + case string: + return v + case bool: + return strconv.FormatBool(v) + case []byte: + return base64.StdEncoding.EncodeToString(v) + case float32: + return strconv.FormatFloat(float64(v), 'g', -1, 32) + case float64: + return strconv.FormatFloat(v, 'g', -1, 64) + case int: + return strconv.Itoa(v) + case int8: + return strconv.FormatInt(int64(v), 10) + case int16: + return strconv.FormatInt(int64(v), 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case int64: + return strconv.FormatInt(v, 10) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint8: + return strconv.FormatUint(uint64(v), 10) + case uint16: + return strconv.FormatUint(uint64(v), 10) + case uint32: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case []interface{}: + return unmarshalSlice(v) + default: + return unmarshalMap(v) + } +} + +// helper function to unmarshal a parameter in map format. +func unmarshalMap(v interface{}) string { + yml, err := yaml.Marshal(v) + if err != nil { + return err.Error() + } + + out, err := json.YAMLToJSON(yml) + if err != nil { + return err.Error() + } + + return string(out) +} + +// helper function to unmarshal a parameter in slice format. +func unmarshalSlice(v interface{}) string { + out, err := yaml.Marshal(v) + if err != nil { + return err.Error() + } + + in := []string{} + + err = yaml.Unmarshal(out, &in) + if err == nil { + return strings.Join(in, ",") + } + + out, err = json.YAMLToJSON(out) + if err != nil { + return err.Error() + } + + return string(out) +} diff --git a/api/types/string_test.go b/api/types/string_test.go new file mode 100644 index 000000000..e362f0766 --- /dev/null +++ b/api/types/string_test.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "reflect" + "testing" +) + +func TestTypes_ToString(t *testing.T) { + // setup tests + tests := []struct { + parameter interface{} + want interface{} + }{ + {parameter: "string", want: "string"}, // string + {parameter: true, want: "true"}, // bool + {parameter: []byte{1}, want: "AQ=="}, // []byte + {parameter: float32(1.1), want: "1.1"}, // float32 + {parameter: float64(1.1), want: "1.1"}, // float64 + {parameter: 1, want: "1"}, // int + {parameter: int8(1), want: "1"}, // int8 + {parameter: int16(1), want: "1"}, // int16 + {parameter: int32(1), want: "1"}, // int32 + {parameter: int64(1), want: "1"}, // int64 + {parameter: uint(1), want: "1"}, // uint + {parameter: uint8(1), want: "1"}, // uint8 + {parameter: uint16(1), want: "1"}, // uint16 + {parameter: uint32(1), want: "1"}, // uint32 + {parameter: uint64(1), want: "1"}, // uint64 + { // map + parameter: map[string]string{"hello": "world"}, + want: "{\"hello\":\"world\"}", + }, + { // slice + parameter: []interface{}{1, 2, 3}, + want: "1,2,3", + }, + { // slice complex + parameter: []interface{}{struct{ Foo string }{Foo: "bar"}}, + want: "[{\"foo\":\"bar\"}]", + }, + { // complex + parameter: []struct{ Foo string }{{"bar"}, {"baz"}}, + want: "[{\"foo\":\"bar\"},{\"foo\":\"baz\"}]", + }, + } + + // run tests + for _, test := range tests { + got := ToString(test.parameter) + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ToString is %v, want %v", got, test.want) + } + } +} diff --git a/api/types/user.go b/api/types/user.go new file mode 100644 index 000000000..a7d7691c0 --- /dev/null +++ b/api/types/user.go @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" +) + +// User is the API representation of a user. +// +// swagger:model User +type User struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + RefreshToken *string `json:"-"` + Token *string `json:"-"` + Favorites *[]string `json:"favorites,omitempty"` + Active *bool `json:"active,omitempty"` + Admin *bool `json:"admin,omitempty"` + Dashboards *[]string `json:"dashboards,omitempty"` +} + +// Crop creates a duplicate of the User with certain fields cropped. +// +// Generally used for cropping large fields that aren't useful for all API calls like favorites and dashboards. +func (u *User) Crop() *User { + return &User{ + ID: u.ID, + Name: u.Name, + RefreshToken: u.RefreshToken, + Token: u.Token, + Active: u.Active, + } +} + +// Environment returns a list of environment variables +// provided from the fields of the User type. +func (u *User) Environment() map[string]string { + return map[string]string{ + "VELA_USER_ACTIVE": ToString(u.GetActive()), + "VELA_USER_ADMIN": ToString(u.GetAdmin()), + "VELA_USER_FAVORITES": ToString(u.GetFavorites()), + "VELA_USER_NAME": ToString(u.GetName()), + } +} + +// GetID returns the ID field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetID() int64 { + // return zero value if User type or ID field is nil + if u == nil || u.ID == nil { + return 0 + } + + return *u.ID +} + +// GetName returns the Name field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetName() string { + // return zero value if User type or Name field is nil + if u == nil || u.Name == nil { + return "" + } + + return *u.Name +} + +// GetRefreshToken returns the RefreshToken field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetRefreshToken() string { + // return zero value if User type or RefreshToken field is nil + if u == nil || u.RefreshToken == nil { + return "" + } + + return *u.RefreshToken +} + +// GetToken returns the Token field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetToken() string { + // return zero value if User type or Token field is nil + if u == nil || u.Token == nil { + return "" + } + + return *u.Token +} + +// GetActive returns the Active field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetActive() bool { + // return zero value if User type or Active field is nil + if u == nil || u.Active == nil { + return false + } + + return *u.Active +} + +// GetAdmin returns the Admin field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetAdmin() bool { + // return zero value if User type or Admin field is nil + if u == nil || u.Admin == nil { + return false + } + + return *u.Admin +} + +// GetFavorites returns the Favorites field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetFavorites() []string { + // return zero value if User type or Favorites field is nil + if u == nil || u.Favorites == nil { + return []string{} + } + + return *u.Favorites +} + +// GetDashboards returns the Dashboards field. +// +// When the provided User type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (u *User) GetDashboards() []string { + // return zero value if User type or Favorites field is nil + if u == nil || u.Dashboards == nil { + return []string{} + } + + return *u.Dashboards +} + +// SetID sets the ID field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetID(v int64) { + // return if User type is nil + if u == nil { + return + } + + u.ID = &v +} + +// SetName sets the Name field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetName(v string) { + // return if User type is nil + if u == nil { + return + } + + u.Name = &v +} + +// SetRefreshToken sets the RefreshToken field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetRefreshToken(v string) { + // return if User type is nil + if u == nil { + return + } + + u.RefreshToken = &v +} + +// SetToken sets the Token field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetToken(v string) { + // return if User type is nil + if u == nil { + return + } + + u.Token = &v +} + +// SetActive sets the Active field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetActive(v bool) { + // return if User type is nil + if u == nil { + return + } + + u.Active = &v +} + +// SetAdmin sets the Admin field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetAdmin(v bool) { + // return if User type is nil + if u == nil { + return + } + + u.Admin = &v +} + +// SetFavorites sets the Favorites field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetFavorites(v []string) { + // return if User type is nil + if u == nil { + return + } + + u.Favorites = &v +} + +// SetDashboard sets the Dashboard field. +// +// When the provided User type is nil, it +// will set nothing and immediately return. +func (u *User) SetDashboards(v []string) { + // return if User type is nil + if u == nil { + return + } + + u.Dashboards = &v +} + +// String implements the Stringer interface for the User type. +func (u *User) String() string { + return fmt.Sprintf(`{ + Active: %t, + Admin: %t, + Dashboards: %s, + Favorites: %s, + ID: %d, + Name: %s, + Token: %s, +}`, + u.GetActive(), + u.GetAdmin(), + u.GetDashboards(), + u.GetFavorites(), + u.GetID(), + u.GetName(), + u.GetToken(), + ) +} diff --git a/api/types/user_test.go b/api/types/user_test.go new file mode 100644 index 000000000..91134e3e0 --- /dev/null +++ b/api/types/user_test.go @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTypes_User_Crop(t *testing.T) { + // setup types + u := testUser() + + want := testUser() + want.Favorites = nil + want.Dashboards = nil + want.Admin = nil + + // run test + got := u.Crop() + + if !reflect.DeepEqual(got, want) { + t.Errorf("Crop is %v, want %v", got, want) + } +} + +func TestTypes_User_Environment(t *testing.T) { + // setup types + want := map[string]string{ + "VELA_USER_ACTIVE": "true", + "VELA_USER_ADMIN": "false", + "VELA_USER_FAVORITES": "[\"github/octocat\"]", + "VELA_USER_NAME": "octocat", + } + + // run test + got := testUser().Environment() + + if !reflect.DeepEqual(got, want) { + t.Errorf("Environment is %v, want %v", got, want) + } +} + +func TestTypes_User_Getters(t *testing.T) { + // setup tests + tests := []struct { + user *User + want *User + }{ + { + user: testUser(), + want: testUser(), + }, + { + user: new(User), + want: new(User), + }, + } + + // run tests + for _, test := range tests { + if test.user.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.user.GetID(), test.want.GetID()) + } + + if test.user.GetName() != test.want.GetName() { + t.Errorf("GetName is %v, want %v", test.user.GetName(), test.want.GetName()) + } + + if test.user.GetRefreshToken() != test.want.GetRefreshToken() { + t.Errorf("GetRefreshToken is %v, want %v", test.user.GetRefreshToken(), test.want.GetRefreshToken()) + } + + if test.user.GetToken() != test.want.GetToken() { + t.Errorf("GetToken is %v, want %v", test.user.GetToken(), test.want.GetToken()) + } + + if !reflect.DeepEqual(test.user.GetFavorites(), test.want.GetFavorites()) { + t.Errorf("GetFavorites is %v, want %v", test.user.GetFavorites(), test.want.GetFavorites()) + } + + if test.user.GetActive() != test.want.GetActive() { + t.Errorf("GetActive is %v, want %v", test.user.GetActive(), test.want.GetActive()) + } + + if test.user.GetAdmin() != test.want.GetAdmin() { + t.Errorf("GetAdmin is %v, want %v", test.user.GetAdmin(), test.want.GetAdmin()) + } + + if !reflect.DeepEqual(test.user.GetDashboards(), test.want.GetDashboards()) { + t.Errorf("GetDashboards is %v, want %v", test.user.GetDashboards(), test.want.GetDashboards()) + } + } +} + +func TestTypes_User_Setters(t *testing.T) { + // setup types + var u *User + + // setup tests + tests := []struct { + user *User + want *User + }{ + { + user: testUser(), + want: testUser(), + }, + { + user: u, + want: new(User), + }, + } + + // run tests + for _, test := range tests { + test.user.SetID(test.want.GetID()) + test.user.SetName(test.want.GetName()) + test.user.SetRefreshToken(test.want.GetRefreshToken()) + test.user.SetToken(test.want.GetToken()) + test.user.SetFavorites(test.want.GetFavorites()) + test.user.SetActive(test.want.GetActive()) + test.user.SetAdmin(test.want.GetAdmin()) + test.user.SetDashboards(test.want.GetDashboards()) + + if test.user.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.user.GetID(), test.want.GetID()) + } + + if test.user.GetName() != test.want.GetName() { + t.Errorf("SetName is %v, want %v", test.user.GetName(), test.want.GetName()) + } + + if test.user.GetRefreshToken() != test.want.GetRefreshToken() { + t.Errorf("SetRefreshToken is %v, want %v", test.user.GetRefreshToken(), test.want.GetRefreshToken()) + } + + if test.user.GetToken() != test.want.GetToken() { + t.Errorf("SetToken is %v, want %v", test.user.GetToken(), test.want.GetToken()) + } + + if !reflect.DeepEqual(test.user.GetFavorites(), test.want.GetFavorites()) { + t.Errorf("SetFavorites is %v, want %v", test.user.GetFavorites(), test.want.GetFavorites()) + } + + if test.user.GetActive() != test.want.GetActive() { + t.Errorf("SetActive is %v, want %v", test.user.GetActive(), test.want.GetActive()) + } + + if test.user.GetAdmin() != test.want.GetAdmin() { + t.Errorf("SetAdmin is %v, want %v", test.user.GetAdmin(), test.want.GetAdmin()) + } + + if !reflect.DeepEqual(test.user.GetDashboards(), test.want.GetDashboards()) { + t.Errorf("SetDashboards is %v, want %v", test.user.GetDashboards(), test.want.GetDashboards()) + } + } +} + +func TestTypes_User_String(t *testing.T) { + // setup types + u := testUser() + + want := fmt.Sprintf(`{ + Active: %t, + Admin: %t, + Dashboards: %s, + Favorites: %s, + ID: %d, + Name: %s, + Token: %s, +}`, + u.GetActive(), + u.GetAdmin(), + u.GetDashboards(), + u.GetFavorites(), + u.GetID(), + u.GetName(), + u.GetToken(), + ) + + // run test + got := u.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testUser is a test helper function to create a User +// type with all fields set to a fake value. +func testUser() *User { + u := new(User) + + u.SetID(1) + u.SetName("octocat") + u.SetToken("superSecretToken") + u.SetFavorites([]string{"github/octocat"}) + u.SetActive(true) + u.SetAdmin(false) + u.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7", "ba657dab-bc6e-421f-9188-86272bd0069a"}) + + return u +} diff --git a/api/types/worker.go b/api/types/worker.go new file mode 100644 index 000000000..af001fa80 --- /dev/null +++ b/api/types/worker.go @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" +) + +// Worker is the API representation of a worker. +// +// swagger:model Worker +type Worker struct { + ID *int64 `json:"id,omitempty"` + Hostname *string `json:"hostname,omitempty"` + Address *string `json:"address,omitempty"` + Routes *[]string `json:"routes,omitempty"` + Active *bool `json:"active,omitempty"` + Status *string `json:"status,omitempty"` + LastStatusUpdateAt *int64 `json:"last_status_update_at,omitempty"` + RunningBuilds *[]*Build `json:"running_builds,omitempty"` + LastBuildStartedAt *int64 `json:"last_build_started_at,omitempty"` + LastBuildFinishedAt *int64 `json:"last_build_finished_at,omitempty"` + LastCheckedIn *int64 `json:"last_checked_in,omitempty"` + BuildLimit *int64 `json:"build_limit,omitempty"` +} + +// GetID returns the ID field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetID() int64 { + // return zero value if Worker type or ID field is nil + if w == nil || w.ID == nil { + return 0 + } + + return *w.ID +} + +// GetHostname returns the Hostname field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetHostname() string { + // return zero value if Worker type or Hostname field is nil + if w == nil || w.Hostname == nil { + return "" + } + + return *w.Hostname +} + +// GetAddress returns the Address field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetAddress() string { + // return zero value if Worker type or Address field is nil + if w == nil || w.Address == nil { + return "" + } + + return *w.Address +} + +// GetRoutes returns the Routes field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetRoutes() []string { + // return zero value if Worker type or Routes field is nil + if w == nil || w.Routes == nil { + return []string{} + } + + return *w.Routes +} + +// GetActive returns the Active field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetActive() bool { + // return zero value if Worker type or Active field is nil + if w == nil || w.Active == nil { + return false + } + + return *w.Active +} + +// GetStatus returns the Status field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetStatus() string { + // return zero value if Worker type or Status field is nil + if w == nil || w.Status == nil { + return "" + } + + return *w.Status +} + +// GetLastStatusUpdateAt returns the LastStatusUpdateAt field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetLastStatusUpdateAt() int64 { + // return zero value if Worker type or LastStatusUpdateAt field is nil + if w == nil || w.LastStatusUpdateAt == nil { + return 0 + } + + return *w.LastStatusUpdateAt +} + +// GetRunningBuilds returns the RunningBuilds field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetRunningBuilds() []*Build { + // return zero value if Worker type or RunningBuilds field is nil + if w == nil || w.RunningBuilds == nil { + return []*Build{} + } + + return *w.RunningBuilds +} + +// GetLastBuildStartedAt returns the LastBuildStartedAt field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetLastBuildStartedAt() int64 { + // return zero value if Worker type or LastBuildStartedAt field is nil + if w == nil || w.LastBuildStartedAt == nil { + return 0 + } + + return *w.LastBuildStartedAt +} + +// GetLastBuildFinishedAt returns the LastBuildFinishedAt field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetLastBuildFinishedAt() int64 { + // return zero value if Worker type or LastBuildFinishedAt field is nil + if w == nil || w.LastBuildFinishedAt == nil { + return 0 + } + + return *w.LastBuildFinishedAt +} + +// GetLastCheckedIn returns the LastCheckedIn field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetLastCheckedIn() int64 { + // return zero value if Worker type or LastCheckedIn field is nil + if w == nil || w.LastCheckedIn == nil { + return 0 + } + + return *w.LastCheckedIn +} + +// GetBuildLimit returns the BuildLimit field. +// +// When the provided Worker type is nil, or the field within +// the type is nil, it returns the zero value for the field. +func (w *Worker) GetBuildLimit() int64 { + // return zero value if Worker type or BuildLimit field is nil + if w == nil || w.BuildLimit == nil { + return 0 + } + + return *w.BuildLimit +} + +// SetID sets the ID field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetID(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.ID = &v +} + +// SetHostname sets the Hostname field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetHostname(v string) { + // return if Worker type is nil + if w == nil { + return + } + + w.Hostname = &v +} + +// SetAddress sets the Address field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetAddress(v string) { + // return if Worker type is nil + if w == nil { + return + } + + w.Address = &v +} + +// SetRoutes sets the Routes field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetRoutes(v []string) { + // return if Worker type is nil + if w == nil { + return + } + + w.Routes = &v +} + +// SetActive sets the Active field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetActive(v bool) { + // return if Worker type is nil + if w == nil { + return + } + + w.Active = &v +} + +// SetStatus sets the Status field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetStatus(v string) { + // return if Worker type is nil + if w == nil { + return + } + + w.Status = &v +} + +// SetLastStatusUpdateAt sets the LastStatusUpdateAt field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetLastStatusUpdateAt(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.LastStatusUpdateAt = &v +} + +// SetRunningBuilds sets the RunningBuilds field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetRunningBuilds(builds []*Build) { + // return if Worker type is nil + if w == nil { + return + } + + w.RunningBuilds = &builds +} + +// SetLastBuildStartedAt sets the LastBuildStartedAt field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetLastBuildStartedAt(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.LastBuildStartedAt = &v +} + +// SetLastBuildFinishedAt sets the LastBuildFinishedAt field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetLastBuildFinishedAt(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.LastBuildFinishedAt = &v +} + +// SetLastCheckedIn sets the LastCheckedIn field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetLastCheckedIn(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.LastCheckedIn = &v +} + +// SetBuildLimit sets the LastBuildLimit field. +// +// When the provided Worker type is nil, it +// will set nothing and immediately return. +func (w *Worker) SetBuildLimit(v int64) { + // return if Worker type is nil + if w == nil { + return + } + + w.BuildLimit = &v +} + +// String implements the Stringer interface for the Worker type. +func (w *Worker) String() string { + return fmt.Sprintf(`{ + ID: %d, + Hostname: %s, + Address: %s, + Routes: %s, + Active: %t, + Status: %s, + LastStatusUpdateAt: %v, + LastBuildStartedAt: %v, + LastBuildFinishedAt: %v, + LastCheckedIn: %v, + BuildLimit: %v, + RunningBuilds: %v, +}`, + w.GetID(), + w.GetHostname(), + w.GetAddress(), + w.GetRoutes(), + w.GetActive(), + w.GetStatus(), + w.GetLastStatusUpdateAt(), + w.GetLastBuildStartedAt(), + w.GetLastBuildFinishedAt(), + w.GetLastCheckedIn(), + w.GetBuildLimit(), + w.GetRunningBuilds(), + ) +} diff --git a/api/types/worker_test.go b/api/types/worker_test.go new file mode 100644 index 000000000..633b7163d --- /dev/null +++ b/api/types/worker_test.go @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + "reflect" + "testing" + "time" +) + +func TestTypes_Worker_Getters(t *testing.T) { + // setup tests + tests := []struct { + worker *Worker + want *Worker + }{ + { + worker: testWorker(), + want: testWorker(), + }, + { + worker: new(Worker), + want: new(Worker), + }, + } + + // run tests + for _, test := range tests { + if test.worker.GetID() != test.want.GetID() { + t.Errorf("GetID is %v, want %v", test.worker.GetID(), test.want.GetID()) + } + + if test.worker.GetHostname() != test.want.GetHostname() { + t.Errorf("GetHostname is %v, want %v", test.worker.GetHostname(), test.want.GetHostname()) + } + + if test.worker.GetAddress() != test.want.GetAddress() { + t.Errorf("Getaddress is %v, want %v", test.worker.GetAddress(), test.want.GetAddress()) + } + + if !reflect.DeepEqual(test.worker.GetRoutes(), test.want.GetRoutes()) { + t.Errorf("GetRoutes is %v, want %v", test.worker.GetRoutes(), test.want.GetRoutes()) + } + + if test.worker.GetActive() != test.want.GetActive() { + t.Errorf("GetActive is %v, want %v", test.worker.GetActive(), test.want.GetActive()) + } + + if test.worker.GetStatus() != test.want.GetStatus() { + t.Errorf("GetStatus is %v, want %v", test.worker.GetStatus(), test.want.GetStatus()) + } + + if test.worker.GetLastStatusUpdateAt() != test.want.GetLastStatusUpdateAt() { + t.Errorf("GetLastStatusUpdateAt is %v, want %v", test.worker.GetLastStatusUpdateAt(), test.want.GetLastStatusUpdateAt()) + } + + if !reflect.DeepEqual(test.worker.GetRunningBuilds(), test.want.GetRunningBuilds()) { + t.Errorf("GetRunningBuilds is %v, want %v", test.worker.GetRunningBuilds(), test.want.GetRunningBuilds()) + } + + if test.worker.GetLastBuildStartedAt() != test.want.GetLastBuildStartedAt() { + t.Errorf("GetLastBuildStartedAt is %v, want %v", test.worker.GetLastBuildStartedAt(), test.want.GetLastBuildStartedAt()) + } + + if test.worker.GetLastBuildFinishedAt() != test.want.GetLastBuildFinishedAt() { + t.Errorf("GetLastBuildFinishedAt is %v, want %v", test.worker.GetLastBuildFinishedAt(), test.want.GetLastBuildFinishedAt()) + } + + if test.worker.GetLastCheckedIn() != test.want.GetLastCheckedIn() { + t.Errorf("GetLastCheckedIn is %v, want %v", test.worker.GetLastCheckedIn(), test.want.GetLastCheckedIn()) + } + + if test.worker.GetBuildLimit() != test.want.GetBuildLimit() { + t.Errorf("GetBuildLimit is %v, want %v", test.worker.GetBuildLimit(), test.want.GetBuildLimit()) + } + } +} + +func TestTypes_Worker_Setters(t *testing.T) { + // setup types + var w *Worker + + // setup tests + tests := []struct { + worker *Worker + want *Worker + }{ + { + worker: testWorker(), + want: testWorker(), + }, + { + worker: w, + want: new(Worker), + }, + } + + // run tests + for _, test := range tests { + test.worker.SetID(test.want.GetID()) + test.worker.SetHostname(test.want.GetHostname()) + test.worker.SetAddress(test.want.GetAddress()) + test.worker.SetRoutes(test.want.GetRoutes()) + test.worker.SetActive(test.want.GetActive()) + test.worker.SetStatus(test.want.GetStatus()) + test.worker.SetLastStatusUpdateAt(test.want.GetLastStatusUpdateAt()) + test.worker.SetRunningBuilds(test.want.GetRunningBuilds()) + test.worker.SetLastBuildStartedAt(test.want.GetLastBuildStartedAt()) + test.worker.SetLastBuildFinishedAt(test.want.GetLastBuildFinishedAt()) + test.worker.SetLastCheckedIn(test.want.GetLastCheckedIn()) + test.worker.SetBuildLimit(test.want.GetBuildLimit()) + + if test.worker.GetID() != test.want.GetID() { + t.Errorf("SetID is %v, want %v", test.worker.GetID(), test.want.GetID()) + } + + if test.worker.GetHostname() != test.want.GetHostname() { + t.Errorf("SetHostname is %v, want %v", test.worker.GetHostname(), test.want.GetHostname()) + } + + if test.worker.GetAddress() != test.want.GetAddress() { + t.Errorf("SetAddress is %v, want %v", test.worker.GetAddress(), test.want.GetAddress()) + } + + if !reflect.DeepEqual(test.worker.GetRoutes(), test.want.GetRoutes()) { + t.Errorf("SetRoutes is %v, want %v", test.worker.GetRoutes(), test.want.GetRoutes()) + } + + if test.worker.GetActive() != test.want.GetActive() { + t.Errorf("SetActive is %v, want %v", test.worker.GetActive(), test.want.GetActive()) + } + + if test.worker.GetStatus() != test.want.GetStatus() { + t.Errorf("SetStatus is %v, want %v", test.worker.GetStatus(), test.want.GetStatus()) + } + + if test.worker.GetLastStatusUpdateAt() != test.want.GetLastStatusUpdateAt() { + t.Errorf("SetLastStatusUpdateAt is %v, want %v", test.worker.GetLastStatusUpdateAt(), test.want.GetLastStatusUpdateAt()) + } + + if test.worker.GetLastBuildStartedAt() != test.want.GetLastBuildStartedAt() { + t.Errorf("SetLastBuildStartedAt is %v, want %v", test.worker.GetLastBuildStartedAt(), test.want.GetLastBuildStartedAt()) + } + + if test.worker.GetLastBuildFinishedAt() != test.want.GetLastBuildFinishedAt() { + t.Errorf("SetLastBuildFinishedAt is %v, want %v", test.worker.GetLastBuildFinishedAt(), test.want.GetLastBuildFinishedAt()) + } + + if test.worker.GetLastCheckedIn() != test.want.GetLastCheckedIn() { + t.Errorf("SetLastCheckedIn is %v, want %v", test.worker.GetLastCheckedIn(), test.want.GetLastCheckedIn()) + } + + if test.worker.GetBuildLimit() != test.want.GetBuildLimit() { + t.Errorf("SetBuildLimit is %v, want %v", test.worker.GetBuildLimit(), test.want.GetBuildLimit()) + } + } +} + +func TestTypes_Worker_String(t *testing.T) { + // setup types + w := testWorker() + + want := fmt.Sprintf(`{ + ID: %d, + Hostname: %s, + Address: %s, + Routes: %s, + Active: %t, + Status: %s, + LastStatusUpdateAt: %v, + LastBuildStartedAt: %v, + LastBuildFinishedAt: %v, + LastCheckedIn: %v, + BuildLimit: %v, + RunningBuilds: %v, +}`, + w.GetID(), + w.GetHostname(), + w.GetAddress(), + w.GetRoutes(), + w.GetActive(), + w.GetStatus(), + w.GetLastStatusUpdateAt(), + w.GetLastBuildStartedAt(), + w.GetLastBuildFinishedAt(), + w.GetLastCheckedIn(), + w.GetBuildLimit(), + w.GetRunningBuilds(), + ) + + // run test + got := w.String() + + if !reflect.DeepEqual(got, want) { + t.Errorf("String is %v, want %v", got, want) + } +} + +// testWorker is a test helper function to create a Worker +// type with all fields set to a fake value. +func testWorker() *Worker { + b := new(Build) + b.SetID(1) + + w := new(Worker) + + w.SetID(1) + w.SetHostname("worker_0") + w.SetAddress("http://localhost:8080") + w.SetRoutes([]string{"vela"}) + w.SetActive(true) + w.SetStatus("available") + w.SetLastStatusUpdateAt(time.Time{}.UTC().Unix()) + w.SetRunningBuilds([]*Build{b}) + w.SetLastBuildStartedAt(time.Time{}.UTC().Unix()) + w.SetLastBuildFinishedAt(time.Time{}.UTC().Unix()) + w.SetLastCheckedIn(time.Time{}.UTC().Unix()) + w.SetBuildLimit(2) + + return w +} diff --git a/api/user/create.go b/api/user/create.go index 575e73c88..e856a8736 100644 --- a/api/user/create.go +++ b/api/user/create.go @@ -7,16 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/users users CreateUser // -// Create a user for the configured backend +// Create a user // // --- // produces: @@ -24,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing the user to create +// description: User object to create // required: true // schema: // "$ref": "#/definitions/User" @@ -36,23 +37,27 @@ import ( // schema: // "$ref": "#/definitions/User" // '400': -// description: Unable to create the user +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the user +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// CreateUser represents the API handler to create -// a user in the configured backend. +// CreateUser represents the API handler to create a user. func CreateUser(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() // capture body from API request - input := new(library.User) + input := new(types.User) err := c.Bind(input) if err != nil { @@ -66,9 +71,9 @@ func CreateUser(c *gin.Context) { // update engine logger with API metadata // // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ + l.WithFields(logrus.Fields{ "user": u.GetName(), - }).Infof("creating new user %s", input.GetName()) + }).Debugf("creating new user %s", input.GetName()) // send API call to create the user user, err := database.FromContext(c).CreateUser(ctx, input) @@ -80,5 +85,10 @@ func CreateUser(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "user": user.GetName(), + "user_id": user.GetID(), + }).Info("user created") + c.JSON(http.StatusCreated, user) } diff --git a/api/user/create_token.go b/api/user/create_token.go index 9f57fde9a..2eb0c9028 100644 --- a/api/user/create_token.go +++ b/api/user/create_token.go @@ -8,12 +8,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/user/token users CreateToken @@ -30,24 +31,24 @@ import ( // description: Successfully created a token for the current user // schema: // "$ref": "#/definitions/Token" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '503': // description: Unable to create a token for the current user // schema: // "$ref": "#/definitions/Error" // CreateToken represents the API handler to create -// a user token in the configured backend. +// a user token. func CreateToken(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("composing token for user %s", u.GetName()) + l.Debugf("composing token for user %s", u.GetName()) tm := c.MustGet("token-manager").(*token.Manager) @@ -73,5 +74,7 @@ func CreateToken(c *gin.Context) { return } + l.Info("user updated - token created") + c.JSON(http.StatusOK, library.Token{Token: &at}) } diff --git a/api/user/delete.go b/api/user/delete.go index 36393d24a..e3cd1e184 100644 --- a/api/user/delete.go +++ b/api/user/delete.go @@ -7,15 +7,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/users/{user} users DeleteUser // -// Delete a user for the configured backend +// Delete a user // // --- // produces: @@ -30,32 +30,30 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully deleted of user +// description: Successfully deleted user // schema: // type: string +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to delete user +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to delete user +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteUser represents the API handler to remove -// a user from the configured backend. +// DeleteUser represents the API handler to remove a user. func DeleteUser(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) user := util.PathParameter(c, "user") ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("deleting user %s", user) + l.Debugf("deleting user %s", user) // send API call to capture the user u, err := database.FromContext(c).GetUserForName(ctx, user) diff --git a/api/user/delete_token.go b/api/user/delete_token.go index adb554015..8ee9f27da 100644 --- a/api/user/delete_token.go +++ b/api/user/delete_token.go @@ -8,12 +8,13 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/user/token users DeleteToken @@ -30,24 +31,24 @@ import ( // description: Successfully delete a token for the current user // schema: // type: string -// '500': +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '503': // description: Unable to delete a token for the current user // schema: // "$ref": "#/definitions/Error" // DeleteToken represents the API handler to revoke -// and recreate a user token in the configured backend. +// and recreate a user token. func DeleteToken(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("revoking token for user %s", u.GetName()) + l.Debugf("revoking token for user %s", u.GetName()) tm := c.MustGet("token-manager").(*token.Manager) @@ -73,5 +74,7 @@ func DeleteToken(c *gin.Context) { return } + l.Info("user updated - token rotated") + c.JSON(http.StatusOK, library.Token{Token: &at}) } diff --git a/api/user/get.go b/api/user/get.go index e888ff804..5d39b9518 100644 --- a/api/user/get.go +++ b/api/user/get.go @@ -7,15 +7,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/users/{user} users GetUser // -// Retrieve a user for the configured backend +// Get a user // // --- // produces: @@ -33,25 +33,23 @@ import ( // description: Successfully retrieved the user // schema: // "$ref": "#/definitions/User" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the user +// description: Not found // schema: // "$ref": "#/definitions/Error" -// GetUser represents the API handler to capture a -// user from the configured backend. +// GetUser represents the API handler to get a user. func GetUser(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) user := util.PathParameter(c, "user") ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("reading user %s", user) + l.Debugf("reading user %s", user) // send API call to capture the user u, err := database.FromContext(c).GetUserForName(ctx, user) diff --git a/api/user/get_current.go b/api/user/get_current.go index 641576df0..a33fab4d2 100644 --- a/api/user/get_current.go +++ b/api/user/get_current.go @@ -6,13 +6,14 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/user" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/router/middleware/user" ) // swagger:operation GET /api/v1/user users GetCurrentUser // -// Retrieve the current authenticated user from the configured backend +// Get the current authenticated user // // --- // produces: @@ -24,19 +25,19 @@ import ( // description: Successfully retrieved the current user // schema: // "$ref": "#/definitions/User" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // GetCurrentUser represents the API handler to capture the -// currently authenticated user from the configured backend. +// currently authenticated user. func GetCurrentUser(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("reading current user %s", u.GetName()) + l.Debugf("reading current user %s", u.GetName()) c.JSON(http.StatusOK, u) } diff --git a/api/user/get_source.go b/api/user/get_source.go index 6390c00e7..19b75eea3 100644 --- a/api/user/get_source.go +++ b/api/user/get_source.go @@ -7,17 +7,18 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/user/source/repos users GetSourceRepos // -// Retrieve a list of repos for the current authenticated user +// Get all repos for the current authenticated user // // --- // produces: @@ -29,28 +30,27 @@ import ( // description: Successfully retrieved a list of repos for the current user // schema: // "$ref": "#/definitions/Repo" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve a list of repos for the current user +// description: Not found // schema: // "$ref": "#/definitions/Error" -// GetSourceRepos represents the API handler to capture -// the list of repos for a user from the configured backend. +// GetSourceRepos represents the API handler to get a list of repos for a user. func GetSourceRepos(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("reading available SCM repos for user %s", u.GetName()) + l.Debugf("reading available SCM repos for user %s", u.GetName()) // variables to capture requested data - dbRepos := []*library.Repo{} - output := make(map[string][]library.Repo) + dbRepos := []*types.Repo{} + output := make(map[string][]types.Repo) // send API call to capture the list of repos for the user srcRepos, err := scm.FromContext(c).ListUserRepos(ctx, u) @@ -72,7 +72,7 @@ func GetSourceRepos(c *gin.Context) { active := false // library struct to omit optional fields - repo := library.Repo{ + repo := types.Repo{ Org: org, Name: name, Active: &active, diff --git a/api/user/list.go b/api/user/list.go index a62ae57aa..12dde536c 100644 --- a/api/user/list.go +++ b/api/user/list.go @@ -8,16 +8,16 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/api" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/users users ListUsers // -// Retrieve a list of users for the configured backend +// Get all users // // --- // produces: @@ -48,30 +48,28 @@ import ( // description: Total number of results // type: integer // Link: -// description: see https://tools.ietf.org/html/rfc5988 +// description: See https://tools.ietf.org/html/rfc5988 // type: string // '400': -// description: Unable to retrieve the list of users +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of users +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListUsers represents the API handler to capture a list -// of users from the configured backend. +// ListUsers represents the API handler to get a list of users. func ListUsers(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Info("reading lite users") + l.Debug("reading lite users") // capture page query parameter if present page, err := strconv.Atoi(c.DefaultQuery("page", "1")) diff --git a/api/user/update.go b/api/user/update.go index 0535da1f5..f356bd956 100644 --- a/api/user/update.go +++ b/api/user/update.go @@ -7,16 +7,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/users/{user} users UpdateUser // -// Update a user for the configured backend +// Update a user // // --- // produces: @@ -29,7 +29,7 @@ import ( // type: string // - in: body // name: body -// description: Payload containing the user to update +// description: The user object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/User" @@ -41,35 +41,33 @@ import ( // schema: // "$ref": "#/definitions/User" // '400': -// description: Unable to update the user +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to update the user +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the user +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// UpdateUser represents the API handler to update -// a user in the configured backend. +// UpdateUser represents the API handler to update a user. func UpdateUser(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) user := util.PathParameter(c, "user") ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("updating user %s", user) + l.Debugf("updating user %s", user) // capture body from API request - input := new(library.User) + input := new(types.User) err := c.Bind(input) if err != nil { @@ -81,7 +79,7 @@ func UpdateUser(c *gin.Context) { } // send API call to capture the user - u, err = database.FromContext(c).GetUserForName(ctx, user) + u, err := database.FromContext(c).GetUserForName(ctx, user) if err != nil { retErr := fmt.Errorf("unable to get user %s: %w", user, err) @@ -106,6 +104,11 @@ func UpdateUser(c *gin.Context) { u.SetFavorites(input.GetFavorites()) } + if input.Dashboards != nil { + // update dashboards if set + u.SetDashboards(input.GetDashboards()) + } + // send API call to update the user u, err = database.FromContext(c).UpdateUser(ctx, u) if err != nil { diff --git a/api/user/update_current.go b/api/user/update_current.go index ab56023ad..d73bb445d 100644 --- a/api/user/update_current.go +++ b/api/user/update_current.go @@ -7,16 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/user users UpdateCurrentUser // -// Update the current authenticated user in the configured backend +// Update the current authenticated user // // --- // produces: @@ -24,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing the user to update +// description: The user object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/User" @@ -36,34 +37,30 @@ import ( // schema: // "$ref": "#/definitions/User" // '400': -// description: Unable to update the current user +// description: Invalid request payload // schema: // "$ref": "#/definitions/Error" -// '404': -// description: Unable to update the current user +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the current user +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateCurrentUser represents the API handler to capture and -// update the currently authenticated user from the configured backend. +// update the currently authenticated user. func UpdateCurrentUser(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("updating current user %s", u.GetName()) + l.Debugf("updating current user %s", u.GetName()) // capture body from API request - input := new(library.User) + input := new(types.User) err := c.Bind(input) if err != nil { @@ -80,6 +77,12 @@ func UpdateCurrentUser(c *gin.Context) { u.SetFavorites(input.GetFavorites()) } + // update user fields if provided + if input.Dashboards != nil { + // update dashboards if set + u.SetDashboards(input.GetDashboards()) + } + // send API call to update the user u, err = database.FromContext(c).UpdateUser(ctx, u) if err != nil { diff --git a/api/version.go b/api/version.go index 349fd5ea4..726e7cc3f 100644 --- a/api/version.go +++ b/api/version.go @@ -12,7 +12,7 @@ import ( // swagger:operation GET /version base Version // -// Get the version of the Vela API +// Get the Vela API version // // --- // produces: diff --git a/api/webhook/post.go b/api/webhook/post.go index 1ca54c40f..e4ac32c3d 100644 --- a/api/webhook/post.go +++ b/api/webhook/post.go @@ -14,25 +14,26 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "gorm.io/gorm" + "github.com/go-vela/server/api/build" + "github.com/go-vela/server/api/types" "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/queue" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" - "gorm.io/gorm" ) var baseErr = "unable to process webhook" // swagger:operation POST /webhook base PostWebhook // -// Deliver a webhook to the vela api +// Deliver a webhook to the Vela API // // --- // produces: @@ -46,23 +47,32 @@ var baseErr = "unable to process webhook" // "$ref": "#/definitions/Webhook" // responses: // '200': -// description: Successfully received the webhook +// description: Successfully received the webhook but build was skipped +// schema: +// type: string +// '201': +// description: Successfully created the build from webhook +// type: json // schema: // "$ref": "#/definitions/Build" // '400': -// description: Malformed webhook payload +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to receive the webhook +// description: Not found // schema: // "$ref": "#/definitions/Error" -// '401': -// description: Unauthenticated +// '429': +// description: Concurrent build limit reached for repository // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to receive the webhook +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -72,12 +82,13 @@ var baseErr = "unable to process webhook" // //nolint:funlen,gocyclo // ignore function length and cyclomatic complexity func PostWebhook(c *gin.Context) { - logrus.Info("webhook received") - // capture middleware values - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() + l.Debug("webhook received") + // duplicate request so we can perform operations on the request body // // https://golang.org/pkg/net/http/#Request.Clone @@ -132,8 +143,8 @@ func PostWebhook(c *gin.Context) { h, r, b := webhook.Hook, webhook.Repo, webhook.Build - logrus.Debugf("hook generated from SCM: %v", h) - logrus.Debugf("repo generated from SCM: %v", r) + l.Debugf("hook generated from SCM: %v", h) + l.Debugf("repo generated from SCM: %v", r) // if event is repository event, handle separately and return if strings.EqualFold(h.GetEvent(), constants.EventRepository) { @@ -163,7 +174,7 @@ func PostWebhook(c *gin.Context) { return } - logrus.Debugf(`build author: %s, + l.Debugf(`build author: %s, build branch: %s, build commit: %s, build ref: %s`, @@ -181,8 +192,16 @@ func PostWebhook(c *gin.Context) { // send API call to update the webhook _, err = database.FromContext(c).UpdateHook(ctx, h) if err != nil { - logrus.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err) + l.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err) } + + l.WithFields(logrus.Fields{ + "hook": h.GetNumber(), + "hook_id": h.GetID(), + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + }).Info("hook updated") }() // send API call to capture parsed repo from webhook @@ -197,8 +216,27 @@ func PostWebhook(c *gin.Context) { return } + // attach a sender SCM id if the webhook payload from the SCM has no sender id + // the code in ProcessWebhook implies that the sender may not always be present + // fallbacks like pusher/commit_author do not have an id + if len(b.GetSenderSCMID()) == 0 || b.GetSenderSCMID() == "0" { + // fetch scm user id for pusher + senderID, err := scm.FromContext(c).GetUserID(ctx, b.GetSender(), repo.GetOwner().GetToken()) + if err != nil { + retErr := fmt.Errorf("unable to assign sender SCM id: %w", err) + util.HandleError(c, http.StatusBadRequest, retErr) + + h.SetStatus(constants.StatusFailure) + h.SetError(retErr.Error()) + + return + } + + b.SetSenderSCMID(senderID) + } + // set the RepoID fields - b.SetRepoID(repo.GetID()) + b.SetRepo(repo) h.SetRepoID(repo.GetID()) // send API call to capture the last hook for the repo @@ -232,6 +270,13 @@ func PostWebhook(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "hook": h.GetNumber(), + "hook_id": h.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + }).Info("hook created") + // verify the webhook from the source control provider if c.Value("webhookvalidation").(bool) { err = scm.FromContext(c).VerifyWebhook(ctx, dupRequest, repo) @@ -273,399 +318,65 @@ func PostWebhook(c *gin.Context) { return } - // check if the repo has a valid owner - if repo.GetUserID() == 0 { - retErr := fmt.Errorf("%s: %s has no valid owner", baseErr, repo.GetFullName()) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // send API call to capture repo owner - logrus.Debugf("capturing owner of repository %s", repo.GetFullName()) - - u, err := database.FromContext(c).GetUser(ctx, repo.GetUserID()) - if err != nil { - retErr := fmt.Errorf("%s: failed to get owner for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // confirm current repo owner has at least write access to repo (needed for status update later) - _, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) - if err != nil { - retErr := fmt.Errorf("unable to publish build to queue: repository owner %s no longer has write access to repository %s", u.GetName(), r.GetFullName()) - util.HandleError(c, http.StatusUnauthorized, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // create SQL filters for querying pending and running builds for repo - filters := map[string]interface{}{ - "status": []string{constants.StatusPending, constants.StatusRunning}, - } - - // send API call to capture the number of pending or running builds for the repo - builds, err := database.FromContext(c).CountBuildsForRepo(ctx, repo, filters) - if err != nil { - retErr := fmt.Errorf("%s: unable to get count of builds for repo %s", baseErr, repo.GetFullName()) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - logrus.Debugf("currently %d builds running on repo %s", builds, repo.GetFullName()) - - // check if the number of pending and running builds exceeds the limit for the repo - if builds >= repo.GetBuildLimit() { - retErr := fmt.Errorf("%s: repo %s has exceeded the concurrent build limit of %d", baseErr, repo.GetFullName(), repo.GetBuildLimit()) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // update fields in build object - logrus.Debugf("updating build number to %d", repo.GetCounter()) - b.SetNumber(repo.GetCounter()) - - logrus.Debug("updating status to pending") - b.SetStatus(constants.StatusPending) - - // if the event is issue_comment and the issue is a pull request, - // call SCM for more data not provided in webhook payload - if strings.EqualFold(b.GetEvent(), constants.EventComment) && webhook.PullRequest.Number > 0 { - commit, branch, baseref, headref, err := scm.FromContext(c).GetPullRequest(ctx, u, repo, webhook.PullRequest.Number) - if err != nil { - retErr := fmt.Errorf("%s: failed to get pull request info for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } + var ( + prComment string + prLabels []string + ) - b.SetCommit(commit) - b.SetBranch(strings.ReplaceAll(branch, "refs/heads/", "")) - b.SetBaseRef(baseref) - b.SetHeadRef(headref) + if strings.EqualFold(b.GetEvent(), constants.EventComment) { + prComment = webhook.PullRequest.Comment } - // variable to store changeset files - var files []string - - // check if the build event is not issue_comment or pull_request - if !strings.EqualFold(b.GetEvent(), constants.EventComment) && - !strings.EqualFold(b.GetEvent(), constants.EventPull) && - !strings.EqualFold(b.GetEvent(), constants.EventDelete) { - // send API call to capture list of files changed for the commit - files, err = scm.FromContext(c).Changeset(ctx, u, repo, b.GetCommit()) - if err != nil { - retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } + if strings.EqualFold(b.GetEvent(), constants.EventPull) { + prLabels = webhook.PullRequest.Labels } - // check if the build event is a pull_request - if strings.EqualFold(b.GetEvent(), constants.EventPull) && webhook.PullRequest.Number > 0 { - // send API call to capture list of files changed for the pull request - files, err = scm.FromContext(c).ChangesetPR(ctx, u, repo, webhook.PullRequest.Number) - if err != nil { - retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } + // construct CompileAndPublishConfig + config := build.CompileAndPublishConfig{ + Build: b, + Metadata: m, + BaseErr: baseErr, + Source: "webhook", + Comment: prComment, + Labels: prLabels, + Retries: 3, } - var ( - // variable to store the raw pipeline configuration - config []byte - // variable to store executable pipeline - p *pipeline.Build - // variable to store pipeline configuration - pipeline *library.Pipeline - // variable to control number of times to retry processing pipeline - retryLimit = 3 - // variable to store the pipeline type for the repository - pipelineType = repo.GetPipelineType() + // generate the queue item + p, item, code, err := build.CompileAndPublish( + c, + config, + database.FromContext(c), + scm.FromContext(c), + compiler.FromContext(c), + queue.FromContext(c), ) - // implement a loop to process asynchronous operations with a retry limit - // - // Some operations taken during the webhook workflow can lead to race conditions - // failing to successfully process the request. This logic ensures we attempt our - // best efforts to handle these cases gracefully. - for i := 0; i < retryLimit; i++ { - logrus.Debugf("compilation loop - attempt %d", i+1) - // check if we're on the first iteration of the loop - if i > 0 { - // incrementally sleep in between retries - time.Sleep(time.Duration(i) * time.Second) - } - - // send database call to attempt to capture the pipeline if we already processed it before - pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, b.GetCommit(), repo) - if err != nil { // assume the pipeline doesn't exist in the database yet - // send API call to capture the pipeline configuration file - config, err = scm.FromContext(c).ConfigBackoff(ctx, u, repo, b.GetCommit()) - if err != nil { - retErr := fmt.Errorf("%s: unable to get pipeline configuration for %s: %w", baseErr, repo.GetFullName(), err) - - util.HandleError(c, http.StatusNotFound, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - } else { - config = pipeline.GetData() - } - - // send API call to capture repo for the counter (grabbing repo again to ensure counter is correct) - repo, err = database.FromContext(c).GetRepoForOrg(ctx, repo.GetOrg(), repo.GetName()) - if err != nil { - retErr := fmt.Errorf("%s: unable to get repo %s: %w", baseErr, r.GetFullName(), err) - - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(retErr).Warningf("retrying #%d", i+1) - - // continue to the next iteration of the loop - continue - } - - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // update DB record of repo (repo) with any changes captured from webhook payload (r) - repo.SetTopics(r.GetTopics()) - repo.SetBranch(r.GetBranch()) - - // update the build numbers based off repo counter - inc := repo.GetCounter() + 1 - repo.SetCounter(inc) - b.SetNumber(inc) - - // populate the build link if a web address is provided - if len(m.Vela.WebAddress) > 0 { - b.SetLink( - fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, repo.GetFullName(), b.GetNumber()), - ) - } - - // ensure we use the expected pipeline type when compiling - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - if len(pipeline.GetType()) > 0 { - repo.SetPipelineType(pipeline.GetType()) - } - - var compiled *library.Pipeline - // parse and compile the pipeline configuration file - p, compiled, err = compiler.FromContext(c). - Duplicate(). - WithBuild(b). - WithComment(webhook.PullRequest.Comment). - WithCommit(b.GetCommit()). - WithFiles(files). - WithMetadata(m). - WithRepo(repo). - WithUser(u). - Compile(config) - if err != nil { - // format the error message with extra information - err = fmt.Errorf("unable to compile pipeline configuration for %s: %w", repo.GetFullName(), err) - - // log the error for traceability - logrus.Error(err.Error()) - - retErr := fmt.Errorf("%s: %w", baseErr, err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // reset the pipeline type for the repo - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - repo.SetPipelineType(pipelineType) - - // skip the build if pipeline compiled to only the init and clone steps - skip := build.SkipEmptyBuild(p) - if skip != "" { - // set build to successful status - b.SetStatus(constants.StatusSkipped) - - // set hook status and message - h.SetStatus(constants.StatusSkipped) - h.SetError(skip) - - // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, b, repo.GetOrg(), repo.GetName()) - if err != nil { - logrus.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err) - } - - c.JSON(http.StatusOK, skip) - - return - } - - // check if the pipeline did not already exist in the database - if pipeline == nil { - pipeline = compiled - pipeline.SetRepoID(repo.GetID()) - pipeline.SetCommit(b.GetCommit()) - pipeline.SetRef(b.GetRef()) - - // send API call to create the pipeline - pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline) - if err != nil { - retErr := fmt.Errorf("%s: failed to create pipeline for %s: %w", baseErr, repo.GetFullName(), err) - - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(retErr).Warningf("retrying #%d", i+1) - - // continue to the next iteration of the loop - continue - } - - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - } - - b.SetPipelineID(pipeline.GetID()) - - // create the objects from the pipeline in the database - // TODO: - // - if a build gets created and something else fails midway, - // the next loop will attempt to create the same build, - // using the same Number and thus create a constraint - // conflict; consider deleting the partially created - // build object in the database - err = build.PlanBuild(ctx, database.FromContext(c), scm.FromContext(c), p, b, repo) - if err != nil { - retErr := fmt.Errorf("%s: %w", baseErr, err) - - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(retErr).Warningf("retrying #%d", i+1) - - // reset fields set by cleanBuild for retry - b.SetError("") - b.SetStatus(constants.StatusPending) - b.SetFinished(0) - - // continue to the next iteration of the loop - continue - } - - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - - // break the loop because everything was successful - break - } // end of retry loop - - // send API call to update repo for ensuring counter is incremented - repo, err = database.FromContext(c).UpdateRepo(ctx, repo) - if err != nil { - retErr := fmt.Errorf("%s: failed to update repo %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) + // error handling done in CompileAndPublish + if err != nil && code == http.StatusOK { + h.SetStatus(constants.StatusSkipped) + h.SetError(err.Error()) - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) + c.JSON(http.StatusOK, err.Error()) return } - // return error if pipeline didn't get populated - if p == nil { - retErr := fmt.Errorf("%s: failed to set pipeline for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) - + if err != nil { h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) + h.SetError(err.Error()) - return - } - - // return error if build didn't get populated - if b == nil { - retErr := fmt.Errorf("%s: failed to set build for %s: %w", baseErr, repo.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) + util.HandleError(c, code, err) return } - // send API call to capture the triggered build - b, err = database.FromContext(c).GetBuildForRepo(ctx, repo, b.GetNumber()) - if err != nil { - retErr := fmt.Errorf("%s: failed to get new build %s/%d: %w", baseErr, repo.GetFullName(), b.GetNumber(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) + // capture the build and repo from the items + b = item.Build - return - } - - // set the BuildID field + // set hook build_id to the generated build id h.SetBuildID(b.GetID()) + // if event is deployment, update the deployment record to include this build if strings.EqualFold(b.GetEvent(), constants.EventDeploy) { d, err := database.FromContext(c).GetDeploymentForRepo(c, repo, webhook.Deployment.GetNumber()) @@ -674,9 +385,9 @@ func PostWebhook(c *gin.Context) { deployment := webhook.Deployment deployment.SetRepoID(repo.GetID()) - deployment.SetBuilds([]*library.Build{b}) + deployment.SetBuilds([]*library.Build{b.ToLibrary()}) - _, err := database.FromContext(c).CreateDeployment(c, deployment) + dr, err := database.FromContext(c).CreateDeployment(c, deployment) if err != nil { retErr := fmt.Errorf("%s: failed to create deployment %s/%d: %w", baseErr, repo.GetFullName(), deployment.GetNumber(), err) util.HandleError(c, http.StatusInternalServerError, retErr) @@ -686,6 +397,14 @@ func PostWebhook(c *gin.Context) { return } + + l.WithFields(logrus.Fields{ + "deployment": dr.GetNumber(), + "deployment_id": dr.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Info("deployment created") } else { retErr := fmt.Errorf("%s: failed to get deployment %s/%d: %w", baseErr, repo.GetFullName(), webhook.Deployment.GetNumber(), err) util.HandleError(c, http.StatusInternalServerError, retErr) @@ -696,8 +415,10 @@ func PostWebhook(c *gin.Context) { return } } else { - build := append(d.GetBuilds(), b) + build := append(d.GetBuilds(), b.ToLibrary()) + d.SetBuilds(build) + _, err := database.FromContext(c).UpdateDeployment(ctx, d) if err != nil { retErr := fmt.Errorf("%s: failed to update deployment %s/%d: %w", baseErr, repo.GetFullName(), d.GetNumber(), err) @@ -708,33 +429,18 @@ func PostWebhook(c *gin.Context) { return } - } - } - c.JSON(http.StatusOK, b) - - // determine queue route - route, err := queue.FromContext(c).Route(&p.Worker) - if err != nil { - logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - build.CleanBuild(ctx, database.FromContext(c), b, nil, nil, err) - - return + l.WithFields(logrus.Fields{ + "deployment": d.GetNumber(), + "deployment_id": d.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Info("deployment updated") + } } - // temporarily set host to the route before it gets picked up by a worker - b.SetHost(route) - - // publish the pipeline.Build to the build_executables table to be requested by a worker - err = build.PublishBuildExecutable(ctx, database.FromContext(c), p, b) - if err != nil { - retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", repo.GetFullName(), b.GetNumber(), err) - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } + c.JSON(http.StatusCreated, b) // regardless of whether the build is published to queue, we want to attempt to auto-cancel if no errors defer func() { @@ -742,29 +448,47 @@ func PostWebhook(c *gin.Context) { // fetch pending and running builds rBs, err := database.FromContext(c).ListPendingAndRunningBuildsForRepo(c, repo) if err != nil { - logrus.Errorf("unable to fetch pending and running builds for %s: %v", repo.GetFullName(), err) + l.Errorf("unable to fetch pending and running builds for %s: %v", repo.GetFullName(), err) } + l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Debugf("found %d pending/running builds", len(rBs)) + for _, rB := range rBs { // call auto cancel routine - canceled, err := build.AutoCancel(c, b, rB, repo, p.Metadata.AutoCancel) + canceled, err := build.AutoCancel(c, b, rB, p.Metadata.AutoCancel) if err != nil { // continue cancel loop if error, but log based on type of error if canceled { - logrus.Errorf("unable to update canceled build error message: %v", err) + l.Errorf("unable to update canceled build error message: %v", err) } else { - logrus.Errorf("unable to cancel running build: %v", err) + l.Errorf("unable to cancel running build: %v", err) } } + + l.WithFields(logrus.Fields{ + "build": rB.GetNumber(), + "build_id": rB.GetID(), + "org": repo.GetOrg(), + "repo": repo.GetName(), + "repo_id": repo.GetID(), + }).Debug("auto-canceled build") } } }() // if the webhook was from a Pull event from a forked repository, verify it is allowed to run if webhook.PullRequest.IsFromFork { + l.Tracef("inside %s workflow for fork PR build %s/%d", repo.GetApproveBuild(), r.GetFullName(), b.GetNumber()) + switch repo.GetApproveBuild() { case constants.ApproveForkAlways: - err = gatekeepBuild(c, b, repo, u) + err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) } @@ -772,9 +496,9 @@ func PostWebhook(c *gin.Context) { return case constants.ApproveForkNoWrite: // determine if build sender has write access to parent repo. If not, this call will result in an error - _, err = scm.FromContext(c).RepoAccess(ctx, b.GetSender(), u.GetToken(), r.GetOrg(), r.GetName()) + _, err = scm.FromContext(c).RepoAccess(ctx, b.GetSender(), r.GetOwner().GetToken(), r.GetOrg(), r.GetName()) if err != nil { - err = gatekeepBuild(c, b, repo, u) + err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) } @@ -782,19 +506,19 @@ func PostWebhook(c *gin.Context) { return } - fallthrough + l.Debugf("fork PR build %s/%d automatically running without approval", repo.GetFullName(), b.GetNumber()) case constants.ApproveOnce: // determine if build sender is in the contributors list for the repo // // NOTE: this call is cumbersome for repos with lots of contributors. Potential TODO: improve this if // GitHub adds a single-contributor API endpoint. - contributor, err := scm.FromContext(c).RepoContributor(ctx, u, b.GetSender(), r.GetOrg(), r.GetName()) + contributor, err := scm.FromContext(c).RepoContributor(ctx, r.GetOwner(), b.GetSender(), r.GetOrg(), r.GetName()) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) } if !contributor { - err = gatekeepBuild(c, b, repo, u) + err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) } @@ -806,39 +530,51 @@ func PostWebhook(c *gin.Context) { case constants.ApproveNever: fallthrough default: - logrus.Debugf("fork PR build %s/%d automatically running without approval", repo.GetFullName(), b.GetNumber()) + l.Debugf("fork PR build %s/%d automatically running without approval", repo.GetFullName(), b.GetNumber()) } } // send API call to set the status on the commit - err = scm.FromContext(c).Status(ctx, u, b, repo.GetOrg(), repo.GetName()) + err = scm.FromContext(c).Status(ctx, repo.GetOwner(), b, repo.GetOrg(), repo.GetName()) if err != nil { - logrus.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err) + l.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err) } // publish the build to the queue - go build.PublishToQueue( + go build.Enqueue( ctx, queue.FromGinContext(c), database.FromContext(c), - b, - repo, - u, - route, + item, + b.GetHost(), ) } // handleRepositoryEvent is a helper function that processes repository events from the SCM and updates // the database resources with any relevant changes resulting from the event, such as name changes, transfers, etc. -func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *types.Metadata, h *library.Hook, r *library.Repo) (*library.Repo, error) { - logrus.Debugf("webhook is repository event, making necessary updates to repo %s", r.GetFullName()) +func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Metadata, h *library.Hook, r *types.Repo) (*types.Repo, error) { + l := c.MustGet("logger").(*logrus.Entry) + + l = l.WithFields(logrus.Fields{ + "event_type": h.GetEvent(), + }) + + l.Debugf("webhook is repository event, making necessary updates to repo %s", r.GetFullName()) defer func() { // send API call to update the webhook - _, err := database.FromContext(c).CreateHook(ctx, h) + hr, err := database.FromContext(c).CreateHook(ctx, h) if err != nil { - logrus.Errorf("unable to create webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err) + l.Errorf("unable to create webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err) } + + l.WithFields(logrus.Fields{ + "hook": hr.GetNumber(), + "hook_id": hr.GetID(), + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + }).Info("hook created") }() switch h.GetEventAction() { @@ -855,7 +591,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *types.Metadat return r, nil // if action is archived, unarchived, or edited, perform edits to relevant repo fields case "archived", "unarchived", constants.ActionEdited: - logrus.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName()) + l.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName()) // send call to get repository from database dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) if err != nil { @@ -911,6 +647,12 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *types.Metadat return nil, err } + l.WithFields(logrus.Fields{ + "org": dbRepo.GetOrg(), + "repo": dbRepo.GetName(), + "repo_id": dbRepo.GetID(), + }).Info("repo updated") + return dbRepo, nil // all other repo event actions are skippable default: @@ -922,8 +664,14 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *types.Metadat // queries the database for the repo that matches that name and org, and updates // that repo to its new name in order to preserve it. It also updates the secrets // associated with that repo as well as build links for the UI. -func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c *gin.Context, m *types.Metadata) (*library.Repo, error) { - logrus.Infof("renaming repository from %s to %s", r.GetPreviousName(), r.GetName()) +func RenameRepository(ctx context.Context, h *library.Hook, r *types.Repo, c *gin.Context, m *internal.Metadata) (*types.Repo, error) { + l := c.MustGet("logger").(*logrus.Entry) + + l = l.WithFields(logrus.Fields{ + "event_type": h.GetEvent(), + }) + + l.Debugf("renaming repository from %s to %s", r.GetPreviousName(), r.GetName()) // get any matching hook with the repo's unique webhook ID in the SCM hook, err := database.FromContext(c).GetHookByWebhookID(ctx, h.GetWebhookID()) @@ -988,6 +736,12 @@ func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * if err != nil { return nil, fmt.Errorf("unable to update secret for repo %s/%s: %w", dbR.GetOrg(), dbR.GetName(), err) } + + l.WithFields(logrus.Fields{ + "secret_id": secret.GetID(), + "repo": secret.GetRepo(), + "org": secret.GetOrg(), + }).Info("secret updated") } // get total number of builds associated with repository @@ -996,7 +750,7 @@ func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * return nil, fmt.Errorf("unable to get build count for repo %s: %w", dbR.GetFullName(), err) } - builds := []*library.Build{} + builds := []*types.Build{} page = 1 // capture all builds belonging to repo in database for build := int64(0); build < t; build += 100 { @@ -1020,6 +774,14 @@ func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * if err != nil { return nil, fmt.Errorf("unable to update build for repo %s: %w", dbR.GetFullName(), err) } + + l.WithFields(logrus.Fields{ + "build_id": build.GetID(), + "build": build.GetNumber(), + "org": dbR.GetOrg(), + "repo": dbR.GetName(), + "repo_id": dbR.GetID(), + }).Info("build updated") } // update the repo name information @@ -1033,7 +795,7 @@ func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * // update the repo in the database dbR, err = database.FromContext(c).UpdateRepo(ctx, dbR) if err != nil { - retErr := fmt.Errorf("%s: failed to update repo %s/%s in database", baseErr, dbR.GetOrg(), dbR.GetName()) + retErr := fmt.Errorf("%s: failed to update repo %s/%s", baseErr, dbR.GetOrg(), dbR.GetName()) util.HandleError(c, http.StatusBadRequest, retErr) h.SetStatus(constants.StatusFailure) @@ -1042,13 +804,30 @@ func RenameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * return nil, retErr } + l.WithFields(logrus.Fields{ + "org": dbR.GetOrg(), + "repo": dbR.GetName(), + "repo_id": dbR.GetID(), + }).Infof("repo updated in database (previous name: %s)", r.GetPreviousName()) + return dbR, nil } // gatekeepBuild is a helper function that will set the status of a build to 'pending approval' and // send a status update to the SCM. -func gatekeepBuild(c *gin.Context, b *library.Build, r *library.Repo, u *library.User) error { - logrus.Debugf("fork PR build %s/%d waiting for approval", r.GetFullName(), b.GetNumber()) +func gatekeepBuild(c *gin.Context, b *types.Build, r *types.Repo) error { + l := c.MustGet("logger").(*logrus.Entry) + + l = l.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + "repo_id": r.GetID(), + "build": b.GetNumber(), + "build_id": b.GetID(), + }) + + l.Debug("fork PR build waiting for approval") + b.SetStatus(constants.StatusPendingApproval) _, err := database.FromContext(c).UpdateBuild(c, b) @@ -1056,6 +835,8 @@ func gatekeepBuild(c *gin.Context, b *library.Build, r *library.Repo, u *library return fmt.Errorf("unable to update build for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) } + l.Info("build updated") + // update the build components to pending approval status err = build.UpdateComponentStatuses(c, b, constants.StatusPendingApproval) if err != nil { @@ -1063,9 +844,9 @@ func gatekeepBuild(c *gin.Context, b *library.Build, r *library.Repo, u *library } // send API call to set the status on the commit - err = scm.FromContext(c).Status(c, u, b, r.GetOrg(), r.GetName()) + err = scm.FromContext(c).Status(c, r.GetOwner(), b, r.GetOrg(), r.GetName()) if err != nil { - logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err) + l.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err) } return nil diff --git a/api/worker/create.go b/api/worker/create.go index 07edf3acc..cfac76e54 100644 --- a/api/worker/create.go +++ b/api/worker/create.go @@ -9,19 +9,20 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/workers workers CreateWorker // -// Create a worker for the configured backend +// Create a worker // // --- // produces: @@ -29,7 +30,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing the worker to create +// description: Worker object to create // required: true // schema: // "$ref": "#/definitions/Worker" @@ -39,26 +40,30 @@ import ( // '201': // description: Successfully created the worker and retrieved auth token // schema: -// "$ref": "#definitions/Token" +// "$ref": "#/definitions/Token" // '400': -// description: Unable to create the worker +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to create the worker +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // CreateWorker represents the API handler to -// create a worker in the configured backend. +// create a worker. func CreateWorker(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) ctx := c.Request.Context() // capture body from API request - input := new(library.Worker) + input := new(types.Worker) err := c.Bind(input) if err != nil { @@ -80,15 +85,9 @@ func CreateWorker(c *gin.Context) { input.SetLastCheckedIn(time.Now().Unix()) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - "worker": input.GetHostname(), - }).Infof("creating new worker %s", input.GetHostname()) + l.Debugf("creating new worker %s", input.GetHostname()) - _, err = database.FromContext(c).CreateWorker(ctx, input) + w, err := database.FromContext(c).CreateWorker(ctx, input) if err != nil { retErr := fmt.Errorf("unable to create worker: %w", err) @@ -97,12 +96,18 @@ func CreateWorker(c *gin.Context) { return } + l.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + "worker_id": w.GetID(), + }).Info("worker created") + switch cl.TokenType { // if symmetric token configured, send back symmetric token case constants.ServerWorkerTokenType: if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) tkn.SetToken(secret) + c.JSON(http.StatusCreated, tkn) return diff --git a/api/worker/delete.go b/api/worker/delete.go index cde6c743a..a31ed9d44 100644 --- a/api/worker/delete.go +++ b/api/worker/delete.go @@ -7,16 +7,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/router/middleware/worker" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation DELETE /api/v1/workers/{worker} workers DeleteWorker // -// Delete a worker for the configured backend +// Delete a worker // // --- // produces: @@ -31,29 +31,34 @@ import ( // - ApiKeyAuth: [] // responses: // '200': -// description: Successfully deleted of worker +// description: Successfully deleted worker // schema: // type: string +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Not found +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to delete worker +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// DeleteWorker represents the API handler to remove -// a worker from the configured backend. +// DeleteWorker represents the API handler to remove a worker. func DeleteWorker(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) w := worker.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - "worker": w.GetHostname(), - }).Infof("deleting worker %s", w.GetHostname()) + l.Debugf("deleting worker %s", w.GetHostname()) // send API call to remove the step err := database.FromContext(c).DeleteWorker(ctx, w) diff --git a/api/worker/get.go b/api/worker/get.go index a8a4d6931..23af82124 100644 --- a/api/worker/get.go +++ b/api/worker/get.go @@ -7,16 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/router/middleware/worker" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/workers/{worker} workers GetWorker // -// Retrieve a worker for the configured backend +// Get a worker // // --- // produces: @@ -34,35 +35,47 @@ import ( // description: Successfully retrieved the worker // schema: // "$ref": "#/definitions/Worker" +// '400': +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '404': -// description: Unable to retrieve the worker +// description: Not found +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// GetWorker represents the API handler to capture a -// worker from the configured backend. +// GetWorker represents the API handler to get a worker. func GetWorker(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) w := worker.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - "worker": w.GetHostname(), - }).Infof("reading worker %s", w.GetHostname()) + l.Debugf("reading worker %s", w.GetHostname()) - w, err := database.FromContext(c).GetWorkerForHostname(ctx, w.GetHostname()) - if err != nil { - retErr := fmt.Errorf("unable to get workers: %w", err) + rBs := []*types.Build{} - util.HandleError(c, http.StatusNotFound, retErr) + for _, b := range w.GetRunningBuilds() { + build, err := database.FromContext(c).GetBuild(ctx, b.GetID()) + if err != nil { + retErr := fmt.Errorf("unable to read build %d: %w", b.GetID(), err) + util.HandleError(c, http.StatusInternalServerError, retErr) - return + return + } + + rBs = append(rBs, build) } + w.SetRunningBuilds(rBs) + c.JSON(http.StatusOK, w) } diff --git a/api/worker/list.go b/api/worker/list.go index f402144da..5c0584809 100644 --- a/api/worker/list.go +++ b/api/worker/list.go @@ -9,15 +9,16 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/sirupsen/logrus" ) // swagger:operation GET /api/v1/workers workers ListWorkers // -// Retrieve a list of workers for the configured backend +// Get all workers // // --- // produces: @@ -29,11 +30,11 @@ import ( // type: boolean // - in: query // name: checked_in_before -// description: filter workers that have checked in before a certain time +// description: Filter workers that have checked in before a certain time // type: integer // - in: query // name: checked_in_after -// description: filter workers that have checked in after a certain time +// description: Filter workers that have checked in after a certain time // type: integer // default: 0 // security: @@ -45,24 +46,26 @@ import ( // type: array // items: // "$ref": "#/definitions/Worker" +// '400': +// description: Invalid request payload +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized +// schema: +// "$ref": "#/definitions/Error" // '500': -// description: Unable to retrieve the list of workers +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" -// ListWorkers represents the API handler to capture a -// list of workers from the configured backend. +// ListWorkers represents the API handler to get a list of workers. func ListWorkers(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Info("reading workers") + l.Debug("reading workers") active := c.Query("active") @@ -86,7 +89,7 @@ func ListWorkers(c *gin.Context) { return } - w, err := database.FromContext(c).ListWorkers(ctx, active, before, after) + workers, err := database.FromContext(c).ListWorkers(ctx, active, before, after) if err != nil { retErr := fmt.Errorf("unable to get workers: %w", err) @@ -95,5 +98,23 @@ func ListWorkers(c *gin.Context) { return } - c.JSON(http.StatusOK, w) + for _, w := range workers { + rBs := []*types.Build{} + + for _, b := range w.GetRunningBuilds() { + build, err := database.FromContext(c).GetBuild(ctx, b.GetID()) + if err != nil { + retErr := fmt.Errorf("unable to read build %d: %w", b.GetID(), err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + rBs = append(rBs, build) + } + + w.SetRunningBuilds(rBs) + } + + c.JSON(http.StatusOK, workers) } diff --git a/api/worker/refresh.go b/api/worker/refresh.go index 15826f6d6..51098efe2 100644 --- a/api/worker/refresh.go +++ b/api/worker/refresh.go @@ -9,6 +9,8 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/claims" @@ -16,12 +18,11 @@ import ( "github.com/go-vela/server/util" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation POST /api/v1/workers/{worker}/refresh workers RefreshWorkerAuth // -// Refresh authorization token for worker +// Refresh authorization token for a worker // // --- // produces: @@ -40,15 +41,19 @@ import ( // schema: // "$ref": "#/definitions/Token" // '400': -// description: Unable to refresh worker auth +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to refresh worker auth +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to refresh worker auth +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" @@ -56,6 +61,7 @@ import ( // refresh the auth token for a worker. func Refresh(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) w := worker.Retrieve(c) cl := claims.Retrieve(c) ctx := c.Request.Context() @@ -64,10 +70,7 @@ func Refresh(c *gin.Context) { if !strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType) && !strings.EqualFold(cl.Subject, w.GetHostname()) { retErr := fmt.Errorf("unable to refresh worker auth: claims subject %s does not match worker hostname %s", cl.Subject, w.GetHostname()) - logrus.WithFields(logrus.Fields{ - "subject": cl.Subject, - "worker": w.GetHostname(), - }).Warnf("attempted refresh of worker %s using token from worker %s", w.GetHostname(), cl.Subject) + l.Warnf("attempted refresh of worker %s using token from worker %s", w.GetHostname(), cl.Subject) util.HandleError(c, http.StatusBadRequest, retErr) @@ -87,12 +90,9 @@ func Refresh(c *gin.Context) { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "worker": w.GetHostname(), - }).Infof("refreshing worker %s authentication", w.GetHostname()) + l.Info("worker updated - check-in time updated") + + l.Debugf("refreshing worker %s authentication", w.GetHostname()) switch cl.TokenType { // if symmetric token configured, send back symmetric token @@ -100,6 +100,7 @@ func Refresh(c *gin.Context) { if secret, ok := c.Value("secret").(string); ok { tkn := new(library.Token) tkn.SetToken(secret) + c.JSON(http.StatusOK, tkn) return diff --git a/api/worker/update.go b/api/worker/update.go index d95ddf7af..199928349 100644 --- a/api/worker/update.go +++ b/api/worker/update.go @@ -7,17 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/router/middleware/worker" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // swagger:operation PUT /api/v1/workers/{worker} workers UpdateWorker // -// Update a worker for the configured backend +// Update a worker // // --- // produces: @@ -25,7 +25,7 @@ import ( // parameters: // - in: body // name: body -// description: Payload containing the worker to update +// description: The worker object with the fields to be updated // required: true // schema: // "$ref": "#/definitions/Worker" @@ -42,36 +42,34 @@ import ( // schema: // "$ref": "#/definitions/Worker" // '400': -// description: Unable to update the worker +// description: Invalid request payload or path +// schema: +// "$ref": "#/definitions/Error" +// '401': +// description: Unauthorized // schema: // "$ref": "#/definitions/Error" // '404': -// description: Unable to update the worker +// description: Not found // schema: // "$ref": "#/definitions/Error" // '500': -// description: Unable to update the worker +// description: Unexpected server error // schema: // "$ref": "#/definitions/Error" // UpdateWorker represents the API handler to -// update a worker in the configured backend. +// update a worker. func UpdateWorker(c *gin.Context) { // capture middleware values - u := user.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) w := worker.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - "worker": w.GetHostname(), - }).Infof("updating worker %s", w.GetHostname()) + l.Debugf("updating worker %s", w.GetHostname()) // capture body from API request - input := new(library.Worker) + input := new(types.Worker) err := c.Bind(input) if err != nil { @@ -97,9 +95,9 @@ func UpdateWorker(c *gin.Context) { w.SetActive(input.GetActive()) } - if input.RunningBuildIDs != nil { + if input.RunningBuilds != nil { // update runningBuildIDs if set - w.SetRunningBuildIDs(input.GetRunningBuildIDs()) + w.SetRunningBuilds(input.GetRunningBuilds()) } if len(input.GetStatus()) > 0 { diff --git a/cmd/vela-server/compiler.go b/cmd/vela-server/compiler.go deleted file mode 100644 index 4e85f046a..000000000 --- a/cmd/vela-server/compiler.go +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "github.com/go-vela/server/compiler" - "github.com/go-vela/server/compiler/native" - - "github.com/go-vela/types/constants" - - "github.com/sirupsen/logrus" - - "github.com/urfave/cli/v2" -) - -// helper function to setup the queue from the CLI arguments. -func setupCompiler(c *cli.Context) (compiler.Engine, error) { - logrus.Debug("Creating queue client from CLI configuration") - return setupCompilerNative(c) -} - -// helper function to setup the Kafka queue from the CLI arguments. -func setupCompilerNative(c *cli.Context) (compiler.Engine, error) { - logrus.Tracef("Creating %s compiler client from CLI configuration", constants.DriverKafka) - return native.New(c) -} diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go index 715c821a2..7dd589f07 100644 --- a/cmd/vela-server/main.go +++ b/cmd/vela-server/main.go @@ -8,17 +8,17 @@ import ( "os" "time" - "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + + _ "github.com/joho/godotenv/autoload" "github.com/go-vela/server/database" "github.com/go-vela/server/queue" "github.com/go-vela/server/scm" "github.com/go-vela/server/secret" "github.com/go-vela/server/version" - "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" - - _ "github.com/joho/godotenv/autoload" + "github.com/go-vela/types/constants" ) //nolint:funlen // ignore line length @@ -79,6 +79,12 @@ func main() { Name: "vela-secret", Usage: "secret used for server <-> agent communication", }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_PLATFORM_SETTINGS_REFRESH_INTERVAL", "VELA_SETTINGS_REFRESH_INTERVAL"}, + Name: "settings-refresh-interval", + Usage: "interval at which platform settings will be refreshed", + Value: 5 * time.Second, + }, &cli.StringFlag{ EnvVars: []string{"VELA_SERVER_PRIVATE_KEY"}, Name: "vela-server-private-key", @@ -175,6 +181,12 @@ func main() { Usage: "sets the duration of the worker register token", Value: 1 * time.Minute, }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_OPEN_ID_TOKEN_DURATION", "OPEN_ID_TOKEN_DURATION"}, + Name: "id-token-duration", + Usage: "sets the duration of an OpenID token requested during a build (should be short)", + Value: 5 * time.Minute, + }, // Compiler Flags &cli.BoolFlag{ EnvVars: []string{"VELA_COMPILER_GITHUB", "COMPILER_GITHUB"}, diff --git a/cmd/vela-server/metadata.go b/cmd/vela-server/metadata.go index 88a29b23a..11da44af4 100644 --- a/cmd/vela-server/metadata.go +++ b/cmd/vela-server/metadata.go @@ -5,18 +5,17 @@ package main import ( "net/url" - "github.com/go-vela/types" - "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" + + "github.com/go-vela/server/internal" ) // helper function to setup the metadata from the CLI arguments. -func setupMetadata(c *cli.Context) (*types.Metadata, error) { - logrus.Debug("Creating metadata from CLI configuration") +func setupMetadata(c *cli.Context) (*internal.Metadata, error) { + logrus.Debug("creating metadata from CLI configuration") - m := new(types.Metadata) + m := new(internal.Metadata) database, err := metadataDatabase(c) if err != nil { @@ -50,45 +49,45 @@ func setupMetadata(c *cli.Context) (*types.Metadata, error) { } // helper function to capture the database metadata from the CLI arguments. -func metadataDatabase(c *cli.Context) (*types.Database, error) { - logrus.Trace("Creating database metadata from CLI configuration") +func metadataDatabase(c *cli.Context) (*internal.Database, error) { + logrus.Trace("creating database metadata from CLI configuration") u, err := url.Parse(c.String("database.addr")) if err != nil { return nil, err } - return &types.Database{ + return &internal.Database{ Driver: c.String("database.driver"), Host: u.Host, }, nil } // helper function to capture the queue metadata from the CLI arguments. -func metadataQueue(c *cli.Context) (*types.Queue, error) { - logrus.Trace("Creating queue metadata from CLI configuration") +func metadataQueue(c *cli.Context) (*internal.Queue, error) { + logrus.Trace("creating queue metadata from CLI configuration") u, err := url.Parse(c.String("queue.addr")) if err != nil { return nil, err } - return &types.Queue{ + return &internal.Queue{ Driver: c.String("queue.driver"), Host: u.Host, }, nil } // helper function to capture the source metadata from the CLI arguments. -func metadataSource(c *cli.Context) (*types.Source, error) { - logrus.Trace("Creating source metadata from CLI configuration") +func metadataSource(c *cli.Context) (*internal.Source, error) { + logrus.Trace("creating source metadata from CLI configuration") u, err := url.Parse(c.String("scm.addr")) if err != nil { return nil, err } - return &types.Source{ + return &internal.Source{ Driver: c.String("scm.driver"), Host: u.Host, }, nil @@ -97,10 +96,10 @@ func metadataSource(c *cli.Context) (*types.Source, error) { // helper function to capture the Vela metadata from the CLI arguments. // //nolint:unparam // ignore unparam for now -func metadataVela(c *cli.Context) (*types.Vela, error) { - logrus.Trace("Creating Vela metadata from CLI configuration") +func metadataVela(c *cli.Context) (*internal.Vela, error) { + logrus.Trace("creating Vela metadata from CLI configuration") - vela := new(types.Vela) + vela := new(internal.Vela) if len(c.String("server-addr")) > 0 { vela.Address = c.String("server-addr") diff --git a/cmd/vela-server/queue.go b/cmd/vela-server/queue.go deleted file mode 100644 index 8b8767e56..000000000 --- a/cmd/vela-server/queue.go +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "github.com/go-vela/server/queue" - - "github.com/sirupsen/logrus" - - "github.com/urfave/cli/v2" -) - -// helper function to setup the queue from the CLI arguments. -func setupQueue(c *cli.Context) (queue.Service, error) { - logrus.Debug("Creating queue client from CLI configuration") - - // queue configuration - _setup := &queue.Setup{ - Driver: c.String("queue.driver"), - Address: c.String("queue.addr"), - Cluster: c.Bool("queue.cluster"), - Routes: c.StringSlice("queue.routes"), - Timeout: c.Duration("queue.pop.timeout"), - PrivateKey: c.String("queue.private-key"), - PublicKey: c.String("queue.public-key"), - } - - // setup the queue - // - // https://pkg.go.dev/github.com/go-vela/server/queue?tab=doc#New - return queue.New(_setup) -} diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index f31de1948..422dcd710 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -9,19 +9,19 @@ import ( "time" "github.com/adhocore/gronx" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" + "github.com/go-vela/server/api/build" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/settings" "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/queue" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" - "github.com/sirupsen/logrus" - - "k8s.io/apimachinery/pkg/util/wait" ) const ( @@ -30,7 +30,7 @@ const ( scheduleWait = "waiting to trigger build for schedule" ) -func processSchedules(ctx context.Context, start time.Time, compiler compiler.Engine, database database.Interface, metadata *types.Metadata, queue queue.Service, scm scm.Service, allowList []string) error { +func processSchedules(ctx context.Context, start time.Time, settings *settings.Platform, compiler compiler.Engine, database database.Interface, metadata *internal.Metadata, queue queue.Service, scm scm.Service) error { logrus.Infof("processing active schedules to create builds") // send API call to capture the list of active schedules @@ -55,7 +55,7 @@ func processSchedules(ctx context.Context, start time.Time, compiler compiler.En // amount of time to get to the end of the list. schedule, err := database.GetSchedule(ctx, s.GetID()) if err != nil { - logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName()) + handleError(ctx, database, err, schedule) continue } @@ -75,7 +75,7 @@ func processSchedules(ctx context.Context, start time.Time, compiler compiler.En // i.e. if it's 4:02 on five minute intervals, this will be 4:00 prevTime, err := gronx.PrevTick(schedule.GetEntry(), true) if err != nil { - logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName()) + handleError(ctx, database, err, schedule) continue } @@ -85,7 +85,7 @@ func processSchedules(ctx context.Context, start time.Time, compiler compiler.En // i.e. if it's 4:02 on five minute intervals, this will be 4:05 nextTime, err := gronx.NextTickAfter(schedule.GetEntry(), scheduled, true) if err != nil { - logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName()) + handleError(ctx, database, err, schedule) continue } @@ -117,33 +117,56 @@ func processSchedules(ctx context.Context, start time.Time, compiler compiler.En // send API call to update schedule for ensuring scheduled_at field is set _, err = database.UpdateSchedule(ctx, schedule, false) if err != nil { - logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName()) + handleError(ctx, database, err, schedule) continue } + logrus.WithFields(logrus.Fields{ + "schedule": schedule.GetName(), + "schedule_id": schedule.GetID(), + }).Info("schedule updated - scheduled at set") + // process the schedule and trigger a new build - err = processSchedule(ctx, schedule, compiler, database, metadata, queue, scm, allowList) + err = processSchedule(ctx, schedule, settings, compiler, database, metadata, queue, scm) if err != nil { - logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName()) + handleError(ctx, database, err, schedule) continue } + + // successfully scheduled build so clear error message, if not already cleared + if schedule.GetError() != "" { + schedule.SetError("") + + // send API call to update schedule with the error message field cleared + _, err = database.UpdateSchedule(ctx, schedule, true) + if err != nil { + handleError(ctx, database, err, schedule) + + continue + } + + logrus.WithFields(logrus.Fields{ + "schedule": schedule.GetName(), + "schedule_id": schedule.GetID(), + }).Info("schedule updated - error message cleared") + } } return nil } -//nolint:funlen // ignore function length and number of statements -func processSchedule(ctx context.Context, s *library.Schedule, compiler compiler.Engine, database database.Interface, metadata *types.Metadata, queue queue.Service, scm scm.Service, allowList []string) error { +// processSchedule will, given a schedule, process it and trigger a new build. +func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Platform, compiler compiler.Engine, database database.Interface, metadata *internal.Metadata, queue queue.Service, scm scm.Service) error { // send API call to capture the repo for the schedule - r, err := database.GetRepo(ctx, s.GetRepoID()) + r, err := database.GetRepo(ctx, s.GetRepo().GetID()) if err != nil { return fmt.Errorf("unable to fetch repo: %w", err) } // ensure repo has not been removed from allow list - if !util.CheckAllowlist(r, allowList) { + if !util.CheckAllowlist(r, settings.GetScheduleAllowlist()) { return fmt.Errorf("skipping schedule: repo %s no longer on allow list", r.GetFullName()) } @@ -154,273 +177,83 @@ func processSchedule(ctx context.Context, s *library.Schedule, compiler compiler return fmt.Errorf("repo %s is not active", r.GetFullName()) } - // check if the repo has a valid owner - if r.GetUserID() == 0 { - return fmt.Errorf("repo %s does not have a valid owner", r.GetFullName()) - } - - // send API call to capture the owner for the repo - u, err := database.GetUser(ctx, r.GetUserID()) - if err != nil { - return fmt.Errorf("unable to get owner for repo %s: %w", r.GetFullName(), err) - } - - // send API call to confirm repo owner has at least write access to repo - _, err = scm.RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) - if err != nil { - return fmt.Errorf("%s does not have at least write access for repo %s", u.GetName(), r.GetFullName()) - } - - // create SQL filters for querying pending and running builds for repo - filters := map[string]interface{}{ - "status": []string{constants.StatusPending, constants.StatusRunning}, - } - - // send API call to capture the number of pending or running builds for the repo - builds, err := database.CountBuildsForRepo(ctx, r, filters) - if err != nil { - return fmt.Errorf("unable to get count of builds for repo %s: %w", r.GetFullName(), err) - } - - // check if the number of pending and running builds exceeds the limit for the repo - if builds >= r.GetBuildLimit() { - return fmt.Errorf("repo %s has excceded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit()) - } - - // send API call to capture the commit sha for the branch - _, commit, err := scm.GetBranch(ctx, u, r, s.GetBranch()) - if err != nil { - return fmt.Errorf("failed to get commit for repo %s on %s branch: %w", r.GetFullName(), r.GetBranch(), err) - } - url := strings.TrimSuffix(r.GetClone(), ".git") - b := new(library.Build) + b := new(api.Build) b.SetAuthor(s.GetCreatedBy()) b.SetBranch(s.GetBranch()) b.SetClone(r.GetClone()) - b.SetCommit(commit) b.SetDeploy(s.GetName()) b.SetEvent(constants.EventSchedule) b.SetMessage(fmt.Sprintf("triggered for %s schedule with %s entry", s.GetName(), s.GetEntry())) b.SetRef(fmt.Sprintf("refs/heads/%s", b.GetBranch())) - b.SetRepoID(r.GetID()) + b.SetRepo(r) b.SetSender(s.GetUpdatedBy()) - b.SetSource(fmt.Sprintf("%s/tree/%s", url, b.GetBranch())) - b.SetStatus(constants.StatusPending) - b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventSchedule, url)) - - // populate the build link if a web address is provided - if len(metadata.Vela.WebAddress) > 0 { - b.SetLink(fmt.Sprintf("%s/%s/%d", metadata.Vela.WebAddress, r.GetFullName(), b.GetNumber())) - } - - var ( - // variable to store the raw pipeline configuration - config []byte - // variable to store executable pipeline - p *pipeline.Build - // variable to store pipeline configuration - pipeline *library.Pipeline - // variable to control number of times to retry processing pipeline - retryLimit = 5 - // variable to store the pipeline type for the repository - pipelineType = r.GetPipelineType() - ) - - // implement a loop to process asynchronous operations with a retry limit - // - // Some operations taken during this workflow can lead to race conditions failing to successfully process - // the request. This logic ensures we attempt our best efforts to handle these cases gracefully. - for i := 0; i < retryLimit; i++ { - logrus.Debugf("compilation loop - attempt %d", i+1) - // check if we're on the first iteration of the loop - if i > 0 { - // incrementally sleep in between retries - time.Sleep(time.Duration(i) * time.Second) - } - - // send API call to attempt to capture the pipeline - pipeline, err = database.GetPipelineForRepo(ctx, b.GetCommit(), r) - if err != nil { // assume the pipeline doesn't exist in the database yet - // send API call to capture the pipeline configuration file - config, err = scm.ConfigBackoff(ctx, u, r, b.GetCommit()) - if err != nil { - return fmt.Errorf("unable to get pipeline config for %s/%s: %w", r.GetFullName(), b.GetCommit(), err) - } - } else { - config = pipeline.GetData() - } - - // send API call to capture repo for the counter (grabbing repo again to ensure counter is correct) - r, err = database.GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) - if err != nil { - err = fmt.Errorf("unable to get repo %s: %w", r.GetFullName(), err) - - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(err).Warningf("retrying #%d", i+1) - - // continue to the next iteration of the loop - continue - } - - return err - } - // set the build numbers based off repo counter - b.SetNumber(r.GetCounter() + 1) - // set the parent equal to the current repo counter - b.SetParent(r.GetCounter()) - // check if the parent is set to 0 - if b.GetParent() == 0 { - // parent should be "1" if it's the first build ran - b.SetParent(1) - } - - r.SetCounter(r.GetCounter() + 1) - - // set the build link if a web address is provided - if len(metadata.Vela.WebAddress) > 0 { - b.SetLink(fmt.Sprintf("%s/%s/%d", metadata.Vela.WebAddress, r.GetFullName(), b.GetNumber())) - } - - // ensure we use the expected pipeline type when compiling - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - if len(pipeline.GetType()) > 0 { - r.SetPipelineType(pipeline.GetType()) - } - - var compiled *library.Pipeline - // parse and compile the pipeline configuration file - p, compiled, err = compiler. - Duplicate(). - WithBuild(b). - WithCommit(b.GetCommit()). - WithMetadata(metadata). - WithRepo(r). - WithUser(u). - Compile(config) - if err != nil { - return fmt.Errorf("unable to compile pipeline config for %s/%s: %w", r.GetFullName(), b.GetCommit(), err) - } - - // reset the pipeline type for the repo - // - // The pipeline type for a repo can change at any time which can break compiling - // existing pipelines in the system for that repo. To account for this, we update - // the repo pipeline type to match what was defined for the existing pipeline - // before compiling. After we're done compiling, we reset the pipeline type. - r.SetPipelineType(pipelineType) - - // skip the build if only the init or clone steps are found - skip := build.SkipEmptyBuild(p) - if skip != "" { - return nil - } - - // check if the pipeline did not already exist in the database - if pipeline == nil { - pipeline = compiled - pipeline.SetRepoID(r.GetID()) - pipeline.SetCommit(b.GetCommit()) - pipeline.SetRef(b.GetRef()) - - // send API call to create the pipeline - pipeline, err = database.CreatePipeline(ctx, pipeline) - if err != nil { - err = fmt.Errorf("failed to create pipeline for %s: %w", r.GetFullName(), err) - - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(err).Warningf("retrying #%d", i+1) - - // continue to the next iteration of the loop - continue - } - - return err - } - } - - b.SetPipelineID(pipeline.GetID()) - - // create the objects from the pipeline in the database - // TODO: - // - if a build gets created and something else fails midway, - // the next loop will attempt to create the same build, - // using the same Number and thus create a constraint - // conflict; consider deleting the partially created - // build object in the database - err = build.PlanBuild(ctx, database, scm, p, b, r) - if err != nil { - // check if the retry limit has been exceeded - if i < retryLimit-1 { - logrus.WithError(err).Warningf("retrying #%d", i+1) - - // reset fields set by cleanBuild for retry - b.SetError("") - b.SetStatus(constants.StatusPending) - b.SetFinished(0) - - // continue to the next iteration of the loop - continue - } - - return err - } - - // break the loop because everything was successful - break - } // end of retry loop - - // send API call to update repo for ensuring counter is incremented - r, err = database.UpdateRepo(ctx, r) - if err != nil { - return fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err) - } - - // send API call to capture the triggered build - b, err = database.GetBuildForRepo(ctx, r, b.GetNumber()) + // fetch scm user id + senderID, err := scm.GetUserID(ctx, s.GetUpdatedBy(), r.GetOwner().GetToken()) if err != nil { - return fmt.Errorf("unable to get new build %s/%d: %w", r.GetFullName(), b.GetNumber(), err) + return fmt.Errorf("unable to get SCM user id for %s: %w", s.GetUpdatedBy(), err) } - // determine queue route - route, err := queue.Route(&p.Worker) - if err != nil { - logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) + b.SetSenderSCMID(senderID) - // error out the build - build.CleanBuild(ctx, database, b, nil, nil, err) + b.SetSource(fmt.Sprintf("%s/tree/%s", url, b.GetBranch())) + b.SetStatus(constants.StatusPending) + b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventSchedule, url)) - return err + // schedule form + config := build.CompileAndPublishConfig{ + Build: b, + Metadata: metadata, + BaseErr: "unable to schedule build", + Source: "schedule", + Retries: 1, } - // temporarily set host to the route before it gets picked up by a worker - b.SetHost(route) + _, item, _, err := build.CompileAndPublish( + ctx, + config, + database, + scm, + compiler, + queue, + ) - err = build.PublishBuildExecutable(ctx, database, p, b) if err != nil { - retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) - - return retErr + return err } // publish the build to the queue - go build.PublishToQueue( + go build.Enqueue( ctx, queue, database, - b, - r, - u, - route, + item, + item.Build.GetHost(), ) return nil } + +func handleError(ctx context.Context, database database.Interface, err error, schedule *api.Schedule) { + // log the error message + logrus.WithError(err).Warnf("%s %s: %s", scheduleErr, schedule.GetName(), err.Error()) + + // format the error message + msg := fmt.Sprintf("%s %s: %s", scheduleErr, schedule.GetName(), err.Error()) + + // update the message field with the error message + schedule.SetError(msg) + + // send API call to update schedule to ensure message field is set + _, err = database.UpdateSchedule(ctx, schedule, true) + if err != nil { + logrus.WithError(err).Warnf("%s %s: %s", scheduleErr, schedule.GetName(), err.Error()) + } + + logrus.WithFields(logrus.Fields{ + "schedule": schedule.GetName(), + "schedule_id": schedule.GetID(), + }).Info("schedule updated - error message set") +} diff --git a/cmd/vela-server/scm.go b/cmd/vela-server/scm.go index d324bdb18..46115248f 100644 --- a/cmd/vela-server/scm.go +++ b/cmd/vela-server/scm.go @@ -3,15 +3,15 @@ package main import ( - "github.com/go-vela/server/scm" "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" + + "github.com/go-vela/server/scm" ) // helper function to setup the scm from the CLI arguments. func setupSCM(c *cli.Context) (scm.Service, error) { - logrus.Debug("Creating scm client from CLI configuration") + logrus.Debug("creating scm client from CLI configuration") // scm configuration _setup := &scm.Setup{ diff --git a/cmd/vela-server/secret.go b/cmd/vela-server/secret.go index 1b0e22ef8..2ca65615a 100644 --- a/cmd/vela-server/secret.go +++ b/cmd/vela-server/secret.go @@ -3,18 +3,17 @@ package main import ( + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "github.com/go-vela/server/database" "github.com/go-vela/server/secret" "github.com/go-vela/types/constants" - - "github.com/sirupsen/logrus" - - "github.com/urfave/cli/v2" ) // helper function to setup the secrets engines from the CLI arguments. func setupSecrets(c *cli.Context, d database.Interface) (map[string]secret.Service, error) { - logrus.Debug("Creating secret clients from CLI configuration") + logrus.Debug("creating secret clients from CLI configuration") secrets := make(map[string]secret.Service) diff --git a/cmd/vela-server/server.go b/cmd/vela-server/server.go index 1910c2d70..43b7a8f3c 100644 --- a/cmd/vela-server/server.go +++ b/cmd/vela-server/server.go @@ -4,6 +4,7 @@ package main import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -13,16 +14,21 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" - "github.com/go-vela/server/router" - "github.com/go-vela/server/router/middleware" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" "golang.org/x/sync/errgroup" - + "gorm.io/gorm" "k8s.io/apimachinery/pkg/util/wait" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/compiler/native" + "github.com/go-vela/server/database" + "github.com/go-vela/server/queue" + "github.com/go-vela/server/router" + "github.com/go-vela/server/router/middleware" ) +//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity func server(c *cli.Context) error { // set log formatter switch c.String("log-formatter") { @@ -67,7 +73,7 @@ func server(c *cli.Context) error { logrus.SetLevel(logrus.PanicLevel) } - compiler, err := setupCompiler(c) + compiler, err := native.FromCLIContext(c) if err != nil { return err } @@ -77,7 +83,7 @@ func server(c *cli.Context) error { return err } - queue, err := setupQueue(c) + queue, err := queue.FromCLIContext(c) if err != nil { return err } @@ -97,12 +103,65 @@ func server(c *cli.Context) error { return err } + tm, err := setupTokenManager(c, database) + if err != nil { + return err + } + + jitter := wait.Jitter(5*time.Second, 2.0) + + logrus.Infof("retrieving initial platform settings after %v delay", jitter) + + time.Sleep(jitter) + + ps, err := database.GetSettings(context.Background()) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + // platform settings record does not exist + if err != nil { + logrus.Info("creating initial platform settings") + + // create initial settings record + ps = settings.FromCLIContext(c) + + // singleton record ID should always be 1 + ps.SetID(1) + + ps.SetCreatedAt(time.Now().UTC().Unix()) + ps.SetUpdatedAt(time.Now().UTC().Unix()) + ps.SetUpdatedBy("vela-server") + + // read in defaults supplied from the cli runtime + compilerSettings := compiler.GetSettings() + ps.SetCompiler(compilerSettings) + + queueSettings := queue.GetSettings() + ps.SetQueue(queueSettings) + + // create the settings record in the database + _, err = database.CreateSettings(context.Background(), ps) + if err != nil { + return err + } + + logrus.Info("initial platform settings created") + } + + // update any internal settings, this occurs in middleware + // to keep settings refreshed for each request + queue.SetSettings(ps) + compiler.SetSettings(ps) + router := router.Load( + middleware.CLI(c), + middleware.Settings(ps), middleware.Compiler(compiler), middleware.Database(database), middleware.Logger(logrus.StandardLogger(), time.RFC3339), middleware.Metadata(metadata), - middleware.TokenManager(setupTokenManager(c)), + middleware.TokenManager(tm), middleware.Queue(queue), middleware.RequestVersion, middleware.Secret(c.String("vela-secret")), @@ -111,7 +170,6 @@ func server(c *cli.Context) error { middleware.QueueSigningPrivateKey(c.String("queue.private-key")), middleware.QueueSigningPublicKey(c.String("queue.public-key")), middleware.QueueAddress(c.String("queue.addr")), - middleware.Allowlist(c.StringSlice("vela-repo-allowlist")), middleware.DefaultBuildLimit(c.Int64("default-build-limit")), middleware.DefaultTimeout(c.Int64("default-build-timeout")), middleware.MaxBuildLimit(c.Int64("max-build-limit")), @@ -121,7 +179,6 @@ func server(c *cli.Context) error { middleware.DefaultRepoEvents(c.StringSlice("default-repo-events")), middleware.DefaultRepoEventsMask(c.Int64("default-repo-events-mask")), middleware.DefaultRepoApproveBuild(c.String("default-repo-approve-build")), - middleware.AllowlistSchedule(c.StringSlice("vela-schedule-allowlist")), middleware.ScheduleFrequency(c.Duration("schedule-minimum-frequency")), ) @@ -158,26 +215,52 @@ func server(c *cli.Context) error { select { case sig := <-signalChannel: logrus.Infof("received signal: %s", sig) + err := srv.Shutdown(ctx) if err != nil { logrus.Error(err) } + done() case <-gctx.Done(): logrus.Info("closing signal goroutine") + err := srv.Shutdown(ctx) if err != nil { logrus.Error(err) } + return gctx.Err() } return nil }) + // spawn goroutine for refreshing settings + g.Go(func() error { + interval := c.Duration("settings-refresh-interval") + + logrus.Infof("refreshing platform settings every %v", interval) + + for { + time.Sleep(interval) + + newSettings, err := database.GetSettings(context.Background()) + if err != nil { + logrus.WithError(err).Warn("unable to refresh platform settings") + + continue + } + + // update the internal fields for the shared settings record + ps.FromSettings(newSettings) + } + }) + // spawn goroutine for starting the server g.Go(func() error { logrus.Infof("starting server on %s", addr.Host) + err = srv.ListenAndServe() if err != nil { // log a message indicating the failure of the server @@ -190,6 +273,7 @@ func server(c *cli.Context) error { // spawn goroutine for starting the scheduler g.Go(func() error { logrus.Info("starting scheduler") + for { // track the starting time for when the server begins processing schedules // @@ -214,7 +298,11 @@ func server(c *cli.Context) error { // sleep for a duration of time before processing schedules time.Sleep(jitter) - err = processSchedules(ctx, start, compiler, database, metadata, queue, scm, c.StringSlice("vela-schedule-allowlist")) + // update internal settings updated through refresh + compiler.SetSettings(ps) + queue.SetSettings(ps) + + err = processSchedules(ctx, start, ps, compiler, database, metadata, queue, scm) if err != nil { logrus.WithError(err).Warn("unable to process schedules") } else { diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go index 39b1600a6..5636d0535 100644 --- a/cmd/vela-server/token.go +++ b/cmd/vela-server/token.go @@ -3,28 +3,35 @@ package main import ( - "github.com/golang-jwt/jwt/v5" + "fmt" "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" + "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" ) // helper function to setup the tokenmanager from the CLI arguments. -func setupTokenManager(c *cli.Context) *token.Manager { - logrus.Debug("Creating token manager from CLI configuration") +func setupTokenManager(c *cli.Context, db database.Interface) (*token.Manager, error) { + logrus.Debug("creating token manager from CLI configuration") tm := &token.Manager{ - PrivateKey: c.String("vela-server-private-key"), - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: c.String("vela-server-private-key"), UserAccessTokenDuration: c.Duration("user-access-token-duration"), UserRefreshTokenDuration: c.Duration("user-refresh-token-duration"), BuildTokenBufferDuration: c.Duration("build-token-buffer-duration"), WorkerAuthTokenDuration: c.Duration("worker-auth-token-duration"), WorkerRegisterTokenDuration: c.Duration("worker-register-token-duration"), + IDTokenDuration: c.Duration("id-token-duration"), + Issuer: fmt.Sprintf("%s/_services/token", c.String("server-addr")), + } + + // generate a new RSA key pair + err := tm.GenerateRSA(c.Context, db) + if err != nil { + return nil, err } - return tm + return tm, nil } diff --git a/cmd/vela-server/validate.go b/cmd/vela-server/validate.go index 26b8eda89..605e0707c 100644 --- a/cmd/vela-server/validate.go +++ b/cmd/vela-server/validate.go @@ -6,14 +6,14 @@ import ( "fmt" "strings" - "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/constants" ) func validate(c *cli.Context) error { - logrus.Debug("Validating CLI configuration") + logrus.Debug("validating CLI configuration") // validate core configuration err := validateCore(c) @@ -32,7 +32,7 @@ func validate(c *cli.Context) error { // helper function to validate the core CLI configuration. func validateCore(c *cli.Context) error { - logrus.Trace("Validating core CLI configuration") + logrus.Trace("validating core CLI configuration") if len(c.String("server-addr")) == 0 { return fmt.Errorf("server-addr (VELA_ADDR or VELA_HOST) flag is not properly configured") @@ -110,7 +110,7 @@ func validateCore(c *cli.Context) error { // helper function to validate the compiler CLI configuration. func validateCompiler(c *cli.Context) error { - logrus.Trace("Validating compiler CLI configuration") + logrus.Trace("validating compiler CLI configuration") if c.Bool("github-driver") { if len(c.String("github-url")) == 0 { diff --git a/compiler/engine.go b/compiler/engine.go index 69812a519..59dca1469 100644 --- a/compiler/engine.go +++ b/compiler/engine.go @@ -3,7 +3,9 @@ package compiler import ( - "github.com/go-vela/types" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/internal" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/go-vela/types/raw" @@ -23,7 +25,7 @@ type Engine interface { // CompileLite defines a function that produces an light executable // representation of a pipeline from an object. This calls // Parse internally to convert the object to a yaml configuration. - CompileLite(interface{}, bool) (*yaml.Build, *library.Pipeline, error) + CompileLite(interface{}, *pipeline.RuleData, bool) (*yaml.Build, *library.Pipeline, error) // Duplicate defines a function that // creates a clone of the Engine. @@ -115,7 +117,7 @@ type Engine interface { // WithBuild defines a function that sets // the library build type in the Engine. - WithBuild(*library.Build) Engine + WithBuild(*api.Build) Engine // WithComment defines a function that sets // the comment in the Engine. WithComment(string) Engine @@ -133,14 +135,23 @@ type Engine interface { WithLocalTemplates([]string) Engine // WithMetadata defines a function that sets // the compiler Metadata type in the Engine. - WithMetadata(*types.Metadata) Engine + WithMetadata(*internal.Metadata) Engine // WithRepo defines a function that sets // the library repo type in the Engine. - WithRepo(*library.Repo) Engine + WithRepo(*api.Repo) Engine // WithUser defines a function that sets // the library user type in the Engine. - WithUser(*library.User) Engine - // WithUser defines a function that sets + WithUser(*api.User) Engine + // WithLabel defines a function that sets + // the label(s) in the Engine. + WithLabels([]string) Engine + // WithPrivateGitHub defines a function that sets // the private github client in the Engine. WithPrivateGitHub(string, string) Engine + // GetSettings defines a function that returns new api settings + // with the compiler Engine fields filled. + GetSettings() settings.Compiler + // SetSettings defines a function that takes api settings + // and updates the compiler Engine. + SetSettings(*settings.Platform) } diff --git a/compiler/native/clone.go b/compiler/native/clone.go index 9075292f7..85016cfc6 100644 --- a/compiler/native/clone.go +++ b/compiler/native/clone.go @@ -30,7 +30,7 @@ func (c *client) CloneStage(p *yaml.Build) (*yaml.Build, error) { Steps: yaml.StepSlice{ &yaml.Step{ Detach: false, - Image: c.CloneImage, + Image: c.GetCloneImage(), Name: cloneStepName, Privileged: false, Pull: constants.PullNotPresent, @@ -63,7 +63,7 @@ func (c *client) CloneStep(p *yaml.Build) (*yaml.Build, error) { // create new clone step clone := &yaml.Step{ Detach: false, - Image: c.CloneImage, + Image: c.GetCloneImage(), Name: cloneStepName, Privileged: false, Pull: constants.PullNotPresent, diff --git a/compiler/native/clone_test.go b/compiler/native/clone_test.go index 662576d53..304f703b2 100644 --- a/compiler/native/clone_test.go +++ b/compiler/native/clone_test.go @@ -7,8 +7,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/yaml" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/yaml" ) const defaultCloneImage = "target/vela-git:latest" @@ -83,7 +84,7 @@ func TestNative_CloneStage(t *testing.T) { // run tests for _, test := range tests { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("unable to create new compiler: %v", err) } @@ -166,7 +167,7 @@ func TestNative_CloneStep(t *testing.T) { // run tests for _, test := range tests { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } diff --git a/compiler/native/compile.go b/compiler/native/compile.go index 9768d90bb..0c739c310 100644 --- a/compiler/native/compile.go +++ b/compiler/native/compile.go @@ -12,16 +12,16 @@ import ( "strings" "time" - "github.com/go-vela/types/constants" - yml "github.com/buildkite/yaml" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-retryablehttp" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - "github.com/hashicorp/go-cleanhttp" - "github.com/hashicorp/go-retryablehttp" ) // ModifyRequest contains the payload passed to the modification endpoint. @@ -70,11 +70,12 @@ func (c *client) Compile(v interface{}) (*pipeline.Build, *library.Pipeline, err Repo: c.repo.GetFullName(), Tag: strings.TrimPrefix(c.build.GetRef(), "refs/tags/"), Target: c.build.GetDeploy(), + Label: c.labels, } switch { case p.Metadata.RenderInline: - newPipeline, err := c.compileInline(p, c.TemplateDepth) + newPipeline, err := c.compileInline(p, c.GetTemplateDepth()) if err != nil { return nil, _pipeline, err } @@ -97,7 +98,7 @@ func (c *client) Compile(v interface{}) (*pipeline.Build, *library.Pipeline, err } // CompileLite produces a partial of an executable pipeline from a yaml configuration. -func (c *client) CompileLite(v interface{}, substitute bool) (*yaml.Build, *library.Pipeline, error) { +func (c *client) CompileLite(v interface{}, ruleData *pipeline.RuleData, substitute bool) (*yaml.Build, *library.Pipeline, error) { p, data, err := c.Parse(v, c.repo.GetPipelineType(), new(yaml.Template)) if err != nil { return nil, nil, err @@ -109,7 +110,7 @@ func (c *client) CompileLite(v interface{}, substitute bool) (*yaml.Build, *libr _pipeline.SetType(c.repo.GetPipelineType()) if p.Metadata.RenderInline { - newPipeline, err := c.compileInline(p, c.TemplateDepth) + newPipeline, err := c.compileInline(p, c.GetTemplateDepth()) if err != nil { return nil, _pipeline, err } @@ -125,36 +126,71 @@ func (c *client) CompileLite(v interface{}, substitute bool) (*yaml.Build, *libr // create map of templates for easy lookup templates := mapFromTemplates(p.Templates) - if len(templates) > 0 { - switch { - case len(p.Stages) > 0: - // inject the templates into the steps - p, err = c.ExpandStages(p, templates, nil) + switch { + case len(p.Stages) > 0: + // inject the templates into the steps + p, err = c.ExpandStages(p, templates, ruleData) + if err != nil { + return nil, _pipeline, err + } + + if substitute { + // inject the substituted environment variables into the steps + p.Stages, err = c.SubstituteStages(p.Stages) if err != nil { return nil, _pipeline, err } + } + + if ruleData != nil { + purgedStages := new(yaml.StageSlice) + + for _, stg := range p.Stages { + purgedSteps := new(yaml.StepSlice) - if substitute { - // inject the substituted environment variables into the steps - p.Stages, err = c.SubstituteStages(p.Stages) - if err != nil { - return nil, _pipeline, err + for _, s := range stg.Steps { + cRuleset := s.Ruleset.ToPipeline() + if match, err := cRuleset.Match(ruleData); err == nil && match { + *purgedSteps = append(*purgedSteps, s) + } + } + + stg.Steps = *purgedSteps + + if len(stg.Steps) > 0 { + *purgedStages = append(*purgedStages, stg) } } - case len(p.Steps) > 0: - // inject the templates into the steps - p, err = c.ExpandSteps(p, templates, nil, c.TemplateDepth) + + p.Stages = *purgedStages + } + + case len(p.Steps) > 0: + // inject the templates into the steps + p, err = c.ExpandSteps(p, templates, ruleData, c.GetTemplateDepth()) + if err != nil { + return nil, _pipeline, err + } + + if substitute { + // inject the substituted environment variables into the steps + p.Steps, err = c.SubstituteSteps(p.Steps) if err != nil { return nil, _pipeline, err } + } + + if ruleData != nil { + purgedSteps := new(yaml.StepSlice) - if substitute { - // inject the substituted environment variables into the steps - p.Steps, err = c.SubstituteSteps(p.Steps) - if err != nil { - return nil, _pipeline, err + for _, s := range p.Steps { + cRuleset := s.Ruleset.ToPipeline() + if match, err := cRuleset.Match(ruleData); err == nil && match { + *purgedSteps = append(*purgedSteps, s) } } + + p.Steps = *purgedSteps } } @@ -173,7 +209,7 @@ func (c *client) compileInline(p *yaml.Build, depth int) (*yaml.Build, error) { // return if max template depth has been reached if depth == 0 { - retErr := fmt.Errorf("max template depth of %d exceeded", c.TemplateDepth) + retErr := fmt.Errorf("max template depth of %d exceeded", c.GetTemplateDepth()) return nil, retErr } @@ -191,6 +227,14 @@ func (c *client) compileInline(p *yaml.Build, depth int) (*yaml.Build, error) { format = constants.PipelineTypeGo } + // initialize variable map if not parsed from config + if len(template.Variables) == 0 { + template.Variables = make(map[string]interface{}) + } + + // inject template name into variables + template.Variables["VELA_TEMPLATE_NAME"] = template.Name + parsed, _, err := c.Parse(bytes, format, template) if err != nil { return nil, err @@ -255,8 +299,6 @@ func (c *client) compileInline(p *yaml.Build, depth int) (*yaml.Build, error) { } // compileSteps executes the workflow for converting a YAML pipeline into an executable struct. -// -//nolint:dupl,lll // linter thinks the steps and stages workflows are identical func (c *client) compileSteps(p *yaml.Build, _pipeline *library.Pipeline, tmpls map[string]*yaml.Template, r *pipeline.RuleData) (*pipeline.Build, *library.Pipeline, error) { var err error @@ -276,7 +318,7 @@ func (c *client) compileSteps(p *yaml.Build, _pipeline *library.Pipeline, tmpls } // inject the templates into the steps - p, err = c.ExpandSteps(p, tmpls, r, c.TemplateDepth) + p, err = c.ExpandSteps(p, tmpls, r, c.GetTemplateDepth()) if err != nil { return nil, _pipeline, err } @@ -352,8 +394,6 @@ func (c *client) compileSteps(p *yaml.Build, _pipeline *library.Pipeline, tmpls } // compileStages executes the workflow for converting a YAML pipeline into an executable struct. -// -//nolint:dupl,lll // linter thinks the steps and stages workflows are identical func (c *client) compileStages(p *yaml.Build, _pipeline *library.Pipeline, tmpls map[string]*yaml.Template, r *pipeline.RuleData) (*pipeline.Build, *library.Pipeline, error) { var err error @@ -458,7 +498,7 @@ func errorHandler(resp *http.Response, err error, attempts int) (*http.Response, } // modifyConfig sends the configuration to external http endpoint for modification. -func (c *client) modifyConfig(build *yaml.Build, libraryBuild *library.Build, repo *library.Repo) (*yaml.Build, error) { +func (c *client) modifyConfig(build *yaml.Build, libraryBuild *api.Build, repo *api.Repo) (*yaml.Build, error) { // create request to send to endpoint data, err := yml.Marshal(build) if err != nil { diff --git a/compiler/native/compile_test.go b/compiler/native/compile_test.go index e71956306..bece3c659 100644 --- a/compiler/native/compile_test.go +++ b/compiler/native/compile_test.go @@ -9,27 +9,21 @@ import ( "net/http/httptest" "os" "path/filepath" - - "github.com/go-vela/types/constants" - "github.com/go-vela/types/raw" - - "github.com/google/go-github/v59/github" - "testing" "time" - "github.com/google/go-cmp/cmp" - - "github.com/go-vela/types/library" - "github.com/go-vela/types/yaml" - - "github.com/go-vela/types" - "github.com/go-vela/types/pipeline" - + yml "github.com/buildkite/yaml" "github.com/gin-gonic/gin" + "github.com/google/go-cmp/cmp" + "github.com/google/go-github/v62/github" "github.com/urfave/cli/v2" - yml "github.com/buildkite/yaml" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/pipeline" + "github.com/go-vela/types/raw" + "github.com/go-vela/types/yaml" ) func TestNative_Compile_StagesPipeline(t *testing.T) { @@ -39,21 +33,21 @@ func TestNative_Compile_StagesPipeline(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -250,7 +244,7 @@ func TestNative_Compile_StagesPipeline(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -307,8 +301,8 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) { type args struct { endpoint string - libraryBuild *library.Build - repo *library.Repo + libraryBuild *api.Build + repo *api.Repo } tests := []struct { @@ -317,13 +311,13 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) { wantErr bool }{ {"bad url", args{ - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: "bad", }, true}, {"invalid return", args{ - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/bad"), }, true}, } @@ -335,8 +329,8 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) { Timeout: 1 * time.Second, Endpoint: tt.args.endpoint, }, - repo: &library.Repo{Name: &author}, - build: &library.Build{Author: &name, Number: &number}, + repo: &api.Repo{Name: &author}, + build: &api.Build{Author: &name, Number: &number}, } _, _, err := compiler.Compile(yaml) if (err != nil) != tt.wantErr { @@ -375,8 +369,8 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) { type args struct { endpoint string - libraryBuild *library.Build - repo *library.Repo + libraryBuild *api.Build + repo *api.Repo } tests := []struct { @@ -385,13 +379,13 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) { wantErr bool }{ {"bad url", args{ - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: "bad", }, true}, {"invalid return", args{ - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/bad"), }, true}, } @@ -422,21 +416,21 @@ func TestNative_Compile_StepsPipeline(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -588,7 +582,7 @@ func TestNative_Compile_StepsPipeline(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -633,21 +627,21 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -680,7 +674,7 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) { buildEnv["GRADLE_USER_HOME"] = ".gradle" buildEnv["HOME"] = "/root" buildEnv["SHELL"] = "/bin/sh" - buildEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew build"}) + buildEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew build", "echo gradle"}) buildEnv["bar"] = "test4" buildEnv["star"] = "test3" @@ -847,7 +841,7 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -904,21 +898,21 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -951,7 +945,7 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) { buildEnv["GRADLE_USER_HOME"] = ".gradle" buildEnv["HOME"] = "/root" buildEnv["SHELL"] = "/bin/sh" - buildEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew build"}) + buildEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"./gradlew build", "echo gradle"}) buildEnv["bar"] = "test4" buildEnv["star"] = "test3" @@ -1092,7 +1086,7 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -1138,21 +1132,21 @@ func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName(t *testi set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -1213,7 +1207,7 @@ func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName(t *testi t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -1259,21 +1253,21 @@ func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName_Inline(t set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -1334,7 +1328,7 @@ func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName_Inline(t t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -1376,23 +1370,24 @@ func TestNative_Compile_InvalidType(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -1413,7 +1408,7 @@ func TestNative_Compile_InvalidType(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -1435,21 +1430,21 @@ func TestNative_Compile_Clone(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -1601,7 +1596,7 @@ func TestNative_Compile_Clone(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -1629,21 +1624,21 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -1695,10 +1690,10 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) { goPipelineType := "go" - goFooEnv := environment(nil, m, &library.Repo{PipelineType: &goPipelineType}, nil) + goFooEnv := environment(nil, m, &api.Repo{PipelineType: &goPipelineType}, nil) goFooEnv["PARAMETER_REGISTRY"] = "foo" - defaultGoEnv := environment(nil, m, &library.Repo{PipelineType: &goPipelineType}, nil) + defaultGoEnv := environment(nil, m, &api.Repo{PipelineType: &goPipelineType}, nil) wantGo := &pipeline.Build{ Version: "1", ID: "__0", @@ -1741,10 +1736,10 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) { starPipelineType := "starlark" - starlarkFooEnv := environment(nil, m, &library.Repo{PipelineType: &starPipelineType}, nil) + starlarkFooEnv := environment(nil, m, &api.Repo{PipelineType: &starPipelineType}, nil) starlarkFooEnv["PARAMETER_REGISTRY"] = "foo" - defaultStarlarkEnv := environment(nil, m, &library.Repo{PipelineType: &starPipelineType}, nil) + defaultStarlarkEnv := environment(nil, m, &api.Repo{PipelineType: &starPipelineType}, nil) wantStarlark := &pipeline.Build{ Version: "1", ID: "__0", @@ -1809,13 +1804,15 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } compiler.WithMetadata(m) - compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType}) + + pipelineType := tt.args.pipelineType + compiler.WithRepo(&api.Repo{PipelineType: &pipelineType}) got, _, err := compiler.Compile(yaml) if err != nil { @@ -1832,6 +1829,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) { func TestNative_Compile_NoStepsorStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) name := "foo" author := "author" @@ -1843,13 +1841,17 @@ func TestNative_Compile_NoStepsorStages(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } - compiler.repo = &library.Repo{Name: &author} - compiler.build = &library.Build{Author: &name, Number: &number} + // todo: this needs to be fixed in compiler validation + // this is a dirty hack to make this test pass + compiler.SetCloneImage("") + + compiler.repo = &api.Repo{Name: &author} + compiler.build = &api.Build{Author: &name, Number: &number} got, _, err := compiler.Compile(yaml) if err == nil { @@ -1864,6 +1866,7 @@ func TestNative_Compile_NoStepsorStages(t *testing.T) { func TestNative_Compile_StepsandStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) name := "foo" author := "author" @@ -1875,13 +1878,13 @@ func TestNative_Compile_StepsandStages(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } - compiler.repo = &library.Repo{Name: &author} - compiler.build = &library.Build{Author: &name, Number: &number} + compiler.repo = &api.Repo{Name: &author} + compiler.build = &api.Build{Author: &name, Number: &number} got, _, err := compiler.Compile(yaml) if err == nil { @@ -1923,21 +1926,21 @@ func Test_client_modifyConfig(t *testing.T) { c.JSON(http.StatusOK, body) }) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -2082,8 +2085,8 @@ func Test_client_modifyConfig(t *testing.T) { type args struct { endpoint string build *yaml.Build - libraryBuild *library.Build - repo *library.Repo + libraryBuild *api.Build + repo *api.Repo } tests := []struct { @@ -2094,38 +2097,38 @@ func Test_client_modifyConfig(t *testing.T) { }{ {"unmodified", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/unmodified"), }, want, false}, {"modified", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/modified"), }, want2, false}, {"invalid endpoint", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: "bad", }, nil, true}, {"unauthorized endpoint", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/unauthorized"), }, nil, true}, {"timeout endpoint", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/timeout"), }, nil, true}, {"empty payload", args{ build: want, - libraryBuild: &library.Build{Number: &number, Author: &author}, - repo: &library.Repo{Name: &name}, + libraryBuild: &api.Build{Number: &number, Author: &author}, + repo: &api.Repo{Name: &name}, endpoint: fmt.Sprintf("%s/%s", s.URL, "config/empty"), }, nil, true}, } @@ -2166,7 +2169,7 @@ func convertFileToGithubResponse(file string) (github.RepositoryContent, error) return content, nil } -func generateTestEnv(command string, m *types.Metadata, pipelineType string) map[string]string { +func generateTestEnv(command string, m *internal.Metadata, pipelineType string) map[string]string { output := environment(nil, m, nil, nil) output["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{command}) output["HOME"] = "/root" @@ -2204,21 +2207,21 @@ func Test_Compile_Inline(t *testing.T) { set.Int("max-template-depth", 5, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -2308,60 +2311,6 @@ func Test_Compile_Inline(t *testing.T) { }, }, }, - { - Name: "golang_foo", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_foo_golang_foo", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from foo", m, ""), - Image: "golang:latest", - Name: "golang_foo", - Pull: "not_present", - Number: 4, - }, - }, - }, - { - Name: "golang_bar", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_bar_golang_bar", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from bar", m, ""), - Image: "golang:latest", - Name: "golang_bar", - Pull: "not_present", - Number: 5, - }, - }, - }, - { - Name: "golang_star", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_star_golang_star", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from star", m, ""), - Image: "golang:latest", - Name: "golang_star", - Pull: "not_present", - Number: 6, - }, - }, - }, { Name: "starlark_foo", Needs: []string{"clone"}, @@ -2376,7 +2325,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "starlark_build_foo", Pull: "not_present", - Number: 7, + Number: 4, }, }, }, @@ -2394,7 +2343,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "starlark_build_bar", Pull: "not_present", - Number: 8, + Number: 5, }, }, }, @@ -2482,60 +2431,6 @@ func Test_Compile_Inline(t *testing.T) { }, }, }, - { - Name: "nested_golang_foo", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_nested_golang_foo_nested_golang_foo", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from foo", m, ""), - Image: "golang:latest", - Name: "nested_golang_foo", - Pull: "not_present", - Number: 5, - }, - }, - }, - { - Name: "nested_golang_bar", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_nested_golang_bar_nested_golang_bar", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from bar", m, ""), - Image: "golang:latest", - Name: "nested_golang_bar", - Pull: "not_present", - Number: 6, - }, - }, - }, - { - Name: "nested_golang_star", - Needs: []string{"clone"}, - Environment: initEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_nested_golang_star_nested_golang_star", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from star", m, ""), - Image: "golang:latest", - Name: "nested_golang_star", - Pull: "not_present", - Number: 7, - }, - }, - }, { Name: "nested_starlark_foo", Needs: []string{"clone"}, @@ -2550,7 +2445,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "nested_starlark_build_foo", Pull: "not_present", - Number: 8, + Number: 5, }, }, }, @@ -2568,7 +2463,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "nested_starlark_build_bar", Pull: "not_present", - Number: 9, + Number: 6, }, }, }, @@ -3017,60 +2912,6 @@ func Test_Compile_Inline(t *testing.T) { }, }, }, - { - Name: "golang_foo", - Needs: []string{"clone"}, - Environment: golangEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_foo_golang_foo", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from foo", m, constants.PipelineTypeGo), - Image: "golang:latest", - Name: "golang_foo", - Pull: "not_present", - Number: 6, - }, - }, - }, - { - Name: "golang_bar", - Needs: []string{"clone"}, - Environment: golangEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_bar_golang_bar", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from bar", m, constants.PipelineTypeGo), - Image: "golang:latest", - Name: "golang_bar", - Pull: "not_present", - Number: 7, - }, - }, - }, - { - Name: "golang_star", - Needs: []string{"clone"}, - Environment: golangEnv, - Steps: []*pipeline.Container{ - { - ID: "__0_golang_star_golang_star", - Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"}, - Directory: "/vela/src/foo//", - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: generateTestEnv("echo hello from star", m, constants.PipelineTypeGo), - Image: "golang:latest", - Name: "golang_star", - Pull: "not_present", - Number: 8, - }, - }, - }, { Name: "starlark_foo", Needs: []string{"clone"}, @@ -3085,7 +2926,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "starlark_build_foo", Pull: "not_present", - Number: 9, + Number: 6, }, }, }, @@ -3103,7 +2944,7 @@ func Test_Compile_Inline(t *testing.T) { Image: "alpine", Name: "starlark_build_bar", Pull: "not_present", - Number: 10, + Number: 7, }, }, }, @@ -3118,7 +2959,7 @@ func Test_Compile_Inline(t *testing.T) { if err != nil { t.Errorf("Reading yaml file return err: %v", err) } - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -3126,7 +2967,8 @@ func Test_Compile_Inline(t *testing.T) { compiler.WithMetadata(m) if tt.args.pipelineType != "" { - compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType}) + pipelineType := tt.args.pipelineType + compiler.WithRepo(&api.Repo{PipelineType: &pipelineType}) } got, _, err := compiler.Compile(yaml) @@ -3181,23 +3023,24 @@ func Test_CompileLite(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -3207,6 +3050,7 @@ func Test_CompileLite(t *testing.T) { file string pipelineType string substitute bool + ruleData *pipeline.RuleData } tests := []struct { @@ -3230,17 +3074,21 @@ func Test_CompileLite(t *testing.T) { }, Templates: []*yaml.Template{ { - Name: "golang", - Source: "github.example.com/github/octocat/golang_inline_stages.yml", - Format: "golang", - Type: "github", - Variables: map[string]any{"image": string("golang:latest")}, + Name: "golang", + Source: "github.example.com/github/octocat/golang_inline_stages.yml", + Format: "golang", + Type: "github", + Variables: map[string]any{ + "image": string("golang:latest"), + "VELA_TEMPLATE_NAME": string("golang"), + }, }, { - Name: "starlark", - Source: "github.example.com/github/octocat/starlark_inline_stages.star", - Format: "starlark", - Type: "github", + Name: "starlark", + Source: "github.example.com/github/octocat/starlark_inline_stages.star", + Format: "starlark", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("starlark")}, }, }, Environment: raw.StringSliceMap{}, @@ -3255,6 +3103,20 @@ func Test_CompileLite(t *testing.T) { Name: "test", Pull: "not_present", }, + { + Commands: raw.StringSlice{"echo from inline ruleset"}, + Image: "alpine", + Name: "ruleset", + Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"push"}, + Branch: []string{"main"}, + }, + Matcher: "filepath", + Operator: "and", + }, + }, }, }, { @@ -3266,6 +3128,14 @@ func Test_CompileLite(t *testing.T) { Image: "golang:latest", Name: "golang_foo", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3278,6 +3148,14 @@ func Test_CompileLite(t *testing.T) { Image: "golang:latest", Name: "golang_bar", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3290,6 +3168,14 @@ func Test_CompileLite(t *testing.T) { Image: "golang:latest", Name: "golang_star", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3321,6 +3207,97 @@ func Test_CompileLite(t *testing.T) { }, wantErr: false, }, + { + name: "render_inline with stages - ruleset", + args: args{ + file: "testdata/inline_with_stages.yml", + pipelineType: "", + substitute: true, + ruleData: &pipeline.RuleData{ + Event: "push", + Branch: "main", + }, + }, + want: &yaml.Build{ + Version: "1", + Metadata: yaml.Metadata{ + RenderInline: true, + Environment: []string{"steps", "services", "secrets"}, + }, + Templates: []*yaml.Template{ + { + Name: "golang", + Source: "github.example.com/github/octocat/golang_inline_stages.yml", + Format: "golang", + Type: "github", + Variables: map[string]any{ + "image": string("golang:latest"), + "VELA_TEMPLATE_NAME": string("golang"), + }, + }, + { + Name: "starlark", + Source: "github.example.com/github/octocat/starlark_inline_stages.star", + Format: "starlark", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("starlark")}, + }, + }, + Environment: raw.StringSliceMap{}, + Stages: []*yaml.Stage{ + { + Name: "test", + Needs: []string{"clone"}, + Steps: []*yaml.Step{ + { + Commands: raw.StringSlice{"echo from inline"}, + Image: "alpine", + Name: "test", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo from inline ruleset"}, + Image: "alpine", + Name: "ruleset", + Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"push"}, + Branch: []string{"main"}, + }, + Matcher: "filepath", + Operator: "and", + }, + }, + }, + }, + { + Name: "starlark_foo", + Needs: []string{"clone"}, + Steps: []*yaml.Step{ + { + Commands: raw.StringSlice{"echo hello from foo"}, + Image: "alpine", + Name: "starlark_build_foo", + Pull: "not_present", + }, + }, + }, + { + Name: "starlark_bar", + Needs: []string{"clone"}, + Steps: []*yaml.Step{ + { + Commands: raw.StringSlice{"echo hello from bar"}, + Image: "alpine", + Name: "starlark_build_bar", + Pull: "not_present", + }, + }, + }, + }, + }, + }, { name: "render_inline with steps", args: args{ @@ -3341,6 +3318,34 @@ func Test_CompileLite(t *testing.T) { Name: "test", Pull: "not_present", }, + { + Commands: raw.StringSlice{"echo from inline ruleset"}, + Image: "alpine", + Name: "ruleset", + Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"deployment:created"}, + Target: []string{"production"}, + }, + Matcher: "filepath", + Operator: "and", + }, + }, + { + Commands: raw.StringSlice{"echo from inline ruleset"}, + Image: "alpine", + Name: "other ruleset", + Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Path: []string{"src/*", "test/*"}, + Event: []string{}, + }, + Matcher: "filepath", + Operator: "and", + }, + }, { Commands: raw.StringSlice{"echo hello from foo"}, Image: "alpine", @@ -3375,9 +3380,345 @@ func Test_CompileLite(t *testing.T) { Environment: raw.StringSliceMap{}, Templates: yaml.TemplateSlice{ { - Name: "golang", - Source: "github.example.com/github/octocat/golang_inline_steps.yml", - Format: "golang", + Name: "golang", + Source: "github.example.com/github/octocat/golang_inline_steps.yml", + Format: "golang", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("golang")}, + }, + { + Name: "starlark", + Source: "github.example.com/github/octocat/starlark_inline_steps.star", + Format: "starlark", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("starlark")}, + }, + }, + }, + wantErr: false, + }, + { + name: "render_inline with steps - ruleset", + args: args{ + file: "testdata/inline_with_steps.yml", + pipelineType: "", + substitute: true, + ruleData: &pipeline.RuleData{ + Event: "deployment:created", + Target: "production", + Path: []string{"README.md"}, + }, + }, + want: &yaml.Build{ + Version: "1", + Metadata: yaml.Metadata{ + RenderInline: true, + Environment: []string{"steps", "services", "secrets"}, + }, + Steps: yaml.StepSlice{ + { + Commands: raw.StringSlice{"echo from inline"}, + Image: "alpine", + Name: "test", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo from inline ruleset"}, + Image: "alpine", + Name: "ruleset", + Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"deployment:created"}, + Target: []string{"production"}, + }, + Matcher: "filepath", + Operator: "and", + }, + }, + { + Commands: raw.StringSlice{"echo hello from foo"}, + Image: "alpine", + Name: "golang_foo", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo hello from bar"}, + Image: "alpine", + Name: "golang_bar", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo hello from star"}, + Image: "alpine", + Name: "golang_star", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo hello from foo"}, + Image: "alpine", + Name: "starlark_build_foo", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo hello from bar"}, + Image: "alpine", + Name: "starlark_build_bar", + Pull: "not_present", + }, + }, + Environment: raw.StringSliceMap{}, + Templates: yaml.TemplateSlice{ + { + Name: "golang", + Source: "github.example.com/github/octocat/golang_inline_steps.yml", + Format: "golang", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("golang")}, + }, + { + Name: "starlark", + Source: "github.example.com/github/octocat/starlark_inline_steps.star", + Format: "starlark", + Type: "github", + Variables: map[string]any{"VELA_TEMPLATE_NAME": string("starlark")}, + }, + }, + }, + }, + { + name: "call template with ruleset", + args: args{ + file: "testdata/steps_pipeline_template.yml", + pipelineType: "", + substitute: true, + ruleData: &pipeline.RuleData{ + Event: "push", + }, + }, + want: &yaml.Build{ + Version: "1", + Metadata: yaml.Metadata{ + Environment: []string{"steps", "services", "secrets"}, + }, + Environment: raw.StringSliceMap{ + "bar": "test4", + "star": "test3", + }, + Secrets: yaml.SecretSlice{ + { + Name: "docker_username", + Key: "org/repo/docker/username", + Engine: "native", + Type: "repo", + Pull: "build_start", + }, + { + Name: "docker_password", + Key: "org/repo/docker/password", + Engine: "vault", + Type: "repo", + Pull: "build_start", + }, + { + Name: "foo_password", + Key: "org/repo/foo/password", + Engine: "vault", + Type: "repo", + Pull: "build_start", + }, + }, + Services: yaml.ServiceSlice{ + { + Image: "postgres:12", + Name: "postgres", + Pull: "not_present", + }, + }, + Steps: yaml.StepSlice{ + { + Commands: raw.StringSlice{"./gradlew downloadDependencies"}, + Image: "openjdk:latest", + Name: "sample_install", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Commands: raw.StringSlice{"./gradlew check"}, + Image: "openjdk:latest", + Name: "sample_test", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Commands: raw.StringSlice{"./gradlew build", "echo gradle"}, + Image: "openjdk:latest", + Name: "sample_build", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Commands: raw.StringSlice{"echo hello from foo"}, + Image: "alpine", + Name: "starlark_build_foo", + Pull: "not_present", + }, + { + Commands: raw.StringSlice{"echo hello from bar"}, + Image: "alpine", + Name: "starlark_build_bar", + Pull: "not_present", + }, + { + Secrets: yaml.StepSecretSlice{ + { + Source: "docker_username", + Target: "registry_username", + }, + { + Source: "docker_password", + Target: "registry_password", + }, + }, + Image: "plugins/docker:18.09", + Name: "docker", + Pull: "always", + Parameters: map[string]any{ + "registry": string("index.docker.io"), + "repo": string("github/octocat"), + "tags": []any{string("latest"), string("dev")}, + }, + }, + }, + Templates: yaml.TemplateSlice{ + { + Name: "gradle", + Source: "github.example.com/foo/bar/long_template.yml", + Type: "github", + }, + { + Name: "starlark", + Source: "github.example.com/github/octocat/starlark_inline_steps.star", + Format: "starlark", + Type: "github", + }, + }, + }, + }, + { + name: "call template with ruleset - no match", + args: args{ + file: "testdata/steps_pipeline_template.yml", + pipelineType: "", + substitute: true, + ruleData: &pipeline.RuleData{ + Event: "pull_request", + }, + }, + want: &yaml.Build{ + Version: "1", + Metadata: yaml.Metadata{ + Environment: []string{"steps", "services", "secrets"}, + }, + Environment: raw.StringSliceMap{ + "bar": "test4", + "star": "test3", + }, + Secrets: yaml.SecretSlice{ + { + Name: "docker_username", + Key: "org/repo/docker/username", + Engine: "native", + Type: "repo", + Pull: "build_start", + }, + { + Name: "docker_password", + Key: "org/repo/docker/password", + Engine: "vault", + Type: "repo", + Pull: "build_start", + }, + { + Name: "foo_password", + Key: "org/repo/foo/password", + Engine: "vault", + Type: "repo", + Pull: "build_start", + }, + }, + Services: yaml.ServiceSlice{ + { + Image: "postgres:12", + Name: "postgres", + Pull: "not_present", + }, + }, + Steps: yaml.StepSlice{ + { + Commands: raw.StringSlice{"./gradlew downloadDependencies"}, + Image: "openjdk:latest", + Name: "sample_install", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Commands: raw.StringSlice{"./gradlew check"}, + Image: "openjdk:latest", + Name: "sample_test", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Commands: raw.StringSlice{"./gradlew build", "echo gradle"}, + Image: "openjdk:latest", + Name: "sample_build", + Pull: "always", + Environment: raw.StringSliceMap{ + "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", + "GRADLE_USER_HOME": ".gradle", + }, + }, + { + Secrets: yaml.StepSecretSlice{ + { + Source: "docker_username", + Target: "registry_username", + }, + { + Source: "docker_password", + Target: "registry_password", + }, + }, + Image: "plugins/docker:18.09", + Name: "docker", + Pull: "always", + Parameters: map[string]any{ + "registry": string("index.docker.io"), + "repo": string("github/octocat"), + "tags": []any{string("latest"), string("dev")}, + }, + }, + }, + Templates: yaml.TemplateSlice{ + { + Name: "gradle", + Source: "github.example.com/foo/bar/long_template.yml", Type: "github", }, { @@ -3388,7 +3729,6 @@ func Test_CompileLite(t *testing.T) { }, }, }, - wantErr: false, }, { name: "golang", @@ -3412,6 +3752,14 @@ func Test_CompileLite(t *testing.T) { Image: "alpine", Name: "foo", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3424,6 +3772,14 @@ func Test_CompileLite(t *testing.T) { Image: "alpine", Name: "bar", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3436,6 +3792,14 @@ func Test_CompileLite(t *testing.T) { Image: "alpine", Name: "star", Pull: "not_present", + Ruleset: yaml.Ruleset{ + If: yaml.Rules{ + Event: []string{"tag"}, + Tag: []string{"v*"}, + }, + Matcher: "filepath", + Operator: "and", + }, }, }, }, @@ -3467,14 +3831,15 @@ func Test_CompileLite(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } compiler.WithMetadata(m) if tt.args.pipelineType != "" { - compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType}) + pipelineType := tt.args.pipelineType + compiler.WithRepo(&api.Repo{PipelineType: &pipelineType}) } yaml, err := os.ReadFile(tt.args.file) @@ -3482,7 +3847,7 @@ func Test_CompileLite(t *testing.T) { t.Errorf("Reading yaml file return err: %v", err) } - got, _, err := compiler.CompileLite(yaml, tt.args.substitute) + got, _, err := compiler.CompileLite(yaml, tt.args.ruleData, tt.args.substitute) if (err != nil) != tt.wantErr { t.Errorf("CompileLite() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/compiler/native/environment.go b/compiler/native/environment.go index 5841825b3..a2542fb3f 100644 --- a/compiler/native/environment.go +++ b/compiler/native/environment.go @@ -7,7 +7,8 @@ import ( "os" "strings" - "github.com/go-vela/types" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" @@ -281,7 +282,7 @@ func appendMap(originalMap, otherMap map[string]string) map[string]string { } // helper function that creates the standard set of environment variables for a pipeline. -func environment(b *library.Build, m *types.Metadata, r *library.Repo, u *library.User) map[string]string { +func environment(b *api.Build, m *internal.Metadata, r *api.Repo, u *api.User) map[string]string { // set default workspace workspace := constants.WorkspaceDefault notImplemented := "TODO" @@ -314,6 +315,7 @@ func environment(b *library.Build, m *types.Metadata, r *library.Repo, u *librar env["VELA_NETRC_MACHINE"] = m.Source.Host env["VELA_QUEUE"] = m.Queue.Driver env["VELA_SOURCE"] = m.Source.Driver + env["VELA_ID_TOKEN_REQUEST_URL"] = fmt.Sprintf("%s/api/v1/repos/%s/builds/%d/id_token", m.Vela.Address, r.GetFullName(), b.GetNumber()) channel = m.Queue.Channel workspace = fmt.Sprintf("%s/%s/%s/%s", workspace, m.Source.Host, r.GetOrg(), r.GetName()) } diff --git a/compiler/native/environment_test.go b/compiler/native/environment_test.go index a6a598acc..cb78fbd99 100644 --- a/compiler/native/environment_test.go +++ b/compiler/native/environment_test.go @@ -8,19 +8,19 @@ import ( "strings" "testing" - "github.com/go-vela/types/raw" "github.com/google/go-cmp/cmp" + "github.com/urfave/cli/v2" - "github.com/go-vela/types" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" + "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - - "github.com/urfave/cli/v2" ) func TestNative_EnvironmentStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -61,7 +61,7 @@ func TestNative_EnvironmentStages(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -79,6 +79,7 @@ func TestNative_EnvironmentStages(t *testing.T) { func TestNative_EnvironmentSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) e := raw.StringSliceMap{ @@ -127,11 +128,6 @@ func TestNative_EnvironmentSteps(t *testing.T) { "BUILD_WORKSPACE": "/vela/src", "CI": "true", "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_COMMENT": "false", - "REPOSITORY_ALLOW_DEPLOY": "false", - "REPOSITORY_ALLOW_PULL": "false", - "REPOSITORY_ALLOW_PUSH": "false", - "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "", "REPOSITORY_CLONE": "", @@ -167,6 +163,7 @@ func TestNative_EnvironmentSteps(t *testing.T) { "VELA_BUILD_REF": "", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", "VELA_BUILD_SOURCE": "", "VELA_BUILD_STARTED": "0", "VELA_BUILD_STATUS": "", @@ -181,11 +178,6 @@ func TestNative_EnvironmentSteps(t *testing.T) { "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "TODO", "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_COMMENT": "false", - "VELA_REPO_ALLOW_DEPLOY": "false", - "VELA_REPO_ALLOW_PULL": "false", - "VELA_REPO_ALLOW_PUSH": "false", - "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "", @@ -196,6 +188,7 @@ func TestNative_EnvironmentSteps(t *testing.T) { "VELA_REPO_LINK": "", "VELA_REPO_NAME": "", "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "0", @@ -215,7 +208,7 @@ func TestNative_EnvironmentSteps(t *testing.T) { } // run test non-local - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -263,6 +256,7 @@ func TestNative_EnvironmentSteps(t *testing.T) { func TestNative_EnvironmentServices(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) e := raw.StringSliceMap{ @@ -311,11 +305,6 @@ func TestNative_EnvironmentServices(t *testing.T) { "BUILD_WORKSPACE": "/vela/src", "CI": "true", "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_COMMENT": "false", - "REPOSITORY_ALLOW_DEPLOY": "false", - "REPOSITORY_ALLOW_PULL": "false", - "REPOSITORY_ALLOW_PUSH": "false", - "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "", "REPOSITORY_CLONE": "", @@ -351,6 +340,7 @@ func TestNative_EnvironmentServices(t *testing.T) { "VELA_BUILD_REF": "", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", "VELA_BUILD_SOURCE": "", "VELA_BUILD_STARTED": "0", "VELA_BUILD_STATUS": "", @@ -365,11 +355,6 @@ func TestNative_EnvironmentServices(t *testing.T) { "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "TODO", "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_COMMENT": "false", - "VELA_REPO_ALLOW_DEPLOY": "false", - "VELA_REPO_ALLOW_PULL": "false", - "VELA_REPO_ALLOW_PUSH": "false", - "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "", @@ -380,6 +365,7 @@ func TestNative_EnvironmentServices(t *testing.T) { "VELA_REPO_LINK": "", "VELA_REPO_NAME": "", "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "0", @@ -399,7 +385,7 @@ func TestNative_EnvironmentServices(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -417,6 +403,7 @@ func TestNative_EnvironmentServices(t *testing.T) { func TestNative_EnvironmentSecrets(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) e := raw.StringSliceMap{ @@ -477,11 +464,6 @@ func TestNative_EnvironmentSecrets(t *testing.T) { "CI": "true", "PARAMETER_FOO": "bar", "REPOSITORY_ACTIVE": "false", - "REPOSITORY_ALLOW_COMMENT": "false", - "REPOSITORY_ALLOW_DEPLOY": "false", - "REPOSITORY_ALLOW_PULL": "false", - "REPOSITORY_ALLOW_PUSH": "false", - "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "", "REPOSITORY_CLONE": "", @@ -517,6 +499,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) { "VELA_BUILD_REF": "", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "", + "VELA_BUILD_SENDER_SCM_ID": "", "VELA_BUILD_SOURCE": "", "VELA_BUILD_STARTED": "0", "VELA_BUILD_STATUS": "", @@ -531,11 +514,6 @@ func TestNative_EnvironmentSecrets(t *testing.T) { "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "TODO", "VELA_REPO_ACTIVE": "false", - "VELA_REPO_ALLOW_COMMENT": "false", - "VELA_REPO_ALLOW_DEPLOY": "false", - "VELA_REPO_ALLOW_PULL": "false", - "VELA_REPO_ALLOW_PUSH": "false", - "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "", @@ -546,6 +524,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) { "VELA_REPO_LINK": "", "VELA_REPO_NAME": "", "VELA_REPO_ORG": "", + "VELA_REPO_OWNER": "", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "0", @@ -566,7 +545,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -576,7 +555,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) { t.Errorf("EnvironmentSecrets returned err: %v", err) } - if diff := cmp.Diff(got, want); diff != "" { + if diff := cmp.Diff(want, got); diff != "" { t.Errorf("EnvironmentSecrets mismatch (-want +got):\n%s", diff) } } @@ -604,47 +583,47 @@ func TestNative_environment(t *testing.T) { tests := []struct { w string - b *library.Build - m *types.Metadata - r *library.Repo - u *library.User + b *api.Build + m *internal.Metadata + r *api.Repo + u *api.User want map[string]string }{ // push { w: workspace, - b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, - m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, + m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // tag { w: workspace, - b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, - m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, + m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // pull_request { w: workspace, - b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, - m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, // deployment { w: workspace, - b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, - m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + b: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + m: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + r: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + u: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_OWNER": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, } @@ -717,10 +696,10 @@ func Test_client_EnvironmentBuild(t *testing.T) { target := "production" type fields struct { - build *library.Build - metadata *types.Metadata - repo *library.Repo - user *library.User + build *api.Build + metadata *internal.Metadata + repo *api.Repo + user *api.User } tests := []struct { @@ -729,31 +708,31 @@ func Test_client_EnvironmentBuild(t *testing.T) { want map[string]string }{ {"push", fields{ - build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, - metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str}, + metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}}, {"tag", fields{ - build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, - metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str}, + metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, {"pull_request", fields{ - build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, - metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, {"deployment", fields{ - build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, - metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}}, - repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL}, - user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, - }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}, + build: &api.Build{ID: &num64, Repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, SenderSCMID: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str}, + metadata: &internal.Metadata{Database: &internal.Database{Driver: str, Host: str}, Queue: &internal.Queue{Channel: str, Driver: str, Host: str}, Source: &internal.Source{Driver: str, Host: str}, Vela: &internal.Vela{Address: str, WebAddress: str}}, + repo: &api.Repo{ID: &num64, Owner: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL}, + user: &api.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL}, + }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_EVENTS": "", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_APPROVED_AT": "0", "VELA_BUILD_APPROVED_BY": "", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SENDER_SCM_ID": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DEPLOYMENT_NUMBER": "0", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_EVENTS": "", "VELA_REPO_APPROVE_BUILD": "", "VELA_REPO_OWNER": "foo", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_ID_TOKEN_REQUEST_URL": "foo/api/v1/repos/foo/builds/1/id_token"}, }, } for _, tt := range tests { diff --git a/compiler/native/expand.go b/compiler/native/expand.go index 734c597e8..365946d60 100644 --- a/compiler/native/expand.go +++ b/compiler/native/expand.go @@ -6,17 +6,16 @@ import ( "fmt" "strings" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" + "github.com/spf13/afero" "github.com/go-vela/server/compiler/registry" "github.com/go-vela/server/compiler/template/native" "github.com/go-vela/server/compiler/template/starlark" - "github.com/spf13/afero" - + "github.com/go-vela/types/constants" + "github.com/go-vela/types/pipeline" "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - "github.com/sirupsen/logrus" ) // ExpandStages injects the template for each @@ -29,7 +28,7 @@ func (c *client) ExpandStages(s *yaml.Build, tmpls map[string]*yaml.Template, r // iterate through all stages for _, stage := range s.Stages { // inject the templates into the steps for the stage - p, err := c.ExpandSteps(&yaml.Build{Steps: stage.Steps, Secrets: s.Secrets, Services: s.Services, Environment: s.Environment}, tmpls, r, c.TemplateDepth) + p, err := c.ExpandSteps(&yaml.Build{Steps: stage.Steps, Secrets: s.Secrets, Services: s.Services, Environment: s.Environment}, tmpls, r, c.GetTemplateDepth()) if err != nil { return nil, err } @@ -52,7 +51,7 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r * // return if max template depth has been reached if depth == 0 { - retErr := fmt.Errorf("max template depth of %d exceeded", c.TemplateDepth) + retErr := fmt.Errorf("max template depth of %d exceeded", c.GetTemplateDepth()) return s, retErr } @@ -61,6 +60,7 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r * secrets := s.Secrets services := s.Services environment := s.Environment + templates := s.Templates if len(environment) == 0 { environment = make(raw.StringSliceMap) @@ -120,6 +120,14 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r * return s, err } + // initialize variable map if not parsed from config + if len(step.Template.Variables) == 0 { + step.Template.Variables = make(map[string]interface{}) + } + + // inject template name into variables + step.Template.Variables["VELA_TEMPLATE_NAME"] = step.Template.Name + tmplBuild, err := c.mergeTemplate(bytes, tmpl, step) if err != nil { return s, err @@ -132,6 +140,8 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r * return s, fmt.Errorf("cannot use render_inline inside a called template (%s)", step.Template.Name) } + templates = append(templates, tmplBuild.Templates...) + tmplBuild, err = c.ExpandSteps(tmplBuild, mapFromTemplates(tmplBuild.Templates), r, depth-1) if err != nil { return s, err @@ -195,6 +205,7 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r * s.Secrets = secrets s.Services = services s.Environment = environment + s.Templates = templates return s, nil } @@ -338,7 +349,7 @@ func (c *client) mergeTemplate(bytes []byte, tmpl *yaml.Template, step *yaml.Ste return native.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables) case constants.PipelineTypeStarlark: //nolint:lll // ignore long line length due to return - return starlark.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables, c.StarlarkExecLimit) + return starlark.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables, c.GetStarlarkExecLimit()) default: //nolint:lll // ignore long line length due to return return &yaml.Build{}, fmt.Errorf("format of %s is unsupported", tmpl.Format) diff --git a/compiler/native/expand_test.go b/compiler/native/expand_test.go index 749bdccb2..2f036f974 100644 --- a/compiler/native/expand_test.go +++ b/compiler/native/expand_test.go @@ -9,14 +9,14 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + "github.com/gin-gonic/gin" + "github.com/google/go-cmp/cmp" + "github.com/urfave/cli/v2" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/pipeline" "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - "github.com/google/go-cmp/cmp" - - "github.com/gin-gonic/gin" - "github.com/urfave/cli/v2" ) func TestNative_ExpandStages(t *testing.T) { @@ -44,6 +44,7 @@ func TestNative_ExpandStages(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) tmpls := map[string]*yaml.Template{ @@ -98,7 +99,7 @@ func TestNative_ExpandStages(t *testing.T) { Pull: "always", }, &yaml.Step{ - Commands: []string{"./gradlew build"}, + Commands: []string{"./gradlew build", "echo gradle"}, Environment: raw.StringSliceMap{ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", "GRADLE_USER_HOME": ".gradle", @@ -144,7 +145,7 @@ func TestNative_ExpandStages(t *testing.T) { } // run test -- missing private github - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -165,7 +166,7 @@ func TestNative_ExpandStages(t *testing.T) { } // run test - compiler, err = New(c) + compiler, err = FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -226,9 +227,10 @@ func TestNative_ExpandSteps(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - testRepo := new(library.Repo) + testRepo := new(api.Repo) testRepo.SetID(1) testRepo.SetOrg("foo") @@ -301,7 +303,7 @@ func TestNative_ExpandSteps(t *testing.T) { Pull: "always", }, &yaml.Step{ - Commands: []string{"./gradlew build"}, + Commands: []string{"./gradlew build", "echo gradle"}, Environment: raw.StringSliceMap{ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", "GRADLE_USER_HOME": ".gradle", @@ -346,7 +348,7 @@ func TestNative_ExpandSteps(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -355,7 +357,7 @@ func TestNative_ExpandSteps(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth) + build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.GetTemplateDepth()) if err != nil { t.Errorf("ExpandSteps_Type%s returned err: %v", test.name, err) } @@ -404,6 +406,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) tmpls := map[string]*yaml.Template{ @@ -450,6 +453,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) { If: yaml.Rules{ Branch: []string{"main"}, }, + Operator: "and", }, }, &yaml.Step{ @@ -466,6 +470,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) { If: yaml.Rules{ Branch: []string{"dev"}, }, + Operator: "and", }, }, } @@ -613,7 +618,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) { wantEnvironment := raw.StringSliceMap{} // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -621,7 +626,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) { ruledata := new(pipeline.RuleData) ruledata.Branch = "main" - build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, ruledata, compiler.TemplateDepth) + build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, ruledata, compiler.GetTemplateDepth()) if err != nil { t.Errorf("ExpandSteps returned err: %v", err) } @@ -668,6 +673,7 @@ func TestNative_ExpandStepsStarlark(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) tmpls := map[string]*yaml.Template{ @@ -706,12 +712,12 @@ func TestNative_ExpandStepsStarlark(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } - build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Secrets: yaml.SecretSlice{}, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, new(pipeline.RuleData), compiler.TemplateDepth) + build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Secrets: yaml.SecretSlice{}, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, new(pipeline.RuleData), compiler.GetTemplateDepth()) if err != nil { t.Errorf("ExpandSteps returned err: %v", err) } @@ -758,14 +764,15 @@ func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - testBuild := new(library.Build) + testBuild := new(api.Build) testBuild.SetID(1) testBuild.SetCommit("123abc456def") - testRepo := new(library.Repo) + testRepo := new(api.Repo) testRepo.SetID(1) testRepo.SetOrg("foo") @@ -823,7 +830,7 @@ func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) { Pull: "always", }, &yaml.Step{ - Commands: []string{"./gradlew build"}, + Commands: []string{"./gradlew build", "echo test"}, Environment: raw.StringSliceMap{ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false", "GRADLE_USER_HOME": ".gradle", @@ -867,8 +874,21 @@ func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) { "star": "test3", } + wantTemplates := yaml.TemplateSlice{ + { + Name: "chain", + Source: "github.example.com/faz/baz/template_calls_template.yml", + Type: "github", + }, + { + Name: "test", + Source: "github.example.com/foo/bar/long_template.yml", + Type: "github", + }, + } + // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -877,7 +897,7 @@ func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth) + build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment, Templates: yaml.TemplateSlice{test.tmpls["chain"]}}, test.tmpls, new(pipeline.RuleData), compiler.GetTemplateDepth()) if err != nil { t.Errorf("ExpandSteps_Type%s returned err: %v", test.name, err) } @@ -897,6 +917,10 @@ func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) { if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" { t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff) } + + if diff := cmp.Diff(build.Templates, wantTemplates); diff != "" { + t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff) + } }) } } @@ -926,14 +950,15 @@ func TestNative_ExpandSteps_TemplateCallTemplate_CircularFail(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - testBuild := new(library.Build) + testBuild := new(api.Build) testBuild.SetID(1) testBuild.SetCommit("123abc456def") - testRepo := new(library.Repo) + testRepo := new(api.Repo) testRepo.SetID(1) testRepo.SetOrg("foo") @@ -970,7 +995,7 @@ func TestNative_ExpandSteps_TemplateCallTemplate_CircularFail(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -979,7 +1004,7 @@ func TestNative_ExpandSteps_TemplateCallTemplate_CircularFail(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth) + _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.GetTemplateDepth()) if err == nil { t.Errorf("ExpandSteps_Type%s should have returned an error", test.name) } @@ -1012,14 +1037,15 @@ func TestNative_ExpandSteps_CallTemplateWithRenderInline(t *testing.T) { set.String("github-url", s.URL, "doc") set.String("github-token", "", "doc") set.Int("max-template-depth", 5, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - testBuild := new(library.Build) + testBuild := new(api.Build) testBuild.SetID(1) testBuild.SetCommit("123abc456def") - testRepo := new(library.Repo) + testRepo := new(api.Repo) testRepo.SetID(1) testRepo.SetOrg("foo") @@ -1056,7 +1082,7 @@ func TestNative_ExpandSteps_CallTemplateWithRenderInline(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating new compiler returned err: %v", err) } @@ -1065,7 +1091,7 @@ func TestNative_ExpandSteps_CallTemplateWithRenderInline(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth) + _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.GetTemplateDepth()) if err == nil { t.Errorf("ExpandSteps_Type%s should have returned an error", test.name) } diff --git a/compiler/native/initialize_test.go b/compiler/native/initialize_test.go index 6a0b2d31c..ea196216a 100644 --- a/compiler/native/initialize_test.go +++ b/compiler/native/initialize_test.go @@ -7,13 +7,15 @@ import ( "reflect" "testing" - "github.com/go-vela/types/yaml" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/yaml" ) func TestNative_InitStage(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -60,7 +62,7 @@ func TestNative_InitStage(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -78,6 +80,7 @@ func TestNative_InitStage(t *testing.T) { func TestNative_InitStep(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -108,7 +111,7 @@ func TestNative_InitStep(t *testing.T) { }, } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } diff --git a/compiler/native/native.go b/compiler/native/native.go index a93c6fd50..63e526f2e 100644 --- a/compiler/native/native.go +++ b/compiler/native/native.go @@ -3,18 +3,19 @@ package native import ( + "fmt" "time" - "github.com/go-vela/server/compiler" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/compiler" "github.com/go-vela/server/compiler/registry" "github.com/go-vela/server/compiler/registry/github" - - "github.com/go-vela/types" - "github.com/go-vela/types/library" - - "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" + "github.com/go-vela/server/internal" + "github.com/go-vela/server/internal/image" ) type ModificationConfig struct { @@ -29,26 +30,26 @@ type client struct { PrivateGithub registry.Service UsePrivateGithub bool ModificationService ModificationConfig - CloneImage string - TemplateDepth int - StarlarkExecLimit uint64 - build *library.Build + settings.Compiler + + build *api.Build comment string commit string files []string local bool localTemplates []string - metadata *types.Metadata - repo *library.Repo - user *library.User + metadata *internal.Metadata + repo *api.Repo + user *api.User + labels []string } -// New returns a Pipeline implementation that integrates with the supported registries. +// FromCLIContext returns a Pipeline implementation that integrates with the supported registries. // //nolint:revive // ignore returning unexported client -func New(ctx *cli.Context) (*client, error) { - logrus.Debug("Creating registry clients from CLI configuration") +func FromCLIContext(ctx *cli.Context) (*client, error) { + logrus.Debug("creating registry clients from CLI configuration") c := new(client) @@ -69,14 +70,24 @@ func New(ctx *cli.Context) (*client, error) { c.Github = github + c.Compiler = settings.Compiler{} + + cloneImage := ctx.String("clone-image") + + // validate clone image + _, err = image.ParseWithError(cloneImage) + if err != nil { + return nil, fmt.Errorf("invalid clone image %s: %w", cloneImage, err) + } + // set the clone image to use for the injected clone step - c.CloneImage = ctx.String("clone-image") + c.SetCloneImage(cloneImage) // set the template depth to use for nested templates - c.TemplateDepth = ctx.Int("max-template-depth") + c.SetTemplateDepth(ctx.Int("max-template-depth")) // set the starlark execution step limit for compiling starlark pipelines - c.StarlarkExecLimit = ctx.Uint64("compiler-starlark-exec-limit") + c.SetStarlarkExecLimit(ctx.Uint64("compiler-starlark-exec-limit")) if ctx.Bool("github-driver") { logrus.Tracef("setting up Private GitHub Client for %s", ctx.String("github-url")) @@ -96,14 +107,14 @@ func New(ctx *cli.Context) (*client, error) { // setupGithub is a helper function to setup the // Github registry service from the CLI arguments. func setupGithub() (registry.Service, error) { - logrus.Tracef("Creating %s registry client from CLI configuration", "github") + logrus.Tracef("creating %s registry client from CLI configuration", "github") return github.New("", "") } // setupPrivateGithub is a helper function to setup the // Github registry service from the CLI arguments. func setupPrivateGithub(addr, token string) (registry.Service, error) { - logrus.Tracef("Creating private %s registry client from CLI configuration", "github") + logrus.Tracef("creating private %s registry client from CLI configuration", "github") return github.New(addr, token) } @@ -124,7 +135,7 @@ func (c *client) Duplicate() compiler.Engine { } // WithBuild sets the library build type in the Engine. -func (c *client) WithBuild(b *library.Build) compiler.Engine { +func (c *client) WithBuild(b *api.Build) compiler.Engine { if b != nil { c.build = b } @@ -174,7 +185,7 @@ func (c *client) WithLocalTemplates(templates []string) compiler.Engine { } // WithMetadata sets the compiler metadata type in the Engine. -func (c *client) WithMetadata(m *types.Metadata) compiler.Engine { +func (c *client) WithMetadata(m *internal.Metadata) compiler.Engine { if m != nil { c.metadata = m } @@ -194,7 +205,7 @@ func (c *client) WithPrivateGitHub(url, token string) compiler.Engine { } // WithRepo sets the library repo type in the Engine. -func (c *client) WithRepo(r *library.Repo) compiler.Engine { +func (c *client) WithRepo(r *api.Repo) compiler.Engine { if r != nil { c.repo = r } @@ -203,10 +214,19 @@ func (c *client) WithRepo(r *library.Repo) compiler.Engine { } // WithUser sets the library user type in the Engine. -func (c *client) WithUser(u *library.User) compiler.Engine { +func (c *client) WithUser(u *api.User) compiler.Engine { if u != nil { c.user = u } return c } + +// WithLabels sets the label(s) in the Engine. +func (c *client) WithLabels(labels []string) compiler.Engine { + if len(labels) != 0 { + c.labels = labels + } + + return c +} diff --git a/compiler/native/native_test.go b/compiler/native/native_test.go index 36be98195..6c5b6d2be 100644 --- a/compiler/native/native_test.go +++ b/compiler/native/native_test.go @@ -7,25 +7,28 @@ import ( "reflect" "testing" - "github.com/go-vela/server/compiler/registry/github" - - "github.com/go-vela/types" - "github.com/go-vela/types/library" - "github.com/urfave/cli/v2" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/compiler/registry/github" + "github.com/go-vela/server/internal" ) func TestNative_New(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) public, _ := github.New("", "") want := &client{ - Github: public, + Github: public, + Compiler: settings.CompilerMockEmpty(), } + want.SetCloneImage(defaultCloneImage) // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("New returned err: %v", err) @@ -44,6 +47,7 @@ func TestNative_New_PrivateGithub(t *testing.T) { set.Bool("github-driver", true, "doc") set.String("github-url", url, "doc") set.String("github-token", token, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) public, _ := github.New("", "") private, _ := github.New(url, token) @@ -51,10 +55,12 @@ func TestNative_New_PrivateGithub(t *testing.T) { Github: public, PrivateGithub: private, UsePrivateGithub: true, + Compiler: settings.CompilerMockEmpty(), } + want.SetCloneImage(defaultCloneImage) // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("New returned err: %v", err) @@ -73,6 +79,7 @@ func TestNative_DuplicateRetainSettings(t *testing.T) { set.Bool("github-driver", true, "doc") set.String("github-url", url, "doc") set.String("github-token", token, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) public, _ := github.New("", "") private, _ := github.New(url, token) @@ -80,10 +87,12 @@ func TestNative_DuplicateRetainSettings(t *testing.T) { Github: public, PrivateGithub: private, UsePrivateGithub: true, + Compiler: settings.CompilerMockEmpty(), } + want.SetCloneImage(defaultCloneImage) // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("New returned err: %v", err) @@ -97,15 +106,16 @@ func TestNative_DuplicateRetainSettings(t *testing.T) { func TestNative_DuplicateStripBuild(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) id := int64(1) - b := &library.Build{ID: &id} + b := &api.Build{ID: &id} - want, _ := New(c) + want, _ := FromCLIContext(c) // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -120,16 +130,17 @@ func TestNative_DuplicateStripBuild(t *testing.T) { func TestNative_WithBuild(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) id := int64(1) - b := &library.Build{ID: &id} + b := &api.Build{ID: &id} - want, _ := New(c) + want, _ := FromCLIContext(c) want.build = b // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -142,15 +153,16 @@ func TestNative_WithBuild(t *testing.T) { func TestNative_WithFiles(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) f := []string{"foo"} - want, _ := New(c) + want, _ := FromCLIContext(c) want.files = f // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -163,14 +175,15 @@ func TestNative_WithFiles(t *testing.T) { func TestNative_WithComment(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) comment := "ok to test" - want, _ := New(c) + want, _ := FromCLIContext(c) want.comment = comment // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -183,14 +196,15 @@ func TestNative_WithComment(t *testing.T) { func TestNative_WithLocal(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) local := true - want, _ := New(c) + want, _ := FromCLIContext(c) want.local = true // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -203,14 +217,15 @@ func TestNative_WithLocal(t *testing.T) { func TestNative_WithLocalTemplates(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) localTemplates := []string{"example:tmpl.yml", "exmpl:template.yml"} - want, _ := New(c) + want, _ := FromCLIContext(c) want.localTemplates = []string{"example:tmpl.yml", "exmpl:template.yml"} // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -223,33 +238,34 @@ func TestNative_WithLocalTemplates(t *testing.T) { func TestNative_WithMetadata(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, } - want, _ := New(c) + want, _ := FromCLIContext(c) want.metadata = m // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -267,15 +283,16 @@ func TestNative_WithPrivateGitHub(t *testing.T) { set.Bool("github-driver", true, "doc") set.String("github-url", url, "doc") set.String("github-token", token, "doc") + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) private, _ := github.New(url, token) - want, _ := New(c) + want, _ := FromCLIContext(c) want.PrivateGithub = private // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -288,16 +305,17 @@ func TestNative_WithPrivateGitHub(t *testing.T) { func TestNative_WithRepo(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) id := int64(1) - r := &library.Repo{ID: &id} + r := &api.Repo{ID: &id} - want, _ := New(c) + want, _ := FromCLIContext(c) want.repo = r // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -310,16 +328,17 @@ func TestNative_WithRepo(t *testing.T) { func TestNative_WithUser(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) id := int64(1) - u := &library.User{ID: &id} + u := &api.User{ID: &id} - want, _ := New(c) + want, _ := FromCLIContext(c) want.user = u // run test - got, err := New(c) + got, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -328,3 +347,24 @@ func TestNative_WithUser(t *testing.T) { t.Errorf("WithUser is %v, want %v", got, want) } } + +func TestNative_WithLabels(t *testing.T) { + // setup types + set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") + c := cli.NewContext(nil, set, nil) + + labels := []string{"documentation", "enhancement"} + want, _ := FromCLIContext(c) + want.labels = []string{"documentation", "enhancement"} + + // run test + got, err := FromCLIContext(c) + if err != nil { + t.Errorf("Unable to create new compiler: %v", err) + } + + if !reflect.DeepEqual(got.WithLabels(labels), want) { + t.Errorf("WithLocalTemplates is %v, want %v", got, want) + } +} diff --git a/compiler/native/parse.go b/compiler/native/parse.go index 83a401ddf..0cd7a5819 100644 --- a/compiler/native/parse.go +++ b/compiler/native/parse.go @@ -7,12 +7,12 @@ import ( "io" "os" + "github.com/buildkite/yaml" + "github.com/go-vela/server/compiler/template/native" "github.com/go-vela/server/compiler/template/starlark" "github.com/go-vela/types/constants" types "github.com/go-vela/types/yaml" - - "github.com/buildkite/yaml" ) // ParseRaw converts an object to a string. @@ -71,7 +71,7 @@ func (c *client) Parse(v interface{}, pipelineType string, template *types.Templ // capture the raw pipeline configuration raw = []byte(parsedRaw) - p, err = starlark.RenderBuild(template.Name, parsedRaw, c.EnvironmentBuild(), template.Variables, c.StarlarkExecLimit) + p, err = starlark.RenderBuild(template.Name, parsedRaw, c.EnvironmentBuild(), template.Variables, c.GetStarlarkExecLimit()) if err != nil { return nil, raw, err } diff --git a/compiler/native/parse_test.go b/compiler/native/parse_test.go index 7176438e1..e0aa9c512 100644 --- a/compiler/native/parse_test.go +++ b/compiler/native/parse_test.go @@ -10,19 +10,18 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" + "github.com/google/go-cmp/cmp" + "github.com/urfave/cli/v2" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - "github.com/google/go-cmp/cmp" - - "github.com/urfave/cli/v2" ) func TestNative_Parse_Metadata_Bytes(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -50,7 +49,7 @@ func TestNative_Parse_Metadata_Bytes(t *testing.T) { func TestNative_Parse_Metadata_File(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -80,7 +79,7 @@ func TestNative_Parse_Metadata_File(t *testing.T) { func TestNative_Parse_Metadata_Invalid(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) // run test got, _, err := client.Parse(nil, "", new(yaml.Template)) @@ -96,7 +95,7 @@ func TestNative_Parse_Metadata_Invalid(t *testing.T) { func TestNative_Parse_Metadata_Path(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -119,7 +118,7 @@ func TestNative_Parse_Metadata_Path(t *testing.T) { func TestNative_Parse_Metadata_Reader(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -147,7 +146,7 @@ func TestNative_Parse_Metadata_Reader(t *testing.T) { func TestNative_Parse_Metadata_String(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -175,7 +174,7 @@ func TestNative_Parse_Metadata_String(t *testing.T) { func TestNative_Parse_Parameters(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Metadata: yaml.Metadata{ Environment: []string{"steps", "services", "secrets"}, @@ -222,7 +221,7 @@ func TestNative_Parse_Parameters(t *testing.T) { func TestNative_Parse_StagesPipeline(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -351,7 +350,7 @@ func TestNative_Parse_StagesPipeline(t *testing.T) { func TestNative_Parse_StepsPipeline(t *testing.T) { // setup types tBool := true - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Version: "1", Metadata: yaml.Metadata{ @@ -453,7 +452,7 @@ func TestNative_Parse_StepsPipeline(t *testing.T) { func TestNative_Parse_Secrets(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Metadata: yaml.Metadata{ Environment: []string{"steps", "services", "secrets"}, @@ -523,7 +522,7 @@ func TestNative_Parse_Secrets(t *testing.T) { func TestNative_Parse_Stages(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Metadata: yaml.Metadata{ Environment: []string{"steps", "services", "secrets"}, @@ -599,7 +598,7 @@ func TestNative_Parse_Stages(t *testing.T) { func TestNative_Parse_Steps(t *testing.T) { // setup types - client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + client, _ := FromCLIContext(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) want := &yaml.Build{ Metadata: yaml.Metadata{ Environment: []string{"steps", "services", "secrets"}, @@ -858,7 +857,7 @@ func TestNative_ParseString_Metadata(t *testing.T) { type FailReader struct{} -func (FailReader) Read(p []byte) (n int, err error) { +func (FailReader) Read(_ []byte) (n int, err error) { return 0, errors.New("this is a reader that fails when you try to read") } @@ -910,11 +909,13 @@ func Test_client_Parse(t *testing.T) { } var c *client - if tt.args.pipelineType == "nil" { + + pipelineType := tt.args.pipelineType + if pipelineType == "nil" { c = &client{} } else { c = &client{ - repo: &library.Repo{PipelineType: &tt.args.pipelineType}, + repo: &api.Repo{PipelineType: &pipelineType}, } } diff --git a/compiler/native/script_test.go b/compiler/native/script_test.go index 2ce2ada59..0f03e6865 100644 --- a/compiler/native/script_test.go +++ b/compiler/native/script_test.go @@ -7,15 +7,16 @@ import ( "reflect" "testing" - "github.com/go-vela/types/yaml" "github.com/google/go-cmp/cmp" - "github.com/urfave/cli/v2" + + "github.com/go-vela/types/yaml" ) func TestNative_ScriptStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) baseEnv := environment(nil, nil, nil, nil) @@ -87,7 +88,7 @@ func TestNative_ScriptStages(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -105,6 +106,7 @@ func TestNative_ScriptStages(t *testing.T) { func TestNative_ScriptSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) emptyEnv := environment(nil, nil, nil, nil) @@ -313,7 +315,7 @@ func TestNative_ScriptSteps(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } diff --git a/compiler/native/settings.go b/compiler/native/settings.go new file mode 100644 index 000000000..12997ec9c --- /dev/null +++ b/compiler/native/settings.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +package native + +import ( + "github.com/go-vela/server/api/types/settings" +) + +// GetSettings retrieves the api settings type. +func (c *client) GetSettings() settings.Compiler { + return c.Compiler +} + +// SetSettings sets the api settings type. +func (c *client) SetSettings(s *settings.Platform) { + if s != nil { + c.SetCloneImage(s.GetCloneImage()) + c.SetTemplateDepth(s.GetTemplateDepth()) + c.SetStarlarkExecLimit(s.GetStarlarkExecLimit()) + } +} diff --git a/compiler/native/substitute.go b/compiler/native/substitute.go index e143cfc1b..9bbc717cd 100644 --- a/compiler/native/substitute.go +++ b/compiler/native/substitute.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/buildkite/yaml" - "github.com/drone/envsubst" types "github.com/go-vela/types/yaml" diff --git a/compiler/native/substitute_test.go b/compiler/native/substitute_test.go index a02d59f8f..0be8eac03 100644 --- a/compiler/native/substitute_test.go +++ b/compiler/native/substitute_test.go @@ -6,10 +6,10 @@ import ( "flag" "testing" + "github.com/google/go-cmp/cmp" "github.com/urfave/cli/v2" "github.com/go-vela/types/yaml" - "github.com/google/go-cmp/cmp" ) func Test_client_SubstituteStages(t *testing.T) { @@ -19,6 +19,7 @@ func Test_client_SubstituteStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) tests := []struct { @@ -112,7 +113,7 @@ func Test_client_SubstituteStages(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } @@ -137,6 +138,7 @@ func Test_client_SubstituteSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) tests := []struct { @@ -236,7 +238,7 @@ func Test_client_SubstituteSteps(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Creating compiler returned err: %v", err) } diff --git a/compiler/native/testdata/golang_inline_stages.yml b/compiler/native/testdata/golang_inline_stages.yml index 59b390d47..f8ca4d849 100644 --- a/compiler/native/testdata/golang_inline_stages.yml +++ b/compiler/native/testdata/golang_inline_stages.yml @@ -8,6 +8,9 @@ stages: steps: - name: {{ $stage }} image: {{ default "alpine" $.image }} + ruleset: + event: tag + tag: v* commands: - echo hello from {{ $stage }} {{ end }} \ No newline at end of file diff --git a/compiler/native/testdata/inline_with_stages.yml b/compiler/native/testdata/inline_with_stages.yml index 89d79b5e3..0834b976d 100644 --- a/compiler/native/testdata/inline_with_stages.yml +++ b/compiler/native/testdata/inline_with_stages.yml @@ -21,4 +21,11 @@ stages: - name: test image: alpine commands: - - echo from inline \ No newline at end of file + - echo from inline + - name: ruleset + image: alpine + ruleset: + event: push + branch: main + commands: + - echo from inline ruleset \ No newline at end of file diff --git a/compiler/native/testdata/inline_with_steps.yml b/compiler/native/testdata/inline_with_steps.yml index 28348cf8d..7bee378dd 100644 --- a/compiler/native/testdata/inline_with_steps.yml +++ b/compiler/native/testdata/inline_with_steps.yml @@ -17,4 +17,17 @@ steps: - name: test image: alpine commands: - - echo from inline \ No newline at end of file + - echo from inline + - name: ruleset + image: alpine + ruleset: + event: deployment + target: production + commands: + - echo from inline ruleset + - name: other ruleset + image: alpine + ruleset: + path: [ "src/*", "test/*" ] + commands: + - echo from inline ruleset \ No newline at end of file diff --git a/compiler/native/testdata/long_template.yml b/compiler/native/testdata/long_template.yml index c9f92ee44..5cc78d9b7 100644 --- a/compiler/native/testdata/long_template.yml +++ b/compiler/native/testdata/long_template.yml @@ -23,6 +23,7 @@ steps: - name: build commands: - ./gradlew build + - echo {{ .VELA_TEMPLATE_NAME }} environment: {{ .environment }} image: {{ .image }} {{ .pull_policy }} diff --git a/compiler/native/testdata/steps_pipeline_template.yml b/compiler/native/testdata/steps_pipeline_template.yml index 7d8e8db28..beaca0fb8 100644 --- a/compiler/native/testdata/steps_pipeline_template.yml +++ b/compiler/native/testdata/steps_pipeline_template.yml @@ -8,6 +8,11 @@ templates: source: github.example.com/foo/bar/long_template.yml type: github + - name: starlark + source: github.example.com/github/octocat/starlark_inline_steps.star + format: starlark + type: github + steps: # would execute the following steps: # 1. sample_get_dependencies @@ -21,6 +26,12 @@ steps: environment: "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }" pull_policy: "pull: true" + - name: starlark + ruleset: + event: push + template: + name: starlark + - name: docker image: plugins/docker:18.09 parameters: diff --git a/compiler/native/transform_test.go b/compiler/native/transform_test.go index 743f9a46c..bcd318103 100644 --- a/compiler/native/transform_test.go +++ b/compiler/native/transform_test.go @@ -7,33 +7,34 @@ import ( "reflect" "testing" - "github.com/go-vela/types" + "github.com/urfave/cli/v2" + + "github.com/go-vela/server/internal" "github.com/go-vela/types/pipeline" "github.com/go-vela/types/yaml" - - "github.com/urfave/cli/v2" ) func TestNative_TransformStages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -220,7 +221,7 @@ func TestNative_TransformStages(t *testing.T) { // run tests for _, test := range tests { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("unable to create new compiler: %v", err) } @@ -257,23 +258,24 @@ func TestNative_TransformStages(t *testing.T) { func TestNative_TransformSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -439,7 +441,7 @@ func TestNative_TransformSteps(t *testing.T) { // run tests for _, test := range tests { - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("unable to create new compiler: %v", err) } diff --git a/compiler/native/validate.go b/compiler/native/validate.go index e3c79d862..b220b34a7 100644 --- a/compiler/native/validate.go +++ b/compiler/native/validate.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/go-multierror" + "github.com/go-vela/types/constants" "github.com/go-vela/types/yaml" ) @@ -123,6 +124,10 @@ func validateStages(s yaml.StageSlice) error { // validateSteps is a helper function that verifies the // steps block in the yaml configuration is valid. func validateSteps(s yaml.StepSlice) error { + reportCount := 0 + + reportMap := make(map[string]string) + for _, step := range s { if len(step.Name) == 0 { return fmt.Errorf("no name provided for step") @@ -136,6 +141,15 @@ func validateSteps(s yaml.StepSlice) error { continue } + if s, ok := reportMap[step.ReportAs]; ok { + return fmt.Errorf("report_as to %s for step %s is already targeted by step %s", step.ReportAs, step.Name, s) + } + + if len(step.ReportAs) > 0 { + reportMap[step.ReportAs] = step.Name + reportCount++ + } + if len(step.Commands) == 0 && len(step.Environment) == 0 && len(step.Parameters) == 0 && len(step.Secrets) == 0 && len(step.Template.Name) == 0 && !step.Detach { @@ -143,5 +157,9 @@ func validateSteps(s yaml.StepSlice) error { } } + if reportCount > constants.ReportStepStatusLimit { + return fmt.Errorf("report_as is limited to %d steps, counted %d", constants.ReportStepStatusLimit, reportCount) + } + return nil } diff --git a/compiler/native/validate_test.go b/compiler/native/validate_test.go index 550bbd3ae..226c75aa9 100644 --- a/compiler/native/validate_test.go +++ b/compiler/native/validate_test.go @@ -4,23 +4,25 @@ package native import ( "flag" + "fmt" "testing" + "github.com/urfave/cli/v2" + "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - - "github.com/urfave/cli/v2" ) func TestNative_Validate_NoVersion(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) p := &yaml.Build{} // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -34,6 +36,7 @@ func TestNative_Validate_NoVersion(t *testing.T) { func TestNative_Validate_NoStagesOrSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) p := &yaml.Build{ @@ -41,7 +44,7 @@ func TestNative_Validate_NoStagesOrSteps(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -55,6 +58,7 @@ func TestNative_Validate_NoStagesOrSteps(t *testing.T) { func TestNative_Validate_StagesAndSteps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -84,7 +88,7 @@ func TestNative_Validate_StagesAndSteps(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -98,6 +102,7 @@ func TestNative_Validate_StagesAndSteps(t *testing.T) { func TestNative_Validate_Services(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -121,7 +126,7 @@ func TestNative_Validate_Services(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -135,6 +140,7 @@ func TestNative_Validate_Services(t *testing.T) { func TestNative_Validate_Services_NoName(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -158,7 +164,7 @@ func TestNative_Validate_Services_NoName(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -172,6 +178,7 @@ func TestNative_Validate_Services_NoName(t *testing.T) { func TestNative_Validate_Services_NoImage(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -195,7 +202,7 @@ func TestNative_Validate_Services_NoImage(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -209,6 +216,7 @@ func TestNative_Validate_Services_NoImage(t *testing.T) { func TestNative_Validate_Stages(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -230,7 +238,7 @@ func TestNative_Validate_Stages(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -244,6 +252,7 @@ func TestNative_Validate_Stages(t *testing.T) { func TestNative_Validate_Stages_NoName(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -264,7 +273,7 @@ func TestNative_Validate_Stages_NoName(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -278,6 +287,7 @@ func TestNative_Validate_Stages_NoName(t *testing.T) { func TestNative_Validate_Stages_NoStepName(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -298,7 +308,7 @@ func TestNative_Validate_Stages_NoStepName(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -312,6 +322,7 @@ func TestNative_Validate_Stages_NoStepName(t *testing.T) { func TestNative_Validate_Stages_NoImage(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -332,7 +343,7 @@ func TestNative_Validate_Stages_NoImage(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -346,6 +357,7 @@ func TestNative_Validate_Stages_NoImage(t *testing.T) { func TestNative_Validate_Stages_NoCommands(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -366,7 +378,7 @@ func TestNative_Validate_Stages_NoCommands(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -380,6 +392,7 @@ func TestNative_Validate_Stages_NoCommands(t *testing.T) { func TestNative_Validate_Stages_NeedsSelfReference(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -402,7 +415,7 @@ func TestNative_Validate_Stages_NeedsSelfReference(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -416,6 +429,7 @@ func TestNative_Validate_Stages_NeedsSelfReference(t *testing.T) { func TestNative_Validate_Steps(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -432,7 +446,7 @@ func TestNative_Validate_Steps(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -446,6 +460,7 @@ func TestNative_Validate_Steps(t *testing.T) { func TestNative_Validate_Steps_NoName(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) p := &yaml.Build{ @@ -460,7 +475,7 @@ func TestNative_Validate_Steps_NoName(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -474,6 +489,7 @@ func TestNative_Validate_Steps_NoName(t *testing.T) { func TestNative_Validate_Steps_NoImage(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -489,7 +505,7 @@ func TestNative_Validate_Steps_NoImage(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } @@ -503,6 +519,7 @@ func TestNative_Validate_Steps_NoImage(t *testing.T) { func TestNative_Validate_Steps_NoCommands(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") c := cli.NewContext(nil, set, nil) str := "foo" @@ -518,12 +535,91 @@ func TestNative_Validate_Steps_NoCommands(t *testing.T) { } // run test - compiler, err := New(c) + compiler, err := FromCLIContext(c) + if err != nil { + t.Errorf("Unable to create new compiler: %v", err) + } + + err = compiler.Validate(p) + if err == nil { + t.Errorf("Validate should have returned err") + } +} + +func TestNative_Validate_Steps_ExceedReportAs(t *testing.T) { + // setup types + set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") + c := cli.NewContext(nil, set, nil) + + str := "foo" + + reportSteps := yaml.StepSlice{} + + for i := 0; i < 12; i++ { + reportStep := &yaml.Step{ + Commands: raw.StringSlice{"echo hello"}, + Image: "alpine", + Name: fmt.Sprintf("%s-%d", str, i), + Pull: "always", + ReportAs: fmt.Sprintf("step-%d", i), + } + reportSteps = append(reportSteps, reportStep) + } + + p := &yaml.Build{ + Version: "v1", + Steps: reportSteps, + } + + // run test + compiler, err := FromCLIContext(c) if err != nil { t.Errorf("Unable to create new compiler: %v", err) } err = compiler.Validate(p) + + if err == nil { + t.Errorf("Validate should have returned err") + } +} + +func TestNative_Validate_MultiReportAs(t *testing.T) { + // setup types + set := flag.NewFlagSet("test", 0) + set.String("clone-image", defaultCloneImage, "doc") + c := cli.NewContext(nil, set, nil) + + str := "foo" + p := &yaml.Build{ + Version: "v1", + Steps: yaml.StepSlice{ + &yaml.Step{ + Commands: raw.StringSlice{"echo hello"}, + Image: "alpine", + Name: str, + Pull: "always", + ReportAs: "bar", + }, + &yaml.Step{ + Commands: raw.StringSlice{"echo hello"}, + Image: "alpine", + Name: str + "-2", + Pull: "always", + ReportAs: "bar", + }, + }, + } + + // run test + compiler, err := FromCLIContext(c) + if err != nil { + t.Errorf("Unable to create new compiler: %v", err) + } + + err = compiler.Validate(p) + if err == nil { t.Errorf("Validate should have returned err") } diff --git a/compiler/registry/github/github.go b/compiler/registry/github/github.go index 5988f891d..59b7b4ba6 100644 --- a/compiler/registry/github/github.go +++ b/compiler/registry/github/github.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/google/go-github/v59/github" + "github.com/google/go-github/v62/github" "golang.org/x/oauth2" ) diff --git a/compiler/registry/github/github_test.go b/compiler/registry/github/github_test.go index 688d7948c..b8f6278f8 100644 --- a/compiler/registry/github/github_test.go +++ b/compiler/registry/github/github_test.go @@ -10,7 +10,7 @@ import ( "reflect" "testing" - "github.com/google/go-github/v59/github" + "github.com/google/go-github/v62/github" "golang.org/x/oauth2" ) diff --git a/compiler/registry/github/parse.go b/compiler/registry/github/parse.go index 45657ed0a..30908e75b 100644 --- a/compiler/registry/github/parse.go +++ b/compiler/registry/github/parse.go @@ -6,9 +6,9 @@ import ( "fmt" "strings" - "github.com/go-vela/server/compiler/registry" - "github.com/goware/urlx" + + "github.com/go-vela/server/compiler/registry" ) // Parse creates the registry source object from diff --git a/compiler/registry/github/template.go b/compiler/registry/github/template.go index 03d503b82..f09b8b7ef 100644 --- a/compiler/registry/github/template.go +++ b/compiler/registry/github/template.go @@ -7,15 +7,14 @@ import ( "fmt" "net/http" - "github.com/go-vela/server/compiler/registry" - - "github.com/go-vela/types/library" + "github.com/google/go-github/v62/github" - "github.com/google/go-github/v59/github" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/compiler/registry" ) // Template captures the templated pipeline configuration from the GitHub repo. -func (c *client) Template(u *library.User, s *registry.Source) ([]byte, error) { +func (c *client) Template(u *api.User, s *registry.Source) ([]byte, error) { // use default GitHub OAuth client we provide cli := c.Github if u != nil { diff --git a/compiler/registry/github/template_test.go b/compiler/registry/github/template_test.go index dd55fda7d..e6b3f2696 100644 --- a/compiler/registry/github/template_test.go +++ b/compiler/registry/github/template_test.go @@ -9,11 +9,10 @@ import ( "reflect" "testing" - "github.com/go-vela/server/compiler/registry" - - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/compiler/registry" ) func TestGithub_Template(t *testing.T) { @@ -36,7 +35,7 @@ func TestGithub_Template(t *testing.T) { // setup types str := "foo" - u := &library.User{ + u := &api.User{ Name: &str, Token: &str, } @@ -97,7 +96,7 @@ func TestGithub_TemplateSourceRef(t *testing.T) { // setup types str := "foo" - u := &library.User{ + u := &api.User{ Name: &str, Token: &str, } @@ -163,7 +162,7 @@ func TestGithub_TemplateEmptySourceRef(t *testing.T) { // setup types str := "foo" - u := &library.User{ + u := &api.User{ Name: &str, Token: &str, } @@ -222,7 +221,7 @@ func TestGithub_Template_BadRequest(t *testing.T) { // setup types str := "foo" - u := &library.User{ + u := &api.User{ Name: &str, Token: &str, } @@ -272,7 +271,7 @@ func TestGithub_Template_NotFound(t *testing.T) { // setup types str := "foo" - u := &library.User{ + u := &api.User{ Name: &str, Token: &str, } diff --git a/compiler/registry/registry.go b/compiler/registry/registry.go index 2ea9caffe..e3116f979 100644 --- a/compiler/registry/registry.go +++ b/compiler/registry/registry.go @@ -2,7 +2,9 @@ package registry -import "github.com/go-vela/types/library" +import ( + api "github.com/go-vela/server/api/types" +) // Service represents the interface for Vela integrating // with the different supported template registries. @@ -13,5 +15,5 @@ type Service interface { // Template defines a function that captures the // templated pipeline configuration from a repo. - Template(*library.User, *Source) ([]byte, error) + Template(*api.User, *Source) ([]byte, error) } diff --git a/compiler/template/native/render.go b/compiler/template/native/render.go index d4185a3d3..a31f642b6 100644 --- a/compiler/template/native/render.go +++ b/compiler/template/native/render.go @@ -7,13 +7,11 @@ import ( "fmt" "text/template" - "github.com/go-vela/types/raw" - - types "github.com/go-vela/types/yaml" - "github.com/Masterminds/sprig/v3" - "github.com/buildkite/yaml" + + "github.com/go-vela/types/raw" + types "github.com/go-vela/types/yaml" ) // Render combines the template with the step in the yaml pipeline. diff --git a/compiler/template/starlark/convert.go b/compiler/template/starlark/convert.go index 9e7bfa70a..d571cd0ef 100644 --- a/compiler/template/starlark/convert.go +++ b/compiler/template/starlark/convert.go @@ -5,8 +5,9 @@ package starlark import ( "strings" - "github.com/go-vela/types/raw" "go.starlark.net/starlark" + + "github.com/go-vela/types/raw" ) // convertTemplateVars takes template variables and converts diff --git a/compiler/template/starlark/convert_test.go b/compiler/template/starlark/convert_test.go index 49101117e..3e868a4ba 100644 --- a/compiler/template/starlark/convert_test.go +++ b/compiler/template/starlark/convert_test.go @@ -6,8 +6,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/raw" "go.starlark.net/starlark" + + "github.com/go-vela/types/raw" ) func TestStarlark_Render_convertTemplateVars(t *testing.T) { diff --git a/compiler/template/starlark/render.go b/compiler/template/starlark/render.go index 4ff2c59ed..93b37cd78 100644 --- a/compiler/template/starlark/render.go +++ b/compiler/template/starlark/render.go @@ -7,12 +7,12 @@ import ( "errors" "fmt" - "github.com/go-vela/types/raw" + yaml "github.com/buildkite/yaml" + "go.starlark.net/starlark" "go.starlark.net/starlarkstruct" - yaml "github.com/buildkite/yaml" + "github.com/go-vela/types/raw" types "github.com/go-vela/types/yaml" - "go.starlark.net/starlark" ) var ( diff --git a/compiler/template/starlark/render_test.go b/compiler/template/starlark/render_test.go index d07a74a6f..a730f1088 100644 --- a/compiler/template/starlark/render_test.go +++ b/compiler/template/starlark/render_test.go @@ -7,9 +7,10 @@ import ( "testing" goyaml "github.com/buildkite/yaml" + "github.com/google/go-cmp/cmp" + "github.com/go-vela/types/raw" "github.com/go-vela/types/yaml" - "github.com/google/go-cmp/cmp" ) func TestStarlark_Render(t *testing.T) { diff --git a/constants/limit.go b/constants/limit.go new file mode 100644 index 000000000..8b492ec3e --- /dev/null +++ b/constants/limit.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +package constants + +// Limits and constraints. +const ( + // BuildLimitMin defines the minimum value for repo concurrent build limit. + BuildLimitMin = 1 + + // BuildLimitMax defines the maximum value for repo concurrent build limit. + BuildLimitMax = 30 + + // BuildLimitDefault defines the default value for repo concurrent build limit. + BuildLimitDefault = 10 + + // BuildTimeoutMin defines the minimum value in minutes for repo build timeout. + BuildTimeoutMin = 1 + + // BuildTimeoutMax defines the maximum value in minutes for repo build timeout. + BuildTimeoutMax = 90 + + // BuildTimeoutDefault defines the default value in minutes for repo build timeout. + BuildTimeoutDefault = 30 + + // FavoritesMaxSize defines the maximum size in characters for user favorites. + FavoritesMaxSize = 5000 + + // RunningBuildIDsMaxSize defines the maximum size in characters for worker RunningBuildIDs. + RunningBuildIDsMaxSize = 500 + + // TopicsMaxSize defines the maximum size in characters for repo topics. Ex: GitHub has a 20-topic, 50-char limit. + TopicsMaxSize = 1020 + + // DeployBuildsMaxSize defines the maximum size in characters for deployment builds. + DeployBuildsMaxSize = 500 + + // ReportStepStatusLimit defines the maximum number of steps in a pipeline that may report their status to the SCM. + ReportStepStatusLimit = 10 + + // DashboardRepoLimit defines the maximum number of repos that can be assigned to a dashboard. + DashboardRepoLimit = 10 + + // UserDashboardLimit defines the maximum number of dashboards that can be assigned to a user. + UserDashboardLimit = 10 + + // DashboardAdminMaxSize defines the maximum size in characters for dashboard admins. + DashboardAdminMaxSize = 5000 +) diff --git a/constants/secret.go b/constants/secret.go new file mode 100644 index 000000000..bb794f025 --- /dev/null +++ b/constants/secret.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Secret types. +const ( + // SecretPullBuild defines the pull policy type for a secret. + SecretPullBuild = "build_start" + + // SecretPullStep defines the pull policy type for a secret. + SecretPullStep = "step_start" + + // SecretOrg defines the secret type for a secret scoped to a specific org. + SecretOrg = "org" + + // SecretRepo defines the secret type for a secret scoped to a specific repo. + SecretRepo = "repo" + + // SecretShared defines the secret type for a secret shared across the installation. + SecretShared = "shared" + + // SecretMask defines the secret mask to be used in place of secret values returned to users. + SecretMask = "[secure]" + + // SecretLogMask defines the secret mask to be used when distributing logs that contain secrets. + SecretLogMask = "***" + + // SecretRestrictedCharacters defines the set of characters that a secret name cannot contain. + // This matches the following characters: + // Equal Sign = + // Null Character \x00 + SecretRestrictedCharacters = "=\x00" +) diff --git a/constants/status.go b/constants/status.go new file mode 100644 index 000000000..489f4a7df --- /dev/null +++ b/constants/status.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Build and step statuses. +const ( + // StatusError defines the status type for build and step error statuses. + StatusError = "error" + + // StatusFailure defines the status type for build and step failure statuses. + StatusFailure = "failure" + + // StatusKilled defines the status type for build and step killed statuses. + StatusKilled = "killed" + + // StatusCanceled defines the status type for build and step canceled statuses. + StatusCanceled = "canceled" + + // StatusPending defines the status type for build and step pending statuses. + StatusPending = "pending" + + // StatusPendingApproval defines the status type for a build waiting to be approved to run. + StatusPendingApproval = "pending approval" + + // StatusRunning defines the status type for build and step running statuses. + StatusRunning = "running" + + // StatusSuccess defines the status type for build and step success statuses. + StatusSuccess = "success" + + // StatusSkipped defines the status type for build and step skipped statuses. + StatusSkipped = "skipped" +) diff --git a/constants/table.go b/constants/table.go new file mode 100644 index 000000000..736335482 --- /dev/null +++ b/constants/table.go @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +package constants + +// Database tables. +const ( + // TableDashboard defines the table type for the database dashboards table. + TableDashboard = "dashboards" + + // TableJWK defines the table type for the database jwks table. + TableJWK = "jwks" +) diff --git a/constants/token.go b/constants/token.go new file mode 100644 index 000000000..793b1f7c7 --- /dev/null +++ b/constants/token.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Constants for tokens. +const ( + // RefreshTokenName is the name associated with the refresh token. + RefreshTokenName = "vela_refresh_token" + + // UserAccessTokenType is the name associated with the user access token type. + UserAccessTokenType = "UserAccess" + + // UserRefreshTokenType is the name associated with the user refresh token type. + UserRefreshTokenType = "UserRefresh" + + // WorkerAuthTokenType is the name associated with the worker authentication token type. + WorkerAuthTokenType = "WorkerAuth" + + // WorkerRegisterTokenType is the name associated with the worker registration token type. + WorkerRegisterTokenType = "WorkerRegister" + + // WorkerBuildTokenType is the name associated with the worker build token type. + WorkerBuildTokenType = "WorkerBuild" + + // ServerWorkerTokenType is the name associated with the server-worker symmetric token. + ServerWorkerTokenType = "ServerWorker" + + // IDRequestTokenType is the name associated with the id request token type. + IDRequestTokenType = "IDRequest" + + // IDTokenType is the name associated with the id token type. + IDTokenType = "ID" +) diff --git a/constants/visibility.go b/constants/visibility.go new file mode 100644 index 000000000..af2f35b04 --- /dev/null +++ b/constants/visibility.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 + +package constants + +// Repo visibility types. +const ( + // VisibilityPublic defines the visibility type for allowing any + // users in Vela to access their repo regardless of the access + // defined in the source control system. + VisibilityPublic = "public" + + // VisibilityPrivate defines the visibility type for only allowing + // users in Vela with pre-defined access in the source control + // system to access their repo. + VisibilityPrivate = "private" +) diff --git a/database/build/build.go b/database/build/build.go index 8342471e7..b425731f3 100644 --- a/database/build/build.go +++ b/database/build/build.go @@ -6,15 +6,17 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( // config represents the settings required to create the engine that implements the BuildInterface interface. config struct { + // specifies the encryption key to use for the Build engine + EncryptionKey string // specifies to skip creating tables and indexes for the Build engine SkipCreation bool } @@ -60,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating build database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of builds table and indexes in the database") + e.logger.Warning("skipping creation of builds table and indexes") return e, nil } diff --git a/database/build/build_test.go b/database/build/build_test.go index 2478b2ded..383c0573c 100644 --- a/database/build/build_test.go +++ b/database/build/build_test.go @@ -10,10 +10,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" - "github.com/go-vela/types/raw" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -115,6 +112,34 @@ func TestBuild_New(t *testing.T) { } } +// TEST RESOURCES + +// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values +// that are otherwise not easily compared. These typically would be values generated +// before adding or updating them in the database. +// +// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime +type AnyArgument struct{} + +// Match satisfies sqlmock.Argument interface. +func (a AnyArgument) Match(_ driver.Value) bool { + return true +} + +// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience. +type NowTimestamp struct{} + +// Match satisfies sqlmock.Argument interface. +func (t NowTimestamp) Match(v driver.Value) bool { + ts, ok := v.(int64) + if !ok { + return false + } + now := time.Now().Unix() + + return now-ts < 10 +} + // testPostgres is a helper function to create a Postgres engine for testing. func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { // create the new mock sql database @@ -147,6 +172,7 @@ func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { WithClient(_postgres), WithLogger(logrus.NewEntry(logrus.StandardLogger())), WithSkipCreation(false), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), ) if err != nil { t.Errorf("unable to create new postgres build engine: %v", err) @@ -170,6 +196,7 @@ func testSqlite(t *testing.T) *engine { WithClient(_sqlite), WithLogger(logrus.NewEntry(logrus.StandardLogger())), WithSkipCreation(false), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), ) if err != nil { t.Errorf("unable to create new sqlite build engine: %v", err) @@ -177,120 +204,3 @@ func testSqlite(t *testing.T) *engine { return _engine } - -// testBuild is a test helper function to create a library -// Build type with all fields set to their zero values. -func testBuild() *library.Build { - return &library.Build{ - ID: new(int64), - RepoID: new(int64), - PipelineID: new(int64), - Number: new(int), - Parent: new(int), - Event: new(string), - EventAction: new(string), - Status: new(string), - Error: new(string), - Enqueued: new(int64), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Deploy: new(string), - DeployNumber: new(int64), - Clone: new(string), - Source: new(string), - Title: new(string), - Message: new(string), - Commit: new(string), - Sender: new(string), - Author: new(string), - Email: new(string), - Link: new(string), - Branch: new(string), - Ref: new(string), - BaseRef: new(string), - HeadRef: new(string), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - ApprovedAt: new(int64), - ApprovedBy: new(string), - } -} - -// testDeployment is a test helper function to create a library -// Repo type with all fields set to their zero values. -func testDeployment() *library.Deployment { - builds := []*library.Build{} - return &library.Deployment{ - ID: new(int64), - RepoID: new(int64), - Number: new(int64), - URL: new(string), - Commit: new(string), - Ref: new(string), - Task: new(string), - Target: new(string), - Description: new(string), - Payload: new(raw.StringSliceMap), - CreatedAt: new(int64), - CreatedBy: new(string), - Builds: builds, - } -} - -// testRepo is a test helper function to create a library -// Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ - ID: new(int64), - UserID: new(int64), - BuildLimit: new(int64), - Timeout: new(int64), - Counter: new(int), - PipelineType: new(string), - Hash: new(string), - Org: new(string), - Name: new(string), - FullName: new(string), - Link: new(string), - Clone: new(string), - Branch: new(string), - Visibility: new(string), - PreviousName: new(string), - Private: new(bool), - Trusted: new(bool), - Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), - } -} - -// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values -// that are otherwise not easily compared. These typically would be values generated -// before adding or updating them in the database. -// -// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime -type AnyArgument struct{} - -// Match satisfies sqlmock.Argument interface. -func (a AnyArgument) Match(_ driver.Value) bool { - return true -} - -// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience. -type NowTimestamp struct{} - -// Match satisfies sqlmock.Argument interface. -func (t NowTimestamp) Match(v driver.Value) bool { - ts, ok := v.(int64) - if !ok { - return false - } - now := time.Now().Unix() - - return now-ts < 10 -} diff --git a/database/build/clean.go b/database/build/clean.go index 8e8f17836..b7d876d16 100644 --- a/database/build/clean.go +++ b/database/build/clean.go @@ -6,22 +6,23 @@ import ( "context" "time" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CleanBuilds updates builds to an error with a provided message with a created timestamp prior to a defined moment. func (e *engine) CleanBuilds(ctx context.Context, msg string, before int64) (int64, error) { - logrus.Tracef("cleaning pending or running builds in the database created prior to %d", before) + logrus.Tracef("cleaning pending or running builds created prior to %d", before) - b := new(library.Build) + b := new(api.Build) b.SetStatus(constants.StatusError) b.SetError(msg) b.SetFinished(time.Now().UTC().Unix()) - build := database.BuildFromLibrary(b) + build := types.BuildFromAPI(b) // send query to the database result := e.client. diff --git a/database/build/clean_test.go b/database/build/clean_test.go index 28eb29195..179dd2fb8 100644 --- a/database/build/clean_test.go +++ b/database/build/clean_test.go @@ -8,35 +8,56 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CleanBuilds(t *testing.T) { // setup types - _buildOne := testBuild() + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + _repo.SetTopics([]string{}) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo.SetOwner(_owner) + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetCreated(1) _buildOne.SetStatus("pending") - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetCreated(2) _buildTwo.SetStatus("running") // setup types - _buildThree := testBuild() + _buildThree := testutils.APIBuild() _buildThree.SetID(3) - _buildThree.SetRepoID(1) + _buildThree.SetRepo(_repo) _buildThree.SetNumber(3) _buildThree.SetCreated(1) _buildThree.SetStatus("success") - _buildFour := testBuild() + _buildFour := testutils.APIBuild() _buildFour.SetID(4) - _buildFour.SetRepoID(1) + _buildFour.SetRepo(_repo) _buildFour.SetNumber(4) _buildFour.SetCreated(5) _buildFour.SetStatus("running") diff --git a/database/build/count.go b/database/build/count.go index 02960a50d..3e86611d9 100644 --- a/database/build/count.go +++ b/database/build/count.go @@ -10,7 +10,7 @@ import ( // CountBuilds gets the count of all builds from the database. func (e *engine) CountBuilds(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all builds from the database") + e.logger.Tracef("getting count of all builds") // variable to store query results var b int64 diff --git a/database/build/count_deployment.go b/database/build/count_deployment.go index da3b23927..46c8170fc 100644 --- a/database/build/count_deployment.go +++ b/database/build/count_deployment.go @@ -5,16 +5,17 @@ package build import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CountBuildsForDeployment gets the count of builds by deployment URL from the database. func (e *engine) CountBuildsForDeployment(ctx context.Context, d *library.Deployment, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "deployment": d.GetURL(), - }).Tracef("getting count of builds for deployment %s from the database", d.GetURL()) + }).Tracef("getting count of builds for deployment %s", d.GetURL()) // variable to store query results var b int64 diff --git a/database/build/count_deployment_test.go b/database/build/count_deployment_test.go index 6b4f83823..64fbbbbae 100644 --- a/database/build/count_deployment_test.go +++ b/database/build/count_deployment_test.go @@ -8,25 +8,46 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CountBuildsForDeployment(t *testing.T) { // setup types - _buildOne := testBuild() + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + _repo.SetTopics([]string{}) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo.SetOwner(_owner) + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetDeployPayload(nil) _buildOne.SetSource("https://github.com/github/octocat/deployments/1") - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetDeployPayload(nil) _buildTwo.SetSource("https://github.com/github/octocat/deployments/1") - _deployment := testDeployment() + _deployment := testutils.APIDeployment() _deployment.SetID(1) _deployment.SetRepoID(1) _deployment.SetURL("https://github.com/github/octocat/deployments/1") diff --git a/database/build/count_org.go b/database/build/count_org.go index 1d01a2cde..d9f4105ed 100644 --- a/database/build/count_org.go +++ b/database/build/count_org.go @@ -5,15 +5,16 @@ package build import ( "context" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // CountBuildsForOrg gets the count of builds by org name from the database. func (e *engine) CountBuildsForOrg(ctx context.Context, org string, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": org, - }).Tracef("getting count of builds for org %s from the database", org) + }).Tracef("getting count of builds for org %s", org) // variable to store query results var b int64 diff --git a/database/build/count_org_test.go b/database/build/count_org_test.go index 6b6283dbb..546e59678 100644 --- a/database/build/count_org_test.go +++ b/database/build/count_org_test.go @@ -8,29 +8,22 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" ) func TestBuild_Engine_CountBuildsForOrg(t *testing.T) { // setup types - _buildOne := testBuild() - _buildOne.SetID(1) - _buildOne.SetRepoID(1) - _buildOne.SetNumber(1) - _buildOne.SetDeployPayload(nil) - _buildOne.SetEvent("push") - - _buildTwo := testBuild() - _buildTwo.SetID(2) - _buildTwo.SetRepoID(2) - _buildTwo.SetNumber(2) - _buildTwo.SetDeployPayload(nil) - _buildTwo.SetEvent("push") + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_owner) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") @@ -39,9 +32,9 @@ func TestBuild_Engine_CountBuildsForOrg(t *testing.T) { _repoOne.SetPipelineType("yaml") _repoOne.SetTopics([]string{}) - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_owner) _repoTwo.SetHash("bar") _repoTwo.SetOrg("foo") _repoTwo.SetName("baz") @@ -50,6 +43,20 @@ func TestBuild_Engine_CountBuildsForOrg(t *testing.T) { _repoTwo.SetPipelineType("yaml") _repoTwo.SetTopics([]string{}) + _buildOne := testutils.APIBuild() + _buildOne.SetID(1) + _buildOne.SetRepo(_repoOne) + _buildOne.SetNumber(1) + _buildOne.SetDeployPayload(nil) + _buildOne.SetEvent("push") + + _buildTwo := testutils.APIBuild() + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repoTwo) + _buildTwo.SetNumber(2) + _buildTwo.SetDeployPayload(nil) + _buildTwo.SetEvent("push") + _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -76,17 +83,17 @@ func TestBuild_Engine_CountBuildsForOrg(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Repo{}) + err = _sqlite.client.AutoMigrate(&types.Repo{}) if err != nil { t.Errorf("unable to create repo table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoOne)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoOne)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoTwo)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoTwo)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } diff --git a/database/build/count_repo.go b/database/build/count_repo.go index 152d867a2..298420caa 100644 --- a/database/build/count_repo.go +++ b/database/build/count_repo.go @@ -5,17 +5,18 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountBuildsForRepo gets the count of builds by repo ID from the database. -func (e *engine) CountBuildsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}) (int64, error) { +func (e *engine) CountBuildsForRepo(ctx context.Context, r *api.Repo, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting count of builds for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of builds for repo %s", r.GetFullName()) // variable to store query results var b int64 diff --git a/database/build/count_repo_test.go b/database/build/count_repo_test.go index ebf0dbe47..77bbc69d6 100644 --- a/database/build/count_repo_test.go +++ b/database/build/count_repo_test.go @@ -8,31 +8,38 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CountBuildsForRepo(t *testing.T) { // setup types - _buildOne := testBuild() - _buildOne.SetID(1) - _buildOne.SetRepoID(1) - _buildOne.SetNumber(1) - _buildOne.SetDeployPayload(nil) - - _buildTwo := testBuild() - _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) - _buildTwo.SetNumber(2) - _buildTwo.SetDeployPayload(nil) + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") _repo.SetVisibility("public") + _buildOne := testutils.APIBuild() + _buildOne.SetID(1) + _buildOne.SetRepo(_repo) + _buildOne.SetNumber(1) + _buildOne.SetDeployPayload(nil) + + _buildTwo := testutils.APIBuild() + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repo) + _buildTwo.SetNumber(2) + _buildTwo.SetDeployPayload(nil) + _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() diff --git a/database/build/count_status.go b/database/build/count_status.go index e95ec8e1e..524d27d76 100644 --- a/database/build/count_status.go +++ b/database/build/count_status.go @@ -10,7 +10,7 @@ import ( // CountBuildsForStatus gets the count of builds by status from the database. func (e *engine) CountBuildsForStatus(ctx context.Context, status string, filters map[string]interface{}) (int64, error) { - e.logger.Tracef("getting count of builds for status %s from the database", status) + e.logger.Tracef("getting count of builds for status %s", status) // variable to store query results var b int64 diff --git a/database/build/count_status_test.go b/database/build/count_status_test.go index cea339953..6fe707142 100644 --- a/database/build/count_status_test.go +++ b/database/build/count_status_test.go @@ -8,20 +8,36 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CountBuildsForStatus(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.GetOwner().SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetDeployPayload(nil) _buildOne.SetStatus("running") - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetDeployPayload(nil) _buildTwo.SetStatus("running") diff --git a/database/build/count_test.go b/database/build/count_test.go index bdcc57078..a1d7717ce 100644 --- a/database/build/count_test.go +++ b/database/build/count_test.go @@ -8,19 +8,35 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CountBuilds(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.GetOwner().SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetDeployPayload(nil) - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetDeployPayload(nil) diff --git a/database/build/create.go b/database/build/create.go index 02b09ab2d..3a3684102 100644 --- a/database/build/create.go +++ b/database/build/create.go @@ -6,26 +6,21 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CreateBuild creates a new build in the database. -func (e *engine) CreateBuild(ctx context.Context, b *library.Build) (*library.Build, error) { +func (e *engine) CreateBuild(ctx context.Context, b *api.Build) (*api.Build, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("creating build %d in the database", b.GetNumber()) + }).Tracef("creating build %d", b.GetNumber()) - // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary - build := database.BuildFromLibrary(b) + build := types.BuildFromAPI(b) - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.Validate err := build.Validate() if err != nil { return nil, err @@ -35,7 +30,13 @@ func (e *engine) CreateBuild(ctx context.Context, b *library.Build) (*library.Bu build = build.Crop() // send query to the database - result := e.client.Table(constants.TableBuild).Create(build) + err = e.client.Table(constants.TableBuild).Create(build).Error + if err != nil { + return nil, err + } + + result := build.ToAPI() + result.SetRepo(b.GetRepo()) - return build.ToLibrary(), result.Error + return result, nil } diff --git a/database/build/create_test.go b/database/build/create_test.go index 86e9b8751..9f9d72399 100644 --- a/database/build/create_test.go +++ b/database/build/create_test.go @@ -8,13 +8,29 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_CreateBuild(t *testing.T) { // setup types - _build := testBuild() + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.GetOwner().SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _build.SetDeployPayload(nil) @@ -26,9 +42,9 @@ func TestBuild_Engine_CreateBuild(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "builds" -("repo_id","pipeline_id","number","parent","event","event_action","status","error","enqueued","created","started","finished","deploy","deploy_number","deploy_payload","clone","source","title","message","commit","sender","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","approved_at","approved_by","id") -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34) RETURNING "id"`). - WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +("repo_id","pipeline_id","number","parent","event","event_action","status","error","enqueued","created","started","finished","deploy","deploy_number","deploy_payload","clone","source","title","message","commit","sender","sender_scm_id","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","approved_at","approved_by","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35) RETURNING "id"`). + WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/build/delete.go b/database/build/delete.go index 9d49f3c12..2bcb282e4 100644 --- a/database/build/delete.go +++ b/database/build/delete.go @@ -5,22 +5,20 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // DeleteBuild deletes an existing build from the database. -func (e *engine) DeleteBuild(ctx context.Context, b *library.Build) error { +func (e *engine) DeleteBuild(ctx context.Context, b *api.Build) error { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("deleting build %d from the database", b.GetNumber()) + }).Tracef("deleting build %d", b.GetNumber()) - // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary - build := database.BuildFromLibrary(b) + build := types.BuildFromAPI(b) // send query to the database return e.client. diff --git a/database/build/delete_test.go b/database/build/delete_test.go index 33ca6e3d6..7a7b21ed0 100644 --- a/database/build/delete_test.go +++ b/database/build/delete_test.go @@ -7,13 +7,29 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_DeleteBuild(t *testing.T) { // setup types - _build := testBuild() + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.GetOwner().SetID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _build.SetDeployPayload(nil) diff --git a/database/build/get.go b/database/build/get.go index 02df1d42a..caaf1fe0c 100644 --- a/database/build/get.go +++ b/database/build/get.go @@ -5,21 +5,23 @@ package build import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // GetBuild gets a build by ID from the database. -func (e *engine) GetBuild(ctx context.Context, id int64) (*library.Build, error) { - e.logger.Tracef("getting build %d from the database", id) +func (e *engine) GetBuild(ctx context.Context, id int64) (*api.Build, error) { + e.logger.Tracef("getting build %d", id) // variable to store query results - b := new(database.Build) + b := new(types.Build) // send query to the database and store result in variable err := e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Where("id = ?", id). Take(b). Error @@ -27,5 +29,10 @@ func (e *engine) GetBuild(ctx context.Context, id int64) (*library.Build, error) return nil, err } - return b.ToLibrary(), nil + err = b.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo: %v", err) + } + + return b.ToAPI(), nil } diff --git a/database/build/get_repo.go b/database/build/get_repo.go index 3728b6232..b144cf432 100644 --- a/database/build/get_repo.go +++ b/database/build/get_repo.go @@ -5,26 +5,29 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // GetBuildForRepo gets a build by repo ID and number from the database. -func (e *engine) GetBuildForRepo(ctx context.Context, r *library.Repo, number int) (*library.Build, error) { +func (e *engine) GetBuildForRepo(ctx context.Context, r *api.Repo, number int) (*api.Build, error) { e.logger.WithFields(logrus.Fields{ "build": number, "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting build %s/%d from the database", r.GetFullName(), number) + }).Tracef("getting build %s/%d", r.GetFullName(), number) // variable to store query results - b := new(database.Build) + b := new(types.Build) // send query to the database and store result in variable err := e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Where("repo_id = ?", r.GetID()). Where("number = ?", number). Take(b). @@ -33,5 +36,10 @@ func (e *engine) GetBuildForRepo(ctx context.Context, r *library.Repo, number in return nil, err } - return b.ToLibrary(), nil + err = b.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %s/%s: %v", r.GetOrg(), r.GetName(), err) + } + + return b.ToAPI(), nil } diff --git a/database/build/get_repo_test.go b/database/build/get_repo_test.go index c34fa8a2e..e2db55642 100644 --- a/database/build/get_repo_test.go +++ b/database/build/get_repo_test.go @@ -4,30 +4,42 @@ package build import ( "context" - "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestBuild_Engine_GetBuildForRepo(t *testing.T) { // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - _build.SetDeployNumber(0) - _build.SetDeployPayload(nil) + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.SetOwner(_owner) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType(constants.PipelineTypeYAML) + _repo.SetTopics([]string{}) + + _build := testutils.APIBuild() + _build.SetID(1) + _build.SetRepo(_repo) + _build.SetNumber(1) + _build.SetDeployNumber(0) + _build.SetDeployPayload(nil) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -37,8 +49,18 @@ func TestBuild_Engine_GetBuildForRepo(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_number", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}). AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", 0, nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND number = $2 LIMIT $3`).WithArgs(1, 1, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -48,12 +70,32 @@ func TestBuild_Engine_GetBuildForRepo(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Build + want *api.Build }{ { failure: false, @@ -86,8 +128,8 @@ func TestBuild_Engine_GetBuildForRepo(t *testing.T) { t.Errorf("GetBuildForRepo for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetBuildForRepo for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("GetBuildForRepo for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/build/get_test.go b/database/build/get_test.go index d48efa243..e4a26d852 100644 --- a/database/build/get_test.go +++ b/database/build/get_test.go @@ -8,14 +8,35 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestBuild_Engine_GetBuild(t *testing.T) { // setup types - _build := testBuild() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType(constants.PipelineTypeYAML) + _repo.SetTopics([]string{}) + + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _build.SetDeployPayload(nil) @@ -27,8 +48,18 @@ func TestBuild_Engine_GetBuild(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -38,12 +69,32 @@ func TestBuild_Engine_GetBuild(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Build + want *api.Build }{ { failure: false, diff --git a/database/build/index.go b/database/build/index.go index f5bea2898..446395cc9 100644 --- a/database/build/index.go +++ b/database/build/index.go @@ -44,7 +44,7 @@ ON builds (status); // CreateBuildIndexes creates the indexes for the builds table in the database. func (e *engine) CreateBuildIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for builds table in the database") + e.logger.Tracef("creating indexes for builds table") // create the created column index for the builds table err := e.client.Exec(CreateCreatedIndex).Error diff --git a/database/build/interface.go b/database/build/interface.go index e28d230b4..01fad378a 100644 --- a/database/build/interface.go +++ b/database/build/interface.go @@ -5,6 +5,7 @@ package build import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -35,29 +36,31 @@ type BuildInterface interface { // CountBuildsForOrg defines a function that gets the count of builds by org name. CountBuildsForOrg(context.Context, string, map[string]interface{}) (int64, error) // CountBuildsForRepo defines a function that gets the count of builds by repo ID. - CountBuildsForRepo(context.Context, *library.Repo, map[string]interface{}) (int64, error) + CountBuildsForRepo(context.Context, *api.Repo, map[string]interface{}) (int64, error) // CountBuildsForStatus defines a function that gets the count of builds by status. CountBuildsForStatus(context.Context, string, map[string]interface{}) (int64, error) // CreateBuild defines a function that creates a new build. - CreateBuild(context.Context, *library.Build) (*library.Build, error) + CreateBuild(context.Context, *api.Build) (*api.Build, error) // DeleteBuild defines a function that deletes an existing build. - DeleteBuild(context.Context, *library.Build) error + DeleteBuild(context.Context, *api.Build) error // GetBuild defines a function that gets a build by ID. - GetBuild(context.Context, int64) (*library.Build, error) + GetBuild(context.Context, int64) (*api.Build, error) // GetBuildForRepo defines a function that gets a build by repo ID and number. - GetBuildForRepo(context.Context, *library.Repo, int) (*library.Build, error) + GetBuildForRepo(context.Context, *api.Repo, int) (*api.Build, error) // LastBuildForRepo defines a function that gets the last build ran by repo ID and branch. - LastBuildForRepo(context.Context, *library.Repo, string) (*library.Build, error) + LastBuildForRepo(context.Context, *api.Repo, string) (*api.Build, error) // ListBuilds defines a function that gets a list of all builds. - ListBuilds(context.Context) ([]*library.Build, error) + ListBuilds(context.Context) ([]*api.Build, error) // ListBuildsForOrg defines a function that gets a list of builds by org name. - ListBuildsForOrg(context.Context, string, map[string]interface{}, int, int) ([]*library.Build, int64, error) + ListBuildsForOrg(context.Context, string, map[string]interface{}, int, int) ([]*api.Build, int64, error) + // ListBuildsForDashboardRepo defines a function that gets a list of builds based on dashboard filters. + ListBuildsForDashboardRepo(context.Context, *api.Repo, []string, []string) ([]*api.Build, error) // ListBuildsForRepo defines a function that gets a list of builds by repo ID. - ListBuildsForRepo(context.Context, *library.Repo, map[string]interface{}, int64, int64, int, int) ([]*library.Build, int64, error) + ListBuildsForRepo(context.Context, *api.Repo, map[string]interface{}, int64, int64, int, int) ([]*api.Build, int64, error) // ListPendingAndRunningBuilds defines a function that gets a list of pending and running builds. - ListPendingAndRunningBuilds(context.Context, string) ([]*library.BuildQueue, error) + ListPendingAndRunningBuilds(context.Context, string) ([]*api.QueueBuild, error) // ListPendingAndRunningBuildsForRepo defines a function that gets a list of pending and running builds for a repo. - ListPendingAndRunningBuildsForRepo(context.Context, *library.Repo) ([]*library.Build, error) + ListPendingAndRunningBuildsForRepo(context.Context, *api.Repo) ([]*api.Build, error) // UpdateBuild defines a function that updates an existing build. - UpdateBuild(context.Context, *library.Build) (*library.Build, error) + UpdateBuild(context.Context, *api.Build) (*api.Build, error) } diff --git a/database/build/last_repo.go b/database/build/last_repo.go index 36b923379..fe04af158 100644 --- a/database/build/last_repo.go +++ b/database/build/last_repo.go @@ -6,27 +6,29 @@ import ( "context" "errors" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // LastBuildForRepo gets the last build by repo ID and branch from the database. -func (e *engine) LastBuildForRepo(ctx context.Context, r *library.Repo, branch string) (*library.Build, error) { +func (e *engine) LastBuildForRepo(ctx context.Context, r *api.Repo, branch string) (*api.Build, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting last build for repo %s from the database", r.GetFullName()) + }).Tracef("getting last build for repo %s", r.GetFullName()) // variable to store query results - b := new(database.Build) + b := new(types.Build) // send query to the database and store result in variable err := e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Where("repo_id = ?", r.GetID()). Where("branch = ?", branch). Order("number DESC"). @@ -42,5 +44,10 @@ func (e *engine) LastBuildForRepo(ctx context.Context, r *library.Repo, branch s return nil, err } - return b.ToLibrary(), nil + err = b.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %s/%s: %v", r.GetOrg(), r.GetName(), err) + } + + return b.ToAPI(), nil } diff --git a/database/build/last_repo_test.go b/database/build/last_repo_test.go index 7baebe01d..140b79651 100644 --- a/database/build/last_repo_test.go +++ b/database/build/last_repo_test.go @@ -8,26 +8,38 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestBuild_Engine_LastBuildForRepo(t *testing.T) { // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - _build.SetDeployPayload(nil) - _build.SetBranch("main") + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.SetOwner(_owner) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType(constants.PipelineTypeYAML) + _repo.SetTopics([]string{}) + + _build := testutils.APIBuild() + _build.SetID(1) + _build.SetRepo(_repo) + _build.SetNumber(1) + _build.SetDeployPayload(nil) + _build.SetBranch("main") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -37,8 +49,18 @@ func TestBuild_Engine_LastBuildForRepo(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND branch = $2 ORDER BY number DESC LIMIT $3`).WithArgs(1, "main", 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -48,12 +70,32 @@ func TestBuild_Engine_LastBuildForRepo(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Build + want *api.Build }{ { failure: false, diff --git a/database/build/list.go b/database/build/list.go index 109d2ae5f..9e3e45c8c 100644 --- a/database/build/list.go +++ b/database/build/list.go @@ -5,19 +5,19 @@ package build import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListBuilds gets a list of all builds from the database. -func (e *engine) ListBuilds(ctx context.Context) ([]*library.Build, error) { - e.logger.Trace("listing all builds from the database") +func (e *engine) ListBuilds(ctx context.Context) ([]*api.Build, error) { + e.logger.Trace("listing all builds") // variables to store query results and return value count := int64(0) - b := new([]database.Build) - builds := []*library.Build{} + b := new([]types.Build) + builds := []*api.Build{} // count the results count, err := e.CountBuilds(ctx) @@ -32,6 +32,8 @@ func (e *engine) ListBuilds(ctx context.Context) ([]*library.Build, error) { // send query to the database and store result in variable err = e.client. + Preload("Repo"). + Preload("Repo.Owner"). Table(constants.TableBuild). Find(&b). Error @@ -44,10 +46,12 @@ func (e *engine) ListBuilds(ctx context.Context) ([]*library.Build, error) { // https://golang.org/doc/faq#closures_and_goroutines tmp := build - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary - builds = append(builds, tmp.ToLibrary()) + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo: %v", err) + } + + builds = append(builds, tmp.ToAPI()) } return builds, nil diff --git a/database/build/list_dashboard.go b/database/build/list_dashboard.go new file mode 100644 index 000000000..8ddc6fa57 --- /dev/null +++ b/database/build/list_dashboard.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" +) + +// ListBuildsForDashboardRepo gets a list of builds by repo ID from the database. +func (e *engine) ListBuildsForDashboardRepo(ctx context.Context, r *api.Repo, branches, events []string) ([]*api.Build, error) { + e.logger.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + }).Tracef("listing builds for repo %s", r.GetFullName()) + + // variables to store query results and return values + b := new([]types.Build) + builds := []*api.Build{} + + query := e.client.Table(constants.TableBuild).Where("repo_id = ?", r.GetID()) + + if len(branches) > 0 { + query = query.Where("branch IN (?)", branches) + } + + if len(events) > 0 { + query = query.Where("event IN (?)", events) + } + + err := query. + Order("number DESC"). + Limit(5). + Find(&b). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, build := range *b { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := build + + builds = append(builds, tmp.ToAPI()) + } + + return builds, nil +} diff --git a/database/build/list_dashboard_test.go b/database/build/list_dashboard_test.go new file mode 100644 index 000000000..803c19db2 --- /dev/null +++ b/database/build/list_dashboard_test.go @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestBuild_Engine_ListBuildsForDashboardRepo(t *testing.T) { + // setup types + _repo := testutils.APIRepo() + _repo.SetID(1) + + _buildOne := testutils.APIBuild() + _buildOne.SetID(1) + _buildOne.SetRepo(_repo) + _buildOne.SetNumber(1) + _buildOne.SetDeployPayload(nil) + _buildOne.SetCreated(1) + _buildOne.SetEvent("push") + _buildOne.SetBranch("main") + + _buildTwo := testutils.APIBuild() + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repo) + _buildTwo.SetNumber(2) + _buildTwo.SetDeployPayload(nil) + _buildTwo.SetCreated(2) + _buildTwo.SetEvent("pull_request") + _buildTwo.SetBranch("main") + + // ListBuildsForDashboardRepo does not return the repo object but the repo ID is needed to create builds + _wantBuildOne := *_buildOne + _wantBuildOne.Repo = testutils.APIRepo() + _wantBuildOne.Repo.Owner = testutils.APIUser().Crop() + + _wantBuildTwo := *_buildTwo + _wantBuildTwo.Repo = testutils.APIRepo() + _wantBuildTwo.Repo.Owner = testutils.APIUser().Crop() + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected query result in mock + _rows := sqlmock.NewRows( + []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "sender_scm_id", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). + AddRow(2, 1, nil, 2, 0, "pull_request", "", "", "", 0, 2, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0). + AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "main", "", "", "", "", "", "", 0, "", 0) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND branch IN ($2) AND event IN ($3,$4) ORDER BY number DESC LIMIT $5`).WithArgs(1, "main", "push", "pull_request", 5).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateBuild(context.TODO(), _buildOne) + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + + _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo) + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*api.Build + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*api.Build{&_wantBuildTwo, &_wantBuildOne}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*api.Build{&_wantBuildTwo, &_wantBuildOne}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListBuildsForDashboardRepo(context.TODO(), _repo, []string{"main"}, []string{"push", "pull_request"}) + + if test.failure { + if err == nil { + t.Errorf("ListBuildsForRepo for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListBuildsForRepo for %s returned err: %v", test.name, err) + } + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("GetDashboard mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/database/build/list_org.go b/database/build/list_org.go index 6fed59519..cfaa1992d 100644 --- a/database/build/list_org.go +++ b/database/build/list_org.go @@ -5,24 +5,25 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // ListBuildsForOrg gets a list of builds by org name from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListBuildsForOrg(ctx context.Context, org string, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) { +func (e *engine) ListBuildsForOrg(ctx context.Context, org string, filters map[string]interface{}, page, perPage int) ([]*api.Build, int64, error) { e.logger.WithFields(logrus.Fields{ "org": org, - }).Tracef("listing builds for org %s from the database", org) + }).Tracef("listing builds for org %s", org) // variables to store query results and return values count := int64(0) - b := new([]database.Build) - builds := []*library.Build{} + b := new([]types.Build) + builds := []*api.Build{} // count the results count, err := e.CountBuildsForOrg(ctx, org, filters) @@ -40,6 +41,8 @@ func (e *engine) ListBuildsForOrg(ctx context.Context, org string, filters map[s err = e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Select("builds.*"). Joins("JOIN repos ON builds.repo_id = repos.id"). Where("repos.org = ?", org). @@ -59,10 +62,12 @@ func (e *engine) ListBuildsForOrg(ctx context.Context, org string, filters map[s // https://golang.org/doc/faq#closures_and_goroutines tmp := build - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary - builds = append(builds, tmp.ToLibrary()) + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo: %v", err) + } + + builds = append(builds, tmp.ToAPI()) } return builds, count, nil diff --git a/database/build/list_org_test.go b/database/build/list_org_test.go index 718517d3d..4d25d6062 100644 --- a/database/build/list_org_test.go +++ b/database/build/list_org_test.go @@ -4,53 +4,62 @@ package build import ( "context" - "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { // setup types - _buildOne := testBuild() - _buildOne.SetID(1) - _buildOne.SetRepoID(1) - _buildOne.SetNumber(1) - _buildOne.SetDeployPayload(nil) - _buildOne.SetEvent("push") - - _buildTwo := testBuild() - _buildTwo.SetID(2) - _buildTwo.SetRepoID(2) - _buildTwo.SetNumber(2) - _buildTwo.SetDeployPayload(nil) - _buildTwo.SetEvent("push") + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_owner) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") _repoOne.SetFullName("foo/bar") _repoOne.SetVisibility("public") _repoOne.SetPipelineType("yaml") + _repoOne.SetAllowEvents(api.NewEventsFromMask(1)) _repoOne.SetTopics([]string{}) - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_owner) _repoTwo.SetHash("bar") _repoTwo.SetOrg("foo") _repoTwo.SetName("baz") _repoTwo.SetFullName("foo/baz") _repoTwo.SetVisibility("public") _repoTwo.SetPipelineType("yaml") + _repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) _repoTwo.SetTopics([]string{}) + _buildOne := testutils.APIBuild() + _buildOne.SetID(1) + _buildOne.SetRepo(_repoOne) + _buildOne.SetNumber(1) + _buildOne.SetDeployPayload(nil) + _buildOne.SetEvent("push") + + _buildTwo := testutils.APIBuild() + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repoTwo) + _buildTwo.SetNumber(2) + _buildTwo.SetDeployPayload(nil) + _buildTwo.SetEvent("push") + _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -63,8 +72,20 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0). AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", ""). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query without filters _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 ORDER BY created DESC,id LIMIT $2`).WithArgs("foo", 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" IN ($1,$2)`).WithArgs(1, 2).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) // create expected count query with event filter result in mock _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) @@ -75,8 +96,20 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0). AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + + _repoRows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", ""). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows = sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query with event filter _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "event" = $2 ORDER BY created DESC,id LIMIT $3`).WithArgs("foo", "push", 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" IN ($1,$2)`).WithArgs(1, 2).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) // create expected count query with visibility filter result in mock _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) @@ -87,8 +120,20 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "approved_at", "approved_by", "timestamp"}). AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0). AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + + _repoRows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", ""). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows = sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query with visibility filter _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "visibility" = $2 ORDER BY created DESC,id LIMIT $3`).WithArgs("foo", "public", 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" IN ($1,$2)`).WithArgs(1, 2).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -103,35 +148,45 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Repo{}) + err = _sqlite.client.AutoMigrate(&types.Repo{}) if err != nil { t.Errorf("unable to create repo table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoOne)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoOne)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoTwo)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoTwo)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine filters map[string]interface{} - want []*library.Build + want []*api.Build }{ { failure: false, name: "postgres without filters", database: _postgres, filters: map[string]interface{}{}, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, @@ -140,7 +195,7 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { filters: map[string]interface{}{ "event": "push", }, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, @@ -149,14 +204,14 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { filters: map[string]interface{}{ "visibility": "public", }, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, name: "sqlite3 without filters", database: _sqlite, filters: map[string]interface{}{}, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, @@ -165,7 +220,7 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { filters: map[string]interface{}{ "event": "push", }, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, @@ -174,7 +229,7 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { filters: map[string]interface{}{ "visibility": "public", }, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, } @@ -195,8 +250,8 @@ func TestBuild_Engine_ListBuildsForOrg(t *testing.T) { t.Errorf("ListBuildsForOrg for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListBuildsForOrg for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListBuildsForOrg for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/build/list_pending_running.go b/database/build/list_pending_running.go index de4b4ac68..858b9c08e 100644 --- a/database/build/list_pending_running.go +++ b/database/build/list_pending_running.go @@ -5,18 +5,18 @@ package build import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListPendingAndRunningBuilds gets a list of all pending and running builds in the provided timeframe from the database. -func (e *engine) ListPendingAndRunningBuilds(ctx context.Context, after string) ([]*library.BuildQueue, error) { - e.logger.Trace("listing all pending and running builds from the database") +func (e *engine) ListPendingAndRunningBuilds(ctx context.Context, after string) ([]*api.QueueBuild, error) { + e.logger.Trace("listing all pending and running builds") // variables to store query results and return value - b := new([]database.BuildQueue) - builds := []*library.BuildQueue{} + b := new([]types.QueueBuild) + builds := []*api.QueueBuild{} // send query to the database and store result in variable err := e.client. @@ -36,10 +36,7 @@ func (e *engine) ListPendingAndRunningBuilds(ctx context.Context, after string) // https://golang.org/doc/faq#closures_and_goroutines tmp := build - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary - builds = append(builds, tmp.ToLibrary()) + builds = append(builds, tmp.ToAPI()) } return builds, nil diff --git a/database/build/list_pending_running_repo.go b/database/build/list_pending_running_repo.go index ad0963aee..cc8984265 100644 --- a/database/build/list_pending_running_repo.go +++ b/database/build/list_pending_running_repo.go @@ -5,22 +5,24 @@ package build import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListPendingAndRunningBuilds gets a list of all pending and running builds in the provided timeframe from the database. -func (e *engine) ListPendingAndRunningBuildsForRepo(ctx context.Context, repo *library.Repo) ([]*library.Build, error) { - e.logger.Trace("listing all pending and running builds from the database") +func (e *engine) ListPendingAndRunningBuildsForRepo(ctx context.Context, repo *api.Repo) ([]*api.Build, error) { + e.logger.Trace("listing all pending and running builds") // variables to store query results and return value - b := new([]database.Build) - builds := []*library.Build{} + b := new([]types.Build) + builds := []*api.Build{} // send query to the database and store result in variable err := e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Select("*"). Where("repo_id = ?", repo.GetID()). Where("status = 'running' OR status = 'pending' OR status = 'pending approval'"). @@ -35,10 +37,12 @@ func (e *engine) ListPendingAndRunningBuildsForRepo(ctx context.Context, repo *l // https://golang.org/doc/faq#closures_and_goroutines tmp := build - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary - builds = append(builds, tmp.ToLibrary()) + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %s/%s: %v", repo.GetOrg(), repo.GetName(), err) + } + + builds = append(builds, tmp.ToAPI()) } return builds, nil diff --git a/database/build/list_pending_running_repo_test.go b/database/build/list_pending_running_repo_test.go index 34993713b..286b68141 100644 --- a/database/build/list_pending_running_repo_test.go +++ b/database/build/list_pending_running_repo_test.go @@ -4,59 +4,72 @@ package build import ( "context" - "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) func TestBuild_Engine_ListPendingAndRunningBuildsForRepo(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repoOne := testutils.APIRepo() + _repoOne.SetID(1) + _repoOne.SetOwner(_owner) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + _repoOne.SetPipelineType("yaml") + _repoOne.SetAllowEvents(api.NewEventsFromMask(1)) + _repoOne.SetTopics([]string{}) + + _repoTwo := testutils.APIRepo() + _repoTwo.SetID(2) + _repoTwo.SetOwner(_owner) + _repoTwo.SetHash("bar") + _repoTwo.SetOrg("foo") + _repoTwo.SetName("baz") + _repoTwo.SetFullName("foo/baz") + _repoTwo.SetVisibility("public") + _repoTwo.SetPipelineType("yaml") + _repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) + _repoTwo.SetTopics([]string{}) + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repoOne) _buildOne.SetNumber(1) _buildOne.SetStatus("running") _buildOne.SetCreated(1) _buildOne.SetDeployPayload(nil) - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repoOne) _buildTwo.SetNumber(2) _buildTwo.SetStatus("pending") _buildTwo.SetCreated(1) _buildTwo.SetDeployPayload(nil) - _buildThree := testBuild() + _buildThree := testutils.APIBuild() _buildThree.SetID(3) - _buildThree.SetRepoID(2) + _buildThree.SetRepo(_repoTwo) _buildThree.SetNumber(1) _buildThree.SetStatus("pending") _buildThree.SetCreated(1) _buildThree.SetDeployPayload(nil) - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("bazzy") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("public") - _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -66,8 +79,18 @@ func TestBuild_Engine_ListPendingAndRunningBuildsForRepo(t *testing.T) { AddRow(2, 1, nil, 2, 0, "", "", "pending", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0). AddRow(1, 1, nil, 1, 0, "", "", "running", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND (status = 'running' OR status = 'pending' OR status = 'pending approval')`).WithArgs(1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -87,46 +110,56 @@ func TestBuild_Engine_ListPendingAndRunningBuildsForRepo(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Repo{}) + err = _sqlite.client.AutoMigrate(&types.Repo{}) if err != nil { t.Errorf("unable to create repo table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repo)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoOne)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoTwo)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repoTwo)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Build + want []*api.Build }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Build{_buildTwo, _buildOne}, + want: []*api.Build{_buildTwo, _buildOne}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, } // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := test.database.ListPendingAndRunningBuildsForRepo(context.TODO(), _repo) + got, err := test.database.ListPendingAndRunningBuildsForRepo(context.TODO(), _repoOne) if test.failure { if err == nil { @@ -140,8 +173,8 @@ func TestBuild_Engine_ListPendingAndRunningBuildsForRepo(t *testing.T) { t.Errorf("ListPendingAndRunningBuildsForRepo for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListPendingAndRunningBuildsForRepo for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListPendingAndRunningBuildsForRepo for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/build/list_pending_running_test.go b/database/build/list_pending_running_test.go index 9115745f3..c164827d8 100644 --- a/database/build/list_pending_running_test.go +++ b/database/build/list_pending_running_test.go @@ -8,50 +8,57 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) func TestBuild_Engine_ListPendingAndRunningBuilds(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetStatus("running") _buildOne.SetCreated(1) _buildOne.SetDeployPayload(nil) - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetStatus("pending") _buildTwo.SetCreated(1) _buildTwo.SetDeployPayload(nil) - _queueOne := new(library.BuildQueue) + _queueOne := new(api.QueueBuild) _queueOne.SetCreated(1) _queueOne.SetFullName("foo/bar") _queueOne.SetNumber(1) _queueOne.SetStatus("running") - _queueTwo := new(library.BuildQueue) + _queueTwo := new(api.QueueBuild) _queueTwo.SetCreated(1) _queueTwo.SetFullName("foo/bar") _queueTwo.SetNumber(2) _queueTwo.SetStatus("pending") - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -74,12 +81,12 @@ func TestBuild_Engine_ListPendingAndRunningBuilds(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Repo{}) + err = _sqlite.client.AutoMigrate(&types.Repo{}) if err != nil { t.Errorf("unable to create repo table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repo)).Error + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error if err != nil { t.Errorf("unable to create test repo for sqlite: %v", err) } @@ -89,19 +96,19 @@ func TestBuild_Engine_ListPendingAndRunningBuilds(t *testing.T) { failure bool name string database *engine - want []*library.BuildQueue + want []*api.QueueBuild }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.BuildQueue{_queueTwo, _queueOne}, + want: []*api.QueueBuild{_queueTwo, _queueOne}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.BuildQueue{_queueTwo, _queueOne}, + want: []*api.QueueBuild{_queueTwo, _queueOne}, }, } diff --git a/database/build/list_repo.go b/database/build/list_repo.go index 8b4386457..2552c2ea3 100644 --- a/database/build/list_repo.go +++ b/database/build/list_repo.go @@ -5,25 +5,26 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // ListBuildsForRepo gets a list of builds by repo ID from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListBuildsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}, before, after int64, page, perPage int) ([]*library.Build, int64, error) { +func (e *engine) ListBuildsForRepo(ctx context.Context, r *api.Repo, filters map[string]interface{}, before, after int64, page, perPage int) ([]*api.Build, int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("listing builds for repo %s from the database", r.GetFullName()) + }).Tracef("listing builds for repo %s", r.GetFullName()) // variables to store query results and return values count := int64(0) - b := new([]database.Build) - builds := []*library.Build{} + b := new([]types.Build) + builds := []*api.Build{} // count the results count, err := e.CountBuildsForRepo(ctx, r, filters) @@ -41,6 +42,8 @@ func (e *engine) ListBuildsForRepo(ctx context.Context, r *library.Repo, filters err = e.client. Table(constants.TableBuild). + Preload("Repo"). + Preload("Repo.Owner"). Where("repo_id = ?", r.GetID()). Where("created < ?", before). Where("created > ?", after). @@ -59,10 +62,12 @@ func (e *engine) ListBuildsForRepo(ctx context.Context, r *library.Repo, filters // https://golang.org/doc/faq#closures_and_goroutines tmp := build - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary - builds = append(builds, tmp.ToLibrary()) + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %s/%s: %v", r.GetOrg(), r.GetName(), err) + } + + builds = append(builds, tmp.ToAPI()) } return builds, count, nil diff --git a/database/build/list_repo_test.go b/database/build/list_repo_test.go index 4cb4622a5..96368faec 100644 --- a/database/build/list_repo_test.go +++ b/database/build/list_repo_test.go @@ -9,34 +9,46 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestBuild_Engine_ListBuildsForRepo(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType(constants.PipelineTypeYAML) + _repo.SetTopics([]string{}) + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetDeployPayload(nil) _buildOne.SetCreated(1) - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetDeployPayload(nil) _buildTwo.SetCreated(2) - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -52,8 +64,18 @@ func TestBuild_Engine_ListBuildsForRepo(t *testing.T) { AddRow(2, 1, nil, 2, 0, "", "", "", "", 0, 2, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0). AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0, "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND created < $2 AND created > $3 ORDER BY number DESC LIMIT $4`).WithArgs(1, AnyArgument{}, 0, 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -68,24 +90,44 @@ func TestBuild_Engine_ListBuildsForRepo(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Build + want []*api.Build }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Build{_buildTwo, _buildOne}, + want: []*api.Build{_buildTwo, _buildOne}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Build{_buildTwo, _buildOne}, + want: []*api.Build{_buildTwo, _buildOne}, }, } diff --git a/database/build/list_test.go b/database/build/list_test.go index 2579ac937..15688d252 100644 --- a/database/build/list_test.go +++ b/database/build/list_test.go @@ -8,21 +8,42 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestBuild_Engine_ListBuilds(t *testing.T) { // setup types - _buildOne := testBuild() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType(constants.PipelineTypeYAML) + _repo.SetTopics([]string{}) + + _buildOne := testutils.APIBuild() _buildOne.SetID(1) - _buildOne.SetRepoID(1) + _buildOne.SetRepo(_repo) _buildOne.SetNumber(1) _buildOne.SetDeployNumber(0) _buildOne.SetDeployPayload(nil) - _buildTwo := testBuild() + _buildTwo := testutils.APIBuild() _buildTwo.SetID(2) - _buildTwo.SetRepoID(1) + _buildTwo.SetRepo(_repo) _buildTwo.SetNumber(2) _buildTwo.SetDeployNumber(0) _buildTwo.SetDeployPayload(nil) @@ -42,8 +63,18 @@ func TestBuild_Engine_ListBuilds(t *testing.T) { AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", 0, nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0). AddRow(2, 1, nil, 2, 0, "", "", "", "", 0, 0, 0, 0, "", 0, nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0) + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) + // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "builds"`).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -58,24 +89,44 @@ func TestBuild_Engine_ListBuilds(t *testing.T) { t.Errorf("unable to create test build for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Build + want []*api.Build }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Build{_buildOne, _buildTwo}, + want: []*api.Build{_buildOne, _buildTwo}, }, } diff --git a/database/build/opts.go b/database/build/opts.go index 7792f9a12..a83ef0a16 100644 --- a/database/build/opts.go +++ b/database/build/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) @@ -23,6 +22,16 @@ func WithClient(client *gorm.DB) EngineOpt { } } +// WithEncryptionKey sets the encryption key in the database engine for Builds. +func WithEncryptionKey(key string) EngineOpt { + return func(e *engine) error { + // set the encryption key in the build engine + e.config.EncryptionKey = key + + return nil + } +} + // WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Builds. func WithLogger(logger *logrus.Entry) EngineOpt { return func(e *engine) error { diff --git a/database/build/opts_test.go b/database/build/opts_test.go index 43fea307f..949651dac 100644 --- a/database/build/opts_test.go +++ b/database/build/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/build/table.go b/database/build/table.go index fde722a17..8a7ae172c 100644 --- a/database/build/table.go +++ b/database/build/table.go @@ -36,6 +36,7 @@ builds ( message VARCHAR(2000), commit VARCHAR(500), sender VARCHAR(250), + sender_scm_id VARCHAR(250), author VARCHAR(250), email VARCHAR(500), link VARCHAR(1000), @@ -80,6 +81,7 @@ builds ( message TEXT, 'commit' TEXT, sender TEXT, + sender_scm_id TEXT, author TEXT, email TEXT, link TEXT, @@ -100,7 +102,7 @@ builds ( // CreateBuildTable creates the builds table in the database. func (e *engine) CreateBuildTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating builds table in the database") + e.logger.Tracef("creating builds table") // handle the driver provided to create the table switch driver { diff --git a/database/build/update.go b/database/build/update.go index ec647a879..e8ff1f274 100644 --- a/database/build/update.go +++ b/database/build/update.go @@ -6,26 +6,21 @@ package build import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // UpdateBuild updates an existing build in the database. -func (e *engine) UpdateBuild(ctx context.Context, b *library.Build) (*library.Build, error) { +func (e *engine) UpdateBuild(ctx context.Context, b *api.Build) (*api.Build, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("updating build %d in the database", b.GetNumber()) + }).Tracef("updating build %d", b.GetNumber()) - // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary - build := database.BuildFromLibrary(b) + build := types.BuildFromAPI(b) - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#Build.Validate err := build.Validate() if err != nil { return nil, err @@ -35,7 +30,13 @@ func (e *engine) UpdateBuild(ctx context.Context, b *library.Build) (*library.Bu build = build.Crop() // send query to the database - result := e.client.Table(constants.TableBuild).Save(build) + err = e.client.Table(constants.TableBuild).Save(build).Error + if err != nil { + return nil, err + } + + result := build.ToAPI() + result.SetRepo(b.GetRepo()) - return build.ToLibrary(), result.Error + return result, nil } diff --git a/database/build/update_test.go b/database/build/update_test.go index 4d75183df..4c0c1137e 100644 --- a/database/build/update_test.go +++ b/database/build/update_test.go @@ -8,13 +8,31 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestBuild_Engine_UpdateBuild(t *testing.T) { // setup types - _build := testBuild() + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _build.SetDeployPayload(nil) @@ -23,9 +41,9 @@ func TestBuild_Engine_UpdateBuild(t *testing.T) { // ensure the mock expects the query _mock.ExpectExec(`UPDATE "builds" -SET "repo_id"=$1,"pipeline_id"=$2,"number"=$3,"parent"=$4,"event"=$5,"event_action"=$6,"status"=$7,"error"=$8,"enqueued"=$9,"created"=$10,"started"=$11,"finished"=$12,"deploy"=$13,"deploy_number"=$14,"deploy_payload"=$15,"clone"=$16,"source"=$17,"title"=$18,"message"=$19,"commit"=$20,"sender"=$21,"author"=$22,"email"=$23,"link"=$24,"branch"=$25,"ref"=$26,"base_ref"=$27,"head_ref"=$28,"host"=$29,"runtime"=$30,"distribution"=$31,"approved_at"=$32,"approved_by"=$33 -WHERE "id" = $34`). - WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +SET "repo_id"=$1,"pipeline_id"=$2,"number"=$3,"parent"=$4,"event"=$5,"event_action"=$6,"status"=$7,"error"=$8,"enqueued"=$9,"created"=$10,"started"=$11,"finished"=$12,"deploy"=$13,"deploy_number"=$14,"deploy_payload"=$15,"clone"=$16,"source"=$17,"title"=$18,"message"=$19,"commit"=$20,"sender"=$21,"sender_scm_id"=$22,"author"=$23,"email"=$24,"link"=$25,"branch"=$26,"ref"=$27,"base_ref"=$28,"head_ref"=$29,"host"=$30,"runtime"=$31,"distribution"=$32,"approved_at"=$33,"approved_by"=$34 +WHERE "id" = $35`). + WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/close_test.go b/database/close_test.go index 7813513aa..f89d93451 100644 --- a/database/close_test.go +++ b/database/close_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/context.go b/database/context.go index fd4cae217..a11863e7d 100644 --- a/database/context.go +++ b/database/context.go @@ -49,6 +49,10 @@ func FromCLIContext(c *cli.Context) (Interface, error) { WithConnectionOpen(c.Int("database.connection.open")), WithDriver(c.String("database.driver")), WithEncryptionKey(c.String("database.encryption.key")), + WithLogLevel(c.String("database.log.level")), + WithLogSkipNotFound(c.Bool("database.log.skip_notfound")), + WithLogSlowThreshold(c.Duration("database.log.slow_threshold")), + WithLogShowSQL(c.Bool("database.log.show_sql")), WithSkipCreation(c.Bool("database.skip_creation")), ) } diff --git a/database/dashboard/create.go b/database/dashboard/create.go new file mode 100644 index 000000000..1af56728a --- /dev/null +++ b/database/dashboard/create.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// CreateDashboard creates a new dashboard in the database. +func (e *engine) CreateDashboard(ctx context.Context, d *api.Dashboard) (*api.Dashboard, error) { + e.logger.WithFields(logrus.Fields{ + "dashboard": d.GetName(), + }).Tracef("creating dashboard %s", d.GetName()) + + dashboard := types.DashboardFromAPI(d) + + err := dashboard.Validate() + if err != nil { + return nil, err + } + + // send query to the database + result := e.client.Table(constants.TableDashboard).Create(dashboard) + + return dashboard.ToAPI(), result.Error +} diff --git a/database/dashboard/create_test.go b/database/dashboard/create_test.go new file mode 100644 index 000000000..dba9fffc3 --- /dev/null +++ b/database/dashboard/create_test.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestDashboard_Engine_CreateDashboard(t *testing.T) { + // setup types + _dashRepo := new(api.DashboardRepo) + _dashRepo.SetID(1) + _dashRepo.SetBranches([]string{"main"}) + _dashRepo.SetEvents([]string{"push"}) + _dashRepos := []*api.DashboardRepo{_dashRepo} + + _admin := new(api.User) + _admin.SetID(1) + _admin.SetName("octocat") + _admin.SetActive(true) + _admins := []*api.User{_admin} + + _dashboard := testutils.APIDashboard() + _dashboard.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + _dashboard.SetName("dash") + _dashboard.SetCreatedAt(1) + _dashboard.SetCreatedBy("user1") + _dashboard.SetUpdatedAt(1) + _dashboard.SetUpdatedBy("user2") + _dashboard.SetAdmins(_admins) + _dashboard.SetRepos(_dashRepos) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"id"}).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8") + + // ensure the mock expects the query + _mock.ExpectQuery(`INSERT INTO "dashboards" +("name","created_at","created_by","updated_at","updated_by","admins","repos","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`). + WithArgs("dash", 1, "user1", 1, "user2", `[{"id":1,"name":"octocat","active":true}]`, `[{"id":1,"branches":["main"],"events":["push"]}]`, "c8da1302-07d6-11ea-882f-4893bca275b8"). + WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CreateDashboard(context.TODO(), _dashboard) + + if test.failure { + if err == nil { + t.Errorf("CreateDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateDashboard for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, _dashboard) { + t.Errorf("CreateDashboard for %s returned %s, want %s", test.name, got, _dashboard) + } + }) + } +} diff --git a/database/dashboard/dashboard.go b/database/dashboard/dashboard.go new file mode 100644 index 000000000..b21471114 --- /dev/null +++ b/database/dashboard/dashboard.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "errors" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + "github.com/go-vela/server/constants" +) + +var ( + // ErrEmptyDashName defines the error type when a + // User type has an empty Name field provided. + ErrEmptyDashName = errors.New("empty dashboard name provided") + + // ErrExceededAdminLimit defines the error type when a + // User type has Admins field provided that exceeds the database limit. + ErrExceededAdminLimit = errors.New("exceeded admins limit") +) + +type ( + // config represents the settings required to create the engine that implements the DashboardService interface. + config struct { + // specifies to skip creating tables and indexes for the Dashboard engine + SkipCreation bool + // specifies the driver for proper popping query + Driver string + } + + // engine represents the dashboard functionality that implements the DashboardService interface. + engine struct { + // engine configuration settings used in dashboard functions + config *config + + ctx context.Context + + // gorm.io/gorm database client used in dashboard functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in dashboard functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with dashboards in the database. +// +//nolint:revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new Dashboard engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating dashboard database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of dashboards table and indexes") + + return e, nil + } + + // create the dashboards table + err := e.CreateDashboardTable(e.ctx, e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableDashboard, err) + } + + return e, nil +} diff --git a/database/dashboard/dashboard_test.go b/database/dashboard/dashboard_test.go new file mode 100644 index 000000000..77d5069a9 --- /dev/null +++ b/database/dashboard/dashboard_test.go @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "database/sql/driver" + "reflect" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestDashboard_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres dashboard engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite dashboard engine: %v", err) + } + + return _engine +} + +// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values +// that are otherwise not easily compared. These typically would be values generated +// before adding or updating them in the database. +// +// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime +type NowTimestamp struct{} + +// Match satisfies sqlmock.Argument interface. +func (t NowTimestamp) Match(v driver.Value) bool { + ts, ok := v.(int64) + if !ok { + return false + } + now := time.Now().Unix() + + return now-ts < 10 +} diff --git a/database/dashboard/delete.go b/database/dashboard/delete.go new file mode 100644 index 000000000..2f49cca9f --- /dev/null +++ b/database/dashboard/delete.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// DeleteDashboard deletes an existing dashboard from the database. +func (e *engine) DeleteDashboard(ctx context.Context, d *api.Dashboard) error { + e.logger.WithFields(logrus.Fields{ + "dashboard": d.GetID(), + }).Tracef("deleting dashboard %s", d.GetID()) + + dashboard := types.DashboardFromAPI(d) + + // send query to the database + return e.client. + Table(constants.TableDashboard). + Delete(dashboard). + Error +} diff --git a/database/dashboard/delete_test.go b/database/dashboard/delete_test.go new file mode 100644 index 000000000..1987d51a4 --- /dev/null +++ b/database/dashboard/delete_test.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestDashboard_Engine_DeleteDashboard(t *testing.T) { + // setup types + _dashboard := testutils.APIDashboard() + _dashboard.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + _dashboard.SetName("vela") + _dashboard.SetCreatedAt(1) + _dashboard.SetCreatedBy("user1") + _dashboard.SetUpdatedAt(1) + _dashboard.SetUpdatedBy("user2") + _dashboard.SetRepos([]*api.DashboardRepo{testutils.APIDashboardRepo()}) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`DELETE FROM "dashboards" WHERE "dashboards"."id" = $1`). + WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateDashboard(context.TODO(), _dashboard) + if err != nil { + t.Errorf("unable to create test dashboard for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.DeleteDashboard(context.TODO(), _dashboard) + + if test.failure { + if err == nil { + t.Errorf("DeleteDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("DeleteDashboard for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/dashboard/get.go b/database/dashboard/get.go new file mode 100644 index 000000000..7c29c987f --- /dev/null +++ b/database/dashboard/get.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// GetDashboard gets a dashboard by UUID from the database. +func (e *engine) GetDashboard(ctx context.Context, id string) (*api.Dashboard, error) { + e.logger.Tracef("getting dashboard %s", id) + + // variable to store query results + d := new(types.Dashboard) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableDashboard). + Where("id = ?", id). + Take(d). + Error + if err != nil { + return nil, err + } + + return d.ToAPI(), nil +} diff --git a/database/dashboard/get_test.go b/database/dashboard/get_test.go new file mode 100644 index 000000000..3727328a6 --- /dev/null +++ b/database/dashboard/get_test.go @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestRepo_Engine_GetDashboard(t *testing.T) { + // setup types + _dashRepo := new(api.DashboardRepo) + _dashRepo.SetID(1) + _dashRepo.SetBranches([]string{"main"}) + _dashRepo.SetEvents([]string{"push"}) + _dashRepos := []*api.DashboardRepo{_dashRepo} + + _admin := new(api.User) + _admin.SetID(1) + _admin.SetName("octocat") + _admin.SetActive(true) + _admins := []*api.User{_admin} + + _dashboard := testutils.APIDashboard() + _dashboard.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + _dashboard.SetName("dash") + _dashboard.SetCreatedAt(1) + _dashboard.SetCreatedBy("user1") + _dashboard.SetUpdatedAt(1) + _dashboard.SetUpdatedBy("user2") + _dashboard.SetRepos(_dashRepos) + _dashboard.SetAdmins(_admins) + + // uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "name", "created_at", "created_by", "updated_at", "updated_by", "admins", "repos"}, + ).AddRow("c8da1302-07d6-11ea-882f-4893bca275b8", "dash", 1, "user1", 1, "user2", []byte(`[{"id":1,"name":"octocat","active":true}]`), []byte(`[{"id":1,"branches":["main"],"events":["push"]}]`)) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "dashboards" WHERE id = $1 LIMIT $2`).WithArgs("c8da1302-07d6-11ea-882f-4893bca275b8", 1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateDashboard(context.TODO(), _dashboard) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *api.Dashboard + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _dashboard, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _dashboard, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetDashboard(context.TODO(), "c8da1302-07d6-11ea-882f-4893bca275b8") + + if test.failure { + if err == nil { + t.Errorf("GetDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetDashboard for %s returned err: %v", test.name, err) + } + + if diff := cmp.Diff(got, test.want); diff != "" { + t.Errorf("GetDashboard mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/database/dashboard/interface.go b/database/dashboard/interface.go new file mode 100644 index 000000000..027389f00 --- /dev/null +++ b/database/dashboard/interface.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + api "github.com/go-vela/server/api/types" +) + +// DashboardInterface represents the Vela interface for repo +// functions with the supported Database backends. +// +//nolint:revive // ignore name stutter +type DashboardInterface interface { + // Dashboard Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + + // CreateDashboardTable defines a function that creates the dashboards table. + CreateDashboardTable(context.Context, string) error + + // Dashboard Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CreateDashboard defines a function that creates a dashboard. + CreateDashboard(context.Context, *api.Dashboard) (*api.Dashboard, error) + // DeleteDashboard defines a function that deletes a dashboard. + DeleteDashboard(context.Context, *api.Dashboard) error + // GetDashboard defines a function that gets a dashboard by ID. + GetDashboard(context.Context, string) (*api.Dashboard, error) + // UpdateDashboard defines a function that updates a dashboard. + UpdateDashboard(context.Context, *api.Dashboard) (*api.Dashboard, error) +} diff --git a/database/dashboard/opts.go b/database/dashboard/opts.go new file mode 100644 index 000000000..548d26d21 --- /dev/null +++ b/database/dashboard/opts.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for dashboards. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for dashboards. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the dashboard engine + e.client = client + + return nil + } +} + +// WithDriver sets the driver type in the database engine for dashboards. +func WithDriver(driver string) EngineOpt { + return func(e *engine) error { + // set the driver type in the dashboard engine + e.config.Driver = driver + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for dashboards. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the dashboard engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for dashboards. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the dashboard engine + e.config.SkipCreation = skipCreation + + return nil + } +} + +// WithContext sets the context in the database engine for dashboards. +func WithContext(ctx context.Context) EngineOpt { + return func(e *engine) error { + e.ctx = ctx + + return nil + } +} diff --git a/database/dashboard/opts_test.go b/database/dashboard/opts_test.go new file mode 100644 index 000000000..0b41b3861 --- /dev/null +++ b/database/dashboard/opts_test.go @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "reflect" + "testing" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func TestDashboard_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestDashboard_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestDashboard_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} + +func TestDashboard_EngineOpt_WithContext(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + ctx context.Context + want context.Context + }{ + { + failure: false, + name: "context set to TODO", + ctx: context.TODO(), + want: context.TODO(), + }, + { + failure: false, + name: "context set to nil", + ctx: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithContext(test.ctx)(e) + + if test.failure { + if err == nil { + t.Errorf("WithContext for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithContext returned err: %v", err) + } + + if !reflect.DeepEqual(e.ctx, test.want) { + t.Errorf("WithContext is %v, want %v", e.ctx, test.want) + } + }) + } +} diff --git a/database/dashboard/table.go b/database/dashboard/table.go new file mode 100644 index 000000000..1cedb30c3 --- /dev/null +++ b/database/dashboard/table.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres dashboards table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +dashboards ( + id UUID PRIMARY KEY, + name VARCHAR(250), + created_at INTEGER, + created_by VARCHAR(250), + updated_at INTEGER, + updated_by VARCHAR(250), + admins JSON DEFAULT NULL, + repos JSON DEFAULT NULL +); +` + + // CreateSqliteTable represents a query to create the Sqlite dashboards table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +dashboards ( + id TEXT PRIMARY KEY, + name TEXT, + created_at INTEGER, + created_by TEXT, + updated_at INTEGER, + updated_by TEXT, + admins TEXT, + repos TEXT +); +` +) + +// CreateDashboardTable creates the dashboards table in the database. +func (e *engine) CreateDashboardTable(ctx context.Context, driver string) error { + e.logger.Tracef("creating dashboards table") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the dashboards table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the dashboards table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/dashboard/table_test.go b/database/dashboard/table_test.go new file mode 100644 index 000000000..2f9c3d953 --- /dev/null +++ b/database/dashboard/table_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestDashboard_Engine_CreateDashboardTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateDashboardTable(context.TODO(), test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateDashboardTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateDashboardTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/dashboard/update.go b/database/dashboard/update.go new file mode 100644 index 000000000..e13387cb3 --- /dev/null +++ b/database/dashboard/update.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// UpdateDashboard updates an existing dashboard in the database. +func (e *engine) UpdateDashboard(ctx context.Context, d *api.Dashboard) (*api.Dashboard, error) { + e.logger.WithFields(logrus.Fields{ + "dashboard": d.GetID(), + }).Tracef("creating dashboard %s", d.GetID()) + + dashboard := types.DashboardFromAPI(d) + + err := dashboard.Validate() + if err != nil { + return nil, err + } + + // send query to the database + err = e.client.Table(constants.TableDashboard).Save(dashboard).Error + if err != nil { + return nil, err + } + + return dashboard.ToAPI(), nil +} diff --git a/database/dashboard/update_test.go b/database/dashboard/update_test.go new file mode 100644 index 000000000..95437613e --- /dev/null +++ b/database/dashboard/update_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestDashboard_Engine_UpdateDashboard(t *testing.T) { + // setup types + _dashRepo := new(api.DashboardRepo) + _dashRepo.SetID(1) + _dashRepo.SetBranches([]string{"main"}) + _dashRepo.SetEvents([]string{"push"}) + _dashRepos := []*api.DashboardRepo{_dashRepo} + + _admin := new(api.User) + _admin.SetID(1) + _admin.SetName("octocat") + _admin.SetActive(true) + _admins := []*api.User{_admin} + + _dashboard := testutils.APIDashboard() + _dashboard.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + _dashboard.SetName("dash") + _dashboard.SetCreatedAt(1) + _dashboard.SetCreatedBy("user1") + _dashboard.SetUpdatedAt(1) + _dashboard.SetUpdatedBy("user2") + _dashboard.SetRepos(_dashRepos) + _dashboard.SetAdmins(_admins) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "dashboards" +SET "name"=$1,"created_at"=$2,"created_by"=$3,"updated_at"=$4,"updated_by"=$5,"admins"=$6,"repos"=$7 WHERE "id" = $8`). + WithArgs("dash", 1, "user1", NowTimestamp{}, "user2", `[{"id":1,"name":"octocat","active":true}]`, `[{"id":1,"branches":["main"],"events":["push"]}]`, "c8da1302-07d6-11ea-882f-4893bca275b8"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateDashboard(context.TODO(), _dashboard) + if err != nil { + t.Errorf("unable to create test dashboard for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.UpdateDashboard(context.TODO(), _dashboard) + _dashboard.SetUpdatedAt(got.GetUpdatedAt()) + + if test.failure { + if err == nil { + t.Errorf("UpdateDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateDashboard for %s returned err: %v", test.name, err) + } + + if diff := cmp.Diff(got, _dashboard); diff != "" { + t.Errorf("GetDashboard mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/database/database.go b/database/database.go index 3fc1adb94..f9e2658ed 100644 --- a/database/database.go +++ b/database/database.go @@ -7,25 +7,28 @@ import ( "fmt" "time" + "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "github.com/go-vela/server/database/build" + "github.com/go-vela/server/database/dashboard" "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/schedule" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/service" + "github.com/go-vela/server/database/settings" "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" - - "gorm.io/driver/postgres" - "gorm.io/driver/sqlite" - "gorm.io/gorm" ) type ( @@ -45,6 +48,14 @@ type ( Driver string // specifies the encryption key to use for the database engine EncryptionKey string + // specifies the database engine specific log level + LogLevel string + // specifies to skip logging when a record is not found + LogSkipNotFound bool + // specifies the threshold for slow queries in the database engine + LogSlowThreshold time.Duration + // specifies whether to log SQL queries in the database engine + LogShowSQL bool // specifies to skip creating tables and indexes for the database engine SkipCreation bool } @@ -60,10 +71,13 @@ type ( // sirupsen/logrus logger used in database functions logger *logrus.Entry + settings.SettingsInterface build.BuildInterface + dashboard.DashboardInterface executable.BuildExecutableInterface deployment.DeploymentInterface hook.HookInterface + jwk.JWKInterface log.LogInterface pipeline.PipelineInterface repo.RepoInterface @@ -107,21 +121,62 @@ func New(opts ...EngineOpt) (Interface, error) { return nil, err } - // update the logger with additional metadata + // by default use the global logger with additional metadata e.logger = logrus.NewEntry(logrus.StandardLogger()).WithField("database", e.Driver()) + // translate the log level to logrus level for the database engine + var dbLogLevel logrus.Level + + switch e.config.LogLevel { + case "t", "trace", "Trace", "TRACE": + dbLogLevel = logrus.TraceLevel + case "d", "debug", "Debug", "DEBUG": + dbLogLevel = logrus.DebugLevel + case "i", "info", "Info", "INFO": + dbLogLevel = logrus.InfoLevel + case "w", "warn", "Warn", "WARN": + dbLogLevel = logrus.WarnLevel + case "e", "error", "Error", "ERROR": + dbLogLevel = logrus.ErrorLevel + case "f", "fatal", "Fatal", "FATAL": + dbLogLevel = logrus.FatalLevel + case "p", "panic", "Panic", "PANIC": + dbLogLevel = logrus.PanicLevel + } + + // if the log level for the database engine is different than + // the global log level, create a new logrus instance + if dbLogLevel != logrus.GetLevel() { + log := logrus.New() + + // set the custom log level + log.Level = dbLogLevel + + // copy the formatter from the global logger to + // retain the same format for the database engine + log.Formatter = logrus.StandardLogger().Formatter + + // update the logger with additional metadata + e.logger = logrus.NewEntry(log).WithField("database", e.Driver()) + } + e.logger.Trace("creating database engine from configuration") - // process the database driver being provided + + // configure gorm to use logrus as internal logger + gormConfig := &gorm.Config{ + Logger: NewGormLogger(e.logger, e.config.LogSlowThreshold, e.config.LogSkipNotFound, e.config.LogShowSQL), + } + switch e.config.Driver { case constants.DriverPostgres: // create the new Postgres database client - e.client, err = gorm.Open(postgres.Open(e.config.Address), &gorm.Config{}) + e.client, err = gorm.Open(postgres.Open(e.config.Address), gormConfig) if err != nil { return nil, err } case constants.DriverSqlite: // create the new Sqlite database client - e.client, err = gorm.Open(sqlite.Open(e.config.Address), &gorm.Config{}) + e.client, err = gorm.Open(sqlite.Open(e.config.Address), gormConfig) if err != nil { return nil, err } @@ -171,5 +226,9 @@ func NewTest() (Interface, error) { WithDriver("sqlite3"), WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), WithSkipCreation(false), + WithLogLevel("warn"), + WithLogShowSQL(false), + WithLogSkipNotFound(true), + WithLogSlowThreshold(200*time.Millisecond), ) } diff --git a/database/database_test.go b/database/database_test.go index a2ea86848..a8b70111a 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -8,7 +8,6 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -33,6 +32,10 @@ func TestDatabase_New(t *testing.T) { ConnectionOpen: 20, EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, }, { @@ -47,6 +50,10 @@ func TestDatabase_New(t *testing.T) { ConnectionOpen: 20, EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, }, { @@ -61,6 +68,10 @@ func TestDatabase_New(t *testing.T) { ConnectionOpen: 20, EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, }, { @@ -75,6 +86,10 @@ func TestDatabase_New(t *testing.T) { ConnectionOpen: 20, EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, }, } @@ -89,6 +104,10 @@ func TestDatabase_New(t *testing.T) { WithConnectionIdle(test.config.ConnectionIdle), WithConnectionOpen(test.config.ConnectionOpen), WithDriver(test.config.Driver), + WithLogLevel(test.config.LogLevel), + WithLogShowSQL(test.config.LogShowSQL), + WithLogSkipNotFound(test.config.LogSkipNotFound), + WithLogSlowThreshold(test.config.LogSlowThreshold), WithEncryptionKey(test.config.EncryptionKey), WithSkipCreation(test.config.SkipCreation), ) @@ -120,6 +139,10 @@ func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { Driver: "postgres", EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, logger: logrus.NewEntry(logrus.StandardLogger()), } @@ -162,6 +185,10 @@ func testSqlite(t *testing.T) *engine { Driver: "sqlite3", EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false, + LogLevel: "info", + LogSkipNotFound: true, + LogSlowThreshold: 100 * time.Millisecond, + LogShowSQL: false, }, logger: logrus.NewEntry(logrus.StandardLogger()), } diff --git a/database/deployment/count.go b/database/deployment/count.go index c83f739c5..26189cf10 100644 --- a/database/deployment/count.go +++ b/database/deployment/count.go @@ -10,7 +10,7 @@ import ( // CountDeployments gets the count of all deployments from the database. func (e *engine) CountDeployments(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all deployments from the database") + e.logger.Tracef("getting count of all deployments") // variable to store query results var d int64 diff --git a/database/deployment/count_repo.go b/database/deployment/count_repo.go index 93a7c1ac8..94ed86558 100644 --- a/database/deployment/count_repo.go +++ b/database/deployment/count_repo.go @@ -5,17 +5,18 @@ package deployment import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountDeploymentsForRepo gets the count of deployments by repo ID from the database. -func (e *engine) CountDeploymentsForRepo(ctx context.Context, r *library.Repo) (int64, error) { +func (e *engine) CountDeploymentsForRepo(ctx context.Context, r *api.Repo) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting count of deployments for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of deployments for repo %s", r.GetFullName()) // variable to store query results var d int64 diff --git a/database/deployment/count_repo_test.go b/database/deployment/count_repo_test.go index 6d3afece4..775361570 100644 --- a/database/deployment/count_repo_test.go +++ b/database/deployment/count_repo_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -15,7 +17,7 @@ func TestDeployment_Engine_CountDeploymentsForRepo(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) @@ -30,7 +32,7 @@ func TestDeployment_Engine_CountDeploymentsForRepo(t *testing.T) { _deploymentOne.SetCreatedBy("octocat") _deploymentOne.SetBuilds(builds) - _deploymentTwo := testDeployment() + _deploymentTwo := testutils.APIDeployment() _deploymentTwo.SetID(2) _deploymentTwo.SetRepoID(2) _deploymentTwo.SetNumber(2) @@ -45,9 +47,9 @@ func TestDeployment_Engine_CountDeploymentsForRepo(t *testing.T) { _deploymentTwo.SetCreatedBy("octocat") _deploymentTwo.SetBuilds(builds) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/deployment/count_test.go b/database/deployment/count_test.go index 5576be750..914bb22a5 100644 --- a/database/deployment/count_test.go +++ b/database/deployment/count_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" ) @@ -50,7 +52,7 @@ func TestDeployment_Engine_CountDeployments(t *testing.T) { builds = append(builds, buildOne) // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) @@ -65,7 +67,7 @@ func TestDeployment_Engine_CountDeployments(t *testing.T) { _deploymentOne.SetCreatedBy("octocat") _deploymentOne.SetBuilds(builds) - _deploymentTwo := testDeployment() + _deploymentTwo := testutils.APIDeployment() _deploymentTwo.SetID(2) _deploymentTwo.SetRepoID(2) _deploymentTwo.SetNumber(2) diff --git a/database/deployment/create.go b/database/deployment/create.go index 467ca7cbd..c766596e8 100644 --- a/database/deployment/create.go +++ b/database/deployment/create.go @@ -5,17 +5,18 @@ package deployment import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateDeployment creates a new deployment in the database. func (e *engine) CreateDeployment(ctx context.Context, d *library.Deployment) (*library.Deployment, error) { e.logger.WithFields(logrus.Fields{ "deployment": d.GetID(), - }).Tracef("creating deployment %d in the database", d.GetID()) + }).Tracef("creating deployment %d", d.GetID()) // cast the library type to database type deployment := database.DeploymentFromLibrary(d) diff --git a/database/deployment/create_test.go b/database/deployment/create_test.go index 5f584f3b7..92a2e3448 100644 --- a/database/deployment/create_test.go +++ b/database/deployment/create_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -14,7 +16,7 @@ func TestDeployment_Engine_CreateDeployment(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) diff --git a/database/deployment/delete.go b/database/deployment/delete.go index 73e2658b9..606ef8262 100644 --- a/database/deployment/delete.go +++ b/database/deployment/delete.go @@ -4,17 +4,19 @@ package deployment import ( "context" + + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeleteDeployment deletes an existing deployment from the database. func (e *engine) DeleteDeployment(ctx context.Context, d *library.Deployment) error { e.logger.WithFields(logrus.Fields{ "deployment": d.GetID(), - }).Tracef("deleting deployment %d in the database", d.GetID()) + }).Tracef("deleting deployment %d", d.GetID()) // cast the library type to database type deployment := database.DeploymentFromLibrary(d) diff --git a/database/deployment/delete_test.go b/database/deployment/delete_test.go index 2fcd4c68f..e037c684c 100644 --- a/database/deployment/delete_test.go +++ b/database/deployment/delete_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -14,7 +16,7 @@ func TestDeployment_Engine_DeleteDeployment(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) diff --git a/database/deployment/deployment.go b/database/deployment/deployment.go index 20df5e838..cd9192438 100644 --- a/database/deployment/deployment.go +++ b/database/deployment/deployment.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -60,7 +60,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating deployment database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of deployment table and indexes in the database") + e.logger.Warning("skipping creation of deployment table and indexes") return e, nil } diff --git a/database/deployment/deployment_test.go b/database/deployment/deployment_test.go index 4421f4f9d..6ab73474f 100644 --- a/database/deployment/deployment_test.go +++ b/database/deployment/deployment_test.go @@ -7,10 +7,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" - "github.com/go-vela/types/raw" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -164,54 +161,3 @@ func testSqlite(t *testing.T) *engine { return _engine } - -// testDeployment is a test helper function to create a library -// Deployment type with all fields set to their zero values. -func testDeployment() *library.Deployment { - builds := []*library.Build{} - return &library.Deployment{ - ID: new(int64), - RepoID: new(int64), - Number: new(int64), - URL: new(string), - Commit: new(string), - Ref: new(string), - Task: new(string), - Target: new(string), - Description: new(string), - Payload: new(raw.StringSliceMap), - CreatedAt: new(int64), - CreatedBy: new(string), - Builds: builds, - } -} - -// testRepo is a test helper function to create a library -// Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ - ID: new(int64), - UserID: new(int64), - BuildLimit: new(int64), - Timeout: new(int64), - Counter: new(int), - PipelineType: new(string), - Hash: new(string), - Org: new(string), - Name: new(string), - FullName: new(string), - Link: new(string), - Clone: new(string), - Branch: new(string), - Visibility: new(string), - PreviousName: new(string), - Private: new(bool), - Trusted: new(bool), - Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), - } -} diff --git a/database/deployment/get.go b/database/deployment/get.go index 9d84b958e..214dcf57f 100644 --- a/database/deployment/get.go +++ b/database/deployment/get.go @@ -13,7 +13,7 @@ import ( // GetDeployment gets a deployment by ID from the database. func (e *engine) GetDeployment(ctx context.Context, id int64) (*library.Deployment, error) { - e.logger.Tracef("getting deployment %d from the database", id) + e.logger.Tracef("getting deployment %d", id) // variable to store query results d := new(database.Deployment) diff --git a/database/deployment/get_repo.go b/database/deployment/get_repo.go index b240adc20..1558b0428 100644 --- a/database/deployment/get_repo.go +++ b/database/deployment/get_repo.go @@ -6,19 +6,21 @@ import ( "context" "strconv" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetDeploymentForRepo gets a deployment by repo ID and number from the database. -func (e *engine) GetDeploymentForRepo(ctx context.Context, r *library.Repo, number int64) (*library.Deployment, error) { +func (e *engine) GetDeploymentForRepo(ctx context.Context, r *api.Repo, number int64) (*library.Deployment, error) { e.logger.WithFields(logrus.Fields{ "deployment": number, "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting deployment %s/%d from the database", r.GetFullName(), number) + }).Tracef("getting deployment %s/%d", r.GetFullName(), number) // variable to store query results d := new(database.Deployment) diff --git a/database/deployment/get_repo_test.go b/database/deployment/get_repo_test.go index 5ddbb61d5..78070f8c3 100644 --- a/database/deployment/get_repo_test.go +++ b/database/deployment/get_repo_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -15,7 +17,7 @@ func TestDeployment_Engine_GetDeploymentForRepo(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) @@ -30,9 +32,9 @@ func TestDeployment_Engine_GetDeploymentForRepo(t *testing.T) { _deploymentOne.SetCreatedBy("octocat") _deploymentOne.SetBuilds(builds) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/deployment/get_test.go b/database/deployment/get_test.go index b557e9330..b579aeecd 100644 --- a/database/deployment/get_test.go +++ b/database/deployment/get_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -15,7 +17,7 @@ func TestDeployment_Engine_GetDeployment(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) diff --git a/database/deployment/index.go b/database/deployment/index.go index 0fa2fac4c..f9b6a3419 100644 --- a/database/deployment/index.go +++ b/database/deployment/index.go @@ -17,7 +17,7 @@ ON deployments (repo_id); // CreateDeploymetsIndexes creates the indexes for the deployments table in the database. func (e *engine) CreateDeploymentIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for deployments table in the database") + e.logger.Tracef("creating indexes for deployments table") // create the repo_id column index for the deployments table return e.client.Exec(CreateRepoIDIndex).Error diff --git a/database/deployment/interface.go b/database/deployment/interface.go index 837aa2121..cd1f6cebc 100644 --- a/database/deployment/interface.go +++ b/database/deployment/interface.go @@ -5,6 +5,7 @@ package deployment import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -29,7 +30,7 @@ type DeploymentInterface interface { // CountDeployments defines a function that gets the count of all deployments. CountDeployments(context.Context) (int64, error) // CountDeploymentsForRepo defines a function that gets the count of deployments by repo ID. - CountDeploymentsForRepo(context.Context, *library.Repo) (int64, error) + CountDeploymentsForRepo(context.Context, *api.Repo) (int64, error) // CreateDeployment defines a function that creates a new deployment. CreateDeployment(context.Context, *library.Deployment) (*library.Deployment, error) // DeleteDeployment defines a function that deletes an existing deployment. @@ -37,11 +38,11 @@ type DeploymentInterface interface { // GetDeployment defines a function that gets a deployment by ID. GetDeployment(context.Context, int64) (*library.Deployment, error) // GetDeploymentForRepo defines a function that gets a deployment by repo ID and number. - GetDeploymentForRepo(context.Context, *library.Repo, int64) (*library.Deployment, error) + GetDeploymentForRepo(context.Context, *api.Repo, int64) (*library.Deployment, error) // ListDeployments defines a function that gets a list of all deployments. ListDeployments(context.Context) ([]*library.Deployment, error) // ListDeploymentsForRepo defines a function that gets a list of deployments by repo ID. - ListDeploymentsForRepo(context.Context, *library.Repo, int, int) ([]*library.Deployment, error) + ListDeploymentsForRepo(context.Context, *api.Repo, int, int) ([]*library.Deployment, error) // UpdateDeployment defines a function that updates an existing deployment. UpdateDeployment(context.Context, *library.Deployment) (*library.Deployment, error) } diff --git a/database/deployment/list.go b/database/deployment/list.go index b3f4da509..f395f668b 100644 --- a/database/deployment/list.go +++ b/database/deployment/list.go @@ -13,7 +13,7 @@ import ( // ListDeployments gets a list of all deployments from the database. func (e *engine) ListDeployments(ctx context.Context) ([]*library.Deployment, error) { - e.logger.Trace("listing all deployments from the database") + e.logger.Trace("listing all deployments") // variables to store query results and return value d := new([]database.Deployment) diff --git a/database/deployment/list_repo.go b/database/deployment/list_repo.go index 471da6cf0..8f9b40eb4 100644 --- a/database/deployment/list_repo.go +++ b/database/deployment/list_repo.go @@ -6,18 +6,20 @@ import ( "context" "strconv" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListDeploymentsForRepo gets a list of deployments by repo ID from the database. -func (e *engine) ListDeploymentsForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Deployment, error) { +func (e *engine) ListDeploymentsForRepo(ctx context.Context, r *api.Repo, page, perPage int) ([]*library.Deployment, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("listing deployments for repo %s from the database", r.GetFullName()) + }).Tracef("listing deployments for repo %s", r.GetFullName()) // variables to store query results and return value d := new([]database.Deployment) diff --git a/database/deployment/list_repo_test.go b/database/deployment/list_repo_test.go index a62948b17..c93de0939 100644 --- a/database/deployment/list_repo_test.go +++ b/database/deployment/list_repo_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestDeployment_Engine_ListDeploymentsForRepo(t *testing.T) { - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") @@ -21,7 +23,7 @@ func TestDeployment_Engine_ListDeploymentsForRepo(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) @@ -36,7 +38,7 @@ func TestDeployment_Engine_ListDeploymentsForRepo(t *testing.T) { _deploymentOne.SetCreatedBy("octocat") _deploymentOne.SetBuilds(builds) - _deploymentTwo := testDeployment() + _deploymentTwo := testutils.APIDeployment() _deploymentTwo.SetID(2) _deploymentTwo.SetRepoID(2) _deploymentTwo.SetNumber(2) diff --git a/database/deployment/list_test.go b/database/deployment/list_test.go index 203bebbe3..8edefe64e 100644 --- a/database/deployment/list_test.go +++ b/database/deployment/list_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -15,7 +17,7 @@ func TestDeployment_Engine_ListDeployments(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) @@ -30,7 +32,7 @@ func TestDeployment_Engine_ListDeployments(t *testing.T) { _deploymentOne.SetCreatedBy("octocat") _deploymentOne.SetBuilds(builds) - _deploymentTwo := testDeployment() + _deploymentTwo := testutils.APIDeployment() _deploymentTwo.SetID(2) _deploymentTwo.SetRepoID(2) _deploymentTwo.SetNumber(2) diff --git a/database/deployment/opts.go b/database/deployment/opts.go index db719b39d..95354e098 100644 --- a/database/deployment/opts.go +++ b/database/deployment/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/deployment/opts_test.go b/database/deployment/opts_test.go index ad393db84..a2dd298b7 100644 --- a/database/deployment/opts_test.go +++ b/database/deployment/opts_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/deployment/table.go b/database/deployment/table.go index 25ac28c78..b519c8abf 100644 --- a/database/deployment/table.go +++ b/database/deployment/table.go @@ -56,7 +56,7 @@ deployments ( // CreateDeploymentTable creates the deployments table in the database. func (e *engine) CreateDeploymentTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating deployments table in the database") + e.logger.Tracef("creating deployments table") // handle the driver provided to create the table switch driver { diff --git a/database/deployment/update.go b/database/deployment/update.go index b163516ff..c2bbecff0 100644 --- a/database/deployment/update.go +++ b/database/deployment/update.go @@ -4,17 +4,19 @@ package deployment import ( "context" + + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdateDeployment updates an existing deployment in the database. func (e *engine) UpdateDeployment(ctx context.Context, d *library.Deployment) (*library.Deployment, error) { e.logger.WithFields(logrus.Fields{ "deployment": d.GetID(), - }).Tracef("updating deployment %d in the database", d.GetID()) + }).Tracef("updating deployment %d", d.GetID()) // cast the library type to database type deployment := database.DeploymentFromLibrary(d) diff --git a/database/deployment/update_test.go b/database/deployment/update_test.go index ca9b2548e..d70b3ea85 100644 --- a/database/deployment/update_test.go +++ b/database/deployment/update_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -14,7 +16,7 @@ func TestDeployment_Engine_UpdateDeployment(t *testing.T) { builds := []*library.Build{} // setup types - _deploymentOne := testDeployment() + _deploymentOne := testutils.APIDeployment() _deploymentOne.SetID(1) _deploymentOne.SetRepoID(1) _deploymentOne.SetNumber(1) diff --git a/database/executable/clean.go b/database/executable/clean.go index 34579f778..b6bd2580a 100644 --- a/database/executable/clean.go +++ b/database/executable/clean.go @@ -5,8 +5,9 @@ package executable import ( "context" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) const CleanExecutablesPostgres = ` diff --git a/database/executable/clean_test.go b/database/executable/clean_test.go index edcb0cf21..92c8be8e8 100644 --- a/database/executable/clean_test.go +++ b/database/executable/clean_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" diff --git a/database/executable/create.go b/database/executable/create.go index 1e240c495..b619da068 100644 --- a/database/executable/create.go +++ b/database/executable/create.go @@ -6,10 +6,11 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateBuildExecutable creates a new build executable in the database. diff --git a/database/executable/executable.go b/database/executable/executable.go index 6160cd39a..8ac0475ef 100644 --- a/database/executable/executable.go +++ b/database/executable/executable.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( diff --git a/database/executable/executable_test.go b/database/executable/executable_test.go index 665ca6da5..2869001a8 100644 --- a/database/executable/executable_test.go +++ b/database/executable/executable_test.go @@ -8,13 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" ) func TestExecutable_New(t *testing.T) { diff --git a/database/executable/opts.go b/database/executable/opts.go index 3f3bde340..c7beb136d 100644 --- a/database/executable/opts.go +++ b/database/executable/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/executable/opts_test.go b/database/executable/opts_test.go index a2e1331bd..43626c093 100644 --- a/database/executable/opts_test.go +++ b/database/executable/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/executable/pop.go b/database/executable/pop.go index bd8e19ddb..62dfde193 100644 --- a/database/executable/pop.go +++ b/database/executable/pop.go @@ -5,15 +5,16 @@ package executable import ( "context" + "gorm.io/gorm/clause" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "gorm.io/gorm/clause" ) // PopBuildExecutable pops a build executable by build_id from the database. func (e *engine) PopBuildExecutable(ctx context.Context, id int64) (*library.BuildExecutable, error) { - e.logger.Tracef("popping build executable for build %d from the database", id) + e.logger.Tracef("popping build executable for build %d", id) // variable to store query results b := new(database.BuildExecutable) @@ -29,7 +30,6 @@ func (e *engine) PopBuildExecutable(ctx context.Context, id int64) (*library.Bui Where("build_id = ?", id). Delete(b). Error - if err != nil { return nil, err } diff --git a/database/executable/pop_test.go b/database/executable/pop_test.go index 1f6954316..6826b2feb 100644 --- a/database/executable/pop_test.go +++ b/database/executable/pop_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" ) diff --git a/database/executable/table.go b/database/executable/table.go index 82fd1002f..9a683f245 100644 --- a/database/executable/table.go +++ b/database/executable/table.go @@ -36,7 +36,7 @@ build_executables ( // CreateBuildExecutableTable creates the build executables table in the database. func (e *engine) CreateBuildExecutableTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating build_executables table in the database") + e.logger.Tracef("creating build_executables table") // handle the driver provided to create the table switch driver { diff --git a/database/flags.go b/database/flags.go index 852387b39..129143655 100644 --- a/database/flags.go +++ b/database/flags.go @@ -5,8 +5,9 @@ package database import ( "time" - "github.com/go-vela/types/constants" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/constants" ) // Flags represents all supported command line interface (CLI) flags for the database. @@ -59,6 +60,33 @@ var Flags = []cli.Flag{ Name: "database.encryption.key", Usage: "AES-256 key for encrypting and decrypting values in the database", }, + &cli.StringFlag{ + EnvVars: []string{"VELA_DATABASE_LOG_LEVEL", "DATABASE_LOG_LEVEL"}, + FilePath: "/vela/database/log_level", + Name: "database.log.level", + Usage: "set log level - options: (trace|debug|info|warn|error|fatal|panic)", + Value: "warn", + }, + &cli.BoolFlag{ + EnvVars: []string{"VELA_DATABASE_LOG_SHOW_SQL", "DATABASE_LOG_SHOW_SQL"}, + FilePath: "/vela/database/log_show_sql", + Name: "database.log.show_sql", + Usage: "show the SQL query in the logs", + }, + &cli.BoolFlag{ + EnvVars: []string{"VELA_DATABASE_LOG_SKIP_NOTFOUND", "DATABASE_LOG_SKIP_NOTFOUND"}, + FilePath: "/vela/database/log_skip_notfound", + Name: "database.log.skip_notfound", + Usage: "skip logging when a resource is not found in the database", + Value: true, + }, + &cli.DurationFlag{ + EnvVars: []string{"VELA_DATABASE_LOG_SLOW_THRESHOLD", "DATABASE_LOG_SLOW_THRESHOLD"}, + FilePath: "/vela/database/log_slow_threshold", + Name: "database.log.slow_threshold", + Usage: "queries that take longer than this threshold are considered slow and will be logged", + Value: 200 * time.Millisecond, + }, &cli.BoolFlag{ EnvVars: []string{"VELA_DATABASE_SKIP_CREATION", "DATABASE_SKIP_CREATION"}, FilePath: "/vela/database/skip_creation", diff --git a/database/hook/count.go b/database/hook/count.go index 7f6d2449b..ce02e6fa0 100644 --- a/database/hook/count.go +++ b/database/hook/count.go @@ -10,7 +10,7 @@ import ( // CountHooks gets the count of all hooks from the database. func (e *engine) CountHooks(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all hooks from the database") + e.logger.Tracef("getting count of all hooks") // variable to store query results var h int64 diff --git a/database/hook/count_repo.go b/database/hook/count_repo.go index 7b536804f..aa794b5f1 100644 --- a/database/hook/count_repo.go +++ b/database/hook/count_repo.go @@ -5,17 +5,18 @@ package hook import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountHooksForRepo gets the count of hooks by repo ID from the database. -func (e *engine) CountHooksForRepo(ctx context.Context, r *library.Repo) (int64, error) { +func (e *engine) CountHooksForRepo(ctx context.Context, r *api.Repo) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting count of hooks for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of hooks for repo %s", r.GetFullName()) // variable to store query results var h int64 diff --git a/database/hook/count_repo_test.go b/database/hook/count_repo_test.go index 87f6c97bb..e6b8c8d43 100644 --- a/database/hook/count_repo_test.go +++ b/database/hook/count_repo_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestHook_Engine_CountHooksForRepo(t *testing.T) { // setup types - _hookOne := testHook() + _hookOne := testutils.APIHook() _hookOne.SetID(1) _hookOne.SetRepoID(1) _hookOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestHook_Engine_CountHooksForRepo(t *testing.T) { _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookOne.SetWebhookID(1) - _hookTwo := testHook() + _hookTwo := testutils.APIHook() _hookTwo.SetID(2) _hookTwo.SetRepoID(2) _hookTwo.SetBuildID(2) @@ -28,9 +30,9 @@ func TestHook_Engine_CountHooksForRepo(t *testing.T) { _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookTwo.SetWebhookID(1) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/hook/count_test.go b/database/hook/count_test.go index 96cc5560e..20c022217 100644 --- a/database/hook/count_test.go +++ b/database/hook/count_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestHook_Engine_CountHooks(t *testing.T) { // setup types - _hookOne := testHook() + _hookOne := testutils.APIHook() _hookOne.SetID(1) _hookOne.SetRepoID(1) _hookOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestHook_Engine_CountHooks(t *testing.T) { _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookOne.SetWebhookID(1) - _hookTwo := testHook() + _hookTwo := testutils.APIHook() _hookTwo.SetID(2) _hookTwo.SetRepoID(1) _hookTwo.SetBuildID(2) diff --git a/database/hook/create.go b/database/hook/create.go index 3ddbd4a9b..c260eb2b5 100644 --- a/database/hook/create.go +++ b/database/hook/create.go @@ -5,17 +5,18 @@ package hook import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateHook creates a new hook in the database. func (e *engine) CreateHook(ctx context.Context, h *library.Hook) (*library.Hook, error) { e.logger.WithFields(logrus.Fields{ "hook": h.GetNumber(), - }).Tracef("creating hook %d in the database", h.GetNumber()) + }).Tracef("creating hook %d", h.GetNumber()) // cast the library type to database type // diff --git a/database/hook/create_test.go b/database/hook/create_test.go index cd5b6e62e..2dd34cb10 100644 --- a/database/hook/create_test.go +++ b/database/hook/create_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestHook_Engine_CreateHook(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) diff --git a/database/hook/delete.go b/database/hook/delete.go index 96b00b12c..0245477af 100644 --- a/database/hook/delete.go +++ b/database/hook/delete.go @@ -5,17 +5,18 @@ package hook import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeleteHook deletes an existing hook from the database. func (e *engine) DeleteHook(ctx context.Context, h *library.Hook) error { e.logger.WithFields(logrus.Fields{ "hook": h.GetNumber(), - }).Tracef("deleting hook %d in the database", h.GetNumber()) + }).Tracef("deleting hook %d", h.GetNumber()) // cast the library type to database type // diff --git a/database/hook/delete_test.go b/database/hook/delete_test.go index cbdef043e..f89e44116 100644 --- a/database/hook/delete_test.go +++ b/database/hook/delete_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestHook_Engine_DeleteHook(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) diff --git a/database/hook/get.go b/database/hook/get.go index 6119c0770..82e421dda 100644 --- a/database/hook/get.go +++ b/database/hook/get.go @@ -12,7 +12,7 @@ import ( // GetHook gets a hook by ID from the database. func (e *engine) GetHook(ctx context.Context, id int64) (*library.Hook, error) { - e.logger.Tracef("getting hook %d from the database", id) + e.logger.Tracef("getting hook %d", id) // variable to store query results h := new(database.Hook) diff --git a/database/hook/get_repo.go b/database/hook/get_repo.go index 6182d86c9..a88d593df 100644 --- a/database/hook/get_repo.go +++ b/database/hook/get_repo.go @@ -5,19 +5,21 @@ package hook import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetHookForRepo gets a hook by repo ID and number from the database. -func (e *engine) GetHookForRepo(ctx context.Context, r *library.Repo, number int) (*library.Hook, error) { +func (e *engine) GetHookForRepo(ctx context.Context, r *api.Repo, number int) (*library.Hook, error) { e.logger.WithFields(logrus.Fields{ "hook": number, "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting hook %s/%d from the database", r.GetFullName(), number) + }).Tracef("getting hook %s/%d", r.GetFullName(), number) // variable to store query results h := new(database.Hook) diff --git a/database/hook/get_repo_test.go b/database/hook/get_repo_test.go index e7c33c2d8..df2dc6d7f 100644 --- a/database/hook/get_repo_test.go +++ b/database/hook/get_repo_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_GetHookForRepo(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) @@ -21,9 +23,9 @@ func TestHook_Engine_GetHookForRepo(t *testing.T) { _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hook.SetWebhookID(1) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/hook/get_test.go b/database/hook/get_test.go index e5183fd62..a33f144c2 100644 --- a/database/hook/get_test.go +++ b/database/hook/get_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_GetHook(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) diff --git a/database/hook/get_webhook.go b/database/hook/get_webhook.go index d7108ff4c..919092fd7 100644 --- a/database/hook/get_webhook.go +++ b/database/hook/get_webhook.go @@ -12,7 +12,7 @@ import ( // GetHookByWebhookID gets a single hook with a matching webhook id in the database. func (e *engine) GetHookByWebhookID(ctx context.Context, webhookID int64) (*library.Hook, error) { - e.logger.Tracef("getting a hook with webhook id %d from the database", webhookID) + e.logger.Tracef("getting a hook with webhook id %d", webhookID) // variable to store query results h := new(database.Hook) diff --git a/database/hook/get_webhook_test.go b/database/hook/get_webhook_test.go index 57ced48f5..975c568af 100644 --- a/database/hook/get_webhook_test.go +++ b/database/hook/get_webhook_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_GetHookByWebhookID(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) diff --git a/database/hook/hook.go b/database/hook/hook.go index ff0358f23..283dad00a 100644 --- a/database/hook/hook.go +++ b/database/hook/hook.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -60,7 +60,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating hook database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of hooks table and indexes in the database") + e.logger.Warning("skipping creation of hooks table and indexes") return e, nil } diff --git a/database/hook/hook_test.go b/database/hook/hook_test.go index 4aaeb6a93..615572398 100644 --- a/database/hook/hook_test.go +++ b/database/hook/hook_test.go @@ -7,9 +7,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -163,54 +161,3 @@ func testSqlite(t *testing.T) *engine { return _engine } - -// testHook is a test helper function to create a library -// Hook type with all fields set to their zero values. -func testHook() *library.Hook { - return &library.Hook{ - ID: new(int64), - RepoID: new(int64), - BuildID: new(int64), - Number: new(int), - SourceID: new(string), - Created: new(int64), - Host: new(string), - Event: new(string), - EventAction: new(string), - Branch: new(string), - Error: new(string), - Status: new(string), - Link: new(string), - WebhookID: new(int64), - } -} - -// testRepo is a test helper function to create a library -// Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ - ID: new(int64), - UserID: new(int64), - BuildLimit: new(int64), - Timeout: new(int64), - Counter: new(int), - PipelineType: new(string), - Hash: new(string), - Org: new(string), - Name: new(string), - FullName: new(string), - Link: new(string), - Clone: new(string), - Branch: new(string), - Visibility: new(string), - PreviousName: new(string), - Private: new(bool), - Trusted: new(bool), - Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), - } -} diff --git a/database/hook/index.go b/database/hook/index.go index 2b8d4e682..b86c9ac39 100644 --- a/database/hook/index.go +++ b/database/hook/index.go @@ -17,7 +17,7 @@ ON hooks (repo_id); // CreateHookIndexes creates the indexes for the hooks table in the database. func (e *engine) CreateHookIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for hooks table in the database") + e.logger.Tracef("creating indexes for hooks table") // create the repo_id column index for the hooks table return e.client.Exec(CreateRepoIDIndex).Error diff --git a/database/hook/interface.go b/database/hook/interface.go index 6a9cec8f6..674dca20c 100644 --- a/database/hook/interface.go +++ b/database/hook/interface.go @@ -5,6 +5,7 @@ package hook import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -29,7 +30,7 @@ type HookInterface interface { // CountHooks defines a function that gets the count of all hooks. CountHooks(context.Context) (int64, error) // CountHooksForRepo defines a function that gets the count of hooks by repo ID. - CountHooksForRepo(context.Context, *library.Repo) (int64, error) + CountHooksForRepo(context.Context, *api.Repo) (int64, error) // CreateHook defines a function that creates a new hook. CreateHook(context.Context, *library.Hook) (*library.Hook, error) // DeleteHook defines a function that deletes an existing hook. @@ -39,13 +40,13 @@ type HookInterface interface { // GetHookByWebhookID defines a function that gets any hook with a matching webhook_id. GetHookByWebhookID(context.Context, int64) (*library.Hook, error) // GetHookForRepo defines a function that gets a hook by repo ID and number. - GetHookForRepo(context.Context, *library.Repo, int) (*library.Hook, error) + GetHookForRepo(context.Context, *api.Repo, int) (*library.Hook, error) // LastHookForRepo defines a function that gets the last hook by repo ID. - LastHookForRepo(context.Context, *library.Repo) (*library.Hook, error) + LastHookForRepo(context.Context, *api.Repo) (*library.Hook, error) // ListHooks defines a function that gets a list of all hooks. ListHooks(context.Context) ([]*library.Hook, error) // ListHooksForRepo defines a function that gets a list of hooks by repo ID. - ListHooksForRepo(context.Context, *library.Repo, int, int) ([]*library.Hook, int64, error) + ListHooksForRepo(context.Context, *api.Repo, int, int) ([]*library.Hook, int64, error) // UpdateHook defines a function that updates an existing hook. UpdateHook(context.Context, *library.Hook) (*library.Hook, error) } diff --git a/database/hook/last_repo.go b/database/hook/last_repo.go index bf85be4dc..710053ad8 100644 --- a/database/hook/last_repo.go +++ b/database/hook/last_repo.go @@ -6,20 +6,21 @@ import ( "context" "errors" + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" - - "gorm.io/gorm" ) // LastHookForRepo gets the last hook by repo ID from the database. -func (e *engine) LastHookForRepo(ctx context.Context, r *library.Repo) (*library.Hook, error) { +func (e *engine) LastHookForRepo(ctx context.Context, r *api.Repo) (*library.Hook, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting last hook for repo %s from the database", r.GetFullName()) + }).Tracef("getting last hook for repo %s", r.GetFullName()) // variable to store query results h := new(database.Hook) diff --git a/database/hook/last_repo_test.go b/database/hook/last_repo_test.go index b7911f91d..277932f2d 100644 --- a/database/hook/last_repo_test.go +++ b/database/hook/last_repo_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_LastHookForRepo(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) @@ -21,9 +23,9 @@ func TestHook_Engine_LastHookForRepo(t *testing.T) { _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hook.SetWebhookID(1) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/hook/list.go b/database/hook/list.go index 8e0949be4..fcfba1e28 100644 --- a/database/hook/list.go +++ b/database/hook/list.go @@ -12,7 +12,7 @@ import ( // ListHooks gets a list of all hooks from the database. func (e *engine) ListHooks(ctx context.Context) ([]*library.Hook, error) { - e.logger.Trace("listing all hooks from the database") + e.logger.Trace("listing all hooks") // variables to store query results and return value count := int64(0) diff --git a/database/hook/list_repo.go b/database/hook/list_repo.go index 28220b1dd..28e80c03e 100644 --- a/database/hook/list_repo.go +++ b/database/hook/list_repo.go @@ -5,18 +5,20 @@ package hook import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListHooksForRepo gets a list of hooks by repo ID from the database. -func (e *engine) ListHooksForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Hook, int64, error) { +func (e *engine) ListHooksForRepo(ctx context.Context, r *api.Repo, page, perPage int) ([]*library.Hook, int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("listing hooks for repo %s from the database", r.GetFullName()) + }).Tracef("listing hooks for repo %s", r.GetFullName()) // variables to store query results and return value count := int64(0) diff --git a/database/hook/list_repo_test.go b/database/hook/list_repo_test.go index 38e72a93f..b68a41747 100644 --- a/database/hook/list_repo_test.go +++ b/database/hook/list_repo_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_ListHooksForRepo(t *testing.T) { // setup types - _hookOne := testHook() + _hookOne := testutils.APIHook() _hookOne.SetID(1) _hookOne.SetRepoID(1) _hookOne.SetBuildID(1) @@ -21,7 +23,7 @@ func TestHook_Engine_ListHooksForRepo(t *testing.T) { _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookOne.SetWebhookID(1) - _hookTwo := testHook() + _hookTwo := testutils.APIHook() _hookTwo.SetID(2) _hookTwo.SetRepoID(1) _hookTwo.SetBuildID(2) @@ -29,9 +31,9 @@ func TestHook_Engine_ListHooksForRepo(t *testing.T) { _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookTwo.SetWebhookID(1) - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetOrg("foo") _repo.SetName("bar") _repo.SetFullName("foo/bar") diff --git a/database/hook/list_test.go b/database/hook/list_test.go index 299585167..89098751d 100644 --- a/database/hook/list_test.go +++ b/database/hook/list_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestHook_Engine_ListHooks(t *testing.T) { // setup types - _hookOne := testHook() + _hookOne := testutils.APIHook() _hookOne.SetID(1) _hookOne.SetRepoID(1) _hookOne.SetBuildID(1) @@ -21,7 +23,7 @@ func TestHook_Engine_ListHooks(t *testing.T) { _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8") _hookOne.SetWebhookID(1) - _hookTwo := testHook() + _hookTwo := testutils.APIHook() _hookTwo.SetID(2) _hookTwo.SetRepoID(1) _hookTwo.SetBuildID(2) diff --git a/database/hook/opts.go b/database/hook/opts.go index e91c5ec6b..36b88b499 100644 --- a/database/hook/opts.go +++ b/database/hook/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/hook/opts_test.go b/database/hook/opts_test.go index cb3c37760..834717e40 100644 --- a/database/hook/opts_test.go +++ b/database/hook/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/hook/table.go b/database/hook/table.go index c9221e9f4..81ef7157f 100644 --- a/database/hook/table.go +++ b/database/hook/table.go @@ -58,7 +58,7 @@ hooks ( // CreateHookTable creates the hooks table in the database. func (e *engine) CreateHookTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating hooks table in the database") + e.logger.Tracef("creating hooks table") // handle the driver provided to create the table switch driver { diff --git a/database/hook/update.go b/database/hook/update.go index e990bbc9c..16222a6d1 100644 --- a/database/hook/update.go +++ b/database/hook/update.go @@ -5,17 +5,18 @@ package hook import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdateHook updates an existing hook in the database. func (e *engine) UpdateHook(ctx context.Context, h *library.Hook) (*library.Hook, error) { e.logger.WithFields(logrus.Fields{ "hook": h.GetNumber(), - }).Tracef("updating hook %d in the database", h.GetNumber()) + }).Tracef("updating hook %d", h.GetNumber()) // cast the library type to database type // diff --git a/database/hook/update_test.go b/database/hook/update_test.go index 44828b5db..72982b6c0 100644 --- a/database/hook/update_test.go +++ b/database/hook/update_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestHook_Engine_UpdateHook(t *testing.T) { // setup types - _hook := testHook() + _hook := testutils.APIHook() _hook.SetID(1) _hook.SetRepoID(1) _hook.SetBuildID(1) diff --git a/database/integration_test.go b/database/integration_test.go index 16ca38062..ad3748c4f 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -10,40 +10,52 @@ import ( "testing" "time" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/v2/jwk" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/settings" "github.com/go-vela/server/database/build" + "github.com/go-vela/server/database/dashboard" "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + dbJWK "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/schedule" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/service" + dbSettings "github.com/go-vela/server/database/settings" "github.com/go-vela/server/database/step" + "github.com/go-vela/server/database/testutils" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" - "github.com/google/go-cmp/cmp" ) // Resources represents the object containing test resources. type Resources struct { - Builds []*library.Build + Builds []*api.Build + Dashboards []*api.Dashboard Deployments []*library.Deployment Executables []*library.BuildExecutable Hooks []*library.Hook + JWKs jwk.Set Logs []*library.Log Pipelines []*library.Pipeline - Repos []*library.Repo - Schedules []*library.Schedule + Repos []*api.Repo + Schedules []*api.Schedule Secrets []*library.Secret Services []*library.Service Steps []*library.Step - Users []*library.User - Workers []*library.Worker + Users []*api.User + Workers []*api.Worker + Platform []*settings.Platform } func TestDatabase_Integration(t *testing.T) { @@ -119,12 +131,16 @@ func TestDatabase_Integration(t *testing.T) { t.Run("test_builds", func(t *testing.T) { testBuilds(t, db, resources) }) + t.Run("test_dashboards", func(t *testing.T) { testDashboards(t, db, resources) }) + t.Run("test_deployments", func(t *testing.T) { testDeployments(t, db, resources) }) t.Run("test_executables", func(t *testing.T) { testExecutables(t, db, resources) }) t.Run("test_hooks", func(t *testing.T) { testHooks(t, db, resources) }) + t.Run("test_jwks", func(t *testing.T) { testJWKs(t, db, resources) }) + t.Run("test_logs", func(t *testing.T) { testLogs(t, db, resources) }) t.Run("test_pipelines", func(t *testing.T) { testPipelines(t, db, resources) }) @@ -143,6 +159,8 @@ func TestDatabase_Integration(t *testing.T) { t.Run("test_workers", func(t *testing.T) { testWorkers(t, db, resources) }) + t.Run("test_settings", func(t *testing.T) { testSettings(t, db, resources) }) + err = db.Close() if err != nil { t.Errorf("unable to close database engine for %s: %v", test.name, err) @@ -169,6 +187,14 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { methods[element.Method(i).Name] = false } + // create the users for build related functions (owners of repos) + for _, user := range resources.Users { + _, err := db.CreateUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to create user %d: %v", user.GetID(), err) + } + } + // create the repos for build related functions for _, repo := range resources.Repos { _, err := db.CreateRepo(context.TODO(), repo) @@ -177,19 +203,19 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { } } - buildOne := new(library.BuildQueue) + buildOne := new(api.QueueBuild) buildOne.SetCreated(1563474076) buildOne.SetFullName("github/octocat") buildOne.SetNumber(1) buildOne.SetStatus("running") - buildTwo := new(library.BuildQueue) + buildTwo := new(api.QueueBuild) buildTwo.SetCreated(1563474076) buildTwo.SetFullName("github/octocat") buildTwo.SetNumber(2) buildTwo.SetStatus("running") - queueBuilds := []*library.BuildQueue{buildOne, buildTwo} + queueBuilds := []*api.QueueBuild{buildOne, buildTwo} // create the builds for _, build := range resources.Builds { @@ -281,11 +307,29 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { if int(count) != len(resources.Builds) { t.Errorf("ListBuildsForRepo() is %v, want %v", count, len(resources.Builds)) } - if diff := cmp.Diff([]*library.Build{resources.Builds[1], resources.Builds[0]}, list); diff != "" { + if diff := cmp.Diff([]*api.Build{resources.Builds[1], resources.Builds[0]}, list); diff != "" { t.Errorf("ListBuildsForRepo() mismatch (-want +got):\n%s", diff) } methods["ListBuildsForRepo"] = true + list, err = db.ListBuildsForDashboardRepo(context.TODO(), resources.Repos[0], []string{"main"}, []string{"push"}) + if err != nil { + t.Errorf("unable to list build for dashboard repo %d: %v", resources.Repos[0].GetID(), err) + } + if len(list) != 1 { + t.Errorf("Number of results for ListBuildsForDashboardRepo() is %v, want %v", len(list), 1) + } + + // ListBuildsForDashboardRepo does not contain nested repo + wantBuild := *resources.Builds[0] + wantBuild.Repo = testutils.APIRepo() + wantBuild.Repo.Owner = testutils.APIUser().Crop() + + if diff := cmp.Diff([]*api.Build{&wantBuild}, list); diff != "" { + t.Errorf("ListBuildsForDashboardRepo() mismatch (-want +got):\n%s", diff) + } + methods["ListBuildsForDashboardRepo"] = true + // list the pending / running builds for a repo list, err = db.ListPendingAndRunningBuildsForRepo(context.TODO(), resources.Repos[0]) if err != nil { @@ -294,7 +338,7 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { if int(count) != len(resources.Builds) { t.Errorf("ListPendingAndRunningBuildsForRepo() is %v, want %v", count, len(resources.Builds)) } - if diff := cmp.Diff([]*library.Build{resources.Builds[0], resources.Builds[1]}, list); diff != "" { + if diff := cmp.Diff([]*api.Build{resources.Builds[0], resources.Builds[1]}, list); diff != "" { t.Errorf("ListPendingAndRunningBuildsForRepo() mismatch (-want +got):\n%s", diff) } methods["ListPendingAndRunningBuildsForRepo"] = true @@ -321,7 +365,7 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { // lookup the builds by repo and number for _, build := range resources.Builds { - repo := resources.Repos[build.GetRepoID()-1] + repo := resources.Repos[build.GetRepo().GetID()-1] got, err = db.GetBuildForRepo(context.TODO(), repo, build.GetNumber()) if err != nil { t.Errorf("unable to get build %d for repo %d: %v", build.GetID(), repo.GetID(), err) @@ -379,6 +423,14 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { } } + // delete the users for the build related functions + for _, user := range resources.Users { + err = db.DeleteUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to delete user %d: %v", user.GetID(), err) + } + } + // ensure we called all the methods we expected to for method, called := range methods { if !called { @@ -387,6 +439,92 @@ func testBuilds(t *testing.T, db Interface, resources *Resources) { } } +func testDashboards(t *testing.T, db Interface, resources *Resources) { + // create a variable to track the number of methods called for schedules + methods := make(map[string]bool) + // capture the element type of the schedule interface + element := reflect.TypeOf(new(dashboard.DashboardInterface)).Elem() + // iterate through all methods found in the schedule interface + for i := 0; i < element.NumMethod(); i++ { + // skip tracking the methods to create indexes and tables for schedules + // since those are already called when the database engine starts + if strings.Contains(element.Method(i).Name, "Index") || + strings.Contains(element.Method(i).Name, "Table") { + continue + } + + // add the method name to the list of functions + methods[element.Method(i).Name] = false + } + + ctx := context.TODO() + + // create the dashboard + for _, dashboard := range resources.Dashboards { + _, err := db.CreateDashboard(ctx, dashboard) + if err != nil { + t.Errorf("unable to create dashboard %s: %v", dashboard.GetID(), err) + } + } + methods["CreateDashboard"] = true + + // lookup the dashboards by ID + for _, dashboard := range resources.Dashboards { + got, err := db.GetDashboard(ctx, dashboard.GetID()) + if err != nil { + t.Errorf("unable to get dashboard %s: %v", dashboard.GetID(), err) + } + + // JSON tags of `-` prevent unmarshaling of tokens, but they are sanitized anyway + cmpAdmins := []*api.User{} + for _, admin := range got.GetAdmins() { + cmpAdmins = append(cmpAdmins, admin.Crop()) + } + got.SetAdmins(cmpAdmins) + + if !cmp.Equal(got, dashboard, CmpOptApproxUpdatedAt()) { + t.Errorf("GetDashboard() is %v, want %v", got, dashboard) + } + } + methods["GetDashboard"] = true + + // update the dashboards + for _, dashboard := range resources.Dashboards { + dashboard.SetUpdatedAt(time.Now().UTC().Unix()) + got, err := db.UpdateDashboard(ctx, dashboard) + if err != nil { + t.Errorf("unable to update dashboard %s: %v", dashboard.GetID(), err) + } + + // JSON marshaling does not include comparing token due to `-` struct tag + cmpAdmins := got.GetAdmins() + for i, admin := range cmpAdmins { + admin.SetToken(resources.Users[i].GetToken()) + } + + if diff := cmp.Diff(dashboard, got, CmpOptApproxUpdatedAt()); diff != "" { + t.Errorf("UpdateDashboard() mismatch (-want +got):\n%s", diff) + } + } + methods["UpdateDashboard"] = true + + // delete the schedules + for _, dashboard := range resources.Dashboards { + err := db.DeleteDashboard(ctx, dashboard) + if err != nil { + t.Errorf("unable to delete dashboard %s: %v", dashboard.GetID(), err) + } + } + methods["DeleteDashboard"] = true + + // ensure we called all the methods we expected to + for method, called := range methods { + if !called { + t.Errorf("method %s was not called for dashboards", method) + } + } +} + func testExecutables(t *testing.T, db Interface, resources *Resources) { // create a variable to track the number of methods called for pipelines methods := make(map[string]bool) @@ -722,6 +860,89 @@ func testHooks(t *testing.T, db Interface, resources *Resources) { } } +func testJWKs(t *testing.T, db Interface, resources *Resources) { + // create a variable to track the number of methods called for jwks + methods := make(map[string]bool) + // capture the element type of the jwk interface + element := reflect.TypeOf(new(dbJWK.JWKInterface)).Elem() + // iterate through all methods found in the jwk interface + for i := 0; i < element.NumMethod(); i++ { + // skip tracking the methods to create indexes and tables for jwks + // since those are already called when the database engine starts + if strings.Contains(element.Method(i).Name, "Table") { + continue + } + + // add the method name to the list of functions + methods[element.Method(i).Name] = false + } + + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Key(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + err := db.CreateJWK(context.TODO(), jkPub) + if err != nil { + t.Errorf("unable to create jwk %s: %v", jkPub.KeyID(), err) + } + } + methods["CreateJWK"] = true + + list, err := db.ListJWKs(context.TODO()) + if err != nil { + t.Errorf("unable to list jwks: %v", err) + } + + if !reflect.DeepEqual(resources.JWKs, list) { + t.Errorf("ListJWKs() mismatch, want %v, got %v", resources.JWKs, list) + } + + methods["ListJWKs"] = true + + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Key(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + got, err := db.GetActiveJWK(context.TODO(), jkPub.KeyID()) + if err != nil { + t.Errorf("unable to get jwk %s: %v", jkPub.KeyID(), err) + } + + if !cmp.Equal(jkPub, got, testutils.JwkKeyOpts) { + t.Errorf("GetJWK() is %v, want %v", got, jkPub) + } + } + + methods["GetActiveJWK"] = true + + err = db.RotateKeys(context.TODO()) + if err != nil { + t.Errorf("unable to rotate keys: %v", err) + } + + for i := 0; i < resources.JWKs.Len(); i++ { + jk, _ := resources.JWKs.Key(i) + + jkPub, _ := jk.(jwk.RSAPublicKey) + + _, err := db.GetActiveJWK(context.TODO(), jkPub.KeyID()) + if err == nil { + t.Errorf("GetActiveJWK() should return err after rotation") + } + } + + methods["RotateKeys"] = true + + // ensure we called all the methods we expected to + for method, called := range methods { + if !called { + t.Errorf("method %s was not called for jwks", method) + } + } +} + func testLogs(t *testing.T, db Interface, resources *Resources) { // create a variable to track the number of methods called for logs methods := make(map[string]bool) @@ -993,6 +1214,14 @@ func testRepos(t *testing.T, db Interface, resources *Resources) { methods[element.Method(i).Name] = false } + // create owners + for _, user := range resources.Users { + _, err := db.CreateUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to create user %d: %v", user.GetID(), err) + } + } + // create the repos for _, repo := range resources.Repos { _, err := db.CreateRepo(context.TODO(), repo) @@ -1109,6 +1338,14 @@ func testRepos(t *testing.T, db Interface, resources *Resources) { } methods["DeleteRepo"] = true + // delete the owners + for _, user := range resources.Users { + err := db.DeleteUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to delete user %d: %v", user.GetID(), err) + } + } + // ensure we called all the methods we expected to for method, called := range methods { if !called { @@ -1135,11 +1372,25 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods[element.Method(i).Name] = false } - ctx := context.TODO() + // create owners + for _, user := range resources.Users { + _, err := db.CreateUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to create user %d: %v", user.GetID(), err) + } + } + + // create the repos + for _, repo := range resources.Repos { + _, err := db.CreateRepo(context.TODO(), repo) + if err != nil { + t.Errorf("unable to create repo %d: %v", repo.GetID(), err) + } + } // create the schedules for _, schedule := range resources.Schedules { - _, err := db.CreateSchedule(ctx, schedule) + _, err := db.CreateSchedule(context.TODO(), schedule) if err != nil { t.Errorf("unable to create schedule %d: %v", schedule.GetID(), err) } @@ -1147,7 +1398,7 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods["CreateSchedule"] = true // count the schedules - count, err := db.CountSchedules(ctx) + count, err := db.CountSchedules(context.TODO()) if err != nil { t.Errorf("unable to count schedules: %v", err) } @@ -1157,7 +1408,7 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods["CountSchedules"] = true // count the schedules for a repo - count, err = db.CountSchedulesForRepo(ctx, resources.Repos[0]) + count, err = db.CountSchedulesForRepo(context.TODO(), resources.Repos[0]) if err != nil { t.Errorf("unable to count schedules for repo %d: %v", resources.Repos[0].GetID(), err) } @@ -1167,7 +1418,7 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods["CountSchedulesForRepo"] = true // list the schedules - list, err := db.ListSchedules(ctx) + list, err := db.ListSchedules(context.TODO()) if err != nil { t.Errorf("unable to list schedules: %v", err) } @@ -1177,7 +1428,7 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods["ListSchedules"] = true // list the active schedules - list, err = db.ListActiveSchedules(ctx) + list, err = db.ListActiveSchedules(context.TODO()) if err != nil { t.Errorf("unable to list schedules: %v", err) } @@ -1187,22 +1438,22 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { methods["ListActiveSchedules"] = true // list the schedules for a repo - list, count, err = db.ListSchedulesForRepo(ctx, resources.Repos[0], 1, 10) + list, count, err = db.ListSchedulesForRepo(context.TODO(), resources.Repos[0], 1, 10) if err != nil { t.Errorf("unable to count schedules for repo %d: %v", resources.Repos[0].GetID(), err) } if int(count) != len(resources.Schedules) { t.Errorf("ListSchedulesForRepo() is %v, want %v", count, len(resources.Schedules)) } - if !cmp.Equal(list, []*library.Schedule{resources.Schedules[1], resources.Schedules[0]}, CmpOptApproxUpdatedAt()) { - t.Errorf("ListSchedulesForRepo() is %v, want %v", list, []*library.Schedule{resources.Schedules[1], resources.Schedules[0]}) + if !cmp.Equal(list, []*api.Schedule{resources.Schedules[1], resources.Schedules[0]}, CmpOptApproxUpdatedAt()) { + t.Errorf("ListSchedulesForRepo() is %v, want %v", list, []*api.Schedule{resources.Schedules[1], resources.Schedules[0]}) } methods["ListSchedulesForRepo"] = true // lookup the schedules by name for _, schedule := range resources.Schedules { - repo := resources.Repos[schedule.GetRepoID()-1] - got, err := db.GetScheduleForRepo(ctx, repo, schedule.GetName()) + repo := resources.Repos[schedule.GetRepo().GetID()-1] + got, err := db.GetScheduleForRepo(context.TODO(), repo, schedule.GetName()) if err != nil { t.Errorf("unable to get schedule %d for repo %d: %v", schedule.GetID(), repo.GetID(), err) } @@ -1215,7 +1466,7 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { // update the schedules for _, schedule := range resources.Schedules { schedule.SetUpdatedAt(time.Now().UTC().Unix()) - got, err := db.UpdateSchedule(ctx, schedule, true) + got, err := db.UpdateSchedule(context.TODO(), schedule, true) if err != nil { t.Errorf("unable to update schedule %d: %v", schedule.GetID(), err) } @@ -1229,13 +1480,29 @@ func testSchedules(t *testing.T, db Interface, resources *Resources) { // delete the schedules for _, schedule := range resources.Schedules { - err = db.DeleteSchedule(ctx, schedule) + err = db.DeleteSchedule(context.TODO(), schedule) if err != nil { t.Errorf("unable to delete schedule %d: %v", schedule.GetID(), err) } } methods["DeleteSchedule"] = true + // delete the repos + for _, repo := range resources.Repos { + err = db.DeleteRepo(context.TODO(), repo) + if err != nil { + t.Errorf("unable to delete repo %d: %v", repo.GetID(), err) + } + } + + // delete the owners + for _, user := range resources.Users { + err := db.DeleteUser(context.TODO(), user) + if err != nil { + t.Errorf("unable to delete user %d: %v", user.GetID(), err) + } + } + // ensure we called all the methods we expected to for method, called := range methods { if !called { @@ -1793,27 +2060,27 @@ func testUsers(t *testing.T, db Interface, resources *Resources) { methods[element.Method(i).Name] = false } - userOne := new(library.User) + userOne := new(api.User) userOne.SetID(1) userOne.SetName("octocat") userOne.SetToken("") userOne.SetRefreshToken("") - userOne.SetHash("") userOne.SetFavorites(nil) + userOne.SetDashboards(nil) userOne.SetActive(false) userOne.SetAdmin(false) - userTwo := new(library.User) + userTwo := new(api.User) userTwo.SetID(2) userTwo.SetName("octokitty") userTwo.SetToken("") userTwo.SetRefreshToken("") - userTwo.SetHash("") userTwo.SetFavorites(nil) + userTwo.SetDashboards(nil) userTwo.SetActive(false) userTwo.SetAdmin(false) - liteUsers := []*library.User{userOne, userTwo} + liteUsers := []*api.User{userOne, userTwo} // create the users for _, user := range resources.Users { @@ -1992,10 +2259,126 @@ func testWorkers(t *testing.T, db Interface, resources *Resources) { } } +func testSettings(t *testing.T, db Interface, resources *Resources) { + // create a variable to track the number of methods called for settings + methods := make(map[string]bool) + // capture the element type of the settings interface + element := reflect.TypeOf(new(dbSettings.SettingsInterface)).Elem() + // iterate through all methods found in the settings interface + for i := 0; i < element.NumMethod(); i++ { + // skip tracking the methods to create indexes and tables for settings + // since those are already called when the database engine starts + if strings.Contains(element.Method(i).Name, "Index") || + strings.Contains(element.Method(i).Name, "Table") { + continue + } + + // add the method name to the list of functions + methods[element.Method(i).Name] = false + } + + // create the settings + for _, s := range resources.Platform { + _, err := db.CreateSettings(context.TODO(), s) + if err != nil { + t.Errorf("unable to create settings %d: %v", s.GetID(), err) + } + } + methods["CreateSettings"] = true + + // update the settings + for _, s := range resources.Platform { + s.SetCloneImage("target/vela-git:abc123") + got, err := db.UpdateSettings(context.TODO(), s) + if err != nil { + t.Errorf("unable to update settings %d: %v", s.GetID(), err) + } + + if !cmp.Equal(got, s) { + t.Errorf("UpdateSettings() is %v, want %v", got, s) + } + } + methods["UpdateSettings"] = true + methods["GetSettings"] = true + + // ensure we called all the methods we expected to + for method, called := range methods { + if !called { + t.Errorf("method %s was not called for settings", method) + } + } +} + func newResources() *Resources { - buildOne := new(library.Build) + userOne := new(api.User) + userOne.SetID(1) + userOne.SetName("octocat") + userOne.SetToken("superSecretToken") + userOne.SetRefreshToken("superSecretRefreshToken") + userOne.SetFavorites([]string{"github/octocat"}) + userOne.SetActive(true) + userOne.SetAdmin(false) + userOne.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + userTwo := new(api.User) + userTwo.SetID(2) + userTwo.SetName("octokitty") + userTwo.SetToken("superSecretToken") + userTwo.SetRefreshToken("superSecretRefreshToken") + userTwo.SetFavorites([]string{"github/octocat"}) + userTwo.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + userTwo.SetActive(true) + userTwo.SetAdmin(false) + + repoOne := new(api.Repo) + repoOne.SetID(1) + repoOne.SetOwner(userOne.Crop()) + repoOne.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + repoOne.SetOrg("github") + repoOne.SetName("octocat") + repoOne.SetFullName("github/octocat") + repoOne.SetLink("https://github.com/github/octocat") + repoOne.SetClone("https://github.com/github/octocat.git") + repoOne.SetBranch("main") + repoOne.SetTopics([]string{"cloud", "security"}) + repoOne.SetBuildLimit(10) + repoOne.SetTimeout(30) + repoOne.SetCounter(0) + repoOne.SetVisibility("public") + repoOne.SetPrivate(false) + repoOne.SetTrusted(false) + repoOne.SetActive(true) + repoOne.SetPipelineType("") + repoOne.SetPreviousName("") + repoOne.SetApproveBuild(constants.ApproveNever) + repoOne.SetAllowEvents(api.NewEventsFromMask(1)) + + repoTwo := new(api.Repo) + repoTwo.SetID(2) + repoTwo.SetOwner(userOne.Crop()) + repoTwo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + repoTwo.SetOrg("github") + repoTwo.SetName("octokitty") + repoTwo.SetFullName("github/octokitty") + repoTwo.SetLink("https://github.com/github/octokitty") + repoTwo.SetClone("https://github.com/github/octokitty.git") + repoTwo.SetBranch("main") + repoTwo.SetTopics([]string{"cloud", "security"}) + repoTwo.SetBuildLimit(10) + repoTwo.SetTimeout(30) + repoTwo.SetCounter(0) + repoTwo.SetVisibility("public") + repoTwo.SetPrivate(false) + repoTwo.SetTrusted(false) + repoTwo.SetActive(true) + repoTwo.SetPipelineType("") + repoTwo.SetPreviousName("") + repoTwo.SetApproveBuild(constants.ApproveForkAlways) + repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) + + buildOne := new(api.Build) buildOne.SetID(1) - buildOne.SetRepoID(1) + buildOne.SetRepo(repoOne) buildOne.SetPipelineID(1) buildOne.SetNumber(1) buildOne.SetParent(1) @@ -2016,6 +2399,7 @@ func newResources() *Resources { buildOne.SetMessage("First commit...") buildOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") buildOne.SetSender("OctoKitty") + buildOne.SetSenderSCMID("123") buildOne.SetAuthor("OctoKitty") buildOne.SetEmail("OctoKitty@github.com") buildOne.SetLink("https://example.company.com/github/octocat/1") @@ -2029,9 +2413,9 @@ func newResources() *Resources { buildOne.SetApprovedAt(1563474078) buildOne.SetApprovedBy("OctoCat") - buildTwo := new(library.Build) + buildTwo := new(api.Build) buildTwo.SetID(2) - buildTwo.SetRepoID(1) + buildTwo.SetRepo(repoOne) buildTwo.SetPipelineID(1) buildTwo.SetNumber(2) buildTwo.SetParent(1) @@ -2052,6 +2436,7 @@ func newResources() *Resources { buildTwo.SetMessage("Second commit...") buildTwo.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135164") buildTwo.SetSender("OctoKitty") + buildTwo.SetSenderSCMID("123") buildTwo.SetAuthor("OctoKitty") buildTwo.SetEmail("OctoKitty@github.com") buildTwo.SetLink("https://example.company.com/github/octocat/2") @@ -2065,6 +2450,39 @@ func newResources() *Resources { buildTwo.SetApprovedAt(1563474078) buildTwo.SetApprovedBy("OctoCat") + dashRepo := new(api.DashboardRepo) + dashRepo.SetID(1) + dashRepo.SetName("go-vela/server") + dashRepo.SetBranches([]string{"main"}) + dashRepo.SetEvents([]string{"push"}) + + // crop and set "-" JSON tag fields to nil for dashboard admins + dashboardAdmins := []*api.User{userOne.Crop(), userTwo.Crop()} + for _, admin := range dashboardAdmins { + admin.Token = nil + admin.RefreshToken = nil + } + + dashboardOne := new(api.Dashboard) + dashboardOne.SetID("ba657dab-bc6e-421f-9188-86272bd0069a") + dashboardOne.SetName("vela") + dashboardOne.SetCreatedAt(1) + dashboardOne.SetCreatedBy("octocat") + dashboardOne.SetUpdatedAt(2) + dashboardOne.SetUpdatedBy("octokitty") + dashboardOne.SetAdmins(dashboardAdmins) + dashboardOne.SetRepos([]*api.DashboardRepo{dashRepo}) + + dashboardTwo := new(api.Dashboard) + dashboardTwo.SetID("45bcf19b-c151-4e2d-b8c6-80a62ba2eae7") + dashboardTwo.SetName("vela") + dashboardTwo.SetCreatedAt(1) + dashboardTwo.SetCreatedBy("octocat") + dashboardTwo.SetUpdatedAt(2) + dashboardTwo.SetUpdatedBy("octokitty") + dashboardTwo.SetAdmins(dashboardAdmins) + dashboardTwo.SetRepos([]*api.DashboardRepo{dashRepo}) + executableOne := new(library.BuildExecutable) executableOne.SetID(1) executableOne.SetBuildID(1) @@ -2154,6 +2572,15 @@ func newResources() *Resources { hookThree.SetLink("https://github.com/github/octocat/settings/hooks/1") hookThree.SetWebhookID(78910) + jwkOne := testutils.JWK() + jwkTwo := testutils.JWK() + + jwkSet := jwk.NewSet() + + _ = jwkSet.AddKey(jwkOne) + + _ = jwkSet.AddKey(jwkTwo) + logServiceOne := new(library.Log) logServiceOne.SetID(1) logServiceOne.SetBuildID(1) @@ -2220,65 +2647,12 @@ func newResources() *Resources { pipelineTwo.SetTemplates(false) pipelineTwo.SetData([]byte("version: 1")) - repoOne := new(library.Repo) - repoOne.SetID(1) - repoOne.SetUserID(1) - repoOne.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") - repoOne.SetOrg("github") - repoOne.SetName("octocat") - repoOne.SetFullName("github/octocat") - repoOne.SetLink("https://github.com/github/octocat") - repoOne.SetClone("https://github.com/github/octocat.git") - repoOne.SetBranch("main") - repoOne.SetTopics([]string{"cloud", "security"}) - repoOne.SetBuildLimit(10) - repoOne.SetTimeout(30) - repoOne.SetCounter(0) - repoOne.SetVisibility("public") - repoOne.SetPrivate(false) - repoOne.SetTrusted(false) - repoOne.SetActive(true) - repoOne.SetAllowPull(false) - repoOne.SetAllowPush(true) - repoOne.SetAllowDeploy(false) - repoOne.SetAllowTag(false) - repoOne.SetAllowComment(false) - repoOne.SetPipelineType("") - repoOne.SetPreviousName("") - repoOne.SetApproveBuild(constants.ApproveNever) - repoOne.SetAllowEvents(library.NewEventsFromMask(1)) - - repoTwo := new(library.Repo) - repoTwo.SetID(2) - repoTwo.SetUserID(1) - repoTwo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") - repoTwo.SetOrg("github") - repoTwo.SetName("octokitty") - repoTwo.SetFullName("github/octokitty") - repoTwo.SetLink("https://github.com/github/octokitty") - repoTwo.SetClone("https://github.com/github/octokitty.git") - repoTwo.SetBranch("main") - repoTwo.SetTopics([]string{"cloud", "security"}) - repoTwo.SetBuildLimit(10) - repoTwo.SetTimeout(30) - repoTwo.SetCounter(0) - repoTwo.SetVisibility("public") - repoTwo.SetPrivate(false) - repoTwo.SetTrusted(false) - repoTwo.SetActive(true) - repoTwo.SetAllowPull(false) - repoTwo.SetAllowPush(true) - repoTwo.SetAllowDeploy(false) - repoTwo.SetAllowTag(false) - repoTwo.SetAllowComment(false) - repoTwo.SetPipelineType("") - repoTwo.SetPreviousName("") - repoTwo.SetApproveBuild(constants.ApproveForkAlways) - repoTwo.SetAllowEvents(library.NewEventsFromMask(1)) + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) - scheduleOne := new(library.Schedule) + scheduleOne := new(api.Schedule) scheduleOne.SetID(1) - scheduleOne.SetRepoID(1) + scheduleOne.SetRepo(repoOne) scheduleOne.SetActive(true) scheduleOne.SetName("nightly") scheduleOne.SetEntry("0 0 * * *") @@ -2288,10 +2662,15 @@ func newResources() *Resources { scheduleOne.SetUpdatedBy("octokitty") scheduleOne.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix()) scheduleOne.SetBranch("main") + scheduleOne.SetError("no version: YAML property provided") + scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - scheduleTwo := new(library.Schedule) + scheduleTwo := new(api.Schedule) scheduleTwo.SetID(2) - scheduleTwo.SetRepoID(1) + scheduleTwo.SetRepo(repoOne) scheduleTwo.SetActive(true) scheduleTwo.SetName("hourly") scheduleTwo.SetEntry("0 * * * *") @@ -2301,6 +2680,8 @@ func newResources() *Resources { scheduleTwo.SetUpdatedBy("octokitty") scheduleTwo.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix()) scheduleTwo.SetBranch("main") + scheduleTwo.SetError("no version: YAML property provided") + scheduleTwo.SetNextRun(nextTime.Unix()) secretOrg := new(library.Secret) secretOrg.SetID(1) @@ -2311,9 +2692,9 @@ func newResources() *Resources { secretOrg.SetValue("bar") secretOrg.SetType("org") secretOrg.SetImages([]string{"alpine"}) - secretOrg.SetEvents([]string{"push", "tag", "deployment"}) secretOrg.SetAllowEvents(library.NewEventsFromMask(1)) secretOrg.SetAllowCommand(true) + secretOrg.SetAllowSubstitution(true) secretOrg.SetCreatedAt(time.Now().UTC().Unix()) secretOrg.SetCreatedBy("octocat") secretOrg.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix()) @@ -2328,9 +2709,9 @@ func newResources() *Resources { secretRepo.SetValue("bar") secretRepo.SetType("repo") secretRepo.SetImages([]string{"alpine"}) - secretRepo.SetEvents([]string{"push", "tag", "deployment"}) secretRepo.SetAllowEvents(library.NewEventsFromMask(1)) secretRepo.SetAllowCommand(true) + secretRepo.SetAllowSubstitution(true) secretRepo.SetCreatedAt(time.Now().UTC().Unix()) secretRepo.SetCreatedBy("octocat") secretRepo.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix()) @@ -2345,8 +2726,8 @@ func newResources() *Resources { secretShared.SetValue("bar") secretShared.SetType("shared") secretShared.SetImages([]string{"alpine"}) - secretShared.SetEvents([]string{"push", "tag", "deployment"}) secretShared.SetAllowCommand(true) + secretShared.SetAllowSubstitution(true) secretShared.SetAllowEvents(library.NewEventsFromMask(1)) secretShared.SetCreatedAt(time.Now().UTC().Unix()) secretShared.SetCreatedBy("octocat") @@ -2404,6 +2785,7 @@ func newResources() *Resources { stepOne.SetHost("example.company.com") stepOne.SetRuntime("docker") stepOne.SetDistribution("linux") + stepOne.SetReportAs("") stepTwo := new(library.Step) stepTwo.SetID(2) @@ -2422,28 +2804,15 @@ func newResources() *Resources { stepTwo.SetHost("example.company.com") stepTwo.SetRuntime("docker") stepTwo.SetDistribution("linux") + stepTwo.SetReportAs("test") - userOne := new(library.User) - userOne.SetID(1) - userOne.SetName("octocat") - userOne.SetToken("superSecretToken") - userOne.SetRefreshToken("superSecretRefreshToken") - userOne.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") - userOne.SetFavorites([]string{"github/octocat"}) - userOne.SetActive(true) - userOne.SetAdmin(false) + _bPartialOne := new(api.Build) + _bPartialOne.SetID(1) - userTwo := new(library.User) - userTwo.SetID(2) - userTwo.SetName("octokitty") - userTwo.SetToken("superSecretToken") - userTwo.SetRefreshToken("superSecretRefreshToken") - userTwo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") - userTwo.SetFavorites([]string{"github/octocat"}) - userTwo.SetActive(true) - userTwo.SetAdmin(false) + _bPartialTwo := new(api.Build) + _bPartialTwo.SetID(2) - workerOne := new(library.Worker) + workerOne := new(api.Worker) workerOne.SetID(1) workerOne.SetHostname("worker-1.example.com") workerOne.SetAddress("https://worker-1.example.com") @@ -2451,13 +2820,13 @@ func newResources() *Resources { workerOne.SetActive(true) workerOne.SetStatus("available") workerOne.SetLastStatusUpdateAt(time.Now().UTC().Unix()) - workerOne.SetRunningBuildIDs([]string{"12345"}) + workerOne.SetRunningBuilds([]*api.Build{_bPartialOne}) workerOne.SetLastBuildStartedAt(time.Now().UTC().Unix()) workerOne.SetLastBuildFinishedAt(time.Now().UTC().Unix()) workerOne.SetLastCheckedIn(time.Now().UTC().Unix() - 60) workerOne.SetBuildLimit(1) - workerTwo := new(library.Worker) + workerTwo := new(api.Worker) workerTwo.SetID(2) workerTwo.SetHostname("worker-2.example.com") workerTwo.SetAddress("https://worker-2.example.com") @@ -2465,26 +2834,28 @@ func newResources() *Resources { workerTwo.SetActive(true) workerTwo.SetStatus("available") workerTwo.SetLastStatusUpdateAt(time.Now().UTC().Unix()) - workerTwo.SetRunningBuildIDs([]string{"12345"}) + workerTwo.SetRunningBuilds([]*api.Build{_bPartialTwo}) workerTwo.SetLastBuildStartedAt(time.Now().UTC().Unix()) workerTwo.SetLastBuildFinishedAt(time.Now().UTC().Unix()) workerTwo.SetLastCheckedIn(time.Now().UTC().Unix() - 60) workerTwo.SetBuildLimit(1) return &Resources{ - Builds: []*library.Build{buildOne, buildTwo}, + Builds: []*api.Build{buildOne, buildTwo}, + Dashboards: []*api.Dashboard{dashboardOne, dashboardTwo}, Deployments: []*library.Deployment{deploymentOne, deploymentTwo}, Executables: []*library.BuildExecutable{executableOne, executableTwo}, Hooks: []*library.Hook{hookOne, hookTwo, hookThree}, + JWKs: jwkSet, Logs: []*library.Log{logServiceOne, logServiceTwo, logStepOne, logStepTwo}, Pipelines: []*library.Pipeline{pipelineOne, pipelineTwo}, - Repos: []*library.Repo{repoOne, repoTwo}, - Schedules: []*library.Schedule{scheduleOne, scheduleTwo}, + Repos: []*api.Repo{repoOne, repoTwo}, + Schedules: []*api.Schedule{scheduleOne, scheduleTwo}, Secrets: []*library.Secret{secretOrg, secretRepo, secretShared}, Services: []*library.Service{serviceOne, serviceTwo}, Steps: []*library.Step{stepOne, stepTwo}, - Users: []*library.User{userOne, userTwo}, - Workers: []*library.Worker{workerOne, workerTwo}, + Users: []*api.User{userOne, userTwo}, + Workers: []*api.Worker{workerOne, workerTwo}, } } diff --git a/database/interface.go b/database/interface.go index 4a51019b3..b054d4329 100644 --- a/database/interface.go +++ b/database/interface.go @@ -4,15 +4,18 @@ package database import ( "github.com/go-vela/server/database/build" + "github.com/go-vela/server/database/dashboard" "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/schedule" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/service" + "github.com/go-vela/server/database/settings" "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" @@ -33,18 +36,27 @@ type Interface interface { // Resource Interface Functions + // SettingsInterface defines the interface for platform settings stored in the database. + settings.SettingsInterface + // BuildInterface defines the interface for builds stored in the database. build.BuildInterface // BuildExecutableInterface defines the interface for build executables stored in the database. executable.BuildExecutableInterface + // DashboardInterface defines the interface for builds store in the database. + dashboard.DashboardInterface + // DeploymentInterface defines the interface for deployments stored in the database. deployment.DeploymentInterface // HookInterface defines the interface for hooks stored in the database. hook.HookInterface + // JWKInterface defines the interface for JWKs stored in the database. + jwk.JWKInterface + // LogInterface defines the interface for logs stored in the database. log.LogInterface diff --git a/database/jwk/create.go b/database/jwk/create.go new file mode 100644 index 000000000..0ef8330b9 --- /dev/null +++ b/database/jwk/create.go @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "database/sql" + + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// CreateJWK creates a new JWK in the database. +func (e *engine) CreateJWK(_ context.Context, j jwk.RSAPublicKey) error { + e.logger.WithFields(logrus.Fields{ + "jwk": j.KeyID(), + }).Tracef("creating key %s", j.KeyID()) + + key := types.JWKFromAPI(j) + key.Active = sql.NullBool{Bool: true, Valid: true} + + // send query to the database + return e.client.Table(constants.TableJWK).Create(key).Error +} diff --git a/database/jwk/create_test.go b/database/jwk/create_test.go new file mode 100644 index 000000000..2275a4c4e --- /dev/null +++ b/database/jwk/create_test.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "encoding/json" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_CreateJWK(t *testing.T) { + // setup types + _jwk := testutils.JWK() + _jwkBytes, err := json.Marshal(_jwk) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`INSERT INTO "jwks" +("id","active","key") +VALUES ($1,$2,$3)`). + WithArgs(_jwk.KeyID(), true, _jwkBytes). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateJWK(context.TODO(), _jwk) + + if test.failure { + if err == nil { + t.Errorf("CreateDashboard for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateDashboard for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/jwk/get.go b/database/jwk/get.go new file mode 100644 index 000000000..b324ae3cc --- /dev/null +++ b/database/jwk/get.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + "github.com/lestrrat-go/jwx/v2/jwk" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// GetActiveJWK gets a JWK by UUID (kid) from the database if active. +func (e *engine) GetActiveJWK(_ context.Context, id string) (jwk.RSAPublicKey, error) { + e.logger.Tracef("getting JWK key %s", id) + + // variable to store query results + j := new(types.JWK) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableJWK). + Where("id = ?", id). + Where("active = ?", true). + Take(j). + Error + if err != nil { + return j.ToAPI(), err + } + + return j.ToAPI(), nil +} diff --git a/database/jwk/get_test.go b/database/jwk/get_test.go new file mode 100644 index 000000000..7af354a63 --- /dev/null +++ b/database/jwk/get_test.go @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "encoding/json" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/v2/jwk" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_GetJWK(t *testing.T) { + // setup types + _jwk := testutils.JWK() + _jwkBytes, err := json.Marshal(_jwk) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow(_jwk.KeyID(), true, _jwkBytes) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwk.KeyID(), true, 1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err = _sqlite.CreateJWK(context.TODO(), _jwk) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want jwk.RSAPublicKey + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _jwk, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _jwk, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetActiveJWK(context.TODO(), _jwk.KeyID()) + + if test.failure { + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + if diff := cmp.Diff(test.want, got, testutils.JwkKeyOpts); diff != "" { + t.Errorf("GetActiveJWK mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/database/jwk/interface.go b/database/jwk/interface.go new file mode 100644 index 000000000..2e66b4bcf --- /dev/null +++ b/database/jwk/interface.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + "github.com/lestrrat-go/jwx/v2/jwk" +) + +// JWKInterface represents the Vela interface for JWK +// functions with the supported Database backends. +// +//nolint:revive // ignore name stutter +type JWKInterface interface { + // JWK Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + CreateJWKTable(context.Context, string) error + + // JWK Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CreateJWK defines a function that creates a JWK. + CreateJWK(context.Context, jwk.RSAPublicKey) error + // RotateKeys defines a function that rotates JWKs. + RotateKeys(context.Context) error + // ListJWKs defines a function that lists all JWKs configured. + ListJWKs(context.Context) (jwk.Set, error) + // GetJWK defines a function that gets a JWK by the provided key ID. + GetActiveJWK(context.Context, string) (jwk.RSAPublicKey, error) +} diff --git a/database/jwk/jwk.go b/database/jwk/jwk.go new file mode 100644 index 000000000..bcce18eb8 --- /dev/null +++ b/database/jwk/jwk.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + "github.com/go-vela/server/constants" +) + +type ( + // config represents the settings required to create the engine that implements the JWKService interface. + config struct { + // specifies to skip creating tables and indexes for the JWK engine + SkipCreation bool + // specifies the driver for proper popping query + Driver string + } + + // engine represents the key set functionality that implements the JWKService interface. + engine struct { + // engine configuration settings used in key set functions + config *config + + ctx context.Context + + // gorm.io/gorm database client used in key set functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in key set functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with key sets in the database. +// +//nolint:revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new JWK engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating key set database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of key sets table and indexes") + + return e, nil + } + + // create the JWK table + err := e.CreateJWKTable(e.ctx, e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableJWK, err) + } + + return e, nil +} diff --git a/database/jwk/jwk_test.go b/database/jwk/jwk_test.go new file mode 100644 index 000000000..95ff9a463 --- /dev/null +++ b/database/jwk/jwk_test.go @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestJWK_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres dashboard engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite dashboard engine: %v", err) + } + + return _engine +} diff --git a/database/jwk/list.go b/database/jwk/list.go new file mode 100644 index 000000000..4c29a4c5c --- /dev/null +++ b/database/jwk/list.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + "github.com/lestrrat-go/jwx/v2/jwk" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// ListJWKs gets a list of all configured JWKs from the database. +func (e *engine) ListJWKs(_ context.Context) (jwk.Set, error) { + e.logger.Trace("listing all JWKs") + + k := new([]types.JWK) + keySet := jwk.NewSet() + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableJWK). + Find(&k). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, key := range *k { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := key + + // convert query result to API type + err = keySet.AddKey(tmp.ToAPI()) + if err != nil { + return nil, err + } + } + + return keySet, nil +} diff --git a/database/jwk/list_test.go b/database/jwk/list_test.go new file mode 100644 index 000000000..6e8bb5399 --- /dev/null +++ b/database/jwk/list_test.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/lestrrat-go/jwx/v2/jwk" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_ListJWKs(t *testing.T) { + // setup types + _jwkOne := testutils.JWK() + _jwkOneBytes, err := json.Marshal(_jwkOne) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _jwkTwo := testutils.JWK() + _jwkTwoBytes, err := json.Marshal(_jwkTwo) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}). + AddRow(_jwkOne.KeyID(), true, _jwkOneBytes). + AddRow(_jwkTwo.KeyID(), true, _jwkTwoBytes) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err = _sqlite.CreateJWK(context.TODO(), _jwkOne) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + err = _sqlite.CreateJWK(context.TODO(), _jwkTwo) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + wantSet := jwk.NewSet() + + err = wantSet.AddKey(_jwkOne) + if err != nil { + t.Errorf("unable to add jwk to set: %v", err) + } + + err = wantSet.AddKey(_jwkTwo) + if err != nil { + t.Errorf("unable to add jwk to set: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want jwk.Set + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: wantSet, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: wantSet, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListJWKs(context.TODO()) + + if test.failure { + if err == nil { + t.Errorf("ListJWKs for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListJWKs for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListJWKs for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/jwk/opts.go b/database/jwk/opts.go new file mode 100644 index 000000000..b26329135 --- /dev/null +++ b/database/jwk/opts.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for key sets. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for key sets. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the key set engine + e.client = client + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for key sets. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the key set engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for key sets. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the key set engine + e.config.SkipCreation = skipCreation + + return nil + } +} + +// WithContext sets the context in the database engine for key sets. +func WithContext(ctx context.Context) EngineOpt { + return func(e *engine) error { + e.ctx = ctx + + return nil + } +} diff --git a/database/jwk/opts_test.go b/database/jwk/opts_test.go new file mode 100644 index 000000000..8a7192111 --- /dev/null +++ b/database/jwk/opts_test.go @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "reflect" + "testing" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func TestHook_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} + +func TestHook_EngineOpt_WithContext(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + ctx context.Context + want context.Context + }{ + { + failure: false, + name: "context set to TODO", + ctx: context.TODO(), + want: context.TODO(), + }, + { + failure: false, + name: "context set to nil", + ctx: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithContext(test.ctx)(e) + + if test.failure { + if err == nil { + t.Errorf("WithContext for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithContext returned err: %v", err) + } + + if !reflect.DeepEqual(e.ctx, test.want) { + t.Errorf("WithContext is %v, want %v", e.ctx, test.want) + } + }) + } +} diff --git a/database/jwk/rotate.go b/database/jwk/rotate.go new file mode 100644 index 000000000..793584037 --- /dev/null +++ b/database/jwk/rotate.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "database/sql" + + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database/types" +) + +// RotateKeys removes all inactive keys and sets active keys to inactive. +func (e *engine) RotateKeys(_ context.Context) error { + e.logger.Trace("rotating jwks") + + k := types.JWK{} + + // remove inactive keys + err := e.client. + Table(constants.TableJWK). + Where("active = ?", false). + Delete(&k). + Error + if err != nil { + return err + } + + // set active keys to inactive + err = e.client. + Table(constants.TableJWK). + Where("active = ?", true). + Update("active", sql.NullBool{Bool: false, Valid: true}). + Error + if err != nil { + return err + } + + return nil +} diff --git a/database/jwk/rotate_test.go b/database/jwk/rotate_test.go new file mode 100644 index 000000000..cd4acbb72 --- /dev/null +++ b/database/jwk/rotate_test.go @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "encoding/json" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" +) + +func TestJWK_Engine_RotateKeys(t *testing.T) { + // setup types + _jwkOne := testutils.JWK() + _jwkOneBytes, err := json.Marshal(_jwkOne) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _jwkTwo := testutils.JWK() + _jwkTwoBytes, err := json.Marshal(_jwkTwo) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow(_jwkOne.KeyID(), true, _jwkOneBytes) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwkOne.KeyID(), true, 1).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "active", "key"}, + ).AddRow(_jwkTwo.KeyID(), true, _jwkTwoBytes) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "jwks" WHERE id = $1 AND active = $2 LIMIT $3`).WithArgs(_jwkTwo.KeyID(), true, 1).WillReturnRows(_rows) + + _mock.ExpectExec(`DELETE FROM "jwks" WHERE active = $1`). + WithArgs(false). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _mock.ExpectExec(`UPDATE "jwks" SET "active"=$1 WHERE active = $2`). + WithArgs(false, true). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err = _sqlite.CreateJWK(context.TODO(), _jwkOne) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + err = _sqlite.CreateJWK(context.TODO(), _jwkTwo) + if err != nil { + t.Errorf("unable to create test jwk for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := test.database.GetActiveJWK(context.TODO(), _jwkOne.KeyID()) + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.KeyID()) + if err != nil { + t.Errorf("GetActiveJWK for %s returned err: %v", test.name, err) + } + + err = test.database.RotateKeys(context.TODO()) + + if test.failure { + if err == nil { + t.Errorf("RotateKeys for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("RotateKeys for %s returned err: %v", test.name, err) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkOne.KeyID()) + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + + _, err = test.database.GetActiveJWK(context.TODO(), _jwkTwo.KeyID()) + if err == nil { + t.Errorf("GetActiveJWK for %s should have returned err", test.name) + } + }) + } +} diff --git a/database/jwk/table.go b/database/jwk/table.go new file mode 100644 index 000000000..b2a40c844 --- /dev/null +++ b/database/jwk/table.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres jwks table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +jwks ( + id UUID PRIMARY KEY, + active BOOLEAN, + key JSON DEFAULT NULL +); +` + + // CreateSqliteTable represents a query to create the Sqlite jwks table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +jwks ( + id TEXT PRIMARY KEY, + active BOOLEAN, + key TEXT +); +` +) + +// CreateJWKTable creates the jwks table in the database. +func (e *engine) CreateJWKTable(ctx context.Context, driver string) error { + e.logger.Tracef("creating jwks table") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the jwks table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the jwks table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/jwk/table_test.go b/database/jwk/table_test.go new file mode 100644 index 000000000..ec8626677 --- /dev/null +++ b/database/jwk/table_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package jwk + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestJWK_Engine_CreateJWKTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateJWKTable(context.TODO(), test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateJWKTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateJWKTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/log/count.go b/database/log/count.go index b26066b94..e3b38b316 100644 --- a/database/log/count.go +++ b/database/log/count.go @@ -10,7 +10,7 @@ import ( // CountLogs gets the count of all logs from the database. func (e *engine) CountLogs(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all logs from the database") + e.logger.Tracef("getting count of all logs") // variable to store query results var l int64 diff --git a/database/log/count_build.go b/database/log/count_build.go index 168d9e1c1..524ff2960 100644 --- a/database/log/count_build.go +++ b/database/log/count_build.go @@ -5,13 +5,13 @@ package log import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" ) // CountLogsForBuild gets the count of logs by build ID from the database. -func (e *engine) CountLogsForBuild(ctx context.Context, b *library.Build) (int64, error) { - e.logger.Tracef("getting count of logs for build %d from the database", b.GetID()) +func (e *engine) CountLogsForBuild(ctx context.Context, b *api.Build) (int64, error) { + e.logger.Tracef("getting count of logs for build %d", b.GetID()) // variable to store query results var l int64 diff --git a/database/log/count_build_test.go b/database/log/count_build_test.go index c66422134..ca05016fb 100644 --- a/database/log/count_build_test.go +++ b/database/log/count_build_test.go @@ -8,26 +8,31 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestLog_Engine_CountLogsForBuild(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) _step.SetStepID(1) - _build := testBuild() + _repo := testutils.APIRepo() + _repo.SetID(1) + + _build := testutils.APIBuild() _build.SetID(1) _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _postgres, _mock := testPostgres(t) diff --git a/database/log/count_test.go b/database/log/count_test.go index cb7f516dc..49ee80de5 100644 --- a/database/log/count_test.go +++ b/database/log/count_test.go @@ -8,17 +8,19 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestLog_Engine_CountLogs(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) diff --git a/database/log/create.go b/database/log/create.go index e1c3a9212..fdabab63c 100644 --- a/database/log/create.go +++ b/database/log/create.go @@ -17,9 +17,9 @@ func (e *engine) CreateLog(ctx context.Context, l *library.Log) error { // check what the log entry is for switch { case l.GetServiceID() > 0: - e.logger.Tracef("creating log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID()) + e.logger.Tracef("creating log for service %d for build %d", l.GetServiceID(), l.GetBuildID()) case l.GetStepID() > 0: - e.logger.Tracef("creating log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID()) + e.logger.Tracef("creating log for step %d for build %d", l.GetStepID(), l.GetBuildID()) } // cast the library type to database type diff --git a/database/log/create_test.go b/database/log/create_test.go index 80514837c..3b86fbe44 100644 --- a/database/log/create_test.go +++ b/database/log/create_test.go @@ -7,18 +7,20 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_CreateLog(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) diff --git a/database/log/delete.go b/database/log/delete.go index 7018396de..31f4ea31e 100644 --- a/database/log/delete.go +++ b/database/log/delete.go @@ -15,9 +15,9 @@ func (e *engine) DeleteLog(ctx context.Context, l *library.Log) error { // check what the log entry is for switch { case l.GetServiceID() > 0: - e.logger.Tracef("deleting log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID()) + e.logger.Tracef("deleting log for service %d for build %d", l.GetServiceID(), l.GetBuildID()) case l.GetStepID() > 0: - e.logger.Tracef("deleting log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID()) + e.logger.Tracef("deleting log for step %d for build %d", l.GetStepID(), l.GetBuildID()) } // cast the library type to database type diff --git a/database/log/delete_test.go b/database/log/delete_test.go index 3b3000fc6..610f0c2cd 100644 --- a/database/log/delete_test.go +++ b/database/log/delete_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestLog_Engine_DeleteLog(t *testing.T) { // setup types - _log := testLog() + _log := testutils.APILog() _log.SetID(1) _log.SetRepoID(1) _log.SetBuildID(1) diff --git a/database/log/get.go b/database/log/get.go index be02ee968..2a825a6da 100644 --- a/database/log/get.go +++ b/database/log/get.go @@ -12,7 +12,7 @@ import ( // GetLog gets a log by ID from the database. func (e *engine) GetLog(ctx context.Context, id int64) (*library.Log, error) { - e.logger.Tracef("getting log %d from the database", id) + e.logger.Tracef("getting log %d", id) // variable to store query results l := new(database.Log) diff --git a/database/log/get_service.go b/database/log/get_service.go index 83d5f2f45..aac501801 100644 --- a/database/log/get_service.go +++ b/database/log/get_service.go @@ -13,7 +13,7 @@ import ( // GetLogForService gets a log by service ID from the database. func (e *engine) GetLogForService(ctx context.Context, s *library.Service) (*library.Log, error) { - e.logger.Tracef("getting log for service %d for build %d from the database", s.GetID(), s.GetBuildID()) + e.logger.Tracef("getting log for service %d for build %d", s.GetID(), s.GetBuildID()) // variable to store query results l := new(database.Log) diff --git a/database/log/get_service_test.go b/database/log/get_service_test.go index e6c9cde8c..b4ae5efe5 100644 --- a/database/log/get_service_test.go +++ b/database/log/get_service_test.go @@ -8,19 +8,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_GetLogForService(t *testing.T) { // setup types - _log := testLog() + _log := testutils.APILog() _log.SetID(1) _log.SetRepoID(1) _log.SetBuildID(1) _log.SetServiceID(1) _log.SetData([]byte{}) - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetID(1) _service.SetRepoID(1) diff --git a/database/log/get_step.go b/database/log/get_step.go index da6d70cf7..f2ca4a3a1 100644 --- a/database/log/get_step.go +++ b/database/log/get_step.go @@ -13,7 +13,7 @@ import ( // GetLogForStep gets a log by step ID from the database. func (e *engine) GetLogForStep(ctx context.Context, s *library.Step) (*library.Log, error) { - e.logger.Tracef("getting log for step %d for build %d from the database", s.GetID(), s.GetBuildID()) + e.logger.Tracef("getting log for step %d for build %d", s.GetID(), s.GetBuildID()) // variable to store query results l := new(database.Log) diff --git a/database/log/get_step_test.go b/database/log/get_step_test.go index 5eea62f56..68073bf85 100644 --- a/database/log/get_step_test.go +++ b/database/log/get_step_test.go @@ -8,19 +8,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_GetLogForStep(t *testing.T) { // setup types - _log := testLog() + _log := testutils.APILog() _log.SetID(1) _log.SetRepoID(1) _log.SetBuildID(1) _log.SetStepID(1) _log.SetData([]byte{}) - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetID(1) _step.SetRepoID(1) diff --git a/database/log/get_test.go b/database/log/get_test.go index db88112b0..d2263704f 100644 --- a/database/log/get_test.go +++ b/database/log/get_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_GetLog(t *testing.T) { // setup types - _log := testLog() + _log := testutils.APILog() _log.SetID(1) _log.SetRepoID(1) _log.SetBuildID(1) diff --git a/database/log/index.go b/database/log/index.go index c167f3825..230a10d8b 100644 --- a/database/log/index.go +++ b/database/log/index.go @@ -17,7 +17,7 @@ ON logs (build_id); // CreateLogIndexes creates the indexes for the logs table in the database. func (e *engine) CreateLogIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for logs table in the database") + e.logger.Tracef("creating indexes for logs table") // create the build_id column index for the logs table return e.client.Exec(CreateBuildIDIndex).Error diff --git a/database/log/interface.go b/database/log/interface.go index 92f5d6cdd..97ace05c6 100644 --- a/database/log/interface.go +++ b/database/log/interface.go @@ -5,6 +5,7 @@ package log import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -29,7 +30,7 @@ type LogInterface interface { // CountLogs defines a function that gets the count of all logs. CountLogs(context.Context) (int64, error) // CountLogsForBuild defines a function that gets the count of logs by build ID. - CountLogsForBuild(context.Context, *library.Build) (int64, error) + CountLogsForBuild(context.Context, *api.Build) (int64, error) // CreateLog defines a function that creates a new log. CreateLog(context.Context, *library.Log) error // DeleteLog defines a function that deletes an existing log. @@ -43,7 +44,7 @@ type LogInterface interface { // ListLogs defines a function that gets a list of all logs. ListLogs(context.Context) ([]*library.Log, error) // ListLogsForBuild defines a function that gets a list of logs by build ID. - ListLogsForBuild(context.Context, *library.Build, int, int) ([]*library.Log, int64, error) + ListLogsForBuild(context.Context, *api.Build, int, int) ([]*library.Log, int64, error) // UpdateLog defines a function that updates an existing log. UpdateLog(context.Context, *library.Log) error } diff --git a/database/log/list.go b/database/log/list.go index ae85a451e..367d8c15e 100644 --- a/database/log/list.go +++ b/database/log/list.go @@ -12,7 +12,7 @@ import ( // ListLogs gets a list of all logs from the database. func (e *engine) ListLogs(ctx context.Context) ([]*library.Log, error) { - e.logger.Trace("listing all logs from the database") + e.logger.Trace("listing all logs") // variables to store query results and return value count := int64(0) diff --git a/database/log/list_build.go b/database/log/list_build.go index 183b1f167..43c117916 100644 --- a/database/log/list_build.go +++ b/database/log/list_build.go @@ -5,14 +5,15 @@ package log import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" ) // ListLogsForBuild gets a list of logs by build ID from the database. -func (e *engine) ListLogsForBuild(ctx context.Context, b *library.Build, page, perPage int) ([]*library.Log, int64, error) { - e.logger.Tracef("listing logs for build %d from the database", b.GetID()) +func (e *engine) ListLogsForBuild(ctx context.Context, b *api.Build, page, perPage int) ([]*library.Log, int64, error) { + e.logger.Tracef("listing logs for build %d", b.GetID()) // variables to store query results and return value count := int64(0) diff --git a/database/log/list_build_test.go b/database/log/list_build_test.go index f18765ec3..e4b9038cd 100644 --- a/database/log/list_build_test.go +++ b/database/log/list_build_test.go @@ -8,29 +8,34 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_ListLogsForBuild(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) _service.SetData([]byte{}) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) _step.SetStepID(1) _step.SetData([]byte{}) - _build := testBuild() + _repo := testutils.APIRepo() + _repo.SetID(1) + + _build := testutils.APIBuild() _build.SetID(1) _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(_repo) _build.SetNumber(1) _postgres, _mock := testPostgres(t) diff --git a/database/log/list_test.go b/database/log/list_test.go index 1bcf1759e..6371c9fd2 100644 --- a/database/log/list_test.go +++ b/database/log/list_test.go @@ -8,19 +8,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_ListLogs(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) _service.SetData([]byte{}) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) diff --git a/database/log/log.go b/database/log/log.go index 0b8378d8f..e95dc6d04 100644 --- a/database/log/log.go +++ b/database/log/log.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -62,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating log database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of logs table and indexes in the database") + e.logger.Warning("skipping creation of logs table and indexes") return e, nil } diff --git a/database/log/log_test.go b/database/log/log_test.go index f8b5bcadf..528dba1e2 100644 --- a/database/log/log_test.go +++ b/database/log/log_test.go @@ -8,9 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -177,98 +175,3 @@ type AnyArgument struct{} func (a AnyArgument) Match(_ driver.Value) bool { return true } - -// testBuild is a test helper function to create a library -// Build type with all fields set to their zero values. -func testBuild() *library.Build { - return &library.Build{ - ID: new(int64), - RepoID: new(int64), - PipelineID: new(int64), - Number: new(int), - Parent: new(int), - Event: new(string), - EventAction: new(string), - Status: new(string), - Error: new(string), - Enqueued: new(int64), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Deploy: new(string), - Clone: new(string), - Source: new(string), - Title: new(string), - Message: new(string), - Commit: new(string), - Sender: new(string), - Author: new(string), - Email: new(string), - Link: new(string), - Branch: new(string), - Ref: new(string), - BaseRef: new(string), - HeadRef: new(string), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - -// testLog is a test helper function to create a library -// Log type with all fields set to their zero values. -func testLog() *library.Log { - return &library.Log{ - ID: new(int64), - RepoID: new(int64), - BuildID: new(int64), - ServiceID: new(int64), - StepID: new(int64), - Data: new([]byte), - } -} - -// testService is a test helper function to create a library -// Service type with all fields set to their zero values. -func testService() *library.Service { - return &library.Service{ - ID: new(int64), - BuildID: new(int64), - RepoID: new(int64), - Number: new(int), - Name: new(string), - Image: new(string), - Status: new(string), - Error: new(string), - ExitCode: new(int), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - -// testStep is a test helper function to create a library -// Step type with all fields set to their zero values. -func testStep() *library.Step { - return &library.Step{ - ID: new(int64), - BuildID: new(int64), - RepoID: new(int64), - Number: new(int), - Name: new(string), - Image: new(string), - Stage: new(string), - Status: new(string), - Error: new(string), - ExitCode: new(int), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} diff --git a/database/log/opts.go b/database/log/opts.go index 03a913d2b..8ad73c6dd 100644 --- a/database/log/opts.go +++ b/database/log/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/log/opts_test.go b/database/log/opts_test.go index d2a098e15..6e63d0736 100644 --- a/database/log/opts_test.go +++ b/database/log/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/log/table.go b/database/log/table.go index aa8c87cfe..94ec398ff 100644 --- a/database/log/table.go +++ b/database/log/table.go @@ -44,7 +44,7 @@ logs ( // CreateLogTable creates the logs table in the database. func (e *engine) CreateLogTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating logs table in the database") + e.logger.Tracef("creating logs table") // handle the driver provided to create the table switch driver { diff --git a/database/log/update.go b/database/log/update.go index 7929ab0fa..f7a148aae 100644 --- a/database/log/update.go +++ b/database/log/update.go @@ -17,9 +17,9 @@ func (e *engine) UpdateLog(ctx context.Context, l *library.Log) error { // check what the log entry is for switch { case l.GetServiceID() > 0: - e.logger.Tracef("updating log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID()) + e.logger.Tracef("updating log for service %d for build %d", l.GetServiceID(), l.GetBuildID()) case l.GetStepID() > 0: - e.logger.Tracef("updating log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID()) + e.logger.Tracef("updating log for step %d for build %d", l.GetStepID(), l.GetBuildID()) } // cast the library type to database type diff --git a/database/log/update_test.go b/database/log/update_test.go index 3550e0c94..ae2ca53f0 100644 --- a/database/log/update_test.go +++ b/database/log/update_test.go @@ -7,19 +7,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestLog_Engine_UpdateLog(t *testing.T) { // setup types - _service := testLog() + _service := testutils.APILog() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) _service.SetServiceID(1) _service.SetData([]byte{}) - _step := testLog() + _step := testutils.APILog() _step.SetID(2) _step.SetRepoID(1) _step.SetBuildID(1) diff --git a/database/logger.go b/database/logger.go new file mode 100644 index 000000000..819574704 --- /dev/null +++ b/database/logger.go @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 + +package database + +import ( + "context" + "errors" + "time" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/utils" +) + +// GormLogger is a custom logger for Gorm. +type GormLogger struct { + slowThreshold time.Duration + skipErrRecordNotFound bool + showSQL bool + entry *logrus.Entry +} + +// NewGormLogger creates a new Gorm logger. +func NewGormLogger(logger *logrus.Entry, slowThreshold time.Duration, skipNotFound, showSQL bool) *GormLogger { + return &GormLogger{ + skipErrRecordNotFound: skipNotFound, + slowThreshold: slowThreshold, + showSQL: showSQL, + entry: logger, + } +} + +// LogMode sets the log mode for the logger. +func (l *GormLogger) LogMode(logger.LogLevel) logger.Interface { + return l +} + +// Info implements the logger.Interface. +func (l *GormLogger) Info(ctx context.Context, msg string, args ...interface{}) { + l.entry.WithContext(ctx).Info(msg, args) +} + +// Warn implements the logger.Interface. +func (l *GormLogger) Warn(ctx context.Context, msg string, args ...interface{}) { + l.entry.WithContext(ctx).Warn(msg, args) +} + +// Error implements the logger.Interface. +func (l *GormLogger) Error(ctx context.Context, msg string, args ...interface{}) { + l.entry.WithContext(ctx).Error(msg, args) +} + +// Trace implements the logger.Interface. +func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + elapsed := time.Since(begin) + sql, rows := fc() + fields := logrus.Fields{ + "rows": rows, + "elapsed": elapsed, + "source": utils.FileWithLineNum(), + } + + if l.showSQL { + fields["sql"] = sql + } + + if err != nil && (!errors.Is(err, gorm.ErrRecordNotFound) || !l.skipErrRecordNotFound) { + l.entry.WithContext(ctx).WithError(err).WithFields(fields).Error("gorm error") + return + } + + if l.slowThreshold != 0 && elapsed > l.slowThreshold { + l.entry.WithContext(ctx).WithFields(fields).Warnf("gorm warn SLOW QUERY >= %s, took %s", l.slowThreshold, elapsed) + return + } + + l.entry.WithContext(ctx).WithFields(fields).Infof("gorm info") +} diff --git a/database/logger_test.go b/database/logger_test.go new file mode 100644 index 000000000..ce64513bf --- /dev/null +++ b/database/logger_test.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 + +package database + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sirupsen/logrus" +) + +func TestNewGormLogger(t *testing.T) { + logger := logrus.NewEntry(logrus.New()) + + type args struct { + logger *logrus.Entry + slowThreshold time.Duration + skipNotFound bool + showSQL bool + } + tests := []struct { + name string + args args + want *GormLogger + }{ + { + name: "logger set", + args: args{ + logger: logger, + slowThreshold: time.Second, + skipNotFound: false, + showSQL: true, + }, + want: &GormLogger{ + slowThreshold: time.Second, + skipErrRecordNotFound: false, + showSQL: true, + entry: logger, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if diff := cmp.Diff(NewGormLogger(tt.args.logger, tt.args.slowThreshold, tt.args.skipNotFound, tt.args.showSQL), tt.want, cmpopts.EquateComparable(GormLogger{})); diff != "" { + t.Errorf("NewGormLogger() mismatch (-got +want):\n%s", diff) + } + }) + } +} diff --git a/database/opts.go b/database/opts.go index 2e758d802..3a35d751e 100644 --- a/database/opts.go +++ b/database/opts.go @@ -80,6 +80,46 @@ func WithEncryptionKey(encryptionKey string) EngineOpt { } } +// WithLogLevel sets the log level in the database engine. +func WithLogLevel(logLevel string) EngineOpt { + return func(e *engine) error { + // set the log level for the database engine + e.config.LogLevel = logLevel + + return nil + } +} + +// WithLogSkipNotFound sets the log skip not found option in the database engine. +func WithLogSkipNotFound(logSkipNotFound bool) EngineOpt { + return func(e *engine) error { + // set the log skip not found option for the database engine + e.config.LogSkipNotFound = logSkipNotFound + + return nil + } +} + +// WithLogSlowThreshold sets the log slow query threshold in the database engine. +func WithLogSlowThreshold(logSlowThreshold time.Duration) EngineOpt { + return func(e *engine) error { + // set the slow query threshold for the database engine + e.config.LogSlowThreshold = logSlowThreshold + + return nil + } +} + +// WithLogShowSQL sets the log show SQL option in the database engine. +func WithLogShowSQL(logShowSQL bool) EngineOpt { + return func(e *engine) error { + // set the log show SQL option for the database engine + e.config.LogShowSQL = logShowSQL + + return nil + } +} + // WithSkipCreation sets the skip creation logic in the database engine. func WithSkipCreation(skipCreation bool) EngineOpt { return func(e *engine) error { diff --git a/database/opts_test.go b/database/opts_test.go index 7089d9271..abd65ad94 100644 --- a/database/opts_test.go +++ b/database/opts_test.go @@ -405,3 +405,179 @@ func TestDatabase_EngineOpt_WithSkipCreation(t *testing.T) { }) } } + +func TestDatabase_EngineOpt_WithLogLevel(t *testing.T) { + e := &engine{config: new(config)} + + tests := []struct { + failure bool + name string + logLevel string + want string + }{ + { + failure: false, + name: "log level set to debug", + logLevel: "debug", + want: "debug", + }, + { + failure: false, + name: "log level set to info", + logLevel: "info", + want: "info", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogLevel(test.logLevel)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogLevel for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogLevel returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.LogLevel, test.want) { + t.Errorf("WithLogLevel is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} + +func TestDatabase_EngineOpt_WithLogSkipNotFound(t *testing.T) { + e := &engine{config: new(config)} + + tests := []struct { + failure bool + name string + skip bool + want bool + }{ + { + failure: false, + name: "log skip not found set to true", + skip: true, + want: true, + }, + { + failure: false, + name: "log skip not found set to false", + skip: false, + want: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogSkipNotFound(test.skip)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogSkipNotFound for %s should have returned err", test.name) + } + + if err != nil { + t.Errorf("WithLogSkipNotFound for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(e.config.LogSkipNotFound, test.want) { + t.Errorf("WithLogSkipNotFound for %s is %v, want %v", test.name, e.config.LogSkipNotFound, test.want) + } + } + }) + } +} + +func TestDatabase_EngineOpt_WithLogSlowThreshold(t *testing.T) { + e := &engine{config: new(config)} + + tests := []struct { + failure bool + name string + threshold time.Duration + want time.Duration + }{ + { + failure: false, + name: "log slow threshold set to 1ms", + threshold: 1 * time.Millisecond, + want: 1 * time.Millisecond, + }, + { + failure: false, + name: "log slow threshold set to 2ms", + threshold: 2 * time.Millisecond, + want: 2 * time.Millisecond, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogSlowThreshold(test.threshold)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogSlowThreshold for %s should have returned err", test.name) + } + + if err != nil { + t.Errorf("WithLogSlowThreshold for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(e.config.LogSlowThreshold, test.want) { + t.Errorf("WithLogSlowThreshold for %s is %v, want %v", test.name, e.config.LogSlowThreshold, test.want) + } + } + }) + } +} + +func TestDatabase_EngineOpt_WithLogShowSQL(t *testing.T) { + e := &engine{config: new(config)} + + tests := []struct { + failure bool + name string + show bool + want bool + }{ + { + failure: false, + name: "log show SQL set to true", + show: true, + want: true, + }, + { + failure: false, + name: "log show SQL set to false", + show: false, + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogShowSQL(test.show)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogShowSQL for %s should have returned err", test.name) + } + + if err != nil { + t.Errorf("WithLogShowSQL for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(e.config.LogShowSQL, test.want) { + t.Errorf("WithLogShowSQL for %s is %v, want %v", test.name, e.config.LogShowSQL, test.want) + } + } + }) + } +} diff --git a/database/pipeline/count.go b/database/pipeline/count.go index a251f571b..3252f5edd 100644 --- a/database/pipeline/count.go +++ b/database/pipeline/count.go @@ -10,7 +10,7 @@ import ( // CountPipelines gets the count of all pipelines from the database. func (e *engine) CountPipelines(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all pipelines from the database") + e.logger.Tracef("getting count of all pipelines") // variable to store query results var p int64 diff --git a/database/pipeline/count_repo.go b/database/pipeline/count_repo.go index 549a9ecc9..6adf46984 100644 --- a/database/pipeline/count_repo.go +++ b/database/pipeline/count_repo.go @@ -5,17 +5,18 @@ package pipeline import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountPipelinesForRepo gets the count of pipelines by repo ID from the database. -func (e *engine) CountPipelinesForRepo(ctx context.Context, r *library.Repo) (int64, error) { +func (e *engine) CountPipelinesForRepo(ctx context.Context, r *api.Repo) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting count of pipelines for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of pipelines for repo %s", r.GetFullName()) // variable to store query results var p int64 diff --git a/database/pipeline/count_repo_test.go b/database/pipeline/count_repo_test.go index e40888f3c..ae306e9e5 100644 --- a/database/pipeline/count_repo_test.go +++ b/database/pipeline/count_repo_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestPipeline_Engine_CountPipelinesForRepo(t *testing.T) { // setup types - _pipelineOne := testPipeline() + _pipelineOne := testutils.APIPipeline() _pipelineOne.SetID(1) _pipelineOne.SetRepoID(1) _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") @@ -21,7 +23,7 @@ func TestPipeline_Engine_CountPipelinesForRepo(t *testing.T) { _pipelineOne.SetType("yaml") _pipelineOne.SetVersion("1") - _pipelineTwo := testPipeline() + _pipelineTwo := testutils.APIPipeline() _pipelineTwo.SetID(2) _pipelineTwo.SetRepoID(1) _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067") @@ -75,7 +77,7 @@ func TestPipeline_Engine_CountPipelinesForRepo(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := test.database.CountPipelinesForRepo(context.TODO(), &library.Repo{ID: _pipelineOne.RepoID}) + got, err := test.database.CountPipelinesForRepo(context.TODO(), &api.Repo{ID: _pipelineOne.RepoID}) if test.failure { if err == nil { diff --git a/database/pipeline/count_test.go b/database/pipeline/count_test.go index 829acfd58..351e84050 100644 --- a/database/pipeline/count_test.go +++ b/database/pipeline/count_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestPipeline_Engine_CountPipelines(t *testing.T) { // setup types - _pipelineOne := testPipeline() + _pipelineOne := testutils.APIPipeline() _pipelineOne.SetID(1) _pipelineOne.SetRepoID(1) _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") @@ -20,7 +22,7 @@ func TestPipeline_Engine_CountPipelines(t *testing.T) { _pipelineOne.SetType("yaml") _pipelineOne.SetVersion("1") - _pipelineTwo := testPipeline() + _pipelineTwo := testutils.APIPipeline() _pipelineTwo.SetID(2) _pipelineTwo.SetRepoID(2) _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067") diff --git a/database/pipeline/create.go b/database/pipeline/create.go index d3ba17711..4ee770b52 100644 --- a/database/pipeline/create.go +++ b/database/pipeline/create.go @@ -5,10 +5,11 @@ package pipeline import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreatePipeline creates a new pipeline in the database. diff --git a/database/pipeline/create_test.go b/database/pipeline/create_test.go index f6a0b7af1..58f52ffcc 100644 --- a/database/pipeline/create_test.go +++ b/database/pipeline/create_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestPipeline_Engine_CreatePipeline(t *testing.T) { // setup types - _pipeline := testPipeline() + _pipeline := testutils.APIPipeline() _pipeline.SetID(1) _pipeline.SetRepoID(1) _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") diff --git a/database/pipeline/delete.go b/database/pipeline/delete.go index 1f62c3048..b35cdd7d3 100644 --- a/database/pipeline/delete.go +++ b/database/pipeline/delete.go @@ -5,17 +5,18 @@ package pipeline import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeletePipeline deletes an existing pipeline from the database. func (e *engine) DeletePipeline(ctx context.Context, p *library.Pipeline) error { e.logger.WithFields(logrus.Fields{ "pipeline": p.GetCommit(), - }).Tracef("deleting pipeline %s from the database", p.GetCommit()) + }).Tracef("deleting pipeline %s", p.GetCommit()) // cast the library type to database type // diff --git a/database/pipeline/delete_test.go b/database/pipeline/delete_test.go index 2b141b702..35a03d463 100644 --- a/database/pipeline/delete_test.go +++ b/database/pipeline/delete_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestPipeline_Engine_DeletePipeline(t *testing.T) { // setup types - _pipeline := testPipeline() + _pipeline := testutils.APIPipeline() _pipeline.SetID(1) _pipeline.SetRepoID(1) _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") diff --git a/database/pipeline/get.go b/database/pipeline/get.go index 30a7f5d39..8e1c54b13 100644 --- a/database/pipeline/get.go +++ b/database/pipeline/get.go @@ -12,7 +12,7 @@ import ( // GetPipeline gets a pipeline by ID from the database. func (e *engine) GetPipeline(ctx context.Context, id int64) (*library.Pipeline, error) { - e.logger.Tracef("getting pipeline %d from the database", id) + e.logger.Tracef("getting pipeline %d", id) // variable to store query results p := new(database.Pipeline) diff --git a/database/pipeline/get_repo.go b/database/pipeline/get_repo.go index 934c0dcd2..0fc493003 100644 --- a/database/pipeline/get_repo.go +++ b/database/pipeline/get_repo.go @@ -5,19 +5,21 @@ package pipeline import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetPipelineForRepo gets a pipeline by number and repo ID from the database. -func (e *engine) GetPipelineForRepo(ctx context.Context, commit string, r *library.Repo) (*library.Pipeline, error) { +func (e *engine) GetPipelineForRepo(ctx context.Context, commit string, r *api.Repo) (*library.Pipeline, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "pipeline": commit, "repo": r.GetName(), - }).Tracef("getting pipeline %s/%s from the database", r.GetFullName(), commit) + }).Tracef("getting pipeline %s/%s", r.GetFullName(), commit) // variable to store query results p := new(database.Pipeline) diff --git a/database/pipeline/get_repo_test.go b/database/pipeline/get_repo_test.go index 82b9669da..d62612cd2 100644 --- a/database/pipeline/get_repo_test.go +++ b/database/pipeline/get_repo_test.go @@ -8,12 +8,15 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestPipeline_Engine_GetPipelineForRepo(t *testing.T) { // setup types - _pipeline := testPipeline() + _pipeline := testutils.APIPipeline() _pipeline.SetID(1) _pipeline.SetRepoID(1) _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") @@ -65,7 +68,7 @@ func TestPipeline_Engine_GetPipelineForRepo(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := test.database.GetPipelineForRepo(context.TODO(), "48afb5bdc41ad69bf22588491333f7cf71135163", &library.Repo{ID: _pipeline.RepoID}) + got, err := test.database.GetPipelineForRepo(context.TODO(), "48afb5bdc41ad69bf22588491333f7cf71135163", &api.Repo{ID: _pipeline.RepoID}) if test.failure { if err == nil { diff --git a/database/pipeline/get_test.go b/database/pipeline/get_test.go index d90404c33..c24f97da0 100644 --- a/database/pipeline/get_test.go +++ b/database/pipeline/get_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestPipeline_Engine_GetPipeline(t *testing.T) { // setup types - _pipeline := testPipeline() + _pipeline := testutils.APIPipeline() _pipeline.SetID(1) _pipeline.SetRepoID(1) _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") diff --git a/database/pipeline/interface.go b/database/pipeline/interface.go index 602cfbb1d..64666952e 100644 --- a/database/pipeline/interface.go +++ b/database/pipeline/interface.go @@ -5,6 +5,7 @@ package pipeline import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -29,7 +30,7 @@ type PipelineInterface interface { // CountPipelines defines a function that gets the count of all pipelines. CountPipelines(context.Context) (int64, error) // CountPipelinesForRepo defines a function that gets the count of pipelines by repo ID. - CountPipelinesForRepo(context.Context, *library.Repo) (int64, error) + CountPipelinesForRepo(context.Context, *api.Repo) (int64, error) // CreatePipeline defines a function that creates a new pipeline. CreatePipeline(context.Context, *library.Pipeline) (*library.Pipeline, error) // DeletePipeline defines a function that deletes an existing pipeline. @@ -37,11 +38,11 @@ type PipelineInterface interface { // GetPipeline defines a function that gets a pipeline by ID. GetPipeline(context.Context, int64) (*library.Pipeline, error) // GetPipelineForRepo defines a function that gets a pipeline by commit SHA and repo ID. - GetPipelineForRepo(context.Context, string, *library.Repo) (*library.Pipeline, error) + GetPipelineForRepo(context.Context, string, *api.Repo) (*library.Pipeline, error) // ListPipelines defines a function that gets a list of all pipelines. ListPipelines(context.Context) ([]*library.Pipeline, error) // ListPipelinesForRepo defines a function that gets a list of pipelines by repo ID. - ListPipelinesForRepo(context.Context, *library.Repo, int, int) ([]*library.Pipeline, int64, error) + ListPipelinesForRepo(context.Context, *api.Repo, int, int) ([]*library.Pipeline, int64, error) // UpdatePipeline defines a function that updates an existing pipeline. UpdatePipeline(context.Context, *library.Pipeline) (*library.Pipeline, error) } diff --git a/database/pipeline/list.go b/database/pipeline/list.go index 2299c8f6f..fd9e077e0 100644 --- a/database/pipeline/list.go +++ b/database/pipeline/list.go @@ -12,7 +12,7 @@ import ( // ListPipelines gets a list of all pipelines from the database. func (e *engine) ListPipelines(ctx context.Context) ([]*library.Pipeline, error) { - e.logger.Trace("listing all pipelines from the database") + e.logger.Trace("listing all pipelines") // variables to store query results and return value count := int64(0) diff --git a/database/pipeline/list_repo.go b/database/pipeline/list_repo.go index f4ed90fde..4f855c746 100644 --- a/database/pipeline/list_repo.go +++ b/database/pipeline/list_repo.go @@ -5,20 +5,22 @@ package pipeline import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListPipelinesForRepo gets a list of pipelines by repo ID from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListPipelinesForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Pipeline, int64, error) { +func (e *engine) ListPipelinesForRepo(ctx context.Context, r *api.Repo, page, perPage int) ([]*library.Pipeline, int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("listing pipelines for repo %s from the database", r.GetFullName()) + }).Tracef("listing pipelines for repo %s", r.GetFullName()) // variables to store query results and return values count := int64(0) diff --git a/database/pipeline/list_repo_test.go b/database/pipeline/list_repo_test.go index aec274702..4acc87e20 100644 --- a/database/pipeline/list_repo_test.go +++ b/database/pipeline/list_repo_test.go @@ -8,12 +8,15 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestPipeline_Engine_ListPipelinesForRepo(t *testing.T) { // setup types - _pipelineOne := testPipeline() + _pipelineOne := testutils.APIPipeline() _pipelineOne.SetID(1) _pipelineOne.SetRepoID(1) _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") @@ -22,7 +25,7 @@ func TestPipeline_Engine_ListPipelinesForRepo(t *testing.T) { _pipelineOne.SetVersion("1") _pipelineOne.SetData([]byte("foo")) - _pipelineTwo := testPipeline() + _pipelineTwo := testutils.APIPipeline() _pipelineTwo.SetID(2) _pipelineTwo.SetRepoID(1) _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067") @@ -86,7 +89,7 @@ func TestPipeline_Engine_ListPipelinesForRepo(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, _, err := test.database.ListPipelinesForRepo(context.TODO(), &library.Repo{ID: _pipelineOne.RepoID}, 1, 10) + got, _, err := test.database.ListPipelinesForRepo(context.TODO(), &api.Repo{ID: _pipelineOne.RepoID}, 1, 10) if test.failure { if err == nil { diff --git a/database/pipeline/list_test.go b/database/pipeline/list_test.go index 02779e483..b485bab6f 100644 --- a/database/pipeline/list_test.go +++ b/database/pipeline/list_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestPipeline_Engine_ListPipelines(t *testing.T) { // setup types - _pipelineOne := testPipeline() + _pipelineOne := testutils.APIPipeline() _pipelineOne.SetID(1) _pipelineOne.SetRepoID(1) _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") @@ -22,7 +24,7 @@ func TestPipeline_Engine_ListPipelines(t *testing.T) { _pipelineOne.SetVersion("1") _pipelineOne.SetData([]byte("foo")) - _pipelineTwo := testPipeline() + _pipelineTwo := testutils.APIPipeline() _pipelineTwo.SetID(2) _pipelineTwo.SetRepoID(2) _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067") diff --git a/database/pipeline/opts.go b/database/pipeline/opts.go index a05c92633..609181652 100644 --- a/database/pipeline/opts.go +++ b/database/pipeline/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/pipeline/opts_test.go b/database/pipeline/opts_test.go index 782c9af93..168bfef46 100644 --- a/database/pipeline/opts_test.go +++ b/database/pipeline/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/pipeline/pipeline.go b/database/pipeline/pipeline.go index c900123ae..0860a64f6 100644 --- a/database/pipeline/pipeline.go +++ b/database/pipeline/pipeline.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( diff --git a/database/pipeline/pipeline_test.go b/database/pipeline/pipeline_test.go index 01582a62a..e748b21f1 100644 --- a/database/pipeline/pipeline_test.go +++ b/database/pipeline/pipeline_test.go @@ -9,9 +9,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -174,28 +172,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testPipeline is a test helper function to create a library -// Pipeline type with all fields set to their zero values. -func testPipeline() *library.Pipeline { - return &library.Pipeline{ - ID: new(int64), - RepoID: new(int64), - Commit: new(string), - Flavor: new(string), - Platform: new(string), - Ref: new(string), - Type: new(string), - Version: new(string), - ExternalSecrets: new(bool), - InternalSecrets: new(bool), - Services: new(bool), - Stages: new(bool), - Steps: new(bool), - Templates: new(bool), - Data: new([]byte), - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/pipeline/update.go b/database/pipeline/update.go index 5b12db3b1..37353d853 100644 --- a/database/pipeline/update.go +++ b/database/pipeline/update.go @@ -5,10 +5,11 @@ package pipeline import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdatePipeline updates an existing pipeline in the database. diff --git a/database/pipeline/update_test.go b/database/pipeline/update_test.go index a489913ab..bb0464550 100644 --- a/database/pipeline/update_test.go +++ b/database/pipeline/update_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestPipeline_Engine_UpdatePipeline(t *testing.T) { // setup types - _pipeline := testPipeline() + _pipeline := testutils.APIPipeline() _pipeline.SetID(1) _pipeline.SetRepoID(1) _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") diff --git a/database/repo/count.go b/database/repo/count.go index c00001b9f..165bf0f0b 100644 --- a/database/repo/count.go +++ b/database/repo/count.go @@ -10,7 +10,7 @@ import ( // CountRepos gets the count of all repos from the database. func (e *engine) CountRepos(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all repos from the database") + e.logger.Tracef("getting count of all repos") // variable to store query results var r int64 diff --git a/database/repo/count_org.go b/database/repo/count_org.go index 145042b55..6e0ce6cb4 100644 --- a/database/repo/count_org.go +++ b/database/repo/count_org.go @@ -5,15 +5,16 @@ package repo import ( "context" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // CountReposForOrg gets the count of repos by org name from the database. func (e *engine) CountReposForOrg(ctx context.Context, org string, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": org, - }).Tracef("getting count of repos for org %s from the database", org) + }).Tracef("getting count of repos for org %s", org) // variable to store query results var r int64 diff --git a/database/repo/count_org_test.go b/database/repo/count_org_test.go index c89299773..1521e3025 100644 --- a/database/repo/count_org_test.go +++ b/database/repo/count_org_test.go @@ -8,22 +8,24 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestRepo_Engine_CountReposForOrg(t *testing.T) { // setup types - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.GetOwner().SetID(1) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") _repoOne.SetFullName("foo/bar") _repoOne.SetVisibility("public") - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.GetOwner().SetID(1) _repoTwo.SetHash("baz") _repoTwo.SetOrg("bar") _repoTwo.SetName("foo") diff --git a/database/repo/count_test.go b/database/repo/count_test.go index c399411d6..4803e7ea9 100644 --- a/database/repo/count_test.go +++ b/database/repo/count_test.go @@ -8,22 +8,24 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestRepo_Engine_CountRepos(t *testing.T) { // setup types - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.GetOwner().SetID(1) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") _repoOne.SetFullName("foo/bar") _repoOne.SetVisibility("public") - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.GetOwner().SetID(1) _repoTwo.SetHash("baz") _repoTwo.SetOrg("bar") _repoTwo.SetName("foo") diff --git a/database/repo/count_user.go b/database/repo/count_user.go index 5cd71f2f0..dfc002d9b 100644 --- a/database/repo/count_user.go +++ b/database/repo/count_user.go @@ -5,16 +5,17 @@ package repo import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountReposForUser gets the count of repos by user ID from the database. -func (e *engine) CountReposForUser(ctx context.Context, u *library.User, filters map[string]interface{}) (int64, error) { +func (e *engine) CountReposForUser(ctx context.Context, u *api.User, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), - }).Tracef("getting count of repos for user %s from the database", u.GetName()) + }).Tracef("getting count of repos for user %s", u.GetName()) // variable to store query results var r int64 diff --git a/database/repo/count_user_test.go b/database/repo/count_user_test.go index a2b580a52..543f13a1c 100644 --- a/database/repo/count_user_test.go +++ b/database/repo/count_user_test.go @@ -8,34 +8,34 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + "github.com/go-vela/server/database/testutils" ) func TestRepo_Engine_CountReposForUser(t *testing.T) { // setup types - _repoOne := testRepo() + _user := testutils.APIUser() + _user.SetID(1) + _user.SetName("foo") + + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_user) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") _repoOne.SetFullName("foo/bar") _repoOne.SetVisibility("public") - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_user) _repoTwo.SetHash("baz") _repoTwo.SetOrg("bar") _repoTwo.SetName("foo") _repoTwo.SetFullName("bar/foo") _repoTwo.SetVisibility("public") - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() diff --git a/database/repo/create.go b/database/repo/create.go index 578b54fb6..166362de9 100644 --- a/database/repo/create.go +++ b/database/repo/create.go @@ -7,35 +7,30 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CreateRepo creates a new repo in the database. -func (e *engine) CreateRepo(ctx context.Context, r *library.Repo) (*library.Repo, error) { +func (e *engine) CreateRepo(ctx context.Context, r *api.Repo) (*api.Repo, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("creating repo %s in the database", r.GetFullName()) + }).Tracef("creating repo %s", r.GetFullName()) // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary - repo := database.RepoFromLibrary(r) + repo := types.RepoFromAPI(r) // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate err := repo.Validate() if err != nil { return nil, err } // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt err = repo.Encrypt(e.config.EncryptionKey) if err != nil { return nil, fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) @@ -52,9 +47,11 @@ func (e *engine) CreateRepo(ctx context.Context, r *library.Repo) (*library.Repo if err != nil { // only log to preserve backwards compatibility e.logger.Errorf("unable to decrypt repo %d: %v", r.GetID(), err) - - return repo.ToLibrary(), nil } - return repo.ToLibrary(), nil + // set owner to provided owner if creation successful + result := repo.ToAPI() + result.SetOwner(r.GetOwner()) + + return result, nil } diff --git a/database/repo/create_test.go b/database/repo/create_test.go index 189622744..07b8204ab 100644 --- a/database/repo/create_test.go +++ b/database/repo/create_test.go @@ -8,13 +8,15 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/google/go-cmp/cmp" + + "github.com/go-vela/server/database/testutils" ) func TestRepo_Engine_CreateRepo(t *testing.T) { // setup types - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -32,9 +34,9 @@ func TestRepo_Engine_CreateRepo(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "repos" -("user_id","hash","org","name","full_name","link","clone","branch","topics","build_limit","timeout","counter","visibility","private","trusted","active","allow_pull","allow_push","allow_deploy","allow_tag","allow_comment","allow_events","pipeline_type","previous_name","approve_build","id") -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26) RETURNING "id"`). - WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, nil, "yaml", "oldName", nil, 1). +("user_id","hash","org","name","full_name","link","clone","branch","topics","build_limit","timeout","counter","visibility","private","trusted","active","allow_events","pipeline_type","previous_name","approve_build","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21) RETURNING "id"`). + WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, nil, "yaml", "oldName", nil, 1). WillReturnRows(_rows) _sqlite := testSqlite(t) @@ -75,7 +77,7 @@ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$ t.Errorf("CreateRepo for %s returned err: %v", test.name, err) } - if diff := cmp.Diff(got, _repo); diff != "" { + if diff := cmp.Diff(_repo, got); diff != "" { t.Errorf("CreateRepo mismatch (-want +got):\n%s", diff) } }) diff --git a/database/repo/delete.go b/database/repo/delete.go index 6432e4ce1..a12e2ec91 100644 --- a/database/repo/delete.go +++ b/database/repo/delete.go @@ -5,23 +5,22 @@ package repo import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // DeleteRepo deletes an existing repo from the database. -func (e *engine) DeleteRepo(ctx context.Context, r *library.Repo) error { +func (e *engine) DeleteRepo(ctx context.Context, r *api.Repo) error { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("deleting repo %s from the database", r.GetFullName()) + }).Tracef("deleting repo %s", r.GetFullName()) // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary - repo := database.RepoFromLibrary(r) + repo := types.RepoFromAPI(r) // send query to the database return e.client. diff --git a/database/repo/delete_test.go b/database/repo/delete_test.go index 30ff1d84c..9326c62b6 100644 --- a/database/repo/delete_test.go +++ b/database/repo/delete_test.go @@ -7,13 +7,15 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestRepo_Engine_DeleteRepo(t *testing.T) { // setup types - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") diff --git a/database/repo/get.go b/database/repo/get.go index af9db8e1c..d29ba16c8 100644 --- a/database/repo/get.go +++ b/database/repo/get.go @@ -5,21 +5,22 @@ package repo import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // GetRepo gets a repo by ID from the database. -func (e *engine) GetRepo(ctx context.Context, id int64) (*library.Repo, error) { - e.logger.Tracef("getting repo %d from the database", id) +func (e *engine) GetRepo(ctx context.Context, id int64) (*api.Repo, error) { + e.logger.Tracef("getting repo %d", id) // variable to store query results - r := new(database.Repo) + r := new(types.Repo) // send query to the database and store result in variable err := e.client. Table(constants.TableRepo). + Preload("Owner"). Where("id = ?", id). Take(r). Error @@ -42,11 +43,11 @@ func (e *engine) GetRepo(ctx context.Context, id int64) (*library.Repo, error) { // return the unencrypted repo // // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - return r.ToLibrary(), nil + return r.ToAPI(), nil } // return the decrypted repo // // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - return r.ToLibrary(), nil + return r.ToAPI(), nil } diff --git a/database/repo/get_org.go b/database/repo/get_org.go index 2a1d053ea..e66b97b37 100644 --- a/database/repo/get_org.go +++ b/database/repo/get_org.go @@ -5,25 +5,27 @@ package repo import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // GetRepoForOrg gets a repo by org and repo name from the database. -func (e *engine) GetRepoForOrg(ctx context.Context, org, name string) (*library.Repo, error) { +func (e *engine) GetRepoForOrg(ctx context.Context, org, name string) (*api.Repo, error) { e.logger.WithFields(logrus.Fields{ "org": org, "repo": name, - }).Tracef("getting repo %s/%s from the database", org, name) + }).Tracef("getting repo %s/%s", org, name) // variable to store query results - r := new(database.Repo) + r := new(types.Repo) // send query to the database and store result in variable err := e.client. Table(constants.TableRepo). + Preload("Owner"). Where("org = ?", org). Where("name = ?", name). Take(r). @@ -33,8 +35,6 @@ func (e *engine) GetRepoForOrg(ctx context.Context, org, name string) (*library. } // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt err = r.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -45,13 +45,9 @@ func (e *engine) GetRepoForOrg(ctx context.Context, org, name string) (*library. e.logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err) // return the unencrypted repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - return r.ToLibrary(), nil + return r.ToAPI(), nil } // return the decrypted repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - return r.ToLibrary(), nil + return r.ToAPI(), nil } diff --git a/database/repo/get_org_test.go b/database/repo/get_org_test.go index 95e6d6a32..1570398c0 100644 --- a/database/repo/get_org_test.go +++ b/database/repo/get_org_test.go @@ -4,18 +4,21 @@ package repo import ( "context" - "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestRepo_Engine_GetRepoForOrg(t *testing.T) { // setup types - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -23,18 +26,30 @@ func TestRepo_Engine_GetRepoForOrg(t *testing.T) { _repo.SetVisibility("public") _repo.SetPipelineType("yaml") _repo.SetTopics([]string{}) - _repo.SetAllowEvents(library.NewEventsFromMask(1)) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo.SetOwner(_owner) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", "", "") + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 AND name = $2 LIMIT $3`).WithArgs("foo", "bar", 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -44,12 +59,22 @@ func TestRepo_Engine_GetRepoForOrg(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Repo + want *api.Repo }{ { failure: false, @@ -82,8 +107,8 @@ func TestRepo_Engine_GetRepoForOrg(t *testing.T) { t.Errorf("GetRepoForOrg for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoForOrg for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(_repo, got); diff != "" { + t.Errorf("GetRepoForOrg mismatch (-want +got):\n%s", diff) } }) } diff --git a/database/repo/get_test.go b/database/repo/get_test.go index 7a4fcdba8..bab82c6f3 100644 --- a/database/repo/get_test.go +++ b/database/repo/get_test.go @@ -8,14 +8,17 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestRepo_Engine_GetRepo(t *testing.T) { // setup types - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -23,18 +26,30 @@ func TestRepo_Engine_GetRepo(t *testing.T) { _repo.SetVisibility("public") _repo.SetPipelineType("yaml") _repo.SetTopics([]string{}) - _repo.SetAllowEvents(library.NewEventsFromMask(1)) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repo.SetOwner(_owner) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", "", "") + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, 1, "yaml", "", "") + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -44,12 +59,22 @@ func TestRepo_Engine_GetRepo(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Repo + want *api.Repo }{ { failure: false, diff --git a/database/repo/index.go b/database/repo/index.go index c2446bda9..9c97ad962 100644 --- a/database/repo/index.go +++ b/database/repo/index.go @@ -17,7 +17,7 @@ ON repos (org, name); // CreateRepoIndexes creates the indexes for the repos table in the database. func (e *engine) CreateRepoIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for repos table in the database") + e.logger.Tracef("creating indexes for repos table") // create the org and name columns index for the repos table return e.client.Exec(CreateOrgNameIndex).Error diff --git a/database/repo/interface.go b/database/repo/interface.go index a69b5fa31..8c4a4e466 100644 --- a/database/repo/interface.go +++ b/database/repo/interface.go @@ -5,7 +5,7 @@ package repo import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) // RepoInterface represents the Vela interface for repo @@ -31,21 +31,21 @@ type RepoInterface interface { // CountReposForOrg defines a function that gets the count of repos by org name. CountReposForOrg(context.Context, string, map[string]interface{}) (int64, error) // CountReposForUser defines a function that gets the count of repos by user ID. - CountReposForUser(context.Context, *library.User, map[string]interface{}) (int64, error) + CountReposForUser(context.Context, *api.User, map[string]interface{}) (int64, error) // CreateRepo defines a function that creates a new repo. - CreateRepo(context.Context, *library.Repo) (*library.Repo, error) + CreateRepo(context.Context, *api.Repo) (*api.Repo, error) // DeleteRepo defines a function that deletes an existing repo. - DeleteRepo(context.Context, *library.Repo) error + DeleteRepo(context.Context, *api.Repo) error // GetRepo defines a function that gets a repo by ID. - GetRepo(context.Context, int64) (*library.Repo, error) + GetRepo(context.Context, int64) (*api.Repo, error) // GetRepoForOrg defines a function that gets a repo by org and repo name. - GetRepoForOrg(context.Context, string, string) (*library.Repo, error) + GetRepoForOrg(context.Context, string, string) (*api.Repo, error) // ListRepos defines a function that gets a list of all repos. - ListRepos(context.Context) ([]*library.Repo, error) + ListRepos(context.Context) ([]*api.Repo, error) // ListReposForOrg defines a function that gets a list of repos by org name. - ListReposForOrg(context.Context, string, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + ListReposForOrg(context.Context, string, string, map[string]interface{}, int, int) ([]*api.Repo, int64, error) // ListReposForUser defines a function that gets a list of repos by user ID. - ListReposForUser(context.Context, *library.User, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + ListReposForUser(context.Context, *api.User, string, map[string]interface{}, int, int) ([]*api.Repo, int64, error) // UpdateRepo defines a function that updates an existing repo. - UpdateRepo(context.Context, *library.Repo) (*library.Repo, error) + UpdateRepo(context.Context, *api.Repo) (*api.Repo, error) } diff --git a/database/repo/list.go b/database/repo/list.go index 790227d63..abe27a434 100644 --- a/database/repo/list.go +++ b/database/repo/list.go @@ -5,19 +5,19 @@ package repo import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListRepos gets a list of all repos from the database. -func (e *engine) ListRepos(ctx context.Context) ([]*library.Repo, error) { - e.logger.Trace("listing all repos from the database") +func (e *engine) ListRepos(ctx context.Context) ([]*api.Repo, error) { + e.logger.Trace("listing all repos") // variables to store query results and return value count := int64(0) - r := new([]database.Repo) - repos := []*library.Repo{} + r := new([]types.Repo) + repos := []*api.Repo{} // count the results count, err := e.CountRepos(ctx) @@ -33,6 +33,7 @@ func (e *engine) ListRepos(ctx context.Context) ([]*library.Repo, error) { // send query to the database and store result in variable err = e.client. Table(constants.TableRepo). + Preload("Owner"). Find(&r). Error if err != nil { @@ -45,8 +46,6 @@ func (e *engine) ListRepos(ctx context.Context) ([]*library.Repo, error) { tmp := repo // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt err = tmp.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -58,9 +57,7 @@ func (e *engine) ListRepos(ctx context.Context) ([]*library.Repo, error) { } // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - repos = append(repos, tmp.ToLibrary()) + repos = append(repos, tmp.ToAPI()) } return repos, nil diff --git a/database/repo/list_org.go b/database/repo/list_org.go index 95783bd95..016044744 100644 --- a/database/repo/list_org.go +++ b/database/repo/list_org.go @@ -5,24 +5,25 @@ package repo import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // ListReposForOrg gets a list of repos by org name from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { +func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filters map[string]interface{}, page, perPage int) ([]*api.Repo, int64, error) { e.logger.WithFields(logrus.Fields{ "org": org, - }).Tracef("listing repos for org %s from the database", org) + }).Tracef("listing repos for org %s", org) // variables to store query results and return values count := int64(0) - r := new([]database.Repo) - repos := []*library.Repo{} + r := new([]types.Repo) + repos := []*api.Repo{} // count the results count, err := e.CountReposForOrg(ctx, org, filters) @@ -49,6 +50,7 @@ func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filter err = e.client. Table(constants.TableRepo). + Preload("Owner"). Select("repos.*"). Joins("LEFT JOIN (?) t on repos.id = t.id", query). Order("latest_build DESC NULLS LAST"). @@ -64,6 +66,7 @@ func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filter default: err = e.client. Table(constants.TableRepo). + Preload("Owner"). Where("org = ?", org). Where(filters). Order("name"). @@ -82,8 +85,6 @@ func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filter tmp := repo // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt err = tmp.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -94,10 +95,8 @@ func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filter e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) } - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - repos = append(repos, tmp.ToLibrary()) + // convert query result to API type + repos = append(repos, tmp.ToAPI()) } return repos, count, nil diff --git a/database/repo/list_org_test.go b/database/repo/list_org_test.go index 5d6bfb99f..9a7bb5aa6 100644 --- a/database/repo/list_org_test.go +++ b/database/repo/list_org_test.go @@ -4,33 +4,28 @@ package repo import ( "context" - "reflect" "testing" "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) func TestRepo_Engine_ListReposForOrg(t *testing.T) { // setup types - _buildOne := new(library.Build) - _buildOne.SetID(1) - _buildOne.SetRepoID(1) - _buildOne.SetNumber(1) - _buildOne.SetCreated(time.Now().UTC().Unix()) + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _buildTwo := new(library.Build) - _buildTwo.SetID(2) - _buildTwo.SetRepoID(2) - _buildTwo.SetNumber(1) - _buildTwo.SetCreated(time.Now().UTC().Unix()) - - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_owner) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") @@ -38,11 +33,11 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { _repoOne.SetVisibility("public") _repoOne.SetPipelineType("yaml") _repoOne.SetTopics([]string{}) - _repoOne.SetAllowEvents(library.NewEventsFromMask(1)) + _repoOne.SetAllowEvents(api.NewEventsFromMask(1)) - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_owner) _repoTwo.SetHash("bar") _repoTwo.SetOrg("foo") _repoTwo.SetName("baz") @@ -50,7 +45,19 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { _repoTwo.SetVisibility("public") _repoTwo.SetPipelineType("yaml") _repoTwo.SetTopics([]string{}) - _repoTwo.SetAllowEvents(library.NewEventsFromMask(1)) + _repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) + + _buildOne := new(api.Build) + _buildOne.SetID(1) + _buildOne.SetRepo(_repoOne) + _buildOne.SetNumber(1) + _buildOne.SetCreated(time.Now().UTC().Unix()) + + _buildTwo := new(api.Build) + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repoTwo) + _buildTwo.SetNumber(1) + _buildTwo.SetCreated(time.Now().UTC().Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -63,12 +70,17 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil). - AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil) + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 ORDER BY name LIMIT $2`).WithArgs("foo", 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) // create expected latest count query result in mock _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) @@ -78,12 +90,17 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { // create expected latest query result in mock _rows = sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil). - AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil) + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil) + + _userRows = sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the latest query _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.org = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT $2`).WithArgs("foo", 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -98,17 +115,27 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Build{}) + err = _sqlite.client.Migrator().CreateTable(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.Migrator().CreateTable(&types.Build{}) if err != nil { t.Errorf("unable to create build table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error + err = _sqlite.client.Table(constants.TableBuild).Create(types.BuildFromAPI(_buildOne)).Error if err != nil { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error + err = _sqlite.client.Table(constants.TableBuild).Create(types.BuildFromAPI(_buildTwo)).Error if err != nil { t.Errorf("unable to create test build for sqlite: %v", err) } @@ -119,35 +146,35 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { name string sort string database *engine - want []*library.Repo + want []*api.Repo }{ { failure: false, name: "postgres with name", database: _postgres, sort: "name", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "postgres with latest", database: _postgres, sort: "latest", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "sqlite with name", database: _sqlite, sort: "name", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "sqlite with latest", database: _sqlite, sort: "latest", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, } @@ -170,8 +197,8 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { t.Errorf("ListReposForOrg for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListReposForOrg for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListReposForOrg for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/repo/list_test.go b/database/repo/list_test.go index 793123257..a4f40dcbe 100644 --- a/database/repo/list_test.go +++ b/database/repo/list_test.go @@ -8,14 +8,23 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestRepo_Engine_ListRepos(t *testing.T) { // setup types - _repoOne := testRepo() + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") + + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_owner) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") @@ -23,11 +32,11 @@ func TestRepo_Engine_ListRepos(t *testing.T) { _repoOne.SetVisibility("public") _repoOne.SetPipelineType("yaml") _repoOne.SetTopics([]string{}) - _repoOne.SetAllowEvents(library.NewEventsFromMask(1)) + _repoOne.SetAllowEvents(api.NewEventsFromMask(1)) - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_owner) _repoTwo.SetHash("baz") _repoTwo.SetOrg("bar") _repoTwo.SetName("foo") @@ -35,7 +44,7 @@ func TestRepo_Engine_ListRepos(t *testing.T) { _repoTwo.SetVisibility("public") _repoTwo.SetPipelineType("yaml") _repoTwo.SetTopics([]string{}) - _repoTwo.SetAllowEvents(library.NewEventsFromMask(1)) + _repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -48,12 +57,17 @@ func TestRepo_Engine_ListRepos(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil). - AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil) + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "repos"`).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -68,24 +82,34 @@ func TestRepo_Engine_ListRepos(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Repo + want []*api.Repo }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, } diff --git a/database/repo/list_user.go b/database/repo/list_user.go index bc11fb1b2..f50374de1 100644 --- a/database/repo/list_user.go +++ b/database/repo/list_user.go @@ -5,24 +5,25 @@ package repo import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // ListReposForUser gets a list of repos by user ID from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { +func (e *engine) ListReposForUser(ctx context.Context, u *api.User, sortBy string, filters map[string]interface{}, page, perPage int) ([]*api.Repo, int64, error) { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), - }).Tracef("listing repos for user %s from the database", u.GetName()) + }).Tracef("listing repos for user %s", u.GetName()) // variables to store query results and return values count := int64(0) - r := new([]database.Repo) - repos := []*library.Repo{} + r := new([]types.Repo) + repos := []*api.Repo{} // count the results count, err := e.CountReposForUser(ctx, u, filters) @@ -49,6 +50,7 @@ func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy s err = e.client. Table(constants.TableRepo). + Preload("Owner"). Select("repos.*"). Joins("LEFT JOIN (?) t on repos.id = t.id", query). Order("latest_build DESC NULLS LAST"). @@ -64,6 +66,7 @@ func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy s default: err = e.client. Table(constants.TableRepo). + Preload("Owner"). Where("user_id = ?", u.GetID()). Where(filters). Order("name"). @@ -82,8 +85,6 @@ func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy s tmp := repo // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt err = tmp.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -95,9 +96,7 @@ func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy s } // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary - repos = append(repos, tmp.ToLibrary()) + repos = append(repos, tmp.ToAPI()) } return repos, count, nil diff --git a/database/repo/list_user_test.go b/database/repo/list_user_test.go index a387c4e5a..49261c95e 100644 --- a/database/repo/list_user_test.go +++ b/database/repo/list_user_test.go @@ -9,28 +9,23 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) func TestRepo_Engine_ListReposForUser(t *testing.T) { // setup types - _buildOne := new(library.Build) - _buildOne.SetID(1) - _buildOne.SetRepoID(1) - _buildOne.SetNumber(1) - _buildOne.SetCreated(time.Now().UTC().Unix()) - - _buildTwo := new(library.Build) - _buildTwo.SetID(2) - _buildTwo.SetRepoID(2) - _buildTwo.SetNumber(1) - _buildTwo.SetCreated(time.Now().UTC().Unix()) + _owner := testutils.APIUser().Crop() + _owner.SetID(1) + _owner.SetName("foo") + _owner.SetToken("bar") - _repoOne := testRepo() + _repoOne := testutils.APIRepo() _repoOne.SetID(1) - _repoOne.SetUserID(1) + _repoOne.SetOwner(_owner) _repoOne.SetHash("baz") _repoOne.SetOrg("foo") _repoOne.SetName("bar") @@ -38,11 +33,11 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { _repoOne.SetVisibility("public") _repoOne.SetPipelineType("yaml") _repoOne.SetTopics([]string{}) - _repoOne.SetAllowEvents(library.NewEventsFromMask(1)) + _repoOne.SetAllowEvents(api.NewEventsFromMask(1)) - _repoTwo := testRepo() + _repoTwo := testutils.APIRepo() _repoTwo.SetID(2) - _repoTwo.SetUserID(1) + _repoTwo.SetOwner(_owner) _repoTwo.SetHash("baz") _repoTwo.SetOrg("bar") _repoTwo.SetName("foo") @@ -50,12 +45,19 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { _repoTwo.SetVisibility("public") _repoTwo.SetPipelineType("yaml") _repoTwo.SetTopics([]string{}) - _repoTwo.SetAllowEvents(library.NewEventsFromMask(1)) + _repoTwo.SetAllowEvents(api.NewEventsFromMask(1)) + + _buildOne := new(api.Build) + _buildOne.SetID(1) + _buildOne.SetRepo(_repoOne) + _buildOne.SetNumber(1) + _buildOne.SetCreated(time.Now().UTC().Unix()) - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") + _buildTwo := new(api.Build) + _buildTwo.SetID(2) + _buildTwo.SetRepo(_repoTwo) + _buildTwo.SetNumber(1) + _buildTwo.SetCreated(time.Now().UTC().Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -68,12 +70,17 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil). - AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil) + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE user_id = $1 ORDER BY name LIMIT $2`).WithArgs(1, 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) // create expected latest count query result in mock _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) @@ -83,12 +90,17 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { // create expected latest query result in mock _rows = sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "allow_events", "pipeline_type", "previous_name", "approve_build"}). - AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil). - AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, 1, "yaml", nil, nil) + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, 1, "yaml", nil, nil) + + _userRows = sqlmock.NewRows( + []string{"id", "name", "token", "hash", "active", "admin"}). + AddRow(1, "foo", "bar", "baz", false, false) // ensure the mock expects the latest query _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.user_id = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT $2`).WithArgs(1, 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -103,17 +115,27 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } - err = _sqlite.client.AutoMigrate(&database.Build{}) + err = _sqlite.client.Migrator().CreateTable(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.client.Migrator().CreateTable(&types.Build{}) if err != nil { t.Errorf("unable to create build table for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error + err = _sqlite.client.Table(constants.TableBuild).Create(types.BuildFromAPI(_buildOne)).Error if err != nil { t.Errorf("unable to create test build for sqlite: %v", err) } - err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error + err = _sqlite.client.Table(constants.TableBuild).Create(types.BuildFromAPI(_buildTwo)).Error if err != nil { t.Errorf("unable to create test build for sqlite: %v", err) } @@ -124,35 +146,35 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { name string sort string database *engine - want []*library.Repo + want []*api.Repo }{ { failure: false, name: "postgres with name", database: _postgres, sort: "name", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "postgres with latest", database: _postgres, sort: "latest", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "sqlite with name", database: _sqlite, sort: "name", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, { failure: false, name: "sqlite with latest", database: _sqlite, sort: "latest", - want: []*library.Repo{_repoOne, _repoTwo}, + want: []*api.Repo{_repoOne, _repoTwo}, }, } @@ -161,7 +183,7 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, _, err := test.database.ListReposForUser(context.TODO(), _user, test.sort, filters, 1, 10) + got, _, err := test.database.ListReposForUser(context.TODO(), _owner, test.sort, filters, 1, 10) if test.failure { if err == nil { diff --git a/database/repo/opts.go b/database/repo/opts.go index f65dcc368..5dcf5be98 100644 --- a/database/repo/opts.go +++ b/database/repo/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/repo/opts_test.go b/database/repo/opts_test.go index b9788a457..8641eaf57 100644 --- a/database/repo/opts_test.go +++ b/database/repo/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/repo/repo.go b/database/repo/repo.go index 5e4300e19..2a915192f 100644 --- a/database/repo/repo.go +++ b/database/repo/repo.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -62,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating repo database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of repos table and indexes in the database") + e.logger.Warning("skipping creation of repos table and indexes") return e, nil } diff --git a/database/repo/repo_test.go b/database/repo/repo_test.go index 597ef1623..498940ebb 100644 --- a/database/repo/repo_test.go +++ b/database/repo/repo_test.go @@ -8,10 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" - "github.com/go-vela/types/library/actions" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -152,7 +149,10 @@ func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { func testSqlite(t *testing.T) *engine { _sqlite, err := gorm.Open( sqlite.Open("file::memory:?cache=shared"), - &gorm.Config{SkipDefaultTransaction: true}, + &gorm.Config{ + SkipDefaultTransaction: true, + DisableForeignKeyConstraintWhenMigrating: true, + }, ) if err != nil { t.Errorf("unable to create new sqlite database: %v", err) @@ -171,65 +171,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testRepo is a test helper function to create a library -// Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ - ID: new(int64), - UserID: new(int64), - BuildLimit: new(int64), - Timeout: new(int64), - Counter: new(int), - PipelineType: new(string), - Hash: new(string), - Org: new(string), - Name: new(string), - FullName: new(string), - Link: new(string), - Clone: new(string), - Branch: new(string), - Visibility: new(string), - PreviousName: new(string), - ApproveBuild: new(string), - Private: new(bool), - Trusted: new(bool), - Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), - AllowEvents: testEvents(), - } -} - -func testEvents() *library.Events { - return &library.Events{ - Push: &actions.Push{ - Branch: new(bool), - Tag: new(bool), - DeleteBranch: new(bool), - DeleteTag: new(bool), - }, - PullRequest: &actions.Pull{ - Opened: new(bool), - Edited: new(bool), - Synchronize: new(bool), - Reopened: new(bool), - }, - Deployment: &actions.Deploy{ - Created: new(bool), - }, - Comment: &actions.Comment{ - Created: new(bool), - Edited: new(bool), - }, - Schedule: &actions.Schedule{ - Run: new(bool), - }, - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/repo/table.go b/database/repo/table.go index c28b6412f..ec033f48b 100644 --- a/database/repo/table.go +++ b/database/repo/table.go @@ -31,11 +31,6 @@ repos ( private BOOLEAN, trusted BOOLEAN, active BOOLEAN, - allow_pull BOOLEAN, - allow_push BOOLEAN, - allow_deploy BOOLEAN, - allow_tag BOOLEAN, - allow_comment BOOLEAN, allow_events INTEGER, pipeline_type TEXT, previous_name VARCHAR(100), @@ -67,11 +62,6 @@ repos ( private BOOLEAN, trusted BOOLEAN, active BOOLEAN, - allow_pull BOOLEAN, - allow_push BOOLEAN, - allow_deploy BOOLEAN, - allow_tag BOOLEAN, - allow_comment BOOLEAN, allow_events INTEGER, pipeline_type TEXT, previous_name TEXT, @@ -84,7 +74,7 @@ repos ( // CreateRepoTable creates the repos table in the database. func (e *engine) CreateRepoTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating repos table in the database") + e.logger.Tracef("creating repos table") // handle the driver provided to create the table switch driver { diff --git a/database/repo/update.go b/database/repo/update.go index 0c3273ce2..9a11b8010 100644 --- a/database/repo/update.go +++ b/database/repo/update.go @@ -7,35 +7,30 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // UpdateRepo updates an existing repo in the database. -func (e *engine) UpdateRepo(ctx context.Context, r *library.Repo) (*library.Repo, error) { +func (e *engine) UpdateRepo(ctx context.Context, r *api.Repo) (*api.Repo, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("creating repo %s in the database", r.GetFullName()) + }).Tracef("creating repo %s", r.GetFullName()) // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary - repo := database.RepoFromLibrary(r) + repo := types.RepoFromAPI(r) // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate err := repo.Validate() if err != nil { return nil, err } // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt err = repo.Encrypt(e.config.EncryptionKey) if err != nil { return nil, fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) @@ -52,9 +47,11 @@ func (e *engine) UpdateRepo(ctx context.Context, r *library.Repo) (*library.Repo if err != nil { // only log to preserve backwards compatibility e.logger.Errorf("unable to decrypt repo %d: %v", r.GetID(), err) - - return repo.ToLibrary(), nil } - return repo.ToLibrary(), nil + // set owner to provided owner if creation successful + result := repo.ToAPI() + result.SetOwner(r.GetOwner()) + + return result, nil } diff --git a/database/repo/update_test.go b/database/repo/update_test.go index bf791a30d..e175a349a 100644 --- a/database/repo/update_test.go +++ b/database/repo/update_test.go @@ -8,15 +8,17 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" ) func TestRepo_Engine_UpdateRepo(t *testing.T) { // setup types - _repo := testRepo() + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -26,16 +28,16 @@ func TestRepo_Engine_UpdateRepo(t *testing.T) { _repo.SetPreviousName("oldName") _repo.SetApproveBuild(constants.ApproveForkAlways) _repo.SetTopics([]string{}) - _repo.SetAllowEvents(library.NewEventsFromMask(1)) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // ensure the mock expects the query _mock.ExpectExec(`UPDATE "repos" -SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"topics"=$9,"build_limit"=$10,"timeout"=$11,"counter"=$12,"visibility"=$13,"private"=$14,"trusted"=$15,"active"=$16,"allow_pull"=$17,"allow_push"=$18,"allow_deploy"=$19,"allow_tag"=$20,"allow_comment"=$21,"allow_events"=$22,"pipeline_type"=$23,"previous_name"=$24,"approve_build"=$25 -WHERE "id" = $26`). - WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, 1, "yaml", "oldName", constants.ApproveForkAlways, 1). +SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"topics"=$9,"build_limit"=$10,"timeout"=$11,"counter"=$12,"visibility"=$13,"private"=$14,"trusted"=$15,"active"=$16,"allow_events"=$17,"pipeline_type"=$18,"previous_name"=$19,"approve_build"=$20 +WHERE "id" = $21`). + WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, 1, "yaml", "oldName", constants.ApproveForkAlways, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/resource.go b/database/resource.go index 2e309dedf..c11341c64 100644 --- a/database/resource.go +++ b/database/resource.go @@ -6,30 +6,57 @@ import ( "context" "github.com/go-vela/server/database/build" + "github.com/go-vela/server/database/dashboard" "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/schedule" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/service" + "github.com/go-vela/server/database/settings" "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" ) // NewResources creates and returns the database agnostic engines for resources. +// +//nolint:funlen // ignore function length func (e *engine) NewResources(ctx context.Context) error { var err error + // create the database agnostic engine for settings + e.SettingsInterface, err = settings.New( + settings.WithContext(e.ctx), + settings.WithClient(e.client), + settings.WithLogger(e.logger), + settings.WithSkipCreation(e.config.SkipCreation), + ) + if err != nil { + return err + } + // create the database agnostic engine for builds e.BuildInterface, err = build.New( build.WithContext(e.ctx), build.WithClient(e.client), build.WithLogger(e.logger), build.WithSkipCreation(e.config.SkipCreation), + build.WithEncryptionKey(e.config.EncryptionKey), + ) + if err != nil { + return err + } + + e.DashboardInterface, err = dashboard.New( + dashboard.WithContext(e.ctx), + dashboard.WithClient(e.client), + dashboard.WithLogger(e.logger), + dashboard.WithSkipCreation(e.config.SkipCreation), ) if err != nil { return err @@ -70,6 +97,17 @@ func (e *engine) NewResources(ctx context.Context) error { return err } + // create the database agnostic engine for JWKs + e.JWKInterface, err = jwk.New( + jwk.WithContext(e.ctx), + jwk.WithClient(e.client), + jwk.WithLogger(e.logger), + jwk.WithSkipCreation(e.config.SkipCreation), + ) + if err != nil { + return err + } + // create the database agnostic engine for logs e.LogInterface, err = log.New( log.WithContext(e.ctx), @@ -110,6 +148,7 @@ func (e *engine) NewResources(ctx context.Context) error { e.ScheduleInterface, err = schedule.New( schedule.WithContext(e.ctx), schedule.WithClient(e.client), + schedule.WithEncryptionKey(e.config.EncryptionKey), schedule.WithLogger(e.logger), schedule.WithSkipCreation(e.config.SkipCreation), ) diff --git a/database/resource_test.go b/database/resource_test.go index 6f20fb4f5..cdc5dbaa3 100644 --- a/database/resource_test.go +++ b/database/resource_test.go @@ -7,16 +7,20 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/server/database/build" + "github.com/go-vela/server/database/dashboard" "github.com/go-vela/server/database/deployment" "github.com/go-vela/server/database/executable" "github.com/go-vela/server/database/hook" + "github.com/go-vela/server/database/jwk" "github.com/go-vela/server/database/log" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/schedule" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/service" + "github.com/go-vela/server/database/settings" "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" @@ -26,12 +30,16 @@ func TestDatabase_Engine_NewResources(t *testing.T) { _postgres, _mock := testPostgres(t) defer _postgres.Close() + // ensure the mock expects the settings queries + _mock.ExpectExec(settings.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the build queries _mock.ExpectExec(build.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(build.CreateCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(build.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(build.CreateSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(build.CreateStatusIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the dashboard queries + _mock.ExpectExec(dashboard.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the build executable queries _mock.ExpectExec(executable.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the deployment queries @@ -40,6 +48,8 @@ func TestDatabase_Engine_NewResources(t *testing.T) { // ensure the mock expects the hook queries _mock.ExpectExec(hook.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(hook.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the jwk queries + _mock.ExpectExec(jwk.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the log queries _mock.ExpectExec(log.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(log.CreateBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) diff --git a/database/schedule/count.go b/database/schedule/count.go index e86b99a41..8da93e69f 100644 --- a/database/schedule/count.go +++ b/database/schedule/count.go @@ -4,12 +4,13 @@ package schedule import ( "context" + "github.com/go-vela/types/constants" ) // CountSchedules gets the count of all schedules from the database. func (e *engine) CountSchedules(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all schedules from the database") + e.logger.Tracef("getting count of all schedules") // variable to store query results var s int64 diff --git a/database/schedule/count_active.go b/database/schedule/count_active.go index cc78e37a6..be2dd9b5e 100644 --- a/database/schedule/count_active.go +++ b/database/schedule/count_active.go @@ -4,12 +4,13 @@ package schedule import ( "context" + "github.com/go-vela/types/constants" ) // CountActiveSchedules gets the count of all active schedules from the database. func (e *engine) CountActiveSchedules(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all active schedules from the database") + e.logger.Tracef("getting count of all active schedules") // variable to store query results var s int64 diff --git a/database/schedule/count_active_test.go b/database/schedule/count_active_test.go index 6fb88849f..a94af19c6 100644 --- a/database/schedule/count_active_test.go +++ b/database/schedule/count_active_test.go @@ -4,36 +4,88 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_CountActiveSchedules(t *testing.T) { - _scheduleOne := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -95,8 +147,8 @@ func TestSchedule_Engine_CountActiveSchedules(t *testing.T) { t.Errorf("CountActiveSchedules for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("CountActiveSchedules for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("CountActiveSchedules for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/count_repo.go b/database/schedule/count_repo.go index 471537d8f..724ce19b3 100644 --- a/database/schedule/count_repo.go +++ b/database/schedule/count_repo.go @@ -4,17 +4,19 @@ package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountSchedulesForRepo gets the count of schedules by repo ID from the database. -func (e *engine) CountSchedulesForRepo(ctx context.Context, r *library.Repo) (int64, error) { +func (e *engine) CountSchedulesForRepo(ctx context.Context, r *api.Repo) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("getting count of schedules for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of schedules for repo %s", r.GetFullName()) // variable to store query results var s int64 diff --git a/database/schedule/count_repo_test.go b/database/schedule/count_repo_test.go index 9618f9cf8..2b95abd10 100644 --- a/database/schedule/count_repo_test.go +++ b/database/schedule/count_repo_test.go @@ -4,46 +4,94 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_CountSchedulesForRepo(t *testing.T) { - _repo := testRepo() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - - _scheduleOne := testSchedule() + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) + _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) + _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) // ensure the mock expects the query _mock.ExpectQuery(`SELECT count(*) FROM "schedules" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows) @@ -72,13 +120,13 @@ func TestSchedule_Engine_CountSchedulesForRepo(t *testing.T) { failure: false, name: "postgres", database: _postgres, - want: 1, + want: 2, }, { failure: false, name: "sqlite3", database: _sqlite, - want: 1, + want: 2, }, } @@ -99,8 +147,8 @@ func TestSchedule_Engine_CountSchedulesForRepo(t *testing.T) { t.Errorf("CountSchedulesForRepo for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("CountSchedulesForRepo for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("CountSchedulesForRepo for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/count_test.go b/database/schedule/count_test.go index 98090ab8f..13be42a30 100644 --- a/database/schedule/count_test.go +++ b/database/schedule/count_test.go @@ -4,34 +4,88 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_CountSchedules(t *testing.T) { - _scheduleOne := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) + _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) + _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -93,8 +147,8 @@ func TestSchedule_Engine_CountSchedules(t *testing.T) { t.Errorf("CountSchedules for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("CountSchedules for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("CountSchedules for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/create.go b/database/schedule/create.go index 9bc2079eb..9b349a7d1 100644 --- a/database/schedule/create.go +++ b/database/schedule/create.go @@ -1,25 +1,25 @@ // SPDX-License-Identifier: Apache-2.0 -//nolint:dupl // ignore similar code with update.go package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CreateSchedule creates a new schedule in the database. -func (e *engine) CreateSchedule(ctx context.Context, s *library.Schedule) (*library.Schedule, error) { +func (e *engine) CreateSchedule(ctx context.Context, s *api.Schedule) (*api.Schedule, error) { e.logger.WithFields(logrus.Fields{ "schedule": s.GetName(), }).Tracef("creating schedule %s in the database", s.GetName()) - // cast the library type to database type - schedule := database.ScheduleFromLibrary(s) + // cast the API type to database type + schedule := types.ScheduleFromAPI(s) // validate the necessary fields are populated err := schedule.Validate() @@ -28,7 +28,14 @@ func (e *engine) CreateSchedule(ctx context.Context, s *library.Schedule) (*libr } // send query to the database - result := e.client.Table(constants.TableSchedule).Create(schedule) + err = e.client.Table(constants.TableSchedule).Create(schedule).Error + if err != nil { + return nil, err + } + + // set repo to provided repo if creation successful + result := schedule.ToAPI() + result.SetRepo(s.GetRepo()) - return schedule.ToLibrary(), result.Error + return result, nil } diff --git a/database/schedule/create_test.go b/database/schedule/create_test.go index 7a2a42cc2..ac028099e 100644 --- a/database/schedule/create_test.go +++ b/database/schedule/create_test.go @@ -4,23 +4,70 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_CreateSchedule(t *testing.T) { - _schedule := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -30,9 +77,9 @@ func TestSchedule_Engine_CreateSchedule(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "schedules" -("repo_id","active","name","entry","created_at","created_by","updated_at","updated_by","scheduled_at","branch","id") -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`). - WithArgs(1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main", 1). +("repo_id","active","name","entry","created_at","created_by","updated_at","updated_by","scheduled_at","branch","error","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING "id"`). + WithArgs(1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided", 1). WillReturnRows(_rows) _sqlite := testSqlite(t) @@ -73,8 +120,8 @@ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`). t.Errorf("CreateSchedule for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, _schedule) { - t.Errorf("CreateSchedule for %s returned %s, want %s", test.name, got, _schedule) + if diff := cmp.Diff(_schedule, got); diff != "" { + t.Errorf("CreateSchedule for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/delete.go b/database/schedule/delete.go index 66e489d25..bc63c2f10 100644 --- a/database/schedule/delete.go +++ b/database/schedule/delete.go @@ -4,20 +4,22 @@ package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // DeleteSchedule deletes an existing schedule from the database. -func (e *engine) DeleteSchedule(ctx context.Context, s *library.Schedule) error { +func (e *engine) DeleteSchedule(ctx context.Context, s *api.Schedule) error { e.logger.WithFields(logrus.Fields{ "schedule": s.GetName(), }).Tracef("deleting schedule %s in the database", s.GetName()) - // cast the library type to database type - schedule := database.ScheduleFromLibrary(s) + // cast the API type to database type + schedule := types.ScheduleFromAPI(s) // send query to the database return e.client. diff --git a/database/schedule/delete_test.go b/database/schedule/delete_test.go index 7a0a8ae76..971377d92 100644 --- a/database/schedule/delete_test.go +++ b/database/schedule/delete_test.go @@ -5,21 +5,68 @@ package schedule import ( "context" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_DeleteSchedule(t *testing.T) { - _schedule := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() diff --git a/database/schedule/get.go b/database/schedule/get.go index 9f352d72b..1bcdc37cb 100644 --- a/database/schedule/get.go +++ b/database/schedule/get.go @@ -4,21 +4,24 @@ package schedule import ( "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // GetSchedule gets a schedule by ID from the database. -func (e *engine) GetSchedule(ctx context.Context, id int64) (*library.Schedule, error) { - e.logger.Tracef("getting schedule %d from the database", id) +func (e *engine) GetSchedule(ctx context.Context, id int64) (*api.Schedule, error) { + e.logger.Tracef("getting schedule %d", id) // variable to store query results - s := new(database.Schedule) + s := new(types.Schedule) // send query to the database and store result in variable err := e.client. Table(constants.TableSchedule). + Preload("Repo"). + Preload("Repo.Owner"). Where("id = ?", id). Take(s). Error @@ -26,5 +29,11 @@ func (e *engine) GetSchedule(ctx context.Context, id int64) (*library.Schedule, return nil, err } - return s.ToLibrary(), nil + // decrypt hash value for repo + err = s.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %d: %v", s.Repo.ID.Int64, err) + } + + return s.ToAPI(), nil } diff --git a/database/schedule/get_repo.go b/database/schedule/get_repo.go index 6ee2e6bb7..e940694a4 100644 --- a/database/schedule/get_repo.go +++ b/database/schedule/get_repo.go @@ -4,26 +4,30 @@ package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // GetScheduleForRepo gets a schedule by repo ID and name from the database. -func (e *engine) GetScheduleForRepo(ctx context.Context, r *library.Repo, name string) (*library.Schedule, error) { +func (e *engine) GetScheduleForRepo(ctx context.Context, r *api.Repo, name string) (*api.Schedule, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), "schedule": name, - }).Tracef("getting schedule %s/%s from the database", r.GetFullName(), name) + }).Tracef("getting schedule %s/%s", r.GetFullName(), name) // variable to store query results - s := new(database.Schedule) + s := new(types.Schedule) // send query to the database and store result in variable err := e.client. Table(constants.TableSchedule). + Preload("Repo"). + Preload("Repo.Owner"). Where("repo_id = ?", r.GetID()). Where("name = ?", name). Take(s). @@ -32,5 +36,11 @@ func (e *engine) GetScheduleForRepo(ctx context.Context, r *library.Repo, name s return nil, err } - return s.ToLibrary(), nil + // decrypt hash value for repo + err = s.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %d: %v", s.Repo.ID.Int64, err) + } + + return s.ToAPI(), nil } diff --git a/database/schedule/get_repo_test.go b/database/schedule/get_repo_test.go index 3745976b7..704443112 100644 --- a/database/schedule/get_repo_test.go +++ b/database/schedule/get_repo_test.go @@ -4,41 +4,92 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_GetScheduleForRepo(t *testing.T) { - _repo := testRepo() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - - _schedule := testSchedule() + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}, - ).AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main") + []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch", "error"}). + AddRow(1, 1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided") + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy", "github", "octocat", "github/octocat", "https://github.com/github/octocat", "https://github.com/github/octocat.git", "main", "{cloud,security}", 10, 30, 0, "public", false, false, true, 1, "", "", constants.ApproveNever) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "refresh_token", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "octocat", "superSecretToken", "superSecretRefreshToken", "{}", true, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE repo_id = $1 AND name = $2 LIMIT $3`).WithArgs(1, "nightly", 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -48,12 +99,32 @@ func TestSchedule_Engine_GetScheduleForRepo(t *testing.T) { t.Errorf("unable to create test schedule for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Schedule + want *api.Schedule }{ { failure: false, @@ -86,8 +157,8 @@ func TestSchedule_Engine_GetScheduleForRepo(t *testing.T) { t.Errorf("GetScheduleForRepo for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetScheduleForRepo for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("GetScheduleForRepo for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/get_test.go b/database/schedule/get_test.go index d229f8cf1..5d6f44fca 100644 --- a/database/schedule/get_test.go +++ b/database/schedule/get_test.go @@ -4,35 +4,92 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_GetSchedule(t *testing.T) { - _schedule := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}, - ).AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main") + []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch", "error"}). + AddRow(1, 1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided") + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy", "github", "octocat", "github/octocat", "https://github.com/github/octocat", "https://github.com/github/octocat.git", "main", "{cloud,security}", 10, 30, 0, "public", false, false, true, 1, "", "", constants.ApproveNever) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "refresh_token", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "octocat", "superSecretToken", "superSecretRefreshToken", "{}", true, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -42,12 +99,32 @@ func TestSchedule_Engine_GetSchedule(t *testing.T) { t.Errorf("unable to create test schedule for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want *library.Schedule + want *api.Schedule }{ { failure: false, @@ -80,8 +157,8 @@ func TestSchedule_Engine_GetSchedule(t *testing.T) { t.Errorf("GetSchedule for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetSchedule for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("GetSchedule for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/interface.go b/database/schedule/interface.go index 8d80a7c36..f4d284b45 100644 --- a/database/schedule/interface.go +++ b/database/schedule/interface.go @@ -5,7 +5,7 @@ package schedule import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) // ScheduleInterface represents the Vela interface for schedule @@ -29,21 +29,21 @@ type ScheduleInterface interface { // CountSchedules defines a function that gets the count of all schedules. CountSchedules(context.Context) (int64, error) // CountSchedulesForRepo defines a function that gets the count of schedules by repo ID. - CountSchedulesForRepo(context.Context, *library.Repo) (int64, error) + CountSchedulesForRepo(context.Context, *api.Repo) (int64, error) // CreateSchedule defines a function that creates a new schedule. - CreateSchedule(context.Context, *library.Schedule) (*library.Schedule, error) + CreateSchedule(context.Context, *api.Schedule) (*api.Schedule, error) // DeleteSchedule defines a function that deletes an existing schedule. - DeleteSchedule(context.Context, *library.Schedule) error + DeleteSchedule(context.Context, *api.Schedule) error // GetSchedule defines a function that gets a schedule by ID. - GetSchedule(context.Context, int64) (*library.Schedule, error) + GetSchedule(context.Context, int64) (*api.Schedule, error) // GetScheduleForRepo defines a function that gets a schedule by repo ID and name. - GetScheduleForRepo(context.Context, *library.Repo, string) (*library.Schedule, error) + GetScheduleForRepo(context.Context, *api.Repo, string) (*api.Schedule, error) // ListActiveSchedules defines a function that gets a list of all active schedules. - ListActiveSchedules(context.Context) ([]*library.Schedule, error) + ListActiveSchedules(context.Context) ([]*api.Schedule, error) // ListSchedules defines a function that gets a list of all schedules. - ListSchedules(context.Context) ([]*library.Schedule, error) + ListSchedules(context.Context) ([]*api.Schedule, error) // ListSchedulesForRepo defines a function that gets a list of schedules by repo ID. - ListSchedulesForRepo(context.Context, *library.Repo, int, int) ([]*library.Schedule, int64, error) + ListSchedulesForRepo(context.Context, *api.Repo, int, int) ([]*api.Schedule, int64, error) // UpdateSchedule defines a function that updates an existing schedule. - UpdateSchedule(context.Context, *library.Schedule, bool) (*library.Schedule, error) + UpdateSchedule(context.Context, *api.Schedule, bool) (*api.Schedule, error) } diff --git a/database/schedule/list.go b/database/schedule/list.go index 4904a1149..4282f554a 100644 --- a/database/schedule/list.go +++ b/database/schedule/list.go @@ -4,19 +4,20 @@ package schedule import ( "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListSchedules gets a list of all schedules from the database. -func (e *engine) ListSchedules(ctx context.Context) ([]*library.Schedule, error) { - e.logger.Trace("listing all schedules from the database") +func (e *engine) ListSchedules(ctx context.Context) ([]*api.Schedule, error) { + e.logger.Trace("listing all schedules") // variables to store query results and return value count := int64(0) - s := new([]database.Schedule) - schedules := []*library.Schedule{} + s := new([]types.Schedule) + schedules := []*api.Schedule{} // count the results count, err := e.CountSchedules(ctx) @@ -32,6 +33,8 @@ func (e *engine) ListSchedules(ctx context.Context) ([]*library.Schedule, error) // send query to the database and store result in variable err = e.client. Table(constants.TableSchedule). + Preload("Repo"). + Preload("Repo.Owner"). Find(&s). Error if err != nil { @@ -43,8 +46,14 @@ func (e *engine) ListSchedules(ctx context.Context) ([]*library.Schedule, error) // https://golang.org/doc/faq#closures_and_goroutines tmp := schedule + // decrypt hash value for repo + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) + } + // convert query result to API type - schedules = append(schedules, tmp.ToLibrary()) + schedules = append(schedules, tmp.ToAPI()) } return schedules, nil diff --git a/database/schedule/list_active.go b/database/schedule/list_active.go index 07616a7de..84e575b1a 100644 --- a/database/schedule/list_active.go +++ b/database/schedule/list_active.go @@ -4,19 +4,20 @@ package schedule import ( "context" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListActiveSchedules gets a list of all active schedules from the database. -func (e *engine) ListActiveSchedules(ctx context.Context) ([]*library.Schedule, error) { - e.logger.Trace("listing all active schedules from the database") +func (e *engine) ListActiveSchedules(ctx context.Context) ([]*api.Schedule, error) { + e.logger.Trace("listing all active schedules") // variables to store query results and return value count := int64(0) - s := new([]database.Schedule) - schedules := []*library.Schedule{} + s := new([]types.Schedule) + schedules := []*api.Schedule{} // count the results count, err := e.CountActiveSchedules(ctx) @@ -32,6 +33,8 @@ func (e *engine) ListActiveSchedules(ctx context.Context) ([]*library.Schedule, // send query to the database and store result in variable err = e.client. Table(constants.TableSchedule). + Preload("Repo"). + Preload("Repo.Owner"). Where("active = ?", true). Find(&s). Error @@ -44,8 +47,14 @@ func (e *engine) ListActiveSchedules(ctx context.Context) ([]*library.Schedule, // https://golang.org/doc/faq#closures_and_goroutines tmp := schedule + // decrypt hash value for repo + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.Repo.ID.Int64, err) + } + // convert query result to API type - schedules = append(schedules, tmp.ToLibrary()) + schedules = append(schedules, tmp.ToAPI()) } return schedules, nil diff --git a/database/schedule/list_active_test.go b/database/schedule/list_active_test.go index 7c604f10f..385cdf595 100644 --- a/database/schedule/list_active_test.go +++ b/database/schedule/list_active_test.go @@ -4,54 +4,116 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_ListActiveSchedules(t *testing.T) { - _scheduleOne := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) // ensure the mock expects the query _mock.ExpectQuery(`SELECT count(*) FROM "schedules" WHERE active = $1`).WithArgs(true).WillReturnRows(_rows) // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}). - AddRow(1, 1, true, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main") + []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch", "error"}). + AddRow(1, 1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided") + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy", "github", "octocat", "github/octocat", "https://github.com/github/octocat", "https://github.com/github/octocat.git", "main", "{cloud,security}", 10, 30, 0, "public", false, false, true, 1, "", "", constants.ApproveNever) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "refresh_token", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "octocat", "superSecretToken", "superSecretRefreshToken", "{}", true, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE active = $1`).WithArgs(true).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -65,25 +127,44 @@ func TestSchedule_Engine_ListActiveSchedules(t *testing.T) { if err != nil { t.Errorf("unable to create test schedule for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } // setup tests tests := []struct { failure bool name string database *engine - want []*library.Schedule + want []*api.Schedule }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Schedule{_scheduleOne}, + want: []*api.Schedule{_scheduleOne}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Schedule{_scheduleOne}, + want: []*api.Schedule{_scheduleOne}, }, } @@ -104,8 +185,8 @@ func TestSchedule_Engine_ListActiveSchedules(t *testing.T) { t.Errorf("ListActiveSchedules for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListActiveSchedules for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListActiveSchedules for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/list_repo.go b/database/schedule/list_repo.go index c5d7866ca..9ec086589 100644 --- a/database/schedule/list_repo.go +++ b/database/schedule/list_repo.go @@ -4,23 +4,25 @@ package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // ListSchedulesForRepo gets a list of schedules by repo ID from the database. -func (e *engine) ListSchedulesForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Schedule, int64, error) { +func (e *engine) ListSchedulesForRepo(ctx context.Context, r *api.Repo, page, perPage int) ([]*api.Schedule, int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - }).Tracef("listing schedules for repo %s from the database", r.GetFullName()) + }).Tracef("listing schedules for repo %s", r.GetFullName()) // variables to store query results and return value count := int64(0) - s := new([]database.Schedule) - schedules := []*library.Schedule{} + s := new([]types.Schedule) + schedules := []*api.Schedule{} // count the results count, err := e.CountSchedulesForRepo(ctx, r) @@ -39,6 +41,8 @@ func (e *engine) ListSchedulesForRepo(ctx context.Context, r *library.Repo, page // send query to the database and store result in variable err = e.client. Table(constants.TableSchedule). + Preload("Repo"). + Preload("Repo.Owner"). Where("repo_id = ?", r.GetID()). Order("id DESC"). Limit(perPage). @@ -54,8 +58,14 @@ func (e *engine) ListSchedulesForRepo(ctx context.Context, r *library.Repo, page // https://golang.org/doc/faq#closures_and_goroutines tmp := schedule - // convert query result to library type - schedules = append(schedules, tmp.ToLibrary()) + // decrypt hash value for repo + err = tmp.Repo.Decrypt(e.config.EncryptionKey) + if err != nil { + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.Repo.ID.Int64, err) + } + + // convert query result to API type + schedules = append(schedules, tmp.ToAPI()) } return schedules, count, nil diff --git a/database/schedule/list_repo_test.go b/database/schedule/list_repo_test.go index 890f0f393..0719e6b68 100644 --- a/database/schedule/list_repo_test.go +++ b/database/schedule/list_repo_test.go @@ -4,41 +4,89 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_ListSchedulesForRepo(t *testing.T) { - _repo := testRepo() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - - _scheduleOne := testSchedule() + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) + _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) + _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -51,11 +99,22 @@ func TestSchedule_Engine_ListSchedulesForRepo(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}). - AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main") + []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch", "error"}). + AddRow(1, 1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided"). + AddRow(2, 1, false, "hourly", "0 * * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided") + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy", "github", "octocat", "github/octocat", "https://github.com/github/octocat", "https://github.com/github/octocat.git", "main", "{cloud,security}", 10, 30, 0, "public", false, false, true, 1, "", "", constants.ApproveNever) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "refresh_token", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "octocat", "superSecretToken", "superSecretRefreshToken", "{}", true, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE repo_id = $1 ORDER BY id DESC LIMIT $2`).WithArgs(1, 10).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -70,24 +129,44 @@ func TestSchedule_Engine_ListSchedulesForRepo(t *testing.T) { t.Errorf("unable to create test schedule for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Schedule + want []*api.Schedule }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Schedule{_scheduleOne}, + want: []*api.Schedule{_scheduleOne, _scheduleTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Schedule{_scheduleOne}, + want: []*api.Schedule{_scheduleTwo, _scheduleOne}, }, } @@ -108,8 +187,8 @@ func TestSchedule_Engine_ListSchedulesForRepo(t *testing.T) { t.Errorf("ListSchedulesForRepo for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListSchedulesForRepo for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListSchedulesForRepo for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/list_test.go b/database/schedule/list_test.go index 463a9dfd9..b699a12c4 100644 --- a/database/schedule/list_test.go +++ b/database/schedule/list_test.go @@ -4,35 +4,89 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_ListSchedules(t *testing.T) { - _scheduleOne := testSchedule() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() + _repo.SetID(1) + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _scheduleOne := testutils.APISchedule() _scheduleOne.SetID(1) - _scheduleOne.SetRepoID(1) + _scheduleOne.SetRepo(_repo) + _scheduleOne.SetActive(true) _scheduleOne.SetName("nightly") _scheduleOne.SetEntry("0 0 * * *") - _scheduleOne.SetCreatedAt(1) - _scheduleOne.SetCreatedBy("user1") - _scheduleOne.SetUpdatedAt(1) - _scheduleOne.SetUpdatedBy("user2") + _scheduleOne.SetCreatedAt(1713476291) + _scheduleOne.SetCreatedBy("octocat") + _scheduleOne.SetUpdatedAt(3013476291) + _scheduleOne.SetUpdatedBy("octokitty") + _scheduleOne.SetScheduledAt(2013476291) _scheduleOne.SetBranch("main") + _scheduleOne.SetError("no version: YAML property provided") + _scheduleOne.SetNextRun(nextTime.Unix()) + + currTime = time.Now().UTC() + nextTime, _ = gronx.NextTickAfter("0 * * * *", currTime, false) - _scheduleTwo := testSchedule() + _scheduleTwo := testutils.APISchedule() _scheduleTwo.SetID(2) - _scheduleTwo.SetRepoID(2) + _scheduleTwo.SetRepo(_repo) + _scheduleTwo.SetActive(false) _scheduleTwo.SetName("hourly") _scheduleTwo.SetEntry("0 * * * *") - _scheduleTwo.SetCreatedAt(1) - _scheduleTwo.SetCreatedBy("user1") - _scheduleTwo.SetUpdatedAt(1) - _scheduleTwo.SetUpdatedBy("user2") + _scheduleTwo.SetCreatedAt(1713476291) + _scheduleTwo.SetCreatedBy("octocat") + _scheduleTwo.SetUpdatedAt(3013476291) + _scheduleTwo.SetUpdatedBy("octokitty") + _scheduleTwo.SetScheduledAt(2013476291) _scheduleTwo.SetBranch("main") + _scheduleTwo.SetError("no version: YAML property provided") + _scheduleTwo.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -45,12 +99,22 @@ func TestSchedule_Engine_ListSchedules(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}). - AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main"). - AddRow(2, 2, false, "hourly", "0 * * * *", 1, "user1", 1, "user2", nil, "main") + []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch", "error"}). + AddRow(1, 1, true, "nightly", "0 0 * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided"). + AddRow(2, 1, false, "hourly", "0 * * * *", 1713476291, "octocat", 3013476291, "octokitty", 2013476291, "main", "no version: YAML property provided") + + _repoRows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_events", "pipeline_type", "previous_name", "approve_build"}). + AddRow(1, 1, "MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy", "github", "octocat", "github/octocat", "https://github.com/github/octocat", "https://github.com/github/octocat.git", "main", "{cloud,security}", 10, 30, 0, "public", false, false, true, 1, "", "", constants.ApproveNever) + + _userRows := sqlmock.NewRows( + []string{"id", "name", "token", "refresh_token", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "octocat", "superSecretToken", "superSecretRefreshToken", "{}", true, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "schedules"`).WillReturnRows(_rows) + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE "repos"."id" = $1`).WithArgs(1).WillReturnRows(_repoRows) + _mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."id" = $1`).WithArgs(1).WillReturnRows(_userRows) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -65,24 +129,44 @@ func TestSchedule_Engine_ListSchedules(t *testing.T) { t.Errorf("unable to create test schedule for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&types.Repo{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.client.AutoMigrate(&types.User{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableUser).Create(types.UserFromAPI(_owner)).Error + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string database *engine - want []*library.Schedule + want []*api.Schedule }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.Schedule{_scheduleOne, _scheduleTwo}, + want: []*api.Schedule{_scheduleOne, _scheduleTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.Schedule{_scheduleOne, _scheduleTwo}, + want: []*api.Schedule{_scheduleOne, _scheduleTwo}, }, } @@ -103,8 +187,8 @@ func TestSchedule_Engine_ListSchedules(t *testing.T) { t.Errorf("ListSchedules for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("ListSchedules for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ListSchedules for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/schedule/opts.go b/database/schedule/opts.go index f5f596d26..c3a0af1eb 100644 --- a/database/schedule/opts.go +++ b/database/schedule/opts.go @@ -4,8 +4,8 @@ package schedule import ( "context" - "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -22,6 +22,16 @@ func WithClient(client *gorm.DB) EngineOpt { } } +// WithEncryptionKey sets the encryption key in the database engine for Schedules. +func WithEncryptionKey(key string) EngineOpt { + return func(e *engine) error { + // set the encryption key in the schedule engine + e.config.EncryptionKey = key + + return nil + } +} + // WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Schedules. func WithLogger(logger *logrus.Entry) EngineOpt { return func(e *engine) error { diff --git a/database/schedule/opts_test.go b/database/schedule/opts_test.go index 93a2d3565..05e63ca54 100644 --- a/database/schedule/opts_test.go +++ b/database/schedule/opts_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/schedule/schedule.go b/database/schedule/schedule.go index a55bd2eb9..b7a83b2dd 100644 --- a/database/schedule/schedule.go +++ b/database/schedule/schedule.go @@ -6,15 +6,17 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( // config represents the settings required to create the engine that implements the ScheduleInterface interface. config struct { + // specifies the encryption key to use for the Schedule engine + EncryptionKey string // specifies to skip creating tables and indexes for the Schedule engine SkipCreation bool } diff --git a/database/schedule/schedule_test.go b/database/schedule/schedule_test.go index d676223a1..a7d4cb936 100644 --- a/database/schedule/schedule_test.go +++ b/database/schedule/schedule_test.go @@ -10,9 +10,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -172,52 +170,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testSchedule is a test helper function to create an API Schedule type with all fields set to their zero values. -func testSchedule() *library.Schedule { - return &library.Schedule{ - ID: new(int64), - RepoID: new(int64), - Active: new(bool), - Name: new(string), - Entry: new(string), - CreatedAt: new(int64), - CreatedBy: new(string), - UpdatedAt: new(int64), - UpdatedBy: new(string), - ScheduledAt: new(int64), - Branch: new(string), - } -} - -// testRepo is a test helper function to create a library Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ - ID: new(int64), - UserID: new(int64), - BuildLimit: new(int64), - Timeout: new(int64), - Counter: new(int), - PipelineType: new(string), - Hash: new(string), - Org: new(string), - Name: new(string), - FullName: new(string), - Link: new(string), - Clone: new(string), - Branch: new(string), - Visibility: new(string), - PreviousName: new(string), - Private: new(bool), - Trusted: new(bool), - Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/schedule/table.go b/database/schedule/table.go index ea8e99cb3..82ae8fdaf 100644 --- a/database/schedule/table.go +++ b/database/schedule/table.go @@ -25,6 +25,7 @@ schedules ( updated_by VARCHAR(250), scheduled_at INTEGER, branch VARCHAR(250), + error VARCHAR(250), UNIQUE(repo_id, name) ); ` @@ -45,6 +46,7 @@ schedules ( updated_by TEXT, scheduled_at INTEGER, branch TEXT, + error TEXT, UNIQUE(repo_id, name) ); ` diff --git a/database/schedule/update.go b/database/schedule/update.go index 87a0e5a9e..7dcc1256d 100644 --- a/database/schedule/update.go +++ b/database/schedule/update.go @@ -5,20 +5,21 @@ package schedule import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // UpdateSchedule updates an existing schedule in the database. -func (e *engine) UpdateSchedule(ctx context.Context, s *library.Schedule, fields bool) (*library.Schedule, error) { +func (e *engine) UpdateSchedule(ctx context.Context, s *api.Schedule, fields bool) (*api.Schedule, error) { e.logger.WithFields(logrus.Fields{ "schedule": s.GetName(), }).Tracef("updating schedule %s in the database", s.GetName()) - // cast the library type to database type - schedule := database.ScheduleFromLibrary(s) + // cast the API type to database type + schedule := types.ScheduleFromAPI(s) // validate the necessary fields are populated err := schedule.Validate() @@ -36,5 +37,13 @@ func (e *engine) UpdateSchedule(ctx context.Context, s *library.Schedule, fields err = e.client.Table(constants.TableSchedule).Model(schedule).UpdateColumn("scheduled_at", s.GetScheduledAt()).Error } - return schedule.ToLibrary(), err + if err != nil { + return nil, err + } + + // set repo to provided repo if update successful + result := schedule.ToAPI() + result.SetRepo(s.GetRepo()) + + return result, nil } diff --git a/database/schedule/update_test.go b/database/schedule/update_test.go index 45f9dbdf8..c936b2ab1 100644 --- a/database/schedule/update_test.go +++ b/database/schedule/update_test.go @@ -4,38 +4,79 @@ package schedule import ( "context" - "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/adhocore/gronx" + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" ) func TestSchedule_Engine_UpdateSchedule_Config(t *testing.T) { - _repo := testRepo() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - - _schedule := testSchedule() + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // ensure the mock expects the query _mock.ExpectExec(`UPDATE "schedules" -SET "repo_id"=$1,"active"=$2,"name"=$3,"entry"=$4,"created_at"=$5,"created_by"=$6,"updated_at"=$7,"updated_by"=$8,"scheduled_at"=$9,"branch"=$10 -WHERE "id" = $11`). - WithArgs(1, false, "nightly", "0 0 * * *", 1, "user1", NowTimestamp{}, "user2", nil, "main", 1). +SET "repo_id"=$1,"active"=$2,"name"=$3,"entry"=$4,"created_at"=$5,"created_by"=$6,"updated_at"=$7,"updated_by"=$8,"scheduled_at"=$9,"branch"=$10,"error"=$11 +WHERE "id" = $12`). + WithArgs(1, true, "nightly", "0 0 * * *", 1713476291, "octocat", NowTimestamp{}, "octokitty", 2013476291, "main", "no version: YAML property provided", 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) @@ -82,38 +123,72 @@ WHERE "id" = $11`). t.Errorf("UpdateSchedule for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, _schedule) { - t.Errorf("UpdateSchedule for %s returned %s, want %s", test.name, got, _schedule) + if diff := cmp.Diff(_schedule, got); diff != "" { + t.Errorf("UpdateSchedule for %s mismatch (-want +got):\n%s", test.name, diff) } }) } } func TestSchedule_Engine_UpdateSchedule_NotConfig(t *testing.T) { - _repo := testRepo() + // setup types + _owner := testutils.APIUser() + _owner.SetID(1) + _owner.SetName("octocat") + _owner.SetToken("superSecretToken") + _owner.SetRefreshToken("superSecretRefreshToken") + _owner.SetFavorites([]string{"github/octocat"}) + _owner.SetActive(true) + _owner.SetAdmin(false) + _owner.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + _repo := testutils.APIRepo() _repo.SetID(1) - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - - _schedule := testSchedule() + _repo.SetOwner(_owner.Crop()) + _repo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy") + _repo.SetOrg("github") + _repo.SetName("octocat") + _repo.SetFullName("github/octocat") + _repo.SetLink("https://github.com/github/octocat") + _repo.SetClone("https://github.com/github/octocat.git") + _repo.SetBranch("main") + _repo.SetTopics([]string{"cloud", "security"}) + _repo.SetBuildLimit(10) + _repo.SetTimeout(30) + _repo.SetCounter(0) + _repo.SetVisibility("public") + _repo.SetPrivate(false) + _repo.SetTrusted(false) + _repo.SetActive(true) + _repo.SetAllowEvents(api.NewEventsFromMask(1)) + _repo.SetPipelineType("") + _repo.SetPreviousName("") + _repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + _schedule := testutils.APISchedule() _schedule.SetID(1) - _schedule.SetRepoID(1) + _schedule.SetRepo(_repo) + _schedule.SetActive(true) _schedule.SetName("nightly") _schedule.SetEntry("0 0 * * *") - _schedule.SetCreatedAt(1) - _schedule.SetCreatedBy("user1") - _schedule.SetUpdatedAt(1) - _schedule.SetUpdatedBy("user2") - _schedule.SetScheduledAt(1) + _schedule.SetCreatedAt(1713476291) + _schedule.SetCreatedBy("octocat") + _schedule.SetUpdatedAt(3013476291) + _schedule.SetUpdatedBy("octokitty") + _schedule.SetScheduledAt(2013476291) _schedule.SetBranch("main") + _schedule.SetError("no version: YAML property provided") + _schedule.SetNextRun(nextTime.Unix()) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // ensure the mock expects the query _mock.ExpectExec(`UPDATE "schedules" SET "scheduled_at"=$1 WHERE "id" = $2`). - WithArgs(1, 1). + WithArgs(2013476291, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) @@ -159,8 +234,8 @@ func TestSchedule_Engine_UpdateSchedule_NotConfig(t *testing.T) { t.Errorf("UpdateSchedule for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, _schedule) { - t.Errorf("CreateSchedule for %s returned %s, want %s", test.name, got, _schedule) + if diff := cmp.Diff(_schedule, got); diff != "" { + t.Errorf("UpdateSchedule for %s mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/secret/count.go b/database/secret/count.go index fd4158d8a..bf91525fa 100644 --- a/database/secret/count.go +++ b/database/secret/count.go @@ -10,7 +10,7 @@ import ( // CountSecrets gets the count of all secrets from the database. func (e *engine) CountSecrets(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all secrets from the database") + e.logger.Tracef("getting count of all secrets") // variable to store query results var s int64 diff --git a/database/secret/count_org.go b/database/secret/count_org.go index 79dec8c44..9b4a8c263 100644 --- a/database/secret/count_org.go +++ b/database/secret/count_org.go @@ -5,8 +5,9 @@ package secret import ( "context" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // CountSecretsForOrg gets the count of secrets by org name from the database. @@ -14,7 +15,7 @@ func (e *engine) CountSecretsForOrg(ctx context.Context, org string, filters map e.logger.WithFields(logrus.Fields{ "org": org, "type": constants.SecretOrg, - }).Tracef("getting count of secrets for org %s from the database", org) + }).Tracef("getting count of secrets for org %s", org) // variable to store query results var s int64 diff --git a/database/secret/count_org_test.go b/database/secret/count_org_test.go index 500d29878..22f8543f8 100644 --- a/database/secret/count_org_test.go +++ b/database/secret/count_org_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" ) diff --git a/database/secret/count_repo.go b/database/secret/count_repo.go index 8fd9b0090..b8edddcb0 100644 --- a/database/secret/count_repo.go +++ b/database/secret/count_repo.go @@ -5,18 +5,19 @@ package secret import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountSecretsForRepo gets the count of secrets by org and repo name from the database. -func (e *engine) CountSecretsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}) (int64, error) { +func (e *engine) CountSecretsForRepo(ctx context.Context, r *api.Repo, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), "type": constants.SecretRepo, - }).Tracef("getting count of secrets for repo %s from the database", r.GetFullName()) + }).Tracef("getting count of secrets for repo %s", r.GetFullName()) // variable to store query results var s int64 diff --git a/database/secret/count_repo_test.go b/database/secret/count_repo_test.go index 3e688b0c2..c925853f8 100644 --- a/database/secret/count_repo_test.go +++ b/database/secret/count_repo_test.go @@ -7,16 +7,16 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/types/constants" ) func TestSecret_Engine_CountSecretsForRepo(t *testing.T) { // setup types _repo := testRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") diff --git a/database/secret/count_team.go b/database/secret/count_team.go index 2dcce27ef..1b5fc8361 100644 --- a/database/secret/count_team.go +++ b/database/secret/count_team.go @@ -6,8 +6,9 @@ import ( "context" "strings" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // CountSecretsForTeam gets the count of secrets by org and team name from the database. @@ -16,7 +17,7 @@ func (e *engine) CountSecretsForTeam(ctx context.Context, org, team string, filt "org": org, "team": team, "type": constants.SecretShared, - }).Tracef("getting count of secrets for team %s/%s from the database", org, team) + }).Tracef("getting count of secrets for team %s/%s", org, team) // variable to store query results var s int64 @@ -48,7 +49,7 @@ func (e *engine) CountSecretsForTeams(ctx context.Context, org string, teams []s "org": org, "teams": teams, "type": constants.SecretShared, - }).Tracef("getting count of secrets for teams %s in org %s from the database", teams, org) + }).Tracef("getting count of secrets for teams %s in org %s", teams, org) // variable to store query results var s int64 diff --git a/database/secret/count_team_test.go b/database/secret/count_team_test.go index 9800b34fd..071e7902f 100644 --- a/database/secret/count_team_test.go +++ b/database/secret/count_team_test.go @@ -7,9 +7,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/types/constants" ) func TestSecret_Engine_CountSecretsForTeam(t *testing.T) { @@ -112,7 +112,7 @@ func TestSecret_Engine_CountSecretsForTeams(t *testing.T) { // setup types _repo := testRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") diff --git a/database/secret/create.go b/database/secret/create.go index 4c8853bd8..be409d304 100644 --- a/database/secret/create.go +++ b/database/secret/create.go @@ -7,10 +7,11 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateSecret creates a new secret in the database. @@ -23,14 +24,14 @@ func (e *engine) CreateSecret(ctx context.Context, s *library.Secret) (*library. "team": s.GetTeam(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("creating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) + }).Tracef("creating secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) default: e.logger.WithFields(logrus.Fields{ "org": s.GetOrg(), "repo": s.GetRepo(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("creating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) + }).Tracef("creating secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) } // cast the library type to database type diff --git a/database/secret/create_test.go b/database/secret/create_test.go index b5d0c3ba5..ed3d95591 100644 --- a/database/secret/create_test.go +++ b/database/secret/create_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -60,23 +62,23 @@ func TestSecret_Engine_CreateSecret(t *testing.T) { // ensure the mock expects the repo secrets query _mock.ExpectQuery(`INSERT INTO "secrets" -("org","repo","team","name","value","type","images","events","allow_events","allow_command","created_at","created_by","updated_at","updated_by","id") +("org","repo","team","name","value","type","images","allow_events","allow_command","allow_substitution","created_at","created_by","updated_at","updated_by","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING "id"`). - WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", nil, nil, 1, false, 1, "user", 1, "user2", 1). + WithArgs("foo", "bar", nil, "baz", testutils.AnyArgument{}, "repo", nil, 1, false, false, 1, "user", 1, "user2", 1). WillReturnRows(_rows) // ensure the mock expects the org secrets query _mock.ExpectQuery(`INSERT INTO "secrets" -("org","repo","team","name","value","type","images","events","allow_events","allow_command","created_at","created_by","updated_at","updated_by","id") +("org","repo","team","name","value","type","images","allow_events","allow_command","allow_substitution","created_at","created_by","updated_at","updated_by","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING "id"`). - WithArgs("foo", "*", nil, "bar", AnyArgument{}, "org", nil, nil, 3, false, 1, "user", 1, "user2", 2). + WithArgs("foo", "*", nil, "bar", testutils.AnyArgument{}, "org", nil, 3, false, false, 1, "user", 1, "user2", 2). WillReturnRows(_rows) // ensure the mock expects the shared secrets query _mock.ExpectQuery(`INSERT INTO "secrets" -("org","repo","team","name","value","type","images","events","allow_events","allow_command","created_at","created_by","updated_at","updated_by","id") +("org","repo","team","name","value","type","images","allow_events","allow_command","allow_substitution","created_at","created_by","updated_at","updated_by","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING "id"`). - WithArgs("foo", nil, "bar", "baz", AnyArgument{}, "shared", nil, nil, 1, false, 1, "user", 1, "user2", 3). + WithArgs("foo", nil, "bar", "baz", testutils.AnyArgument{}, "shared", nil, 1, false, false, 1, "user", 1, "user2", 3). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/secret/delete.go b/database/secret/delete.go index b4068faec..bc3cba36f 100644 --- a/database/secret/delete.go +++ b/database/secret/delete.go @@ -5,10 +5,11 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeleteSecret deletes an existing secret from the database. @@ -23,14 +24,14 @@ func (e *engine) DeleteSecret(ctx context.Context, s *library.Secret) error { "team": s.GetTeam(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("deleting secret %s/%s/%s/%s from the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) + }).Tracef("deleting secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) default: e.logger.WithFields(logrus.Fields{ "org": s.GetOrg(), "repo": s.GetRepo(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("deleting secret %s/%s/%s/%s from the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) + }).Tracef("deleting secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) } // cast the library type to database type diff --git a/database/secret/delete_test.go b/database/secret/delete_test.go index 0c078c98f..57db11e88 100644 --- a/database/secret/delete_test.go +++ b/database/secret/delete_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" ) diff --git a/database/secret/get.go b/database/secret/get.go index c8cafa2df..6f5bc7bf9 100644 --- a/database/secret/get.go +++ b/database/secret/get.go @@ -12,7 +12,7 @@ import ( // GetSecret gets a secret by ID from the database. func (e *engine) GetSecret(ctx context.Context, id int64) (*library.Secret, error) { - e.logger.Tracef("getting secret %d from the database", id) + e.logger.Tracef("getting secret %d", id) // variable to store query results s := new(database.Secret) diff --git a/database/secret/get_org.go b/database/secret/get_org.go index 950368735..7d1766c88 100644 --- a/database/secret/get_org.go +++ b/database/secret/get_org.go @@ -5,10 +5,11 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetSecretForOrg gets a secret by org name from the database. @@ -17,7 +18,7 @@ func (e *engine) GetSecretForOrg(ctx context.Context, org, name string) (*librar "org": org, "secret": name, "type": constants.SecretOrg, - }).Tracef("getting org secret %s/%s from the database", org, name) + }).Tracef("getting org secret %s/%s", org, name) // variable to store query results s := new(database.Secret) diff --git a/database/secret/get_org_test.go b/database/secret/get_org_test.go index dec38ba8a..af127fab3 100644 --- a/database/secret/get_org_test.go +++ b/database/secret/get_org_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -32,8 +33,8 @@ func TestSecret_Engine_GetSecretForOrg(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND name = $3 LIMIT $4`). diff --git a/database/secret/get_repo.go b/database/secret/get_repo.go index b33b0d58d..690823c08 100644 --- a/database/secret/get_repo.go +++ b/database/secret/get_repo.go @@ -5,20 +5,22 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetSecretForRepo gets a secret by org and repo name from the database. -func (e *engine) GetSecretForRepo(ctx context.Context, name string, r *library.Repo) (*library.Secret, error) { +func (e *engine) GetSecretForRepo(ctx context.Context, name string, r *api.Repo) (*library.Secret, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), "secret": name, "type": constants.SecretRepo, - }).Tracef("getting repo secret %s/%s from the database", r.GetFullName(), name) + }).Tracef("getting repo secret %s/%s", r.GetFullName(), name) // variable to store query results s := new(database.Secret) diff --git a/database/secret/get_repo_test.go b/database/secret/get_repo_test.go index 3f0282a6b..99ea7610d 100644 --- a/database/secret/get_repo_test.go +++ b/database/secret/get_repo_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -16,7 +17,7 @@ func TestSecret_Engine_GetSecretForRepo(t *testing.T) { // setup types _repo := testRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -42,8 +43,8 @@ func TestSecret_Engine_GetSecretForRepo(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND repo = $3 AND name = $4 LIMIT $5`). diff --git a/database/secret/get_team.go b/database/secret/get_team.go index b11a9fd41..e03845947 100644 --- a/database/secret/get_team.go +++ b/database/secret/get_team.go @@ -5,10 +5,11 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetSecretForTeam gets a secret by org and team name from the database. @@ -18,7 +19,7 @@ func (e *engine) GetSecretForTeam(ctx context.Context, org, team, name string) ( "team": team, "secret": name, "type": constants.SecretShared, - }).Tracef("getting shared secret %s/%s/%s from the database", org, team, name) + }).Tracef("getting shared secret %s/%s/%s", org, team, name) // variable to store query results s := new(database.Secret) diff --git a/database/secret/get_team_test.go b/database/secret/get_team_test.go index e36674a35..96e3bf880 100644 --- a/database/secret/get_team_test.go +++ b/database/secret/get_team_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -32,8 +33,8 @@ func TestSecret_Engine_GetSecretForTeam(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND team = $3 AND name = $4 LIMIT $5`). diff --git a/database/secret/get_test.go b/database/secret/get_test.go index 979f626e0..0d38aa679 100644 --- a/database/secret/get_test.go +++ b/database/secret/get_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" ) @@ -31,8 +32,8 @@ func TestSecret_Engine_GetSecret(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) diff --git a/database/secret/index.go b/database/secret/index.go index f0cd1837d..e045031bb 100644 --- a/database/secret/index.go +++ b/database/secret/index.go @@ -33,7 +33,7 @@ ON secrets (type, org); // CreateSecretIndexes creates the indexes for the secrets table in the database. func (e *engine) CreateSecretIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for secrets table in the database") + e.logger.Tracef("creating indexes for secrets table") // create the type, org and repo columns index for the secrets table err := e.client.Exec(CreateTypeOrgRepo).Error diff --git a/database/secret/interface.go b/database/secret/interface.go index e431fd458..b5aa4f73e 100644 --- a/database/secret/interface.go +++ b/database/secret/interface.go @@ -5,6 +5,7 @@ package secret import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -31,7 +32,7 @@ type SecretInterface interface { // CountSecretsForOrg defines a function that gets the count of secrets by org name. CountSecretsForOrg(context.Context, string, map[string]interface{}) (int64, error) // CountSecretsForRepo defines a function that gets the count of secrets by org and repo name. - CountSecretsForRepo(context.Context, *library.Repo, map[string]interface{}) (int64, error) + CountSecretsForRepo(context.Context, *api.Repo, map[string]interface{}) (int64, error) // CountSecretsForTeam defines a function that gets the count of secrets by org and team name. CountSecretsForTeam(context.Context, string, string, map[string]interface{}) (int64, error) // CountSecretsForTeams defines a function that gets the count of secrets by teams within an org. @@ -45,7 +46,7 @@ type SecretInterface interface { // GetSecretForOrg defines a function that gets a secret by org name. GetSecretForOrg(context.Context, string, string) (*library.Secret, error) // GetSecretForRepo defines a function that gets a secret by org and repo name. - GetSecretForRepo(context.Context, string, *library.Repo) (*library.Secret, error) + GetSecretForRepo(context.Context, string, *api.Repo) (*library.Secret, error) // GetSecretForTeam defines a function that gets a secret by org and team name. GetSecretForTeam(context.Context, string, string, string) (*library.Secret, error) // ListSecrets defines a function that gets a list of all secrets. @@ -53,7 +54,7 @@ type SecretInterface interface { // ListSecretsForOrg defines a function that gets a list of secrets by org name. ListSecretsForOrg(context.Context, string, map[string]interface{}, int, int) ([]*library.Secret, int64, error) // ListSecretsForRepo defines a function that gets a list of secrets by org and repo name. - ListSecretsForRepo(context.Context, *library.Repo, map[string]interface{}, int, int) ([]*library.Secret, int64, error) + ListSecretsForRepo(context.Context, *api.Repo, map[string]interface{}, int, int) ([]*library.Secret, int64, error) // ListSecretsForTeam defines a function that gets a list of secrets by org and team name. ListSecretsForTeam(context.Context, string, string, map[string]interface{}, int, int) ([]*library.Secret, int64, error) // ListSecretsForTeams defines a function that gets a list of secrets by teams within an org. diff --git a/database/secret/list.go b/database/secret/list.go index 08746332c..a61e2088f 100644 --- a/database/secret/list.go +++ b/database/secret/list.go @@ -12,7 +12,7 @@ import ( // ListSecrets gets a list of all secrets from the database. func (e *engine) ListSecrets(ctx context.Context) ([]*library.Secret, error) { - e.logger.Trace("listing all secrets from the database") + e.logger.Trace("listing all secrets") // variables to store query results and return value count := int64(0) diff --git a/database/secret/list_org.go b/database/secret/list_org.go index bb57d4eb3..d8c7ced5a 100644 --- a/database/secret/list_org.go +++ b/database/secret/list_org.go @@ -5,10 +5,11 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListSecretsForOrg gets a list of secrets by org name from the database. @@ -18,7 +19,7 @@ func (e *engine) ListSecretsForOrg(ctx context.Context, org string, filters map[ e.logger.WithFields(logrus.Fields{ "org": org, "type": constants.SecretOrg, - }).Tracef("listing secrets for org %s from the database", org) + }).Tracef("listing secrets for org %s", org) // variables to store query results and return values count := int64(0) diff --git a/database/secret/list_org_test.go b/database/secret/list_org_test.go index 9fe2da2e1..f3a95f0cd 100644 --- a/database/secret/list_org_test.go +++ b/database/secret/list_org_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -52,9 +53,9 @@ func TestSecret_Engine_ListSecretsForOrg(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(2, "org", "foo", "*", "", "bar", "baz", nil, nil, 1, false, 1, "user", 1, "user2"). - AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(2, "org", "foo", "*", "", "bar", "baz", nil, 1, false, false, 1, "user", 1, "user2"). + AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 ORDER BY id DESC LIMIT $3`). diff --git a/database/secret/list_repo.go b/database/secret/list_repo.go index 213ee9353..ef2fe546c 100644 --- a/database/secret/list_repo.go +++ b/database/secret/list_repo.go @@ -5,21 +5,23 @@ package secret import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListSecretsForRepo gets a list of secrets by org name from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListSecretsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) { +func (e *engine) ListSecretsForRepo(ctx context.Context, r *api.Repo, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) { e.logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), "type": constants.SecretRepo, - }).Tracef("listing secrets for repo %s from the database", r.GetFullName()) + }).Tracef("listing secrets for repo %s", r.GetFullName()) // variables to store query results and return values count := int64(0) diff --git a/database/secret/list_repo_test.go b/database/secret/list_repo_test.go index 838dd96f9..1b70c1bda 100644 --- a/database/secret/list_repo_test.go +++ b/database/secret/list_repo_test.go @@ -7,9 +7,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -17,7 +17,7 @@ func TestSecret_Engine_ListSecretsForRepo(t *testing.T) { // setup types _repo := testRepo() _repo.SetID(1) - _repo.SetUserID(1) + _repo.GetOwner().SetID(1) _repo.SetHash("baz") _repo.SetOrg("foo") _repo.SetName("bar") @@ -63,9 +63,9 @@ func TestSecret_Engine_ListSecretsForRepo(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, nil, 1, false, 1, "user", 1, "user2"). - AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, 1, false, false, 1, "user", 1, "user2"). + AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND repo = $3 ORDER BY id DESC LIMIT $4`). diff --git a/database/secret/list_team.go b/database/secret/list_team.go index 1f5e317c5..93a5d6db1 100644 --- a/database/secret/list_team.go +++ b/database/secret/list_team.go @@ -6,10 +6,11 @@ import ( "context" "strings" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListSecretsForTeam gets a list of secrets by org and team name from the database. @@ -20,7 +21,7 @@ func (e *engine) ListSecretsForTeam(ctx context.Context, org, team string, filte "org": org, "team": team, "type": constants.SecretShared, - }).Tracef("listing secrets for team %s/%s from the database", org, team) + }).Tracef("listing secrets for team %s/%s", org, team) // variables to store query results and return values count := int64(0) @@ -96,7 +97,7 @@ func (e *engine) ListSecretsForTeams(ctx context.Context, org string, teams []st "org": org, "teams": teams, "type": constants.SecretShared, - }).Tracef("listing secrets for teams %s in org %s from the database", teams, org) + }).Tracef("listing secrets for teams %s in org %s", teams, org) // variables to store query results and return values count := int64(0) diff --git a/database/secret/list_team_test.go b/database/secret/list_team_test.go index bed709312..81708949a 100644 --- a/database/secret/list_team_test.go +++ b/database/secret/list_team_test.go @@ -7,9 +7,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -53,9 +53,9 @@ func TestSecret_Engine_ListSecretsForTeam(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, nil, 1, false, 1, "user", 1, "user2"). - AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, 1, false, false, 1, "user", 1, "user2"). + AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND team = $3 ORDER BY id DESC LIMIT $4`). @@ -134,6 +134,7 @@ func TestSecret_Engine_ListSecretsForTeams(t *testing.T) { _secretOne.SetCreatedBy("user") _secretOne.SetUpdatedAt(1) _secretOne.SetUpdatedBy("user2") + _secretOne.SetAllowEvents(library.NewEventsFromMask(1)) _secretTwo := testSecret() _secretTwo.SetID(2) @@ -146,6 +147,7 @@ func TestSecret_Engine_ListSecretsForTeams(t *testing.T) { _secretTwo.SetCreatedBy("user") _secretTwo.SetUpdatedAt(1) _secretTwo.SetUpdatedBy("user2") + _secretTwo.SetAllowEvents(library.NewEventsFromMask(1)) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -159,9 +161,9 @@ func TestSecret_Engine_ListSecretsForTeams(t *testing.T) { // create expected name query result in mock _rows = sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, nil, false, 1, "user", 1, "user2"). - AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, 1, false, 1, "user", 1, "user2"). + AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, 1, false, 1, "user", 1, "user2") // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND LOWER(team) IN ($3,$4) ORDER BY id DESC LIMIT $5`). diff --git a/database/secret/list_test.go b/database/secret/list_test.go index 8077637ee..aa72d5baf 100644 --- a/database/secret/list_test.go +++ b/database/secret/list_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" ) @@ -50,9 +51,9 @@ func TestSecret_Engine_ListSecrets(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}). - AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, 1, false, 1, "user", 1, "user2"). - AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, nil, 1, false, 1, "user", 1, "user2") + []string{"id", "type", "org", "repo", "team", "name", "value", "images", "allow_events", "allow_command", "allow_substitution", "created_at", "created_by", "updated_at", "updated_by"}). + AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, 1, false, false, 1, "user", 1, "user2"). + AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, 1, false, false, 1, "user", 1, "user2") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "secrets"`).WillReturnRows(_rows) diff --git a/database/secret/opts.go b/database/secret/opts.go index cb1589d32..2a6216989 100644 --- a/database/secret/opts.go +++ b/database/secret/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/secret/opts_test.go b/database/secret/opts_test.go index f446c09f2..7865daef9 100644 --- a/database/secret/opts_test.go +++ b/database/secret/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/secret/secret.go b/database/secret/secret.go index 7cf9981c8..4a7c772e8 100644 --- a/database/secret/secret.go +++ b/database/secret/secret.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -62,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating secret database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of secrets table and indexes in the database") + e.logger.Warning("skipping creation of secrets table and indexes") return e, nil } diff --git a/database/secret/secret_test.go b/database/secret/secret_test.go index 31156ebca..41bb4dd3a 100644 --- a/database/secret/secret_test.go +++ b/database/secret/secret_test.go @@ -3,19 +3,18 @@ package secret import ( - "database/sql/driver" "reflect" "testing" - "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" - "github.com/go-vela/types/library/actions" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/library" + "github.com/go-vela/types/library/actions" ) func TestSecret_New(t *testing.T) { @@ -178,10 +177,9 @@ func testSqlite(t *testing.T) *engine { // testRepo is a test helper function to create a library // Repo type with all fields set to their zero values. -func testRepo() *library.Repo { - return &library.Repo{ +func testRepo() *api.Repo { + return &api.Repo{ ID: new(int64), - UserID: new(int64), BuildLimit: new(int64), Timeout: new(int64), Counter: new(int), @@ -198,11 +196,6 @@ func testRepo() *library.Repo { Private: new(bool), Trusted: new(bool), Active: new(bool), - AllowPull: new(bool), - AllowPush: new(bool), - AllowDeploy: new(bool), - AllowTag: new(bool), - AllowComment: new(bool), } } @@ -210,21 +203,21 @@ func testRepo() *library.Repo { // Secret type with all fields set to their zero values. func testSecret() *library.Secret { return &library.Secret{ - ID: new(int64), - Org: new(string), - Repo: new(string), - Team: new(string), - Name: new(string), - Value: new(string), - Type: new(string), - Images: new([]string), - Events: new([]string), - AllowEvents: testEvents(), - AllowCommand: new(bool), - CreatedAt: new(int64), - CreatedBy: new(string), - UpdatedAt: new(int64), - UpdatedBy: new(string), + ID: new(int64), + Org: new(string), + Repo: new(string), + Team: new(string), + Name: new(string), + Value: new(string), + Type: new(string), + Images: new([]string), + AllowEvents: testEvents(), + AllowCommand: new(bool), + AllowSubstitution: new(bool), + CreatedAt: new(int64), + CreatedBy: new(string), + UpdatedAt: new(int64), + UpdatedBy: new(string), } } @@ -241,6 +234,8 @@ func testEvents() *library.Events { Edited: new(bool), Synchronize: new(bool), Reopened: new(bool), + Labeled: new(bool), + Unlabeled: new(bool), }, Deployment: &actions.Deploy{ Created: new(bool), @@ -254,29 +249,3 @@ func testEvents() *library.Events { }, } } - -// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values -// that are otherwise not easily compared. These typically would be values generated -// before adding or updating them in the database. -// -// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime -type AnyArgument struct{} - -// Match satisfies sqlmock.Argument interface. -func (a AnyArgument) Match(_ driver.Value) bool { - return true -} - -// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience. -type NowTimestamp struct{} - -// Match satisfies sqlmock.Argument interface. -func (t NowTimestamp) Match(v driver.Value) bool { - ts, ok := v.(int64) - if !ok { - return false - } - now := time.Now().Unix() - - return now-ts < 10 -} diff --git a/database/secret/table.go b/database/secret/table.go index f3d42ea46..e696409c2 100644 --- a/database/secret/table.go +++ b/database/secret/table.go @@ -14,21 +14,21 @@ const ( CREATE TABLE IF NOT EXISTS secrets ( - id SERIAL PRIMARY KEY, - type VARCHAR(100), - org VARCHAR(250), - repo VARCHAR(250), - team VARCHAR(250), - name VARCHAR(250), - value BYTEA, - images VARCHAR(1000), - events VARCHAR(1000), - allow_events INTEGER, - allow_command BOOLEAN, - created_at INTEGER, - created_by VARCHAR(250), - updated_at INTEGER, - updated_by VARCHAR(250), + id SERIAL PRIMARY KEY, + type VARCHAR(100), + org VARCHAR(250), + repo VARCHAR(250), + team VARCHAR(250), + name VARCHAR(250), + value BYTEA, + images VARCHAR(1000), + allow_events INTEGER, + allow_command BOOLEAN, + allow_substitution BOOLEAN, + created_at INTEGER, + created_by VARCHAR(250), + updated_at INTEGER, + updated_by VARCHAR(250), UNIQUE(type, org, repo, name), UNIQUE(type, org, team, name) ); @@ -39,21 +39,21 @@ secrets ( CREATE TABLE IF NOT EXISTS secrets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - type TEXT, - org TEXT, - repo TEXT, - team TEXT, - name TEXT, - value TEXT, - images TEXT, - events TEXT, - allow_events INTEGER, - allow_command BOOLEAN, - created_at INTEGER, - created_by TEXT, - updated_at INTEGER, - updated_by TEXT, + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT, + org TEXT, + repo TEXT, + team TEXT, + name TEXT, + value TEXT, + images TEXT, + allow_events INTEGER, + allow_command BOOLEAN, + allow_substitution BOOLEAN, + created_at INTEGER, + created_by TEXT, + updated_at INTEGER, + updated_by TEXT, UNIQUE(type, org, repo, name), UNIQUE(type, org, team, name) ); @@ -62,7 +62,7 @@ secrets ( // CreateSecretTable creates the secrets table in the database. func (e *engine) CreateSecretTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating secrets table in the database") + e.logger.Tracef("creating secrets table") // handle the driver provided to create the table switch driver { diff --git a/database/secret/update.go b/database/secret/update.go index 04fc98b84..0bb713e47 100644 --- a/database/secret/update.go +++ b/database/secret/update.go @@ -7,10 +7,11 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdateSecret updates an existing secret in the database. @@ -23,14 +24,14 @@ func (e *engine) UpdateSecret(ctx context.Context, s *library.Secret) (*library. "team": s.GetTeam(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("updating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) + }).Tracef("updating secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName()) default: e.logger.WithFields(logrus.Fields{ "org": s.GetOrg(), "repo": s.GetRepo(), "secret": s.GetName(), "type": s.GetType(), - }).Tracef("updating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) + }).Tracef("updating secret %s/%s/%s/%s", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName()) } // cast the library type to database type diff --git a/database/secret/update_test.go b/database/secret/update_test.go index f26ddaa24..e53d43b4e 100644 --- a/database/secret/update_test.go +++ b/database/secret/update_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) @@ -57,23 +59,23 @@ func TestSecret_Engine_UpdateSecret(t *testing.T) { // ensure the mock expects the repo query _mock.ExpectExec(`UPDATE "secrets" -SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_events"=$9,"allow_command"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 +SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"allow_events"=$8,"allow_command"=$9,"allow_substitution"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 WHERE "id" = $15`). - WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", nil, nil, 1, false, 1, "user", AnyArgument{}, "user2", 1). + WithArgs("foo", "bar", nil, "baz", testutils.AnyArgument{}, "repo", nil, 1, false, false, 1, "user", testutils.AnyArgument{}, "user2", 1). WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the org query _mock.ExpectExec(`UPDATE "secrets" -SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_events"=$9,"allow_command"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 +SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"allow_events"=$8,"allow_command"=$9,"allow_substitution"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 WHERE "id" = $15`). - WithArgs("foo", "*", nil, "bar", AnyArgument{}, "org", nil, nil, 1, false, 1, "user", AnyArgument{}, "user2", 2). + WithArgs("foo", "*", nil, "bar", testutils.AnyArgument{}, "org", nil, 1, false, false, 1, "user", testutils.AnyArgument{}, "user2", 2). WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the shared query _mock.ExpectExec(`UPDATE "secrets" -SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_events"=$9,"allow_command"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 +SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"allow_events"=$8,"allow_command"=$9,"allow_substitution"=$10,"created_at"=$11,"created_by"=$12,"updated_at"=$13,"updated_by"=$14 WHERE "id" = $15`). - WithArgs("foo", nil, "bar", "baz", AnyArgument{}, "shared", nil, nil, 1, false, 1, "user", NowTimestamp{}, "user2", 3). + WithArgs("foo", nil, "bar", "baz", testutils.AnyArgument{}, "shared", nil, 1, false, false, 1, "user", testutils.NowTimestamp{}, "user2", 3). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/service/clean.go b/database/service/clean.go index e9e42de58..b4becb0ad 100644 --- a/database/service/clean.go +++ b/database/service/clean.go @@ -6,10 +6,11 @@ import ( "context" "time" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CleanServices updates services to an error with a created timestamp prior to a defined moment. diff --git a/database/service/clean_test.go b/database/service/clean_test.go index a3edada5b..b72d14dad 100644 --- a/database/service/clean_test.go +++ b/database/service/clean_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_CleanService(t *testing.T) { // setup types - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -22,7 +24,7 @@ func TestService_Engine_CleanService(t *testing.T) { _serviceOne.SetCreated(1) _serviceOne.SetStatus("running") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(1) @@ -32,7 +34,7 @@ func TestService_Engine_CleanService(t *testing.T) { _serviceTwo.SetCreated(1) _serviceTwo.SetStatus("pending") - _serviceThree := testService() + _serviceThree := testutils.APIService() _serviceThree.SetID(3) _serviceThree.SetRepoID(1) _serviceThree.SetBuildID(1) @@ -42,7 +44,7 @@ func TestService_Engine_CleanService(t *testing.T) { _serviceThree.SetCreated(1) _serviceThree.SetStatus("success") - _serviceFour := testService() + _serviceFour := testutils.APIService() _serviceFour.SetID(4) _serviceFour.SetRepoID(1) _serviceFour.SetBuildID(1) diff --git a/database/service/count.go b/database/service/count.go index dd1f63188..f8bee25b1 100644 --- a/database/service/count.go +++ b/database/service/count.go @@ -10,7 +10,7 @@ import ( // CountServices gets the count of all services from the database. func (e *engine) CountServices(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all services from the database") + e.logger.Tracef("getting count of all services") // variable to store query results var s int64 diff --git a/database/service/count_build.go b/database/service/count_build.go index a70c326d2..1e84819a2 100644 --- a/database/service/count_build.go +++ b/database/service/count_build.go @@ -5,16 +5,17 @@ package service import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountServicesForBuild gets the count of services by build ID from the database. -func (e *engine) CountServicesForBuild(ctx context.Context, b *library.Build, filters map[string]interface{}) (int64, error) { +func (e *engine) CountServicesForBuild(ctx context.Context, b *api.Build, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("getting count of services for build %d from the database", b.GetNumber()) + }).Tracef("getting count of services for build %d", b.GetNumber()) // variable to store query results var s int64 diff --git a/database/service/count_build_test.go b/database/service/count_build_test.go index 09b013c0c..e0c1cefcf 100644 --- a/database/service/count_build_test.go +++ b/database/service/count_build_test.go @@ -8,16 +8,18 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_CountServicesForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -25,7 +27,7 @@ func TestService_Engine_CountServicesForBuild(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(2) diff --git a/database/service/count_test.go b/database/service/count_test.go index bbd0b6faf..c25a0d5b0 100644 --- a/database/service/count_test.go +++ b/database/service/count_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_CountServices(t *testing.T) { // setup types - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestService_Engine_CountServices(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(2) diff --git a/database/service/create.go b/database/service/create.go index dc5726dea..6d2ec8f74 100644 --- a/database/service/create.go +++ b/database/service/create.go @@ -5,10 +5,11 @@ package service import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateService creates a new service in the database. diff --git a/database/service/create_test.go b/database/service/create_test.go index b284bd4f8..6893daabc 100644 --- a/database/service/create_test.go +++ b/database/service/create_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_CreateService(t *testing.T) { // setup types - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) diff --git a/database/service/delete.go b/database/service/delete.go index 90fc552f6..3f0bd331e 100644 --- a/database/service/delete.go +++ b/database/service/delete.go @@ -5,17 +5,18 @@ package service import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeleteService deletes an existing service from the database. func (e *engine) DeleteService(ctx context.Context, s *library.Service) error { e.logger.WithFields(logrus.Fields{ "service": s.GetNumber(), - }).Tracef("deleting service %s from the database", s.GetName()) + }).Tracef("deleting service %s", s.GetName()) // cast the library type to database type // diff --git a/database/service/delete_test.go b/database/service/delete_test.go index 3600242d9..5481e9258 100644 --- a/database/service/delete_test.go +++ b/database/service/delete_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_DeleteService(t *testing.T) { // setup types - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) diff --git a/database/service/get.go b/database/service/get.go index aacda2904..eee921123 100644 --- a/database/service/get.go +++ b/database/service/get.go @@ -12,7 +12,7 @@ import ( // GetService gets a service by ID from the database. func (e *engine) GetService(ctx context.Context, id int64) (*library.Service, error) { - e.logger.Tracef("getting service %d from the database", id) + e.logger.Tracef("getting service %d", id) // variable to store query results s := new(database.Service) diff --git a/database/service/get_build.go b/database/service/get_build.go index 8822743f8..81a95e0bc 100644 --- a/database/service/get_build.go +++ b/database/service/get_build.go @@ -5,18 +5,20 @@ package service import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetServiceForBuild gets a service by number and build ID from the database. -func (e *engine) GetServiceForBuild(ctx context.Context, b *library.Build, number int) (*library.Service, error) { +func (e *engine) GetServiceForBuild(ctx context.Context, b *api.Build, number int) (*library.Service, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), "service": number, - }).Tracef("getting service %d from the database", number) + }).Tracef("getting service %d", number) // variable to store query results s := new(database.Service) diff --git a/database/service/get_build_test.go b/database/service/get_build_test.go index b8f859649..da26e4ce9 100644 --- a/database/service/get_build_test.go +++ b/database/service/get_build_test.go @@ -8,17 +8,19 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestService_Engine_GetServiceForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) diff --git a/database/service/get_test.go b/database/service/get_test.go index 8aa8961e5..612811eed 100644 --- a/database/service/get_test.go +++ b/database/service/get_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestService_Engine_GetService(t *testing.T) { // setup types - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) diff --git a/database/service/interface.go b/database/service/interface.go index 90982afa0..40fc30293 100644 --- a/database/service/interface.go +++ b/database/service/interface.go @@ -5,6 +5,7 @@ package service import ( "context" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -29,7 +30,7 @@ type ServiceInterface interface { // CountServices defines a function that gets the count of all services. CountServices(context.Context) (int64, error) // CountServicesForBuild defines a function that gets the count of services by build ID. - CountServicesForBuild(context.Context, *library.Build, map[string]interface{}) (int64, error) + CountServicesForBuild(context.Context, *api.Build, map[string]interface{}) (int64, error) // CreateService defines a function that creates a new service. CreateService(context.Context, *library.Service) (*library.Service, error) // DeleteService defines a function that deletes an existing service. @@ -37,11 +38,11 @@ type ServiceInterface interface { // GetService defines a function that gets a service by ID. GetService(context.Context, int64) (*library.Service, error) // GetServiceForBuild defines a function that gets a service by number and build ID. - GetServiceForBuild(context.Context, *library.Build, int) (*library.Service, error) + GetServiceForBuild(context.Context, *api.Build, int) (*library.Service, error) // ListServices defines a function that gets a list of all services. ListServices(context.Context) ([]*library.Service, error) // ListServicesForBuild defines a function that gets a list of services by build ID. - ListServicesForBuild(context.Context, *library.Build, map[string]interface{}, int, int) ([]*library.Service, int64, error) + ListServicesForBuild(context.Context, *api.Build, map[string]interface{}, int, int) ([]*library.Service, int64, error) // ListServiceImageCount defines a function that gets a list of all service images and the count of their occurrence. ListServiceImageCount(context.Context) (map[string]float64, error) // ListServiceStatusCount defines a function that gets a list of all service statuses and the count of their occurrence. diff --git a/database/service/list.go b/database/service/list.go index 5da3a06ed..656beee10 100644 --- a/database/service/list.go +++ b/database/service/list.go @@ -12,7 +12,7 @@ import ( // ListServices gets a list of all services from the database. func (e *engine) ListServices(ctx context.Context) ([]*library.Service, error) { - e.logger.Trace("listing all services from the database") + e.logger.Trace("listing all services") // variables to store query results and return value count := int64(0) diff --git a/database/service/list_build.go b/database/service/list_build.go index b273835f8..047aba669 100644 --- a/database/service/list_build.go +++ b/database/service/list_build.go @@ -5,17 +5,19 @@ package service import ( "context" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListServicesForBuild gets a list of all services from the database. -func (e *engine) ListServicesForBuild(ctx context.Context, b *library.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Service, int64, error) { +func (e *engine) ListServicesForBuild(ctx context.Context, b *api.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Service, int64, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("listing services for build %d from the database", b.GetNumber()) + }).Tracef("listing services for build %d", b.GetNumber()) // variables to store query results and return value count := int64(0) diff --git a/database/service/list_build_test.go b/database/service/list_build_test.go index f33e98ac3..cd09061ee 100644 --- a/database/service/list_build_test.go +++ b/database/service/list_build_test.go @@ -8,17 +8,19 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestService_Engine_ListServicesForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -26,7 +28,7 @@ func TestService_Engine_ListServicesForBuild(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(1) diff --git a/database/service/list_image.go b/database/service/list_image.go index f5267d5da..a94447e6c 100644 --- a/database/service/list_image.go +++ b/database/service/list_image.go @@ -11,7 +11,7 @@ import ( // ListServiceImageCount gets a list of all service images and the count of their occurrence from the database. func (e *engine) ListServiceImageCount(ctx context.Context) (map[string]float64, error) { - e.logger.Tracef("getting count of all images for services from the database") + e.logger.Tracef("getting count of all images for services") // variables to store query results and return value s := []struct { diff --git a/database/service/list_image_test.go b/database/service/list_image_test.go index fdf8a35fa..1bf4578b6 100644 --- a/database/service/list_image_test.go +++ b/database/service/list_image_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_ListServiceImageCount(t *testing.T) { // setup types - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestService_Engine_ListServiceImageCount(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(1) diff --git a/database/service/list_status.go b/database/service/list_status.go index dde89795a..9e5f106fd 100644 --- a/database/service/list_status.go +++ b/database/service/list_status.go @@ -11,7 +11,7 @@ import ( // ListServiceStatusCount gets a list of all service statuses and the count of their occurrence from the database. func (e *engine) ListServiceStatusCount(ctx context.Context) (map[string]float64, error) { - e.logger.Tracef("getting count of all statuses for services from the database") + e.logger.Tracef("getting count of all statuses for services") // variables to store query results and return value s := []struct { diff --git a/database/service/list_status_test.go b/database/service/list_status_test.go index 0b6e7ea1a..56b6f9203 100644 --- a/database/service/list_status_test.go +++ b/database/service/list_status_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_ListServiceStatusCount(t *testing.T) { // setup types - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestService_Engine_ListServiceStatusCount(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(1) diff --git a/database/service/list_test.go b/database/service/list_test.go index 24f718e94..462dae56c 100644 --- a/database/service/list_test.go +++ b/database/service/list_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestService_Engine_ListServices(t *testing.T) { // setup types - _serviceOne := testService() + _serviceOne := testutils.APIService() _serviceOne.SetID(1) _serviceOne.SetRepoID(1) _serviceOne.SetBuildID(1) @@ -21,7 +23,7 @@ func TestService_Engine_ListServices(t *testing.T) { _serviceOne.SetName("foo") _serviceOne.SetImage("bar") - _serviceTwo := testService() + _serviceTwo := testutils.APIService() _serviceTwo.SetID(2) _serviceTwo.SetRepoID(1) _serviceTwo.SetBuildID(2) diff --git a/database/service/opts.go b/database/service/opts.go index c06c6a931..6ed08ed22 100644 --- a/database/service/opts.go +++ b/database/service/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/service/opts_test.go b/database/service/opts_test.go index 043d1e290..f34c53126 100644 --- a/database/service/opts_test.go +++ b/database/service/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/service/service.go b/database/service/service.go index 0f1b92b7d..f02f6ae6b 100644 --- a/database/service/service.go +++ b/database/service/service.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( diff --git a/database/service/service_test.go b/database/service/service_test.go index e1f6f92f3..9dcec640b 100644 --- a/database/service/service_test.go +++ b/database/service/service_test.go @@ -9,9 +9,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -164,65 +162,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testBuild is a test helper function to create a library -// Build type with all fields set to their zero values. -func testBuild() *library.Build { - return &library.Build{ - ID: new(int64), - RepoID: new(int64), - PipelineID: new(int64), - Number: new(int), - Parent: new(int), - Event: new(string), - EventAction: new(string), - Status: new(string), - Error: new(string), - Enqueued: new(int64), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Deploy: new(string), - Clone: new(string), - Source: new(string), - Title: new(string), - Message: new(string), - Commit: new(string), - Sender: new(string), - Author: new(string), - Email: new(string), - Link: new(string), - Branch: new(string), - Ref: new(string), - BaseRef: new(string), - HeadRef: new(string), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - -// testService is a test helper function to create a library -// Service type with all fields set to their zero values. -func testService() *library.Service { - return &library.Service{ - ID: new(int64), - BuildID: new(int64), - RepoID: new(int64), - Number: new(int), - Name: new(string), - Image: new(string), - Status: new(string), - Error: new(string), - ExitCode: new(int), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/service/table.go b/database/service/table.go index 7cf7561c3..56e78b249 100644 --- a/database/service/table.go +++ b/database/service/table.go @@ -60,7 +60,7 @@ services ( // CreateServiceTable creates the services table in the database. func (e *engine) CreateServiceTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating services table in the database") + e.logger.Tracef("creating services table") // handle the driver provided to create the table switch driver { diff --git a/database/service/update.go b/database/service/update.go index 677a2c71c..6eb43a7e2 100644 --- a/database/service/update.go +++ b/database/service/update.go @@ -5,17 +5,18 @@ package service import ( "context" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdateService updates an existing service in the database. func (e *engine) UpdateService(ctx context.Context, s *library.Service) (*library.Service, error) { e.logger.WithFields(logrus.Fields{ "service": s.GetNumber(), - }).Tracef("updating service %s in the database", s.GetName()) + }).Tracef("updating service %s", s.GetName()) // cast the library type to database type // diff --git a/database/service/update_test.go b/database/service/update_test.go index 6e8001deb..82dccac9c 100644 --- a/database/service/update_test.go +++ b/database/service/update_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestService_Engine_UpdateService(t *testing.T) { // setup types - _service := testService() + _service := testutils.APIService() _service.SetID(1) _service.SetRepoID(1) _service.SetBuildID(1) diff --git a/database/settings/create.go b/database/settings/create.go new file mode 100644 index 000000000..8c5747320 --- /dev/null +++ b/database/settings/create.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/database/types" +) + +// CreateSettings creates a platform settings record in the database. +func (e *engine) CreateSettings(_ context.Context, s *settings.Platform) (*settings.Platform, error) { + e.logger.Tracef("creating platform settings with %v", s.String()) + + // cast the api type to database type + settings := types.SettingsFromAPI(s) + + // validate the necessary fields are populated + err := settings.Validate() + if err != nil { + return nil, err + } + + // send query to the database + err = e.client.Table(TableSettings).Create(settings.Nullify()).Error + if err != nil { + return nil, err + } + + return s, nil +} diff --git a/database/settings/create_test.go b/database/settings/create_test.go new file mode 100644 index 000000000..e621bf76e --- /dev/null +++ b/database/settings/create_test.go @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestSettings_Engine_CreateSettings(t *testing.T) { + // setup types + _settings := testSettings() + _settings.SetID(1) + _settings.SetCloneImage("target/vela-git:latest") + _settings.SetTemplateDepth(10) + _settings.SetStarlarkExecLimit(100) + _settings.SetRoutes([]string{"vela"}) + _settings.SetRepoAllowlist([]string{"octocat/hello-world"}) + _settings.SetScheduleAllowlist([]string{"*"}) + _settings.SetCreatedAt(1) + _settings.SetUpdatedAt(1) + _settings.SetUpdatedBy("") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`INSERT INTO "settings" ("compiler","queue","repo_allowlist","schedule_allowlist","created_at","updated_at","updated_by","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`). + WithArgs(`{"clone_image":{"String":"target/vela-git:latest","Valid":true},"template_depth":{"Int64":10,"Valid":true},"starlark_exec_limit":{"Int64":100,"Valid":true}}`, + `{"routes":["vela"]}`, `{"octocat/hello-world"}`, `{"*"}`, 1, 1, ``, 1). + WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CreateSettings(context.TODO(), _settings) + + if test.failure { + if err == nil { + t.Errorf("CreateSettings for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateSettings for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, _settings) { + t.Errorf("CreateSettings for %s returned %s, want %s", test.name, got, _settings) + } + }) + } +} diff --git a/database/settings/get.go b/database/settings/get.go new file mode 100644 index 000000000..96bc19ef1 --- /dev/null +++ b/database/settings/get.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/database/types" +) + +// GetSettings gets platform settings from the database. +func (e *engine) GetSettings(ctx context.Context) (*settings.Platform, error) { + e.logger.Trace("getting platform settings") + + // variable to store query results + s := new(types.Platform) + + // send query to the database and store result in variable + err := e.client. + Table(TableSettings). + Where("id = ?", 1). + Take(s). + Error + if err != nil { + return nil, err + } + + // return the settings + return s.ToAPI(), nil +} diff --git a/database/settings/get_test.go b/database/settings/get_test.go new file mode 100644 index 000000000..83196848b --- /dev/null +++ b/database/settings/get_test.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/api/types/settings" +) + +func TestSettings_Engine_GetSettings(t *testing.T) { + // setup types + _settings := testSettings() + _settings.SetID(1) + _settings.SetCloneImage("target/vela-git:latest") + _settings.SetTemplateDepth(10) + _settings.SetStarlarkExecLimit(100) + _settings.SetRoutes([]string{"vela"}) + _settings.SetRepoAllowlist([]string{"octocat/hello-world"}) + _settings.SetScheduleAllowlist([]string{"*"}) + _settings.SetCreatedAt(1) + _settings.SetUpdatedAt(1) + _settings.SetUpdatedBy("octocat") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "compiler", "queue", "repo_allowlist", "schedule_allowlist", "created_at", "updated_at", "updated_by"}). + AddRow(1, `{"clone_image":{"String":"target/vela-git:latest","Valid":true},"template_depth":{"Int64":10,"Valid":true},"starlark_exec_limit":{"Int64":100,"Valid":true}}`, + `{"routes":["vela"]}`, `{"octocat/hello-world"}`, `{"*"}`, 1, 1, `octocat`) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "settings" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateSettings(context.TODO(), _settings) + if err != nil { + t.Errorf("unable to create test settings for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *settings.Platform + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _settings, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _settings, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetSettings(context.TODO()) + + if test.failure { + if err == nil { + t.Errorf("GetSettings for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetSettings for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetSettings for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/settings/interface.go b/database/settings/interface.go new file mode 100644 index 000000000..a7cc755cd --- /dev/null +++ b/database/settings/interface.go @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/server/api/types/settings" +) + +// SettingsInterface represents the Vela interface for settings +// functions with the supported Database backends. +// +//nolint:revive // ignore name stutter +type SettingsInterface interface { + // CreateSettings defines a function that creates a platform settings record. + CreateSettings(context.Context, *settings.Platform) (*settings.Platform, error) + // GetSettings defines a function that gets platform settings. + GetSettings(context.Context) (*settings.Platform, error) + // UpdateSettings defines a function that updates platform settings. + UpdateSettings(context.Context, *settings.Platform) (*settings.Platform, error) +} diff --git a/database/settings/opts.go b/database/settings/opts.go new file mode 100644 index 000000000..a9646d1bf --- /dev/null +++ b/database/settings/opts.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for Settings. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for Settings. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the settings engine + e.client = client + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Settings. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the settings engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for Settings. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the settings engine + e.config.SkipCreation = skipCreation + + return nil + } +} + +// WithContext sets the context in the database engine for Settings. +func WithContext(ctx context.Context) EngineOpt { + return func(e *engine) error { + e.ctx = ctx + + return nil + } +} diff --git a/database/settings/opts_test.go b/database/settings/opts_test.go new file mode 100644 index 000000000..6fccdec17 --- /dev/null +++ b/database/settings/opts_test.go @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "reflect" + "testing" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func TestSettings_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestSettings_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestSettings_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} + +func TestSettings_EngineOpt_WithContext(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + ctx context.Context + want context.Context + }{ + { + failure: false, + name: "context set to TODO", + ctx: context.TODO(), + want: context.TODO(), + }, + { + failure: false, + name: "context set to nil", + ctx: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithContext(test.ctx)(e) + + if test.failure { + if err == nil { + t.Errorf("WithContext for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithContext returned err: %v", err) + } + + if !reflect.DeepEqual(e.ctx, test.want) { + t.Errorf("WithContext is %v, want %v", e.ctx, test.want) + } + }) + } +} diff --git a/database/settings/settings.go b/database/settings/settings.go new file mode 100644 index 000000000..e86ea23b1 --- /dev/null +++ b/database/settings/settings.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +const ( + TableSettings = "settings" +) + +type ( + // config represents the settings required to create the engine that implements the SettingsInterface interface. + config struct { + // specifies to skip creating tables and indexes for the Settings engine + SkipCreation bool + } + + // engine represents the settings functionality that implements the SettingsInterface interface. + engine struct { + // engine configuration settings used in settings functions + config *config + + ctx context.Context + + // gorm.io/gorm database client used in settings functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in settings functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with settings in the database. +// +//nolint:revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new Settings engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of settings table and indexes") + + return e, nil + } + + // create the settings table + err := e.CreateSettingsTable(e.ctx, e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", TableSettings, err) + } + + return e, nil +} diff --git a/database/settings/settings_test.go b/database/settings/settings_test.go new file mode 100644 index 000000000..0e1ac773f --- /dev/null +++ b/database/settings/settings_test.go @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + + "github.com/go-vela/server/api/types/settings" +) + +func TestSettings_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres settings engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite settings engine: %v", err) + } + + return _engine +} + +// testSettings is a test helper function to create an api +// Platform type with all fields set to their zero values. +func testSettings() *settings.Platform { + s := settings.PlatformMockEmpty() + + return &s +} diff --git a/database/settings/table.go b/database/settings/table.go new file mode 100644 index 000000000..18a4207ab --- /dev/null +++ b/database/settings/table.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres settings table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +settings ( + id SERIAL PRIMARY KEY, + compiler JSON DEFAULT NULL, + queue JSON DEFAULT NULL, + repo_allowlist VARCHAR(1000), + schedule_allowlist VARCHAR(1000), + created_at INTEGER, + updated_at INTEGER, + updated_by VARCHAR(250) +); +` + + // CreateSqliteTable represents a query to create the Sqlite settings table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + compiler TEXT, + queue TEXT, + repo_allowlist VARCHAR(1000), + schedule_allowlist VARCHAR(1000), + created_at INTEGER, + updated_at INTEGER, + updated_by TEXT +); +` +) + +// CreateSettingsTable creates the settings table in the database. +func (e *engine) CreateSettingsTable(_ context.Context, driver string) error { + e.logger.Tracef("creating settings table") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the steps table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the steps table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/settings/table_test.go b/database/settings/table_test.go new file mode 100644 index 000000000..13eaab42b --- /dev/null +++ b/database/settings/table_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestSettings_Engine_CreateSettingsTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateSettingsTable(context.TODO(), test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateSettingsTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateSettingsTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/settings/update.go b/database/settings/update.go new file mode 100644 index 000000000..c8a3e1679 --- /dev/null +++ b/database/settings/update.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/database/types" +) + +// UpdateSettings updates a platform settings in the database. +func (e *engine) UpdateSettings(_ context.Context, s *settings.Platform) (*settings.Platform, error) { + e.logger.Trace("updating platform settings in the database") + + // cast the api type to database type + dbS := types.SettingsFromAPI(s) + + // validate the necessary fields are populated + err := dbS.Validate() + if err != nil { + return nil, err + } + + // send query to the database + err = e.client.Table(TableSettings).Save(dbS.Nullify()).Error + if err != nil { + return nil, err + } + + s = dbS.ToAPI() + + return s, nil +} diff --git a/database/settings/update_test.go b/database/settings/update_test.go new file mode 100644 index 000000000..7b9f31a38 --- /dev/null +++ b/database/settings/update_test.go @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" +) + +func TestSettings_Engine_UpdateSettings(t *testing.T) { + // setup types + _settings := testSettings() + _settings.SetID(1) + _settings.SetCloneImage("target/vela-git:latest") + _settings.SetTemplateDepth(10) + _settings.SetStarlarkExecLimit(100) + _settings.SetRoutes([]string{"vela", "large"}) + _settings.SetRepoAllowlist([]string{"octocat/hello-world"}) + _settings.SetScheduleAllowlist([]string{"*"}) + _settings.SetCreatedAt(1) + _settings.SetUpdatedAt(1) + _settings.SetUpdatedBy("octocat") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "settings" SET "compiler"=$1,"queue"=$2,"repo_allowlist"=$3,"schedule_allowlist"=$4,"created_at"=$5,"updated_at"=$6,"updated_by"=$7 WHERE "id" = $8`). + WithArgs(`{"clone_image":{"String":"target/vela-git:latest","Valid":true},"template_depth":{"Int64":10,"Valid":true},"starlark_exec_limit":{"Int64":100,"Valid":true}}`, + `{"routes":["vela","large"]}`, `{"octocat/hello-world"}`, `{"*"}`, 1, testutils.AnyArgument{}, "octocat", 1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + _, err := _sqlite.CreateSettings(context.TODO(), _settings) + if err != nil { + t.Errorf("unable to create test settings for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.UpdateSettings(context.TODO(), _settings) + got.SetUpdatedAt(_settings.GetUpdatedAt()) + + if test.failure { + if err == nil { + t.Errorf("UpdateSettings for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateSettings for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, _settings) { + t.Errorf("UpdateSettings for %s returned %s, want %s", test.name, got, _settings) + } + }) + } +} diff --git a/database/step/clean.go b/database/step/clean.go index 0dc685709..3e75c906b 100644 --- a/database/step/clean.go +++ b/database/step/clean.go @@ -6,10 +6,11 @@ import ( "context" "time" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CleanSteps updates steps to an error with a created timestamp prior to a defined moment. diff --git a/database/step/clean_test.go b/database/step/clean_test.go index a7cadded5..132d3a00d 100644 --- a/database/step/clean_test.go +++ b/database/step/clean_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_CleanStep(t *testing.T) { // setup types - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -22,7 +24,7 @@ func TestStep_Engine_CleanStep(t *testing.T) { _stepOne.SetCreated(1) _stepOne.SetStatus("running") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(1) @@ -32,7 +34,7 @@ func TestStep_Engine_CleanStep(t *testing.T) { _stepTwo.SetCreated(1) _stepTwo.SetStatus("pending") - _stepThree := testStep() + _stepThree := testutils.APIStep() _stepThree.SetID(3) _stepThree.SetRepoID(1) _stepThree.SetBuildID(1) @@ -42,7 +44,7 @@ func TestStep_Engine_CleanStep(t *testing.T) { _stepThree.SetCreated(1) _stepThree.SetStatus("success") - _stepFour := testStep() + _stepFour := testutils.APIStep() _stepFour.SetID(4) _stepFour.SetRepoID(1) _stepFour.SetBuildID(1) diff --git a/database/step/count.go b/database/step/count.go index b47668bd0..d5c64ba34 100644 --- a/database/step/count.go +++ b/database/step/count.go @@ -4,12 +4,13 @@ package step import ( "context" + "github.com/go-vela/types/constants" ) // CountSteps gets the count of all steps from the database. func (e *engine) CountSteps(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all steps from the database") + e.logger.Tracef("getting count of all steps") // variable to store query results var s int64 diff --git a/database/step/count_build.go b/database/step/count_build.go index 8da485331..6b4d63fc4 100644 --- a/database/step/count_build.go +++ b/database/step/count_build.go @@ -4,16 +4,18 @@ package step import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // CountStepsForBuild gets the count of steps by build ID from the database. -func (e *engine) CountStepsForBuild(ctx context.Context, b *library.Build, filters map[string]interface{}) (int64, error) { +func (e *engine) CountStepsForBuild(ctx context.Context, b *api.Build, filters map[string]interface{}) (int64, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("getting count of steps for build %d from the database", b.GetNumber()) + }).Tracef("getting count of steps for build %d", b.GetNumber()) // variable to store query results var s int64 diff --git a/database/step/count_build_test.go b/database/step/count_build_test.go index e17b666f3..dad4c0a59 100644 --- a/database/step/count_build_test.go +++ b/database/step/count_build_test.go @@ -8,16 +8,18 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_CountStepsForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -25,7 +27,7 @@ func TestStep_Engine_CountStepsForBuild(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(2) diff --git a/database/step/count_test.go b/database/step/count_test.go index 621f0a7cd..bdc358a21 100644 --- a/database/step/count_test.go +++ b/database/step/count_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_CountSteps(t *testing.T) { // setup types - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestStep_Engine_CountSteps(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(2) diff --git a/database/step/create.go b/database/step/create.go index a4ab98cfe..99c9987b4 100644 --- a/database/step/create.go +++ b/database/step/create.go @@ -4,10 +4,12 @@ package step import ( "context" + + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // CreateStep creates a new step in the database. diff --git a/database/step/create_test.go b/database/step/create_test.go index b7a494c06..07824dc5a 100644 --- a/database/step/create_test.go +++ b/database/step/create_test.go @@ -8,17 +8,20 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_CreateStep(t *testing.T) { // setup types - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetRepoID(1) _step.SetBuildID(1) _step.SetNumber(1) _step.SetName("foo") _step.SetImage("bar") + _step.SetReportAs("test") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -28,9 +31,9 @@ func TestStep_Engine_CreateStep(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "steps" -("build_id","repo_id","number","name","image","stage","status","error","exit_code","created","started","finished","host","runtime","distribution","id") -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING "id"`). - WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +("build_id","repo_id","number","name","image","stage","status","error","exit_code","created","started","finished","host","runtime","distribution","report_as","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) RETURNING "id"`). + WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "test", 1). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/step/delete.go b/database/step/delete.go index 5b185a447..dfd2d5bfa 100644 --- a/database/step/delete.go +++ b/database/step/delete.go @@ -4,17 +4,19 @@ package step import ( "context" + + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // DeleteStep deletes an existing step from the database. func (e *engine) DeleteStep(ctx context.Context, s *library.Step) error { e.logger.WithFields(logrus.Fields{ "step": s.GetNumber(), - }).Tracef("deleting step %s from the database", s.GetName()) + }).Tracef("deleting step %s", s.GetName()) // cast the library type to database type // diff --git a/database/step/delete_test.go b/database/step/delete_test.go index 476ee969f..25eae5a69 100644 --- a/database/step/delete_test.go +++ b/database/step/delete_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_DeleteStep(t *testing.T) { // setup types - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetRepoID(1) _step.SetBuildID(1) diff --git a/database/step/get.go b/database/step/get.go index 8884fe51a..7a4e18041 100644 --- a/database/step/get.go +++ b/database/step/get.go @@ -4,6 +4,7 @@ package step import ( "context" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" @@ -11,7 +12,7 @@ import ( // GetStep gets a step by ID from the database. func (e *engine) GetStep(ctx context.Context, id int64) (*library.Step, error) { - e.logger.Tracef("getting step %d from the database", id) + e.logger.Tracef("getting step %d", id) // variable to store query results s := new(database.Step) diff --git a/database/step/get_build.go b/database/step/get_build.go index 6be0d3cb1..5d32ba582 100644 --- a/database/step/get_build.go +++ b/database/step/get_build.go @@ -4,18 +4,21 @@ package step import ( "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // GetStepForBuild gets a step by number and build ID from the database. -func (e *engine) GetStepForBuild(ctx context.Context, b *library.Build, number int) (*library.Step, error) { +func (e *engine) GetStepForBuild(ctx context.Context, b *api.Build, number int) (*library.Step, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), "step": number, - }).Tracef("getting step %d from the database", number) + }).Tracef("getting step %d", number) // variable to store query results s := new(database.Step) diff --git a/database/step/get_build_test.go b/database/step/get_build_test.go index 55669735c..c5490aa58 100644 --- a/database/step/get_build_test.go +++ b/database/step/get_build_test.go @@ -4,21 +4,23 @@ package step import ( "context" - "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestStep_Engine_GetStepForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetRepoID(1) _step.SetBuildID(1) @@ -34,8 +36,8 @@ func TestStep_Engine_GetStepForBuild(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). - AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution", "report_as"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "", "") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "steps" WHERE build_id = $1 AND number = $2 LIMIT $3`).WithArgs(1, 1, 1).WillReturnRows(_rows) @@ -85,8 +87,8 @@ func TestStep_Engine_GetStepForBuild(t *testing.T) { t.Errorf("GetStepForBuild for %s returned err: %v", test.name, err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepForBuild for %s is %v, want %v", test.name, got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("GetStepForBuild for %s is a mismatch (-want +got):\n%s", test.name, diff) } }) } diff --git a/database/step/get_test.go b/database/step/get_test.go index 762e69988..4c05103ea 100644 --- a/database/step/get_test.go +++ b/database/step/get_test.go @@ -8,18 +8,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestStep_Engine_GetStep(t *testing.T) { // setup types - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetRepoID(1) _step.SetBuildID(1) _step.SetNumber(1) _step.SetName("foo") _step.SetImage("bar") + ctx := context.TODO() _postgres, _mock := testPostgres(t) @@ -27,8 +30,8 @@ func TestStep_Engine_GetStep(t *testing.T) { // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). - AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution", "report_as"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "", "") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "steps" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) diff --git a/database/step/interface.go b/database/step/interface.go index 70bbc1c72..6de0cc168 100644 --- a/database/step/interface.go +++ b/database/step/interface.go @@ -4,6 +4,8 @@ package step import ( "context" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -28,7 +30,7 @@ type StepInterface interface { // CountSteps defines a function that gets the count of all steps. CountSteps(context.Context) (int64, error) // CountStepsForBuild defines a function that gets the count of steps by build ID. - CountStepsForBuild(context.Context, *library.Build, map[string]interface{}) (int64, error) + CountStepsForBuild(context.Context, *api.Build, map[string]interface{}) (int64, error) // CreateStep defines a function that creates a new step. CreateStep(context.Context, *library.Step) (*library.Step, error) // DeleteStep defines a function that deletes an existing step. @@ -36,11 +38,11 @@ type StepInterface interface { // GetStep defines a function that gets a step by ID. GetStep(context.Context, int64) (*library.Step, error) // GetStepForBuild defines a function that gets a step by number and build ID. - GetStepForBuild(context.Context, *library.Build, int) (*library.Step, error) + GetStepForBuild(context.Context, *api.Build, int) (*library.Step, error) // ListSteps defines a function that gets a list of all steps. ListSteps(ctx context.Context) ([]*library.Step, error) // ListStepsForBuild defines a function that gets a list of steps by build ID. - ListStepsForBuild(context.Context, *library.Build, map[string]interface{}, int, int) ([]*library.Step, int64, error) + ListStepsForBuild(context.Context, *api.Build, map[string]interface{}, int, int) ([]*library.Step, int64, error) // ListStepImageCount defines a function that gets a list of all step images and the count of their occurrence. ListStepImageCount(context.Context) (map[string]float64, error) // ListStepStatusCount defines a function that gets a list of all step statuses and the count of their occurrence. diff --git a/database/step/list.go b/database/step/list.go index b4dd0a329..0d53297bd 100644 --- a/database/step/list.go +++ b/database/step/list.go @@ -4,6 +4,7 @@ package step import ( "context" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" @@ -11,7 +12,7 @@ import ( // ListSteps gets a list of all steps from the database. func (e *engine) ListSteps(ctx context.Context) ([]*library.Step, error) { - e.logger.Trace("listing all steps from the database") + e.logger.Trace("listing all steps") // variables to store query results and return value count := int64(0) diff --git a/database/step/list_build.go b/database/step/list_build.go index 0a446af27..ac0a608d1 100644 --- a/database/step/list_build.go +++ b/database/step/list_build.go @@ -4,17 +4,20 @@ package step import ( "context" + + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // ListStepsForBuild gets a list of all steps from the database. -func (e *engine) ListStepsForBuild(ctx context.Context, b *library.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Step, int64, error) { +func (e *engine) ListStepsForBuild(ctx context.Context, b *api.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Step, int64, error) { e.logger.WithFields(logrus.Fields{ "build": b.GetNumber(), - }).Tracef("listing steps for build %d from the database", b.GetNumber()) + }).Tracef("listing steps for build %d", b.GetNumber()) // variables to store query results and return value count := int64(0) diff --git a/database/step/list_build_test.go b/database/step/list_build_test.go index db53531c2..327076fec 100644 --- a/database/step/list_build_test.go +++ b/database/step/list_build_test.go @@ -8,17 +8,19 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestStep_Engine_ListStepsForBuild(t *testing.T) { // setup types - _build := testBuild() + _build := testutils.APIBuild() _build.SetID(1) - _build.SetRepoID(1) + _build.SetRepo(testutils.APIRepo()) _build.SetNumber(1) - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -26,13 +28,14 @@ func TestStep_Engine_ListStepsForBuild(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(1) _stepTwo.SetNumber(2) _stepTwo.SetName("foo") _stepTwo.SetImage("bar") + _stepTwo.SetReportAs("test") _postgres, _mock := testPostgres(t) @@ -47,9 +50,9 @@ func TestStep_Engine_ListStepsForBuild(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). - AddRow(2, 1, 1, 2, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). - AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution", "report_as"}). + AddRow(2, 1, 1, 2, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "", "test"). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "", "") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "steps" WHERE build_id = $1 ORDER BY id DESC LIMIT $2`).WithArgs(1, 10).WillReturnRows(_rows) diff --git a/database/step/list_image.go b/database/step/list_image.go index 7669d637a..5b0f46aec 100644 --- a/database/step/list_image.go +++ b/database/step/list_image.go @@ -11,7 +11,7 @@ import ( // ListStepImageCount gets a list of all step images and the count of their occurrence from the database. func (e *engine) ListStepImageCount(ctx context.Context) (map[string]float64, error) { - e.logger.Tracef("getting count of all images for steps from the database") + e.logger.Tracef("getting count of all images for steps") // variables to store query results and return value s := []struct { diff --git a/database/step/list_image_test.go b/database/step/list_image_test.go index ea0b78585..0447f9fc4 100644 --- a/database/step/list_image_test.go +++ b/database/step/list_image_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_ListStepImageCount(t *testing.T) { // setup types - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestStep_Engine_ListStepImageCount(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(1) diff --git a/database/step/list_status.go b/database/step/list_status.go index d93db3c30..c74b8db3c 100644 --- a/database/step/list_status.go +++ b/database/step/list_status.go @@ -11,7 +11,7 @@ import ( // ListStepStatusCount gets a list of all step statuses and the count of their occurrence from the database. func (e *engine) ListStepStatusCount(ctx context.Context) (map[string]float64, error) { - e.logger.Tracef("getting count of all statuses for steps from the database") + e.logger.Tracef("getting count of all statuses for steps") // variables to store query results and return value s := []struct { diff --git a/database/step/list_status_test.go b/database/step/list_status_test.go index 22f9d51c1..e6fa36ab4 100644 --- a/database/step/list_status_test.go +++ b/database/step/list_status_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_ListStepStatusCount(t *testing.T) { // setup types - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -20,7 +22,7 @@ func TestStep_Engine_ListStepStatusCount(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(1) diff --git a/database/step/list_test.go b/database/step/list_test.go index 284ae802f..921a3f2f7 100644 --- a/database/step/list_test.go +++ b/database/step/list_test.go @@ -8,12 +8,14 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" "github.com/go-vela/types/library" ) func TestStep_Engine_ListSteps(t *testing.T) { // setup types - _stepOne := testStep() + _stepOne := testutils.APIStep() _stepOne.SetID(1) _stepOne.SetRepoID(1) _stepOne.SetBuildID(1) @@ -21,7 +23,7 @@ func TestStep_Engine_ListSteps(t *testing.T) { _stepOne.SetName("foo") _stepOne.SetImage("bar") - _stepTwo := testStep() + _stepTwo := testutils.APIStep() _stepTwo.SetID(2) _stepTwo.SetRepoID(1) _stepTwo.SetBuildID(2) @@ -42,9 +44,9 @@ func TestStep_Engine_ListSteps(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). - AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). - AddRow(2, 1, 2, 1, "bar", "foo", "", "", "", 0, 0, 0, 0, "", "", "") + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution", "report_as"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "", ""). + AddRow(2, 1, 2, 1, "bar", "foo", "", "", "", 0, 0, 0, 0, "", "", "", "") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "steps"`).WillReturnRows(_rows) diff --git a/database/step/opts.go b/database/step/opts.go index 50fdc6eb6..520c60d8d 100644 --- a/database/step/opts.go +++ b/database/step/opts.go @@ -4,8 +4,8 @@ package step import ( "context" - "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" "gorm.io/gorm" ) diff --git a/database/step/opts_test.go b/database/step/opts_test.go index 6338b2f51..f3fcc3797 100644 --- a/database/step/opts_test.go +++ b/database/step/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/step/step.go b/database/step/step.go index 96e3989ab..edb3cb26e 100644 --- a/database/step/step.go +++ b/database/step/step.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( diff --git a/database/step/step_test.go b/database/step/step_test.go index bbd2a09da..6643e5139 100644 --- a/database/step/step_test.go +++ b/database/step/step_test.go @@ -9,9 +9,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -164,66 +162,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testBuild is a test helper function to create a library -// Build type with all fields set to their zero values. -func testBuild() *library.Build { - return &library.Build{ - ID: new(int64), - RepoID: new(int64), - PipelineID: new(int64), - Number: new(int), - Parent: new(int), - Event: new(string), - EventAction: new(string), - Status: new(string), - Error: new(string), - Enqueued: new(int64), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Deploy: new(string), - Clone: new(string), - Source: new(string), - Title: new(string), - Message: new(string), - Commit: new(string), - Sender: new(string), - Author: new(string), - Email: new(string), - Link: new(string), - Branch: new(string), - Ref: new(string), - BaseRef: new(string), - HeadRef: new(string), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - -// testStep is a test helper function to create a library -// Step type with all fields set to their zero values. -func testStep() *library.Step { - return &library.Step{ - ID: new(int64), - BuildID: new(int64), - RepoID: new(int64), - Number: new(int), - Name: new(string), - Image: new(string), - Stage: new(string), - Status: new(string), - Error: new(string), - ExitCode: new(int), - Created: new(int64), - Started: new(int64), - Finished: new(int64), - Host: new(string), - Runtime: new(string), - Distribution: new(string), - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/step/table.go b/database/step/table.go index c322d67a2..445e7ac50 100644 --- a/database/step/table.go +++ b/database/step/table.go @@ -30,7 +30,11 @@ steps ( host VARCHAR(250), runtime VARCHAR(250), distribution VARCHAR(250), +<<<<<<< HEAD check_id INTEGER, +======= + report_as VARCHAR(250), +>>>>>>> hackathon/github_app UNIQUE(build_id, number) ); ` @@ -56,7 +60,11 @@ steps ( host TEXT, runtime TEXT, distribution TEXT, +<<<<<<< HEAD check_id INTEGER, +======= + report_as TEXT, +>>>>>>> hackathon/github_app UNIQUE(build_id, number) ); ` diff --git a/database/step/update.go b/database/step/update.go index 2417c87a0..57fee9e74 100644 --- a/database/step/update.go +++ b/database/step/update.go @@ -4,10 +4,12 @@ package step import ( "context" + + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/database" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // UpdateStep updates an existing step in the database. diff --git a/database/step/update_test.go b/database/step/update_test.go index 14ddf009c..06a589307 100644 --- a/database/step/update_test.go +++ b/database/step/update_test.go @@ -8,11 +8,13 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestStep_Engine_UpdateStep(t *testing.T) { // setup types - _step := testStep() + _step := testutils.APIStep() _step.SetID(1) _step.SetRepoID(1) _step.SetBuildID(1) @@ -28,9 +30,9 @@ func TestStep_Engine_UpdateStep(t *testing.T) { // ensure the mock expects the query _mock.ExpectExec(`UPDATE "steps" -SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"stage"=$6,"status"=$7,"error"=$8,"exit_code"=$9,"created"=$10,"started"=$11,"finished"=$12,"host"=$13,"runtime"=$14,"distribution"=$15 -WHERE "id" = $16`). - WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). +SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"stage"=$6,"status"=$7,"error"=$8,"exit_code"=$9,"created"=$10,"started"=$11,"finished"=$12,"host"=$13,"runtime"=$14,"distribution"=$15,"report_as"=$16 +WHERE "id" = $17`). + WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/testutils/api_resources.go b/database/testutils/api_resources.go new file mode 100644 index 000000000..0831567ae --- /dev/null +++ b/database/testutils/api_resources.go @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "crypto/rand" + "crypto/rsa" + + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwk" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/api/types/actions" + "github.com/go-vela/types/library" + "github.com/go-vela/types/raw" +) + +// API TEST RESOURCES +// +// These are API resources initialized to their zero values for testing. + +func APIBuild() *api.Build { + return &api.Build{ + ID: new(int64), + Repo: APIRepo(), + PipelineID: new(int64), + Number: new(int), + Parent: new(int), + Event: new(string), + EventAction: new(string), + Status: new(string), + Error: new(string), + Enqueued: new(int64), + Created: new(int64), + Started: new(int64), + Finished: new(int64), + Deploy: new(string), + DeployNumber: new(int64), + Clone: new(string), + Source: new(string), + Title: new(string), + Message: new(string), + Commit: new(string), + Sender: new(string), + SenderSCMID: new(string), + Author: new(string), + Email: new(string), + Link: new(string), + Branch: new(string), + Ref: new(string), + BaseRef: new(string), + HeadRef: new(string), + Host: new(string), + Runtime: new(string), + Distribution: new(string), + ApprovedAt: new(int64), + ApprovedBy: new(string), + } +} + +func APIDeployment() *library.Deployment { + builds := []*library.Build{} + + return &library.Deployment{ + ID: new(int64), + RepoID: new(int64), + Number: new(int64), + URL: new(string), + Commit: new(string), + Ref: new(string), + Task: new(string), + Target: new(string), + Description: new(string), + Payload: new(raw.StringSliceMap), + CreatedAt: new(int64), + CreatedBy: new(string), + Builds: builds, + } +} + +func APIEvents() *api.Events { + return &api.Events{ + Push: &actions.Push{ + Branch: new(bool), + Tag: new(bool), + DeleteBranch: new(bool), + DeleteTag: new(bool), + }, + PullRequest: &actions.Pull{ + Opened: new(bool), + Edited: new(bool), + Synchronize: new(bool), + Reopened: new(bool), + Labeled: new(bool), + Unlabeled: new(bool), + }, + Deployment: &actions.Deploy{ + Created: new(bool), + }, + Comment: &actions.Comment{ + Created: new(bool), + Edited: new(bool), + }, + Schedule: &actions.Schedule{ + Run: new(bool), + }, + } +} + +func APIRepo() *api.Repo { + return &api.Repo{ + ID: new(int64), + Owner: APIUser(), + BuildLimit: new(int64), + Timeout: new(int64), + Counter: new(int), + PipelineType: new(string), + Hash: new(string), + Org: new(string), + Name: new(string), + FullName: new(string), + Link: new(string), + Clone: new(string), + Branch: new(string), + Visibility: new(string), + PreviousName: new(string), + Private: new(bool), + Trusted: new(bool), + Active: new(bool), + AllowEvents: APIEvents(), + Topics: new([]string), + ApproveBuild: new(string), + } +} + +func APIUser() *api.User { + return &api.User{ + ID: new(int64), + Name: new(string), + RefreshToken: new(string), + Token: new(string), + Favorites: new([]string), + Dashboards: new([]string), + Active: new(bool), + Admin: new(bool), + } +} + +func APIHook() *library.Hook { + return &library.Hook{ + ID: new(int64), + RepoID: new(int64), + BuildID: new(int64), + Number: new(int), + SourceID: new(string), + Created: new(int64), + Host: new(string), + Event: new(string), + EventAction: new(string), + Branch: new(string), + Error: new(string), + Status: new(string), + Link: new(string), + WebhookID: new(int64), + } +} + +func APILog() *library.Log { + return &library.Log{ + ID: new(int64), + RepoID: new(int64), + BuildID: new(int64), + ServiceID: new(int64), + StepID: new(int64), + Data: new([]byte), + } +} + +func APISchedule() *api.Schedule { + return &api.Schedule{ + ID: new(int64), + Repo: APIRepo(), + Active: new(bool), + Name: new(string), + Entry: new(string), + CreatedAt: new(int64), + CreatedBy: new(string), + UpdatedAt: new(int64), + UpdatedBy: new(string), + ScheduledAt: new(int64), + Branch: new(string), + Error: new(string), + } +} + +func APIService() *library.Service { + return &library.Service{ + ID: new(int64), + BuildID: new(int64), + RepoID: new(int64), + Number: new(int), + Name: new(string), + Image: new(string), + Status: new(string), + Error: new(string), + ExitCode: new(int), + Created: new(int64), + Started: new(int64), + Finished: new(int64), + Host: new(string), + Runtime: new(string), + Distribution: new(string), + } +} + +func APIStep() *library.Step { + return &library.Step{ + ID: new(int64), + BuildID: new(int64), + RepoID: new(int64), + Number: new(int), + Name: new(string), + Image: new(string), + Stage: new(string), + Status: new(string), + Error: new(string), + ExitCode: new(int), + Created: new(int64), + Started: new(int64), + Finished: new(int64), + Host: new(string), + Runtime: new(string), + Distribution: new(string), + ReportAs: new(string), + } +} + +func APIPipeline() *library.Pipeline { + return &library.Pipeline{ + ID: new(int64), + RepoID: new(int64), + Commit: new(string), + Flavor: new(string), + Platform: new(string), + Ref: new(string), + Type: new(string), + Version: new(string), + ExternalSecrets: new(bool), + InternalSecrets: new(bool), + Services: new(bool), + Stages: new(bool), + Steps: new(bool), + Templates: new(bool), + Data: new([]byte), + } +} + +func APIDashboard() *api.Dashboard { + return &api.Dashboard{ + ID: new(string), + Name: new(string), + CreatedAt: new(int64), + CreatedBy: new(string), + UpdatedAt: new(int64), + UpdatedBy: new(string), + Admins: &[]*api.User{APIUser()}, + Repos: &[]*api.DashboardRepo{APIDashboardRepo()}, + } +} + +func APIDashboardRepo() *api.DashboardRepo { + return &api.DashboardRepo{ + ID: new(int64), + Branches: new([]string), + Events: new([]string), + } +} + +func JWK() jwk.RSAPublicKey { + privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil + } + + pubJwk, err := jwk.FromRaw(privateRSAKey.PublicKey) + if err != nil { + return nil + } + + switch j := pubJwk.(type) { + case jwk.RSAPublicKey: + // assign KID to key pair + kid, err := uuid.NewV7() + if err != nil { + return nil + } + + err = pubJwk.Set(jwk.KeyIDKey, kid.String()) + if err != nil { + return nil + } + + return j + default: + return nil + } +} diff --git a/database/testutils/mock_args.go b/database/testutils/mock_args.go new file mode 100644 index 000000000..29a362664 --- /dev/null +++ b/database/testutils/mock_args.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + "database/sql/driver" + "reflect" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/lestrrat-go/jwx/v2/jwk" +) + +// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values +// that are otherwise not easily compared. These typically would be values generated +// before adding or updating them in the database. +// +// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime +type AnyArgument struct{} + +// Match satisfies sqlmock.Argument interface. +func (a AnyArgument) Match(_ driver.Value) bool { + return true +} + +// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience. +type NowTimestamp struct{} + +// Match satisfies sqlmock.Argument interface. +func (t NowTimestamp) Match(v driver.Value) bool { + ts, ok := v.(int64) + if !ok { + return false + } + + now := time.Now().Unix() + + return now-ts < 10 +} + +var JwkKeyOpts = cmp.Options{ + cmp.FilterValues(func(x, y interface{}) bool { + _, xOk := x.(jwk.RSAPublicKey) + _, yOk := y.(jwk.RSAPublicKey) + return xOk && yOk + }, cmp.Comparer(func(x, y interface{}) bool { + xJWK := x.(jwk.RSAPublicKey) + yJWK := y.(jwk.RSAPublicKey) + + var rawXKey, rawYKey interface{} + + if err := xJWK.Raw(&rawXKey); err != nil { + return false + } + + if err := yJWK.Raw(&rawYKey); err != nil { + return false + } + + return reflect.DeepEqual(rawXKey, rawYKey) && xJWK.KeyID() == yJWK.KeyID() + })), +} diff --git a/database/types/build.go b/database/types/build.go new file mode 100644 index 000000000..66157607e --- /dev/null +++ b/database/types/build.go @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "errors" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/util" + "github.com/go-vela/types/raw" +) + +var ( + // ErrEmptyBuildNumber defines the error type when a + // Build type has an empty Number field provided. + ErrEmptyBuildNumber = errors.New("empty build number provided") + + // ErrEmptyBuildRepoID defines the error type when a + // Build type has an empty `RepoID` field provided. + ErrEmptyBuildRepoID = errors.New("empty build repo_id provided") +) + +const ( + // Maximum title field length. + maxTitleLength = 1000 + // Maximum message field length. + maxMessageLength = 2000 + // Maximum error field length. + maxErrorLength = 1000 +) + +// Build is the database representation of a build for a pipeline. +type Build struct { + ID sql.NullInt64 `sql:"id"` + RepoID sql.NullInt64 `sql:"repo_id"` + PipelineID sql.NullInt64 `sql:"pipeline_id"` + Number sql.NullInt32 `sql:"number"` + Parent sql.NullInt32 `sql:"parent"` + Event sql.NullString `sql:"event"` + EventAction sql.NullString `sql:"event_action"` + Status sql.NullString `sql:"status"` + Error sql.NullString `sql:"error"` + Enqueued sql.NullInt64 `sql:"enqueued"` + Created sql.NullInt64 `sql:"created"` + Started sql.NullInt64 `sql:"started"` + Finished sql.NullInt64 `sql:"finished"` + Deploy sql.NullString `sql:"deploy"` + DeployNumber sql.NullInt64 `sql:"deploy_number"` + DeployPayload raw.StringSliceMap `sql:"deploy_payload" gorm:"type:varchar(2000)"` + Clone sql.NullString `sql:"clone"` + Source sql.NullString `sql:"source"` + Title sql.NullString `sql:"title"` + Message sql.NullString `sql:"message"` + Commit sql.NullString `sql:"commit"` + Sender sql.NullString `sql:"sender"` + SenderSCMID sql.NullString `sql:"sender_scm_id"` + Author sql.NullString `sql:"author"` + Email sql.NullString `sql:"email"` + Link sql.NullString `sql:"link"` + Branch sql.NullString `sql:"branch"` + Ref sql.NullString `sql:"ref"` + BaseRef sql.NullString `sql:"base_ref"` + HeadRef sql.NullString `sql:"head_ref"` + Host sql.NullString `sql:"host"` + Runtime sql.NullString `sql:"runtime"` + Distribution sql.NullString `sql:"distribution"` + ApprovedAt sql.NullInt64 `sql:"approved_at"` + ApprovedBy sql.NullString `sql:"approved_by"` + + Repo Repo `gorm:"foreignKey:RepoID"` +} + +// Crop prepares the Build type for inserting into the database by +// trimming values that may exceed the database column limit. +func (b *Build) Crop() *Build { + // trim the Title field to 1000 characters + if len(b.Title.String) > maxTitleLength { + b.Title = sql.NullString{String: b.Title.String[:maxTitleLength], Valid: true} + } + + // trim the Message field to 2000 characters + if len(b.Message.String) > maxMessageLength { + b.Message = sql.NullString{String: b.Message.String[:maxMessageLength], Valid: true} + } + + // trim the Error field to 1000 characters + if len(b.Error.String) > maxErrorLength { + b.Error = sql.NullString{String: b.Error.String[:maxErrorLength], Valid: true} + } + + return b +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Build type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +// +//nolint:gocyclo,funlen // ignore cyclomatic complexity due to number of fields +func (b *Build) Nullify() *Build { + if b == nil { + return nil + } + + // check if the ID field should be false + if b.ID.Int64 == 0 { + b.ID.Valid = false + } + + // check if the RepoID field should be false + if b.RepoID.Int64 == 0 { + b.RepoID.Valid = false + } + + // check if the PipelineID field should be false + if b.PipelineID.Int64 == 0 { + b.PipelineID.Valid = false + } + + // check if the Number field should be false + if b.Number.Int32 == 0 { + b.Number.Valid = false + } + + // check if the Parent field should be false + if b.Parent.Int32 == 0 { + b.Parent.Valid = false + } + + // check if the Event field should be false + if len(b.Event.String) == 0 { + b.Event.Valid = false + } + + // check if the EventAction field should be false + if len(b.EventAction.String) == 0 { + b.EventAction.Valid = false + } + + // check if the Status field should be false + if len(b.Status.String) == 0 { + b.Status.Valid = false + } + + // check if the Error field should be false + if len(b.Error.String) == 0 { + b.Error.Valid = false + } + + // check if the Enqueued field should be false + if b.Enqueued.Int64 == 0 { + b.Enqueued.Valid = false + } + + // check if the Created field should be false + if b.Created.Int64 == 0 { + b.Created.Valid = false + } + + // check if the Started field should be false + if b.Started.Int64 == 0 { + b.Started.Valid = false + } + + // check if the Finished field should be false + if b.Finished.Int64 == 0 { + b.Finished.Valid = false + } + + // check if the Deploy field should be false + if len(b.Deploy.String) == 0 { + b.Deploy.Valid = false + } + + // check if the DeployNumber field should be false + if b.DeployNumber.Int64 == 0 { + b.DeployNumber.Valid = false + } + + // check if the Clone field should be false + if len(b.Clone.String) == 0 { + b.Clone.Valid = false + } + + // check if the Source field should be false + if len(b.Source.String) == 0 { + b.Source.Valid = false + } + + // check if the Title field should be false + if len(b.Title.String) == 0 { + b.Title.Valid = false + } + + // check if the Message field should be false + if len(b.Message.String) == 0 { + b.Message.Valid = false + } + + // check if the Commit field should be false + if len(b.Commit.String) == 0 { + b.Commit.Valid = false + } + + // check if the Sender field should be false + if len(b.Sender.String) == 0 { + b.Sender.Valid = false + } + + // check if the SenderSCMID field should be false + if len(b.SenderSCMID.String) == 0 { + b.SenderSCMID.Valid = false + } + + // check if the Author field should be false + if len(b.Author.String) == 0 { + b.Author.Valid = false + } + + // check if the Email field should be false + if len(b.Email.String) == 0 { + b.Email.Valid = false + } + + // check if the Link field should be false + if len(b.Link.String) == 0 { + b.Link.Valid = false + } + + // check if the Branch field should be false + if len(b.Branch.String) == 0 { + b.Branch.Valid = false + } + + // check if the Ref field should be false + if len(b.Ref.String) == 0 { + b.Ref.Valid = false + } + + // check if the BaseRef field should be false + if len(b.BaseRef.String) == 0 { + b.BaseRef.Valid = false + } + + // check if the HeadRef field should be false + if len(b.HeadRef.String) == 0 { + b.HeadRef.Valid = false + } + + // check if the Host field should be false + if len(b.Host.String) == 0 { + b.Host.Valid = false + } + + // check if the Runtime field should be false + if len(b.Runtime.String) == 0 { + b.Runtime.Valid = false + } + + // check if the Distribution field should be false + if len(b.Distribution.String) == 0 { + b.Distribution.Valid = false + } + + // check if the ApprovedAt field should be false + if b.ApprovedAt.Int64 == 0 { + b.ApprovedAt.Valid = false + } + + // check if the ApprovedBy field should be false + if len(b.ApprovedBy.String) == 0 { + b.ApprovedBy.Valid = false + } + + return b +} + +// ToAPI converts the Build type +// to an API Build type. +func (b *Build) ToAPI() *api.Build { + build := new(api.Build) + + build.SetID(b.ID.Int64) + build.SetRepo(b.Repo.ToAPI()) + build.SetPipelineID(b.PipelineID.Int64) + build.SetNumber(int(b.Number.Int32)) + build.SetParent(int(b.Parent.Int32)) + build.SetEvent(b.Event.String) + build.SetEventAction(b.EventAction.String) + build.SetStatus(b.Status.String) + build.SetError(b.Error.String) + build.SetEnqueued(b.Enqueued.Int64) + build.SetCreated(b.Created.Int64) + build.SetStarted(b.Started.Int64) + build.SetFinished(b.Finished.Int64) + build.SetDeploy(b.Deploy.String) + build.SetDeployNumber(b.DeployNumber.Int64) + build.SetDeployPayload(b.DeployPayload) + build.SetClone(b.Clone.String) + build.SetSource(b.Source.String) + build.SetTitle(b.Title.String) + build.SetMessage(b.Message.String) + build.SetCommit(b.Commit.String) + build.SetSender(b.Sender.String) + build.SetSenderSCMID(b.SenderSCMID.String) + build.SetAuthor(b.Author.String) + build.SetEmail(b.Email.String) + build.SetLink(b.Link.String) + build.SetBranch(b.Branch.String) + build.SetRef(b.Ref.String) + build.SetBaseRef(b.BaseRef.String) + build.SetHeadRef(b.HeadRef.String) + build.SetHost(b.Host.String) + build.SetRuntime(b.Runtime.String) + build.SetDistribution(b.Distribution.String) + build.SetApprovedAt(b.ApprovedAt.Int64) + build.SetApprovedBy(b.ApprovedBy.String) + + return build +} + +// Validate verifies the necessary fields for +// the Build type are populated correctly. +func (b *Build) Validate() error { + // verify the RepoID field is populated + if b.RepoID.Int64 <= 0 { + return ErrEmptyBuildRepoID + } + + // verify the Number field is populated + if b.Number.Int32 <= 0 { + return ErrEmptyBuildNumber + } + + // ensure that all Build string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + b.Event = sql.NullString{String: util.Sanitize(b.Event.String), Valid: b.Event.Valid} + b.EventAction = sql.NullString{String: util.Sanitize(b.EventAction.String), Valid: b.EventAction.Valid} + b.Status = sql.NullString{String: util.Sanitize(b.Status.String), Valid: b.Status.Valid} + b.Error = sql.NullString{String: util.Sanitize(b.Error.String), Valid: b.Error.Valid} + b.Deploy = sql.NullString{String: util.Sanitize(b.Deploy.String), Valid: b.Deploy.Valid} + b.Clone = sql.NullString{String: util.Sanitize(b.Clone.String), Valid: b.Clone.Valid} + b.Source = sql.NullString{String: util.Sanitize(b.Source.String), Valid: b.Source.Valid} + b.Title = sql.NullString{String: util.Sanitize(b.Title.String), Valid: b.Title.Valid} + b.Message = sql.NullString{String: util.Sanitize(b.Message.String), Valid: b.Message.Valid} + b.Commit = sql.NullString{String: util.Sanitize(b.Commit.String), Valid: b.Commit.Valid} + b.Sender = sql.NullString{String: util.Sanitize(b.Sender.String), Valid: b.Sender.Valid} + b.SenderSCMID = sql.NullString{String: util.Sanitize(b.SenderSCMID.String), Valid: b.SenderSCMID.Valid} + b.Author = sql.NullString{String: util.Sanitize(b.Author.String), Valid: b.Author.Valid} + b.Email = sql.NullString{String: util.Sanitize(b.Email.String), Valid: b.Email.Valid} + b.Link = sql.NullString{String: util.Sanitize(b.Link.String), Valid: b.Link.Valid} + b.Branch = sql.NullString{String: util.Sanitize(b.Branch.String), Valid: b.Branch.Valid} + b.Ref = sql.NullString{String: util.Sanitize(b.Ref.String), Valid: b.Ref.Valid} + b.BaseRef = sql.NullString{String: util.Sanitize(b.BaseRef.String), Valid: b.BaseRef.Valid} + b.HeadRef = sql.NullString{String: util.Sanitize(b.HeadRef.String), Valid: b.HeadRef.Valid} + b.Host = sql.NullString{String: util.Sanitize(b.Host.String), Valid: b.Host.Valid} + b.Runtime = sql.NullString{String: util.Sanitize(b.Runtime.String), Valid: b.Runtime.Valid} + b.Distribution = sql.NullString{String: util.Sanitize(b.Distribution.String), Valid: b.Distribution.Valid} + b.ApprovedBy = sql.NullString{String: util.Sanitize(b.ApprovedBy.String), Valid: b.ApprovedBy.Valid} + + return nil +} + +// BuildFromAPI converts the API Build type +// to a database build type. +func BuildFromAPI(b *api.Build) *Build { + build := &Build{ + ID: sql.NullInt64{Int64: b.GetID(), Valid: true}, + RepoID: sql.NullInt64{Int64: b.GetRepo().GetID(), Valid: true}, + PipelineID: sql.NullInt64{Int64: b.GetPipelineID(), Valid: true}, + Number: sql.NullInt32{Int32: int32(b.GetNumber()), Valid: true}, + Parent: sql.NullInt32{Int32: int32(b.GetParent()), Valid: true}, + Event: sql.NullString{String: b.GetEvent(), Valid: true}, + EventAction: sql.NullString{String: b.GetEventAction(), Valid: true}, + Status: sql.NullString{String: b.GetStatus(), Valid: true}, + Error: sql.NullString{String: b.GetError(), Valid: true}, + Enqueued: sql.NullInt64{Int64: b.GetEnqueued(), Valid: true}, + Created: sql.NullInt64{Int64: b.GetCreated(), Valid: true}, + Started: sql.NullInt64{Int64: b.GetStarted(), Valid: true}, + Finished: sql.NullInt64{Int64: b.GetFinished(), Valid: true}, + Deploy: sql.NullString{String: b.GetDeploy(), Valid: true}, + DeployNumber: sql.NullInt64{Int64: b.GetDeployNumber(), Valid: true}, + DeployPayload: b.GetDeployPayload(), + Clone: sql.NullString{String: b.GetClone(), Valid: true}, + Source: sql.NullString{String: b.GetSource(), Valid: true}, + Title: sql.NullString{String: b.GetTitle(), Valid: true}, + Message: sql.NullString{String: b.GetMessage(), Valid: true}, + Commit: sql.NullString{String: b.GetCommit(), Valid: true}, + Sender: sql.NullString{String: b.GetSender(), Valid: true}, + SenderSCMID: sql.NullString{String: b.GetSenderSCMID(), Valid: true}, + Author: sql.NullString{String: b.GetAuthor(), Valid: true}, + Email: sql.NullString{String: b.GetEmail(), Valid: true}, + Link: sql.NullString{String: b.GetLink(), Valid: true}, + Branch: sql.NullString{String: b.GetBranch(), Valid: true}, + Ref: sql.NullString{String: b.GetRef(), Valid: true}, + BaseRef: sql.NullString{String: b.GetBaseRef(), Valid: true}, + HeadRef: sql.NullString{String: b.GetHeadRef(), Valid: true}, + Host: sql.NullString{String: b.GetHost(), Valid: true}, + Runtime: sql.NullString{String: b.GetRuntime(), Valid: true}, + Distribution: sql.NullString{String: b.GetDistribution(), Valid: true}, + ApprovedAt: sql.NullInt64{Int64: b.GetApprovedAt(), Valid: true}, + ApprovedBy: sql.NullString{String: b.GetApprovedBy(), Valid: true}, + } + + return build.Nullify() +} diff --git a/database/types/build_test.go b/database/types/build_test.go new file mode 100644 index 000000000..3eb12f142 --- /dev/null +++ b/database/types/build_test.go @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "math/rand" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/raw" +) + +func TestTypes_Build_Crop(t *testing.T) { + // setup types + title := randomString(1001) + message := randomString(2001) + err := randomString(1001) + + b := testBuild() + b.Title = sql.NullString{String: title, Valid: true} + b.Message = sql.NullString{String: message, Valid: true} + b.Error = sql.NullString{String: err, Valid: true} + + want := testBuild() + want.Title = sql.NullString{String: title[:1000], Valid: true} + want.Message = sql.NullString{String: message[:2000], Valid: true} + want.Error = sql.NullString{String: err[:1000], Valid: true} + + // run test + got := b.Crop() + + if !reflect.DeepEqual(got, want) { + t.Errorf("Crop is %v, want %v", got, want) + } +} + +func TestTypes_Build_Nullify(t *testing.T) { + // setup types + var b *Build + + want := &Build{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + RepoID: sql.NullInt64{Int64: 0, Valid: false}, + PipelineID: sql.NullInt64{Int64: 0, Valid: false}, + Number: sql.NullInt32{Int32: 0, Valid: false}, + Parent: sql.NullInt32{Int32: 0, Valid: false}, + Event: sql.NullString{String: "", Valid: false}, + EventAction: sql.NullString{String: "", Valid: false}, + Status: sql.NullString{String: "", Valid: false}, + Error: sql.NullString{String: "", Valid: false}, + Enqueued: sql.NullInt64{Int64: 0, Valid: false}, + Created: sql.NullInt64{Int64: 0, Valid: false}, + Started: sql.NullInt64{Int64: 0, Valid: false}, + Finished: sql.NullInt64{Int64: 0, Valid: false}, + Deploy: sql.NullString{String: "", Valid: false}, + DeployNumber: sql.NullInt64{Int64: 0, Valid: false}, + DeployPayload: nil, + Clone: sql.NullString{String: "", Valid: false}, + Source: sql.NullString{String: "", Valid: false}, + Title: sql.NullString{String: "", Valid: false}, + Message: sql.NullString{String: "", Valid: false}, + Commit: sql.NullString{String: "", Valid: false}, + Sender: sql.NullString{String: "", Valid: false}, + Author: sql.NullString{String: "", Valid: false}, + Email: sql.NullString{String: "", Valid: false}, + Link: sql.NullString{String: "", Valid: false}, + Branch: sql.NullString{String: "", Valid: false}, + Ref: sql.NullString{String: "", Valid: false}, + BaseRef: sql.NullString{String: "", Valid: false}, + HeadRef: sql.NullString{String: "", Valid: false}, + Host: sql.NullString{String: "", Valid: false}, + Runtime: sql.NullString{String: "", Valid: false}, + Distribution: sql.NullString{String: "", Valid: false}, + } + + // setup tests + tests := []struct { + build *Build + want *Build + }{ + { + build: testBuild(), + want: testBuild(), + }, + { + build: b, + want: nil, + }, + { + build: new(Build), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.build.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Build_ToAPI(t *testing.T) { + // setup types + want := new(api.Build) + + want.SetID(1) + want.SetRepo(testRepo().ToAPI()) + want.SetPipelineID(1) + want.SetNumber(1) + want.SetParent(1) + want.SetEvent("push") + want.SetEventAction("") + want.SetStatus("running") + want.SetError("") + want.SetEnqueued(1563474077) + want.SetCreated(1563474076) + want.SetStarted(1563474078) + want.SetFinished(1563474079) + want.SetDeploy("") + want.SetDeployNumber(0) + want.SetDeployPayload(nil) + want.SetClone("https://github.com/github/octocat.git") + want.SetSource("https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163") + want.SetTitle("push received from https://github.com/github/octocat") + want.SetMessage("First commit...") + want.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") + want.SetSender("OctoKitty") + want.SetSenderSCMID("123") + want.SetAuthor("OctoKitty") + want.SetEmail("OctoKitty@github.com") + want.SetLink("https://example.company.com/github/octocat/1") + want.SetBranch("main") + want.SetRef("refs/heads/main") + want.SetBaseRef("") + want.SetHeadRef("") + want.SetHost("example.company.com") + want.SetRuntime("docker") + want.SetDistribution("linux") + want.SetDeployPayload(raw.StringSliceMap{"foo": "test1", "bar": "test2"}) + want.SetApprovedAt(1563474076) + want.SetApprovedBy("OctoCat") + + // run test + got := testBuild().ToAPI() + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ToAPI() mismatch (-want +got):\n%s", diff) + } +} + +func TestTypes_Build_Validate(t *testing.T) { + // setup tests + tests := []struct { + failure bool + build *Build + }{ + { + failure: false, + build: testBuild(), + }, + { // no repo_id set for build + failure: true, + build: &Build{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Number: sql.NullInt32{Int32: 1, Valid: true}, + }, + }, + { // no number set for build + failure: true, + build: &Build{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + }, + }, + } + + // run tests + for _, test := range tests { + err := test.build.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestTypes_Build_BuildFromAPI(t *testing.T) { + // setup types + b := new(api.Build) + + r := testutils.APIRepo() + r.SetID(1) + + b.SetID(1) + b.SetRepo(r) + b.SetPipelineID(1) + b.SetNumber(1) + b.SetParent(1) + b.SetEvent("push") + b.SetEventAction("") + b.SetStatus("running") + b.SetError("") + b.SetEnqueued(1563474077) + b.SetCreated(1563474076) + b.SetStarted(1563474078) + b.SetFinished(1563474079) + b.SetDeploy("") + b.SetDeployNumber(0) + b.SetDeployPayload(nil) + b.SetClone("https://github.com/github/octocat.git") + b.SetSource("https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetTitle("push received from https://github.com/github/octocat") + b.SetMessage("First commit...") + b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetSender("OctoKitty") + b.SetSenderSCMID("123") + b.SetAuthor("OctoKitty") + b.SetEmail("OctoKitty@github.com") + b.SetLink("https://example.company.com/github/octocat/1") + b.SetBranch("main") + b.SetRef("refs/heads/main") + b.SetBaseRef("") + b.SetHeadRef("") + b.SetHost("example.company.com") + b.SetRuntime("docker") + b.SetDistribution("linux") + b.SetDeployPayload(raw.StringSliceMap{"foo": "test1", "bar": "test2"}) + b.SetApprovedAt(1563474076) + b.SetApprovedBy("OctoCat") + + want := testBuild() + want.Repo = Repo{} + + // run test + got := BuildFromAPI(b) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("FromAPI() mismatch (-want +got):\n%s", diff) + } +} + +func randomString(n int) string { + var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + b := make([]rune, n) + for i := range b { + //nolint:gosec // accepting weak RNG for test + b[i] = letter[rand.Intn(len(letter))] + } + + return string(b) +} + +// testBuild is a test helper function to create a Build +// type with all fields set to a fake value. +func testBuild() *Build { + return &Build{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + PipelineID: sql.NullInt64{Int64: 1, Valid: true}, + Number: sql.NullInt32{Int32: 1, Valid: true}, + Parent: sql.NullInt32{Int32: 1, Valid: true}, + Event: sql.NullString{String: "push", Valid: true}, + EventAction: sql.NullString{String: "", Valid: false}, + Status: sql.NullString{String: "running", Valid: true}, + Error: sql.NullString{String: "", Valid: false}, + Enqueued: sql.NullInt64{Int64: 1563474077, Valid: true}, + Created: sql.NullInt64{Int64: 1563474076, Valid: true}, + Started: sql.NullInt64{Int64: 1563474078, Valid: true}, + Finished: sql.NullInt64{Int64: 1563474079, Valid: true}, + Deploy: sql.NullString{String: "", Valid: false}, + DeployNumber: sql.NullInt64{Int64: 0, Valid: false}, + DeployPayload: raw.StringSliceMap{"foo": "test1", "bar": "test2"}, + Clone: sql.NullString{String: "https://github.com/github/octocat.git", Valid: true}, + Source: sql.NullString{String: "https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163", Valid: true}, + Title: sql.NullString{String: "push received from https://github.com/github/octocat", Valid: true}, + Message: sql.NullString{String: "First commit...", Valid: true}, + Commit: sql.NullString{String: "48afb5bdc41ad69bf22588491333f7cf71135163", Valid: true}, + Sender: sql.NullString{String: "OctoKitty", Valid: true}, + SenderSCMID: sql.NullString{String: "123", Valid: true}, + Author: sql.NullString{String: "OctoKitty", Valid: true}, + Email: sql.NullString{String: "OctoKitty@github.com", Valid: true}, + Link: sql.NullString{String: "https://example.company.com/github/octocat/1", Valid: true}, + Branch: sql.NullString{String: "main", Valid: true}, + Ref: sql.NullString{String: "refs/heads/main", Valid: true}, + BaseRef: sql.NullString{String: "", Valid: false}, + HeadRef: sql.NullString{String: "", Valid: false}, + Host: sql.NullString{String: "example.company.com", Valid: true}, + Runtime: sql.NullString{String: "docker", Valid: true}, + Distribution: sql.NullString{String: "linux", Valid: true}, + ApprovedAt: sql.NullInt64{Int64: 1563474076, Valid: true}, + ApprovedBy: sql.NullString{String: "OctoCat", Valid: true}, + + Repo: *testRepo(), + } +} diff --git a/database/types/dashboard.go b/database/types/dashboard.go new file mode 100644 index 000000000..0d6023058 --- /dev/null +++ b/database/types/dashboard.go @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + + "github.com/google/uuid" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/util" +) + +var ( + // ErrEmptyDashName defines the error type when a + // User type has an empty Name field provided. + ErrEmptyDashName = errors.New("empty dashboard name provided") + + // ErrExceededAdminLimit defines the error type when a + // User type has Admins field provided that exceeds the database limit. + ErrExceededAdminLimit = errors.New("exceeded admins limit") +) + +type ( + // Dashboard is the database representation of a dashboard. + Dashboard struct { + ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v7()"` + Name sql.NullString `sql:"name"` + CreatedAt sql.NullInt64 `sql:"created_at"` + CreatedBy sql.NullString `sql:"created_by"` + UpdatedAt sql.NullInt64 `sql:"updated_at"` + UpdatedBy sql.NullString `sql:"updated_by"` + Admins AdminsJSON + Repos DashReposJSON + } + + DashReposJSON []*api.DashboardRepo + AdminsJSON []*api.User +) + +// Value - Implementation of valuer for database/sql for DashReposJSON. +func (r DashReposJSON) Value() (driver.Value, error) { + valueString, err := json.Marshal(r) + return string(valueString), err +} + +// Scan - Implement the database/sql scanner interface for DashReposJSON. +func (r *DashReposJSON) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return json.Unmarshal(v, &r) + case string: + return json.Unmarshal([]byte(v), &r) + default: + return fmt.Errorf("wrong type for repos: %T", v) + } +} + +// Value - Implementation of valuer for database/sql for AdminsJSON. +func (a AdminsJSON) Value() (driver.Value, error) { + valueString, err := json.Marshal(a) + return string(valueString), err +} + +// Scan - Implement the database/sql scanner interface for AdminsJSON. +func (a *AdminsJSON) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return json.Unmarshal(v, &a) + case string: + return json.Unmarshal([]byte(v), &a) + default: + return fmt.Errorf("wrong type for admins: %T", v) + } +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Dashboard type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (d *Dashboard) Nullify() *Dashboard { + if d == nil { + return nil + } + + // check if the Name field should be false + if len(d.Name.String) == 0 { + d.Name.Valid = false + } + + // check if the CreatedAt field should be false + if d.CreatedAt.Int64 == 0 { + d.CreatedAt.Valid = false + } + + // check if the CreatedBy field should be false + if len(d.CreatedBy.String) == 0 { + d.CreatedBy.Valid = false + } + + // check if the UpdatedAt field should be false + if d.UpdatedAt.Int64 == 0 { + d.UpdatedAt.Valid = false + } + + // check if the UpdatedBy field should be false + if len(d.UpdatedBy.String) == 0 { + d.UpdatedBy.Valid = false + } + + return d +} + +// ToAPI converts the Dashboard type +// to an API Dashboard type. +func (d *Dashboard) ToAPI() *api.Dashboard { + dashboard := new(api.Dashboard) + + dashboard.SetID(d.ID.String()) + dashboard.SetName(d.Name.String) + dashboard.SetAdmins(d.Admins) + dashboard.SetCreatedAt(d.CreatedAt.Int64) + dashboard.SetCreatedBy(d.CreatedBy.String) + dashboard.SetUpdatedAt(d.UpdatedAt.Int64) + dashboard.SetUpdatedBy(d.UpdatedBy.String) + dashboard.SetRepos(d.Repos) + + return dashboard +} + +// Validate verifies the necessary fields for +// the Dashboard type are populated correctly. +func (d *Dashboard) Validate() error { + // verify the Name field is populated + if len(d.Name.String) == 0 { + return ErrEmptyDashName + } + + // verify the number of repos + if len(d.Repos) > constants.DashboardRepoLimit { + return fmt.Errorf("exceeded repos limit of %d", constants.DashboardRepoLimit) + } + + // ensure that all Dashboard string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + d.Name = sql.NullString{String: util.Sanitize(d.Name.String), Valid: d.Name.Valid} + + return nil +} + +// DashboardFromAPI converts the API Dashboard type +// to a database Dashboard type. +func DashboardFromAPI(d *api.Dashboard) *Dashboard { + var ( + id uuid.UUID + err error + ) + + if d.GetID() == "" { + id = uuid.New() + } else { + id, err = uuid.Parse(d.GetID()) + if err != nil { + return nil + } + } + + dashboard := &Dashboard{ + ID: id, + Name: sql.NullString{String: d.GetName(), Valid: true}, + CreatedAt: sql.NullInt64{Int64: d.GetCreatedAt(), Valid: true}, + CreatedBy: sql.NullString{String: d.GetCreatedBy(), Valid: true}, + UpdatedAt: sql.NullInt64{Int64: d.GetUpdatedAt(), Valid: true}, + UpdatedBy: sql.NullString{String: d.GetUpdatedBy(), Valid: true}, + Admins: d.GetAdmins(), + Repos: d.GetRepos(), + } + + return dashboard.Nullify() +} diff --git a/database/types/dashboard_test.go b/database/types/dashboard_test.go new file mode 100644 index 000000000..60ca2cfa0 --- /dev/null +++ b/database/types/dashboard_test.go @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + + api "github.com/go-vela/server/api/types" +) + +func TestTypes_Dashboard_Nullify(t *testing.T) { + // setup types + var h *Dashboard + + want := &Dashboard{ + Name: sql.NullString{String: "", Valid: false}, + CreatedAt: sql.NullInt64{Int64: 0, Valid: false}, + CreatedBy: sql.NullString{String: "", Valid: false}, + UpdatedAt: sql.NullInt64{Int64: 0, Valid: false}, + UpdatedBy: sql.NullString{String: "", Valid: false}, + } + + // setup tests + tests := []struct { + dashboard *Dashboard + want *Dashboard + }{ + { + dashboard: testDashboard(), + want: testDashboard(), + }, + { + dashboard: h, + want: nil, + }, + { + dashboard: new(Dashboard), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.dashboard.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Dashboard_ToAPI(t *testing.T) { + // setup types + want := new(api.Dashboard) + want.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + want.SetName("vela") + want.SetCreatedAt(1) + want.SetCreatedBy("octocat") + want.SetUpdatedAt(2) + want.SetUpdatedBy("octokitty") + want.SetAdmins(testAdminsJSON()) + want.SetRepos(testDashReposJSON()) + + uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + h := &Dashboard{ + ID: uuid, + Name: sql.NullString{String: "vela", Valid: true}, + CreatedAt: sql.NullInt64{Int64: 1, Valid: true}, + CreatedBy: sql.NullString{String: "octocat", Valid: true}, + UpdatedAt: sql.NullInt64{Int64: 2, Valid: true}, + UpdatedBy: sql.NullString{String: "octokitty", Valid: true}, + Admins: testAdminsJSON(), + Repos: testDashReposJSON(), + } + + // run test + got := h.ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_Dashboard_Validate(t *testing.T) { + uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + + dashRepo := new(api.DashboardRepo) + dashRepo.SetName("dashboard-repo") + + dashRepos := []*api.DashboardRepo{} + for i := 0; i < 11; i++ { + dashRepos = append(dashRepos, dashRepo) + } + + exceededReposDashboard := testDashboard() + exceededReposDashboard.Repos = DashReposJSON(dashRepos) + + // setup tests + tests := []struct { + failure bool + dashboard *Dashboard + }{ + { + failure: false, + dashboard: testDashboard(), + }, + { // no name set for dashboard + failure: true, + dashboard: &Dashboard{ + ID: uuid, + }, + }, + { // hit repo limit + failure: true, + dashboard: exceededReposDashboard, + }, + } + + // run tests + for _, test := range tests { + err := test.dashboard.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestTypes_DashboardFromAPI(t *testing.T) { + uuid, err := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + if err != nil { + t.Errorf("error parsing uuid: %v", err) + } + + // setup types + want := &Dashboard{ + ID: uuid, + Name: sql.NullString{String: "vela", Valid: true}, + CreatedAt: sql.NullInt64{Int64: 1, Valid: true}, + CreatedBy: sql.NullString{String: "octocat", Valid: true}, + UpdatedAt: sql.NullInt64{Int64: 2, Valid: true}, + UpdatedBy: sql.NullString{String: "octokitty", Valid: true}, + Admins: testAdminsJSON(), + Repos: testDashReposJSON(), + } + + d := new(api.Dashboard) + d.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + d.SetName("vela") + d.SetCreatedAt(1) + d.SetCreatedBy("octocat") + d.SetUpdatedAt(2) + d.SetUpdatedBy("octokitty") + d.SetAdmins(testAdminsJSON()) + d.SetRepos(testDashReposJSON()) + + // run test + got := DashboardFromAPI(d) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("DashboardFromAPI() mismatch (-want +got):\n%s", diff) + } + + // test empty uuid results in generated uuid + d.SetID("") + + //nolint:staticcheck // linter is lying + got = DashboardFromAPI(d) + + if len(got.ID) != 16 { + t.Errorf("Length is %d", len(got.ID)) + } + + // test poorly formed uuid results in nil dashboard + d.SetID("123-abc") + + got = DashboardFromAPI(d) + + if got != nil { + t.Errorf("DashboardFromAPI should have returned nil") + } +} + +// testDashboard is a test helper function to create a Dashboard +// type with all fields set to a fake value. +func testDashboard() *Dashboard { + uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + + return &Dashboard{ + ID: uuid, + Name: sql.NullString{String: "vela", Valid: true}, + CreatedAt: sql.NullInt64{Int64: time.Now().UTC().Unix(), Valid: true}, + CreatedBy: sql.NullString{String: "octocat", Valid: true}, + UpdatedAt: sql.NullInt64{Int64: time.Now().UTC().Unix(), Valid: true}, + UpdatedBy: sql.NullString{String: "octokitty", Valid: true}, + Admins: testAdminsJSON(), + Repos: testDashReposJSON(), + } +} + +// testDashReposJSON is a test helper function to create a DashReposJSON +// type with all fields set to a fake value. +func testDashReposJSON() DashReposJSON { + d := new(api.DashboardRepo) + + d.SetName("go-vela/server") + d.SetID(1) + d.SetBranches([]string{"main"}) + d.SetEvents([]string{"push", "tag"}) + + return DashReposJSON{d} +} + +// testAdminsJSON is a test helper function to create a DashReposJSON +// type with all fields set to a fake value. +func testAdminsJSON() AdminsJSON { + u1 := new(api.User) + + u1.SetName("octocat") + u1.SetID(1) + u1.SetActive(true) + u1.SetToken("foo") + + u2 := new(api.User) + + u2.SetName("octokitty") + u2.SetID(2) + u2.SetActive(true) + u2.SetToken("bar") + + return AdminsJSON{u1, u2} +} diff --git a/database/types/jwk.go b/database/types/jwk.go new file mode 100644 index 000000000..69ba9ad43 --- /dev/null +++ b/database/types/jwk.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "encoding/json" + "errors" + + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwk" +) + +var ( + // ErrInvalidKID defines the error type when a + // JWK type has an invalid ID field provided. + ErrInvalidKID = errors.New("invalid key identifier provided") +) + +type ( + // JWK is the database representation of a jwk. + JWK struct { + ID uuid.UUID `gorm:"type:uuid"` + Active sql.NullBool `sql:"active"` + Key []byte `sql:"key"` + } +) + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the JWK type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (j *JWK) Nullify() *JWK { + if j == nil { + return nil + } + + return j +} + +// ToAPI converts the JWK type +// to an API JWK type. +func (j *JWK) ToAPI() jwk.RSAPublicKey { + parsedKey, _ := jwk.ParseKey(j.Key) + + switch jwk := parsedKey.(type) { + case jwk.RSAPublicKey: + return jwk + default: + return nil + } +} + +// JWKFromAPI converts the API JWK type +// to a database JWK type. +func JWKFromAPI(j jwk.RSAPublicKey) *JWK { + var ( + id uuid.UUID + err error + ) + + id, err = uuid.Parse(j.KeyID()) + if err != nil { + return nil + } + + bytesKey, _ := json.Marshal(j) + + jwk := &JWK{ + ID: id, + Key: bytesKey, + } + + return jwk.Nullify() +} diff --git a/database/types/jwk_test.go b/database/types/jwk_test.go new file mode 100644 index 000000000..01fbf6ddc --- /dev/null +++ b/database/types/jwk_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "encoding/json" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + + "github.com/go-vela/server/database/testutils" +) + +func TestTypes_JWK_Nullify(t *testing.T) { + // setup tests + var j *JWK + + tests := []struct { + JWK *JWK + want *JWK + }{ + { + JWK: testJWK(), + want: testJWK(), + }, + { + JWK: j, + want: nil, + }, + } + + // run tests + for _, test := range tests { + got := test.JWK.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_JWK_ToAPI(t *testing.T) { + // setup types + want := testutils.JWK() + + wantBytes, err := json.Marshal(want) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + uuid, _ := uuid.Parse(want.KeyID()) + h := &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: true, Valid: true}, + Key: wantBytes, + } + + // run test + got := h.ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_JWKFromAPI(t *testing.T) { + j := testutils.JWK() + + jBytes, err := json.Marshal(j) + if err != nil { + t.Errorf("unable to marshal JWK: %v", err) + } + + uuid, err := uuid.Parse(j.KeyID()) + if err != nil { + t.Errorf("unable to parse JWK key id: %v", err) + } + + // setup types + want := &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: false, Valid: false}, + Key: jBytes, + } + + // run test + got := JWKFromAPI(j) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("JWKFromAPI() mismatch (-want +got):\n%s", diff) + } +} + +// testJWK is a test helper function to create a JWK +// type with all fields set to a fake value. +func testJWK() *JWK { + uuid, _ := uuid.Parse("c8da1302-07d6-11ea-882f-4893bca275b8") + + return &JWK{ + ID: uuid, + Active: sql.NullBool{Bool: true, Valid: true}, + } +} diff --git a/database/types/queue_build.go b/database/types/queue_build.go new file mode 100644 index 000000000..51f0cfd0a --- /dev/null +++ b/database/types/queue_build.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + + api "github.com/go-vela/server/api/types" +) + +// QueueBuild is the database representation of the builds in the queue. +type QueueBuild struct { + Status sql.NullString `sql:"status"` + Number sql.NullInt32 `sql:"number"` + Created sql.NullInt64 `sql:"created"` + FullName sql.NullString `sql:"full_name"` +} + +// ToAPI converts the QueueBuild type +// to a API QueueBuild type. +func (b *QueueBuild) ToAPI() *api.QueueBuild { + buildQueue := new(api.QueueBuild) + + buildQueue.SetStatus(b.Status.String) + buildQueue.SetNumber(b.Number.Int32) + buildQueue.SetCreated(b.Created.Int64) + buildQueue.SetFullName(b.FullName.String) + + return buildQueue +} + +// QueueBuildFromAPI converts the API QueueBuild type +// to a database build queue type. +func QueueBuildFromAPI(b *api.QueueBuild) *QueueBuild { + buildQueue := &QueueBuild{ + Status: sql.NullString{String: b.GetStatus(), Valid: true}, + Number: sql.NullInt32{Int32: b.GetNumber(), Valid: true}, + Created: sql.NullInt64{Int64: b.GetCreated(), Valid: true}, + FullName: sql.NullString{String: b.GetFullName(), Valid: true}, + } + + return buildQueue +} diff --git a/database/types/queue_build_test.go b/database/types/queue_build_test.go new file mode 100644 index 000000000..55a413230 --- /dev/null +++ b/database/types/queue_build_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "testing" + + api "github.com/go-vela/server/api/types" +) + +func TestTypes_QueueBuild_ToAPI(t *testing.T) { + // setup types + want := new(api.QueueBuild) + + want.SetNumber(1) + want.SetStatus("running") + want.SetCreated(1563474076) + want.SetFullName("github/octocat") + + // run test + got := testQueueBuild().ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_QueueBuild_FromAPI(t *testing.T) { + // setup types + b := new(api.QueueBuild) + + b.SetNumber(1) + b.SetStatus("running") + b.SetCreated(1563474076) + b.SetFullName("github/octocat") + + want := testQueueBuild() + + // run test + got := QueueBuildFromAPI(b) + + if !reflect.DeepEqual(got, want) { + t.Errorf("QueueBuildFromAPI is %v, want %v", got, want) + } +} + +// testQueueBuild is a test helper function to create a QueueBuild +// type with all fields set to a fake value. +func testQueueBuild() *QueueBuild { + return &QueueBuild{ + Number: sql.NullInt32{Int32: 1, Valid: true}, + Status: sql.NullString{String: "running", Valid: true}, + Created: sql.NullInt64{Int64: 1563474076, Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + } +} diff --git a/database/types/repo.go b/database/types/repo.go new file mode 100644 index 000000000..e3780f851 --- /dev/null +++ b/database/types/repo.go @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "encoding/base64" + "errors" + + "github.com/lib/pq" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" +) + +var ( + // ErrEmptyRepoFullName defines the error type when a + // Repo type has an empty FullName field provided. + ErrEmptyRepoFullName = errors.New("empty repo full_name provided") + + // ErrEmptyRepoHash defines the error type when a + // Repo type has an empty Hash field provided. + ErrEmptyRepoHash = errors.New("empty repo hash provided") + + // ErrEmptyRepoName defines the error type when a + // Repo type has an empty Name field provided. + ErrEmptyRepoName = errors.New("empty repo name provided") + + // ErrEmptyRepoOrg defines the error type when a + // Repo type has an empty Org field provided. + ErrEmptyRepoOrg = errors.New("empty repo org provided") + + // ErrEmptyRepoUserID defines the error type when a + // Repo type has an empty UserID field provided. + ErrEmptyRepoUserID = errors.New("empty repo user_id provided") + + // ErrEmptyRepoVisibility defines the error type when a + // Repo type has an empty Visibility field provided. + ErrEmptyRepoVisibility = errors.New("empty repo visibility provided") + + // ErrExceededTopicsLimit defines the error type when a + // Repo type has Topics field provided that exceeds the database limit. + ErrExceededTopicsLimit = errors.New("exceeded topics limit") +) + +// Repo is the database representation of a repo. +type Repo struct { + ID sql.NullInt64 `sql:"id"` + UserID sql.NullInt64 `sql:"user_id"` + Hash sql.NullString `sql:"hash"` + Org sql.NullString `sql:"org"` + Name sql.NullString `sql:"name"` + FullName sql.NullString `sql:"full_name"` + Link sql.NullString `sql:"link"` + Clone sql.NullString `sql:"clone"` + Branch sql.NullString `sql:"branch"` + Topics pq.StringArray `sql:"topics" gorm:"type:varchar(1020)"` + BuildLimit sql.NullInt64 `sql:"build_limit"` + Timeout sql.NullInt64 `sql:"timeout"` + Counter sql.NullInt32 `sql:"counter"` + Visibility sql.NullString `sql:"visibility"` + Private sql.NullBool `sql:"private"` + Trusted sql.NullBool `sql:"trusted"` + Active sql.NullBool `sql:"active"` + AllowEvents sql.NullInt64 `sql:"allow_events"` + PipelineType sql.NullString `sql:"pipeline_type"` + PreviousName sql.NullString `sql:"previous_name"` + ApproveBuild sql.NullString `sql:"approve_build"` + + Owner User `gorm:"foreignKey:UserID"` +} + +// Decrypt will manipulate the existing repo hash by +// base64 decoding that value. Then, a AES-256 cipher +// block is created from the encryption key in order to +// decrypt the base64 decoded secret value. +func (r *Repo) Decrypt(key string) error { + // base64 decode the encrypted repo hash + decoded, err := base64.StdEncoding.DecodeString(r.Hash.String) + if err != nil { + return err + } + + // decrypt the base64 decoded repo hash + decrypted, err := util.Decrypt(key, decoded) + if err != nil { + return err + } + + // set the decrypted repo hash + r.Hash = sql.NullString{ + String: string(decrypted), + Valid: true, + } + + // decrypt owner + err = r.Owner.Decrypt(key) + if err != nil { + return err + } + + return nil +} + +// Encrypt will manipulate the existing repo hash by +// creating a AES-256 cipher block from the encryption +// key in order to encrypt the repo hash. Then, the +// repo hash is base64 encoded for transport across +// network boundaries. +func (r *Repo) Encrypt(key string) error { + // encrypt the repo hash + encrypted, err := util.Encrypt(key, []byte(r.Hash.String)) + if err != nil { + return err + } + + // base64 encode the encrypted repo hash to make it network safe + r.Hash = sql.NullString{ + String: base64.StdEncoding.EncodeToString(encrypted), + Valid: true, + } + + return nil +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Repo type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (r *Repo) Nullify() *Repo { + if r == nil { + return nil + } + + // check if the ID field should be false + if r.ID.Int64 == 0 { + r.ID.Valid = false + } + + // check if the UserID field should be false + if r.UserID.Int64 == 0 { + r.UserID.Valid = false + } + + // check if the Hash field should be false + if len(r.Hash.String) == 0 { + r.Hash.Valid = false + } + + // check if the Org field should be false + if len(r.Org.String) == 0 { + r.Org.Valid = false + } + + // check if the Name field should be false + if len(r.Name.String) == 0 { + r.Name.Valid = false + } + + // check if the FullName field should be false + if len(r.FullName.String) == 0 { + r.FullName.Valid = false + } + + // check if the Link field should be false + if len(r.Link.String) == 0 { + r.Link.Valid = false + } + + // check if the Clone field should be false + if len(r.Clone.String) == 0 { + r.Clone.Valid = false + } + + // check if the Branch field should be false + if len(r.Branch.String) == 0 { + r.Branch.Valid = false + } + + // check if the BuildLimit field should be false + if r.BuildLimit.Int64 == 0 { + r.BuildLimit.Valid = false + } + + // check if the Timeout field should be false + if r.Timeout.Int64 == 0 { + r.Timeout.Valid = false + } + + // check if the AllowEvents field should be false + if r.AllowEvents.Int64 == 0 { + r.AllowEvents.Valid = false + } + + // check if the Visibility field should be false + if len(r.Visibility.String) == 0 { + r.Visibility.Valid = false + } + + // check if the PipelineType field should be false + if len(r.PipelineType.String) == 0 { + r.PipelineType.Valid = false + } + + // check if the PreviousName field should be false + if len(r.PreviousName.String) == 0 { + r.PreviousName.Valid = false + } + + // check if the ApproveForkBuild field should be false + if len(r.ApproveBuild.String) == 0 { + r.ApproveBuild.Valid = false + } + + return r +} + +// ToAPI converts the Repo type +// to an API Repo type. +func (r *Repo) ToAPI() *api.Repo { + repo := new(api.Repo) + + repo.SetID(r.ID.Int64) + repo.SetOwner(r.Owner.ToAPI().Crop()) + repo.SetHash(r.Hash.String) + repo.SetOrg(r.Org.String) + repo.SetName(r.Name.String) + repo.SetFullName(r.FullName.String) + repo.SetLink(r.Link.String) + repo.SetClone(r.Clone.String) + repo.SetBranch(r.Branch.String) + repo.SetTopics(r.Topics) + repo.SetBuildLimit(r.BuildLimit.Int64) + repo.SetTimeout(r.Timeout.Int64) + repo.SetCounter(int(r.Counter.Int32)) + repo.SetVisibility(r.Visibility.String) + repo.SetPrivate(r.Private.Bool) + repo.SetTrusted(r.Trusted.Bool) + repo.SetActive(r.Active.Bool) + repo.SetAllowEvents(api.NewEventsFromMask(r.AllowEvents.Int64)) + repo.SetPipelineType(r.PipelineType.String) + repo.SetPreviousName(r.PreviousName.String) + repo.SetApproveBuild(r.ApproveBuild.String) + + return repo +} + +// Validate verifies the necessary fields for +// the Repo type are populated correctly. +func (r *Repo) Validate() error { + // verify the UserID field is populated + if r.UserID.Int64 <= 0 { + return ErrEmptyRepoUserID + } + + // verify the Hash field is populated + if len(r.Hash.String) == 0 { + return ErrEmptyRepoHash + } + + // verify the Org field is populated + if len(r.Org.String) == 0 { + return ErrEmptyRepoOrg + } + + // verify the Name field is populated + if len(r.Name.String) == 0 { + return ErrEmptyRepoName + } + + // verify the FullName field is populated + if len(r.FullName.String) == 0 { + return ErrEmptyRepoFullName + } + + // verify the Visibility field is populated + if len(r.Visibility.String) == 0 { + return ErrEmptyRepoVisibility + } + + // calculate total size of favorites while sanitizing entries + total := 0 + + for i, t := range r.Topics { + r.Topics[i] = util.Sanitize(t) + total += len(t) + } + + // verify the Favorites field is within the database constraints + // len is to factor in number of comma separators included in the database field, + // removing 1 due to the last item not having an appended comma + if (total + len(r.Topics) - 1) > constants.TopicsMaxSize { + return ErrExceededTopicsLimit + } + + // ensure that all Repo string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + r.Org = sql.NullString{String: util.Sanitize(r.Org.String), Valid: r.Org.Valid} + r.Name = sql.NullString{String: util.Sanitize(r.Name.String), Valid: r.Name.Valid} + r.FullName = sql.NullString{String: util.Sanitize(r.FullName.String), Valid: r.FullName.Valid} + r.Link = sql.NullString{String: util.Sanitize(r.Link.String), Valid: r.Link.Valid} + r.Clone = sql.NullString{String: util.Sanitize(r.Clone.String), Valid: r.Clone.Valid} + r.Branch = sql.NullString{String: util.Sanitize(r.Branch.String), Valid: r.Branch.Valid} + r.Visibility = sql.NullString{String: util.Sanitize(r.Visibility.String), Valid: r.Visibility.Valid} + r.PipelineType = sql.NullString{String: util.Sanitize(r.PipelineType.String), Valid: r.PipelineType.Valid} + + return nil +} + +// RepoFromAPI converts the API Repo type +// to a database repo type. +func RepoFromAPI(r *api.Repo) *Repo { + repo := &Repo{ + ID: sql.NullInt64{Int64: r.GetID(), Valid: true}, + UserID: sql.NullInt64{Int64: r.GetOwner().GetID(), Valid: true}, + Hash: sql.NullString{String: r.GetHash(), Valid: true}, + Org: sql.NullString{String: r.GetOrg(), Valid: true}, + Name: sql.NullString{String: r.GetName(), Valid: true}, + FullName: sql.NullString{String: r.GetFullName(), Valid: true}, + Link: sql.NullString{String: r.GetLink(), Valid: true}, + Clone: sql.NullString{String: r.GetClone(), Valid: true}, + Branch: sql.NullString{String: r.GetBranch(), Valid: true}, + Topics: pq.StringArray(r.GetTopics()), + BuildLimit: sql.NullInt64{Int64: r.GetBuildLimit(), Valid: true}, + Timeout: sql.NullInt64{Int64: r.GetTimeout(), Valid: true}, + Counter: sql.NullInt32{Int32: int32(r.GetCounter()), Valid: true}, + Visibility: sql.NullString{String: r.GetVisibility(), Valid: true}, + Private: sql.NullBool{Bool: r.GetPrivate(), Valid: true}, + Trusted: sql.NullBool{Bool: r.GetTrusted(), Valid: true}, + Active: sql.NullBool{Bool: r.GetActive(), Valid: true}, + AllowEvents: sql.NullInt64{Int64: r.GetAllowEvents().ToDatabase(), Valid: true}, + PipelineType: sql.NullString{String: r.GetPipelineType(), Valid: true}, + PreviousName: sql.NullString{String: r.GetPreviousName(), Valid: true}, + ApproveBuild: sql.NullString{String: r.GetApproveBuild(), Valid: true}, + } + + return repo.Nullify() +} diff --git a/database/types/repo_test.go b/database/types/repo_test.go new file mode 100644 index 000000000..d41ca7f8b --- /dev/null +++ b/database/types/repo_test.go @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" +) + +func TestTypes_Repo_Decrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + encrypted := testRepo() + + err := encrypted.Encrypt(key) + if err != nil { + t.Errorf("unable to encrypt repo: %v", err) + } + + err = encrypted.Owner.Encrypt(key) + if err != nil { + t.Errorf("unable to encrypt user: %v", err) + } + + // setup tests + tests := []struct { + failure bool + key string + repo Repo + }{ + { + failure: false, + key: key, + repo: *encrypted, + }, + { + failure: true, + key: "", + repo: *encrypted, + }, + { + failure: true, + key: key, + repo: *testRepo(), + }, + } + + // run tests + for _, test := range tests { + err := test.repo.Decrypt(test.key) + + if test.failure { + if err == nil { + t.Errorf("Decrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Decrypt returned err: %v", err) + } + } +} + +func TestTypes_Repo_Encrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + + // setup tests + tests := []struct { + failure bool + key string + repo *Repo + }{ + { + failure: false, + key: key, + repo: testRepo(), + }, + { + failure: true, + key: "", + repo: testRepo(), + }, + } + + // run tests + for _, test := range tests { + err := test.repo.Encrypt(test.key) + + if test.failure { + if err == nil { + t.Errorf("Encrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Encrypt returned err: %v", err) + } + } +} + +func TestTypes_Repo_Nullify(t *testing.T) { + // setup types + var r *Repo + + want := &Repo{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + UserID: sql.NullInt64{Int64: 0, Valid: false}, + Hash: sql.NullString{String: "", Valid: false}, + Org: sql.NullString{String: "", Valid: false}, + Name: sql.NullString{String: "", Valid: false}, + FullName: sql.NullString{String: "", Valid: false}, + Link: sql.NullString{String: "", Valid: false}, + Clone: sql.NullString{String: "", Valid: false}, + Branch: sql.NullString{String: "", Valid: false}, + Timeout: sql.NullInt64{Int64: 0, Valid: false}, + AllowEvents: sql.NullInt64{Int64: 0, Valid: false}, + Visibility: sql.NullString{String: "", Valid: false}, + PipelineType: sql.NullString{String: "", Valid: false}, + ApproveBuild: sql.NullString{String: "", Valid: false}, + } + + // setup tests + tests := []struct { + repo *Repo + want *Repo + }{ + { + repo: testRepo(), + want: testRepo(), + }, + { + repo: r, + want: nil, + }, + { + repo: new(Repo), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.repo.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Repo_ToAPI(t *testing.T) { + // setup types + want := new(api.Repo) + e := api.NewEventsFromMask(1) + + owner := testutils.APIUser().Crop() + owner.SetID(1) + owner.SetName("octocat") + owner.SetActive(true) + owner.SetToken("superSecretToken") + owner.SetRefreshToken("superSecretRefreshToken") + + want.SetID(1) + want.SetOwner(owner) + want.SetHash("superSecretHash") + want.SetOrg("github") + want.SetName("octocat") + want.SetFullName("github/octocat") + want.SetLink("https://github.com/github/octocat") + want.SetClone("https://github.com/github/octocat.git") + want.SetBranch("main") + want.SetTopics([]string{"cloud", "security"}) + want.SetBuildLimit(10) + want.SetTimeout(30) + want.SetCounter(0) + want.SetVisibility("public") + want.SetPrivate(false) + want.SetTrusted(false) + want.SetActive(true) + want.SetAllowEvents(e) + want.SetPipelineType("yaml") + want.SetPreviousName("oldName") + want.SetApproveBuild(constants.ApproveNever) + + // run test + got := testRepo().ToAPI() + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ToAPI() mismatch (-want +got):\n%s", diff) + } +} + +func TestTypes_Repo_Validate(t *testing.T) { + // setup types + topics := []string{} + longTopic := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + for len(topics) < 21 { + topics = append(topics, longTopic) + } + + // setup tests + tests := []struct { + failure bool + repo *Repo + }{ + { + failure: false, + repo: testRepo(), + }, + { // no user_id set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + }, + }, + { // no hash set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + }, + }, + { // no org set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + }, + }, + { // no name set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + }, + }, + { // no full_name set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + }, + }, + { // no visibility set for repo + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + }, + }, + { // topics exceed max size + failure: true, + repo: &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Topics: topics, + }, + }, + } + + // run tests + for _, test := range tests { + err := test.repo.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestTypes_RepoFromAPI(t *testing.T) { + // setup types + r := new(api.Repo) + owner := testutils.APIUser() + owner.SetID(1) + + r.SetID(1) + r.SetOwner(owner) + r.SetHash("superSecretHash") + r.SetOrg("github") + r.SetName("octocat") + r.SetFullName("github/octocat") + r.SetLink("https://github.com/github/octocat") + r.SetClone("https://github.com/github/octocat.git") + r.SetBranch("main") + r.SetTopics([]string{"cloud", "security"}) + r.SetBuildLimit(10) + r.SetTimeout(30) + r.SetCounter(0) + r.SetVisibility("public") + r.SetPrivate(false) + r.SetTrusted(false) + r.SetActive(true) + r.SetAllowEvents(api.NewEventsFromMask(1)) + r.SetPipelineType("yaml") + r.SetPreviousName("oldName") + r.SetApproveBuild(constants.ApproveNever) + + want := testRepo() + want.Owner = User{} + + // run test + got := RepoFromAPI(r) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("RepoFromAPI() mismatch (-want +got):\n%s", diff) + } +} + +// testRepo is a test helper function to create a Repo +// type with all fields set to a fake value. +func testRepo() *Repo { + return &Repo{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + UserID: sql.NullInt64{Int64: 1, Valid: true}, + Hash: sql.NullString{String: "superSecretHash", Valid: true}, + Org: sql.NullString{String: "github", Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + FullName: sql.NullString{String: "github/octocat", Valid: true}, + Link: sql.NullString{String: "https://github.com/github/octocat", Valid: true}, + Clone: sql.NullString{String: "https://github.com/github/octocat.git", Valid: true}, + Branch: sql.NullString{String: "main", Valid: true}, + Topics: []string{"cloud", "security"}, + BuildLimit: sql.NullInt64{Int64: 10, Valid: true}, + Timeout: sql.NullInt64{Int64: 30, Valid: true}, + Counter: sql.NullInt32{Int32: 0, Valid: true}, + Visibility: sql.NullString{String: "public", Valid: true}, + Private: sql.NullBool{Bool: false, Valid: true}, + Trusted: sql.NullBool{Bool: false, Valid: true}, + Active: sql.NullBool{Bool: true, Valid: true}, + AllowEvents: sql.NullInt64{Int64: 1, Valid: true}, + PipelineType: sql.NullString{String: "yaml", Valid: true}, + PreviousName: sql.NullString{String: "oldName", Valid: true}, + ApproveBuild: sql.NullString{String: constants.ApproveNever, Valid: true}, + + Owner: *testUser(), + } +} diff --git a/database/types/schedule.go b/database/types/schedule.go new file mode 100644 index 000000000..ecd578b48 --- /dev/null +++ b/database/types/schedule.go @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "errors" + "time" + + "github.com/adhocore/gronx" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/util" +) + +var ( + // ErrEmptyScheduleEntry defines the error type when a Schedule type has an empty Entry field provided. + ErrEmptyScheduleEntry = errors.New("empty schedule entry provided") + + // ErrEmptyScheduleName defines the error type when a Schedule type has an empty Name field provided. + ErrEmptyScheduleName = errors.New("empty schedule name provided") + + // ErrEmptyScheduleRepoID defines the error type when a Schedule type has an empty RepoID field provided. + ErrEmptyScheduleRepoID = errors.New("empty schedule repo_id provided") + + // ErrInvalidScheduleEntry defines the error type when a Schedule type has an invalid Entry field provided. + ErrInvalidScheduleEntry = errors.New("invalid schedule entry provided") +) + +type Schedule struct { + ID sql.NullInt64 `sql:"id"` + RepoID sql.NullInt64 `sql:"repo_id"` + Active sql.NullBool `sql:"active"` + Name sql.NullString `sql:"name"` + Entry sql.NullString `sql:"entry"` + CreatedAt sql.NullInt64 `sql:"created_at"` + CreatedBy sql.NullString `sql:"created_by"` + UpdatedAt sql.NullInt64 `sql:"updated_at"` + UpdatedBy sql.NullString `sql:"updated_by"` + ScheduledAt sql.NullInt64 `sql:"scheduled_at"` + Branch sql.NullString `sql:"branch"` + Error sql.NullString `sql:"error"` + + Repo Repo `gorm:"foreignKey:RepoID"` +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Schedule type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (s *Schedule) Nullify() *Schedule { + if s == nil { + return nil + } + + // check if the ID field should be valid + s.ID.Valid = s.ID.Int64 != 0 + // check if the RepoID field should be valid + s.RepoID.Valid = s.RepoID.Int64 != 0 + // check if the ID field should be valid + s.Active.Valid = s.RepoID.Int64 != 0 + // check if the Name field should be valid + s.Name.Valid = len(s.Name.String) != 0 + // check if the Entry field should be valid + s.Entry.Valid = len(s.Entry.String) != 0 + // check if the CreatedAt field should be valid + s.CreatedAt.Valid = s.CreatedAt.Int64 != 0 + // check if the CreatedBy field should be valid + s.CreatedBy.Valid = len(s.CreatedBy.String) != 0 + // check if the UpdatedAt field should be valid + s.UpdatedAt.Valid = s.UpdatedAt.Int64 != 0 + // check if the UpdatedBy field should be valid + s.UpdatedBy.Valid = len(s.UpdatedBy.String) != 0 + // check if the ScheduledAt field should be valid + s.ScheduledAt.Valid = s.ScheduledAt.Int64 != 0 + // check if the Branch field should be valid + s.Branch.Valid = len(s.Branch.String) != 0 + // check if the Error field should be valid + s.Error.Valid = len(s.Error.String) != 0 + + return s +} + +// ToAPI converts the Schedule type +// to an API Schedule type. +func (s *Schedule) ToAPI() *api.Schedule { + schedule := new(api.Schedule) + + t := time.Now().UTC() + nextTime, err := gronx.NextTickAfter(s.Entry.String, t, false) + + schedule.SetID(s.ID.Int64) + schedule.SetRepo(s.Repo.ToAPI()) + schedule.SetActive(s.Active.Bool) + schedule.SetName(s.Name.String) + schedule.SetEntry(s.Entry.String) + schedule.SetCreatedAt(s.CreatedAt.Int64) + schedule.SetCreatedBy(s.CreatedBy.String) + schedule.SetUpdatedAt(s.UpdatedAt.Int64) + schedule.SetUpdatedBy(s.UpdatedBy.String) + schedule.SetScheduledAt(s.ScheduledAt.Int64) + schedule.SetBranch(s.Branch.String) + schedule.SetError(s.Error.String) + + if err == nil { + schedule.SetNextRun(nextTime.Unix()) + } else { + schedule.SetNextRun(0) + } + + return schedule +} + +// Validate verifies the necessary fields for +// the Schedule type are populated correctly. +func (s *Schedule) Validate() error { + // verify the RepoID field is populated + if s.RepoID.Int64 <= 0 { + return ErrEmptyScheduleRepoID + } + + // verify the Name field is populated + if len(s.Name.String) <= 0 { + return ErrEmptyScheduleName + } + + // verify the Entry field is populated + if len(s.Entry.String) <= 0 { + return ErrEmptyScheduleEntry + } + + gron := gronx.New() + if !gron.IsValid(s.Entry.String) { + return ErrInvalidScheduleEntry + } + + // ensure that all Schedule string fields that can be returned as JSON are sanitized to avoid unsafe HTML content + s.Name = sql.NullString{String: util.Sanitize(s.Name.String), Valid: s.Name.Valid} + s.Entry = sql.NullString{String: util.Sanitize(s.Entry.String), Valid: s.Entry.Valid} + + return nil +} + +// ScheduleFromAPI converts the API Schedule type +// to a database Schedule type. +func ScheduleFromAPI(s *api.Schedule) *Schedule { + schedule := &Schedule{ + ID: sql.NullInt64{Int64: s.GetID(), Valid: true}, + RepoID: sql.NullInt64{Int64: s.GetRepo().GetID(), Valid: true}, + Active: sql.NullBool{Bool: s.GetActive(), Valid: true}, + Name: sql.NullString{String: s.GetName(), Valid: true}, + Entry: sql.NullString{String: s.GetEntry(), Valid: true}, + CreatedAt: sql.NullInt64{Int64: s.GetCreatedAt(), Valid: true}, + CreatedBy: sql.NullString{String: s.GetCreatedBy(), Valid: true}, + UpdatedAt: sql.NullInt64{Int64: s.GetUpdatedAt(), Valid: true}, + UpdatedBy: sql.NullString{String: s.GetUpdatedBy(), Valid: true}, + ScheduledAt: sql.NullInt64{Int64: s.GetScheduledAt(), Valid: true}, + Branch: sql.NullString{String: s.GetBranch(), Valid: true}, + Error: sql.NullString{String: s.GetError(), Valid: true}, + } + + return schedule.Nullify() +} diff --git a/database/types/schedule_test.go b/database/types/schedule_test.go new file mode 100644 index 000000000..aa7ad21be --- /dev/null +++ b/database/types/schedule_test.go @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "testing" + "time" + + "github.com/adhocore/gronx" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/types/constants" +) + +func TestTypes_Schedule_Nullify(t *testing.T) { + tests := []struct { + name string + schedule *Schedule + want *Schedule + }{ + { + name: "schedule with fields", + schedule: testSchedule(), + want: testSchedule(), + }, + { + name: "schedule with empty fields", + schedule: new(Schedule), + want: &Schedule{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + RepoID: sql.NullInt64{Int64: 0, Valid: false}, + Active: sql.NullBool{Bool: false, Valid: false}, + Name: sql.NullString{String: "", Valid: false}, + Entry: sql.NullString{String: "", Valid: false}, + CreatedAt: sql.NullInt64{Int64: 0, Valid: false}, + CreatedBy: sql.NullString{String: "", Valid: false}, + UpdatedAt: sql.NullInt64{Int64: 0, Valid: false}, + UpdatedBy: sql.NullString{String: "", Valid: false}, + ScheduledAt: sql.NullInt64{Int64: 0, Valid: false}, + Branch: sql.NullString{String: "", Valid: false}, + Error: sql.NullString{String: "", Valid: false}, + }, + }, + { + name: "empty schedule", + schedule: nil, + want: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.schedule.Nullify() + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + }) + } +} + +func TestTypes_Schedule_ToAPI(t *testing.T) { + // setup types + e := api.NewEventsFromMask(1) + + owner := testutils.APIUser().Crop() + owner.SetID(1) + owner.SetName("octocat") + owner.SetActive(true) + owner.SetToken("superSecretToken") + owner.SetRefreshToken("superSecretRefreshToken") + + repo := testutils.APIRepo() + repo.SetID(1) + repo.SetOwner(owner) + repo.SetHash("superSecretHash") + repo.SetOrg("github") + repo.SetName("octocat") + repo.SetFullName("github/octocat") + repo.SetLink("https://github.com/github/octocat") + repo.SetClone("https://github.com/github/octocat.git") + repo.SetBranch("main") + repo.SetTopics([]string{"cloud", "security"}) + repo.SetBuildLimit(10) + repo.SetTimeout(30) + repo.SetCounter(0) + repo.SetVisibility("public") + repo.SetPrivate(false) + repo.SetTrusted(false) + repo.SetActive(true) + repo.SetAllowEvents(e) + repo.SetPipelineType("yaml") + repo.SetPreviousName("oldName") + repo.SetApproveBuild(constants.ApproveNever) + + currTime := time.Now().UTC() + nextTime, _ := gronx.NextTickAfter("0 0 * * *", currTime, false) + + want := testutils.APISchedule() + want.SetID(1) + want.SetActive(true) + want.SetRepo(repo) + want.SetName("nightly") + want.SetEntry("0 0 * * *") + want.SetCreatedAt(time.Now().UTC().Unix()) + want.SetCreatedBy("user1") + want.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix()) + want.SetUpdatedBy("user2") + want.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix()) + want.SetBranch("main") + want.SetError("unable to trigger build for schedule nightly: unknown character") + want.SetNextRun(nextTime.Unix()) + + // run test + got := testSchedule().ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_Schedule_Validate(t *testing.T) { + tests := []struct { + name string + failure bool + schedule *Schedule + }{ + { + name: "schedule with valid fields", + failure: false, + schedule: testSchedule(), + }, + { + name: "schedule with invalid entry", + failure: true, + schedule: &Schedule{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "invalid", Valid: false}, + Entry: sql.NullString{String: "!@#$%^&*()", Valid: false}, + }, + }, + { + name: "schedule with missing entry", + failure: true, + schedule: &Schedule{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "nightly", Valid: false}, + }, + }, + { + name: "schedule with missing name", + failure: true, + schedule: &Schedule{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + Entry: sql.NullString{String: "0 0 * * *", Valid: false}, + }, + }, + { + name: "schedule with missing repo_id", + failure: true, + schedule: &Schedule{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "nightly", Valid: false}, + Entry: sql.NullString{String: "0 0 * * *", Valid: false}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.schedule.Validate() + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + return + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + }) + } +} + +func TestTypes_ScheduleFromAPI(t *testing.T) { + // setup types + s := new(api.Schedule) + repo := testutils.APIRepo() + repo.SetID(1) + + s.SetID(1) + s.SetActive(true) + s.SetRepo(repo) + s.SetName("nightly") + s.SetEntry("0 0 * * *") + s.SetCreatedAt(time.Now().UTC().Unix()) + s.SetCreatedBy("user1") + s.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix()) + s.SetUpdatedBy("user2") + s.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix()) + s.SetBranch("main") + s.SetError("unable to trigger build for schedule nightly: unknown character") + + want := testSchedule() + want.Repo = Repo{} + + // run test + got := ScheduleFromAPI(s) + + if !reflect.DeepEqual(got, want) { + t.Errorf("ScheduleFromAPI is %v, want %v", got, want) + } +} + +// testSchedule is a test helper function to create a Schedule type with all fields set to a fake value. +func testSchedule() *Schedule { + return &Schedule{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + RepoID: sql.NullInt64{Int64: 1, Valid: true}, + Active: sql.NullBool{Bool: true, Valid: true}, + Name: sql.NullString{String: "nightly", Valid: true}, + Entry: sql.NullString{String: "0 0 * * *", Valid: true}, + CreatedAt: sql.NullInt64{Int64: time.Now().UTC().Unix(), Valid: true}, + CreatedBy: sql.NullString{String: "user1", Valid: true}, + UpdatedAt: sql.NullInt64{Int64: time.Now().Add(time.Hour * 1).UTC().Unix(), Valid: true}, + UpdatedBy: sql.NullString{String: "user2", Valid: true}, + ScheduledAt: sql.NullInt64{Int64: time.Now().Add(time.Hour * 2).UTC().Unix(), Valid: true}, + Branch: sql.NullString{String: "main", Valid: true}, + Error: sql.NullString{String: "unable to trigger build for schedule nightly: unknown character", Valid: true}, + + Repo: *testRepo(), + } +} diff --git a/database/types/settings.go b/database/types/settings.go new file mode 100644 index 000000000..ea0d47a2a --- /dev/null +++ b/database/types/settings.go @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + + "github.com/lib/pq" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/util" +) + +var ( + // ErrEmptyCloneImage defines the error type when a + // Settings type has an empty CloneImage field provided. + ErrEmptyCloneImage = errors.New("empty settings clone image provided") +) + +type ( + // Platform is the database representation of platform settings. + Platform struct { + ID sql.NullInt64 `sql:"id"` + Compiler + Queue + + RepoAllowlist pq.StringArray `json:"repo_allowlist" sql:"repo_allowlist" gorm:"type:varchar(1000)"` + ScheduleAllowlist pq.StringArray `json:"schedule_allowlist" sql:"schedule_allowlist" gorm:"type:varchar(1000)"` + + CreatedAt sql.NullInt64 `sql:"created_at"` + UpdatedAt sql.NullInt64 `sql:"updated_at"` + UpdatedBy sql.NullString `sql:"updated_by"` + } + + // Compiler is the database representation of compiler settings. + Compiler struct { + CloneImage sql.NullString `json:"clone_image" sql:"clone_image"` + TemplateDepth sql.NullInt64 `json:"template_depth" sql:"template_depth"` + StarlarkExecLimit sql.NullInt64 `json:"starlark_exec_limit" sql:"starlark_exec_limit"` + } + + // Queue is the database representation of queue settings. + Queue struct { + Routes pq.StringArray `json:"routes" sql:"routes" gorm:"type:varchar(1000)"` + } +) + +// Value - Implementation of valuer for database/sql for Compiler. +func (r Compiler) Value() (driver.Value, error) { + valueString, err := json.Marshal(r) + return string(valueString), err +} + +// Scan - Implement the database/sql scanner interface for Compiler. +func (r *Compiler) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return json.Unmarshal(v, &r) + case string: + return json.Unmarshal([]byte(v), &r) + default: + return fmt.Errorf("wrong type for compiler: %T", v) + } +} + +// Value - Implementation of valuer for database/sql for Queue. +func (r Queue) Value() (driver.Value, error) { + valueString, err := json.Marshal(r) + return string(valueString), err +} + +// Scan - Implement the database/sql scanner interface for Queue. +func (r *Queue) Scan(value interface{}) error { + switch v := value.(type) { + case []byte: + return json.Unmarshal(v, &r) + case string: + return json.Unmarshal([]byte(v), &r) + default: + return fmt.Errorf("wrong type for queue: %T", v) + } +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Settings type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (ps *Platform) Nullify() *Platform { + if ps == nil { + return nil + } + + // check if the ID field should be false + if ps.ID.Int64 == 0 { + ps.ID.Valid = false + } + + // check if the CloneImage field should be false + if len(ps.CloneImage.String) == 0 { + ps.CloneImage.Valid = false + } + + // check if the CreatedAt field should be false + if ps.CreatedAt.Int64 < 0 { + ps.CreatedAt.Valid = false + } + + // check if the UpdatedAt field should be false + if ps.UpdatedAt.Int64 < 0 { + ps.UpdatedAt.Valid = false + } + + return ps +} + +// ToAPI converts the Settings type +// to an API Settings type. +func (ps *Platform) ToAPI() *settings.Platform { + psAPI := new(settings.Platform) + psAPI.SetID(ps.ID.Int64) + + psAPI.SetRepoAllowlist(ps.RepoAllowlist) + psAPI.SetScheduleAllowlist(ps.ScheduleAllowlist) + + psAPI.Compiler = &settings.Compiler{} + psAPI.SetCloneImage(ps.CloneImage.String) + psAPI.SetTemplateDepth(int(ps.TemplateDepth.Int64)) + psAPI.SetStarlarkExecLimit(uint64(ps.StarlarkExecLimit.Int64)) + + psAPI.Queue = &settings.Queue{} + psAPI.SetRoutes(ps.Routes) + + psAPI.SetCreatedAt(ps.CreatedAt.Int64) + psAPI.SetUpdatedAt(ps.UpdatedAt.Int64) + psAPI.SetUpdatedBy(ps.UpdatedBy.String) + + return psAPI +} + +// Validate verifies the necessary fields for +// the Settings type are populated correctly. +func (ps *Platform) Validate() error { + // verify the CloneImage field is populated + if len(ps.CloneImage.String) == 0 { + return ErrEmptyCloneImage + } + + // verify compiler settings are within limits + if ps.TemplateDepth.Int64 <= 0 { + return fmt.Errorf("template depth must be greater than zero, got: %d", ps.TemplateDepth.Int64) + } + + if ps.StarlarkExecLimit.Int64 <= 0 { + return fmt.Errorf("starlark exec limit must be greater than zero, got: %d", ps.StarlarkExecLimit.Int64) + } + + // ensure that all Settings string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + ps.CloneImage = sql.NullString{String: util.Sanitize(ps.CloneImage.String), Valid: ps.CloneImage.Valid} + + // ensure that all Queue.Routes are sanitized + // to avoid unsafe HTML content + for i, v := range ps.Routes { + ps.Routes[i] = util.Sanitize(v) + } + + // ensure that all RepoAllowlist are sanitized + // to avoid unsafe HTML content + for i, v := range ps.RepoAllowlist { + ps.RepoAllowlist[i] = util.Sanitize(v) + } + + // ensure that all ScheduleAllowlist are sanitized + // to avoid unsafe HTML content + for i, v := range ps.ScheduleAllowlist { + ps.ScheduleAllowlist[i] = util.Sanitize(v) + } + + if ps.CreatedAt.Int64 < 0 { + return fmt.Errorf("created_at must be greater than zero, got: %d", ps.CreatedAt.Int64) + } + + if ps.UpdatedAt.Int64 < 0 { + return fmt.Errorf("updated_at must be greater than zero, got: %d", ps.UpdatedAt.Int64) + } + + return nil +} + +// SettingsFromAPI converts the API Settings type +// to a database Settings type. +func SettingsFromAPI(s *settings.Platform) *Platform { + settings := &Platform{ + ID: sql.NullInt64{Int64: s.GetID(), Valid: true}, + Compiler: Compiler{ + CloneImage: sql.NullString{String: s.GetCloneImage(), Valid: true}, + TemplateDepth: sql.NullInt64{Int64: int64(s.GetTemplateDepth()), Valid: true}, + StarlarkExecLimit: sql.NullInt64{Int64: int64(s.GetStarlarkExecLimit()), Valid: true}, + }, + Queue: Queue{ + Routes: pq.StringArray(s.GetRoutes()), + }, + RepoAllowlist: pq.StringArray(s.GetRepoAllowlist()), + ScheduleAllowlist: pq.StringArray(s.GetScheduleAllowlist()), + CreatedAt: sql.NullInt64{Int64: s.GetCreatedAt(), Valid: true}, + UpdatedAt: sql.NullInt64{Int64: s.GetUpdatedAt(), Valid: true}, + UpdatedBy: sql.NullString{String: s.GetUpdatedBy(), Valid: true}, + } + + return settings.Nullify() +} diff --git a/database/types/settings_test.go b/database/types/settings_test.go new file mode 100644 index 000000000..40285eb34 --- /dev/null +++ b/database/types/settings_test.go @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "testing" + + api "github.com/go-vela/server/api/types/settings" +) + +func TestTypes_Platform_Nullify(t *testing.T) { + // setup types + var ps *Platform + + want := &Platform{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + } + + // setup tests + tests := []struct { + repo *Platform + want *Platform + }{ + { + repo: testPlatform(), + want: testPlatform(), + }, + { + repo: ps, + want: nil, + }, + { + repo: new(Platform), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.repo.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Platform_ToAPI(t *testing.T) { + // setup types + want := new(api.Platform) + want.SetID(1) + want.SetRepoAllowlist([]string{"github/octocat"}) + want.SetScheduleAllowlist([]string{"*"}) + want.SetCreatedAt(0) + want.SetUpdatedAt(0) + want.SetUpdatedBy("") + + want.Compiler = new(api.Compiler) + want.SetCloneImage("target/vela-git:latest") + want.SetTemplateDepth(10) + want.SetStarlarkExecLimit(100) + + want.Queue = new(api.Queue) + want.SetRoutes([]string{"vela"}) + + // run test + got := testPlatform().ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_Platform_Validate(t *testing.T) { + // setup tests + tests := []struct { + failure bool + settings *Platform + }{ + { + failure: false, + settings: testPlatform(), + }, + { // no CloneImage set for settings + failure: true, + settings: &Platform{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Compiler: Compiler{ + TemplateDepth: sql.NullInt64{Int64: 10, Valid: true}, + StarlarkExecLimit: sql.NullInt64{Int64: 100, Valid: true}, + }, + }, + }, + { // no TemplateDepth set for settings + failure: true, + settings: &Platform{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Compiler: Compiler{ + CloneImage: sql.NullString{String: "target/vela-git:latest", Valid: true}, + StarlarkExecLimit: sql.NullInt64{Int64: 100, Valid: true}, + }, + }, + }, + { // no StarlarkExecLimit set for settings + failure: true, + settings: &Platform{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Compiler: Compiler{ + CloneImage: sql.NullString{String: "target/vela-git:latest", Valid: true}, + TemplateDepth: sql.NullInt64{Int64: 10, Valid: true}, + }, + }, + }, + { // no queue fields set for settings + failure: false, + settings: &Platform{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Compiler: Compiler{ + CloneImage: sql.NullString{String: "target/vela-git:latest", Valid: true}, + TemplateDepth: sql.NullInt64{Int64: 10, Valid: true}, + StarlarkExecLimit: sql.NullInt64{Int64: 100, Valid: true}, + }, + Queue: Queue{}, + }, + }, + } + + // run tests + for _, test := range tests { + err := test.settings.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestTypes_Platform_PlatformFromAPI(t *testing.T) { + // setup types + s := new(api.Platform) + s.SetID(1) + s.SetRepoAllowlist([]string{"github/octocat"}) + s.SetScheduleAllowlist([]string{"*"}) + s.SetCreatedAt(0) + s.SetUpdatedAt(0) + s.SetUpdatedBy("") + + s.Compiler = new(api.Compiler) + s.SetCloneImage("target/vela-git:latest") + s.SetTemplateDepth(10) + s.SetStarlarkExecLimit(100) + + s.Queue = new(api.Queue) + s.SetRoutes([]string{"vela"}) + + want := testPlatform() + + // run test + got := SettingsFromAPI(s) + + if !reflect.DeepEqual(got, want) { + t.Errorf("PlatformFromAPI is %v, want %v", got, want) + } +} + +// testPlatform is a test helper function to create a Platform +// type with all fields set to a fake value. +func testPlatform() *Platform { + return &Platform{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Compiler: Compiler{ + CloneImage: sql.NullString{String: "target/vela-git:latest", Valid: true}, + TemplateDepth: sql.NullInt64{Int64: 10, Valid: true}, + StarlarkExecLimit: sql.NullInt64{Int64: 100, Valid: true}, + }, + Queue: Queue{ + Routes: []string{"vela"}, + }, + RepoAllowlist: []string{"github/octocat"}, + ScheduleAllowlist: []string{"*"}, + CreatedAt: sql.NullInt64{Int64: 0, Valid: true}, + UpdatedAt: sql.NullInt64{Int64: 0, Valid: true}, + UpdatedBy: sql.NullString{String: "", Valid: true}, + } +} diff --git a/database/types/user.go b/database/types/user.go new file mode 100644 index 000000000..bde757a6b --- /dev/null +++ b/database/types/user.go @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "encoding/base64" + "errors" + "regexp" + + "github.com/lib/pq" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/util" +) + +var ( + // userRegex defines the regex pattern for validating + // the Name field for the User type. + userRegex = regexp.MustCompile("^[a-zA-Z0-9_-]{0,38}$") + + // ErrEmptyUserName defines the error type when a + // User type has an empty Name field provided. + ErrEmptyUserName = errors.New("empty user name provided") + + // ErrEmptyUserRefreshToken defines the error type when a + // User type has an empty RefreshToken field provided. + ErrEmptyUserRefreshToken = errors.New("empty user refresh token provided") + + // ErrEmptyUserToken defines the error type when a + // User type has an empty Token field provided. + ErrEmptyUserToken = errors.New("empty user token provided") + + // ErrInvalidUserName defines the error type when a + // User type has an invalid Name field provided. + ErrInvalidUserName = errors.New("invalid user name provided") + + // ErrExceededFavoritesLimit defines the error type when a + // User type has Favorites field provided that exceeds the database limit. + ErrExceededFavoritesLimit = errors.New("exceeded favorites limit") + + // ErrExceededDashboardsLimit defines the error type when a + // User type has Dashboards field provided that exceeds the database limit. + ErrExceededDashboardsLimit = errors.New("exceeded dashboards limit") +) + +// User is the database representation of a user. +type User struct { + ID sql.NullInt64 `sql:"id"` + Name sql.NullString `sql:"name"` + RefreshToken sql.NullString `sql:"refresh_token"` + Token sql.NullString `sql:"token"` + Favorites pq.StringArray `sql:"favorites" gorm:"type:varchar(5000)"` + Active sql.NullBool `sql:"active"` + Admin sql.NullBool `sql:"admin"` + Dashboards pq.StringArray `sql:"dashboards" gorm:"type:varchar(5000)"` +} + +// Decrypt will manipulate the existing user tokens by +// base64 decoding them. Then, a AES-256 cipher +// block is created from the encryption key in order to +// decrypt the base64 decoded user tokens. +func (u *User) Decrypt(key string) error { + // base64 decode the encrypted user token + decoded, err := base64.StdEncoding.DecodeString(u.Token.String) + if err != nil { + return err + } + + // decrypt the base64 decoded user token + decrypted, err := util.Decrypt(key, decoded) + if err != nil { + return err + } + + // set the decrypted user token + u.Token = sql.NullString{ + String: string(decrypted), + Valid: true, + } + + // base64 decode the encrypted user refresh token + decoded, err = base64.StdEncoding.DecodeString(u.RefreshToken.String) + if err != nil { + return err + } + + // decrypt the base64 decoded user refresh token + decrypted, err = util.Decrypt(key, decoded) + if err != nil { + return err + } + + // set the decrypted user refresh token + u.RefreshToken = sql.NullString{ + String: string(decrypted), + Valid: true, + } + + return nil +} + +// Encrypt will manipulate the existing user tokens by +// creating a AES-256 cipher block from the encryption +// key in order to encrypt the user tokens. Then, the +// user tokens are base64 encoded for transport across +// network boundaries. +func (u *User) Encrypt(key string) error { + // encrypt the user token + encrypted, err := util.Encrypt(key, []byte(u.Token.String)) + if err != nil { + return err + } + + // base64 encode the encrypted user token to make it network safe + u.Token = sql.NullString{ + String: base64.StdEncoding.EncodeToString(encrypted), + Valid: true, + } + + // encrypt the user refresh token + encrypted, err = util.Encrypt(key, []byte(u.RefreshToken.String)) + if err != nil { + return err + } + + // base64 encode the encrypted user refresh token to make it network safe + u.RefreshToken = sql.NullString{ + String: base64.StdEncoding.EncodeToString(encrypted), + Valid: true, + } + + return nil +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the User type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (u *User) Nullify() *User { + if u == nil { + return nil + } + + // check if the ID field should be false + if u.ID.Int64 == 0 { + u.ID.Valid = false + } + + // check if the Name field should be false + if len(u.Name.String) == 0 { + u.Name.Valid = false + } + + // check if the RefreshToken field should be false + if len(u.RefreshToken.String) == 0 { + u.RefreshToken.Valid = false + } + + // check if the Token field should be false + if len(u.Token.String) == 0 { + u.Token.Valid = false + } + + return u +} + +// ToAPI converts the User type +// to an API User type. +func (u *User) ToAPI() *api.User { + user := new(api.User) + + user.SetID(u.ID.Int64) + user.SetName(u.Name.String) + user.SetRefreshToken(u.RefreshToken.String) + user.SetToken(u.Token.String) + user.SetActive(u.Active.Bool) + user.SetAdmin(u.Admin.Bool) + user.SetFavorites(u.Favorites) + user.SetDashboards(u.Dashboards) + + return user +} + +// Validate verifies the necessary fields for +// the User type are populated correctly. +func (u *User) Validate() error { + // verify the Name field is populated + if len(u.Name.String) == 0 { + return ErrEmptyUserName + } + + // verify the Token field is populated + if len(u.Token.String) == 0 { + return ErrEmptyUserToken + } + + // verify the Name field is valid + if !userRegex.MatchString(u.Name.String) { + return ErrInvalidUserName + } + + // calculate total size of favorites + total := 0 + for _, f := range u.Favorites { + total += len(f) + } + + // verify the Favorites field is within the database constraints + // len is to factor in number of comma separators included in the database field, + // removing 1 due to the last item not having an appended comma + if (total + len(u.Favorites) - 1) > constants.FavoritesMaxSize { + return ErrExceededFavoritesLimit + } + + // validate number of dashboards + if len(u.Dashboards) > constants.UserDashboardLimit { + return ErrExceededDashboardsLimit + } + + // ensure that all User string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + u.Name = sql.NullString{String: util.Sanitize(u.Name.String), Valid: u.Name.Valid} + + // ensure that all Favorites are sanitized + // to avoid unsafe HTML content + for i, v := range u.Favorites { + u.Favorites[i] = util.Sanitize(v) + } + + return nil +} + +// UserFromAPI converts the API User type +// to a database User type. +func UserFromAPI(u *api.User) *User { + user := &User{ + ID: sql.NullInt64{Int64: u.GetID(), Valid: true}, + Name: sql.NullString{String: u.GetName(), Valid: true}, + RefreshToken: sql.NullString{String: u.GetRefreshToken(), Valid: true}, + Token: sql.NullString{String: u.GetToken(), Valid: true}, + Active: sql.NullBool{Bool: u.GetActive(), Valid: true}, + Admin: sql.NullBool{Bool: u.GetAdmin(), Valid: true}, + Favorites: pq.StringArray(u.GetFavorites()), + Dashboards: pq.StringArray(u.GetDashboards()), + } + + return user.Nullify() +} diff --git a/database/types/user_test.go b/database/types/user_test.go new file mode 100644 index 000000000..3d02427e8 --- /dev/null +++ b/database/types/user_test.go @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "strconv" + "testing" + + api "github.com/go-vela/server/api/types" +) + +func TestTypes_User_Decrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + encrypted := testUser() + + err := encrypted.Encrypt(key) + if err != nil { + t.Errorf("unable to encrypt user: %v", err) + } + + // setup tests + tests := []struct { + failure bool + key string + user User + }{ + { + failure: false, + key: key, + user: *encrypted, + }, + { + failure: true, + key: "", + user: *encrypted, + }, + { + failure: true, + key: key, + user: *testUser(), + }, + } + + // run tests + for _, test := range tests { + err := test.user.Decrypt(test.key) + + if test.failure { + if err == nil { + t.Errorf("Decrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Decrypt returned err: %v", err) + } + } +} + +func TestTypes_User_Encrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + + // setup tests + tests := []struct { + failure bool + key string + user *User + }{ + { + failure: false, + key: key, + user: testUser(), + }, + { + failure: true, + key: "", + user: testUser(), + }, + } + + // run tests + for _, test := range tests { + err := test.user.Encrypt(test.key) + + if test.failure { + if err == nil { + t.Errorf("Encrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Encrypt returned err: %v", err) + } + } +} + +func TestTypes_User_Nullify(t *testing.T) { + // setup types + var u *User + + want := &User{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + Name: sql.NullString{String: "", Valid: false}, + RefreshToken: sql.NullString{String: "", Valid: false}, + Token: sql.NullString{String: "", Valid: false}, + Active: sql.NullBool{Bool: false, Valid: false}, + Admin: sql.NullBool{Bool: false, Valid: false}, + } + + // setup tests + tests := []struct { + user *User + want *User + }{ + { + user: testUser(), + want: testUser(), + }, + { + user: u, + want: nil, + }, + { + user: new(User), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.user.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_User_ToAPI(t *testing.T) { + // setup types + want := new(api.User) + + want.SetID(1) + want.SetName("octocat") + want.SetRefreshToken("superSecretRefreshToken") + want.SetToken("superSecretToken") + want.SetFavorites([]string{"github/octocat"}) + want.SetActive(true) + want.SetAdmin(false) + want.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + // run test + got := testUser().ToAPI() + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_User_Validate(t *testing.T) { + // setup tests + tests := []struct { + failure bool + user *User + }{ + { + failure: false, + user: testUser(), + }, + { // no name set for user + failure: true, + user: &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Token: sql.NullString{String: "superSecretToken", Valid: true}, + }, + }, + { // no token set for user + failure: true, + user: &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + }, + }, + { // invalid name set for user + failure: true, + user: &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "!@#$%^&*()", Valid: true}, + RefreshToken: sql.NullString{String: "superSecretRefreshToken", Valid: true}, + Token: sql.NullString{String: "superSecretToken", Valid: true}, + }, + }, + { // invalid favorites set for user + failure: true, + user: &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + Token: sql.NullString{String: "superSecretToken", Valid: true}, + Favorites: exceededField(500), + }, + }, + { // invalid dashboards set for user + failure: true, + user: &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + Token: sql.NullString{String: "superSecretToken", Valid: true}, + Dashboards: exceededField(11), + }, + }, + } + + // run tests + for _, test := range tests { + err := test.user.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestFromAPI(t *testing.T) { + // setup types + u := new(api.User) + + u.SetID(1) + u.SetName("octocat") + u.SetRefreshToken("superSecretRefreshToken") + u.SetToken("superSecretToken") + u.SetFavorites([]string{"github/octocat"}) + u.SetActive(true) + u.SetAdmin(false) + u.SetDashboards([]string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}) + + want := testUser() + + // run test + got := UserFromAPI(u) + + if !reflect.DeepEqual(got, want) { + t.Errorf("FromAPI is %v, want %v", got, want) + } +} + +// testUser is a test helper function to create a User +// type with all fields set to a fake value. +func testUser() *User { + return &User{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Name: sql.NullString{String: "octocat", Valid: true}, + RefreshToken: sql.NullString{String: "superSecretRefreshToken", Valid: true}, + Token: sql.NullString{String: "superSecretToken", Valid: true}, + Favorites: []string{"github/octocat"}, + Active: sql.NullBool{Bool: true, Valid: true}, + Admin: sql.NullBool{Bool: false, Valid: true}, + Dashboards: []string{"45bcf19b-c151-4e2d-b8c6-80a62ba2eae7"}, + } +} + +// exceededField returns a list of strings that exceed the maximum size of a field. +func exceededField(indexes int) []string { + // initialize empty favorites + values := []string{} + + // add enough strings to exceed the character limit + for i := 0; i < indexes; i++ { + // construct field + // use i to adhere to unique favorites + field := "github/octocat-" + strconv.Itoa(i) + + values = append(values, field) + } + + return values +} diff --git a/database/types/worker.go b/database/types/worker.go new file mode 100644 index 000000000..16062e65b --- /dev/null +++ b/database/types/worker.go @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "errors" + "fmt" + + "github.com/lib/pq" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" +) + +var ( + // ErrEmptyWorkerHost defines the error type when a + // Worker type has an empty Host field provided. + ErrEmptyWorkerHost = errors.New("empty worker hostname provided") + + // ErrEmptyWorkerAddress defines the error type when a + // Worker type has an empty Address field provided. + ErrEmptyWorkerAddress = errors.New("empty worker address provided") + + // ErrExceededRunningBuildIDsLimit defines the error type when a + // Worker type has RunningBuildIDs field provided that exceeds the database limit. + ErrExceededRunningBuildIDsLimit = errors.New("exceeded running build ids limit") +) + +// Worker is the database representation of a worker. +type Worker struct { + ID sql.NullInt64 `sql:"id"` + Hostname sql.NullString `sql:"hostname"` + Address sql.NullString `sql:"address"` + Routes pq.StringArray `sql:"routes" gorm:"type:varchar(1000)"` + Active sql.NullBool `sql:"active"` + Status sql.NullString `sql:"status"` + LastStatusUpdateAt sql.NullInt64 `sql:"last_status_update_at"` + RunningBuildIDs pq.StringArray `sql:"running_build_ids" gorm:"type:varchar(500)"` + LastBuildStartedAt sql.NullInt64 `sql:"last_build_started_at"` + LastBuildFinishedAt sql.NullInt64 `sql:"last_build_finished_at"` + LastCheckedIn sql.NullInt64 `sql:"last_checked_in"` + BuildLimit sql.NullInt64 `sql:"build_limit"` +} + +// Nullify ensures the valid flag for +// the sql.Null types are properly set. +// +// When a field within the Build type is the zero +// value for the field, the valid flag is set to +// false causing it to be NULL in the database. +func (w *Worker) Nullify() *Worker { + if w == nil { + return nil + } + + // check if the ID field should be false + if w.ID.Int64 == 0 { + w.ID.Valid = false + } + + // check if the Hostname field should be false + if len(w.Hostname.String) == 0 { + w.Hostname.Valid = false + } + + // check if the Address field should be false + if len(w.Address.String) == 0 { + w.Address.Valid = false + } + + // check if the Status field should be false + if len(w.Status.String) == 0 { + w.Status.Valid = false + } + + // check if the LastStatusUpdateAt field should be false + if w.LastStatusUpdateAt.Int64 == 0 { + w.LastStatusUpdateAt.Valid = false + } + + // check if the LastBuildStartedAt field should be false + if w.LastBuildStartedAt.Int64 == 0 { + w.LastBuildStartedAt.Valid = false + } + + // check if the LastBuildFinishedAt field should be false + if w.LastBuildFinishedAt.Int64 == 0 { + w.LastBuildFinishedAt.Valid = false + } + + // check if the LastCheckedIn field should be false + if w.LastCheckedIn.Int64 == 0 { + w.LastCheckedIn.Valid = false + } + + if w.BuildLimit.Int64 == 0 { + w.BuildLimit.Valid = false + } + + return w +} + +// ToAPI converts the Worker type +// to an API Worker type. +func (w *Worker) ToAPI(builds []*api.Build) *api.Worker { + worker := new(api.Worker) + + worker.SetID(w.ID.Int64) + worker.SetHostname(w.Hostname.String) + worker.SetAddress(w.Address.String) + worker.SetRoutes(w.Routes) + worker.SetActive(w.Active.Bool) + worker.SetStatus(w.Status.String) + worker.SetLastStatusUpdateAt(w.LastStatusUpdateAt.Int64) + worker.SetRunningBuilds(builds) + worker.SetLastBuildStartedAt(w.LastBuildStartedAt.Int64) + worker.SetLastBuildFinishedAt(w.LastBuildFinishedAt.Int64) + worker.SetLastCheckedIn(w.LastCheckedIn.Int64) + worker.SetBuildLimit(w.BuildLimit.Int64) + + return worker +} + +// Validate verifies the necessary fields for +// the Worker type are populated correctly. +func (w *Worker) Validate() error { + // verify the Host field is populated + if len(w.Hostname.String) == 0 { + return ErrEmptyWorkerHost + } + + // verify the Address field is populated + if len(w.Address.String) == 0 { + return ErrEmptyWorkerAddress + } + + // calculate total size of RunningBuildIds + total := 0 + for _, f := range w.RunningBuildIDs { + total += len(f) + } + + // verify the RunningBuildIds field is within the database constraints + // len is to factor in number of comma separators included in the database field, + // removing 1 due to the last item not having an appended comma + if (total + len(w.RunningBuildIDs) - 1) > constants.RunningBuildIDsMaxSize { + return ErrExceededRunningBuildIDsLimit + } + + // ensure that all Worker string fields + // that can be returned as JSON are sanitized + // to avoid unsafe HTML content + w.Hostname = sql.NullString{String: util.Sanitize(w.Hostname.String), Valid: w.Hostname.Valid} + w.Address = sql.NullString{String: util.Sanitize(w.Address.String), Valid: w.Address.Valid} + + // ensure that all Routes are sanitized + // to avoid unsafe HTML content + for i, v := range w.Routes { + w.Routes[i] = util.Sanitize(v) + } + + return nil +} + +// WorkerFromAPI converts the API worker type +// to a database worker type. +func WorkerFromAPI(w *api.Worker) *Worker { + var rBs []string + + for _, b := range w.GetRunningBuilds() { + rBs = append(rBs, fmt.Sprint(b.GetID())) + } + + worker := &Worker{ + ID: sql.NullInt64{Int64: w.GetID(), Valid: true}, + Hostname: sql.NullString{String: w.GetHostname(), Valid: true}, + Address: sql.NullString{String: w.GetAddress(), Valid: true}, + Routes: pq.StringArray(w.GetRoutes()), + Active: sql.NullBool{Bool: w.GetActive(), Valid: true}, + Status: sql.NullString{String: w.GetStatus(), Valid: true}, + LastStatusUpdateAt: sql.NullInt64{Int64: w.GetLastStatusUpdateAt(), Valid: true}, + RunningBuildIDs: pq.StringArray(rBs), + LastBuildStartedAt: sql.NullInt64{Int64: w.GetLastBuildStartedAt(), Valid: true}, + LastBuildFinishedAt: sql.NullInt64{Int64: w.GetLastBuildFinishedAt(), Valid: true}, + LastCheckedIn: sql.NullInt64{Int64: w.GetLastCheckedIn(), Valid: true}, + BuildLimit: sql.NullInt64{Int64: w.GetBuildLimit(), Valid: true}, + } + + return worker.Nullify() +} diff --git a/database/types/worker_test.go b/database/types/worker_test.go new file mode 100644 index 000000000..ec835d1f1 --- /dev/null +++ b/database/types/worker_test.go @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "database/sql" + "reflect" + "strconv" + "testing" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" +) + +func TestTypes_Worker_Nullify(t *testing.T) { + // setup types + var w *Worker + + want := &Worker{ + ID: sql.NullInt64{Int64: 0, Valid: false}, + Hostname: sql.NullString{String: "", Valid: false}, + Address: sql.NullString{String: "", Valid: false}, + Active: sql.NullBool{Bool: false, Valid: false}, + Status: sql.NullString{String: "", Valid: false}, + LastStatusUpdateAt: sql.NullInt64{Int64: 0, Valid: false}, + LastBuildStartedAt: sql.NullInt64{Int64: 0, Valid: false}, + LastBuildFinishedAt: sql.NullInt64{Int64: 0, Valid: false}, + LastCheckedIn: sql.NullInt64{Int64: 0, Valid: false}, + BuildLimit: sql.NullInt64{Int64: 0, Valid: false}, + } + + // setup tests + tests := []struct { + repo *Worker + want *Worker + }{ + { + repo: testWorker(), + want: testWorker(), + }, + { + repo: w, + want: nil, + }, + { + repo: new(Worker), + want: want, + }, + } + + // run tests + for _, test := range tests { + got := test.repo.Nullify() + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Nullify is %v, want %v", got, test.want) + } + } +} + +func TestTypes_Worker_ToAPI(t *testing.T) { + // setup types + rB := testutils.APIBuild() + rB.SetID(1) + + want := new(api.Worker) + + want.SetID(1) + want.SetHostname("worker_0") + want.SetAddress("http://localhost:8080") + want.SetRoutes([]string{"vela"}) + want.SetActive(true) + want.SetStatus("available") + want.SetLastStatusUpdateAt(1563474077) + want.SetRunningBuilds([]*api.Build{rB}) + want.SetLastBuildStartedAt(1563474077) + want.SetLastBuildFinishedAt(1563474077) + want.SetLastCheckedIn(1563474077) + want.SetBuildLimit(2) + + // run test + got := testWorker().ToAPI(want.GetRunningBuilds()) + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToAPI is %v, want %v", got, want) + } +} + +func TestTypes_Worker_Validate(t *testing.T) { + // setup tests + tests := []struct { + failure bool + worker *Worker + }{ + { + failure: false, + worker: testWorker(), + }, + { // no Hostname set for worker + failure: true, + worker: &Worker{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Address: sql.NullString{String: "http://localhost:8080", Valid: true}, + Active: sql.NullBool{Bool: true, Valid: true}, + LastCheckedIn: sql.NullInt64{Int64: 1563474077, Valid: true}, + }, + }, + { // no Address set for worker + failure: true, + worker: &Worker{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Hostname: sql.NullString{String: "worker_0", Valid: true}, + Active: sql.NullBool{Bool: true, Valid: true}, + LastCheckedIn: sql.NullInt64{Int64: 1563474077, Valid: true}, + }, + }, + { // invalid RunningBuildIDs set for worker + failure: true, + worker: &Worker{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Address: sql.NullString{String: "http://localhost:8080", Valid: true}, + Hostname: sql.NullString{String: "worker_0", Valid: true}, + Active: sql.NullBool{Bool: true, Valid: true}, + RunningBuildIDs: exceededRunningBuildIDs(), + LastCheckedIn: sql.NullInt64{Int64: 1563474077, Valid: true}, + }, + }, + } + + // run tests + for _, test := range tests { + err := test.worker.Validate() + + if test.failure { + if err == nil { + t.Errorf("Validate should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Validate returned err: %v", err) + } + } +} + +func TestTypes_Worker_WorkerFromAPI(t *testing.T) { + // setup types + rB := testutils.APIBuild() + rB.SetID(1) + + w := new(api.Worker) + + w.SetID(1) + w.SetHostname("worker_0") + w.SetAddress("http://localhost:8080") + w.SetRoutes([]string{"vela"}) + w.SetActive(true) + w.SetStatus("available") + w.SetLastStatusUpdateAt(1563474077) + w.SetRunningBuilds([]*api.Build{rB}) + w.SetLastBuildStartedAt(1563474077) + w.SetLastBuildFinishedAt(1563474077) + w.SetLastCheckedIn(1563474077) + w.SetBuildLimit(2) + + want := testWorker() + + // run test + got := WorkerFromAPI(w) + + if !reflect.DeepEqual(got, want) { + t.Errorf("WorkerFromLibrary is %v, want %v", got, want) + } +} + +// testWorker is a test helper function to create a Worker +// type with all fields set to a fake value. +func testWorker() *Worker { + return &Worker{ + ID: sql.NullInt64{Int64: 1, Valid: true}, + Hostname: sql.NullString{String: "worker_0", Valid: true}, + Address: sql.NullString{String: "http://localhost:8080", Valid: true}, + Routes: []string{"vela"}, + Active: sql.NullBool{Bool: true, Valid: true}, + Status: sql.NullString{String: "available", Valid: true}, + LastStatusUpdateAt: sql.NullInt64{Int64: 1563474077, Valid: true}, + RunningBuildIDs: []string{"1"}, + LastBuildStartedAt: sql.NullInt64{Int64: 1563474077, Valid: true}, + LastBuildFinishedAt: sql.NullInt64{Int64: 1563474077, Valid: true}, + LastCheckedIn: sql.NullInt64{Int64: 1563474077, Valid: true}, + BuildLimit: sql.NullInt64{Int64: 2, Valid: true}, + } +} + +// exceededRunningBuildIDs returns a list of valid running builds that exceed the maximum size. +func exceededRunningBuildIDs() []string { + // initialize empty runningBuildIDs + runningBuildIDs := []string{} + + // add enough build ids to exceed the character limit + for i := 0; i < 50; i++ { + // construct runningBuildID + // use i to adhere to unique runningBuildIDs + runningBuildID := "1234567890-" + strconv.Itoa(i) + + runningBuildIDs = append(runningBuildIDs, runningBuildID) + } + + return runningBuildIDs +} diff --git a/database/user/count.go b/database/user/count.go index dc5554b37..c48fa00ed 100644 --- a/database/user/count.go +++ b/database/user/count.go @@ -10,7 +10,7 @@ import ( // CountUsers gets the count of all users from the database. func (e *engine) CountUsers(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all users from the database") + e.logger.Tracef("getting count of all users") // variable to store query results var u int64 diff --git a/database/user/count_test.go b/database/user/count_test.go index 78af924aa..ebeb32181 100644 --- a/database/user/count_test.go +++ b/database/user/count_test.go @@ -8,21 +8,21 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_CountUsers(t *testing.T) { // setup types - _userOne := testUser() + _userOne := testutils.APIUser() _userOne.SetID(1) _userOne.SetName("foo") _userOne.SetToken("bar") - _userOne.SetHash("baz") - _userTwo := testUser() + _userTwo := testutils.APIUser() _userTwo.SetID(2) _userTwo.SetName("baz") _userTwo.SetToken("bar") - _userTwo.SetHash("foo") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() diff --git a/database/user/create.go b/database/user/create.go index 6010d1e8f..e9cf0f2c6 100644 --- a/database/user/create.go +++ b/database/user/create.go @@ -7,22 +7,21 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CreateUser creates a new user in the database. -func (e *engine) CreateUser(ctx context.Context, u *library.User) (*library.User, error) { +func (e *engine) CreateUser(ctx context.Context, u *api.User) (*api.User, error) { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), - }).Tracef("creating user %s in the database", u.GetName()) + }).Tracef("creating user %s", u.GetName()) - // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) + // cast the API type to database type + user := types.UserFromAPI(u) // validate the necessary fields are populated // @@ -49,5 +48,5 @@ func (e *engine) CreateUser(ctx context.Context, u *library.User) (*library.User return nil, fmt.Errorf("unable to decrypt user %s: %w", u.GetName(), err) } - return user.ToLibrary(), result.Error + return user.ToAPI(), result.Error } diff --git a/database/user/create_test.go b/database/user/create_test.go index 283bdb443..97dea7f3e 100644 --- a/database/user/create_test.go +++ b/database/user/create_test.go @@ -8,15 +8,16 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_CreateUser(t *testing.T) { // setup types - _user := testUser() + _user := testutils.APIUser() _user.SetID(1) _user.SetName("foo") _user.SetToken("bar") - _user.SetHash("baz") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -26,9 +27,9 @@ func TestUser_Engine_CreateUser(t *testing.T) { // ensure the mock expects the query _mock.ExpectQuery(`INSERT INTO "users" -("name","refresh_token","token","hash","favorites","active","admin","id") +("name","refresh_token","token","favorites","active","admin","dashboards","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`). - WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1). + WithArgs("foo", AnyArgument{}, AnyArgument{}, nil, false, false, AnyArgument{}, 1). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/user/delete.go b/database/user/delete.go index 5747bf404..3154461e2 100644 --- a/database/user/delete.go +++ b/database/user/delete.go @@ -5,22 +5,21 @@ package user import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // DeleteUser deletes an existing user from the database. -func (e *engine) DeleteUser(ctx context.Context, u *library.User) error { +func (e *engine) DeleteUser(ctx context.Context, u *api.User) error { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), - }).Tracef("deleting user %s from the database", u.GetName()) + }).Tracef("deleting user %s", u.GetName()) - // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) + // cast the API type to database type + user := types.UserFromAPI(u) // send query to the database return e.client. diff --git a/database/user/delete_test.go b/database/user/delete_test.go index c06ccf5e2..2a282d856 100644 --- a/database/user/delete_test.go +++ b/database/user/delete_test.go @@ -7,15 +7,16 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_DeleteUser(t *testing.T) { // setup types - _user := testUser() + _user := testutils.APIUser() _user.SetID(1) _user.SetName("foo") _user.SetToken("bar") - _user.SetHash("baz") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() diff --git a/database/user/get.go b/database/user/get.go index e488ce73f..a2f7d8468 100644 --- a/database/user/get.go +++ b/database/user/get.go @@ -5,17 +5,17 @@ package user import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // GetUser gets a user by ID from the database. -func (e *engine) GetUser(ctx context.Context, id int64) (*library.User, error) { - e.logger.Tracef("getting user %d from the database", id) +func (e *engine) GetUser(ctx context.Context, id int64) (*api.User, error) { + e.logger.Tracef("getting user %d", id) // variable to store query results - u := new(database.User) + u := new(types.User) // send query to the database and store result in variable err := e.client. @@ -28,8 +28,6 @@ func (e *engine) GetUser(ctx context.Context, id int64) (*library.User, error) { } // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt err = u.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -41,7 +39,5 @@ func (e *engine) GetUser(ctx context.Context, id int64) (*library.User, error) { } // return the decrypted user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - return u.ToLibrary(), nil + return u.ToAPI(), nil } diff --git a/database/user/get_name.go b/database/user/get_name.go index d23ce1611..8d3633eb8 100644 --- a/database/user/get_name.go +++ b/database/user/get_name.go @@ -5,20 +5,21 @@ package user import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // GetUserForName gets a user by name from the database. -func (e *engine) GetUserForName(ctx context.Context, name string) (*library.User, error) { +func (e *engine) GetUserForName(ctx context.Context, name string) (*api.User, error) { e.logger.WithFields(logrus.Fields{ "user": name, - }).Tracef("getting user %s from the database", name) + }).Tracef("getting user %s", name) // variable to store query results - u := new(database.User) + u := new(types.User) // send query to the database and store result in variable err := e.client. @@ -31,8 +32,6 @@ func (e *engine) GetUserForName(ctx context.Context, name string) (*library.User } // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt err = u.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -44,7 +43,5 @@ func (e *engine) GetUserForName(ctx context.Context, name string) (*library.User } // return the decrypted user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - return u.ToLibrary(), nil + return u.ToAPI(), nil } diff --git a/database/user/get_name_test.go b/database/user/get_name_test.go index 621a75c87..b4bbe7f74 100644 --- a/database/user/get_name_test.go +++ b/database/user/get_name_test.go @@ -8,25 +8,28 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_GetUserForName(t *testing.T) { // setup types - _user := testUser() + _user := testutils.APIUser() _user.SetID(1) _user.SetName("foo") _user.SetToken("bar") - _user.SetHash("baz") + _user.SetFavorites([]string{}) + _user.SetDashboards([]string{}) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). - AddRow(1, "foo", "", "bar", "baz", "{}", false, false) + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "users" WHERE name = $1 LIMIT $2`).WithArgs("foo", 1).WillReturnRows(_rows) @@ -44,7 +47,7 @@ func TestUser_Engine_GetUserForName(t *testing.T) { failure bool name string database *engine - want *library.User + want *api.User }{ { failure: false, diff --git a/database/user/get_test.go b/database/user/get_test.go index f879b8bd9..3981799f3 100644 --- a/database/user/get_test.go +++ b/database/user/get_test.go @@ -8,25 +8,28 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_GetUser(t *testing.T) { // setup types - _user := testUser() + _user := testutils.APIUser() _user.SetID(1) _user.SetName("foo") _user.SetToken("bar") - _user.SetHash("baz") + _user.SetFavorites([]string{}) + _user.SetDashboards([]string{}) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // create expected result in mock _rows := sqlmock.NewRows( - []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). - AddRow(1, "foo", "", "bar", "baz", "{}", false, false) + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "users" WHERE id = $1 LIMIT $2`).WithArgs(1, 1).WillReturnRows(_rows) @@ -44,7 +47,7 @@ func TestUser_Engine_GetUser(t *testing.T) { failure bool name string database *engine - want *library.User + want *api.User }{ { failure: false, diff --git a/database/user/index.go b/database/user/index.go index 25c3edef3..d35e1accd 100644 --- a/database/user/index.go +++ b/database/user/index.go @@ -17,7 +17,7 @@ ON users (refresh_token); // CreateUserIndexes creates the indexes for the users table in the database. func (e *engine) CreateUserIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for users table in the database") + e.logger.Tracef("creating indexes for users table") // create the refresh_token column index for the users table return e.client.Exec(CreateUserRefreshIndex).Error diff --git a/database/user/interface.go b/database/user/interface.go index a5801d53a..55a1b2d62 100644 --- a/database/user/interface.go +++ b/database/user/interface.go @@ -5,7 +5,7 @@ package user import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) // UserInterface represents the Vela interface for user @@ -29,17 +29,17 @@ type UserInterface interface { // CountUsers defines a function that gets the count of all users. CountUsers(context.Context) (int64, error) // CreateUser defines a function that creates a new user. - CreateUser(context.Context, *library.User) (*library.User, error) + CreateUser(context.Context, *api.User) (*api.User, error) // DeleteUser defines a function that deletes an existing user. - DeleteUser(context.Context, *library.User) error + DeleteUser(context.Context, *api.User) error // GetUser defines a function that gets a user by ID. - GetUser(context.Context, int64) (*library.User, error) + GetUser(context.Context, int64) (*api.User, error) // GetUserForName defines a function that gets a user by name. - GetUserForName(context.Context, string) (*library.User, error) + GetUserForName(context.Context, string) (*api.User, error) // ListUsers defines a function that gets a list of all users. - ListUsers(context.Context) ([]*library.User, error) + ListUsers(context.Context) ([]*api.User, error) // ListLiteUsers defines a function that gets a lite list of users. - ListLiteUsers(context.Context, int, int) ([]*library.User, int64, error) + ListLiteUsers(context.Context, int, int) ([]*api.User, int64, error) // UpdateUser defines a function that updates an existing user. - UpdateUser(context.Context, *library.User) (*library.User, error) + UpdateUser(context.Context, *api.User) (*api.User, error) } diff --git a/database/user/list.go b/database/user/list.go index aaa24644d..80c7f5681 100644 --- a/database/user/list.go +++ b/database/user/list.go @@ -5,19 +5,19 @@ package user import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListUsers gets a list of all users from the database. -func (e *engine) ListUsers(ctx context.Context) ([]*library.User, error) { - e.logger.Trace("listing all users from the database") +func (e *engine) ListUsers(ctx context.Context) ([]*api.User, error) { + e.logger.Trace("listing all users") // variables to store query results and return value count := int64(0) - u := new([]database.User) - users := []*library.User{} + u := new([]types.User) + users := []*api.User{} // count the results count, err := e.CountUsers(ctx) @@ -45,8 +45,6 @@ func (e *engine) ListUsers(ctx context.Context) ([]*library.User, error) { tmp := user // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt err = tmp.Decrypt(e.config.EncryptionKey) if err != nil { // TODO: remove backwards compatibility before 1.x.x release @@ -57,10 +55,8 @@ func (e *engine) ListUsers(ctx context.Context) ([]*library.User, error) { e.logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err) } - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - users = append(users, tmp.ToLibrary()) + // convert query result to API type + users = append(users, tmp.ToAPI()) } return users, nil diff --git a/database/user/list_lite.go b/database/user/list_lite.go index 83a40be56..1a5c570f9 100644 --- a/database/user/list_lite.go +++ b/database/user/list_lite.go @@ -5,21 +5,21 @@ package user import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListLiteUsers gets a lite (only: id, name) list of users from the database. // //nolint:lll // ignore long line length due to variable names -func (e *engine) ListLiteUsers(ctx context.Context, page, perPage int) ([]*library.User, int64, error) { - e.logger.Trace("listing lite users from the database") +func (e *engine) ListLiteUsers(ctx context.Context, page, perPage int) ([]*api.User, int64, error) { + e.logger.Trace("listing lite users") // variables to store query results and return values count := int64(0) - u := new([]database.User) - users := []*library.User{} + u := new([]types.User) + users := []*api.User{} // count the results count, err := e.CountUsers(ctx) @@ -51,10 +51,8 @@ func (e *engine) ListLiteUsers(ctx context.Context, page, perPage int) ([]*libra // https://golang.org/doc/faq#closures_and_goroutines tmp := user - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - users = append(users, tmp.ToLibrary()) + // convert query result to API type + users = append(users, tmp.ToAPI()) } return users, count, nil diff --git a/database/user/list_lite_test.go b/database/user/list_lite_test.go index 431b7705b..31a4b1f0f 100644 --- a/database/user/list_lite_test.go +++ b/database/user/list_lite_test.go @@ -8,24 +8,26 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_ListLiteUsers(t *testing.T) { // setup types - _userOne := testUser() + _userOne := testutils.APIUser() _userOne.SetID(1) _userOne.SetName("foo") _userOne.SetToken("bar") - _userOne.SetHash("baz") _userOne.SetFavorites([]string{}) + _userOne.SetDashboards([]string{}) - _userTwo := testUser() + _userTwo := testutils.APIUser() _userTwo.SetID(2) _userTwo.SetName("baz") _userTwo.SetToken("bar") - _userTwo.SetHash("foo") _userTwo.SetFavorites([]string{}) + _userTwo.SetDashboards([]string{}) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -61,32 +63,32 @@ func TestUser_Engine_ListLiteUsers(t *testing.T) { // empty fields not returned by query _userOne.RefreshToken = new(string) _userOne.Token = new(string) - _userOne.Hash = new(string) _userOne.Favorites = new([]string) + _userOne.Dashboards = new([]string) _userTwo.RefreshToken = new(string) _userTwo.Token = new(string) - _userTwo.Hash = new(string) _userTwo.Favorites = new([]string) + _userTwo.Dashboards = new([]string) // setup tests tests := []struct { failure bool name string database *engine - want []*library.User + want []*api.User }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.User{_userOne, _userTwo}, + want: []*api.User{_userOne, _userTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.User{_userTwo, _userOne}, + want: []*api.User{_userTwo, _userOne}, }, } diff --git a/database/user/list_test.go b/database/user/list_test.go index da61b5b67..ce2e7b3f7 100644 --- a/database/user/list_test.go +++ b/database/user/list_test.go @@ -8,24 +8,26 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_ListUsers(t *testing.T) { // setup types - _userOne := testUser() + _userOne := testutils.APIUser() _userOne.SetID(1) _userOne.SetName("foo") _userOne.SetToken("bar") - _userOne.SetHash("baz") _userOne.SetFavorites([]string{}) + _userOne.SetDashboards([]string{}) - _userTwo := testUser() + _userTwo := testutils.APIUser() _userTwo.SetID(2) _userTwo.SetName("baz") _userTwo.SetToken("bar") - _userTwo.SetHash("foo") _userTwo.SetFavorites([]string{}) + _userTwo.SetDashboards([]string{}) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -38,9 +40,9 @@ func TestUser_Engine_ListUsers(t *testing.T) { // create expected result in mock _rows = sqlmock.NewRows( - []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). - AddRow(1, "foo", "", "bar", "baz", "{}", false, false). - AddRow(2, "baz", "", "bar", "foo", "{}", false, false) + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin", "dashboards"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false, "{}"). + AddRow(2, "baz", "", "bar", "foo", "{}", false, false, "{}") // ensure the mock expects the query _mock.ExpectQuery(`SELECT * FROM "users"`).WillReturnRows(_rows) @@ -63,19 +65,19 @@ func TestUser_Engine_ListUsers(t *testing.T) { failure bool name string database *engine - want []*library.User + want []*api.User }{ { failure: false, name: "postgres", database: _postgres, - want: []*library.User{_userOne, _userTwo}, + want: []*api.User{_userOne, _userTwo}, }, { failure: false, name: "sqlite3", database: _sqlite, - want: []*library.User{_userOne, _userTwo}, + want: []*api.User{_userOne, _userTwo}, }, } diff --git a/database/user/opts.go b/database/user/opts.go index 4901e5be3..96bdf8b38 100644 --- a/database/user/opts.go +++ b/database/user/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/user/opts_test.go b/database/user/opts_test.go index 77c74506c..05df3e33e 100644 --- a/database/user/opts_test.go +++ b/database/user/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/user/table.go b/database/user/table.go index 070546435..183390b43 100644 --- a/database/user/table.go +++ b/database/user/table.go @@ -18,10 +18,10 @@ users ( name VARCHAR(250), refresh_token VARCHAR(500), token VARCHAR(500), - hash VARCHAR(500), favorites VARCHAR(5000), active BOOLEAN, admin BOOLEAN, + dashboards VARCHAR(5000), UNIQUE(name) ); ` @@ -35,10 +35,10 @@ users ( name TEXT, refresh_token TEXT, token TEXT, - hash TEXT, favorites TEXT, active BOOLEAN, admin BOOLEAN, + dashboards TEXT, UNIQUE(name) ); ` @@ -46,7 +46,7 @@ users ( // CreateUserTable creates the users table in the database. func (e *engine) CreateUserTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating users table in the database") + e.logger.Tracef("creating users table") // handle the driver provided to create the table switch driver { diff --git a/database/user/update.go b/database/user/update.go index 043ed71de..19f653f8d 100644 --- a/database/user/update.go +++ b/database/user/update.go @@ -7,34 +7,29 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // UpdateUser updates an existing user in the database. -func (e *engine) UpdateUser(ctx context.Context, u *library.User) (*library.User, error) { +func (e *engine) UpdateUser(ctx context.Context, u *api.User) (*api.User, error) { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), - }).Tracef("updating user %s in the database", u.GetName()) + }).Tracef("updating user %s", u.GetName()) // cast the library type to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) + user := types.UserFromAPI(u) // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate err := user.Validate() if err != nil { return nil, err } // encrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt err = user.Encrypt(e.config.EncryptionKey) if err != nil { return nil, fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) @@ -49,5 +44,5 @@ func (e *engine) UpdateUser(ctx context.Context, u *library.User) (*library.User return nil, fmt.Errorf("unable to decrypt user %s: %w", u.GetName(), err) } - return user.ToLibrary(), result.Error + return user.ToAPI(), result.Error } diff --git a/database/user/update_test.go b/database/user/update_test.go index 83b455d77..8170c6375 100644 --- a/database/user/update_test.go +++ b/database/user/update_test.go @@ -8,24 +8,25 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + + "github.com/go-vela/server/database/testutils" ) func TestUser_Engine_UpdateUser(t *testing.T) { // setup types - _user := testUser() + _user := testutils.APIUser() _user.SetID(1) _user.SetName("foo") _user.SetToken("bar") - _user.SetHash("baz") _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() // ensure the mock expects the query _mock.ExpectExec(`UPDATE "users" -SET "name"=$1,"refresh_token"=$2,"token"=$3,"hash"=$4,"favorites"=$5,"active"=$6,"admin"=$7 +SET "name"=$1,"refresh_token"=$2,"token"=$3,"favorites"=$4,"active"=$5,"admin"=$6,"dashboards"=$7 WHERE "id" = $8`). - WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1). + WithArgs("foo", AnyArgument{}, AnyArgument{}, nil, false, false, nil, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/user/user.go b/database/user/user.go index 2cf13feb2..ad2e2bae5 100644 --- a/database/user/user.go +++ b/database/user/user.go @@ -6,10 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + "github.com/go-vela/types/constants" ) type ( @@ -62,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating user database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of users table and indexes in the database") + e.logger.Warning("skipping creation of users table and indexes") return e, nil } diff --git a/database/user/user_test.go b/database/user/user_test.go index a3ab4ff11..8e33e6e62 100644 --- a/database/user/user_test.go +++ b/database/user/user_test.go @@ -8,9 +8,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -170,21 +168,6 @@ func testSqlite(t *testing.T) *engine { return _engine } -// testUser is a test helper function to create a library -// User type with all fields set to their zero values. -func testUser() *library.User { - return &library.User{ - ID: new(int64), - Name: new(string), - RefreshToken: new(string), - Token: new(string), - Hash: new(string), - Favorites: new([]string), - Active: new(bool), - Admin: new(bool), - } -} - // This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values // that are otherwise not easily compared. These typically would be values generated // before adding or updating them in the database. diff --git a/database/validate.go b/database/validate.go index 4c519d081..8025b325e 100644 --- a/database/validate.go +++ b/database/validate.go @@ -6,8 +6,9 @@ import ( "fmt" "strings" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // Validate verifies the required fields from the provided configuration are populated correctly. diff --git a/database/worker/count.go b/database/worker/count.go index eef910c36..0d992cdca 100644 --- a/database/worker/count.go +++ b/database/worker/count.go @@ -10,7 +10,7 @@ import ( // CountWorkers gets the count of all workers from the database. func (e *engine) CountWorkers(ctx context.Context) (int64, error) { - e.logger.Tracef("getting count of all workers from the database") + e.logger.Tracef("getting count of all workers") // variable to store query results var w int64 diff --git a/database/worker/create.go b/database/worker/create.go index 12b386319..445669deb 100644 --- a/database/worker/create.go +++ b/database/worker/create.go @@ -5,22 +5,23 @@ package worker import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // CreateWorker creates a new worker in the database. -func (e *engine) CreateWorker(ctx context.Context, w *library.Worker) (*library.Worker, error) { +func (e *engine) CreateWorker(ctx context.Context, w *api.Worker) (*api.Worker, error) { e.logger.WithFields(logrus.Fields{ "worker": w.GetHostname(), - }).Tracef("creating worker %s in the database", w.GetHostname()) + }).Tracef("creating worker %s", w.GetHostname()) // cast the library type to database type // // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary - worker := database.WorkerFromLibrary(w) + worker := types.WorkerFromAPI(w) // validate the necessary fields are populated // @@ -33,5 +34,5 @@ func (e *engine) CreateWorker(ctx context.Context, w *library.Worker) (*library. // send query to the database result := e.client.Table(constants.TableWorker).Create(worker) - return worker.ToLibrary(), result.Error + return worker.ToAPI(w.GetRunningBuilds()), result.Error } diff --git a/database/worker/create_test.go b/database/worker/create_test.go index dad81473a..6809bb33e 100644 --- a/database/worker/create_test.go +++ b/database/worker/create_test.go @@ -28,7 +28,7 @@ func TestWorker_Engine_CreateWorker(t *testing.T) { _mock.ExpectQuery(`INSERT INTO "workers" ("hostname","address","routes","active","status","last_status_update_at","running_build_ids","last_build_started_at","last_build_finished_at","last_checked_in","build_limit","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING "id"`). - WithArgs("worker_0", "localhost", nil, true, nil, nil, nil, nil, nil, nil, nil, 1). + WithArgs("worker_0", "localhost", nil, true, nil, nil, `{"1"}`, nil, nil, nil, nil, 1). WillReturnRows(_rows) _sqlite := testSqlite(t) diff --git a/database/worker/delete.go b/database/worker/delete.go index ba846db91..8df8ff5e0 100644 --- a/database/worker/delete.go +++ b/database/worker/delete.go @@ -5,22 +5,23 @@ package worker import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // DeleteWorker deletes an existing worker from the database. -func (e *engine) DeleteWorker(ctx context.Context, w *library.Worker) error { +func (e *engine) DeleteWorker(ctx context.Context, w *api.Worker) error { e.logger.WithFields(logrus.Fields{ "worker": w.GetHostname(), - }).Tracef("deleting worker %s from the database", w.GetHostname()) + }).Tracef("deleting worker %s", w.GetHostname()) // cast the library type to database type // // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary - worker := database.WorkerFromLibrary(w) + worker := types.WorkerFromAPI(w) // send query to the database return e.client. diff --git a/database/worker/get.go b/database/worker/get.go index 12201d4d5..cde920413 100644 --- a/database/worker/get.go +++ b/database/worker/get.go @@ -5,17 +5,17 @@ package worker import ( "context" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // GetWorker gets a worker by ID from the database. -func (e *engine) GetWorker(ctx context.Context, id int64) (*library.Worker, error) { - e.logger.Tracef("getting worker %d from the database", id) +func (e *engine) GetWorker(ctx context.Context, id int64) (*api.Worker, error) { + e.logger.Tracef("getting worker %d", id) // variable to store query results - w := new(database.Worker) + w := new(types.Worker) // send query to the database and store result in variable err := e.client. @@ -30,5 +30,5 @@ func (e *engine) GetWorker(ctx context.Context, id int64) (*library.Worker, erro // return the worker // // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary - return w.ToLibrary(), nil + return w.ToAPI(convertToBuilds(w.RunningBuildIDs)), nil } diff --git a/database/worker/get_hostname.go b/database/worker/get_hostname.go index 3370620a0..1646e262e 100644 --- a/database/worker/get_hostname.go +++ b/database/worker/get_hostname.go @@ -5,20 +5,21 @@ package worker import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // GetWorkerForHostname gets a worker by hostname from the database. -func (e *engine) GetWorkerForHostname(ctx context.Context, hostname string) (*library.Worker, error) { +func (e *engine) GetWorkerForHostname(ctx context.Context, hostname string) (*api.Worker, error) { e.logger.WithFields(logrus.Fields{ "worker": hostname, - }).Tracef("getting worker %s from the database", hostname) + }).Tracef("getting worker %s", hostname) // variable to store query results - w := new(database.Worker) + w := new(types.Worker) // send query to the database and store result in variable err := e.client. @@ -33,5 +34,5 @@ func (e *engine) GetWorkerForHostname(ctx context.Context, hostname string) (*li // return the worker // // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary - return w.ToLibrary(), nil + return w.ToAPI(convertToBuilds(w.RunningBuildIDs)), nil } diff --git a/database/worker/get_hostname_test.go b/database/worker/get_hostname_test.go index cf264ab0c..a65854eba 100644 --- a/database/worker/get_hostname_test.go +++ b/database/worker/get_hostname_test.go @@ -8,7 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" ) func TestWorker_Engine_GetWorkerForName(t *testing.T) { @@ -18,6 +19,7 @@ func TestWorker_Engine_GetWorkerForName(t *testing.T) { _worker.SetHostname("worker_0") _worker.SetAddress("localhost") _worker.SetActive(true) + _worker.SetRunningBuilds(nil) // sqlmock cannot parse string array values _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -43,7 +45,7 @@ func TestWorker_Engine_GetWorkerForName(t *testing.T) { failure bool name string database *engine - want *library.Worker + want *api.Worker }{ { failure: false, diff --git a/database/worker/get_test.go b/database/worker/get_test.go index bfd9bf2a3..33d9c37e7 100644 --- a/database/worker/get_test.go +++ b/database/worker/get_test.go @@ -8,7 +8,8 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" ) func TestWorker_Engine_GetWorker(t *testing.T) { @@ -18,6 +19,7 @@ func TestWorker_Engine_GetWorker(t *testing.T) { _worker.SetHostname("worker_0") _worker.SetAddress("localhost") _worker.SetActive(true) + _worker.SetRunningBuilds(nil) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -43,7 +45,7 @@ func TestWorker_Engine_GetWorker(t *testing.T) { failure bool name string database *engine - want *library.Worker + want *api.Worker }{ { failure: false, diff --git a/database/worker/index.go b/database/worker/index.go index 3220d4a63..65fe7326f 100644 --- a/database/worker/index.go +++ b/database/worker/index.go @@ -17,7 +17,7 @@ ON workers (hostname, address); // CreateWorkerIndexes creates the indexes for the workers table in the database. func (e *engine) CreateWorkerIndexes(ctx context.Context) error { - e.logger.Tracef("creating indexes for workers table in the database") + e.logger.Tracef("creating indexes for workers table") // create the hostname and address columns index for the workers table return e.client.Exec(CreateHostnameAddressIndex).Error diff --git a/database/worker/interface.go b/database/worker/interface.go index 589e8971b..8f8d024a1 100644 --- a/database/worker/interface.go +++ b/database/worker/interface.go @@ -5,7 +5,7 @@ package worker import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) // WorkerInterface represents the Vela interface for worker @@ -29,15 +29,15 @@ type WorkerInterface interface { // CountWorkers defines a function that gets the count of all workers. CountWorkers(context.Context) (int64, error) // CreateWorker defines a function that creates a new worker. - CreateWorker(context.Context, *library.Worker) (*library.Worker, error) + CreateWorker(context.Context, *api.Worker) (*api.Worker, error) // DeleteWorker defines a function that deletes an existing worker. - DeleteWorker(context.Context, *library.Worker) error + DeleteWorker(context.Context, *api.Worker) error // GetWorker defines a function that gets a worker by ID. - GetWorker(context.Context, int64) (*library.Worker, error) + GetWorker(context.Context, int64) (*api.Worker, error) // GetWorkerForHostname defines a function that gets a worker by hostname. - GetWorkerForHostname(context.Context, string) (*library.Worker, error) + GetWorkerForHostname(context.Context, string) (*api.Worker, error) // ListWorkers defines a function that gets a list of all workers. - ListWorkers(context.Context, string, int64, int64) ([]*library.Worker, error) + ListWorkers(context.Context, string, int64, int64) ([]*api.Worker, error) // UpdateWorker defines a function that updates an existing worker. - UpdateWorker(context.Context, *library.Worker) (*library.Worker, error) + UpdateWorker(context.Context, *api.Worker) (*api.Worker, error) } diff --git a/database/worker/list.go b/database/worker/list.go index bb8169d61..e2af759dd 100644 --- a/database/worker/list.go +++ b/database/worker/list.go @@ -7,18 +7,18 @@ import ( "fmt" "strconv" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" ) // ListWorkers gets a list of all workers from the database. -func (e *engine) ListWorkers(ctx context.Context, active string, before, after int64) ([]*library.Worker, error) { - e.logger.Trace("listing all workers from the database") +func (e *engine) ListWorkers(ctx context.Context, active string, before, after int64) ([]*api.Worker, error) { + e.logger.Trace("listing all workers") // variables to store query results and return value - w := new([]database.Worker) - workers := []*library.Worker{} + results := new([]types.Worker) + workers := []*api.Worker{} // build query with checked in constraints query := e.client.Table(constants.TableWorker). @@ -37,20 +37,20 @@ func (e *engine) ListWorkers(ctx context.Context, active string, before, after i } // send query to the database and store result in variable - err := query.Find(&w).Error + err := query.Find(&results).Error if err != nil { return nil, err } // iterate through all query results - for _, worker := range *w { + for _, worker := range *results { // https://golang.org/doc/faq#closures_and_goroutines tmp := worker // convert query result to library type // // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary - workers = append(workers, tmp.ToLibrary()) + workers = append(workers, tmp.ToAPI(convertToBuilds(tmp.RunningBuildIDs))) } return workers, nil diff --git a/database/worker/list_test.go b/database/worker/list_test.go index 4a5988228..948230b3b 100644 --- a/database/worker/list_test.go +++ b/database/worker/list_test.go @@ -8,8 +8,9 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/google/go-cmp/cmp" + + api "github.com/go-vela/server/api/types" ) func TestWorker_Engine_ListWorkers(t *testing.T) { @@ -21,6 +22,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { _workerOne.SetHostname("worker_0") _workerOne.SetAddress("localhost") _workerOne.SetActive(true) + _workerOne.SetRunningBuilds(nil) _workerOne.SetLastCheckedIn(newer) _workerTwo := testWorker() @@ -29,6 +31,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { _workerTwo.SetAddress("localhost") _workerTwo.SetActive(true) _workerTwo.SetLastCheckedIn(older) + _workerTwo.SetRunningBuilds(nil) _workerThree := testWorker() _workerThree.SetID(3) @@ -36,6 +39,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { _workerThree.SetAddress("localhost") _workerThree.SetActive(false) _workerThree.SetLastCheckedIn(newer) + _workerThree.SetRunningBuilds(nil) _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -75,7 +79,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { active string name string database *engine - want []*library.Worker + want []*api.Worker }{ { failure: false, @@ -83,7 +87,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { active: "all", name: "sqlite3 before filter", database: _sqlite, - want: []*library.Worker{_workerTwo}, + want: []*api.Worker{_workerTwo}, }, { failure: false, @@ -91,7 +95,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { active: "all", name: "postgres catch all", database: _postgres, - want: []*library.Worker{_workerOne, _workerTwo, _workerThree}, + want: []*api.Worker{_workerOne, _workerTwo, _workerThree}, }, { failure: false, @@ -99,7 +103,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { active: "all", name: "sqlite3 catch all", database: _sqlite, - want: []*library.Worker{_workerOne, _workerTwo, _workerThree}, + want: []*api.Worker{_workerOne, _workerTwo, _workerThree}, }, { failure: false, @@ -107,7 +111,7 @@ func TestWorker_Engine_ListWorkers(t *testing.T) { active: "true", name: "sqlite3 active filter", database: _sqlite, - want: []*library.Worker{_workerOne, _workerTwo}, + want: []*api.Worker{_workerOne, _workerTwo}, }, } diff --git a/database/worker/opts.go b/database/worker/opts.go index 71048282b..b2f3a570a 100644 --- a/database/worker/opts.go +++ b/database/worker/opts.go @@ -6,7 +6,6 @@ import ( "context" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/worker/opts_test.go b/database/worker/opts_test.go index f627d25ed..2484f6455 100644 --- a/database/worker/opts_test.go +++ b/database/worker/opts_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) diff --git a/database/worker/table.go b/database/worker/table.go index 9fcfdd113..403dd5ad3 100644 --- a/database/worker/table.go +++ b/database/worker/table.go @@ -53,7 +53,7 @@ workers ( // CreateWorkerTable creates the workers table in the database. func (e *engine) CreateWorkerTable(ctx context.Context, driver string) error { - e.logger.Tracef("creating workers table in the database") + e.logger.Tracef("creating workers table") // handle the driver provided to create the table switch driver { diff --git a/database/worker/update.go b/database/worker/update.go index 3cae0d164..872d9c82d 100644 --- a/database/worker/update.go +++ b/database/worker/update.go @@ -5,22 +5,23 @@ package worker import ( "context" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database/types" + "github.com/go-vela/types/constants" ) // UpdateWorker updates an existing worker in the database. -func (e *engine) UpdateWorker(ctx context.Context, w *library.Worker) (*library.Worker, error) { +func (e *engine) UpdateWorker(ctx context.Context, w *api.Worker) (*api.Worker, error) { e.logger.WithFields(logrus.Fields{ "worker": w.GetHostname(), - }).Tracef("updating worker %s in the database", w.GetHostname()) + }).Tracef("updating worker %s", w.GetHostname()) // cast the library type to database type // // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary - worker := database.WorkerFromLibrary(w) + worker := types.WorkerFromAPI(w) // validate the necessary fields are populated // @@ -33,5 +34,5 @@ func (e *engine) UpdateWorker(ctx context.Context, w *library.Worker) (*library. // send query to the database result := e.client.Table(constants.TableWorker).Save(worker) - return worker.ToLibrary(), result.Error + return worker.ToAPI(w.GetRunningBuilds()), result.Error } diff --git a/database/worker/update_test.go b/database/worker/update_test.go index d077bc567..5a483660b 100644 --- a/database/worker/update_test.go +++ b/database/worker/update_test.go @@ -25,7 +25,7 @@ func TestWorker_Engine_UpdateWorker(t *testing.T) { _mock.ExpectExec(`UPDATE "workers" SET "hostname"=$1,"address"=$2,"routes"=$3,"active"=$4,"status"=$5,"last_status_update_at"=$6,"running_build_ids"=$7,"last_build_started_at"=$8,"last_build_finished_at"=$9,"last_checked_in"=$10,"build_limit"=$11 WHERE "id" = $12`). - WithArgs("worker_0", "localhost", nil, true, nil, nil, nil, nil, nil, nil, nil, 1). + WithArgs("worker_0", "localhost", nil, true, nil, nil, `{"1"}`, nil, nil, nil, nil, 1). WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) diff --git a/database/worker/worker.go b/database/worker/worker.go index 09b69176a..655a7dd2f 100644 --- a/database/worker/worker.go +++ b/database/worker/worker.go @@ -5,11 +5,13 @@ package worker import ( "context" "fmt" + "strconv" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" - "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) type ( @@ -60,7 +62,7 @@ func New(opts ...EngineOpt) (*engine, error) { // check if we should skip creating worker database objects if e.config.SkipCreation { - e.logger.Warning("skipping creation of workers table and indexes in the database") + e.logger.Warning("skipping creation of workers table and indexes") return e, nil } @@ -79,3 +81,23 @@ func New(opts ...EngineOpt) (*engine, error) { return e, nil } + +// convertToBuilds is a helper function that generates build objects with ID fields given a list of IDs. +func convertToBuilds(ids []string) []*api.Build { + // create stripped build objects holding the IDs + var rBs []*api.Build + + for _, b := range ids { + id, err := strconv.ParseInt(b, 10, 64) + if err != nil { + return nil + } + + build := new(api.Build) + build.SetID(id) + + rBs = append(rBs, build) + } + + return rBs +} diff --git a/database/worker/worker_test.go b/database/worker/worker_test.go index a219d1ffc..bf54eb4da 100644 --- a/database/worker/worker_test.go +++ b/database/worker/worker_test.go @@ -7,12 +7,12 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" - "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" ) func TestWorker_New(t *testing.T) { @@ -106,6 +106,48 @@ func TestWorker_New(t *testing.T) { } } +func TestWorker_convertToBuilds(t *testing.T) { + _buildOne := new(api.Build) + _buildOne.SetID(1) + + _buildTwo := new(api.Build) + _buildTwo.SetID(2) + + // setup tests + tests := []struct { + name string + ids []string + want []*api.Build + }{ + { + name: "one id", + ids: []string{"1"}, + want: []*api.Build{_buildOne}, + }, + { + name: "multiple ids", + ids: []string{"1", "2"}, + want: []*api.Build{_buildOne, _buildTwo}, + }, + { + name: "not int64", + ids: []string{"1", "foo"}, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := convertToBuilds(test.ids) + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("convertToBuilds for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + // testPostgres is a helper function to create a Postgres engine for testing. func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { // create the new mock sql database @@ -166,8 +208,11 @@ func testSqlite(t *testing.T) *engine { // testWorker is a test helper function to create a library // Worker type with all fields set to their zero values. -func testWorker() *library.Worker { - return &library.Worker{ +func testWorker() *api.Worker { + b := new(api.Build) + b.SetID(1) + + return &api.Worker{ ID: new(int64), Hostname: new(string), Address: new(string), @@ -175,7 +220,7 @@ func testWorker() *library.Worker { Active: new(bool), Status: new(string), LastStatusUpdateAt: new(int64), - RunningBuildIDs: new([]string), + RunningBuilds: &[]*api.Build{b}, LastBuildStartedAt: new(int64), LastBuildFinishedAt: new(int64), LastCheckedIn: new(int64), diff --git a/go.mod b/go.mod index db34dcd0b..01e8223bf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/go-vela/server -go 1.21 +go 1.22.0 + +toolchain go1.22.4 replace github.com/go-vela/types => ../types @@ -9,40 +11,44 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/sprig/v3 v3.2.3 - github.com/adhocore/gronx v1.6.7 - github.com/alicebob/miniredis/v2 v2.31.1 - github.com/aws/aws-sdk-go v1.50.24 - github.com/bradleyfalzon/ghinstallation/v2 v2.9.0 + github.com/adhocore/gronx v1.8.1 + github.com/alicebob/miniredis/v2 v2.33.0 + github.com/aws/aws-sdk-go v1.54.1 + github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3 + github.com/distribution/reference v0.6.0 github.com/drone/envsubst v1.0.3 - github.com/gin-gonic/gin v1.9.1 + github.com/ghodss/yaml v1.0.0 + github.com/gin-gonic/gin v1.10.0 github.com/go-playground/assert/v2 v2.2.0 - github.com/go-vela/types v0.23.1 - github.com/golang-jwt/jwt/v5 v5.2.0 + github.com/go-vela/types v0.24.0-rc2 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-cmp v0.6.0 - github.com/google/go-github/v59 v59.0.0 + github.com/google/go-github/v62 v62.0.0 github.com/google/uuid v1.6.0 github.com/goware/urlx v0.3.2 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.5 - github.com/hashicorp/vault/api v1.12.0 + github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/vault/api v1.14.0 github.com/joho/godotenv v1.5.1 + github.com/lestrrat-go/jwx/v2 v2.0.21 + github.com/lib/pq v1.10.9 + github.com/microcosm-cc/bluemonday v1.0.26 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.18.0 - github.com/redis/go-redis/v9 v9.5.1 + github.com/prometheus/client_golang v1.19.1 + github.com/redis/go-redis/v9 v9.5.3 github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 - github.com/urfave/cli/v2 v2.27.1 - go.starlark.net v0.0.0-20240123142251-f86470692795 - golang.org/x/crypto v0.19.0 - golang.org/x/oauth2 v0.17.0 - golang.org/x/sync v0.6.0 - gopkg.in/square/go-jose.v2 v2.6.0 - gorm.io/driver/postgres v1.5.6 - gorm.io/driver/sqlite v1.5.5 - gorm.io/gorm v1.25.7 - k8s.io/apimachinery v0.29.2 + github.com/urfave/cli/v2 v2.27.2 + go.starlark.net v0.0.0-20240314022150-ee8ed142361c + golang.org/x/crypto v0.24.0 + golang.org/x/oauth2 v0.21.0 + golang.org/x/sync v0.7.0 + gorm.io/driver/postgres v1.5.9 + gorm.io/driver/sqlite v1.5.6 + gorm.io/gorm v1.25.10 + k8s.io/apimachinery v0.30.2 ) require ( @@ -52,27 +58,26 @@ require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.9.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/fatih/color v1.10.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/ghodss/yaml v1.0.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect - github.com/google/go-github/v57 v57.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/css v1.0.0 // indirect @@ -86,47 +91,50 @@ require ( github.com/imdario/mergo v0.3.11 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.5 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/yuin/gopher-lua v1.1.0 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect ) diff --git a/go.sum b/go.sum index 1980be9bd..e0c966146 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,6 @@ github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWO github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc= github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -16,76 +15,82 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/adhocore/gronx v1.6.7 h1:yE/AKQP/yhjMRqV943XiPqBdmUwIF8VHJwm6KZhnk48= -github.com/adhocore/gronx v1.6.7/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.8.1 h1:F2mLTG5sB11z7vplwD4iydz3YCEjstSfYmCrdSm3t6A= +github.com/adhocore/gronx v1.8.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE= -github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6TTnfX8Y= -github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw= -github.com/aws/aws-sdk-go v1.50.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.54.1 h1:+ULL7oLC+v3T00fOMIohUarPI3SR3oyDd6FBEvgdhvs= +github.com/aws/aws-sdk-go v1.54.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bradleyfalzon/ghinstallation/v2 v2.9.0 h1:HmxIYqnxubRYcYGRc5v3wUekmo5Wv2uX3gukmWJ0AFk= -github.com/bradleyfalzon/ghinstallation/v2 v2.9.0/go.mod h1:wmkTDJf8CmVypxE8ijIStFnKoTa6solK5QfdmJrP9KI= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3 h1:q+sMKdA6L8LyGVudTkpGoC73h6ak2iWSPFiFo/pFOU8= github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3/go.mod h1:5hCug3EZaHXU3FdCA3gJm0YTNi+V+ooA2qNTiVpky4A= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -94,26 +99,17 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs= -github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw= -github.com/google/go-github/v59 v59.0.0 h1:7h6bgpF5as0YQLLkEiVqpgtJqjimMYhBkD4jT5aN3VA= -github.com/google/go-github/v59 v59.0.0/go.mod h1:rJU4R0rQHFVFDOkqGWxfLNo6vEk4dv40oDjhV/gH6wM= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -131,14 +127,13 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= @@ -150,8 +145,8 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= -github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU= +github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= @@ -160,8 +155,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -178,29 +175,39 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= +github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -220,23 +227,25 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= -github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= +github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -244,6 +253,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -255,6 +266,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -263,87 +275,81 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= -github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= +github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= -github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= -github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -go.starlark.net v0.0.0-20240123142251-f86470692795 h1:LmbG8Pq7KDGkglKVn8VpZOZj6vb9b8nKEGcg9l03epM= -go.starlark.net v0.0.0-20240123142251-f86470692795/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.starlark.net v0.0.0-20240314022150-ee8ed142361c h1:roAjH18hZcwI4hHStHbkXjF5b7UUyZ/0SG3hXNN1SjA= +go.starlark.net v0.0.0-20240314022150-ee8ed142361c/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -355,17 +361,11 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -374,18 +374,19 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU= -gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= -gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= -gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= -gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= -gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= -k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/image/doc.go b/internal/image/doc.go new file mode 100644 index 000000000..66d473d15 --- /dev/null +++ b/internal/image/doc.go @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package image provides the ability for Vela to manage +// and manipulate images. +// +// Usage: +// +// import "github.com/go-vela/server/internal/image" +package image diff --git a/internal/image/image.go b/internal/image/image.go new file mode 100644 index 000000000..daa5b37bc --- /dev/null +++ b/internal/image/image.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package image + +import ( + "github.com/distribution/reference" +) + +// ParseWithError digests the provided image into a +// fully qualified canonical reference. If an error +// occurs, it will return the last digested form of +// the image. +func ParseWithError(_image string) (string, error) { + // parse the image provided into a + // named, fully qualified reference + // + // https://pkg.go.dev/github.com/distribution/reference#ParseAnyReference + _reference, err := reference.ParseAnyReference(_image) + if err != nil { + return _image, err + } + + // ensure we have the canonical form of the named reference + // + // https://pkg.go.dev/github.com/distribution/reference#ParseNamed + _canonical, err := reference.ParseNamed(_reference.String()) + if err != nil { + return _reference.String(), err + } + + // ensure the canonical reference has a tag + // + // https://pkg.go.dev/github.com/distribution/reference#TagNameOnly + return reference.TagNameOnly(_canonical).String(), nil +} diff --git a/internal/image/image_test.go b/internal/image/image_test.go new file mode 100644 index 000000000..365fad2ff --- /dev/null +++ b/internal/image/image_test.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +package image + +import ( + "strings" + "testing" +) + +func TestImage_ParseWithError(t *testing.T) { + // setup tests + tests := []struct { + name string + failure bool + image string + want string + }{ + { + name: "image only", + failure: false, + image: "golang", + want: "docker.io/library/golang:latest", + }, + { + name: "image and tag", + failure: false, + image: "golang:latest", + want: "docker.io/library/golang:latest", + }, + { + name: "image and tag", + failure: false, + image: "golang:1.14", + want: "docker.io/library/golang:1.14", + }, + { + name: "fails with bad image", + failure: true, + image: "!@#$%^&*()", + want: "!@#$%^&*()", + }, + { + name: "fails with image sha", + failure: true, + image: "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + want: "sha256:1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ParseWithError(test.image) + + if test.failure { + if err == nil { + t.Errorf("ParseWithError should have returned err") + } + + if !strings.EqualFold(got, test.want) { + t.Errorf("ParseWithError is %s want %s", got, test.want) + } + + return // continue to next test + } + + if err != nil { + t.Errorf("ParseWithError returned err: %v", err) + } + + if !strings.EqualFold(got, test.want) { + t.Errorf("ParseWithError is %s want %s", got, test.want) + } + }) + } +} diff --git a/internal/metadata.go b/internal/metadata.go new file mode 100644 index 000000000..00a94bde0 --- /dev/null +++ b/internal/metadata.go @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import "time" + +type ( + // Database is the extra set of database data passed to the compiler. + Database struct { + Driver string `json:"driver"` + Host string `json:"host"` + } + + // Queue is the extra set of queue data passed to the compiler. + Queue struct { + Channel string `json:"channel"` + Driver string `json:"driver"` + Host string `json:"host"` + } + + // Source is the extra set of source data passed to the compiler. + Source struct { + Driver string `json:"driver"` + Host string `json:"host"` + } + + // Vela is the extra set of Vela data passed to the compiler. + Vela struct { + Address string `json:"address"` + WebAddress string `json:"web_address"` + WebOauthCallbackPath string `json:"web_oauth_callback_path"` + AccessTokenDuration time.Duration `json:"access_token_duration"` + RefreshTokenDuration time.Duration `json:"refresh_token_duration"` + } + + // Metadata is the extra set of data passed to the compiler for + // converting a yaml configuration to an executable pipeline. + Metadata struct { + Database *Database `json:"database"` + Queue *Queue `json:"queue"` + Source *Source `json:"source"` + Vela *Vela `json:"vela"` + } +) diff --git a/internal/token/compose.go b/internal/token/compose.go index 6e48647e2..8fa0a1c8b 100644 --- a/internal/token/compose.go +++ b/internal/token/compose.go @@ -7,9 +7,10 @@ import ( "net/url" "github.com/gin-gonic/gin" - "github.com/go-vela/types" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" ) // Compose generates a refresh and access token pair unique @@ -18,10 +19,10 @@ import ( // guarantee the signature is unique per token. The refresh // token is returned to store with the user // in the database. -func (tm *Manager) Compose(c *gin.Context, u *library.User) (string, string, error) { +func (tm *Manager) Compose(c *gin.Context, u *api.User) (string, string, error) { // grab the metadata from the context to pull in provided // cookie duration information - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) // mint token options for refresh token rmto := MintTokenOpts{ diff --git a/internal/token/compose_test.go b/internal/token/compose_test.go index a73bf912b..c6efd58ce 100644 --- a/internal/token/compose_test.go +++ b/internal/token/compose_test.go @@ -9,24 +9,22 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/types" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" + "github.com/golang-jwt/jwt/v5" - jwt "github.com/golang-jwt/jwt/v5" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" + "github.com/go-vela/types/constants" ) func TestToken_Compose(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -48,13 +46,13 @@ func TestToken_Compose(t *testing.T) { tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - want, err := tkn.SignedString([]byte(tm.PrivateKey)) + want, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } - m := &types.Metadata{ - Vela: &types.Vela{ + m := &internal.Metadata{ + Vela: &internal.Vela{ AccessTokenDuration: d, }, } diff --git a/internal/token/generate_rsa.go b/internal/token/generate_rsa.go new file mode 100644 index 000000000..fae46aef2 --- /dev/null +++ b/internal/token/generate_rsa.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package token + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "fmt" + + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwk" + + "github.com/go-vela/server/database" +) + +// GenerateRSA creates an RSA key pair and sets it in the token manager and saves the JWK in the database. +func (tm *Manager) GenerateRSA(ctx context.Context, db database.Interface) error { + // generate key pair + privateRSAKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + + pubJwk, err := jwk.FromRaw(privateRSAKey.PublicKey) + if err != nil { + return err + } + + switch j := pubJwk.(type) { + case jwk.RSAPublicKey: + // assign KID to key pair + kid, err := uuid.NewV7() + if err != nil { + return err + } + + err = pubJwk.Set(jwk.KeyIDKey, kid.String()) + if err != nil { + return err + } + + // create the JWK in the database + err = db.CreateJWK(context.TODO(), j) + if err != nil { + return err + } + + // create the RSA key set for token manager + keySet := RSAKeySet{ + PrivateKey: privateRSAKey, + KID: kid.String(), + } + + tm.RSAKeySet = keySet + + return nil + default: + return fmt.Errorf("invalid JWK type parsed from generation") + } +} diff --git a/internal/token/manager.go b/internal/token/manager.go index c02484b87..8094bd131 100644 --- a/internal/token/manager.go +++ b/internal/token/manager.go @@ -3,17 +3,21 @@ package token import ( + "crypto/rsa" "time" - - "github.com/golang-jwt/jwt/v5" ) +type RSAKeySet struct { + PrivateKey *rsa.PrivateKey + KID string +} + type Manager struct { - // PrivateKey key used to sign tokens - PrivateKey string + // PrivateKeyHMAC is the private key used to sign and validate closed-system tokens + PrivateKeyHMAC string - // SignMethod method to sign tokens - SignMethod jwt.SigningMethod + // RSAKeySet is the private key used to sign and validate open-system tokens (OIDC) + RSAKeySet RSAKeySet // UserAccessTokenDuration specifies the token duration to use for users UserAccessTokenDuration time.Duration @@ -29,4 +33,10 @@ type Manager struct { // WorkerRegisterTokenDuration specifies the token duration for worker register WorkerRegisterTokenDuration time.Duration + + // IDTokenDuration specifies the token duration for ID tokens + IDTokenDuration time.Duration + + // Issuer specifies the issuer of the token + Issuer string } diff --git a/internal/token/mint.go b/internal/token/mint.go index 22c9599b9..1cbb65663 100644 --- a/internal/token/mint.go +++ b/internal/token/mint.go @@ -3,35 +3,48 @@ package token import ( + "context" "errors" "fmt" "time" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/golang-jwt/jwt/v5" + "gorm.io/gorm" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" + "github.com/go-vela/server/database" ) // Claims struct is an extension of the JWT standard claims. It // includes information about the user. type Claims struct { - BuildID int64 `json:"build_id"` - IsActive bool `json:"is_active"` - IsAdmin bool `json:"is_admin"` - Repo string `json:"repo"` - TokenType string `json:"token_type"` + BuildID int64 `json:"build_id,omitempty"` + BuildNumber int `json:"build_number,omitempty"` + Actor string `json:"actor,omitempty"` + IsActive bool `json:"is_active,omitempty"` + IsAdmin bool `json:"is_admin,omitempty"` + Repo string `json:"repo,omitempty"` + TokenType string `json:"token_type,omitempty"` + Image string `json:"image,omitempty"` + Request string `json:"request,omitempty"` + Commands bool `json:"commands,omitempty"` jwt.RegisteredClaims } // MintTokenOpts is a type to inform the token minter how to construct // the token. type MintTokenOpts struct { - BuildID int64 + Build *api.Build Hostname string Repo string TokenDuration time.Duration TokenType string - User *library.User + User *api.User + Audience []string + Image string + Request string + Commands bool } // MintToken mints a Vela JWT Token given a set of options. @@ -51,7 +64,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Subject = mto.User.GetName() case constants.WorkerBuildTokenType: - if mto.BuildID == 0 { + if mto.Build.GetID() == 0 { return "", errors.New("missing build id for build token") } @@ -63,7 +76,7 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { return "", errors.New("missing host name for build token") } - claims.BuildID = mto.BuildID + claims.BuildID = mto.Build.GetID() claims.Repo = mto.Repo claims.Subject = mto.Hostname @@ -74,6 +87,28 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.Subject = mto.Hostname + case constants.IDRequestTokenType: + if len(mto.Repo) == 0 { + return "", errors.New("missing repo for ID request token") + } + + if mto.Build == nil { + return "", errors.New("missing build for ID request token") + } + + if mto.Build.GetID() == 0 { + return "", errors.New("missing build id for ID request token") + } + + claims.Repo = mto.Repo + claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) + claims.BuildID = mto.Build.GetID() + claims.BuildNumber = mto.Build.GetNumber() + claims.Actor = mto.Build.GetSender() + claims.Image = mto.Image + claims.Request = mto.Request + claims.Commands = mto.Commands + default: return "", errors.New("invalid token type") } @@ -82,10 +117,81 @@ func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) { claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration)) claims.TokenType = mto.TokenType - tk := jwt.NewWithClaims(tm.SignMethod, claims) + tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + //sign token with configured private signing key + token, err := tk.SignedString([]byte(tm.PrivateKeyHMAC)) + if err != nil { + return "", fmt.Errorf("unable to sign token: %w", err) + } + + return token, nil +} + +// MintIDToken mints a Vela JWT ID Token for a build. +func (tm *Manager) MintIDToken(ctx context.Context, mto *MintTokenOpts, db database.Interface) (string, error) { + // initialize claims struct + var claims = new(api.OpenIDClaims) + + // validate provided claims + if len(mto.Repo) == 0 { + return "", errors.New("missing repo for ID token") + } + + if mto.Build == nil { + return "", errors.New("missing build for ID token") + } + + if mto.Build.GetNumber() == 0 { + return "", errors.New("missing build id for ID token") + } + + if len(mto.Build.GetSender()) == 0 { + return "", errors.New("missing build sender for ID token") + } + + // set claims based on input + claims.Actor = mto.Build.GetSender() + claims.ActorSCMID = mto.Build.GetSenderSCMID() + claims.BuildNumber = mto.Build.GetNumber() + claims.BuildID = mto.Build.GetID() + claims.Repo = mto.Repo + claims.Event = fmt.Sprintf("%s:%s", mto.Build.GetEvent(), mto.Build.GetEventAction()) + claims.SHA = mto.Build.GetCommit() + claims.Ref = mto.Build.GetRef() + claims.Subject = fmt.Sprintf("repo:%s:ref:%s:event:%s", mto.Repo, mto.Build.GetRef(), mto.Build.GetEvent()) + claims.Audience = mto.Audience + claims.TokenType = mto.TokenType + claims.Image = mto.Image + claims.Request = mto.Request + claims.Commands = mto.Commands + + // set standard claims + claims.IssuedAt = jwt.NewNumericDate(time.Now()) + claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration)) + claims.Issuer = tm.Issuer + + tk := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + + // verify key is active in the database before signing + _, err := db.GetActiveJWK(ctx, tm.RSAKeySet.KID) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return "", fmt.Errorf("unable to get active public key: %w", err) + } + + // generate a new RSA key pair if previous key is inactive (rotated) + err = tm.GenerateRSA(ctx, db) + if err != nil { + return "", fmt.Errorf("unable to generate RSA key pair: %w", err) + } + } + + // set KID header + tk.Header["kid"] = tm.RSAKeySet.KID //sign token with configured private signing key - token, err := tk.SignedString([]byte(tm.PrivateKey)) + token, err := tk.SignedString(tm.RSAKeySet.PrivateKey) if err != nil { return "", fmt.Errorf("unable to sign token: %w", err) } diff --git a/internal/token/parse.go b/internal/token/parse.go index c42d90686..d5db3041e 100644 --- a/internal/token/parse.go +++ b/internal/token/parse.go @@ -42,7 +42,7 @@ func (tm *Manager) ParseToken(token string) (*Claims, error) { return nil, errors.New("token has no expiration") } - return []byte(tm.PrivateKey), err + return []byte(tm.PrivateKeyHMAC), err }) if err != nil { diff --git a/internal/token/parse_test.go b/internal/token/parse_test.go index c1e5d3ac0..600efe35e 100644 --- a/internal/token/parse_test.go +++ b/internal/token/parse_test.go @@ -8,23 +8,26 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - jwt "github.com/golang-jwt/jwt/v5" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) func TestTokenManager_ParseToken(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") + + b := new(api.Build) + b.SetID(1) + b.SetNumber(1) + b.SetSender("octocat") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -75,7 +78,7 @@ func TestTokenManager_ParseToken(t *testing.T) { { TokenType: constants.WorkerBuildTokenType, Mto: &MintTokenOpts{ - BuildID: 1, + Build: b, Repo: "foo/bar", Hostname: "worker", TokenType: constants.WorkerBuildTokenType, @@ -117,15 +120,13 @@ func TestTokenManager_ParseToken(t *testing.T) { func TestTokenManager_ParseToken_Error_NoParse(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -143,15 +144,13 @@ func TestTokenManager_ParseToken_Error_NoParse(t *testing.T) { func TestTokenManager_ParseToken_Expired(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -176,15 +175,13 @@ func TestTokenManager_ParseToken_Expired(t *testing.T) { func TestTokenManager_ParseToken_NoSubject(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -199,7 +196,7 @@ func TestTokenManager_ParseToken_NoSubject(t *testing.T) { } tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - token, err := tkn.SignedString([]byte(tm.PrivateKey)) + token, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } @@ -217,15 +214,13 @@ func TestTokenManager_ParseToken_NoSubject(t *testing.T) { func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -241,7 +236,7 @@ func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) { } tkn := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) - token, err := tkn.SignedString([]byte(tm.PrivateKey)) + token, err := tkn.SignedString([]byte(tm.PrivateKeyHMAC)) if err != nil { t.Errorf("Unable to create test token: %v", err) } @@ -259,15 +254,13 @@ func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) { func TestToken_Parse_AccessToken_NoExpiration(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -280,7 +273,7 @@ func TestToken_Parse_AccessToken_NoExpiration(t *testing.T) { } tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - token, err := tkn.SignedString([]byte(u.GetHash())) + token, err := tkn.SignedString([]byte("123abc")) if err != nil { t.Errorf("Unable to create test token: %v", err) } diff --git a/internal/token/refresh.go b/internal/token/refresh.go index 678c5ccb0..257391230 100644 --- a/internal/token/refresh.go +++ b/internal/token/refresh.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" "github.com/go-vela/types/constants" ) diff --git a/internal/token/refresh_test.go b/internal/token/refresh_test.go index 6618b88c9..49c809eb6 100644 --- a/internal/token/refresh_test.go +++ b/internal/token/refresh_test.go @@ -10,23 +10,21 @@ import ( "time" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/golang-jwt/jwt/v5" ) func TestTokenManager_Refresh(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -79,15 +77,13 @@ func TestTokenManager_Refresh(t *testing.T) { func TestTokenManager_Refresh_Expired(t *testing.T) { // setup types - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") tm := &Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } diff --git a/internal/webhook.go b/internal/webhook.go new file mode 100644 index 000000000..9c193fe58 --- /dev/null +++ b/internal/webhook.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "strings" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" +) + +var ( + skipDirectiveMsg = "skip ci directive found in commit title/message" +) + +// PullRequest defines the data pulled from PRs while +// processing a webhook. +type PullRequest struct { + Comment string + Number int + IsFromFork bool + Labels []string +} + +// Webhook defines a struct that is used to return +// the required data when processing webhook event +// a for a source provider event. +type Webhook struct { + Hook *library.Hook + Repo *api.Repo + Build *api.Build + PullRequest PullRequest + Deployment *library.Deployment +} + +// ShouldSkip uses the build information +// associated with the given hook to determine +// whether the hook should be skipped. +func (w *Webhook) ShouldSkip() (bool, string) { + // push or tag event + if strings.EqualFold(constants.EventPush, w.Build.GetEvent()) || strings.EqualFold(constants.EventTag, w.Build.GetEvent()) { + // check for skip ci directive in message or title + if hasSkipDirective(w.Build.GetMessage()) || + hasSkipDirective(w.Build.GetTitle()) { + return true, skipDirectiveMsg + } + } + + return false, "" +} + +// hasSkipDirective is a small helper function +// to check a string for a number of patterns +// that signal to vela that the hook should +// be skipped from processing. +func hasSkipDirective(s string) bool { + sl := strings.ToLower(s) + + switch { + case strings.Contains(sl, "[skip ci]"), + strings.Contains(sl, "[ci skip]"), + strings.Contains(sl, "[skip vela]"), + strings.Contains(sl, "[vela skip]"), + strings.Contains(sl, "***no_ci***"): + return true + default: + return false + } +} diff --git a/internal/webhook_test.go b/internal/webhook_test.go new file mode 100644 index 000000000..4e421854c --- /dev/null +++ b/internal/webhook_test.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "testing" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" +) + +func TestWebhook_ShouldSkip(t *testing.T) { + // set up tests + tests := []struct { + hook *Webhook + wantBool bool + wantString string + }{ + { + &Webhook{Build: testPushBuild("testing [SKIP CI]", "", constants.EventPush)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing", "wip [ci skip]", constants.EventPush)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing [skip VELA]", "", constants.EventPush)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing", "wip [vela skip]", constants.EventPush)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing ***NO_CI*** ok", "nothing", constants.EventPush)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing ok", "nothing", constants.EventPush)}, + false, + "", + }, + { + &Webhook{Build: testPushBuild("testing [SKIP CI]", "", constants.EventTag)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing", "wip [ci skip]", constants.EventTag)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing [skip VELA]", "", constants.EventTag)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing", "wip [vela skip]", constants.EventTag)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing ***NO_CI*** ok", "nothing", constants.EventTag)}, + true, + skipDirectiveMsg, + }, + { + &Webhook{Build: testPushBuild("testing ok", "nothing", constants.EventTag)}, + false, + "", + }, + } + + // run tests + for _, test := range tests { + gotBool, gotString := test.hook.ShouldSkip() + + if gotString != test.wantString { + t.Errorf("returned an error, wanted %s, but got %s", test.wantString, gotString) + } + + if gotBool != test.wantBool { + t.Errorf("returned an error, wanted %v, but got %v", test.wantBool, gotBool) + } + } +} + +func testPushBuild(message, title, event string) *api.Build { + b := new(api.Build) + + b.SetEvent(event) + + if len(message) > 0 { + b.SetMessage(message) + } + + if len(title) > 0 { + b.SetTitle(title) + } + + b.SetCommit("deadbeef") + + return b +} diff --git a/mock/server/authentication.go b/mock/server/authentication.go index 5c99ee8c9..b1903eeb9 100644 --- a/mock/server/authentication.go +++ b/mock/server/authentication.go @@ -7,6 +7,9 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/lestrrat-go/jwx/v2/jwk" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" @@ -18,6 +21,43 @@ const ( TokenRefreshResp = `{ "token": "header.payload.signature" }` + + // OpenIDConfigResp represents a JSON return for an OpenID configuration. + OpenIDConfigResp = `{ + "issuer": "https://vela.com/_services/token", + "jwks_uri": "https://vela.com/_services/token/.well-known/jwks", + "supported_claims": [ + "sub", + "exp", + "iat", + "iss", + "aud", + "build_number", + "build_id", + "repo", + "token_type", + "actor", + "actor_scm_id", + "commands", + "image", + "request" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ] +}` + + // JWKSResp represents a JSON return for the JWKS. + JWKSResp = `{ + "keys": [ + { + "e": "AQAB", + "kid": "f7ec4ab7-c9a2-440e-bfb3-83b6599479ea", + "kty": "RSA", + "n": "weh9G_J6yZEugOFo6MQ057t_ExafteA_zVRS3CEPWiOgBLLRymh-KS6aCW-kHVuyBsnWNrCcc5cRJ6ISFnQMtkJtbpV_72qbw0zhFLiYomZDh5nb5dqCoiWIVNG8_a_My9jhXAIghs8MLbG-_Tj9jZb3K3n3Ies-Cg1E5SWO3YX8I1_X7ZlgqhEbktoy2RvR_crQA_fi1jRW5Q6PldIJmu4FIeXN_ny_sgg6ZQtTImFderUy1aUxUnpjilU-yv13eJejYQnJ7rExJVsDqq3B_CnYD2ioJC6b7aoEPvCpZ_1VgTTnQt6nedmr2Hih3GHgDNsM-BFr63aG3qZ5v9bVRw" + } + ] +` ) // getTokenRefresh returns mock JSON for a http GET. @@ -99,3 +139,23 @@ func validateOAuthToken(c *gin.Context) { c.JSON(http.StatusOK, "oauth token was created by vela") } + +// openIDConfig returns a mock response for a http GET. +func openIDConfig(c *gin.Context) { + data := []byte(OpenIDConfigResp) + + var body api.OpenIDConfig + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// getJWKS returns a mock response for a http GET. +func getJWKS(c *gin.Context) { + data := []byte(JWKSResp) + + var body jwk.RSAPublicKey + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} diff --git a/mock/server/build.go b/mock/server/build.go index 4b5d8c1d4..ebbe44b88 100644 --- a/mock/server/build.go +++ b/mock/server/build.go @@ -9,6 +9,8 @@ import ( "strings" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" "github.com/go-vela/types/library" ) @@ -17,7 +19,52 @@ const ( // BuildResp represents a JSON return for a single build. BuildResp = `{ "id": 1, - "repo_id": 1, + "repo": { + "id": 1, + "owner": { + "id": 1, + "name": "octocat", + "favorites": [], + "active": true, + "admin": false + }, + "org": "github", + "counter": 10, + "name": "octocat", + "full_name": "github/octocat", + "link": "https://github.com/github/octocat", + "clone": "https://github.com/github/octocat", + "branch": "main", + "build_limit": 10, + "timeout": 60, + "visibility": "public", + "private": false, + "trusted": true, + "pipeline_type": "yaml", + "topics": [], + "active": true, + "allow_events": { + "push": { + "branch": true, + "tag": true + }, + "pull_request": { + "opened": true, + "synchronize": true, + "reopened": true, + "edited": false + }, + "deployment": { + "created": true + }, + "comment": { + "created": false, + "edited": false + } + }, + "approve_build": "fork-always", + "previous_name": "" + }, "pipeline_id": 1, "number": 1, "parent": 1, @@ -38,6 +85,7 @@ const ( "message": "First commit...", "commit": "48afb5bdc41ad69bf22588491333f7cf71135163", "sender": "OctoKitty", + "sender_scm_id": "0", "author": "OctoKitty", "email": "octokitty@github.com", "link": "https://vela.example.company.com/github/octocat/1", @@ -155,6 +203,20 @@ const ( "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJidWlsZF9pZCI6MSwicmVwbyI6ImZvby9iYXIiLCJzdWIiOiJPY3RvY2F0IiwiaWF0IjoxNTE2MjM5MDIyfQ.hD7gXpaf9acnLBdOBa4GOEa5KZxdzd0ZvK6fGwaN4bc" }` + // IDTokenResp represents a JSON return for requesting an ID token. + // + //nolint:gosec // not actual credentials + IDTokenResp = `{ + "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY3ZWM0YWI3LWM5YTItNDQwZS1iZmIzLTgzYjY1OTk0NzllYSJ9.eyJidWlsZF9udW1iZXIiOjEsImFjdG9yIjoiT2N0b2NhdCIsInJlcG8iOiJPY3RvY2F0L3Rlc3QiLCJ0b2tlbl90eXBlIjoiSUQiLCJpbWFnZSI6ImFscGluZTpsYXRlc3QiLCJjb21tYW5kcyI6dHJ1ZSwicmVxdWVzdCI6IndyaXRlIiwiaXNzIjoiaHR0cHM6Ly92ZWxhLmNvbS9fc2VydmljZXMvdG9rZW4iLCJzdWIiOiJyZXBvOk9jdG9jYXQvdGVzdDpyZWY6cmVmcy9oZWFkcy9tYWluOmV2ZW50OnB1c2giLCJleHAiOjE3MTQ0OTU4MjMsImlhdCI6MTcxNDQ5NTUyMywiYXVkIjpbInRlc3QiLCJleGFtcGxlIl19.nCniV3r0TjJT0JGRtcubhhJnffo_uBUcYvFRlqSOHEOjRQo5cSwNfiePVicfWQYNbLYeJ_7HmxT2C27jCa2MjaYNXFXoVVAP9Cl-LIMqkpiIG85gHlsJaJILT_a8a1F6an0gK1st-iPB6h7casMXR479zdWnVX30WEE7Ed34T-ee6MOxYZ6-VDsVvxVLYe8dYMxvJkBWDI31djqzYfdWWWyYTPqfLCyRaojZeiCzKMt7m5GDRiOLXooYorv3ybizFoupr4md3Kk9MlArgMn7PYNGnmZuzJ01JrIUk6FTVty638yUmEIgiW7sdLzZG9HJ3E3hS6WJleXj--E95wmyyw" + }` + + // IDTokenRequestTokenResp represents a JSON return for requesting an IDTokenRequestToken. + // + //nolint:gosec // not actual credentials + IDTokenRequestTokenResp = `{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY3ZWM0YWI3LWM5YTItNDQwZS1iZmIzLTgzYjY1OTk0NzllYSJ9.eyJidWlsZF9pZCI6MSwiYnVpbGRfbnVtYmVyIjoxLCJhY3RvciI6Ik9jdG9jYXQiLCJyZXBvIjoiT2N0b2NhdC90ZXN0IiwidG9rZW5fdHlwZSI6IklEUmVxdWVzdCIsImltYWdlIjoiYWxwaW5lOmxhdGVzdCIsImNvbW1hbmRzIjp0cnVlLCJyZXF1ZXN0Ijoid3JpdGUiLCJpc3MiOiJodHRwczovL3ZlbGEuY29tL19zZXJ2aWNlcy90b2tlbiIsInN1YiI6InJlcG86T2N0b2NhdC90ZXN0OnJlZjpyZWZzL2hlYWRzL21haW46ZXZlbnQ6cHVzaCIsImV4cCI6MTcxNDQ5NTgyMywiaWF0IjoxNzE0NDk1NTIzfQ.l7ulJ7g5iTWGrR_IOBC2borJj2yixRAMZpsZEeaMvUw" + }` + // BuildExecutableResp represents a JSON return for requesting a build executable. BuildExecutableResp = `{ "id": 1 @@ -170,7 +232,7 @@ const ( func getBuilds(c *gin.Context) { data := []byte(BuildsResp) - var body []library.Build + var body []api.Build _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -190,7 +252,7 @@ func getBuild(c *gin.Context) { data := []byte(BuildResp) - var body library.Build + var body api.Build _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -222,7 +284,7 @@ func getLogs(c *gin.Context) { func addBuild(c *gin.Context) { data := []byte(BuildResp) - var body library.Build + var body api.Build _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -246,7 +308,7 @@ func updateBuild(c *gin.Context) { data := []byte(BuildResp) - var body library.Build + var body api.Build _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -285,7 +347,7 @@ func restartBuild(c *gin.Context) { data := []byte(BuildResp) - var body library.Build + var body api.Build _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -339,7 +401,7 @@ func buildQueue(c *gin.Context) { data := []byte(BuildQueueResp) - var body []library.BuildQueue + var body []api.QueueBuild _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -372,6 +434,46 @@ func buildToken(c *gin.Context) { c.JSON(http.StatusOK, body) } +// idToken has a param :build returns mock JSON for a http GET. +// +// Pass "0" to :build to test receiving a http 400 response. +func idToken(c *gin.Context) { + b := c.Param("build") + + if strings.EqualFold(b, "0") { + c.AbortWithStatusJSON(http.StatusBadRequest, "") + + return + } + + data := []byte(IDTokenResp) + + var body library.Token + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// idTokenRequestToken has a param :build returns mock JSON for a http GET. +// +// Pass "0" to :build to test receiving a http 400 response. +func idTokenRequestToken(c *gin.Context) { + b := c.Param("build") + + if strings.EqualFold(b, "0") { + c.AbortWithStatusJSON(http.StatusBadRequest, "") + + return + } + + data := []byte(IDTokenRequestTokenResp) + + var body library.Token + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + // buildExecutable has a param :build returns mock JSON for a http GET. // // Pass "0" to :build to test receiving a http 500 response. diff --git a/mock/server/build_test.go b/mock/server/build_test.go index 572fa4300..0d7c4825a 100644 --- a/mock/server/build_test.go +++ b/mock/server/build_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestBuild_ActiveBuildResp(t *testing.T) { - testBuild := library.Build{} + testBuild := api.Build{} err := json.Unmarshal([]byte(BuildResp), &testBuild) if err != nil { diff --git a/mock/server/dashboard.go b/mock/server/dashboard.go new file mode 100644 index 000000000..c2a017050 --- /dev/null +++ b/mock/server/dashboard.go @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types" +) + +const ( + // DashboardResp represents a JSON return for a dashboard. + DashboardResp = `{ + "id": "c976470d-34c1-49b2-9a98-1035871c576b", + "name": "my-dashboard", + "created_at": 1714573212, + "created_by": "Octocat", + "updated_at": 1714573212, + "updated_by": "Octocat", + "admins": [ + { + "id": 1, + "name": "Octocat", + "active": true + } + ], + "repos": [ + { + "id": 1, + "name": "Octocat/vela-repo", + "branches": [ + "main" + ], + "events": [ + "push" + ] + } + ] +}` + + // DashCardResp represents a JSON return for a DashCard. + DashCardResp = `{ + "dashboard": { + "id": "6e9f84c3-d853-4afb-b56e-99ff200264c0", + "name": "dashboard-1", + "created_at": 1714677999, + "created_by": "Octocat", + "updated_at": 1714678173, + "updated_by": "Octocat", + "admins": [ + { + "id": 1, + "name": "Octocat", + "active": true + } + ], + "repos": [ + { + "id": 2, + "name": "Octocat/test-repo" + }, + { + "id": 1, + "name": "Octocat/test-repo-2" + } + ] + }, + "repos": [ + { + "org": "Octocat", + "name": "test-repo", + "counter": 1, + "builds": [ + { + "number": 1, + "started": 1714678666, + "finished": 1714678672, + "sender": "Octocat", + "status": "failure", + "event": "deployment", + "branch": "refs/heads/main", + "link": "http://vela/Octocat/test-repo/1" + } + ] + }, + { + "org": "Octocat", + "name": "test-repo-2" + } + ] +}` + + // DashCardsResp represents a JSON return for multiple DashCards. + DashCardsResp = `[ +{ + "dashboard": { + "id": "6e9f84c3-d853-4afb-b56e-99ff200264c0", + "name": "dashboard-1", + "created_at": 1714677999, + "created_by": "Octocat", + "updated_at": 1714678173, + "updated_by": "Octocat", + "admins": [ + { + "id": 1, + "name": "Octocat", + "active": true + } + ], + "repos": [ + { + "id": 2, + "name": "Octocat/test-repo" + }, + { + "id": 1, + "name": "Octocat/test-repo-2" + } + ] + }, + "repos": [ + { + "org": "Octocat", + "name": "test-repo", + "counter": 1, + "builds": [ + { + "number": 1, + "started": 1714678666, + "finished": 1714678672, + "sender": "Octocat", + "status": "failure", + "event": "deployment", + "branch": "refs/heads/main", + "link": "http://vela/Octocat/test-repo/1" + } + ] + }, + { + "org": "Octocat", + "name": "test-repo-2" + } + ] +}, +{ + "dashboard": { + "id": "6e9f84c3-d853-4afb-b56e-99ff200264c1", + "name": "dashboard-2", + "created_at": 1714677999, + "created_by": "Octocat", + "updated_at": 1714678173, + "updated_by": "Octocat", + "admins": [ + { + "id": 1, + "name": "Octocat", + "active": true + } + ], + "repos": [ + { + "id": 2, + "name": "Octocat/test-repo" + }, + { + "id": 1, + "name": "Octocat/test-repo-2" + } + ] + }, + "repos": [ + { + "org": "Octocat", + "name": "test-repo", + "counter": 1, + "builds": [ + { + "number": 1, + "started": 1714678666, + "finished": 1714678672, + "sender": "Octocat", + "status": "failure", + "event": "deployment", + "branch": "refs/heads/main", + "link": "http://vela/Octocat/test-repo/1" + } + ] + }, + { + "org": "Octocat", + "name": "test-repo-2" + } + ] +} +]` +) + +// getDashboards returns mock JSON for a http GET. +func getDashboards(c *gin.Context) { + data := []byte(DashCardsResp) + + var body []api.DashCard + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// getDashboard has a param :dashboard returns mock JSON for a http GET. +func getDashboard(c *gin.Context) { + d := c.Param("dashboard") + + if strings.EqualFold(d, "0") { + msg := fmt.Sprintf("Dashboard %s does not exist", d) + + c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg}) + + return + } + + data := []byte(DashCardResp) + + var body api.DashCard + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// addDashboard returns mock JSON for a http POST. +func addDashboard(c *gin.Context) { + data := []byte(DashboardResp) + + var body api.Dashboard + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusCreated, body) +} + +// updateDashboard returns mock JSON for a http PUT. +func updateDashboard(c *gin.Context) { + data := []byte(DashboardResp) + + var body api.Dashboard + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} diff --git a/mock/server/dashboard_test.go b/mock/server/dashboard_test.go new file mode 100644 index 000000000..de09a0257 --- /dev/null +++ b/mock/server/dashboard_test.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "encoding/json" + "reflect" + "testing" + + api "github.com/go-vela/server/api/types" +) + +func TestDashboard_ActiveDashboardResp(t *testing.T) { + testDashboard := api.Dashboard{} + + err := json.Unmarshal([]byte(DashboardResp), &testDashboard) + if err != nil { + t.Errorf("error unmarshaling dashboard: %v", err) + } + + tDashboard := reflect.TypeOf(testDashboard) + + for i := 0; i < tDashboard.NumField(); i++ { + if reflect.ValueOf(testDashboard).Field(i).IsNil() { + t.Errorf("DashboardResp missing field %s", tDashboard.Field(i).Name) + } + } + + testDashCard := api.DashCard{} + + err = json.Unmarshal([]byte(DashCardResp), &testDashCard) + if err != nil { + t.Errorf("error unmarshaling dash card: %v", err) + } + + tDashCard := reflect.TypeOf(testDashCard) + + for i := 0; i < tDashCard.NumField(); i++ { + if reflect.ValueOf(testDashCard).Field(i).IsNil() { + t.Errorf("DashCardResp missing field %s", tDashCard.Field(i).Name) + } + } + + testDashCards := []api.DashCard{} + err = json.Unmarshal([]byte(DashCardsResp), &testDashCards) + if err != nil { + t.Errorf("error unmarshaling dash cards: %v", err) + } + + for _, testDashCard := range testDashCards { + tDashCard := reflect.TypeOf(testDashCard) + + for i := 0; i < tDashCard.NumField(); i++ { + if reflect.ValueOf(testDashCard).Field(i).IsNil() { + t.Errorf("DashboardsResp missing field %s", tDashboard.Field(i).Name) + } + } + } +} diff --git a/mock/server/deployment.go b/mock/server/deployment.go index 758d6d208..e68e24d07 100644 --- a/mock/server/deployment.go +++ b/mock/server/deployment.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) diff --git a/mock/server/hook.go b/mock/server/hook.go index b31967211..58078da99 100644 --- a/mock/server/hook.go +++ b/mock/server/hook.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) diff --git a/mock/server/log.go b/mock/server/log.go index fffefaedf..98b359b57 100644 --- a/mock/server/log.go +++ b/mock/server/log.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) diff --git a/mock/server/pipeline.go b/mock/server/pipeline.go index 73ee1b42f..0d90cf45d 100644 --- a/mock/server/pipeline.go +++ b/mock/server/pipeline.go @@ -8,13 +8,12 @@ import ( "net/http" "strings" - "github.com/go-vela/types/library" - + yml "github.com/buildkite/yaml" "github.com/gin-gonic/gin" + "github.com/go-vela/types" + "github.com/go-vela/types/library" "github.com/go-vela/types/yaml" - - yml "github.com/buildkite/yaml" ) const ( diff --git a/mock/server/repo.go b/mock/server/repo.go index cc480b289..aba7cbaf9 100644 --- a/mock/server/repo.go +++ b/mock/server/repo.go @@ -9,15 +9,22 @@ import ( "strings" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" - "github.com/go-vela/types/library" ) const ( // RepoResp represents a JSON return for a single repo. RepoResp = `{ "id": 1, - "user_id": 1, + "owner": { + "id": 1, + "name": "octocat", + "favorites": [], + "active": true, + "admin": false + }, "org": "github", "counter": 10, "name": "octocat", @@ -33,11 +40,6 @@ const ( "pipeline_type": "yaml", "topics": [], "active": true, - "allow_pull": false, - "allow_push": true, - "allow_deploy": false, - "allow_tag": false, - "allow_comment": false, "allow_events": { "push": { "branch": true, @@ -77,11 +79,7 @@ const ( "visibility": "public", "private": false, "trusted": true, - "active": true, - "allow_pr": false, - "allow_push": true, - "allow_deploy": false, - "allow_tag": false + "active": true }, { "id": 2, @@ -97,11 +95,7 @@ const ( "visibility": "public", "private": false, "trusted": true, - "active": true, - "allow_pr": false, - "allow_push": true, - "allow_deploy": false, - "allow_tag": false + "active": true } ]` ) @@ -110,7 +104,7 @@ const ( func getRepos(c *gin.Context) { data := []byte(ReposResp) - var body []library.Repo + var body []api.Repo _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -132,7 +126,7 @@ func getRepo(c *gin.Context) { data := []byte(RepoResp) - var body library.Repo + var body api.Repo _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -142,7 +136,7 @@ func getRepo(c *gin.Context) { func addRepo(c *gin.Context) { data := []byte(RepoResp) - var body library.Repo + var body api.Repo _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -166,7 +160,7 @@ func updateRepo(c *gin.Context) { data := []byte(RepoResp) - var body library.Repo + var body api.Repo _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) diff --git a/mock/server/repo_test.go b/mock/server/repo_test.go index 1e59ba022..b6b408a8f 100644 --- a/mock/server/repo_test.go +++ b/mock/server/repo_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestRepo_ActiveRepoResp(t *testing.T) { - testRepo := library.Repo{} + testRepo := api.Repo{} err := json.Unmarshal([]byte(RepoResp), &testRepo) if err != nil { diff --git a/mock/server/schedule.go b/mock/server/schedule.go index 0bc2fd017..73882e4af 100644 --- a/mock/server/schedule.go +++ b/mock/server/schedule.go @@ -9,28 +9,122 @@ import ( "strings" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" - "github.com/go-vela/types/library" ) const ( // ScheduleResp represents a JSON return for a single schedule. ScheduleResp = `{ - "id": 2, - "repo_id": 1, - "active": true, - "name": "foo", - "entry": "@weekly", - "created_at": 1683154980, - "created_by": "octocat", - "updated_at": 1683154980, - "updated_by": "octocat", - "scheduled_at": 0, - "branch": "main" -}` + "id": 2, + "repo": { + "id": 1, + "owner": { + "id": 1, + "name": "octocat", + "favorites": [], + "active": true, + "admin": false + }, + "org": "github", + "counter": 10, + "name": "octocat", + "full_name": "github/octocat", + "link": "https://github.com/github/octocat", + "clone": "https://github.com/github/octocat", + "branch": "main", + "build_limit": 10, + "timeout": 60, + "visibility": "public", + "private": false, + "trusted": true, + "pipeline_type": "yaml", + "topics": [], + "active": true, + "allow_events": { + "push": { + "branch": true, + "tag": true + }, + "pull_request": { + "opened": true, + "synchronize": true, + "reopened": true, + "edited": false + }, + "deployment": { + "created": true + }, + "comment": { + "created": false, + "edited": false + } + }, + "approve_build": "fork-always", + "previous_name": "" + }, + "active": true, + "name": "foo", + "entry": "@weekly", + "created_at": 1683154980, + "created_by": "octocat", + "updated_at": 1683154980, + "updated_by": "octocat", + "scheduled_at": 0, + "branch": "main", + "error": "error message", + "next_run": 0 + }` SchedulesResp = `[ { "id": 2, + "repo": { + "id": 1, + "owner": { + "id": 1, + "name": "octocat", + "favorites": [], + "active": true, + "admin": false + }, + "org": "github", + "counter": 10, + "name": "octocat", + "full_name": "github/octocat", + "link": "https://github.com/github/octocat", + "clone": "https://github.com/github/octocat", + "branch": "main", + "build_limit": 10, + "timeout": 60, + "visibility": "public", + "private": false, + "trusted": true, + "pipeline_type": "yaml", + "topics": [], + "active": true, + "allow_events": { + "push": { + "branch": true, + "tag": true + }, + "pull_request": { + "opened": true, + "synchronize": true, + "reopened": true, + "edited": false + }, + "deployment": { + "created": true + }, + "comment": { + "created": false, + "edited": false + } + }, + "approve_build": "fork-always", + "previous_name": "" + }, "active": true, "name": "foo", "entry": "@weekly", @@ -39,11 +133,58 @@ const ( "updated_at": 1683154980, "updated_by": "octocat", "scheduled_at": 0, - "repo_id": 1, - "branch": "main" + "branch": "main", + "error": "error message", + "next_run": 0 }, { "id": 1, + "repo": { + "id": 1, + "owner": { + "id": 1, + "name": "octocat", + "favorites": [], + "active": true, + "admin": false + }, + "org": "github", + "counter": 10, + "name": "octocat", + "full_name": "github/octocat", + "link": "https://github.com/github/octocat", + "clone": "https://github.com/github/octocat", + "branch": "main", + "build_limit": 10, + "timeout": 60, + "visibility": "public", + "private": false, + "trusted": true, + "pipeline_type": "yaml", + "topics": [], + "active": true, + "allow_events": { + "push": { + "branch": true, + "tag": true + }, + "pull_request": { + "opened": true, + "synchronize": true, + "reopened": true, + "edited": false + }, + "deployment": { + "created": true + }, + "comment": { + "created": false, + "edited": false + } + }, + "approve_build": "fork-always", + "previous_name": "" + }, "active": true, "name": "bar", "entry": "@weekly", @@ -53,16 +194,17 @@ const ( "updated_by": "octocat", "scheduled_at": 0, "repo_id": 1, - "branch": "main - } -]` + "branch": "main", + "error": "error message", + "next_run": 0 + }]` ) // getSchedules returns mock JSON for a http GET. func getSchedules(c *gin.Context) { data := []byte(SchedulesResp) - var body []library.Schedule + var body []api.Schedule _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -84,7 +226,7 @@ func getSchedule(c *gin.Context) { data := []byte(ScheduleResp) - var body library.Schedule + var body api.Schedule _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -94,7 +236,7 @@ func getSchedule(c *gin.Context) { func addSchedule(c *gin.Context) { data := []byte(ScheduleResp) - var body library.Schedule + var body api.Schedule _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -118,7 +260,7 @@ func updateSchedule(c *gin.Context) { data := []byte(ScheduleResp) - var body library.Schedule + var body api.Schedule _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) diff --git a/mock/server/schedule_test.go b/mock/server/schedule_test.go index b88c9f223..293676e02 100644 --- a/mock/server/schedule_test.go +++ b/mock/server/schedule_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestSchedule_ActiveScheduleResp(t *testing.T) { - testSchedule := library.Schedule{} + testSchedule := api.Schedule{} err := json.Unmarshal([]byte(ScheduleResp), &testSchedule) if err != nil { diff --git a/mock/server/scm.go b/mock/server/scm.go index 0dea97c37..e191da268 100644 --- a/mock/server/scm.go +++ b/mock/server/scm.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" ) diff --git a/mock/server/secret.go b/mock/server/secret.go index 1747fbcf4..6cd45047f 100644 --- a/mock/server/secret.go +++ b/mock/server/secret.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) @@ -28,7 +29,6 @@ const ( "images": [ "alpine" ], - "events": [], "allow_events": { "push": { "branch": true, @@ -49,6 +49,7 @@ const ( } }, "allow_command": true, + "allow_substitution": true, "created_at": 1, "created_by": "Octocat", "updated_at": 2, @@ -67,9 +68,6 @@ const ( "type": "repo", "images": [ "alpine" - ], - "events": [ - "push" ] }, { @@ -82,9 +80,6 @@ const ( "type": "org", "images": [ "alpine" - ], - "events": [ - "push" ] }, { @@ -97,9 +92,6 @@ const ( "type": "shared", "images": [ "alpine" - ], - "events": [ - "push" ] } ]` diff --git a/mock/server/server.go b/mock/server/server.go index 0b7dcff79..4b249cec3 100644 --- a/mock/server/server.go +++ b/mock/server/server.go @@ -29,6 +29,9 @@ func FakeHandler() http.Handler { e.PUT("/api/v1/admin/user", updateUser) e.POST("/api/v1/admin/workers/:worker/register", registerToken) e.PUT("api/v1/admin/clean", cleanResoures) + e.GET("/api/v1/admin/settings", getSettings) + e.PUT("/api/v1/admin/settings", updateSettings) + e.DELETE("/api/v1/admin/settings", restoreSettings) // mock endpoints for build calls e.GET("/api/v1/repos/:org/:repo/builds/:build", getBuild) @@ -42,6 +45,14 @@ func FakeHandler() http.Handler { e.DELETE("/api/v1/repos/:org/:repo/builds/:build", removeBuild) e.GET("/api/v1/repos/:org/:repo/builds/:build/token", buildToken) e.GET("/api/v1/repos/:org/:repo/builds/:build/executable", buildExecutable) + e.GET("/api/v1/repos/:org/:repo/builds/:build/id_token", idToken) + e.GET("/api/v1/repos/:org/:repo/builds/:build/id_request_token", idTokenRequestToken) + + // mock endpoints for dashboard calls + e.GET("/api/v1/dashboards/:dashboard", getDashboard) + e.GET("/api/v1/user/dashboards", getDashboards) + e.POST("/api/v1/dashboards", addDashboard) + e.PUT("/api/v1/dashboards/:dashboard", updateDashboard) // mock endpoints for deployment calls e.GET("/api/v1/deployments/:org/:repo", getDeployments) @@ -137,6 +148,10 @@ func FakeHandler() http.Handler { e.GET("/validate-token", validateToken) e.GET("/validate-oauth", validateOAuthToken) + // mock endpoints for oidc calls + e.GET("/_services/token/.well-known/openid-configuration", openIDConfig) + e.GET("/_services/token/.well-known/jwks", getJWKS) + // mock endpoint for queue credentials e.GET("/api/v1/queue/info", getQueueCreds) diff --git a/mock/server/service.go b/mock/server/service.go index a4111611a..c42e97dd6 100644 --- a/mock/server/service.go +++ b/mock/server/service.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) diff --git a/mock/server/settings.go b/mock/server/settings.go new file mode 100644 index 000000000..07e5c9419 --- /dev/null +++ b/mock/server/settings.go @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "encoding/json" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/types/settings" +) + +const ( + // SettingsResp represents a JSON return for a single settings. + SettingsResp = ` + { + "id": 1, + "compiler": { + "clone_image": "target/vela-git", + "template_depth": 3, + "starlark_exec_limit": 100 + }, + "queue": { + "routes": [ + "vela" + ] + }, + "repo_allowlist": [ + "*" + ], + "schedule_allowlist": [ + "octocat/hello-world" + ], + "created_at": 1, + "updated_at": 1, + "updated_by": "octocat" + }` + + // UpdateSettingsResp represents a JSON return for modifying a settings field. + UpdateSettingsResp = ` + { + "id": 1, + "compiler": { + "clone_image": "target/vela-git:latest", + "template_depth": 5, + "starlark_exec_limit": 123 + }, + "queue": { + "routes": [ + "vela", + "large" + ] + }, + "repo_allowlist": [], + "schedule_allowlist": [ + "octocat/hello-world", + "octocat/*" + ], + "created_at": 1, + "updated_at": 1, + "updated_by": "octocat" + }` + + // RestoreSettingsResp represents a JSON return for restoring the settings record to the defaults. + RestoreSettingsResp = ` + { + "id": 1, + "compiler": { + "clone_image": "target/vela-git:latest", + "template_depth": 5, + "starlark_exec_limit": 123 + }, + "queue": { + "routes": [ + "vela", + "large" + ] + }, + "repo_allowlist": [], + "schedule_allowlist": [ + "octocat/hello-world", + "octocat/*" + ], + "created_at": 1, + "updated_at": 1, + "updated_by": "octocat" + }` +) + +// getSettings has a param :settings returns mock JSON for a http GET. +func getSettings(c *gin.Context) { + data := []byte(SettingsResp) + + var body settings.Platform + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// updateSettings returns mock JSON for a http PUT. +func updateSettings(c *gin.Context) { + data := []byte(UpdateSettingsResp) + + var body settings.Platform + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} + +// restoreSettings returns mock JSON for a http DELETE. +func restoreSettings(c *gin.Context) { + data := []byte(RestoreSettingsResp) + + var body settings.Platform + _ = json.Unmarshal(data, &body) + + c.JSON(http.StatusOK, body) +} diff --git a/mock/server/settings_test.go b/mock/server/settings_test.go new file mode 100644 index 000000000..d827cc756 --- /dev/null +++ b/mock/server/settings_test.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/go-vela/server/api/types/settings" +) + +func TestSettings_GetResp(t *testing.T) { + testSettings := settings.Platform{} + + err := json.Unmarshal([]byte(SettingsResp), &testSettings) + if err != nil { + t.Errorf("error unmarshaling settings: %v", err) + } + + tSettings := reflect.TypeOf(testSettings) + + for i := 0; i < tSettings.NumField(); i++ { + f := reflect.ValueOf(testSettings).Field(i) + if f.IsNil() { + t.Errorf("SettingsResp missing field %s", tSettings.Field(i).Name) + } + } +} + +func TestSettings_UpdateResp(t *testing.T) { + testSettings := settings.Platform{} + + err := json.Unmarshal([]byte(UpdateSettingsResp), &testSettings) + if err != nil { + t.Errorf("error unmarshaling settings: %v", err) + } + + tSettings := reflect.TypeOf(testSettings) + + for i := 0; i < tSettings.NumField(); i++ { + f := reflect.ValueOf(testSettings).Field(i) + if f.IsNil() { + t.Errorf("UpdateSettingsResp missing field %s", tSettings.Field(i).Name) + } + } +} + +func TestSettings_RestoreResp(t *testing.T) { + testSettings := settings.Platform{} + + err := json.Unmarshal([]byte(RestoreSettingsResp), &testSettings) + if err != nil { + t.Errorf("error unmarshaling settings: %v", err) + } + + tSettings := reflect.TypeOf(testSettings) + + for i := 0; i < tSettings.NumField(); i++ { + f := reflect.ValueOf(testSettings).Field(i) + if f.IsNil() { + t.Errorf("RestoreSettingsResp missing field %s", tSettings.Field(i).Name) + } + } +} diff --git a/mock/server/step.go b/mock/server/step.go index e5694ab85..ff4c32ce8 100644 --- a/mock/server/step.go +++ b/mock/server/step.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/go-vela/types" "github.com/go-vela/types/library" ) @@ -32,7 +33,8 @@ const ( "finished": 0, "host": "host.company.com", "runtime": "docker", - "distribution": "linux" + "distribution": "linux", + "report_as": "test" }` // StepsResp represents a JSON return for one to many steps. @@ -51,7 +53,8 @@ const ( "finished": 0, "host": "host.company.com", "runtime": "docker", - "distribution": "linux" + "distribution": "linux", + "report_as": "" }, { "id": 1, @@ -67,7 +70,8 @@ const ( "finished": 0, "host": "host.company.com", "runtime": "docker", - "distribution": "linux" + "distribution": "linux", + "report_as": "test" } ]` ) diff --git a/mock/server/user.go b/mock/server/user.go index 1ec672b47..54fbedd64 100644 --- a/mock/server/user.go +++ b/mock/server/user.go @@ -10,8 +10,9 @@ import ( "strings" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" - "github.com/go-vela/types/library" ) const ( @@ -22,7 +23,8 @@ const ( "token": null, "favorites": ["github/octocat"], "active": true, - "admin": false + "admin": false, + "dashboards": [] }` // UsersResp represents a JSON return for one to many users. @@ -33,7 +35,8 @@ const ( "token": null, "favorites": ["github/octocat"], "active": true, - "admin": false + "admin": false, + "dashboards": [] }, { "id": 1, @@ -41,7 +44,8 @@ const ( "token": null, "favorites": ["github/octocat"], "active": true, - "admin": false + "admin": false, + "dashboards": [] } ]` ) @@ -50,7 +54,7 @@ const ( func getUsers(c *gin.Context) { data := []byte(UsersResp) - var body []library.User + var body []api.User _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -72,7 +76,7 @@ func getUser(c *gin.Context) { data := []byte(UserResp) - var body library.User + var body api.User _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -82,7 +86,7 @@ func getUser(c *gin.Context) { func addUser(c *gin.Context) { data := []byte(UserResp) - var body library.User + var body api.User _ = json.Unmarshal(data, &body) c.JSON(http.StatusCreated, body) @@ -106,7 +110,7 @@ func updateUser(c *gin.Context) { data := []byte(UserResp) - var body library.User + var body api.User _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) diff --git a/mock/server/user_test.go b/mock/server/user_test.go index 9fcf3484b..603ae0c72 100644 --- a/mock/server/user_test.go +++ b/mock/server/user_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestUser_ActiveUserResp(t *testing.T) { - testUser := library.User{} + testUser := api.User{} err := json.Unmarshal([]byte(UserResp), &testUser) if err != nil { diff --git a/mock/server/worker.go b/mock/server/worker.go index 6abf38451..6e5e6ac5e 100644 --- a/mock/server/worker.go +++ b/mock/server/worker.go @@ -9,6 +9,8 @@ import ( "strings" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" "github.com/go-vela/types/library" ) @@ -27,12 +29,71 @@ const ( ], "active": true, "last_checked_in": 1602612590, - "status": "idle", - "last_status_update_at": 160000000, - "running_build_ids": [], - "last_build_started_at": 1, - "last_build_finished_at": 2, - "build_limit": 1 + "status": "busy", + "last_status_update_at": 1602612590, + "last_build_started_at": 1602612590, + "last_build_finished_at": 1602612590, + "build_limit": 2, + "running_builds": [ + { + "id": 2, + "repo_id": 1, + "number": 2, + "parent": 1, + "event": "push", + "status": "running", + "error": "", + "enqueued": 1563474204, + "created": 1563474204, + "started": 1563474204, + "finished": 0, + "deploy": "", + "clone": "https://github.com/github/octocat.git", + "source": "https://github.com/github/octocat/commit/48afb5bdc41ad69bf22588491333f7cf71135163", + "title": "push received from https://github.com/github/octocat", + "message": "Second commit...", + "commit": "48afb5bdc41ad69bf22588491333f7cf71135163", + "sender": "OctoKitty", + "author": "OctoKitty", + "email": "octokitty@github.com", + "link": "https://vela.example.company.com/github/octocat/1", + "branch": "main", + "ref": "refs/heads/main", + "base_ref": "", + "host": "ed95dcc0687c", + "runtime": "", + "distribution": "" + }, + { + "id": 1, + "repo_id": 1, + "number": 1, + "parent": 1, + "event": "push", + "status": "running", + "error": "", + "enqueued": 1563474077, + "created": 1563474076, + "started": 1563474077, + "finished": 0, + "deploy": "", + "clone": "https://github.com/github/octocat.git", + "source": "https://github.com/github/octocat/commit/48afb5bdc41ad69bf22588491333f7cf71135163", + "title": "push received from https://github.com/github/octocat", + "message": "First commit...", + "commit": "48afb5bdc41ad69bf22588491333f7cf71135163", + "sender": "OctoKitty", + "author": "OctoKitty", + "email": "octokitty@github.com", + "link": "https://vela.example.company.com/github/octocat/1", + "branch": "main", + "ref": "refs/heads/main", + "base_ref": "", + "host": "82823eb770b0", + "runtime": "", + "distribution": "" + } + ] }` // WorkersResp represents a JSON return for one to many workers. @@ -47,7 +108,43 @@ const ( "large:docker" ], "active": true, - "last_checked_in": 1602612590 + "last_checked_in": 1602612590, + "status": "available", + "last_status_update_at": 1602612590, + "last_build_started_at": 1602612590, + "last_build_finished_at": 1602612590, + "build_limit": 2, + "running_builds": [ + { + "id": 2, + "repo_id": 1, + "number": 2, + "parent": 1, + "event": "push", + "status": "running", + "error": "", + "enqueued": 1563474204, + "created": 1563474204, + "started": 1563474204, + "finished": 0, + "deploy": "", + "clone": "https://github.com/github/octocat.git", + "source": "https://github.com/github/octocat/commit/48afb5bdc41ad69bf22588491333f7cf71135163", + "title": "push received from https://github.com/github/octocat", + "message": "Second commit...", + "commit": "48afb5bdc41ad69bf22588491333f7cf71135163", + "sender": "OctoKitty", + "author": "OctoKitty", + "email": "octokitty@github.com", + "link": "https://vela.example.company.com/github/octocat/1", + "branch": "main", + "ref": "refs/heads/main", + "base_ref": "", + "host": "ed95dcc0687c", + "runtime": "", + "distribution": "" + } + ] }, { "id": 2, @@ -59,7 +156,13 @@ const ( "large:docker" ], "active": true, - "last_checked_in": 1602612590 + "last_checked_in": 1602612590, + "status": "idle", + "last_status_update_at": 1602612590, + "last_build_started_at": 1602612590, + "last_build_finished_at": 1602612590, + "build_limit": 2, + "running_builds": [] } ]` @@ -93,7 +196,7 @@ const ( func getWorkers(c *gin.Context) { data := []byte(WorkersResp) - var body []library.Worker + var body []api.Worker _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -113,7 +216,7 @@ func getWorker(c *gin.Context) { data := []byte(WorkerResp) - var body library.Worker + var body api.Worker _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) @@ -145,7 +248,7 @@ func updateWorker(c *gin.Context) { data := []byte(WorkerResp) - var body library.Worker + var body api.Worker _ = json.Unmarshal(data, &body) c.JSON(http.StatusOK, body) diff --git a/mock/server/worker_test.go b/mock/server/worker_test.go index defc3bd86..a9a46b810 100644 --- a/mock/server/worker_test.go +++ b/mock/server/worker_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestWorker_ActiveWorkerResp(t *testing.T) { - testWorker := library.Worker{} + testWorker := api.Worker{} err := json.Unmarshal([]byte(WorkerResp), &testWorker) if err != nil { @@ -26,3 +26,22 @@ func TestWorker_ActiveWorkerResp(t *testing.T) { } } } + +func TestWorker_ListActiveWorkerResp(t *testing.T) { + testWorkers := []api.Worker{} + + err := json.Unmarshal([]byte(WorkersResp), &testWorkers) + if err != nil { + t.Errorf("error unmarshaling worker: %v", err) + } + + for index, worker := range testWorkers { + tWorker := reflect.TypeOf(worker) + + for i := 0; i < tWorker.NumField(); i++ { + if reflect.ValueOf(worker).Field(i).IsNil() { + t.Errorf("WorkersResp index %d missing field %s", index, tWorker.Field(i).Name) + } + } + } +} diff --git a/queue/flags.go b/queue/flags.go index 6fc3a6f19..35e5745f5 100644 --- a/queue/flags.go +++ b/queue/flags.go @@ -5,8 +5,9 @@ package queue import ( "time" - "github.com/go-vela/types/constants" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/constants" ) // Flags represents all supported command line diff --git a/queue/models/item.go b/queue/models/item.go new file mode 100644 index 000000000..83b370d48 --- /dev/null +++ b/queue/models/item.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package models + +import ( + api "github.com/go-vela/server/api/types" +) + +// ItemVersion allows the worker to detect items that were queued before an Vela server +// upgrade or downgrade, so it can handle such stale data gracefully. +// For example, the worker could fail a stale build or ask the server to recompile it. +// This is not a public API and is unrelated to the version key in pipeline yaml. +const ItemVersion uint64 = 3 + +// Item is the queue representation of an item to publish to the queue. +type Item struct { + Build *api.Build `json:"build"` + // The 0-value is the implicit ItemVersion for queued Items that pre-date adding the field. + ItemVersion uint64 `json:"item_version"` +} + +// ToItem creates a queue item from a build. +func ToItem(b *api.Build) *Item { + return &Item{ + Build: b, + ItemVersion: ItemVersion, + } +} diff --git a/queue/models/item_test.go b/queue/models/item_test.go new file mode 100644 index 000000000..ba14ef5e4 --- /dev/null +++ b/queue/models/item_test.go @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 + +package models + +import ( + "reflect" + "testing" + + api "github.com/go-vela/server/api/types" +) + +func TestTypes_ToItem(t *testing.T) { + // setup types + booL := false + num := 1 + num64 := int64(num) + str := "foo" + e := new(api.Events) + + b := &api.Build{ + ID: &num64, + Repo: &api.Repo{ + ID: &num64, + Owner: &api.User{ + ID: &num64, + Name: &str, + Token: &str, + Active: &booL, + Admin: &booL, + }, + Org: &str, + Name: &str, + FullName: &str, + Link: &str, + Clone: &str, + Branch: &str, + Timeout: &num64, + Visibility: &str, + Private: &booL, + Trusted: &booL, + Active: &booL, + AllowEvents: e, + }, + Number: &num, + Parent: &num, + Event: &str, + Status: &str, + Error: &str, + Enqueued: &num64, + Created: &num64, + Started: &num64, + Finished: &num64, + Deploy: &str, + Clone: &str, + Source: &str, + Title: &str, + Message: &str, + Commit: &str, + Sender: &str, + Author: &str, + Branch: &str, + Ref: &str, + BaseRef: &str, + } + want := &Item{ + Build: &api.Build{ + ID: &num64, + Repo: &api.Repo{ + ID: &num64, + Owner: &api.User{ + ID: &num64, + Name: &str, + Token: &str, + Active: &booL, + Admin: &booL, + }, + Org: &str, + Name: &str, + FullName: &str, + Link: &str, + Clone: &str, + Branch: &str, + Timeout: &num64, + Visibility: &str, + Private: &booL, + Trusted: &booL, + Active: &booL, + AllowEvents: e, + }, + Number: &num, + Parent: &num, + Event: &str, + Status: &str, + Error: &str, + Enqueued: &num64, + Created: &num64, + Started: &num64, + Finished: &num64, + Deploy: &str, + Clone: &str, + Source: &str, + Title: &str, + Message: &str, + Commit: &str, + Sender: &str, + Author: &str, + Branch: &str, + Ref: &str, + BaseRef: &str, + }, + ItemVersion: ItemVersion, + } + + // run test + got := ToItem(b) + + if !reflect.DeepEqual(got, want) { + t.Errorf("ToItem is %v, want %v", got, want) + } +} diff --git a/queue/queue.go b/queue/queue.go index 8288375e1..fdff9b364 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -5,10 +5,33 @@ package queue import ( "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + + "github.com/go-vela/types/constants" ) +// FromCLIContext helper function to setup the queue from the CLI arguments. +func FromCLIContext(c *cli.Context) (Service, error) { + logrus.Debug("creating queue client from CLI configuration") + + // queue configuration + _setup := &Setup{ + Driver: c.String("queue.driver"), + Address: c.String("queue.addr"), + Cluster: c.Bool("queue.cluster"), + Routes: c.StringSlice("queue.routes"), + Timeout: c.Duration("queue.pop.timeout"), + PrivateKey: c.String("queue.private-key"), + PublicKey: c.String("queue.public-key"), + } + + // setup the queue + // + // https://pkg.go.dev/github.com/go-vela/server/queue?tab=doc#New + return New(_setup) +} + // New creates and returns a Vela service capable of // integrating with the configured queue environment. // Currently, the following queues are supported: diff --git a/queue/redis/driver_test.go b/queue/redis/driver_test.go index e9736d326..52894f1ba 100644 --- a/queue/redis/driver_test.go +++ b/queue/redis/driver_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/alicebob/miniredis/v2" + "github.com/go-vela/types/constants" ) @@ -27,7 +28,7 @@ func TestRedis_Driver(t *testing.T) { _service, err := New( WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())), - WithChannels("foo"), + WithRoutes("foo"), WithCluster(false), WithTimeout(5*time.Second), ) diff --git a/queue/redis/length.go b/queue/redis/length.go index c4d7bb7c3..7f5ed9b0b 100644 --- a/queue/redis/length.go +++ b/queue/redis/length.go @@ -6,13 +6,13 @@ import ( "context" ) -// Length tallies all items present in the configured channels in the queue. +// Length tallies all items present in the configured routes in the queue. func (c *client) Length(ctx context.Context) (int64, error) { - c.Logger.Tracef("reading length of all configured channels in queue") + c.Logger.Tracef("reading length of all configured routes in queue") total := int64(0) - for _, channel := range c.config.Channels { + for _, channel := range c.GetRoutes() { items, err := c.Redis.LLen(ctx, channel).Result() if err != nil { return 0, err diff --git a/queue/redis/length_test.go b/queue/redis/length_test.go index 0049b3809..cd87b1036 100644 --- a/queue/redis/length_test.go +++ b/queue/redis/length_test.go @@ -4,20 +4,18 @@ package redis import ( "context" + "encoding/json" "reflect" "testing" - "github.com/go-vela/types" - "gopkg.in/square/go-jose.v2/json" + "github.com/go-vela/server/queue/models" ) func TestRedis_Length(t *testing.T) { // setup types // use global variables in redis_test.go - _item := &types.Item{ + _item := &models.Item{ Build: _build, - Repo: _repo, - User: _user, } // setup queue item @@ -34,33 +32,32 @@ func TestRedis_Length(t *testing.T) { // setup tests tests := []struct { - channels []string - want int64 + routes []string + want int64 }{ { - channels: []string{"vela"}, - want: 1, + routes: []string{"vela"}, + want: 1, }, { - channels: []string{"vela", "vela:second", "vela:third"}, - want: 4, + routes: []string{"vela", "vela:second", "vela:third"}, + want: 4, }, { - channels: []string{"vela", "vela:second", "phony"}, - want: 6, + routes: []string{"vela", "vela:second", "phony"}, + want: 6, }, } // run tests for _, test := range tests { - for _, channel := range test.channels { + for _, channel := range test.routes { err := _redis.Push(context.Background(), channel, bytes) if err != nil { t.Errorf("unable to push item to queue: %v", err) } } got, err := _redis.Length(context.Background()) - if err != nil { t.Errorf("Length returned err: %v", err) } diff --git a/queue/redis/opts.go b/queue/redis/opts.go index 582994fea..f58dab06a 100644 --- a/queue/redis/opts.go +++ b/queue/redis/opts.go @@ -29,18 +29,18 @@ func WithAddress(address string) ClientOpt { } } -// WithChannels sets the channels in the queue client for Redis. -func WithChannels(channels ...string) ClientOpt { +// WithRoutes sets the routes in the queue client for Redis. +func WithRoutes(routes ...string) ClientOpt { return func(c *client) error { - c.Logger.Trace("configuring channels in redis queue client") + c.Logger.Trace("configuring routes in redis queue client") - // check if the channels provided are empty - if len(channels) == 0 { - return fmt.Errorf("no Redis queue channels provided") + // check if the routes provided are empty + if len(routes) == 0 { + return fmt.Errorf("no Redis queue routes provided") } - // set the queue channels in the redis client - c.config.Channels = channels + // set the queue routes in the redis client + c.SetRoutes(routes) return nil } diff --git a/queue/redis/opts_test.go b/queue/redis/opts_test.go index aa775c121..6e729ce52 100644 --- a/queue/redis/opts_test.go +++ b/queue/redis/opts_test.go @@ -64,7 +64,7 @@ func TestRedis_ClientOpt_WithAddress(t *testing.T) { } } -func TestRedis_ClientOpt_WithChannels(t *testing.T) { +func TestRedis_ClientOpt_WithRoutes(t *testing.T) { // setup tests // create a local fake redis instance // @@ -76,19 +76,19 @@ func TestRedis_ClientOpt_WithChannels(t *testing.T) { defer _redis.Close() tests := []struct { - failure bool - channels []string - want []string + failure bool + routes []string + want []string }{ { - failure: false, - channels: []string{"foo", "bar"}, - want: []string{"foo", "bar"}, + failure: false, + routes: []string{"foo", "bar"}, + want: []string{"foo", "bar"}, }, { - failure: true, - channels: []string{}, - want: []string{}, + failure: true, + routes: []string{}, + want: []string{}, }, } @@ -96,23 +96,23 @@ func TestRedis_ClientOpt_WithChannels(t *testing.T) { for _, test := range tests { _service, err := New( WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())), - WithChannels(test.channels...), + WithRoutes(test.routes...), ) if test.failure { if err == nil { - t.Errorf("WithChannels should have returned err") + t.Errorf("WithRoutes should have returned err") } continue } if err != nil { - t.Errorf("WithChannels returned err: %v", err) + t.Errorf("WithRoutes returned err: %v", err) } - if !reflect.DeepEqual(_service.config.Channels, test.want) { - t.Errorf("WithChannels is %v, want %v", _service.config.Channels, test.want) + if !reflect.DeepEqual(_service.GetRoutes(), test.want) { + t.Errorf("WithRoutes is %v, want %v", _service.GetRoutes(), test.want) } } } diff --git a/queue/redis/ping_test.go b/queue/redis/ping_test.go index 29f84ee26..39fde9272 100644 --- a/queue/redis/ping_test.go +++ b/queue/redis/ping_test.go @@ -5,9 +5,10 @@ package redis import ( "context" "fmt" - "github.com/alicebob/miniredis/v2" "testing" "time" + + "github.com/alicebob/miniredis/v2" ) func TestRedis_Ping_Good(t *testing.T) { @@ -21,7 +22,7 @@ func TestRedis_Ping_Good(t *testing.T) { // setup redis mock goodRedis, err := New( WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())), - WithChannels("foo"), + WithRoutes("foo"), WithCluster(false), WithTimeout(5*time.Second), ) @@ -48,7 +49,7 @@ func TestRedis_Ping_Bad(t *testing.T) { // setup redis mock badRedis, _ := New( WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())), - WithChannels("foo"), + WithRoutes("foo"), WithCluster(false), WithTimeout(5*time.Second), ) diff --git a/queue/redis/pop.go b/queue/redis/pop.go index 250d0c915..7c5658029 100644 --- a/queue/redis/pop.go +++ b/queue/redis/pop.go @@ -7,29 +7,30 @@ import ( "encoding/json" "errors" - "github.com/go-vela/types" "github.com/redis/go-redis/v9" "golang.org/x/crypto/nacl/sign" + + "github.com/go-vela/server/queue/models" ) // Pop grabs an item from the specified channel off the queue. -func (c *client) Pop(ctx context.Context, routes []string) (*types.Item, error) { - c.Logger.Tracef("popping item from queue %s", c.config.Channels) +func (c *client) Pop(ctx context.Context, inRoutes []string) (*models.Item, error) { + c.Logger.Tracef("popping item from queue %s", c.GetRoutes()) - // define channels to pop from - var channels []string + // define routes to pop from + var routes []string // if routes were supplied, use those if len(routes) > 0 { - channels = routes + routes = inRoutes } else { - channels = c.config.Channels + routes = c.GetRoutes() } // build a redis queue command to pop an item from queue // // https://pkg.go.dev/github.com/go-redis/redis?tab=doc#Client.BLPop - popCmd := c.Redis.BLPop(ctx, c.config.Timeout, channels...) + popCmd := c.Redis.BLPop(ctx, c.config.Timeout, routes...) // blocking call to pop item from queue // @@ -58,7 +59,7 @@ func (c *client) Pop(ctx context.Context, routes []string) (*types.Item, error) } // unmarshal result into queue item - item := new(types.Item) + item := new(models.Item) err = json.Unmarshal(opened, item) if err != nil { diff --git a/queue/redis/pop_test.go b/queue/redis/pop_test.go index eb05030bd..8afcad695 100644 --- a/queue/redis/pop_test.go +++ b/queue/redis/pop_test.go @@ -4,22 +4,21 @@ package redis import ( "context" - "reflect" + "encoding/json" "testing" "time" - "github.com/go-vela/types" + "github.com/google/go-cmp/cmp" "golang.org/x/crypto/nacl/sign" - "gopkg.in/square/go-jose.v2/json" + + "github.com/go-vela/server/queue/models" ) func TestRedis_Pop(t *testing.T) { // setup types // use global variables in redis_test.go - _item := &types.Item{ + _item := &models.Item{ Build: _build, - Repo: _repo, - User: _user, } var signed []byte @@ -65,7 +64,7 @@ func TestRedis_Pop(t *testing.T) { t.Errorf("unable to create queue service: %v", err) } // overwrite channel to be invalid - badChannel.config.Channels = nil + badChannel.SetRoutes(nil) signed = sign.Sign(out, bytes, badChannel.config.PrivateKey) @@ -77,10 +76,10 @@ func TestRedis_Pop(t *testing.T) { // setup tests tests := []struct { - failure bool - redis *client - want *types.Item - channels []string + failure bool + redis *client + want *models.Item + routes []string }{ { failure: false, @@ -88,10 +87,10 @@ func TestRedis_Pop(t *testing.T) { want: _item, }, { - failure: false, - redis: _redis, - want: _item, - channels: []string{"custom"}, + failure: false, + redis: _redis, + want: _item, + routes: []string{"custom"}, }, { failure: false, @@ -107,7 +106,7 @@ func TestRedis_Pop(t *testing.T) { // run tests for _, test := range tests { - got, err := test.redis.Pop(context.Background(), test.channels) + got, err := test.redis.Pop(context.Background(), test.routes) if test.failure { if err == nil { @@ -121,8 +120,8 @@ func TestRedis_Pop(t *testing.T) { t.Errorf("Pop returned err: %v", err) } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("Pop is %v, want %v", got, test.want) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Pop() mismatch (-want +got):\n%s", diff) } } } diff --git a/queue/redis/push_test.go b/queue/redis/push_test.go index 5db7e1510..c8fe1bc7f 100644 --- a/queue/redis/push_test.go +++ b/queue/redis/push_test.go @@ -7,16 +7,14 @@ import ( "encoding/json" "testing" - "github.com/go-vela/types" + "github.com/go-vela/server/queue/models" ) func TestRedis_Push(t *testing.T) { // setup types // use global variables in redis_test.go - _item := &types.Item{ + _item := &models.Item{ Build: _build, - Repo: _repo, - User: _user, } // setup queue item diff --git a/queue/redis/redis.go b/queue/redis/redis.go index 643cb51e2..88b962c00 100644 --- a/queue/redis/redis.go +++ b/queue/redis/redis.go @@ -6,19 +6,18 @@ import ( "context" "fmt" "strings" - "time" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/api/types/settings" ) type config struct { // specifies the address to use for the Redis client Address string - // specifies a list of channels for managing builds for the Redis client - Channels []string // enables the Redis client to integrate with a Redis cluster Cluster bool // specifies the timeout to use for the Redis client @@ -33,6 +32,9 @@ type client struct { config *config Redis *redis.Client Options *redis.Options + + settings.Queue + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry Logger *logrus.Entry } @@ -175,7 +177,7 @@ func pingQueue(c *client) error { // This function is intended for running tests only. // //nolint:revive // ignore returning unexported client -func NewTest(signingPrivateKey, signingPublicKey string, channels ...string) (*client, error) { +func NewTest(signingPrivateKey, signingPublicKey string, routes ...string) (*client, error) { // create a local fake redis instance // // https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run @@ -186,7 +188,7 @@ func NewTest(signingPrivateKey, signingPublicKey string, channels ...string) (*c return New( WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())), - WithChannels(channels...), + WithRoutes(routes...), WithCluster(false), WithPrivateKey(signingPrivateKey), WithPublicKey(signingPublicKey), diff --git a/queue/redis/redis_test.go b/queue/redis/redis_test.go index 919892976..b08fda760 100644 --- a/queue/redis/redis_test.go +++ b/queue/redis/redis_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/alicebob/miniredis/v2" - "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" + + api "github.com/go-vela/server/api/types" ) // The following functions were taken from @@ -46,8 +46,29 @@ func Strings(v []string) *[]string { return &v } var ( _signingPrivateKey = "tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg==" _signingPublicKey = "DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=" - _build = &library.Build{ - ID: Int64(1), + _build = &api.Build{ + ID: Int64(1), + Repo: &api.Repo{ + ID: Int64(1), + Owner: &api.User{ + ID: Int64(1), + Name: String("octocat"), + Token: nil, + Active: Bool(true), + Admin: Bool(false), + }, + Org: String("github"), + Name: String("octocat"), + FullName: String("github/octocat"), + Link: String("https://github.com/github/octocat"), + Clone: String("https://github.com/github/octocat.git"), + Branch: String("main"), + Timeout: Int64(60), + Visibility: String("public"), + Private: Bool(false), + Trusted: Bool(false), + Active: Bool(true), + }, Number: Int(1), Parent: Int(1), Event: String("push"), @@ -72,81 +93,6 @@ var ( Runtime: String("docker"), Distribution: String("linux"), } - - _repo = &library.Repo{ - ID: Int64(1), - Org: String("github"), - Name: String("octocat"), - FullName: String("github/octocat"), - Link: String("https://github.com/github/octocat"), - Clone: String("https://github.com/github/octocat.git"), - Branch: String("main"), - Timeout: Int64(60), - Visibility: String("public"), - Private: Bool(false), - Trusted: Bool(false), - Active: Bool(true), - AllowPull: Bool(false), - AllowPush: Bool(true), - AllowDeploy: Bool(false), - AllowTag: Bool(false), - } - - _steps = &pipeline.Build{ - Version: "1", - ID: "github_octocat_1", - Services: pipeline.ContainerSlice{ - { - ID: "service_github_octocat_1_postgres", - Directory: "/home/github/octocat", - Environment: map[string]string{"FOO": "bar"}, - Image: "postgres:12-alpine", - Name: "postgres", - Number: 1, - Ports: []string{"5432:5432"}, - Pull: "not_present", - }, - }, - Steps: pipeline.ContainerSlice{ - { - ID: "step_github_octocat_1_init", - Directory: "/home/github/octocat", - Environment: map[string]string{"FOO": "bar"}, - Image: "#init", - Name: "init", - Number: 1, - Pull: "always", - }, - { - ID: "step_github_octocat_1_clone", - Directory: "/home/github/octocat", - Environment: map[string]string{"FOO": "bar"}, - Image: "target/vela-git:v0.5.1", - Name: "clone", - Number: 2, - Pull: "always", - }, - { - ID: "step_github_octocat_1_echo", - Commands: []string{"echo hello"}, - Directory: "/home/github/octocat", - Environment: map[string]string{"FOO": "bar"}, - Image: "alpine:latest", - Name: "echo", - Number: 3, - Pull: "always", - }, - }, - } - - _user = &library.User{ - ID: Int64(1), - Name: String("octocat"), - Token: nil, - Hash: nil, - Active: Bool(true), - Admin: Bool(false), - } ) func TestRedis_New(t *testing.T) { @@ -179,7 +125,7 @@ func TestRedis_New(t *testing.T) { for _, test := range tests { _, err := New( WithAddress(test.address), - WithChannels("foo"), + WithRoutes("foo"), WithCluster(false), WithTimeout(5*time.Second), ) diff --git a/queue/redis/route.go b/queue/redis/route.go index a25a537c6..e4fc0ae05 100644 --- a/queue/redis/route.go +++ b/queue/redis/route.go @@ -13,7 +13,7 @@ import ( // Route decides which route a build gets placed within the queue. func (c *client) Route(w *pipeline.Worker) (string, error) { - c.Logger.Tracef("deciding route from queue channels %s", c.config.Channels) + c.Logger.Tracef("deciding route from queue routes %s", c.GetRoutes()) // create buffer to store route buf := bytes.Buffer{} @@ -37,7 +37,7 @@ func (c *client) Route(w *pipeline.Worker) (string, error) { route := strings.TrimLeft(buf.String(), ":") - for _, r := range c.config.Channels { + for _, r := range c.GetRoutes() { if strings.EqualFold(route, r) { return route, nil } diff --git a/queue/redis/settings.go b/queue/redis/settings.go new file mode 100644 index 000000000..70747b6c3 --- /dev/null +++ b/queue/redis/settings.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +package redis + +import ( + "github.com/go-vela/server/api/types/settings" +) + +// GetSettings retrieves the api settings type in the Engine. +func (c *client) GetSettings() settings.Queue { + return c.Queue +} + +// SetSettings sets the api settings type in the Engine. +func (c *client) SetSettings(s *settings.Platform) { + if s != nil { + c.SetRoutes(s.GetRoutes()) + } +} diff --git a/queue/service.go b/queue/service.go index fd36a64bc..a976e7045 100644 --- a/queue/service.go +++ b/queue/service.go @@ -5,7 +5,8 @@ package queue import ( "context" - "github.com/go-vela/types" + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/queue/models" "github.com/go-vela/types/pipeline" ) @@ -24,7 +25,7 @@ type Service interface { // Pop defines a function that grabs an // item off the queue. - Pop(context.Context, []string) (*types.Item, error) + Pop(context.Context, []string) (*models.Item, error) // Push defines a function that publishes an // item to the specified route in the queue. @@ -37,4 +38,12 @@ type Service interface { // Route defines a function that decides which // channel a build gets placed within the queue. Route(*pipeline.Worker) (string, error) + + // GetSettings defines a function that returns + // queue settings. + GetSettings() settings.Queue + + // SetSettings defines a function that takes api settings + // and updates the compiler Engine. + SetSettings(*settings.Platform) } diff --git a/queue/setup.go b/queue/setup.go index f240c9fe6..c554c9238 100644 --- a/queue/setup.go +++ b/queue/setup.go @@ -7,9 +7,10 @@ import ( "strings" "time" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/queue/redis" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // Setup represents the configuration necessary for @@ -44,7 +45,7 @@ func (s *Setup) Redis() (Service, error) { // https://pkg.go.dev/github.com/go-vela/server/queue/redis?tab=doc#New return redis.New( redis.WithAddress(s.Address), - redis.WithChannels(s.Routes...), + redis.WithRoutes(s.Routes...), redis.WithCluster(s.Cluster), redis.WithTimeout(s.Timeout), redis.WithPrivateKey(s.PrivateKey), diff --git a/router/admin.go b/router/admin.go index cc321346e..d2eb667ac 100644 --- a/router/admin.go +++ b/router/admin.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/admin" "github.com/go-vela/server/router/middleware/perm" ) @@ -11,18 +12,20 @@ import ( // AdminHandlers is a function that extends the provided base router group // with the API handlers for admin functionality. // -// GET /api/v1/admin/builds/queue -// GET /api/v1/admin/build/:id -// PUT /api/v1/admin/build -// PUT /api/v1/admin/clean -// PUT /api/v1/admin/deployment -// PUT /api/v1/admin/hook -// PUT /api/v1/admin/repo -// PUT /api/v1/admin/secret -// PUT /api/v1/admin/service -// PUT /api/v1/admin/step -// PUT /api/v1/admin/user -// POST /api/v1/admin/workers/:worker/register. +// GET /api/v1/admin/builds/queue +// PUT /api/v1/admin/build +// PUT /api/v1/admin/clean +// PUT /api/v1/admin/deployment +// PUT /api/v1/admin/hook +// PUT /api/v1/admin/repo +// PUT /api/v1/admin/secret +// PUT /api/v1/admin/service +// PUT /api/v1/admin/step +// PUT /api/v1/admin/user +// POST /api/v1/admin/workers/:worker/register +// GET /api/v1/admin/settings +// PUT /api/v1/admin/settings +// DELETE /api/v1/admin/settings. func AdminHandlers(base *gin.RouterGroup) { // Admin endpoints _admin := base.Group("/admin", perm.MustPlatformAdmin()) @@ -45,6 +48,9 @@ func AdminHandlers(base *gin.RouterGroup) { // Admin repo endpoint _admin.PUT("/repo", admin.UpdateRepo) + // Admin rotate keys endpoint + _admin.POST("/rotate_oidc_keys", admin.RotateOIDCKeys) + // Admin secret endpoint _admin.PUT("/secret", admin.UpdateSecret) @@ -59,5 +65,10 @@ func AdminHandlers(base *gin.RouterGroup) { // Admin worker endpoint _admin.POST("/workers/:worker/register", admin.RegisterToken) + + // Admin settings endpoints + _admin.GET("/settings", admin.GetSettings) + _admin.PUT("/settings", admin.UpdateSettings) + _admin.DELETE("/settings", admin.RestoreSettings) } // end of admin endpoints } diff --git a/router/build.go b/router/build.go index 2464dba84..924576763 100644 --- a/router/build.go +++ b/router/build.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/build" "github.com/go-vela/server/api/log" "github.com/go-vela/server/router/middleware" @@ -62,6 +63,8 @@ func BuildHandlers(base *gin.RouterGroup) { b.DELETE("/cancel", executors.Establish(), perm.MustWrite(), build.CancelBuild) b.GET("/logs", perm.MustRead(), log.ListLogsForBuild) b.GET("/token", perm.MustWorkerAuthToken(), build.GetBuildToken) + b.GET("/id_token", perm.MustIDRequestToken(), build.GetIDToken) + b.GET("/id_request_token", perm.MustBuildAccess(), build.GetIDRequestToken) b.GET("/graph", perm.MustRead(), build.GetBuildGraph) b.GET("/executable", perm.MustBuildAccess(), build.GetBuildExecutable) diff --git a/router/dashboard.go b/router/dashboard.go new file mode 100644 index 000000000..32e8490db --- /dev/null +++ b/router/dashboard.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package router + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/dashboard" + dMiddleware "github.com/go-vela/server/router/middleware/dashboard" +) + +// DashboardHandlers is a function that extends the provided base router group +// with the API handlers for dashboard functionality. +// +// POST /api/v1/dashboards +// GET /api/v1/dashboards/:id +// PUT /api/v1/dashboards/:id +// DELETE /api/v1/dashboards/:id . +func DashboardHandlers(base *gin.RouterGroup) { + // Dashboard endpoints + dashboards := base.Group("/dashboards") + { + dashboards.POST("", dashboard.CreateDashboard) + + d := dashboards.Group("/:dashboard", dMiddleware.Establish()) + { + d.GET("", dashboard.GetDashboard) + d.PUT("", dashboard.UpdateDashboard) + d.DELETE("", dashboard.DeleteDashboard) + } + } // end of dashboard endpoints +} diff --git a/router/deployment.go b/router/deployment.go index 4269da663..0f7640561 100644 --- a/router/deployment.go +++ b/router/deployment.go @@ -4,9 +4,9 @@ package router import ( "github.com/gin-gonic/gin" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/api/deployment" + "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/perm" "github.com/go-vela/server/router/middleware/repo" ) diff --git a/router/hook.go b/router/hook.go index 503ad77c1..896279e66 100644 --- a/router/hook.go +++ b/router/hook.go @@ -4,7 +4,9 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/hook" + hmiddleware "github.com/go-vela/server/router/middleware/hook" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/perm" "github.com/go-vela/server/router/middleware/repo" @@ -25,9 +27,13 @@ func HookHandlers(base *gin.RouterGroup) { { _hooks.POST("", perm.MustPlatformAdmin(), hook.CreateHook) _hooks.GET("", perm.MustRead(), hook.ListHooks) - _hooks.GET("/:hook", perm.MustRead(), hook.GetHook) - _hooks.PUT("/:hook", perm.MustPlatformAdmin(), hook.UpdateHook) - _hooks.DELETE("/:hook", perm.MustPlatformAdmin(), hook.DeleteHook) - _hooks.POST("/:hook/redeliver", perm.MustWrite(), hook.RedeliverHook) + + _hook := _hooks.Group("/:hook", hmiddleware.Establish()) + { + _hook.GET("", perm.MustRead(), hook.GetHook) + _hook.PUT("", perm.MustPlatformAdmin(), hook.UpdateHook) + _hook.DELETE("", perm.MustPlatformAdmin(), hook.DeleteHook) + _hook.POST("/redeliver", perm.MustWrite(), hook.RedeliverHook) + } } // end of hooks endpoints } diff --git a/router/log.go b/router/log.go index 6fbea4888..9806aca82 100644 --- a/router/log.go +++ b/router/log.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/log" "github.com/go-vela/server/router/middleware/perm" ) diff --git a/router/middleware/allowlist.go b/router/middleware/allowlist.go deleted file mode 100644 index edfe5feb2..000000000 --- a/router/middleware/allowlist.go +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "github.com/gin-gonic/gin" -) - -// Allowlist is a middleware function that attaches the allowlist used -// to limit which repos can be activated within the system. -func Allowlist(allowlist []string) gin.HandlerFunc { - return func(c *gin.Context) { - c.Set("allowlist", allowlist) - c.Next() - } -} diff --git a/router/middleware/allowlist_schedule.go b/router/middleware/allowlist_schedule.go deleted file mode 100644 index d22a7aa20..000000000 --- a/router/middleware/allowlist_schedule.go +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package middleware - -import ( - "github.com/gin-gonic/gin" -) - -// AllowlistSchedule is a middleware function that attaches the allowlistschedule used -// to limit which repos can utilize the schedule feature within the system. -func AllowlistSchedule(allowlistschedule []string) gin.HandlerFunc { - return func(c *gin.Context) { - c.Set("allowlistschedule", allowlistschedule) - c.Next() - } -} diff --git a/router/middleware/auth/auth.go b/router/middleware/auth/auth.go index 09e9319c2..53ed6a8eb 100644 --- a/router/middleware/auth/auth.go +++ b/router/middleware/auth/auth.go @@ -6,9 +6,9 @@ import ( "fmt" "net/http" - "github.com/go-vela/types/constants" - "github.com/golang-jwt/jwt/v5/request" + + "github.com/go-vela/types/constants" ) // RetrieveAccessToken gets the passed in access token from the header in the request. diff --git a/router/middleware/build/build.go b/router/middleware/build/build.go index 70eba1b9a..c59d85aa8 100644 --- a/router/middleware/build/build.go +++ b/router/middleware/build/build.go @@ -8,26 +8,24 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the build in the given context. -func Retrieve(c *gin.Context) *library.Build { +func Retrieve(c *gin.Context) *api.Build { return FromContext(c) } // Establish sets the build in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() if r == nil { @@ -53,15 +51,7 @@ func Establish() gin.HandlerFunc { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": number, - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Debugf("reading build %s/%d", r.GetFullName(), number) + l.Debugf("reading build %d", number) b, err := database.FromContext(c).GetBuildForRepo(ctx, r, number) if err != nil { @@ -71,6 +61,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "build_id": b.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, b) c.Next() } diff --git a/router/middleware/build/build_test.go b/router/middleware/build/build_test.go index f86353d3a..aacf14949 100644 --- a/router/middleware/build/build_test.go +++ b/router/middleware/build/build_test.go @@ -6,19 +6,22 @@ import ( "context" "net/http" "net/http/httptest" - "reflect" "testing" "github.com/gin-gonic/gin" + "github.com/google/go-cmp/cmp" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" + "github.com/go-vela/server/database/testutils" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/types/library" ) func TestBuild_Retrieve(t *testing.T) { // setup types - want := new(library.Build) + want := new(api.Build) want.SetID(1) // setup context @@ -37,18 +40,23 @@ func TestBuild_Retrieve(t *testing.T) { func TestBuild_Establish(t *testing.T) { // setup types - r := new(library.Repo) + owner := testutils.APIUser().Crop() + owner.SetID(1) + owner.SetName("octocat") + owner.SetToken("foo") + + r := testutils.APIRepo() r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - want := new(library.Build) + want := new(api.Build) want.SetID(1) - want.SetRepoID(1) + want.SetRepo(r) want.SetPipelineID(0) want.SetNumber(1) want.SetParent(1) @@ -68,6 +76,7 @@ func TestBuild_Establish(t *testing.T) { want.SetMessage("") want.SetCommit("") want.SetSender("") + want.SetSenderSCMID("") want.SetAuthor("") want.SetEmail("") want.SetLink("") @@ -82,7 +91,7 @@ func TestBuild_Establish(t *testing.T) { want.SetApprovedAt(0) want.SetApprovedBy("") - got := new(library.Build) + got := new(api.Build) // setup database db, err := database.NewTest() @@ -93,11 +102,24 @@ func TestBuild_Establish(t *testing.T) { defer func() { _ = db.DeleteBuild(context.TODO(), want) _ = db.DeleteRepo(context.TODO(), r) + _ = db.DeleteUser(context.TODO(), owner) db.Close() }() - _, _ = db.CreateRepo(context.TODO(), r) - _, _ = db.CreateBuild(context.TODO(), want) + _, err = db.CreateUser(context.TODO(), owner) + if err != nil { + t.Errorf("unable to create test user: %v", err) + } + + _, err = db.CreateRepo(context.TODO(), r) + if err != nil { + t.Errorf("unable to create test repo: %v", err) + } + + _, err = db.CreateBuild(context.TODO(), want) + if err != nil { + t.Errorf("unable to create test build: %v", err) + } // setup context gin.SetMode(gin.TestMode) @@ -107,6 +129,7 @@ func TestBuild_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -124,8 +147,8 @@ func TestBuild_Establish(t *testing.T) { t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK) } - if !reflect.DeepEqual(got, want) { - t.Errorf("Establish is %v, want %v", got, want) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Establish mismatch (-want +got):\n%s", diff) } } @@ -145,6 +168,7 @@ func TestBuild_Establish_NoRepo(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) @@ -158,9 +182,12 @@ func TestBuild_Establish_NoRepo(t *testing.T) { func TestBuild_Establish_NoBuildParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -188,6 +215,7 @@ func TestBuild_Establish_NoBuildParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -206,9 +234,12 @@ func TestBuild_Establish_NoBuildParameter(t *testing.T) { func TestBuild_Establish_InvalidBuildParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -236,6 +267,7 @@ func TestBuild_Establish_InvalidBuildParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/foo", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -254,9 +286,9 @@ func TestBuild_Establish_InvalidBuildParameter(t *testing.T) { func TestBuild_Establish_NoBuild(t *testing.T) { // setup types - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -284,6 +316,7 @@ func TestBuild_Establish_NoBuild(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) diff --git a/router/middleware/build/context.go b/router/middleware/build/context.go index 99f358add..6870f5364 100644 --- a/router/middleware/build/context.go +++ b/router/middleware/build/context.go @@ -5,7 +5,7 @@ package build import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "build" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the Build associated with this context. -func FromContext(c context.Context) *library.Build { +func FromContext(c context.Context) *api.Build { value := c.Value(key) if value == nil { return nil } - b, ok := value.(*library.Build) + b, ok := value.(*api.Build) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) *library.Build { // ToContext adds the Build to this context if it supports // the Setter interface. -func ToContext(c Setter, b *library.Build) { +func ToContext(c Setter, b *api.Build) { c.Set(key, b) } diff --git a/router/middleware/build/context_test.go b/router/middleware/build/context_test.go index 43dd8fe5f..4a30a1988 100644 --- a/router/middleware/build/context_test.go +++ b/router/middleware/build/context_test.go @@ -5,15 +5,15 @@ package build import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestBuild_FromContext(t *testing.T) { // setup types bID := int64(1) - want := &library.Build{ID: &bID} + want := &api.Build{ID: &bID} // setup context gin.SetMode(gin.TestMode) @@ -72,7 +72,7 @@ func TestBuild_FromContext_Empty(t *testing.T) { func TestBuild_ToContext(t *testing.T) { // setup types bID := int64(1) - want := &library.Build{ID: &bID} + want := &api.Build{ID: &bID} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/claims/claims.go b/router/middleware/claims/claims.go index a81a456fe..e306f9e7e 100644 --- a/router/middleware/claims/claims.go +++ b/router/middleware/claims/claims.go @@ -6,12 +6,13 @@ import ( "net/http" "strings" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/auth" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - - "github.com/gin-gonic/gin" ) // Retrieve gets the claims in the given context. @@ -22,7 +23,9 @@ func Retrieve(c *gin.Context) *token.Claims { // Establish sets the claims in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) tm := c.MustGet("token-manager").(*token.Manager) + // get the access token from the request at, err := auth.RetrieveAccessToken(c.Request) if err != nil { @@ -51,6 +54,13 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "claim_subject": claims.Subject, + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, claims) c.Next() } diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go index 2eb89b3bd..9d065df0f 100644 --- a/router/middleware/claims/claims_test.go +++ b/router/middleware/claims/claims_test.go @@ -13,11 +13,13 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/golang-jwt/jwt/v5" ) func TestClaims_Retrieve(t *testing.T) { @@ -50,19 +52,22 @@ func TestClaims_Retrieve(t *testing.T) { func TestClaims_Establish(t *testing.T) { // setup types - user := new(library.User) + user := new(api.User) user.SetID(1) user.SetName("foo") user.SetRefreshToken("fresh") user.SetToken("bar") - user.SetHash("baz") user.SetActive(true) user.SetAdmin(false) user.SetFavorites([]string{}) + build := new(api.Build) + build.SetID(1) + build.SetNumber(1) + build.SetSender("octocat") + tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerAuthTokenDuration: time.Minute * 20, @@ -112,7 +117,7 @@ func TestClaims_Establish(t *testing.T) { }, Mto: &token.MintTokenOpts{ Hostname: "host", - BuildID: 1, + Build: build, Repo: "foo/bar", TokenDuration: time.Minute * 35, TokenType: constants.WorkerBuildTokenType, @@ -194,6 +199,7 @@ func TestClaims_Establish(t *testing.T) { gin.SetMode(gin.TestMode) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(Establish()) engine.PUT(tt.Endpoint, func(c *gin.Context) { @@ -223,8 +229,7 @@ func TestClaims_Establish(t *testing.T) { func TestClaims_Establish_NoToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -235,6 +240,7 @@ func TestClaims_Establish_NoToken(t *testing.T) { context, engine := gin.CreateTestContext(resp) context.Request, _ = http.NewRequest(http.MethodGet, "/workers/host", nil) + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(Establish()) @@ -249,8 +255,7 @@ func TestClaims_Establish_NoToken(t *testing.T) { func TestClaims_Establish_BadToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -261,10 +266,9 @@ func TestClaims_Establish_BadToken(t *testing.T) { context, engine := gin.CreateTestContext(resp) context.Request, _ = http.NewRequest(http.MethodGet, "/workers/host", nil) - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("octocat") - u.SetHash("abc") // setup database db, err := database.NewTest() @@ -289,6 +293,7 @@ func TestClaims_Establish_BadToken(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn)) + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { c.Set("secret", "very-secret") }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) diff --git a/router/middleware/claims/context_test.go b/router/middleware/claims/context_test.go index 55bbf9241..b4f91baac 100644 --- a/router/middleware/claims/context_test.go +++ b/router/middleware/claims/context_test.go @@ -6,11 +6,11 @@ import ( "testing" "time" - "github.com/go-vela/server/internal/token" - "github.com/go-vela/types/constants" + "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" - "github.com/gin-gonic/gin" + "github.com/go-vela/server/internal/token" + "github.com/go-vela/types/constants" ) func TestClaims_FromContext(t *testing.T) { diff --git a/router/middleware/cli.go b/router/middleware/cli.go new file mode 100644 index 000000000..acc1972eb --- /dev/null +++ b/router/middleware/cli.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/urfave/cli/v2" + + cliMiddleware "github.com/go-vela/server/router/middleware/cli" +) + +// CLI is a middleware function that attaches the cli client +// to the context of every http.Request. +func CLI(cliCtx *cli.Context) gin.HandlerFunc { + return func(c *gin.Context) { + cliMiddleware.ToContext(c, cliCtx) + + c.Next() + } +} diff --git a/router/middleware/cli/context.go b/router/middleware/cli/context.go new file mode 100644 index 000000000..13952ddc2 --- /dev/null +++ b/router/middleware/cli/context.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +package cli + +import ( + "context" + + "github.com/urfave/cli/v2" +) + +const key = "cli" + +// Setter defines a context that enables setting values. +type Setter interface { + Set(string, interface{}) +} + +// FromContext returns the cli context associated with this context. +func FromContext(c context.Context) *cli.Context { + value := c.Value(key) + if value == nil { + return nil + } + + s, ok := value.(*cli.Context) + if !ok { + return nil + } + + return s +} + +// ToContext adds the cli context to this context if it supports +// the Setter interface. +func ToContext(c Setter, s *cli.Context) { + c.Set(key, s) +} diff --git a/router/middleware/cli/context_test.go b/router/middleware/cli/context_test.go new file mode 100644 index 000000000..4269f3c06 --- /dev/null +++ b/router/middleware/cli/context_test.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 + +package cli + +import ( + "testing" + + "github.com/gin-gonic/gin" + "github.com/urfave/cli/v2" +) + +func TestSettings_FromContext(t *testing.T) { + // setup types + want := &cli.Context{} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, want) + + // run test + got := FromContext(context) + + if got != want { + t.Errorf("FromContext is %v, want %v", got, want) + } +} + +func TestSettings_FromContext_Bad(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_FromContext_WrongType(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, 1) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_FromContext_Empty(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_ToContext(t *testing.T) { + // setup types + want := &cli.Context{} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := context.Value(key) + + if got != want { + t.Errorf("ToContext is %v, want %v", got, want) + } +} diff --git a/router/middleware/cli/doc.go b/router/middleware/cli/doc.go new file mode 100644 index 000000000..3bdc404c6 --- /dev/null +++ b/router/middleware/cli/doc.go @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package cli provides the ability for inserting +// Vela cli resources into or extracting Vela cli +// resources from the middleware chain for the API. +// +// Usage: +// +// import "github.com/go-vela/server/router/middleware/cli" +package cli diff --git a/router/middleware/allowlist_schedule_test.go b/router/middleware/cli_test.go similarity index 65% rename from router/middleware/allowlist_schedule_test.go rename to router/middleware/cli_test.go index 1461b8719..d94e68c75 100644 --- a/router/middleware/allowlist_schedule_test.go +++ b/router/middleware/cli_test.go @@ -9,12 +9,18 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/urfave/cli/v2" ) -func TestMiddleware_AllowlistSchedule(t *testing.T) { +func TestMiddleware_CLI(t *testing.T) { // setup types - got := []string{""} - want := []string{"foobar"} + want := &cli.Context{ + App: &cli.App{ + Name: "foo", + }, + } + + got := &cli.Context{} // setup context gin.SetMode(gin.TestMode) @@ -24,9 +30,9 @@ func TestMiddleware_AllowlistSchedule(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) // setup mock server - engine.Use(AllowlistSchedule(want)) + engine.Use(CLI(want)) engine.GET("/health", func(c *gin.Context) { - got = c.Value("allowlistschedule").([]string) + got = c.Value("cli").(*cli.Context) c.Status(http.StatusOK) }) @@ -35,10 +41,10 @@ func TestMiddleware_AllowlistSchedule(t *testing.T) { engine.ServeHTTP(context.Writer, context.Request) if resp.Code != http.StatusOK { - t.Errorf("AllowlistSchedule returned %v, want %v", resp.Code, http.StatusOK) + t.Errorf("CLI returned %v, want %v", resp.Code, http.StatusOK) } if !reflect.DeepEqual(got, want) { - t.Errorf("AllowlistSchedule is %v, want %v", got, want) + t.Errorf("CLI is %v, want %v", got, want) } } diff --git a/router/middleware/compiler.go b/router/middleware/compiler.go index 503040192..fbd382a4d 100644 --- a/router/middleware/compiler.go +++ b/router/middleware/compiler.go @@ -4,14 +4,20 @@ package middleware import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/compiler" + "github.com/go-vela/server/router/middleware/settings" ) // Compiler is a middleware function that initializes the compiler and // attaches to the context of every http.Request. -func Compiler(cli compiler.Engine) gin.HandlerFunc { +func Compiler(comp compiler.Engine) gin.HandlerFunc { return func(c *gin.Context) { - compiler.WithGinContext(c, cli) + s := settings.FromContext(c) + comp.SetSettings(s) + + compiler.WithGinContext(c, comp) + c.Next() } } diff --git a/router/middleware/compiler_test.go b/router/middleware/compiler_test.go index 8a64f5176..d018b06a9 100644 --- a/router/middleware/compiler_test.go +++ b/router/middleware/compiler_test.go @@ -9,29 +9,53 @@ import ( "reflect" "testing" - "github.com/go-vela/server/compiler" - "github.com/go-vela/server/compiler/native" - "github.com/gin-gonic/gin" - "github.com/urfave/cli/v2" + + "github.com/go-vela/server/api/types/settings" + "github.com/go-vela/server/compiler" + "github.com/go-vela/server/compiler/native" + sMiddleware "github.com/go-vela/server/router/middleware/settings" ) func TestMiddleware_CompilerNative(t *testing.T) { // setup types - var got compiler.Engine + defaultCloneImage := "target/vela-git" + wantCloneImage := "target/vela-git:latest" - want, _ := native.New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil)) + set := flag.NewFlagSet("", flag.ExitOnError) + set.String("clone-image", defaultCloneImage, "doc") + + want, _ := native.FromCLIContext(cli.NewContext(nil, set, nil)) + want.SetCloneImage(wantCloneImage) + + var got compiler.Engine + got, _ = native.FromCLIContext(cli.NewContext(nil, set, nil)) // setup context gin.SetMode(gin.TestMode) resp := httptest.NewRecorder() context, engine := gin.CreateTestContext(resp) + + engine.Use(func() gin.HandlerFunc { + return func(c *gin.Context) { + s := settings.Platform{ + Compiler: &settings.Compiler{}, + } + s.SetCloneImage(wantCloneImage) + + sMiddleware.ToContext(c, &s) + + c.Next() + } + }(), + ) + + engine.Use(Compiler(got)) + context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) - // setup mock server - engine.Use(Compiler(want)) engine.GET("/health", func(c *gin.Context) { got = compiler.FromContext(c) diff --git a/router/middleware/dashboard/context.go b/router/middleware/dashboard/context.go new file mode 100644 index 000000000..f0f973015 --- /dev/null +++ b/router/middleware/dashboard/context.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + + api "github.com/go-vela/server/api/types" +) + +const key = "dashboard" + +// Setter defines a context that enables setting values. +type Setter interface { + Set(string, interface{}) +} + +// FromContext returns the Dashboard associated with this context. +func FromContext(c context.Context) *api.Dashboard { + value := c.Value(key) + if value == nil { + return nil + } + + b, ok := value.(*api.Dashboard) + if !ok { + return nil + } + + return b +} + +// ToContext adds the Dashboard to this context if it supports +// the Setter interface. +func ToContext(c Setter, b *api.Dashboard) { + c.Set(key, b) +} diff --git a/router/middleware/dashboard/context_test.go b/router/middleware/dashboard/context_test.go new file mode 100644 index 000000000..abda97051 --- /dev/null +++ b/router/middleware/dashboard/context_test.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "testing" + + "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" +) + +func TestDashboard_FromContext(t *testing.T) { + // setup types + uuid := "c8da1302-07d6-11ea-882f-4893bca275b8" + want := &api.Dashboard{ID: &uuid} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, want) + + // run test + got := FromContext(context) + + if got != want { + t.Errorf("FromContext is %v, want %v", got, want) + } +} + +func TestDashboard_FromContext_Bad(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestDashboard_FromContext_WrongType(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, 1) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestDashboard_FromContext_Empty(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestDashboard_ToContext(t *testing.T) { + // setup types + uuid := "c8da1302-07d6-11ea-882f-4893bca275b8" + want := &api.Dashboard{ID: &uuid} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := context.Value(key) + + if got != want { + t.Errorf("ToContext is %v, want %v", got, want) + } +} diff --git a/router/middleware/dashboard/dashboard.go b/router/middleware/dashboard/dashboard.go new file mode 100644 index 000000000..e1b86335b --- /dev/null +++ b/router/middleware/dashboard/dashboard.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" +) + +// Retrieve gets the build in the given context. +func Retrieve(c *gin.Context) *api.Dashboard { + return FromContext(c) +} + +// Establish sets the build in the given context. +func Establish() gin.HandlerFunc { + return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + u := user.Retrieve(c) + ctx := c.Request.Context() + + id := util.PathParameter(c, "dashboard") + if len(id) == 0 { + userBoards := u.GetDashboards() + if len(userBoards) == 0 { + retErr := fmt.Errorf("no available dashboards for user %s", u.GetName()) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + id = userBoards[0] + } + + l.Debugf("reading dashboard %s", id) + + d, err := database.FromContext(c).GetDashboard(ctx, id) + if err != nil { + retErr := fmt.Errorf("unable to read dashboard %s: %w", id, err) + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + l = l.WithFields(logrus.Fields{ + "dashboard": d.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + + ToContext(c, d) + c.Next() + } +} diff --git a/router/middleware/dashboard/dashboard_test.go b/router/middleware/dashboard/dashboard_test.go new file mode 100644 index 000000000..39c6b6f5f --- /dev/null +++ b/router/middleware/dashboard/dashboard_test.go @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dashboard + +import ( + "context" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" +) + +func TestDashboard_Retrieve(t *testing.T) { + // setup types + want := new(api.Dashboard) + want.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := Retrieve(context) + + if got != want { + t.Errorf("Retrieve is %v, want %v", got, want) + } +} + +func TestDashboard_Establish(t *testing.T) { + // setup types + want := new(api.Dashboard) + want.SetID("c8da1302-07d6-11ea-882f-4893bca275b8") + want.SetName("vela") + want.SetCreatedAt(1) + want.SetCreatedBy("octocat") + want.SetUpdatedAt(1) + want.SetUpdatedBy("octokitty") + + wantRepo := new(api.DashboardRepo) + wantRepo.SetID(1) + wantRepo.SetName("go-vela/server") + wantRepo.SetBranches([]string{"main"}) + wantRepo.SetEvents([]string{"push"}) + + want.SetRepos([]*api.DashboardRepo{wantRepo}) + + wantAdmin := new(api.User) + wantAdmin.SetID(1) + wantAdmin.SetName("octocat") + wantAdmin.SetActive(true) + + want.SetAdmins([]*api.User{wantAdmin}) + + got := new(api.Dashboard) + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteDashboard(context.TODO(), want) + db.Close() + }() + + _, _ = db.CreateDashboard(context.TODO(), want) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/c8da1302-07d6-11ea-882f-4893bca275b8", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(Establish()) + engine.GET("/:dashboard", func(c *gin.Context) { + got = Retrieve(c) + + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(resp, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Establish is %v, want %v", got, want) + } +} + +func TestDashboard_Establish_NoDashboardParameter(t *testing.T) { + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + defer db.Close() + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "//test", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(Establish()) + engine.GET("/:dashboard/test", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusBadRequest { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusBadRequest) + } +} + +func TestDashboard_Establish_NoDashboard(t *testing.T) { + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + defer db.Close() + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/c8da1302-07d6-11ea-882f-4893bca275b8", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(Establish()) + engine.GET("/:dashboard", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusNotFound { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusNotFound) + } +} diff --git a/router/middleware/dashboard/doc.go b/router/middleware/dashboard/doc.go new file mode 100644 index 000000000..e12e9d7d5 --- /dev/null +++ b/router/middleware/dashboard/doc.go @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package dashboard provides the ability for inserting +// Vela dashboard resources into or extracting Vela dashboard +// resources from the middleware chain for the API. +// +// Usage: +// +// import "github.com/go-vela/server/router/middleware/dashboard" +package dashboard diff --git a/router/middleware/database.go b/router/middleware/database.go index 38755fa89..e5ce7ec98 100644 --- a/router/middleware/database.go +++ b/router/middleware/database.go @@ -4,6 +4,7 @@ package middleware import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" ) diff --git a/router/middleware/database_test.go b/router/middleware/database_test.go index 8b7cca49c..96a52c449 100644 --- a/router/middleware/database_test.go +++ b/router/middleware/database_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" ) diff --git a/router/middleware/default_repo_settings_test.go b/router/middleware/default_repo_settings_test.go index 1e5a1b744..75cc7a02a 100644 --- a/router/middleware/default_repo_settings_test.go +++ b/router/middleware/default_repo_settings_test.go @@ -8,9 +8,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/constants" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/constants" ) func TestMiddleware_DefaultRepoEvents(t *testing.T) { diff --git a/router/middleware/executors/context.go b/router/middleware/executors/context.go index d040fa73a..8fcd5cfa0 100644 --- a/router/middleware/executors/context.go +++ b/router/middleware/executors/context.go @@ -5,7 +5,7 @@ package executors import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "executors" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the executors associated with this context. -func FromContext(c context.Context) []library.Executor { +func FromContext(c context.Context) []api.Executor { value := c.Value(key) if value == nil { return nil } - e, ok := value.([]library.Executor) + e, ok := value.([]api.Executor) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) []library.Executor { // ToContext adds the executor to this context if it supports // the Setter interface. -func ToContext(c Setter, e []library.Executor) { +func ToContext(c Setter, e []api.Executor) { c.Set(key, e) } diff --git a/router/middleware/executors/context_test.go b/router/middleware/executors/context_test.go index 3cabdb687..b84b2598b 100644 --- a/router/middleware/executors/context_test.go +++ b/router/middleware/executors/context_test.go @@ -6,16 +6,16 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestExecutors_FromContext(t *testing.T) { // setup types eID := int64(1) - e := library.Executor{ID: &eID} - want := []library.Executor{e} + e := api.Executor{ID: &eID} + want := []api.Executor{e} // setup context gin.SetMode(gin.TestMode) @@ -74,8 +74,8 @@ func TestExecutors_FromContext_Empty(t *testing.T) { func TestExecutors_ToContext(t *testing.T) { // setup types eID := int64(1) - e := library.Executor{ID: &eID} - want := []library.Executor{e} + e := api.Executor{ID: &eID} + want := []api.Executor{e} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/executors/executor_test.go b/router/middleware/executors/executor_test.go index 66006b6e2..8ce9dc58a 100644 --- a/router/middleware/executors/executor_test.go +++ b/router/middleware/executors/executor_test.go @@ -6,16 +6,16 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestExecutors_Retrieve(t *testing.T) { // setup types eID := int64(1) - e := library.Executor{ID: &eID} - want := []library.Executor{e} + e := api.Executor{ID: &eID} + want := []api.Executor{e} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index 688434f07..21616d64a 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -12,23 +12,24 @@ import ( "time" "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" ) // Retrieve gets the executors in the given context. -func Retrieve(c *gin.Context) []library.Executor { +func Retrieve(c *gin.Context) []api.Executor { return FromContext(c) } // Establish sets the executors in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { - e := new([]library.Executor) + e := new([]api.Executor) b := build.Retrieve(c) ctx := c.Request.Context() diff --git a/router/middleware/header.go b/router/middleware/header.go index f04180bfd..a54d737f8 100644 --- a/router/middleware/header.go +++ b/router/middleware/header.go @@ -7,8 +7,9 @@ import ( "time" "github.com/gin-gonic/gin" + + "github.com/go-vela/server/internal" "github.com/go-vela/server/version" - "github.com/go-vela/types" ) // NoCache is a middleware function that appends headers @@ -24,16 +25,18 @@ func NoCache(c *gin.Context) { // for OPTIONS preflight requests and aborts then exits // the middleware chain and ends the request. func Options(c *gin.Context) { - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") + if len(m.Vela.WebAddress) > 0 { c.Header("Access-Control-Allow-Origin", m.Vela.WebAddress) c.Header("Access-Control-Allow-Credentials", "true") } + c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Access-Control-Max-Age", "86400") @@ -58,7 +61,7 @@ func Secure(c *gin.Context) { // CORS related requests. These are attached to actual requests // unlike the OPTIONS preflight requests. func Cors(c *gin.Context) { - m := c.MustGet("metadata").(*types.Metadata) + m := c.MustGet("metadata").(*internal.Metadata) c.Header("Access-Control-Allow-Origin", "*") diff --git a/router/middleware/header_test.go b/router/middleware/header_test.go index ab13f992a..bd6d41fc5 100644 --- a/router/middleware/header_test.go +++ b/router/middleware/header_test.go @@ -11,7 +11,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/types" + + "github.com/go-vela/server/internal" ) func TestMiddleware_NoCache(t *testing.T) { @@ -64,8 +65,8 @@ func TestMiddleware_Options(t *testing.T) { wantHeaders := "authorization, origin, content-type, accept" wantAllow := "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS" wantContentType := "application/json" - m := &types.Metadata{ - Vela: &types.Vela{ + m := &internal.Metadata{ + Vela: &internal.Vela{ Address: "http://localhost:8080", }, } @@ -120,8 +121,8 @@ func TestMiddleware_Options(t *testing.T) { func TestMiddleware_Options_InvalidMethod(t *testing.T) { // setup types - m := &types.Metadata{ - Vela: &types.Vela{ + m := &internal.Metadata{ + Vela: &internal.Vela{ Address: "http://localhost:8080", }, } @@ -178,8 +179,8 @@ func TestMiddleware_Cors(t *testing.T) { // setup types wantOrigin := "*" wantExposeHeaders := "link, x-total-count" - m := &types.Metadata{ - Vela: &types.Vela{ + m := &internal.Metadata{ + Vela: &internal.Vela{ Address: "http://localhost:8080", }, } diff --git a/router/middleware/hook/context.go b/router/middleware/hook/context.go new file mode 100644 index 000000000..b1fa95a4c --- /dev/null +++ b/router/middleware/hook/context.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +package hook + +import ( + "context" + + "github.com/go-vela/types/library" +) + +const key = "hook" + +// Setter defines a context that enables setting values. +type Setter interface { + Set(string, interface{}) +} + +// FromContext returns the Repo associated with this context. +func FromContext(c context.Context) *library.Hook { + value := c.Value(key) + if value == nil { + return nil + } + + r, ok := value.(*library.Hook) + if !ok { + return nil + } + + return r +} + +// ToContext adds the Repo to this context if it supports +// the Setter interface. +func ToContext(c Setter, r *library.Hook) { + c.Set(key, r) +} diff --git a/router/middleware/hook/context_test.go b/router/middleware/hook/context_test.go new file mode 100644 index 000000000..316632435 --- /dev/null +++ b/router/middleware/hook/context_test.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +package hook + +import ( + "testing" + + "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" +) + +func TestHook_FromContext(t *testing.T) { + // setup types + num := int64(1) + want := &library.Hook{ID: &num} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, want) + + // run test + got := FromContext(context) + + if got != want { + t.Errorf("FromContext is %v, want %v", got, want) + } +} + +func TestHook_FromContext_Bad(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestHook_FromContext_WrongType(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, 1) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestHook_FromContext_Empty(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestHook_ToContext(t *testing.T) { + // setup types + num := int64(1) + want := &library.Hook{ID: &num} + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := context.Value(key) + + if got != want { + t.Errorf("ToContext is %v, want %v", got, want) + } +} diff --git a/router/middleware/hook/doc.go b/router/middleware/hook/doc.go new file mode 100644 index 000000000..0b110262c --- /dev/null +++ b/router/middleware/hook/doc.go @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package hook provides the ability for inserting +// Vela hook resources into or extracting Vela hook +// resources from the middleware chain for the API. +// +// Usage: +// +// import "github.com/go-vela/server/router/middleware/hook" +package hook diff --git a/router/middleware/hook/hook.go b/router/middleware/hook/hook.go new file mode 100644 index 000000000..18d0c7c21 --- /dev/null +++ b/router/middleware/hook/hook.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +package hook + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/repo" + "github.com/go-vela/server/util" + "github.com/go-vela/types/library" +) + +// Retrieve gets the hook in the given context. +func Retrieve(c *gin.Context) *library.Hook { + return FromContext(c) +} + +// Establish sets the hook in the given context. +func Establish() gin.HandlerFunc { + return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + o := org.Retrieve(c) + r := repo.Retrieve(c) + ctx := c.Request.Context() + + if r == nil { + retErr := fmt.Errorf("repo %s/%s not found", o, util.PathParameter(c, "repo")) + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + hParam := util.PathParameter(c, "hook") + if len(hParam) == 0 { + retErr := fmt.Errorf("no hook parameter provided") + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + number, err := strconv.Atoi(hParam) + if err != nil { + retErr := fmt.Errorf("malformed hook parameter provided: %s", hParam) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + l.Debugf("reading hook %s/%d", r.GetFullName(), number) + + h, err := database.FromContext(c).GetHookForRepo(ctx, r, number) + if err != nil { + retErr := fmt.Errorf("unable to read hook %s/%d: %w", r.GetFullName(), number, err) + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + l = l.WithFields(logrus.Fields{ + "hook": h.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + + ToContext(c, h) + c.Next() + } +} diff --git a/router/middleware/hook/hook_test.go b/router/middleware/hook/hook_test.go new file mode 100644 index 000000000..fa3deef79 --- /dev/null +++ b/router/middleware/hook/hook_test.go @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 + +package hook + +import ( + "context" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/repo" + "github.com/go-vela/types/library" +) + +func TestHook_Retrieve(t *testing.T) { + // setup types + want := new(library.Hook) + want.SetID(1) + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := Retrieve(context) + + if got != want { + t.Errorf("Retrieve is %v, want %v", got, want) + } +} + +func TestHook_Establish(t *testing.T) { + // setup types + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + want := new(library.Hook) + want.SetID(1) + want.SetRepoID(1) + want.SetBuildID(0) + want.SetNumber(1) + want.SetSourceID("ok") + want.SetStatus("") + want.SetError("") + want.SetCreated(0) + want.SetHost("") + want.SetEvent("") + want.SetEventAction("") + want.SetBranch("") + want.SetError("") + want.SetStatus("") + want.SetLink("") + want.SetWebhookID(1) + + got := new(library.Hook) + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteRepo(context.TODO(), r) + _ = db.DeleteHook(context.TODO(), want) + db.Close() + }() + + _, _ = db.CreateRepo(context.TODO(), r) + _, _ = db.CreateHook(context.TODO(), want) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hooks/foo/bar/1", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(Establish()) + engine.GET("/hooks/:org/:repo/:hook", func(c *gin.Context) { + got = Retrieve(c) + + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Establish is %v, want %v", got, want) + } +} + +func TestHook_Establish_NoRepo(t *testing.T) { + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + defer db.Close() + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hooks/foo/bar/1", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(Establish()) + engine.GET("/hooks/:org/:repo/:hook", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusNotFound { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusNotFound) + } +} + +func TestHook_Establish_NoHookParameter(t *testing.T) { + // setup types + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteRepo(context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(context.TODO(), r) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hooks/foo/bar", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(Establish()) + engine.GET("/hooks/:org/:repo/:hook", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusBadRequest { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusBadRequest) + } +} + +func TestHook_Establish_InvalidHookParameter(t *testing.T) { + // setup types + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteRepo(context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(context.TODO(), r) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hooks/foo/bar/foo", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(Establish()) + engine.GET("/hooks/:org/:repo/:hook", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusBadRequest { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusBadRequest) + } +} + +func TestHook_Establish_NoHook(t *testing.T) { + // setup types + r := new(api.Repo) + r.SetID(1) + r.GetOwner().SetID(1) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteRepo(context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(context.TODO(), r) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hooks/foo/bar/1", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(Establish()) + engine.GET("/hooks/:org/:repo/:hook", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusNotFound { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusNotFound) + } +} diff --git a/router/middleware/logger.go b/router/middleware/logger.go index 4f85a397e..5b8be89c9 100644 --- a/router/middleware/logger.go +++ b/router/middleware/logger.go @@ -6,16 +6,22 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/router/middleware/build" + "github.com/go-vela/server/router/middleware/claims" + "github.com/go-vela/server/router/middleware/dashboard" + "github.com/go-vela/server/router/middleware/hook" "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/pipeline" "github.com/go-vela/server/router/middleware/repo" + "github.com/go-vela/server/router/middleware/schedule" "github.com/go-vela/server/router/middleware/service" "github.com/go-vela/server/router/middleware/step" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/router/middleware/worker" "github.com/go-vela/server/util" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // This file, in part, reproduces portions of @@ -45,11 +51,20 @@ func Logger(logger *logrus.Logger, timeFormat string) gin.HandlerFunc { // some evil middlewares modify this values path := util.EscapeValue(c.Request.URL.Path) - c.Next() + fields := logrus.Fields{ + "ip": util.EscapeValue(c.ClientIP()), + "path": path, + } - end := time.Now() + entry := logger.WithFields(fields) - latency := end.Sub(start) + // set the logger in the context so + // downstream handlers can use it + c.Set("logger", entry) + + c.Next() + + latency := time.Since(start) // prevent us from logging the health endpoint if c.Request.URL.Path != "/health" { @@ -72,6 +87,7 @@ func Logger(logger *logrus.Logger, timeFormat string) gin.HandlerFunc { build := build.Retrieve(c) if build != nil { fields["build"] = build.Number + fields["build_id"] = build.ID } org := org.Retrieve(c) @@ -79,29 +95,73 @@ func Logger(logger *logrus.Logger, timeFormat string) gin.HandlerFunc { fields["org"] = org } + pipeline := pipeline.Retrieve(c) + if pipeline != nil { + fields["pipeline_id"] = pipeline.ID + } + repo := repo.Retrieve(c) if repo != nil { fields["repo"] = repo.Name + fields["repo_id"] = repo.ID } service := service.Retrieve(c) if service != nil { fields["service"] = service.Number + fields["service_id"] = service.ID + } + + hook := hook.Retrieve(c) + if hook != nil { + fields["hook"] = hook.Number + fields["hook_id"] = hook.ID } step := step.Retrieve(c) if step != nil { fields["step"] = step.Number + fields["step_id"] = step.ID + } + + schedule := schedule.Retrieve(c) + if schedule != nil { + fields["schedule"] = schedule.Name + fields["schedule_id"] = schedule.ID + } + + dashboard := dashboard.Retrieve(c) + if dashboard != nil { + fields["dashboard"] = dashboard.Name + fields["dashboard_id"] = dashboard.ID } user := user.Retrieve(c) - if user != nil { + // we check to make sure user name is populated + // because when it's not a user token, we still + // inject an empty user object into the context + // which results in log entries with 'user: null' + if user != nil && user.GetName() != "" { fields["user"] = user.Name + fields["user_id"] = user.ID } worker := worker.Retrieve(c) if worker != nil { fields["worker"] = worker.Hostname + fields["worker_id"] = worker.ID + } + + // if there's no user or worker in the context + // of this request, we log claims subject + _, hasUser := fields["user"] + _, hasWorker := fields["worker"] + + if !hasUser && !hasWorker { + claims := claims.Retrieve(c) + if claims != nil { + fields["claims_subject"] = claims.Subject + } } entry := logger.WithFields(fields) @@ -144,6 +204,7 @@ func (f *ECSFormatter) Format(e *logrus.Entry) ([]byte, error) { for k, v := range e.Data { switch k { + // map fields attached to requests case "ip": data["client.ip"] = v case "latency": @@ -158,6 +219,11 @@ func (f *ECSFormatter) Format(e *logrus.Entry) ([]byte, error) { data["user_agent.name"] = v case "version": data["user_agent.version"] = v + + // map other fields + case "user": + data["user.name"] = v + default: extraData[k] = v } @@ -181,7 +247,7 @@ func (f *ECSFormatter) Format(e *logrus.Entry) ([]byte, error) { } jf := logrus.JSONFormatter{ - TimestampFormat: "2006-01-02T15:04:05.000Z0700", + TimestampFormat: time.RFC3339, // same as default in logrus FieldMap: ecsFieldMap, } diff --git a/router/middleware/logger_test.go b/router/middleware/logger_test.go index 9264ade1a..25e3dad14 100644 --- a/router/middleware/logger_test.go +++ b/router/middleware/logger_test.go @@ -14,6 +14,10 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/service" @@ -21,24 +25,22 @@ import ( "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/router/middleware/worker" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/test" ) func TestMiddleware_Logger(t *testing.T) { // setup types - b := new(library.Build) - b.SetID(1) - b.SetRepoID(1) - b.SetNumber(1) - - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + svc := new(library.Service) svc.SetID(1) svc.SetRepoID(1) @@ -53,12 +55,12 @@ func TestMiddleware_Logger(t *testing.T) { s.SetNumber(1) s.SetName("foo") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - w := new(library.Worker) + w := new(api.Worker) w.SetID(1) w.SetHostname("worker_0") w.SetAddress("localhost") @@ -161,17 +163,17 @@ func TestMiddleware_Logger_Error(t *testing.T) { func TestMiddleware_Logger_Sanitize(t *testing.T) { var logBody, logWant map[string]interface{} - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") logRepo, _ := json.Marshal(r) - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEmail("octocat@github.com") logBuild, _ := json.Marshal(b) diff --git a/router/middleware/metadata.go b/router/middleware/metadata.go index 67ed62157..d1d6122a2 100644 --- a/router/middleware/metadata.go +++ b/router/middleware/metadata.go @@ -5,12 +5,12 @@ package middleware import ( "github.com/gin-gonic/gin" - "github.com/go-vela/types" + "github.com/go-vela/server/internal" ) // Metadata is a middleware function that attaches the metadata // to the context of every http.Request. -func Metadata(m *types.Metadata) gin.HandlerFunc { +func Metadata(m *internal.Metadata) gin.HandlerFunc { return func(c *gin.Context) { c.Set("metadata", m) c.Next() diff --git a/router/middleware/metadata_test.go b/router/middleware/metadata_test.go index 0baaee36a..6e3161c96 100644 --- a/router/middleware/metadata_test.go +++ b/router/middleware/metadata_test.go @@ -8,15 +8,15 @@ import ( "reflect" "testing" - "github.com/go-vela/types" - "github.com/gin-gonic/gin" + + "github.com/go-vela/server/internal" ) func TestMiddleware_Metadata(t *testing.T) { // setup types - got := new(types.Metadata) - want := &types.Metadata{} + got := new(internal.Metadata) + want := &internal.Metadata{} // setup context gin.SetMode(gin.TestMode) @@ -28,7 +28,7 @@ func TestMiddleware_Metadata(t *testing.T) { // setup mock server engine.Use(Metadata(want)) engine.GET("/health", func(c *gin.Context) { - got = c.Value("metadata").(*types.Metadata) + got = c.Value("metadata").(*internal.Metadata) c.Status(http.StatusOK) }) diff --git a/router/middleware/org/org.go b/router/middleware/org/org.go index b53c223d8..78ccbe708 100644 --- a/router/middleware/org/org.go +++ b/router/middleware/org/org.go @@ -7,6 +7,8 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/util" ) @@ -18,7 +20,10 @@ func Retrieve(c *gin.Context) string { // Establish used to check if org param is used only. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + oParam := util.PathParameter(c, "org") + if len(oParam) == 0 { retErr := fmt.Errorf("no org parameter provided") util.HandleError(c, http.StatusBadRequest, retErr) @@ -26,8 +31,14 @@ func Establish() gin.HandlerFunc { return } - ToContext(c, oParam) + l = l.WithFields(logrus.Fields{ + "org": oParam, + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, oParam) c.Next() } } diff --git a/router/middleware/org/org_test.go b/router/middleware/org/org_test.go index 27021222f..2e5607ef0 100644 --- a/router/middleware/org/org_test.go +++ b/router/middleware/org/org_test.go @@ -10,8 +10,10 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/types/library" ) func TestOrg_Retrieve(t *testing.T) { @@ -33,9 +35,9 @@ func TestOrg_Retrieve(t *testing.T) { func TestOrg_Establish(t *testing.T) { // setup types - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -48,11 +50,6 @@ func TestOrg_Establish(t *testing.T) { r.SetPrivate(false) r.SetTrusted(false) r.SetActive(false) - r.SetAllowPull(false) - r.SetAllowPush(false) - r.SetAllowDeploy(false) - r.SetAllowTag(false) - r.SetAllowComment(false) want := "foo" got := "" @@ -78,6 +75,7 @@ func TestOrg_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org", func(c *gin.Context) { @@ -114,6 +112,7 @@ func TestOrg_Establish_NoOrgParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "//test", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/test", func(c *gin.Context) { diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index a8656a261..5206ea9a2 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -8,29 +8,24 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/constants" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/claims" - "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // MustPlatformAdmin ensures the user has admin access to the platform. func MustPlatformAdmin() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("verifying user %s is a platform admin", cl.Subject) + l.Debugf("verifying user %s is a platform admin", cl.Subject) switch { case cl.IsAdmin: @@ -38,11 +33,10 @@ func MustPlatformAdmin() gin.HandlerFunc { default: if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) { - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - "repo": cl.Repo, - "build": cl.BuildID, - }).Warnf("attempted access of admin endpoint with build token from %s", cl.Subject) + l.WithFields(logrus.Fields{ + "claims_repo": cl.Repo, + "claims_build": cl.BuildID, + }).Warnf("attempted access of admin endpoint with build token by %s", cl.Subject) } retErr := fmt.Errorf("user %s is not a platform admin", cl.Subject) @@ -56,14 +50,10 @@ func MustPlatformAdmin() gin.HandlerFunc { // MustWorkerRegisterToken ensures the token is a registration token retrieved by a platform admin. func MustWorkerRegisterToken() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("verifying user %s has a registration token for worker", cl.Subject) + l.Debugf("verifying user %s has a registration token for worker", cl.Subject) switch cl.TokenType { case constants.WorkerRegisterTokenType: @@ -89,20 +79,14 @@ func MustWorkerRegisterToken() gin.HandlerFunc { // MustWorkerAuthToken ensures the token is a worker auth token. func MustWorkerAuthToken() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "worker": cl.Subject, - }).Debugf("verifying worker %s has a valid auth token", cl.Subject) + l.Debugf("verifying worker %s has a valid auth token", cl.Subject) // global permissions bypass if cl.IsAdmin { - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("user %s has platform admin permissions", cl.Subject) + l.Debugf("user %s has platform admin permissions", cl.Subject) return } @@ -131,24 +115,18 @@ func MustWorkerAuthToken() gin.HandlerFunc { // MustBuildAccess ensures the token is a build token for the appropriate build. func MustBuildAccess() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) b := build.Retrieve(c) // global permissions bypass if cl.IsAdmin { - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - }).Debugf("user %s has platform admin permissions", cl.Subject) + l.Debugf("user %s has platform admin permissions", cl.Subject) return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "worker": cl.Subject, - }).Debugf("verifying worker %s has a valid build token", cl.Subject) + l.Debugf("verifying worker %s has a valid build token", cl.Subject) // validate token type and match build id in request with build id in token claims switch cl.TokenType { @@ -157,10 +135,9 @@ func MustBuildAccess() gin.HandlerFunc { return } - logrus.WithFields(logrus.Fields{ - "user": cl.Subject, - "repo": cl.Repo, - "build": cl.BuildID, + l.WithFields(logrus.Fields{ + "claims_repo": cl.Repo, + "claims_build": cl.BuildID, }).Warnf("build token for build %d attempted to be used for build %d by %s", cl.BuildID, b.GetID(), cl.Subject) fallthrough @@ -173,11 +150,47 @@ func MustBuildAccess() gin.HandlerFunc { } } +// MustIDRequestToken ensures the token is a valid ID request token for the appropriate build. +func MustIDRequestToken() gin.HandlerFunc { + return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + cl := claims.Retrieve(c) + b := build.Retrieve(c) + + logrus.Debugf("verifying worker %s has a valid build token", cl.Subject) + + // verify expected type + if !strings.EqualFold(cl.TokenType, constants.IDRequestTokenType) { + retErr := fmt.Errorf("invalid token: must provide a valid request ID token") + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + + // if build is not in a running state, then an ID token should not be needed + if !strings.EqualFold(b.GetStatus(), constants.StatusRunning) { + util.HandleError(c, http.StatusBadRequest, fmt.Errorf("invalid request")) + + return + } + + // verify expected build id + if b.GetID() != cl.BuildID { + l.WithFields(logrus.Fields{ + "claims_repo": cl.Repo, + "claims_build": cl.BuildID, + }).Warnf("request ID token for build %d attempted to be used for %s build %d by %s", cl.BuildID, b.GetStatus(), b.GetID(), cl.Subject) + + retErr := fmt.Errorf("invalid token") + util.HandleError(c, http.StatusUnauthorized, retErr) + } + } +} + // MustSecretAdmin ensures the user has admin access to the org, repo or team. -// -//nolint:funlen // ignore function length func MustSecretAdmin() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) u := user.Retrieve(c) e := util.PathParameter(c, "engine") @@ -190,29 +203,20 @@ func MustSecretAdmin() gin.HandlerFunc { // create log fields from API metadata fields := logrus.Fields{ - "engine": e, - "org": o, - "repo": n, - "type": t, - "user": u.GetName(), + "secret_engine": e, + "secret_org": o, + "secret_repo": n, + "secret_type": t, } // check if secret is a shared secret if strings.EqualFold(t, constants.SecretShared) { // update log fields from API metadata - fields = logrus.Fields{ - "engine": e, - "org": o, - "team": n, - "type": t, - "user": u.GetName(), - } + delete(fields, "repo") + fields["secret_team"] = n } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(fields) + logger := l.WithFields(fields) if u.GetAdmin() { return @@ -293,8 +297,7 @@ func MustSecretAdmin() gin.HandlerFunc { // check if user is accessing shared secrets in personal org if strings.EqualFold(o, u.GetName()) { logger.WithFields(logrus.Fields{ - "org": o, - "user": u.GetName(), + "secret_org": o, }).Debugf("skipping gathering teams for user %s with org %s", u.GetName(), o) return @@ -344,21 +347,12 @@ func MustSecretAdmin() gin.HandlerFunc { // MustAdmin ensures the user has admin access to the repo. func MustAdmin() gin.HandlerFunc { return func(c *gin.Context) { - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - - logger.Debugf("verifying user %s has 'admin' permissions for repo %s", u.GetName(), r.GetFullName()) + l.Debugf("verifying user %s has 'admin' permissions for repo %s", u.GetName(), r.GetFullName()) if u.GetAdmin() { return @@ -371,18 +365,9 @@ func MustAdmin() gin.HandlerFunc { // try again using the repo owner token // // https://docs.github.com/en/rest/reference/repos#get-repository-permissions-for-a-user - ro, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), r.GetOwner().GetToken(), r.GetOrg(), r.GetName()) if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) - if err != nil { - logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) + l.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } } @@ -403,21 +388,12 @@ func MustAdmin() gin.HandlerFunc { // MustWrite ensures the user has admin or write access to the repo. func MustWrite() gin.HandlerFunc { return func(c *gin.Context) { - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - - logger.Debugf("verifying user %s has 'write' permissions for repo %s", u.GetName(), r.GetFullName()) + l.Debugf("verifying user %s has 'write' permissions for repo %s", u.GetName(), r.GetFullName()) if u.GetAdmin() { return @@ -430,18 +406,9 @@ func MustWrite() gin.HandlerFunc { // try again using the repo owner token // // https://docs.github.com/en/rest/reference/repos#get-repository-permissions-for-a-user - ro, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), r.GetOwner().GetToken(), r.GetOrg(), r.GetName()) if err != nil { - logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) + l.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } } @@ -463,24 +430,15 @@ func MustWrite() gin.HandlerFunc { // MustRead ensures the user has admin, write or read access to the repo. func MustRead() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) - o := org.Retrieve(c) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logger := logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }) - // check if the repo visibility field is set to public if strings.EqualFold(r.GetVisibility(), constants.VisibilityPublic) { - logger.Debugf("skipping 'read' check for repo %s with %s visibility for user %s", r.GetFullName(), r.GetVisibility(), u.GetName()) + l.Debugf("skipping 'read' check for repo %s with %s visibility for user %s", r.GetFullName(), r.GetVisibility(), u.GetName()) return } @@ -499,7 +457,7 @@ func MustRead() gin.HandlerFunc { return } - logger.Debugf("verifying user %s has 'read' permissions for repo %s", u.GetName(), r.GetFullName()) + l.Debugf("verifying user %s has 'read' permissions for repo %s", u.GetName(), r.GetFullName()) // return if user is platform admin if u.GetAdmin() { @@ -513,18 +471,9 @@ func MustRead() gin.HandlerFunc { // try again using the repo owner token // // https://docs.github.com/en/rest/reference/repos#get-repository-permissions-for-a-user - ro, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) - if err != nil { - retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), r.GetOwner().GetToken(), r.GetOrg(), r.GetName()) if err != nil { - logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) + l.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } } diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index c2c9642e4..a39c3a1b2 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -11,6 +11,10 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/constants" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/build" @@ -20,9 +24,6 @@ import ( "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/scm/github" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/golang-jwt/jwt/v5" ) func TestPerm_MustPlatformAdmin(t *testing.T) { @@ -30,17 +31,15 @@ func TestPerm_MustPlatformAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) mto := &token.MintTokenOpts{ @@ -85,6 +84,7 @@ func TestPerm_MustPlatformAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -112,17 +112,15 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -167,6 +165,7 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -192,8 +191,7 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { func TestPerm_MustWorkerRegisterToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -218,6 +216,7 @@ func TestPerm_MustWorkerRegisterToken(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) @@ -239,17 +238,15 @@ func TestPerm_MustWorkerRegisterToken(t *testing.T) { func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("vela-worker") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) mto := &token.MintTokenOpts{ @@ -283,6 +280,7 @@ func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) @@ -306,8 +304,7 @@ func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) { func TestPerm_MustWorkerAuthToken(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -332,6 +329,7 @@ func TestPerm_MustWorkerAuthToken(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) engine.Use(user.Establish()) @@ -355,8 +353,7 @@ func TestPerm_MustWorkerAuth_ServerWorkerToken(t *testing.T) { // setup types secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, WorkerRegisterTokenDuration: time.Minute * 1, @@ -373,6 +370,7 @@ func TestPerm_MustWorkerAuth_ServerWorkerToken(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) @@ -397,30 +395,32 @@ func TestPerm_MustBuildAccess(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -455,6 +455,7 @@ func TestPerm_MustBuildAccess(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -483,30 +484,31 @@ func TestPerm_MustBuildAccess_PlatAdmin(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("admin") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -548,6 +550,7 @@ func TestPerm_MustBuildAccess_PlatAdmin(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -576,30 +579,35 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) + wB := new(api.Build) + wB.SetID(2) + tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 2, + Build: wB, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -634,6 +642,7 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -658,34 +667,321 @@ func TestPerm_MustBuildToken_WrongBuild(t *testing.T) { } } +func TestPerm_MustIDRequestToken(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + b.SetStatus(constants.StatusRunning) + b.SetCommit("456def") + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + Build: b, + Repo: r.GetFullName(), + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, err := tm.MintToken(mto) + if err != nil { + t.Errorf("unable to mint token: %v", err) + } + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + ctx := _context.TODO() + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) + + // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) + engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + s1 := httptest.NewServer(engine) + defer s1.Close() + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("MustIDRequestToken returned %v, want %v: %v", resp.Code, http.StatusOK, resp.Body.String()) + } +} + +func TestPerm_MustIDRequestToken_NotRunning(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + b.SetStatus(constants.StatusSuccess) + b.SetCommit("456def") + + u := new(api.User) + u.SetID(1) + u.SetName("admin") + u.SetToken("bar") + u.SetAdmin(true) + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + Build: b, + Repo: "foo/bar", + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, _ := tm.MintToken(mto) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + ctx := _context.TODO() + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + _ = db.DeleteUser(_context.TODO(), u) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + _, _ = db.CreateUser(_context.TODO(), u) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) + + // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) + engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + s1 := httptest.NewServer(engine) + defer s1.Close() + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusBadRequest { + t.Errorf("MustIDRequestToken returned %v, want %v", resp.Code, http.StatusOK) + } +} + +func TestPerm_MustIDRequestToken_WrongBuild(t *testing.T) { + // setup types + secret := "superSecret" + + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) + r.SetID(1) + r.SetOwner(owner) + r.SetHash("baz") + r.SetOrg("foo") + r.SetName("bar") + r.SetFullName("foo/bar") + r.SetVisibility("public") + + b := new(api.Build) + b.SetID(1) + b.SetRepo(r) + b.SetNumber(1) + + wB := new(api.Build) + wB.SetID(2) + + tm := &token.Manager{ + PrivateKeyHMAC: "123abc", + UserAccessTokenDuration: time.Minute * 5, + UserRefreshTokenDuration: time.Minute * 30, + } + + mto := &token.MintTokenOpts{ + Hostname: "foo/bar/456def", + Build: wB, + Repo: "foo/bar", + TokenDuration: time.Minute * 30, + TokenType: constants.IDRequestTokenType, + } + + tok, _ := tm.MintToken(mto) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + + ctx := _context.TODO() + + // setup database + db, err := database.NewTest() + if err != nil { + t.Errorf("unable to create test database engine: %v", err) + } + + defer func() { + _ = db.DeleteBuild(ctx, b) + _ = db.DeleteRepo(_context.TODO(), r) + db.Close() + }() + + _, _ = db.CreateRepo(_context.TODO(), r) + _, _ = db.CreateBuild(ctx, b) + + context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil) + context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) + + // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) + engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) + engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) + engine.Use(claims.Establish()) + engine.Use(user.Establish()) + engine.Use(org.Establish()) + engine.Use(repo.Establish()) + engine.Use(build.Establish()) + engine.Use(MustIDRequestToken()) + engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + s1 := httptest.NewServer(engine) + defer s1.Close() + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusBadRequest { + t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusBadRequest) + } +} + func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -720,6 +1016,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -745,30 +1042,32 @@ func TestPerm_MustSecretAdmin_BuildToken_Org(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -803,6 +1102,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Org(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -828,30 +1128,32 @@ func TestPerm_MustSecretAdmin_BuildToken_Shared(t *testing.T) { // setup types secret := "superSecret" - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } mto := &token.MintTokenOpts{ Hostname: "worker", - BuildID: 1, + Build: b, Repo: "foo/bar", TokenDuration: time.Minute * 30, TokenType: constants.WorkerBuildTokenType, @@ -886,6 +1188,7 @@ func TestPerm_MustSecretAdmin_BuildToken_Shared(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -912,26 +1215,27 @@ func TestPerm_MustAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -981,6 +1285,7 @@ func TestPerm_MustAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1010,26 +1315,27 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) mto := &token.MintTokenOpts{ @@ -1079,6 +1385,7 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1108,26 +1415,27 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1177,6 +1485,7 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1206,26 +1515,27 @@ func TestPerm_MustWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1275,6 +1585,7 @@ func TestPerm_MustWrite(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1304,26 +1615,27 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) mto := &token.MintTokenOpts{ @@ -1373,6 +1685,7 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1402,26 +1715,27 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1471,6 +1785,7 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1500,26 +1815,27 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1569,6 +1885,7 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1598,26 +1915,27 @@ func TestPerm_MustRead(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1667,6 +1985,7 @@ func TestPerm_MustRead(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1696,26 +2015,27 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) mto := &token.MintTokenOpts{ @@ -1765,6 +2085,7 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1794,31 +2115,33 @@ func TestPerm_MustRead_WorkerBuildToken(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) mto := &token.MintTokenOpts{ Hostname: "worker", TokenDuration: time.Minute * 35, TokenType: constants.WorkerBuildTokenType, - BuildID: 1, + Build: b, Repo: "foo/bar", } @@ -1851,6 +2174,7 @@ func TestPerm_MustRead_WorkerBuildToken(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1880,26 +2204,27 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -1949,6 +2274,7 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -1978,26 +2304,27 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -2047,6 +2374,7 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -2076,26 +2404,27 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -2145,6 +2474,7 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -2174,26 +2504,27 @@ func TestPerm_MustRead_NotRead(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("private") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foob") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(false) mto := &token.MintTokenOpts{ @@ -2243,6 +2574,7 @@ func TestPerm_MustRead_NotRead(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) diff --git a/router/middleware/pipeline/context_test.go b/router/middleware/pipeline/context_test.go index d9379d943..4ec7c6607 100644 --- a/router/middleware/pipeline/context_test.go +++ b/router/middleware/pipeline/context_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/go-vela/types/library" ) diff --git a/router/middleware/pipeline/pipeline.go b/router/middleware/pipeline/pipeline.go index 61f308854..162c80ea5 100644 --- a/router/middleware/pipeline/pipeline.go +++ b/router/middleware/pipeline/pipeline.go @@ -7,16 +7,17 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/compiler" "github.com/go-vela/server/database" - "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/internal" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/util" - "github.com/go-vela/types" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the pipeline in the given context. @@ -27,7 +28,7 @@ func Retrieve(c *gin.Context) *library.Pipeline { // Establish sets the pipeline in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { - o := org.Retrieve(c) + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) u := user.Retrieve(c) ctx := c.Request.Context() @@ -51,15 +52,7 @@ func Establish() gin.HandlerFunc { entry := fmt.Sprintf("%s/%s", r.GetFullName(), p) - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "pipeline": p, - "repo": r.GetName(), - "user": u.GetName(), - }).Debugf("reading pipeline %s", entry) + l.Debugf("reading pipeline %s", entry) pipeline, err := database.FromContext(c).GetPipelineForRepo(ctx, p, r) if err != nil { // assume the pipeline doesn't exist in the database yet (before pipeline support was added) @@ -73,13 +66,15 @@ func Establish() gin.HandlerFunc { return } + b := new(api.Build) + b.SetRepo(r) + // parse and compile the pipeline configuration file _, pipeline, err = compiler.FromContext(c). Duplicate(). WithCommit(p). - WithMetadata(c.MustGet("metadata").(*types.Metadata)). - WithRepo(r). - WithUser(u). + WithMetadata(c.MustGet("metadata").(*internal.Metadata)). + WithBuild(b). Compile(config) if err != nil { retErr := fmt.Errorf("unable to compile pipeline configuration for %s: %w", entry, err) @@ -90,8 +85,15 @@ func Establish() gin.HandlerFunc { } } - ToContext(c, pipeline) + l = l.WithFields(logrus.Fields{ + "pipeline": pipeline.GetCommit(), + "pipeline_id": pipeline.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, pipeline) c.Next() } } diff --git a/router/middleware/pipeline/pipeline_test.go b/router/middleware/pipeline/pipeline_test.go index 545edb338..1408d0dea 100644 --- a/router/middleware/pipeline/pipeline_test.go +++ b/router/middleware/pipeline/pipeline_test.go @@ -13,9 +13,14 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/compiler" "github.com/go-vela/server/compiler/native" "github.com/go-vela/server/database" + "github.com/go-vela/server/internal" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/router/middleware/org" @@ -23,11 +28,8 @@ import ( "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/scm" "github.com/go-vela/server/scm/github" - "github.com/go-vela/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/golang-jwt/jwt/v5" - "github.com/urfave/cli/v2" ) func TestPipeline_Retrieve(t *testing.T) { @@ -66,9 +68,12 @@ func TestPipeline_Retrieve(t *testing.T) { func TestPipeline_Establish(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -117,6 +122,7 @@ func TestPipeline_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar/48afb5bdc41ad69bf22588491333f7cf71135163", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -155,6 +161,7 @@ func TestPipeline_Establish_NoRepo(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar/48afb5bdc41ad69bf22588491333f7cf71135163", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) @@ -168,9 +175,12 @@ func TestPipeline_Establish_NoRepo(t *testing.T) { func TestPipeline_Establish_NoPipelineParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -198,6 +208,7 @@ func TestPipeline_Establish_NoPipelineParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -219,43 +230,44 @@ func TestPipeline_Establish_NoPipeline(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") u.SetToken("bar") - u.SetHash("baz") u.SetAdmin(true) - m := &types.Metadata{ - Database: &types.Database{ + m := &internal.Metadata{ + Database: &internal.Database{ Driver: "foo", Host: "foo", }, - Queue: &types.Queue{ + Queue: &internal.Queue{ Channel: "foo", Driver: "foo", Host: "foo", }, - Source: &types.Source{ + Source: &internal.Source{ Driver: "foo", Host: "foo", }, - Vela: &types.Vela{ + Vela: &internal.Vela{ Address: "foo", WebAddress: "foo", }, @@ -275,7 +287,7 @@ func TestPipeline_Establish_NoPipeline(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("clone-image", "target/vela-git:latest", "doc") - comp, err := native.New(cli.NewContext(nil, set, nil)) + comp, err := native.FromCLIContext(cli.NewContext(nil, set, nil)) if err != nil { t.Errorf("unable to create compiler: %v", err) } @@ -317,6 +329,7 @@ func TestPipeline_Establish_NoPipeline(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("metadata", m) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) diff --git a/router/middleware/queue.go b/router/middleware/queue.go index 0ebbe72ca..eff077aa0 100644 --- a/router/middleware/queue.go +++ b/router/middleware/queue.go @@ -4,14 +4,20 @@ package middleware import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/queue" + "github.com/go-vela/server/router/middleware/settings" ) // Queue is a middleware function that initializes the queue and // attaches to the context of every http.Request. func Queue(q queue.Service) gin.HandlerFunc { return func(c *gin.Context) { + s := settings.FromContext(c) + q.SetSettings(s) + queue.WithGinContext(c, q) + c.Next() } } diff --git a/router/middleware/queue_test.go b/router/middleware/queue_test.go index 2f6a05fa6..af42c5422 100644 --- a/router/middleware/queue_test.go +++ b/router/middleware/queue_test.go @@ -8,10 +8,10 @@ import ( "reflect" "testing" + "github.com/gin-gonic/gin" + "github.com/go-vela/server/queue" "github.com/go-vela/server/queue/redis" - - "github.com/gin-gonic/gin" ) func TestMiddleware_Queue(t *testing.T) { diff --git a/router/middleware/repo/context.go b/router/middleware/repo/context.go index 620aaf0c8..73c0db4f1 100644 --- a/router/middleware/repo/context.go +++ b/router/middleware/repo/context.go @@ -5,7 +5,7 @@ package repo import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "repo" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the Repo associated with this context. -func FromContext(c context.Context) *library.Repo { +func FromContext(c context.Context) *api.Repo { value := c.Value(key) if value == nil { return nil } - r, ok := value.(*library.Repo) + r, ok := value.(*api.Repo) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) *library.Repo { // ToContext adds the Repo to this context if it supports // the Setter interface. -func ToContext(c Setter, r *library.Repo) { +func ToContext(c Setter, r *api.Repo) { c.Set(key, r) } diff --git a/router/middleware/repo/context_test.go b/router/middleware/repo/context_test.go index ebcadf01e..2dd3c8ab8 100644 --- a/router/middleware/repo/context_test.go +++ b/router/middleware/repo/context_test.go @@ -5,15 +5,15 @@ package repo import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestRepo_FromContext(t *testing.T) { // setup types num := int64(1) - want := &library.Repo{ID: &num} + want := &api.Repo{ID: &num} // setup context gin.SetMode(gin.TestMode) @@ -72,7 +72,7 @@ func TestRepo_FromContext_Empty(t *testing.T) { func TestRepo_ToContext(t *testing.T) { // setup types num := int64(1) - want := &library.Repo{ID: &num} + want := &api.Repo{ID: &num} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/repo/repo.go b/router/middleware/repo/repo.go index 748a17bb7..bbc678660 100644 --- a/router/middleware/repo/repo.go +++ b/router/middleware/repo/repo.go @@ -7,24 +7,24 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/org" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the repo in the given context. -func Retrieve(c *gin.Context) *library.Repo { +func Retrieve(c *gin.Context) *api.Repo { return FromContext(c) } // Establish sets the repo in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) o := org.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() rParam := util.PathParameter(c, "repo") @@ -35,14 +35,7 @@ func Establish() gin.HandlerFunc { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": rParam, - "user": u.GetName(), - }).Debugf("reading repo %s/%s", o, rParam) + l.Debugf("reading repo %s", rParam) r, err := database.FromContext(c).GetRepoForOrg(ctx, o, rParam) if err != nil { @@ -52,6 +45,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "repo": r.GetName(), + "repo_id": r.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, r) c.Next() } diff --git a/router/middleware/repo/repo_test.go b/router/middleware/repo/repo_test.go index 9ae835eae..c20067c62 100644 --- a/router/middleware/repo/repo_test.go +++ b/router/middleware/repo/repo_test.go @@ -9,16 +9,18 @@ import ( "reflect" "testing" - "github.com/go-vela/server/router/middleware/org" - "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/types/library" + "github.com/go-vela/server/database/testutils" + "github.com/go-vela/server/router/middleware/org" ) func TestRepo_Retrieve(t *testing.T) { // setup types - want := new(library.Repo) + want := new(api.Repo) want.SetID(1) // setup context @@ -36,9 +38,15 @@ func TestRepo_Retrieve(t *testing.T) { func TestRepo_Establish(t *testing.T) { // setup types - want := new(library.Repo) + owner := testutils.APIUser().Crop() + owner.SetID(1) + owner.SetName("foo") + owner.SetActive(false) + owner.SetToken("bar") + + want := new(api.Repo) want.SetID(1) - want.SetUserID(1) + want.SetOwner(owner) want.SetHash("baz") want.SetOrg("foo") want.SetName("bar") @@ -54,17 +62,12 @@ func TestRepo_Establish(t *testing.T) { want.SetPrivate(false) want.SetTrusted(false) want.SetActive(false) - want.SetAllowPull(false) - want.SetAllowPush(false) - want.SetAllowDeploy(false) - want.SetAllowTag(false) - want.SetAllowComment(false) - want.SetAllowEvents(library.NewEventsFromMask(1)) + want.SetAllowEvents(api.NewEventsFromMask(1)) want.SetPipelineType("yaml") want.SetPreviousName("") want.SetApproveBuild("") - got := new(library.Repo) + got := new(api.Repo) // setup database db, err := database.NewTest() @@ -74,9 +77,11 @@ func TestRepo_Establish(t *testing.T) { defer func() { _ = db.DeleteRepo(context.TODO(), want) + _ = db.DeleteUser(context.TODO(), owner) db.Close() }() + _, _ = db.CreateUser(context.TODO(), owner) _, _ = db.CreateRepo(context.TODO(), want) // setup context @@ -87,6 +92,7 @@ func TestRepo_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(Establish()) @@ -124,6 +130,7 @@ func TestRepo_Establish_NoOrgParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "//bar/test", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/:repo/test", func(c *gin.Context) { @@ -154,6 +161,7 @@ func TestRepo_Establish_NoRepoParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo//test", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/:repo/test", func(c *gin.Context) { @@ -184,6 +192,7 @@ func TestRepo_Establish_NoRepo(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/:repo", func(c *gin.Context) { diff --git a/router/middleware/schedule/context.go b/router/middleware/schedule/context.go index 258c243ca..5c8920d01 100644 --- a/router/middleware/schedule/context.go +++ b/router/middleware/schedule/context.go @@ -5,7 +5,7 @@ package schedule import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "schedule" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the Schedule associated with this context. -func FromContext(c context.Context) *library.Schedule { +func FromContext(c context.Context) *api.Schedule { value := c.Value(key) if value == nil { return nil } - s, ok := value.(*library.Schedule) + s, ok := value.(*api.Schedule) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) *library.Schedule { // ToContext adds the Schedule to this context if it supports // the Setter interface. -func ToContext(c Setter, s *library.Schedule) { +func ToContext(c Setter, s *api.Schedule) { c.Set(key, s) } diff --git a/router/middleware/schedule/context_test.go b/router/middleware/schedule/context_test.go index c75974fa0..f0f841135 100644 --- a/router/middleware/schedule/context_test.go +++ b/router/middleware/schedule/context_test.go @@ -6,13 +6,14 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" ) func TestSchedule_FromContext(t *testing.T) { // setup types num := int64(1) - want := &library.Schedule{ID: &num} + want := &api.Schedule{ID: &num} // setup context gin.SetMode(gin.TestMode) @@ -71,7 +72,7 @@ func TestSchedule_FromContext_Empty(t *testing.T) { func TestSchedule_ToContext(t *testing.T) { // setup types num := int64(1) - want := &library.Schedule{ID: &num} + want := &api.Schedule{ID: &num} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/schedule/schedule.go b/router/middleware/schedule/schedule.go index 59de83e41..ff617bc3a 100644 --- a/router/middleware/schedule/schedule.go +++ b/router/middleware/schedule/schedule.go @@ -7,24 +7,24 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the schedule in the given context. -func Retrieve(c *gin.Context) *library.Schedule { +func Retrieve(c *gin.Context) *api.Schedule { return FromContext(c) } // Establish sets the schedule in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() sParam := util.PathParameter(c, "schedule") @@ -35,14 +35,7 @@ func Establish() gin.HandlerFunc { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - "user": u.GetName(), - }).Debugf("reading schedule %s for repo %s", sParam, r.GetFullName()) + l.Debugf("reading schedule %s", sParam) s, err := database.FromContext(c).GetScheduleForRepo(ctx, r, sParam) if err != nil { @@ -52,6 +45,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "schedule": s.GetName(), + "schedule_id": s.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, s) c.Next() } diff --git a/router/middleware/scm.go b/router/middleware/scm.go index 3f6ddee87..d6b945d2f 100644 --- a/router/middleware/scm.go +++ b/router/middleware/scm.go @@ -4,6 +4,7 @@ package middleware import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/scm" ) diff --git a/router/middleware/scm_test.go b/router/middleware/scm_test.go index 2c37a3de9..2000b0d5d 100644 --- a/router/middleware/scm_test.go +++ b/router/middleware/scm_test.go @@ -8,10 +8,10 @@ import ( "reflect" "testing" + "github.com/gin-gonic/gin" + "github.com/go-vela/server/scm" "github.com/go-vela/server/scm/github" - - "github.com/gin-gonic/gin" ) func TestMiddleware_Scm(t *testing.T) { diff --git a/router/middleware/secret_test.go b/router/middleware/secret_test.go index 544cd28cf..c5f54bb6d 100644 --- a/router/middleware/secret_test.go +++ b/router/middleware/secret_test.go @@ -8,11 +8,11 @@ import ( "reflect" "testing" + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" "github.com/go-vela/server/secret" "github.com/go-vela/server/secret/native" - - "github.com/gin-gonic/gin" ) func TestMiddleware_Secret(t *testing.T) { diff --git a/router/middleware/secure_cookie_test.go b/router/middleware/secure_cookie_test.go index c7ceb83b9..560086bc5 100644 --- a/router/middleware/secure_cookie_test.go +++ b/router/middleware/secure_cookie_test.go @@ -7,9 +7,8 @@ import ( "net/http/httptest" "testing" - "github.com/go-playground/assert/v2" - "github.com/gin-gonic/gin" + "github.com/go-playground/assert/v2" ) func TestCookie_SecureCookie(t *testing.T) { diff --git a/router/middleware/service/context_test.go b/router/middleware/service/context_test.go index 6751bc92f..8c1cc74f4 100644 --- a/router/middleware/service/context_test.go +++ b/router/middleware/service/context_test.go @@ -5,9 +5,9 @@ package service import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestService_FromContext(t *testing.T) { diff --git a/router/middleware/service/service.go b/router/middleware/service/service.go index 1d974f4e6..7089e22f7 100644 --- a/router/middleware/service/service.go +++ b/router/middleware/service/service.go @@ -8,14 +8,14 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the service in the given context. @@ -27,10 +27,10 @@ func Retrieve(c *gin.Context) *library.Service { func Establish() gin.HandlerFunc { return func(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() if r == nil { @@ -63,16 +63,7 @@ func Establish() gin.HandlerFunc { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "service": number, - "repo": r.GetName(), - "user": u.GetName(), - }).Debugf("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), number) + l.Debugf("reading service %d", number) s, err := database.FromContext(c).GetServiceForBuild(ctx, b, number) if err != nil { @@ -82,6 +73,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "service": s.GetName(), + "service_id": s.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, s) c.Next() } diff --git a/router/middleware/service/service_test.go b/router/middleware/service/service_test.go index fba880671..681975c36 100644 --- a/router/middleware/service/service_test.go +++ b/router/middleware/service/service_test.go @@ -10,6 +10,9 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/org" @@ -37,18 +40,21 @@ func TestService_Retrieve(t *testing.T) { func TestService_Establish(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) want := new(library.Service) @@ -95,6 +101,7 @@ func TestService_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -134,6 +141,7 @@ func TestService_Establish_NoRepo(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/:repo/builds/:build/services/:service", func(c *gin.Context) { @@ -150,9 +158,9 @@ func TestService_Establish_NoRepo(t *testing.T) { func TestService_Establish_NoBuild(t *testing.T) { // setup types - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -180,6 +188,7 @@ func TestService_Establish_NoBuild(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -198,18 +207,21 @@ func TestService_Establish_NoBuild(t *testing.T) { func TestService_Establish_NoServiceParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -235,6 +247,7 @@ func TestService_Establish_NoServiceParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -254,18 +267,21 @@ func TestService_Establish_NoServiceParameter(t *testing.T) { func TestService_Establish_InvalidServiceParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -291,6 +307,7 @@ func TestService_Establish_InvalidServiceParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services/foo", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -310,18 +327,18 @@ func TestService_Establish_InvalidServiceParameter(t *testing.T) { func TestService_Establish_NoService(t *testing.T) { // setup types - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.GetOwner().SetID(1) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -347,6 +364,7 @@ func TestService_Establish_NoService(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/services/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) diff --git a/router/middleware/settings.go b/router/middleware/settings.go new file mode 100644 index 000000000..1e985bf73 --- /dev/null +++ b/router/middleware/settings.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/types/settings" + sMiddleware "github.com/go-vela/server/router/middleware/settings" +) + +// Settings is a middleware function that attaches settings +// to the context of every http.Request. +func Settings(s *settings.Platform) gin.HandlerFunc { + return func(c *gin.Context) { + sMiddleware.ToContext(c, s) + + c.Next() + } +} diff --git a/router/middleware/settings/context.go b/router/middleware/settings/context.go new file mode 100644 index 000000000..f5aa5fb62 --- /dev/null +++ b/router/middleware/settings/context.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "context" + + "github.com/go-vela/server/api/types/settings" +) + +const key = "settings" + +// Setter defines a context that enables setting values. +type Setter interface { + Set(string, interface{}) +} + +// FromContext returns the Settings associated with this context. +func FromContext(c context.Context) *settings.Platform { + value := c.Value(key) + if value == nil { + return nil + } + + s, ok := value.(*settings.Platform) + if !ok { + return nil + } + + return s +} + +// ToContext adds the Settings to this context if it supports +// the Setter interface. +func ToContext(c Setter, s *settings.Platform) { + c.Set(key, s) +} diff --git a/router/middleware/settings/context_test.go b/router/middleware/settings/context_test.go new file mode 100644 index 000000000..0c17b4297 --- /dev/null +++ b/router/middleware/settings/context_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 + +package settings + +import ( + "testing" + + "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/types/settings" +) + +func TestSettings_FromContext(t *testing.T) { + // setup types + num := int64(1) + cloneImage := "target/vela-git" + + cs := settings.Compiler{ + CloneImage: &cloneImage, + } + + want := &settings.Platform{ + ID: &num, + Compiler: &cs, + } + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, want) + + // run test + got := FromContext(context) + + if got != want { + t.Errorf("FromContext is %v, want %v", got, want) + } +} + +func TestSettings_FromContext_Bad(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_FromContext_WrongType(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, 1) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_FromContext_Empty(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestSettings_ToContext(t *testing.T) { + // setup types + num := int64(1) + cloneImage := "target/vela-git" + + cs := settings.Compiler{ + CloneImage: &cloneImage, + } + + want := &settings.Platform{ + ID: &num, + Compiler: &cs, + } + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := context.Value(key) + + if got != want { + t.Errorf("ToContext is %v, want %v", got, want) + } +} diff --git a/router/middleware/settings/doc.go b/router/middleware/settings/doc.go new file mode 100644 index 000000000..2c83d65fc --- /dev/null +++ b/router/middleware/settings/doc.go @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package settings provides the ability for inserting +// Vela settings resources into or extracting Vela settings +// resources from the middleware chain for the API. +// +// Usage: +// +// import "github.com/go-vela/server/router/middleware/settings" +package settings diff --git a/router/middleware/allowlist_test.go b/router/middleware/settings_test.go similarity index 60% rename from router/middleware/allowlist_test.go rename to router/middleware/settings_test.go index 2bc59623d..d0b5d2b58 100644 --- a/router/middleware/allowlist_test.go +++ b/router/middleware/settings_test.go @@ -9,12 +9,16 @@ import ( "testing" "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/types/settings" ) -func TestMiddleware_Allowlist(t *testing.T) { +func TestMiddleware_Settings(t *testing.T) { // setup types - got := []string{""} - want := []string{"foobar"} + want := settings.PlatformMockEmpty() + want.SetCloneImage("target/vela-git") + + got := settings.PlatformMockEmpty() // setup context gin.SetMode(gin.TestMode) @@ -24,9 +28,9 @@ func TestMiddleware_Allowlist(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) // setup mock server - engine.Use(Allowlist(want)) + engine.Use(Settings(&want)) engine.GET("/health", func(c *gin.Context) { - got = c.Value("allowlist").([]string) + got = *c.Value("settings").(*settings.Platform) c.Status(http.StatusOK) }) @@ -35,10 +39,10 @@ func TestMiddleware_Allowlist(t *testing.T) { engine.ServeHTTP(context.Writer, context.Request) if resp.Code != http.StatusOK { - t.Errorf("Secret returned %v, want %v", resp.Code, http.StatusOK) + t.Errorf("Settings returned %v, want %v", resp.Code, http.StatusOK) } if !reflect.DeepEqual(got, want) { - t.Errorf("Secret is %v, want %v", got, want) + t.Errorf("Settings is %v, want %v", got, want) } } diff --git a/router/middleware/step/context_test.go b/router/middleware/step/context_test.go index 1b7932db2..c42feb41c 100644 --- a/router/middleware/step/context_test.go +++ b/router/middleware/step/context_test.go @@ -5,9 +5,9 @@ package step import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestStep_FromContext(t *testing.T) { diff --git a/router/middleware/step/step.go b/router/middleware/step/step.go index 89ba1aa1f..c1e31cf43 100644 --- a/router/middleware/step/step.go +++ b/router/middleware/step/step.go @@ -8,14 +8,14 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" - "github.com/go-vela/server/router/middleware/user" "github.com/go-vela/server/util" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the step in the given context. @@ -27,10 +27,10 @@ func Retrieve(c *gin.Context) *library.Step { func Establish() gin.HandlerFunc { return func(c *gin.Context) { // capture middleware values + l := c.MustGet("logger").(*logrus.Entry) b := build.Retrieve(c) o := org.Retrieve(c) r := repo.Retrieve(c) - u := user.Retrieve(c) ctx := c.Request.Context() if r == nil { @@ -63,16 +63,7 @@ func Establish() gin.HandlerFunc { return } - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "org": o, - "step": number, - "repo": r.GetName(), - "user": u.GetName(), - }).Debugf("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), number) + l.Debugf("reading step %d", number) s, err := database.FromContext(c).GetStepForBuild(ctx, b, number) if err != nil { @@ -82,6 +73,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "step": s.GetNumber(), + "step_id": s.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, s) c.Next() } diff --git a/router/middleware/step/step_test.go b/router/middleware/step/step_test.go index 8d9e19271..120170d04 100644 --- a/router/middleware/step/step_test.go +++ b/router/middleware/step/step_test.go @@ -10,6 +10,9 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/build" "github.com/go-vela/server/router/middleware/org" @@ -38,18 +41,21 @@ func TestStep_Retrieve(t *testing.T) { func TestStep_Establish(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) want := new(library.Step) @@ -69,6 +75,7 @@ func TestStep_Establish(t *testing.T) { want.SetHost("") want.SetRuntime("") want.SetDistribution("") + want.SetReportAs("") got := new(library.Step) @@ -97,6 +104,7 @@ func TestStep_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -136,6 +144,7 @@ func TestStep_Establish_NoRepo(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/:org/:repo/builds/:build/steps/:step", func(c *gin.Context) { @@ -152,9 +161,12 @@ func TestStep_Establish_NoRepo(t *testing.T) { func TestStep_Establish_NoBuild(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") @@ -182,6 +194,7 @@ func TestStep_Establish_NoBuild(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -200,18 +213,21 @@ func TestStep_Establish_NoBuild(t *testing.T) { func TestStep_Establish_NoStepParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -237,6 +253,7 @@ func TestStep_Establish_NoStepParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -256,18 +273,21 @@ func TestStep_Establish_NoStepParameter(t *testing.T) { func TestStep_Establish_InvalidStepParameter(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -293,6 +313,7 @@ func TestStep_Establish_InvalidStepParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps/foo", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) @@ -312,18 +333,21 @@ func TestStep_Establish_InvalidStepParameter(t *testing.T) { func TestStep_Establish_NoStep(t *testing.T) { // setup types - r := new(library.Repo) + owner := new(api.User) + owner.SetID(1) + + r := new(api.Repo) r.SetID(1) - r.SetUserID(1) + r.SetOwner(owner) r.SetHash("baz") r.SetOrg("foo") r.SetName("bar") r.SetFullName("foo/bar") r.SetVisibility("public") - b := new(library.Build) + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) // setup database @@ -349,6 +373,7 @@ func TestStep_Establish_NoStep(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/foo/bar/builds/1/steps/1", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(org.Establish()) engine.Use(repo.Establish()) diff --git a/router/middleware/token_manager_test.go b/router/middleware/token_manager_test.go index 9a26f9b43..6daee4e88 100644 --- a/router/middleware/token_manager_test.go +++ b/router/middleware/token_manager_test.go @@ -8,9 +8,9 @@ import ( "reflect" "testing" - "github.com/go-vela/server/internal/token" - "github.com/gin-gonic/gin" + + "github.com/go-vela/server/internal/token" ) func TestMiddleware_TokenManager(t *testing.T) { @@ -21,7 +21,7 @@ func TestMiddleware_TokenManager(t *testing.T) { var got *token.Manager want := new(token.Manager) - want.PrivateKey = "123abc" + want.PrivateKeyHMAC = "123abc" // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/user/context.go b/router/middleware/user/context.go index 0b4a54891..99a4fc3d7 100644 --- a/router/middleware/user/context.go +++ b/router/middleware/user/context.go @@ -5,7 +5,7 @@ package user import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "user" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the User associated with this context. -func FromContext(c context.Context) *library.User { +func FromContext(c context.Context) *api.User { value := c.Value(key) if value == nil { return nil } - u, ok := value.(*library.User) + u, ok := value.(*api.User) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) *library.User { // ToContext adds the User to this context if it supports // the Setter interface. -func ToContext(c Setter, u *library.User) { +func ToContext(c Setter, u *api.User) { c.Set(key, u) } diff --git a/router/middleware/user/context_test.go b/router/middleware/user/context_test.go index ced7cd896..29f400329 100644 --- a/router/middleware/user/context_test.go +++ b/router/middleware/user/context_test.go @@ -5,15 +5,15 @@ package user import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestUser_FromContext(t *testing.T) { // setup types uID := int64(1) - want := &library.User{ID: &uID} + want := &api.User{ID: &uID} // setup context gin.SetMode(gin.TestMode) @@ -72,7 +72,7 @@ func TestUser_FromContext_Empty(t *testing.T) { func TestUser_ToContext(t *testing.T) { // setup types uID := int64(1) - want := &library.User{ID: &uID} + want := &api.User{ID: &uID} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/user/user.go b/router/middleware/user/user.go index 71fdc1a2d..cc883fd41 100644 --- a/router/middleware/user/user.go +++ b/router/middleware/user/user.go @@ -6,31 +6,31 @@ import ( "net/http" "strings" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/util" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) // Retrieve gets the user in the given context. -func Retrieve(c *gin.Context) *library.User { +func Retrieve(c *gin.Context) *api.User { return FromContext(c) } // Establish sets the user in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) cl := claims.Retrieve(c) ctx := c.Request.Context() // if token is not a user token or claims were not retrieved, establish empty user to better handle nil checks if cl == nil || !strings.EqualFold(cl.TokenType, constants.UserAccessTokenType) { - u := new(library.User) + u := new(api.User) ToContext(c, u) c.Next() @@ -38,7 +38,7 @@ func Establish() gin.HandlerFunc { return } - logrus.Debugf("parsing user access token") + l.Debugf("parsing user access token") // lookup user in claims subject in the database u, err := database.FromContext(c).GetUserForName(ctx, cl.Subject) @@ -47,6 +47,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "user": u.GetName(), + "user_id": u.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, u) c.Next() } diff --git a/router/middleware/user/user_test.go b/router/middleware/user/user_test.go index d84a18847..28d7f7a3d 100644 --- a/router/middleware/user/user_test.go +++ b/router/middleware/user/user_test.go @@ -12,19 +12,20 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/internal/token" "github.com/go-vela/server/router/middleware/claims" "github.com/go-vela/server/scm" "github.com/go-vela/server/scm/github" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/golang-jwt/jwt/v5" ) func TestUser_Retrieve(t *testing.T) { // setup types - want := new(library.User) + want := new(api.User) want.SetID(1) // setup context @@ -46,23 +47,22 @@ func TestUser_Establish(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - want := new(library.User) + want := new(api.User) want.SetID(1) want.SetName("foo") want.SetRefreshToken("fresh") want.SetToken("bar") - want.SetHash("baz") want.SetActive(false) want.SetAdmin(false) want.SetFavorites([]string{}) + want.SetDashboards([]string{}) - got := new(library.User) + got := new(api.User) gin.SetMode(gin.TestMode) @@ -112,6 +112,7 @@ func TestUser_Establish(t *testing.T) { client, _ := github.NewTest(s.URL) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) @@ -143,8 +144,7 @@ func TestUser_Establish_NoToken(t *testing.T) { // setup types secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -163,6 +163,7 @@ func TestUser_Establish_NoToken(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/users/foo", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) @@ -182,15 +183,14 @@ func TestUser_Establish_DiffTokenType(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - want := new(library.User) + want := new(api.User) - got := new(library.User) + got := new(api.User) // setup context gin.SetMode(gin.TestMode) @@ -201,6 +201,7 @@ func TestUser_Establish_DiffTokenType(t *testing.T) { context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret)) // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) }) engine.Use(claims.Establish()) @@ -231,8 +232,7 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) { secret := "superSecret" tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } @@ -255,6 +255,7 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) { client, _ := github.NewTest("") // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(func(c *gin.Context) { scm.ToContext(c, client) }) @@ -273,13 +274,12 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) { func TestUser_Establish_NoUser(t *testing.T) { // setup types tm := &token.Manager{ - PrivateKey: "123abc", - SignMethod: jwt.SigningMethodHS256, + PrivateKeyHMAC: "123abc", UserAccessTokenDuration: time.Minute * 5, UserRefreshTokenDuration: time.Minute * 30, } - u := new(library.User) + u := new(api.User) u.SetID(1) u.SetName("foo") @@ -318,6 +318,7 @@ func TestUser_Establish_NoUser(t *testing.T) { client, _ := github.NewTest("") // setup vela mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { c.Set("secret", secret) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(func(c *gin.Context) { scm.ToContext(c, client) }) diff --git a/router/middleware/webhook_validation_test.go b/router/middleware/webhook_validation_test.go index cb8a65639..f70d1fbdc 100644 --- a/router/middleware/webhook_validation_test.go +++ b/router/middleware/webhook_validation_test.go @@ -7,9 +7,8 @@ import ( "net/http/httptest" "testing" - "github.com/go-playground/assert/v2" - "github.com/gin-gonic/gin" + "github.com/go-playground/assert/v2" ) func TestWebhook_WebhookValidation(t *testing.T) { diff --git a/router/middleware/worker/context.go b/router/middleware/worker/context.go index a7780a3e8..2e9772a9e 100644 --- a/router/middleware/worker/context.go +++ b/router/middleware/worker/context.go @@ -5,7 +5,7 @@ package worker import ( "context" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) const key = "worker" @@ -16,13 +16,13 @@ type Setter interface { } // FromContext returns the Worker associated with this context. -func FromContext(c context.Context) *library.Worker { +func FromContext(c context.Context) *api.Worker { value := c.Value(key) if value == nil { return nil } - w, ok := value.(*library.Worker) + w, ok := value.(*api.Worker) if !ok { return nil } @@ -32,6 +32,6 @@ func FromContext(c context.Context) *library.Worker { // ToContext adds the Worker to this context if it supports // the Setter interface. -func ToContext(c Setter, w *library.Worker) { +func ToContext(c Setter, w *api.Worker) { c.Set(key, w) } diff --git a/router/middleware/worker/context_test.go b/router/middleware/worker/context_test.go index 76efd687b..3eb58d513 100644 --- a/router/middleware/worker/context_test.go +++ b/router/middleware/worker/context_test.go @@ -5,15 +5,15 @@ package worker import ( "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + api "github.com/go-vela/server/api/types" ) func TestWorker_FromContext(t *testing.T) { // setup types num := int64(1) - want := &library.Worker{ID: &num} + want := &api.Worker{ID: &num} // setup context gin.SetMode(gin.TestMode) @@ -72,7 +72,7 @@ func TestWorker_FromContext_Empty(t *testing.T) { func TestWorker_ToContext(t *testing.T) { // setup types num := int64(1) - want := &library.Worker{ID: &num} + want := &api.Worker{ID: &num} // setup context gin.SetMode(gin.TestMode) diff --git a/router/middleware/worker/worker.go b/router/middleware/worker/worker.go index 11586af9c..628ffb9f3 100644 --- a/router/middleware/worker/worker.go +++ b/router/middleware/worker/worker.go @@ -7,20 +7,22 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" "github.com/go-vela/server/util" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Retrieve gets the worker in the given context. -func Retrieve(c *gin.Context) *library.Worker { +func Retrieve(c *gin.Context) *api.Worker { return FromContext(c) } // Establish sets the worker in the given context. func Establish() gin.HandlerFunc { return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) ctx := c.Request.Context() wParam := util.PathParameter(c, "worker") @@ -31,7 +33,7 @@ func Establish() gin.HandlerFunc { return } - logrus.Debugf("Reading worker %s", wParam) + l.Debugf("reading worker %s", wParam) w, err := database.FromContext(c).GetWorkerForHostname(ctx, wParam) if err != nil { @@ -41,6 +43,14 @@ func Establish() gin.HandlerFunc { return } + l = l.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + "worker_id": w.GetID(), + }) + + // update the logger with the new fields + c.Set("logger", l) + ToContext(c, w) c.Next() } diff --git a/router/middleware/worker/worker_test.go b/router/middleware/worker/worker_test.go index 2d3578cd0..6ea01df56 100644 --- a/router/middleware/worker/worker_test.go +++ b/router/middleware/worker/worker_test.go @@ -10,13 +10,15 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/database" - "github.com/go-vela/types/library" ) func TestWorker_Retrieve(t *testing.T) { // setup types - want := new(library.Worker) + want := new(api.Worker) want.SetID(1) // setup context @@ -34,7 +36,10 @@ func TestWorker_Retrieve(t *testing.T) { func TestWorker_Establish(t *testing.T) { // setup types - want := new(library.Worker) + b := new(api.Build) + b.SetID(1) + + want := new(api.Worker) want.SetID(1) want.SetHostname("worker_0") want.SetAddress("localhost") @@ -42,13 +47,13 @@ func TestWorker_Establish(t *testing.T) { want.SetActive(true) want.SetStatus("available") want.SetLastStatusUpdateAt(12345) - want.SetRunningBuildIDs([]string{}) + want.SetRunningBuilds([]*api.Build{b}) want.SetLastBuildStartedAt(12345) want.SetLastBuildFinishedAt(12345) want.SetLastCheckedIn(12345) want.SetBuildLimit(0) - got := new(library.Worker) + got := new(api.Worker) // setup database db, err := database.NewTest() @@ -71,6 +76,7 @@ func TestWorker_Establish(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/workers/worker_0", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/workers/:worker", func(c *gin.Context) { @@ -107,6 +113,7 @@ func TestWorker_Establish_NoWorkerParameter(t *testing.T) { context.Request, _ = http.NewRequest(http.MethodGet, "/workers/", nil) // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) engine.Use(func(c *gin.Context) { database.ToContext(c, db) }) engine.Use(Establish()) engine.GET("/workers/:worker", func(c *gin.Context) { diff --git a/router/middleware/worker_test.go b/router/middleware/worker_test.go index 7717d03c5..5f01dc8a4 100644 --- a/router/middleware/worker_test.go +++ b/router/middleware/worker_test.go @@ -37,10 +37,10 @@ func TestMiddleware_Worker(t *testing.T) { engine.ServeHTTP(context.Writer, context.Request) if resp.Code != http.StatusOK { - t.Errorf("Secret returned %v, want %v", resp.Code, http.StatusOK) + t.Errorf("Worker returned %v, want %v", resp.Code, http.StatusOK) } if !reflect.DeepEqual(got, want) { - t.Errorf("Secret is %v, want %v", got, want) + t.Errorf("Worker is %v, want %v", got, want) } } diff --git a/router/pipeline.go b/router/pipeline.go index 7d11a4e2e..fd982d8f7 100644 --- a/router/pipeline.go +++ b/router/pipeline.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/pipeline" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/perm" diff --git a/router/queue.go b/router/queue.go index ca408ffe3..5082a4284 100644 --- a/router/queue.go +++ b/router/queue.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/queue" "github.com/go-vela/server/router/middleware/perm" ) @@ -11,7 +12,7 @@ import ( // QueueHandlers is a function that extends the provided base router group // with the API handlers for queue registration functionality. // -// POST /api/v1/queue/register. +// GET /api/v1/queue/info . func QueueHandlers(base *gin.RouterGroup) { // Queue endpoints _queue := base.Group("/queue") diff --git a/router/repo.go b/router/repo.go index 0ed7d1be1..3cd7ecc9f 100644 --- a/router/repo.go +++ b/router/repo.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/build" "github.com/go-vela/server/api/repo" "github.com/go-vela/server/router/middleware" diff --git a/router/router.go b/router/router.go index 57f8e3d09..2e7aebbb2 100644 --- a/router/router.go +++ b/router/router.go @@ -31,6 +31,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api" "github.com/go-vela/server/api/auth" "github.com/go-vela/server/api/webhook" @@ -88,6 +89,10 @@ func Load(options ...gin.HandlerFunc) *gin.Engine { // Webhook endpoint r.POST("/webhook", webhook.PostWebhook) + // JWKS endpoints + r.GET("/_services/token/.well-known/openid-configuration", api.GetOpenIDConfig) + r.GET("/_services/token/.well-known/jwks", api.GetJWKS) + // Authentication endpoints authenticate := r.Group("/authenticate") { @@ -123,8 +128,8 @@ func Load(options ...gin.HandlerFunc) *gin.Engine { // Source code management endpoints ScmHandlers(baseAPI) - // Search endpoints - SearchHandlers(baseAPI) + // Dashboard endpoints + DashboardHandlers(baseAPI) // Secret endpoints SecretHandlers(baseAPI) diff --git a/router/schedule.go b/router/schedule.go index 4a94f7737..49abaca33 100644 --- a/router/schedule.go +++ b/router/schedule.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/schedule" "github.com/go-vela/server/router/middleware" "github.com/go-vela/server/router/middleware/org" diff --git a/router/scm.go b/router/scm.go index 10c1cea81..1b0a7c96d 100644 --- a/router/scm.go +++ b/router/scm.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/scm" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" diff --git a/router/search.go b/router/search.go index f5679048d..d62db11b4 100644 --- a/router/search.go +++ b/router/search.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/build" ) diff --git a/router/secret.go b/router/secret.go index 419a0cab6..bb103ca5c 100644 --- a/router/secret.go +++ b/router/secret.go @@ -3,10 +3,10 @@ package router import ( + "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/secret" "github.com/go-vela/server/router/middleware/perm" - - "github.com/gin-gonic/gin" ) // SecretHandlers is a function that extends the provided base router group diff --git a/router/service.go b/router/service.go index 683a38b6f..48787d19d 100644 --- a/router/service.go +++ b/router/service.go @@ -5,6 +5,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/service" "github.com/go-vela/server/router/middleware" "github.com/go-vela/server/router/middleware/perm" diff --git a/router/step.go b/router/step.go index d26f70385..934e61d81 100644 --- a/router/step.go +++ b/router/step.go @@ -5,6 +5,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/step" "github.com/go-vela/server/router/middleware" "github.com/go-vela/server/router/middleware/perm" diff --git a/router/user.go b/router/user.go index 8f8e68fcc..149fe3146 100644 --- a/router/user.go +++ b/router/user.go @@ -4,6 +4,8 @@ package router import ( "github.com/gin-gonic/gin" + + "github.com/go-vela/server/api/dashboard" "github.com/go-vela/server/api/user" "github.com/go-vela/server/router/middleware/perm" ) @@ -20,7 +22,8 @@ import ( // PUT /api/v1/user // GET /api/v1/user/source/repos // POST /api/v1/user/token -// DELETE /api/v1/user/token . +// DELETE /api/v1/user/token +// GET /api/v1/user/dashboards . func UserHandlers(base *gin.RouterGroup) { // Users endpoints _users := base.Group("/users") @@ -40,5 +43,6 @@ func UserHandlers(base *gin.RouterGroup) { _user.GET("/source/repos", user.GetSourceRepos) _user.POST("/token", user.CreateToken) _user.DELETE("/token", user.DeleteToken) + _user.GET("/dashboards", dashboard.ListUserDashboards) } // end of user endpoints } diff --git a/router/worker.go b/router/worker.go index 9ab61ead0..648d1efdf 100644 --- a/router/worker.go +++ b/router/worker.go @@ -4,6 +4,7 @@ package router import ( "github.com/gin-gonic/gin" + "github.com/go-vela/server/api/worker" "github.com/go-vela/server/router/middleware" "github.com/go-vela/server/router/middleware/perm" diff --git a/scm/context_test.go b/scm/context_test.go index e98afa30c..6a54a456f 100644 --- a/scm/context_test.go +++ b/scm/context_test.go @@ -7,9 +7,9 @@ import ( "net/http/httptest" "testing" - "github.com/go-vela/server/scm/github" - "github.com/gin-gonic/gin" + + "github.com/go-vela/server/scm/github" ) func TestSCM_FromContext(t *testing.T) { diff --git a/scm/flags.go b/scm/flags.go index 45cb2d055..fecb944ce 100644 --- a/scm/flags.go +++ b/scm/flags.go @@ -3,8 +3,9 @@ package scm import ( - "github.com/go-vela/types/constants" "github.com/urfave/cli/v2" + + "github.com/go-vela/types/constants" ) // Flags represents all supported command line diff --git a/scm/github/access.go b/scm/github/access.go index ea2445a3b..4fd570497 100644 --- a/scm/github/access.go +++ b/scm/github/access.go @@ -6,14 +6,14 @@ import ( "context" "strings" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" - "github.com/go-vela/types/library" - "github.com/google/go-github/v59/github" + api "github.com/go-vela/server/api/types" ) // OrgAccess captures the user's access level for an org. -func (c *client) OrgAccess(ctx context.Context, u *library.User, org string) (string, error) { +func (c *client) OrgAccess(ctx context.Context, u *api.User, org string) (string, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "user": u.GetName(), @@ -81,7 +81,7 @@ func (c *client) RepoAccess(ctx context.Context, name, token, org, repo string) } // TeamAccess captures the user's access level for a team. -func (c *client) TeamAccess(ctx context.Context, u *library.User, org, team string) (string, error) { +func (c *client) TeamAccess(ctx context.Context, u *api.User, org, team string) (string, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "team": team, @@ -143,7 +143,7 @@ func (c *client) TeamAccess(ctx context.Context, u *library.User, org, team stri } // ListUsersTeamsForOrg captures the user's teams for an org. -func (c *client) ListUsersTeamsForOrg(ctx context.Context, u *library.User, org string) ([]string, error) { +func (c *client) ListUsersTeamsForOrg(ctx context.Context, u *api.User, org string) ([]string, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "user": u.GetName(), @@ -187,7 +187,7 @@ func (c *client) ListUsersTeamsForOrg(ctx context.Context, u *library.User, org } // RepoContributor lists all contributors from a repository and checks if the sender is one of the contributors. -func (c *client) RepoContributor(ctx context.Context, owner *library.User, sender, org, repo string) (bool, error) { +func (c *client) RepoContributor(ctx context.Context, owner *api.User, sender, org, repo string) (bool, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "repo": repo, diff --git a/scm/github/access_test.go b/scm/github/access_test.go index 77ba7e347..c730c1f4d 100644 --- a/scm/github/access_test.go +++ b/scm/github/access_test.go @@ -11,7 +11,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestGithub_OrgAccess_Admin(t *testing.T) { @@ -34,7 +34,7 @@ func TestGithub_OrgAccess_Admin(t *testing.T) { // setup types want := "admin" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -76,7 +76,7 @@ func TestGithub_OrgAccess_Member(t *testing.T) { // setup types want := "member" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -106,7 +106,7 @@ func TestGithub_OrgAccess_NotFound(t *testing.T) { // setup types want := "" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -144,7 +144,7 @@ func TestGithub_OrgAccess_Pending(t *testing.T) { // setup types want := "" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -174,7 +174,7 @@ func TestGithub_OrgAccess_Personal(t *testing.T) { // setup types want := "admin" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -212,7 +212,7 @@ func TestGithub_RepoAccess_Admin(t *testing.T) { // setup types want := "admin" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -242,7 +242,7 @@ func TestGithub_RepoAccess_NotFound(t *testing.T) { // setup types want := "" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -280,7 +280,7 @@ func TestGithub_TeamAccess_Admin(t *testing.T) { // setup types want := "admin" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -322,7 +322,7 @@ func TestGithub_TeamAccess_NoAccess(t *testing.T) { // setup types want := "" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -352,7 +352,7 @@ func TestGithub_TeamAccess_NotFound(t *testing.T) { // setup types want := "" - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -390,7 +390,7 @@ func TestGithub_TeamList(t *testing.T) { // setup types want := []string{"Justice League", "octocat"} - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -436,7 +436,7 @@ func TestGithub_RepoContributor(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") diff --git a/scm/github/authentication.go b/scm/github/authentication.go index 609790c77..074a39d04 100644 --- a/scm/github/authentication.go +++ b/scm/github/authentication.go @@ -10,9 +10,10 @@ import ( "net/url" "strings" + "github.com/google/go-github/v62/github" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/server/random" - "github.com/go-vela/types/library" - "github.com/google/go-github/v59/github" ) // Authorize uses the given access token to authorize the user. @@ -55,7 +56,7 @@ func (c *client) Login(ctx context.Context, w http.ResponseWriter, r *http.Reque // Authenticate completes the authentication workflow for the session // and returns the remote user details. -func (c *client) Authenticate(ctx context.Context, w http.ResponseWriter, r *http.Request, oAuthState string) (*library.User, error) { +func (c *client) Authenticate(ctx context.Context, w http.ResponseWriter, r *http.Request, oAuthState string) (*api.User, error) { c.Logger.Trace("authenticating user") // get the OAuth code @@ -88,7 +89,7 @@ func (c *client) Authenticate(ctx context.Context, w http.ResponseWriter, r *htt return nil, err } - return &library.User{ + return &api.User{ Name: &u, Token: &token.AccessToken, }, nil @@ -96,7 +97,7 @@ func (c *client) Authenticate(ctx context.Context, w http.ResponseWriter, r *htt // AuthenticateToken completes the authentication workflow // for the session and returns the remote user details. -func (c *client) AuthenticateToken(ctx context.Context, r *http.Request) (*library.User, error) { +func (c *client) AuthenticateToken(ctx context.Context, r *http.Request) (*api.User, error) { c.Logger.Trace("authenticating user via token") token := r.Header.Get("Token") @@ -119,7 +120,7 @@ func (c *client) AuthenticateToken(ctx context.Context, r *http.Request) (*libra return nil, err } - return &library.User{ + return &api.User{ Name: &u, Token: &token, }, nil diff --git a/scm/github/authentication_test.go b/scm/github/authentication_test.go index 47c524e4d..12b9b91b2 100644 --- a/scm/github/authentication_test.go +++ b/scm/github/authentication_test.go @@ -3,15 +3,15 @@ package github import ( + _context "context" "net/http" "net/http/httptest" "reflect" "testing" - _context "context" - "github.com/gin-gonic/gin" - "github.com/go-vela/types/library" + + api "github.com/go-vela/server/api/types" ) func TestGithub_Authenticate(t *testing.T) { @@ -38,7 +38,7 @@ func TestGithub_Authenticate(t *testing.T) { defer s.Close() // setup types - want := new(library.User) + want := new(api.User) want.SetName("octocat") want.SetToken("foo") @@ -326,7 +326,7 @@ func TestGithub_AuthenticateToken(t *testing.T) { defer s.Close() // setup types - want := new(library.User) + want := new(api.User) want.SetName("octocat") want.SetToken("foo") diff --git a/scm/github/changeset.go b/scm/github/changeset.go index a3ba3113b..770e603ab 100644 --- a/scm/github/changeset.go +++ b/scm/github/changeset.go @@ -6,22 +6,22 @@ import ( "context" "fmt" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" - "github.com/go-vela/types/library" - "github.com/google/go-github/v59/github" + api "github.com/go-vela/server/api/types" ) // Changeset captures the list of files changed for a commit. -func (c *client) Changeset(ctx context.Context, u *library.User, r *library.Repo, sha string) ([]string, error) { +func (c *client) Changeset(ctx context.Context, r *api.Repo, sha string) ([]string, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - "user": u.GetName(), + "user": r.GetOwner().GetName(), }).Tracef("capturing commit changeset for %s/commit/%s", r.GetFullName(), sha) // create GitHub OAuth client with user's token - client := c.newClientToken(u.GetToken()) + client := c.newClientToken(r.GetOwner().GetToken()) s := []string{} // set the max per page for the options to capture the commit @@ -42,15 +42,15 @@ func (c *client) Changeset(ctx context.Context, u *library.User, r *library.Repo } // ChangesetPR captures the list of files changed for a pull request. -func (c *client) ChangesetPR(ctx context.Context, u *library.User, r *library.Repo, number int) ([]string, error) { +func (c *client) ChangesetPR(ctx context.Context, r *api.Repo, number int) ([]string, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - "user": u.GetName(), + "user": r.GetOwner().GetName(), }).Tracef("capturing pull request changeset for %s/pull/%d", r.GetFullName(), number) // create GitHub OAuth client with user's token - client := c.newClientToken(u.GetToken()) + client := c.newClientToken(r.GetOwner().GetToken()) s := []string{} f := []*github.CommitFile{} diff --git a/scm/github/changeset_test.go b/scm/github/changeset_test.go index 2fefbad93..c3c411769 100644 --- a/scm/github/changeset_test.go +++ b/scm/github/changeset_test.go @@ -11,7 +11,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestGithub_Changeset(t *testing.T) { @@ -34,18 +34,19 @@ func TestGithub_Changeset(t *testing.T) { // setup types want := []string{"file1.txt"} - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("repos") r.SetName("octocat") + r.SetOwner(u) client, _ := NewTest(s.URL) // run test - got, err := client.Changeset(context.TODO(), u, r, "6dcb09b5b57875f334f61aebed695e2e4193db5e") + got, err := client.Changeset(context.TODO(), r, "6dcb09b5b57875f334f61aebed695e2e4193db5e") if resp.Code != http.StatusOK { t.Errorf("Changeset returned %v, want %v", resp.Code, http.StatusOK) @@ -80,18 +81,19 @@ func TestGithub_ChangesetPR(t *testing.T) { // setup types want := []string{"file1.txt"} - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("repos") r.SetName("octocat") + r.SetOwner(u) client, _ := NewTest(s.URL) // run test - got, err := client.ChangesetPR(context.TODO(), u, r, 1) + got, err := client.ChangesetPR(context.TODO(), r, 1) if resp.Code != http.StatusOK { t.Errorf("ChangesetPR returned %v, want %v", resp.Code, http.StatusOK) diff --git a/scm/github/deployment.go b/scm/github/deployment.go index 675503800..91357afa9 100644 --- a/scm/github/deployment.go +++ b/scm/github/deployment.go @@ -6,15 +6,16 @@ import ( "context" "encoding/json" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" - "github.com/google/go-github/v59/github" ) // GetDeployment gets a deployment from the GitHub repo. -func (c *client) GetDeployment(ctx context.Context, u *library.User, r *library.Repo, id int64) (*library.Deployment, error) { +func (c *client) GetDeployment(ctx context.Context, u *api.User, r *api.Repo, id int64) (*library.Deployment, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -55,7 +56,7 @@ func (c *client) GetDeployment(ctx context.Context, u *library.User, r *library. } // GetDeploymentCount counts a list of deployments from the GitHub repo. -func (c *client) GetDeploymentCount(ctx context.Context, u *library.User, r *library.Repo) (int64, error) { +func (c *client) GetDeploymentCount(ctx context.Context, u *api.User, r *api.Repo) (int64, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -97,7 +98,7 @@ func (c *client) GetDeploymentCount(ctx context.Context, u *library.User, r *lib } // GetDeploymentList gets a list of deployments from the GitHub repo. -func (c *client) GetDeploymentList(ctx context.Context, u *library.User, r *library.Repo, page, perPage int) ([]*library.Deployment, error) { +func (c *client) GetDeploymentList(ctx context.Context, u *api.User, r *api.Repo, page, perPage int) ([]*library.Deployment, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -155,11 +156,12 @@ func (c *client) GetDeploymentList(ctx context.Context, u *library.User, r *libr } // CreateDeployment creates a new deployment for the GitHub repo. -func (c *client) CreateDeployment(ctx context.Context, u *library.User, r *library.Repo, d *library.Deployment) error { +func (c *client) CreateDeployment(ctx context.Context, u *api.User, r *api.Repo, d *library.Deployment) error { c.Logger.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - "user": u.GetName(), + "org": r.GetOrg(), + "repo": r.GetName(), + "user": u.GetName(), + "user_id": u.GetID(), }).Tracef("creating deployment for repo %s", r.GetFullName()) // create GitHub OAuth client with user's token diff --git a/scm/github/deployment_test.go b/scm/github/deployment_test.go index 378a2417a..f985194ee 100644 --- a/scm/github/deployment_test.go +++ b/scm/github/deployment_test.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/library" ) @@ -31,11 +32,11 @@ func TestGithub_CreateDeployment(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) r.SetOrg("foo") r.SetName("bar") diff --git a/scm/github/github.go b/scm/github/github.go index 782e924c8..5db5f45b2 100644 --- a/scm/github/github.go +++ b/scm/github/github.go @@ -12,12 +12,10 @@ import ( "net/url" "strings" - "github.com/go-vela/types/library" - "github.com/bradleyfalzon/ghinstallation/v2" - "github.com/google/go-github/v59/github" + api "github.com/go-vela/server/api/types" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" - "golang.org/x/oauth2" ) @@ -202,7 +200,7 @@ func (c *client) newClientToken(token string) *github.Client { } // helper function to return the GitHub App token. -func (c *client) newGithubAppToken(r *library.Repo) (*github.Client, error) { +func (c *client) newGithubAppToken(r *api.Repo) (*github.Client, error) { // create a github client based off the existing GitHub App configuration client, err := github.NewClient(&http.Client{Transport: c.AppsTransport}).WithEnterpriseURLs(c.config.API, c.config.API) if err != nil { diff --git a/scm/github/github_test.go b/scm/github/github_test.go index 14a8ba15c..83a2d6033 100644 --- a/scm/github/github_test.go +++ b/scm/github/github_test.go @@ -10,7 +10,7 @@ import ( "reflect" "testing" - "github.com/google/go-github/v59/github" + "github.com/google/go-github/v62/github" "golang.org/x/oauth2" ) diff --git a/scm/github/org.go b/scm/github/org.go index 48b3701a8..2ac0ba714 100644 --- a/scm/github/org.go +++ b/scm/github/org.go @@ -8,11 +8,11 @@ import ( "github.com/sirupsen/logrus" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) // GetOrgName gets org name from Github. -func (c *client) GetOrgName(ctx context.Context, u *library.User, o string) (string, error) { +func (c *client) GetOrgName(ctx context.Context, u *api.User, o string) (string, error) { c.Logger.WithFields(logrus.Fields{ "org": o, "user": u.GetName(), diff --git a/scm/github/org_test.go b/scm/github/org_test.go index b9d64e84a..75e8d3ed1 100644 --- a/scm/github/org_test.go +++ b/scm/github/org_test.go @@ -11,7 +11,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/go-vela/types/library" + api "github.com/go-vela/server/api/types" ) func TestGithub_GetOrgName(t *testing.T) { @@ -32,7 +32,7 @@ func TestGithub_GetOrgName(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -74,7 +74,7 @@ func TestGithub_GetOrgName_Personal(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -115,7 +115,7 @@ func TestGithub_GetOrgName_Fail(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") diff --git a/scm/github/repo.go b/scm/github/repo.go index 7415196f3..daf59cc88 100644 --- a/scm/github/repo.go +++ b/scm/github/repo.go @@ -10,21 +10,22 @@ import ( "strings" "time" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/google/go-github/v59/github" ) // ConfigBackoff is a wrapper for Config that will retry five times if the function // fails to retrieve the yaml/yml file. -func (c *client) ConfigBackoff(ctx context.Context, u *library.User, r *library.Repo, ref string) (data []byte, err error) { +func (c *client) ConfigBackoff(ctx context.Context, u *api.User, r *api.Repo, ref string) (data []byte, err error) { // number of times to retry retryLimit := 5 for i := 0; i < retryLimit; i++ { - logrus.Debugf("Fetching config file - Attempt %d", i+1) + logrus.Debugf("fetching config file - Attempt %d", i+1) // attempt to fetch the config data, err = c.Config(ctx, u, r, ref) @@ -47,7 +48,7 @@ func (c *client) ConfigBackoff(ctx context.Context, u *library.User, r *library. } // Config gets the pipeline configuration from the GitHub repo. -func (c *client) Config(ctx context.Context, u *library.User, r *library.Repo, ref string) ([]byte, error) { +func (c *client) Config(ctx context.Context, u *api.User, r *api.Repo, ref string) ([]byte, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -94,7 +95,7 @@ func (c *client) Config(ctx context.Context, u *library.User, r *library.Repo, r } // Disable deactivates a repo by deleting the webhook. -func (c *client) Disable(ctx context.Context, u *library.User, org, name string) error { +func (c *client) Disable(ctx context.Context, u *api.User, org, name string) error { c.Logger.WithFields(logrus.Fields{ "org": org, "repo": name, @@ -122,11 +123,8 @@ func (c *client) Disable(ctx context.Context, u *library.User, org, name string) continue } - // cast url from hook configuration to string - hookURL := hook.Config["url"].(string) - // capture hook ID if the hook url matches - if hookURL == fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress) { + if strings.EqualFold(hook.GetConfig().GetURL(), fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress)) { ids = append(ids, hook.GetID()) } } @@ -152,7 +150,7 @@ func (c *client) Disable(ctx context.Context, u *library.User, org, name string) } // Enable activates a repo by creating the webhook. -func (c *client) Enable(ctx context.Context, u *library.User, r *library.Repo, h *library.Hook) (*library.Hook, string, error) { +func (c *client) Enable(ctx context.Context, u *api.User, r *api.Repo, h *library.Hook) (*library.Hook, string, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -192,10 +190,10 @@ func (c *client) Enable(ctx context.Context, u *library.User, r *library.Repo, h // create the hook object to make the API call hook := &github.Hook{ Events: events, - Config: map[string]interface{}{ - "url": fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress), - "content_type": "form", - "secret": r.GetHash(), + Config: &github.HookConfig{ + URL: github.String(fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress)), + ContentType: github.String("form"), + Secret: github.String(r.GetHash()), }, Active: github.Bool(true), } @@ -226,7 +224,7 @@ func (c *client) Enable(ctx context.Context, u *library.User, r *library.Repo, h } // Update edits a repo webhook. -func (c *client) Update(ctx context.Context, u *library.User, r *library.Repo, hookID int64) (bool, error) { +func (c *client) Update(ctx context.Context, u *api.User, r *api.Repo, hookID int64) (bool, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -266,10 +264,10 @@ func (c *client) Update(ctx context.Context, u *library.User, r *library.Repo, h // create the hook object to make the API call hook := &github.Hook{ Events: events, - Config: map[string]interface{}{ - "url": fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress), - "content_type": "form", - "secret": r.GetHash(), + Config: &github.HookConfig{ + URL: github.String(fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress)), + ContentType: github.String("form"), + Secret: github.String(r.GetHash()), }, Active: github.Bool(true), } @@ -283,7 +281,7 @@ func (c *client) Update(ctx context.Context, u *library.User, r *library.Repo, h } // Status sends the commit status for the given SHA from the GitHub repo. -func (c *client) Status(ctx context.Context, u *library.User, b *library.Build, org, name string) error { +func (c *client) Status(ctx context.Context, u *api.User, b *api.Build, org, name string) error { c.Logger.WithFields(logrus.Fields{ "build": b.GetNumber(), "org": org, @@ -291,6 +289,12 @@ func (c *client) Status(ctx context.Context, u *library.User, b *library.Build, "user": u.GetName(), }).Tracef("setting commit status for %s/%s/%d @ %s", org, name, b.GetNumber(), b.GetCommit()) + // only report opened, synchronize, and reopened action types for pull_request events + if strings.EqualFold(b.GetEvent(), constants.EventPull) && !strings.EqualFold(b.GetEventAction(), constants.ActionOpened) && + !strings.EqualFold(b.GetEventAction(), constants.ActionSynchronize) && !strings.EqualFold(b.GetEventAction(), constants.ActionReopened) { + return nil + } + // create GitHub OAuth client with user's token client := c.newClientToken(*u.Token) @@ -306,12 +310,14 @@ func (c *client) Status(ctx context.Context, u *library.User, b *library.Build, // depending on what the status of the build is switch b.GetStatus() { case constants.StatusRunning, constants.StatusPending: + //nolint:goconst // ignore making constant state = "pending" description = fmt.Sprintf("the build is %s", b.GetStatus()) case constants.StatusPendingApproval: state = "pending" description = "build needs approval from repo admin to run" case constants.StatusSuccess: + //nolint:goconst // ignore making constant state = "success" description = "the build was successful" case constants.StatusFailure: @@ -387,8 +393,77 @@ func (c *client) Status(ctx context.Context, u *library.User, b *library.Build, return err } +// StepStatus sends the commit status for the given SHA to the GitHub repo with the step as the context. +func (c *client) StepStatus(ctx context.Context, u *api.User, b *api.Build, s *library.Step, org, name string) error { + c.Logger.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "org": org, + "repo": name, + "user": u.GetName(), + }).Tracef("setting commit status for %s/%s/%d @ %s", org, name, b.GetNumber(), b.GetCommit()) + + // no commit statuses on deployments + if strings.EqualFold(b.GetEvent(), constants.EventDeploy) { + return nil + } + + // create GitHub OAuth client with user's token + client := c.newClientToken(*u.Token) + + context := fmt.Sprintf("%s/%s/%s", c.config.StatusContext, b.GetEvent(), s.GetReportAs()) + url := fmt.Sprintf("%s/%s/%s/%d#step:%d", c.config.WebUIAddress, org, name, b.GetNumber(), s.GetNumber()) + + var ( + state string + description string + ) + + // set the state and description for the status context + // depending on what the status of the step is + switch s.GetStatus() { + case constants.StatusRunning, constants.StatusPending, constants.StatusPendingApproval: + state = "pending" + description = fmt.Sprintf("the step is %s", s.GetStatus()) + case constants.StatusSuccess: + state = "success" + description = "the step was successful" + case constants.StatusFailure: + state = "failure" + description = "the step has failed" + case constants.StatusCanceled: + state = "failure" + description = "the step was canceled" + case constants.StatusKilled: + state = "failure" + description = "the step was killed" + case constants.StatusSkipped: + state = "failure" + description = "step was skipped or never ran" + default: + state = "error" + description = "there was an error" + } + + // create the status object to make the API call + status := &github.RepoStatus{ + Context: github.String(context), + Description: github.String(description), + State: github.String(state), + } + + // provide "Details" link in GitHub UI if server was configured with it + if len(c.config.WebUIAddress) > 0 && b.GetStatus() != constants.StatusSkipped { + status.TargetURL = github.String(url) + } + + // send API call to create the status context for the commit + _, _, err := client.Repositories.CreateStatus(ctx, org, name, b.GetCommit(), status) + + return err +} + // GetRepo gets repo information from Github. -func (c *client) GetRepo(ctx context.Context, u *library.User, r *library.Repo) (*library.Repo, int, error) { +func (c *client) GetRepo(ctx context.Context, u *api.User, r *api.Repo) (*api.Repo, int, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -408,7 +483,7 @@ func (c *client) GetRepo(ctx context.Context, u *library.User, r *library.Repo) } // GetOrgAndRepoName returns the name of the org and the repository in the SCM. -func (c *client) GetOrgAndRepoName(ctx context.Context, u *library.User, o string, r string) (string, string, error) { +func (c *client) GetOrgAndRepoName(ctx context.Context, u *api.User, o string, r string) (string, string, error) { c.Logger.WithFields(logrus.Fields{ "org": o, "repo": r, @@ -428,7 +503,7 @@ func (c *client) GetOrgAndRepoName(ctx context.Context, u *library.User, o strin } // ListUserRepos returns a list of all repos the user has access to. -func (c *client) ListUserRepos(ctx context.Context, u *library.User) ([]*library.Repo, error) { +func (c *client) ListUserRepos(ctx context.Context, u *api.User) ([]*api.Repo, error) { c.Logger.WithFields(logrus.Fields{ "user": u.GetName(), }).Tracef("listing source repositories for %s", u.GetName()) @@ -437,17 +512,17 @@ func (c *client) ListUserRepos(ctx context.Context, u *library.User) ([]*library client := c.newClientToken(u.GetToken()) r := []*github.Repository{} - f := []*library.Repo{} + f := []*api.Repo{} // set the max per page for the options to capture the list of repos - opts := &github.RepositoryListOptions{ + opts := &github.RepositoryListByAuthenticatedUserOptions{ ListOptions: github.ListOptions{PerPage: 100}, // 100 is max } // loop to capture *ALL* the repos for { // send API call to capture the user's repos - repos, resp, err := client.Repositories.List(ctx, "", opts) + repos, resp, err := client.Repositories.ListByAuthenticatedUser(ctx, opts) if err != nil { return nil, fmt.Errorf("unable to list user repos: %w", err) } @@ -482,7 +557,7 @@ func (c *client) ListUserRepos(ctx context.Context, u *library.User) ([]*library } // toLibraryRepo does a partial conversion of a github repo to a library repo. -func toLibraryRepo(gr github.Repository) *library.Repo { +func toLibraryRepo(gr github.Repository) *api.Repo { // setting the visbility to match the SCM visbility var visibility string if *gr.Private { @@ -491,7 +566,7 @@ func toLibraryRepo(gr github.Repository) *library.Repo { visibility = constants.VisibilityPublic } - return &library.Repo{ + return &api.Repo{ Org: gr.GetOwner().Login, Name: gr.Name, FullName: gr.FullName, @@ -506,15 +581,15 @@ func toLibraryRepo(gr github.Repository) *library.Repo { // GetPullRequest defines a function that retrieves // a pull request for a repo. -func (c *client) GetPullRequest(ctx context.Context, u *library.User, r *library.Repo, number int) (string, string, string, string, error) { +func (c *client) GetPullRequest(ctx context.Context, r *api.Repo, number int) (string, string, string, string, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - "user": u.GetName(), + "user": r.GetOwner().GetName(), }).Tracef("retrieving pull request %d for repo %s", number, r.GetFullName()) // create GitHub OAuth client with user's token - client := c.newClientToken(u.GetToken()) + client := c.newClientToken(r.GetOwner().GetToken()) pull, _, err := client.PullRequests.Get(ctx, r.GetOrg(), r.GetName(), number) if err != nil { @@ -530,7 +605,7 @@ func (c *client) GetPullRequest(ctx context.Context, u *library.User, r *library } // GetHTMLURL retrieves the html_url from repository contents from the GitHub repo. -func (c *client) GetHTMLURL(ctx context.Context, u *library.User, org, repo, name, ref string) (string, error) { +func (c *client) GetHTMLURL(ctx context.Context, u *api.User, org, repo, name, ref string) (string, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "repo": repo, @@ -566,15 +641,15 @@ func (c *client) GetHTMLURL(ctx context.Context, u *library.User, org, repo, nam } // GetBranch defines a function that retrieves a branch for a repo. -func (c *client) GetBranch(ctx context.Context, u *library.User, r *library.Repo, branch string) (string, string, error) { +func (c *client) GetBranch(ctx context.Context, r *api.Repo, branch string) (string, string, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), - "user": u.GetName(), + "user": r.GetOwner().GetName(), }).Tracef("retrieving branch %s for repo %s", branch, r.GetFullName()) // create GitHub OAuth client with user's token - client := c.newClientToken(u.GetToken()) + client := c.newClientToken(r.GetOwner().GetToken()) maxRedirects := 3 @@ -587,7 +662,7 @@ func (c *client) GetBranch(ctx context.Context, u *library.User, r *library.Repo } // CreateChecks defines a function that does stuff... -func (c *client) CreateChecks(ctx context.Context, r *library.Repo, commit, step, event string) (int64, error) { +func (c *client) CreateChecks(ctx context.Context, r *api.Repo, commit, step, event string) (int64, error) { // create client from GitHub App client, err := c.newGithubAppToken(r) if err != nil { @@ -608,7 +683,7 @@ func (c *client) CreateChecks(ctx context.Context, r *library.Repo, commit, step } // UpdateChecks defines a function that does stuff... -func (c *client) UpdateChecks(ctx context.Context, r *library.Repo, s *library.Step, commit, event string) error { +func (c *client) UpdateChecks(ctx context.Context, r *api.Repo, s *library.Step, commit, event string) error { // create client from GitHub App client, err := c.newGithubAppToken(r) if err != nil { diff --git a/scm/github/repo_test.go b/scm/github/repo_test.go index 997c793d6..7ef874181 100644 --- a/scm/github/repo_test.go +++ b/scm/github/repo_test.go @@ -14,6 +14,7 @@ import ( "github.com/gin-gonic/gin" + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" ) @@ -46,11 +47,11 @@ func TestGithub_Config_YML(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -107,11 +108,11 @@ func TestGithub_ConfigBackoff_YML(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -161,11 +162,11 @@ func TestGithub_Config_YAML(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -215,11 +216,11 @@ func TestGithub_Config_Star(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") r.SetPipelineType(constants.PipelineTypeStarlark) @@ -275,11 +276,11 @@ func TestGithub_Config_Star_Prefer(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") r.SetPipelineType(constants.PipelineTypeStarlark) @@ -330,11 +331,11 @@ func TestGithub_Config_Py(t *testing.T) { } // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") r.SetPipelineType(constants.PipelineTypeStarlark) @@ -380,11 +381,11 @@ func TestGithub_Config_YAML_BadRequest(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -422,11 +423,11 @@ func TestGithub_Config_NotFound(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -471,11 +472,11 @@ func TestGithub_Config_BadEncoding(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("foo") r.SetName("bar") @@ -518,7 +519,7 @@ func TestGithub_Disable(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -552,7 +553,7 @@ func TestGithub_Disable_NotFoundHooks(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -591,7 +592,7 @@ func TestGithub_Disable_HooksButNotFound(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -633,7 +634,7 @@ func TestGithub_Disable_MultipleHooks(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -673,7 +674,7 @@ func TestGithub_Enable(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -685,14 +686,11 @@ func TestGithub_Enable(t *testing.T) { wantHook.SetEvent("initialize") wantHook.SetStatus("success") - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) r.SetName("bar") r.SetOrg("foo") r.SetHash("secret") - r.SetAllowPush(true) - r.SetAllowPull(true) - r.SetAllowDeploy(true) client, _ := NewTest(s.URL) @@ -730,18 +728,15 @@ func TestGithub_Update(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetID(1) r.SetName("bar") r.SetOrg("foo") r.SetHash("secret") - r.SetAllowPush(true) - r.SetAllowPull(true) - r.SetAllowDeploy(true) hookID := int64(1) @@ -776,11 +771,11 @@ func TestGithub_Update_webhookExists_True(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) client, _ := NewTest(s.URL) @@ -813,11 +808,11 @@ func TestGithub_Update_webhookExists_False(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) client, _ := NewTest(s.URL) @@ -851,13 +846,16 @@ func TestGithub_Status_Deployment(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventDeploy) b.SetStatus(constants.StatusRunning) @@ -896,18 +894,28 @@ func TestGithub_Status_Running(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusRunning) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusRunning) + client, _ := NewTest(s.URL) // run test @@ -920,6 +928,16 @@ func TestGithub_Status_Running(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_Status_Success(t *testing.T) { @@ -940,18 +958,28 @@ func TestGithub_Status_Success(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusRunning) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusSuccess) + client, _ := NewTest(s.URL) // run test @@ -964,6 +992,16 @@ func TestGithub_Status_Success(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_Status_Failure(t *testing.T) { @@ -984,18 +1022,28 @@ func TestGithub_Status_Failure(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusRunning) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusFailure) + client, _ := NewTest(s.URL) // run test @@ -1008,6 +1056,16 @@ func TestGithub_Status_Failure(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_Status_Killed(t *testing.T) { @@ -1028,18 +1086,28 @@ func TestGithub_Status_Killed(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusRunning) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusKilled) + client, _ := NewTest(s.URL) // run test @@ -1052,6 +1120,16 @@ func TestGithub_Status_Killed(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_Status_Skipped(t *testing.T) { @@ -1072,18 +1150,28 @@ func TestGithub_Status_Skipped(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusSkipped) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusSkipped) + client, _ := NewTest(s.URL) // run test @@ -1096,6 +1184,16 @@ func TestGithub_Status_Skipped(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_Status_Error(t *testing.T) { @@ -1116,18 +1214,28 @@ func TestGithub_Status_Error(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - b := new(library.Build) + r := new(api.Repo) + r.SetID(1) + + b := new(api.Build) b.SetID(1) - b.SetRepoID(1) + b.SetRepo(r) b.SetNumber(1) b.SetEvent(constants.EventPush) b.SetStatus(constants.StatusRunning) b.SetCommit("abcd1234") + step := new(library.Step) + step.SetID(1) + step.SetNumber(1) + step.SetName("test") + step.SetReportAs("test") + step.SetStatus(constants.StatusError) + client, _ := NewTest(s.URL) // run test @@ -1140,6 +1248,16 @@ func TestGithub_Status_Error(t *testing.T) { if err != nil { t.Errorf("Status returned err: %v", err) } + + err = client.StepStatus(context.TODO(), u, b, step, "foo", "bar") + + if resp.Code != http.StatusOK { + t.Errorf("Status returned %v, want %v", resp.Code, http.StatusOK) + } + + if err != nil { + t.Errorf("Status returned err: %v", err) + } } func TestGithub_GetRepo(t *testing.T) { @@ -1160,15 +1278,15 @@ func TestGithub_GetRepo(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("octocat") r.SetName("Hello-World") - want := new(library.Repo) + want := new(api.Repo) want.SetOrg("octocat") want.SetName("Hello-World") want.SetFullName("octocat/Hello-World") @@ -1214,11 +1332,11 @@ func TestGithub_GetRepo_Fail(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("octocat") r.SetName("Hello-World") @@ -1254,7 +1372,7 @@ func TestGithub_GetOrgAndRepoName(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -1300,7 +1418,7 @@ func TestGithub_GetOrgAndRepoName_Fail(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") @@ -1332,11 +1450,11 @@ func TestGithub_ListUserRepos(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("octocat") r.SetName("Hello-World") r.SetFullName("octocat/Hello-World") @@ -1347,7 +1465,7 @@ func TestGithub_ListUserRepos(t *testing.T) { r.SetTopics([]string{"octocat", "atom", "electron", "api"}) r.SetVisibility("public") - want := []*library.Repo{r} + want := []*api.Repo{r} client, _ := NewTest(s.URL) @@ -1380,11 +1498,11 @@ func TestGithub_ListUserRepos_Ineligible(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - want := []*library.Repo{} + want := []*api.Repo{} client, _ := NewTest(s.URL) @@ -1417,13 +1535,14 @@ func TestGithub_GetPullRequest(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("octocat") r.SetName("Hello-World") + r.SetOwner(u) wantCommit := "6dcb09b5b57875f334f61aebed695e2e4193db5e" wantBranch := "main" @@ -1433,7 +1552,7 @@ func TestGithub_GetPullRequest(t *testing.T) { client, _ := NewTest(s.URL) // run test - gotCommit, gotBranch, gotBaseRef, gotHeadRef, err := client.GetPullRequest(context.TODO(), u, r, 1) + gotCommit, gotBranch, gotBaseRef, gotHeadRef, err := client.GetPullRequest(context.TODO(), r, 1) if err != nil { t.Errorf("Status returned err: %v", err) } @@ -1473,15 +1592,16 @@ func TestGithub_GetBranch(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("foo") u.SetToken("bar") - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("octocat") r.SetName("Hello-World") r.SetFullName("octocat/Hello-World") r.SetBranch("main") + r.SetOwner(u) wantBranch := "main" wantCommit := "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d" @@ -1489,7 +1609,7 @@ func TestGithub_GetBranch(t *testing.T) { client, _ := NewTest(s.URL) // run test - gotBranch, gotCommit, err := client.GetBranch(context.TODO(), u, r, "main") + gotBranch, gotCommit, err := client.GetBranch(context.TODO(), r, "main") if err != nil { t.Errorf("Status returned err: %v", err) } diff --git a/scm/github/testdata/hooks/pull_request_edited_while_labeled.json b/scm/github/testdata/hooks/pull_request_edited_while_labeled.json new file mode 100644 index 000000000..55471932e --- /dev/null +++ b/scm/github/testdata/hooks/pull_request_edited_while_labeled.json @@ -0,0 +1,466 @@ +{ + "action": "edited", + "number": 1, + "pull_request": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1", + "id": 191568743, + "node_id": "MDExOlB1bGxSZXF1ZXN0MTkxNTY4NzQz", + "html_url": "https://github.com/Codertocat/Hello-World/pull/1", + "diff_url": "https://github.com/Codertocat/Hello-World/pull/1.diff", + "patch_url": "https://github.com/Codertocat/Hello-World/pull/1.patch", + "issue_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1", + "number": 1, + "state": "open", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into main.", + "created_at": "2018-05-30T20:18:30Z", + "updated_at": "2018-05-30T20:18:50Z", + "closed_at": "2018-05-30T20:18:50Z", + "merged_at": null, + "merge_commit_sha": "414cb0069601a32b00bd122a2380cd283626a8e5", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [ + { + "id": 3768735, + "node_id": "MDU6TGFiZWwzNzY4NzM1", + "url": "https://git.target.com/api/v3/repos/WinSan/vela-sandbox/labels/documentation", + "name": "documentation", + "color": "0075ca", + "default": true, + "description": "Improvements or additions to documentation" + }, + { + "id": 3768737, + "node_id": "MDU6TGFiZWwzNzY4NzM3", + "url": "https://git.target.com/api/v3/repos/WinSan/vela-sandbox/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + } + ], + "milestone": null, + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a", + "head": { + "label": "Codertocat:changes", + "ref": "changes", + "sha": "34c5c7793cb3b279e22454cb6750c80560547b3a", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "base": { + "label": "Codertocat:main", + "ref": "main", + "sha": "a10867b14bb761a232cd80139fbd4c0d33264240", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1" + }, + "html": { + "href": "https://github.com/Codertocat/Hello-World/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a" + } + }, + "author_association": "OWNER", + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 0, + "review_comments": 1, + "maintainer_can_modify": false, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 + }, + "changes": { + "title": { + "from": "Update README.md." + } + }, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/scm/github/testdata/hooks/pull_request_labeled.json b/scm/github/testdata/hooks/pull_request_labeled.json new file mode 100644 index 000000000..5d2cbd6eb --- /dev/null +++ b/scm/github/testdata/hooks/pull_request_labeled.json @@ -0,0 +1,451 @@ +{ + "action": "labeled", + "number": 1, + "pull_request": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1", + "id": 191568743, + "node_id": "MDExOlB1bGxSZXF1ZXN0MTkxNTY4NzQz", + "html_url": "https://github.com/Codertocat/Hello-World/pull/1", + "diff_url": "https://github.com/Codertocat/Hello-World/pull/1.diff", + "patch_url": "https://github.com/Codertocat/Hello-World/pull/1.patch", + "issue_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1", + "number": 1, + "state": "open", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into main.", + "created_at": "2018-05-30T20:18:30Z", + "updated_at": "2018-05-30T20:18:50Z", + "closed_at": "2018-05-30T20:18:50Z", + "merged_at": null, + "merge_commit_sha": "414cb0069601a32b00bd122a2380cd283626a8e5", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a", + "head": { + "label": "Codertocat:changes", + "ref": "changes", + "sha": "34c5c7793cb3b279e22454cb6750c80560547b3a", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "base": { + "label": "Codertocat:main", + "ref": "main", + "sha": "a10867b14bb761a232cd80139fbd4c0d33264240", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1" + }, + "html": { + "href": "https://github.com/Codertocat/Hello-World/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a" + } + }, + "author_association": "OWNER", + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 0, + "review_comments": 1, + "maintainer_can_modify": false, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 + }, + "label": { + "id": 3768735, + "node_id": "MDU6TGFiZWwzNzY4NzM1", + "url": "https://github.com/Codertocat/Hello-World/labels/documentation", + "name": "documentation", + "color": "0075ca", + "default": true, + "description": "Improvements or additions to documentation" + }, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/scm/github/testdata/hooks/pull_request_unlabeled.json b/scm/github/testdata/hooks/pull_request_unlabeled.json new file mode 100644 index 000000000..adf48f380 --- /dev/null +++ b/scm/github/testdata/hooks/pull_request_unlabeled.json @@ -0,0 +1,451 @@ +{ + "action": "unlabeled", + "number": 1, + "pull_request": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1", + "id": 191568743, + "node_id": "MDExOlB1bGxSZXF1ZXN0MTkxNTY4NzQz", + "html_url": "https://github.com/Codertocat/Hello-World/pull/1", + "diff_url": "https://github.com/Codertocat/Hello-World/pull/1.diff", + "patch_url": "https://github.com/Codertocat/Hello-World/pull/1.patch", + "issue_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1", + "number": 1, + "state": "open", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into main.", + "created_at": "2018-05-30T20:18:30Z", + "updated_at": "2018-05-30T20:18:50Z", + "closed_at": "2018-05-30T20:18:50Z", + "merged_at": null, + "merge_commit_sha": "414cb0069601a32b00bd122a2380cd283626a8e5", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a", + "head": { + "label": "Codertocat:changes", + "ref": "changes", + "sha": "34c5c7793cb3b279e22454cb6750c80560547b3a", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "base": { + "label": "Codertocat:main", + "ref": "main", + "sha": "a10867b14bb761a232cd80139fbd4c0d33264240", + "user": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1" + }, + "html": { + "href": "https://github.com/Codertocat/Hello-World/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a" + } + }, + "author_association": "OWNER", + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 0, + "review_comments": 1, + "maintainer_can_modify": false, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 + }, + "label": { + "id": 3768735, + "node_id": "MDU6TGFiZWwzNzY4NzM1", + "url": "https://github.com/Codertocat/Hello-World/labels/documentation", + "name": "documentation", + "color": "0075ca", + "default": true, + "description": "Improvements or additions to documentation" + }, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2018-05-30T20:18:04Z", + "updated_at": "2018-05-30T20:18:50Z", + "pushed_at": "2018-05-30T20:18:48Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 1, + "license": null, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/scm/github/user.go b/scm/github/user.go new file mode 100644 index 000000000..e50cc19ff --- /dev/null +++ b/scm/github/user.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package github + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" +) + +// GetUserID captures the user's scm id. +func (c *client) GetUserID(ctx context.Context, name string, token string) (string, error) { + c.Logger.WithFields(logrus.Fields{ + "user": name, + }).Tracef("capturing SCM user id for %s", name) + + // create GitHub OAuth client with user's token + client := c.newClientToken(token) + + // send API call to capture user + user, _, err := client.Users.Get(ctx, name) + if err != nil { + return "", err + } + + return fmt.Sprint(user.GetID()), nil +} diff --git a/scm/github/webhook.go b/scm/github/webhook.go index 07b3fc163..a0c623604 100644 --- a/scm/github/webhook.go +++ b/scm/github/webhook.go @@ -13,18 +13,19 @@ import ( "strings" "time" + "github.com/google/go-github/v62/github" "github.com/sirupsen/logrus" - "github.com/go-vela/types" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/google/go-github/v59/github" ) // ProcessWebhook parses the webhook from a repo. // //nolint:nilerr // ignore webhook returning nil -func (c *client) ProcessWebhook(ctx context.Context, request *http.Request) (*types.Webhook, error) { +func (c *client) ProcessWebhook(ctx context.Context, request *http.Request) (*internal.Webhook, error) { c.Logger.Tracef("processing GitHub webhook") // create our own record of the hook and populate its fields @@ -55,20 +56,20 @@ func (c *client) ProcessWebhook(ctx context.Context, request *http.Request) (*ty payload, err := github.ValidatePayloadFromBody(contentType, request.Body, "", nil) if err != nil { - return &types.Webhook{Hook: h}, nil + return &internal.Webhook{Hook: h}, nil } // parse the payload from the webhook event, err := github.ParseWebHook(github.WebHookType(request), payload) if err != nil { - return &types.Webhook{Hook: h}, nil + return &internal.Webhook{Hook: h}, nil } // process the event from the webhook switch event := event.(type) { case *github.PushEvent: - return c.processPushEvent(h, event) + return c.processPushEvent(ctx, h, event) case *github.PullRequestEvent: return c.processPREvent(h, event) case *github.DeploymentEvent: @@ -79,11 +80,11 @@ func (c *client) ProcessWebhook(ctx context.Context, request *http.Request) (*ty return c.processRepositoryEvent(h, event) } - return &types.Webhook{Hook: h}, nil + return &internal.Webhook{Hook: h}, nil } // VerifyWebhook verifies the webhook from a repo. -func (c *client) VerifyWebhook(ctx context.Context, request *http.Request, r *library.Repo) error { +func (c *client) VerifyWebhook(ctx context.Context, request *http.Request, r *api.Repo) error { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), @@ -98,7 +99,7 @@ func (c *client) VerifyWebhook(ctx context.Context, request *http.Request, r *li } // RedeliverWebhook redelivers webhooks from GitHub. -func (c *client) RedeliverWebhook(ctx context.Context, u *library.User, r *library.Repo, h *library.Hook) error { +func (c *client) RedeliverWebhook(ctx context.Context, u *api.User, r *api.Repo, h *library.Hook) error { // create GitHub OAuth client with user's token //nolint:contextcheck // do not need to pass context in this instance client := c.newClientToken(*u.Token) @@ -127,7 +128,7 @@ func (c *client) RedeliverWebhook(ctx context.Context, u *library.User, r *libra } // processPushEvent is a helper function to process the push event. -func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (*types.Webhook, error) { +func (c *client) processPushEvent(ctx context.Context, h *library.Hook, payload *github.PushEvent) (*internal.Webhook, error) { c.Logger.WithFields(logrus.Fields{ "org": payload.GetRepo().GetOwner().GetLogin(), "repo": payload.GetRepo().GetName(), @@ -136,7 +137,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* repo := payload.GetRepo() // convert payload to library repo - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(repo.GetOwner().GetLogin()) r.SetName(repo.GetName()) r.SetFullName(repo.GetFullName()) @@ -147,7 +148,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* r.SetTopics(repo.Topics) // convert payload to library build - b := new(library.Build) + b := new(api.Build) b.SetEvent(constants.EventPush) b.SetClone(repo.GetCloneURL()) b.SetSource(payload.GetHeadCommit().GetURL()) @@ -155,6 +156,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* b.SetMessage(payload.GetHeadCommit().GetMessage()) b.SetCommit(payload.GetHeadCommit().GetID()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetHeadCommit().GetAuthor().GetLogin()) b.SetEmail(payload.GetHeadCommit().GetAuthor().GetEmail()) b.SetBranch(strings.TrimPrefix(payload.GetRef(), "refs/heads/")) @@ -224,7 +226,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* } } - return &types.Webhook{ + return &internal.Webhook{ Hook: h, Repo: r, Build: b, @@ -232,7 +234,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (* } // processPREvent is a helper function to process the pull_request event. -func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEvent) (*types.Webhook, error) { +func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEvent) (*internal.Webhook, error) { c.Logger.WithFields(logrus.Fields{ "org": payload.GetRepo().GetOwner().GetLogin(), "repo": payload.GetRepo().GetName(), @@ -247,22 +249,24 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven // if the pull request state isn't open we ignore it if payload.GetPullRequest().GetState() != "open" { - return &types.Webhook{Hook: h}, nil + return &internal.Webhook{Hook: h}, nil } - // skip if the pull request action is not opened, synchronize, reopened, or edited + // skip if the pull request action is not opened, synchronize, reopened, edited, labeled, or unlabeled if !strings.EqualFold(payload.GetAction(), "opened") && !strings.EqualFold(payload.GetAction(), "synchronize") && !strings.EqualFold(payload.GetAction(), "reopened") && - !strings.EqualFold(payload.GetAction(), "edited") { - return &types.Webhook{Hook: h}, nil + !strings.EqualFold(payload.GetAction(), "edited") && + !strings.EqualFold(payload.GetAction(), "labeled") && + !strings.EqualFold(payload.GetAction(), "unlabeled") { + return &internal.Webhook{Hook: h}, nil } // capture the repo from the payload repo := payload.GetRepo() // convert payload to library repo - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(repo.GetOwner().GetLogin()) r.SetName(repo.GetName()) r.SetFullName(repo.GetFullName()) @@ -272,8 +276,8 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven r.SetPrivate(repo.GetPrivate()) r.SetTopics(repo.Topics) - // convert payload to library build - b := new(library.Build) + // convert payload to api build + b := new(api.Build) b.SetEvent(constants.EventPull) b.SetEventAction(payload.GetAction()) b.SetClone(repo.GetCloneURL()) @@ -282,6 +286,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven b.SetMessage(payload.GetPullRequest().GetTitle()) b.SetCommit(payload.GetPullRequest().GetHead().GetSHA()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetPullRequest().GetUser().GetLogin()) b.SetEmail(payload.GetPullRequest().GetUser().GetEmail()) b.SetBranch(payload.GetPullRequest().GetBase().GetRef()) @@ -302,6 +307,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven // ensure the build sender is set if len(b.GetSender()) == 0 { b.SetSender(payload.GetPullRequest().GetUser().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetPullRequest().GetUser().GetID())) } // ensure the build email is set @@ -309,14 +315,26 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven b.SetEmail(payload.GetPullRequest().GetHead().GetUser().GetEmail()) } + var prLabels []string + if strings.EqualFold(payload.GetAction(), "labeled") || + strings.EqualFold(payload.GetAction(), "unlabeled") { + prLabels = append(prLabels, payload.GetLabel().GetName()) + } else { + labels := payload.GetPullRequest().Labels + for _, label := range labels { + prLabels = append(prLabels, label.GetName()) + } + } + // determine if pull request head is a fork and does not match the repo name of base fromFork := payload.GetPullRequest().GetHead().GetRepo().GetFork() && !strings.EqualFold(payload.GetPullRequest().GetBase().GetRepo().GetFullName(), payload.GetPullRequest().GetHead().GetRepo().GetFullName()) - return &types.Webhook{ - PullRequest: types.PullRequest{ + return &internal.Webhook{ + PullRequest: internal.PullRequest{ Number: payload.GetNumber(), IsFromFork: fromFork, + Labels: prLabels, }, Hook: h, Repo: r, @@ -325,7 +343,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven } // processDeploymentEvent is a helper function to process the deployment event. -func (c *client) processDeploymentEvent(h *library.Hook, payload *github.DeploymentEvent) (*types.Webhook, error) { +func (c *client) processDeploymentEvent(h *library.Hook, payload *github.DeploymentEvent) (*internal.Webhook, error) { c.Logger.WithFields(logrus.Fields{ "org": payload.GetRepo().GetOwner().GetLogin(), "repo": payload.GetRepo().GetName(), @@ -335,7 +353,7 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym repo := payload.GetRepo() // convert payload to library repo - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(repo.GetOwner().GetLogin()) r.SetName(repo.GetName()) r.SetFullName(repo.GetFullName()) @@ -345,9 +363,10 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym r.SetPrivate(repo.GetPrivate()) r.SetTopics(repo.Topics) - // convert payload to library build - b := new(library.Build) + // convert payload to api build + b := new(api.Build) b.SetEvent(constants.EventDeploy) + b.SetEventAction(constants.ActionCreated) b.SetClone(repo.GetCloneURL()) b.SetDeploy(payload.GetDeployment().GetEnvironment()) b.SetDeployNumber(payload.GetDeployment().GetID()) @@ -356,6 +375,8 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym b.SetMessage(payload.GetDeployment().GetDescription()) b.SetCommit(payload.GetDeployment().GetSHA()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) + b.SetAuthor(payload.GetDeployment().GetCreator().GetLogin()) b.SetEmail(payload.GetDeployment().GetCreator().GetEmail()) b.SetBranch(payload.GetDeployment().GetRef()) @@ -385,7 +406,7 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym // unmarshal the payload into the expected map[string]string format err := json.Unmarshal(payload.GetDeployment().Payload, &deployPayload) if err != nil { - return &types.Webhook{}, err + return &internal.Webhook{}, err } // check if the map is empty @@ -412,11 +433,12 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym // update the hook object h.SetBranch(b.GetBranch()) h.SetEvent(constants.EventDeploy) + h.SetEventAction(constants.ActionCreated) h.SetLink( fmt.Sprintf("https://%s/%s/settings/hooks", h.GetHost(), r.GetFullName()), ) - return &types.Webhook{ + return &internal.Webhook{ Hook: h, Repo: r, Build: b, @@ -425,7 +447,7 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym } // processIssueCommentEvent is a helper function to process the issue comment event. -func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.IssueCommentEvent) (*types.Webhook, error) { +func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.IssueCommentEvent) (*internal.Webhook, error) { c.Logger.WithFields(logrus.Fields{ "org": payload.GetRepo().GetOwner().GetLogin(), "repo": payload.GetRepo().GetName(), @@ -437,10 +459,10 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue fmt.Sprintf("https://%s/%s/settings/hooks", h.GetHost(), payload.GetRepo().GetFullName()), ) - // skip if the comment action is deleted - if strings.EqualFold(payload.GetAction(), "deleted") { - // return &types.Webhook{Hook: h}, nil - return &types.Webhook{ + // skip if the comment action is deleted or not part of a pull request + if strings.EqualFold(payload.GetAction(), "deleted") || !payload.GetIssue().IsPullRequest() { + // return &internal.Webhook{Hook: h}, nil + return &internal.Webhook{ Hook: h, }, nil } @@ -449,7 +471,7 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue repo := payload.GetRepo() // convert payload to library repo - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(repo.GetOwner().GetLogin()) r.SetName(repo.GetName()) r.SetFullName(repo.GetFullName()) @@ -460,7 +482,7 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue r.SetTopics(repo.Topics) // convert payload to library build - b := new(library.Build) + b := new(api.Build) b.SetEvent(constants.EventComment) b.SetEventAction(payload.GetAction()) b.SetClone(repo.GetCloneURL()) @@ -468,24 +490,15 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventComment, repo.GetHTMLURL())) b.SetMessage(payload.Issue.GetTitle()) b.SetSender(payload.GetSender().GetLogin()) + b.SetSenderSCMID(fmt.Sprint(payload.GetSender().GetID())) b.SetAuthor(payload.GetIssue().GetUser().GetLogin()) b.SetEmail(payload.GetIssue().GetUser().GetEmail()) - // treat as non-pull-request comment by default and - // set ref to default branch for the repo - b.SetRef(fmt.Sprintf("refs/heads/%s", r.GetBranch())) - - pr := 0 - // override ref and pull request number if this is - // a comment on a pull request - if payload.GetIssue().IsPullRequest() { - b.SetRef(fmt.Sprintf("refs/pull/%d/head", payload.GetIssue().GetNumber())) - pr = payload.GetIssue().GetNumber() - } + b.SetRef(fmt.Sprintf("refs/pull/%d/head", payload.GetIssue().GetNumber())) - return &types.Webhook{ - PullRequest: types.PullRequest{ + return &internal.Webhook{ + PullRequest: internal.PullRequest{ Comment: payload.GetComment().GetBody(), - Number: pr, + Number: payload.GetIssue().GetNumber(), }, Hook: h, Repo: r, @@ -495,13 +508,13 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue // processRepositoryEvent is a helper function to process the repository event. -func (c *client) processRepositoryEvent(h *library.Hook, payload *github.RepositoryEvent) (*types.Webhook, error) { +func (c *client) processRepositoryEvent(h *library.Hook, payload *github.RepositoryEvent) (*internal.Webhook, error) { logrus.Tracef("processing repository event GitHub webhook for %s", payload.GetRepo().GetFullName()) repo := payload.GetRepo() // convert payload to library repo - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(repo.GetOwner().GetLogin()) r.SetName(repo.GetName()) r.SetFullName(repo.GetFullName()) @@ -519,7 +532,7 @@ func (c *client) processRepositoryEvent(h *library.Hook, payload *github.Reposit fmt.Sprintf("https://%s/%s/settings/hooks", h.GetHost(), r.GetFullName()), ) - return &types.Webhook{ + return &internal.Webhook{ Hook: h, Repo: r, }, nil @@ -527,7 +540,7 @@ func (c *client) processRepositoryEvent(h *library.Hook, payload *github.Reposit // getDeliveryID gets the last 100 webhook deliveries for a repo and // finds the matching delivery id with the source id in the hook. -func (c *client) getDeliveryID(ctx context.Context, ghClient *github.Client, r *library.Repo, h *library.Hook) (int64, error) { +func (c *client) getDeliveryID(ctx context.Context, ghClient *github.Client, r *api.Repo, h *library.Hook) (int64, error) { c.Logger.WithFields(logrus.Fields{ "org": r.GetOrg(), "repo": r.GetName(), diff --git a/scm/github/webhook_test.go b/scm/github/webhook_test.go index 35f6581de..b2f8f5e98 100644 --- a/scm/github/webhook_test.go +++ b/scm/github/webhook_test.go @@ -13,12 +13,13 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/go-vela/types/raw" "github.com/google/go-cmp/cmp" - "github.com/go-vela/types" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" + "github.com/go-vela/types/raw" ) func TestGithub_ProcessWebhook_Push(t *testing.T) { @@ -56,7 +57,7 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -66,7 +67,7 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics([]string{"go", "vela"}) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("push") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") wantBuild.SetSource("https://github.com/Codertocat/Hello-World/commit/9c93babf58917cd6f6f6772b5df2b098f507ff95") @@ -74,13 +75,14 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) { wantBuild.SetMessage("Update README.md") wantBuild.SetCommit("9c93babf58917cd6f6f6772b5df2b098f507ff95") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") wantBuild.SetRef("refs/heads/main") wantBuild.SetBaseRef("") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, Build: wantBuild, @@ -134,7 +136,7 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -144,7 +146,7 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics([]string{"go", "vela"}) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("push") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") wantBuild.SetSource("https://github.com/Codertocat/Hello-World/commit/9c93babf58917cd6f6f6772b5df2b098f507ff95") @@ -152,13 +154,14 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { wantBuild.SetMessage("Update README.md") wantBuild.SetCommit("9c93babf58917cd6f6f6772b5df2b098f507ff95") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("0") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") wantBuild.SetRef("refs/heads/main") wantBuild.SetBaseRef("") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, Build: wantBuild, @@ -170,8 +173,8 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) { t.Errorf("ProcessWebhook returned err: %v", err) } - if !reflect.DeepEqual(got, want) { - t.Errorf("ProcessWebhook is %v, want %v", got, want) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ProcessWebhook() mismatch (-want +got):\n%s", diff) } } @@ -210,7 +213,7 @@ func TestGithub_ProcessWebhook_Push_Branch_Delete(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -220,7 +223,7 @@ func TestGithub_ProcessWebhook_Push_Branch_Delete(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics([]string{"go", "vela"}) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("delete") wantBuild.SetEventAction("branch") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") @@ -229,13 +232,14 @@ func TestGithub_ProcessWebhook_Push_Branch_Delete(t *testing.T) { wantBuild.SetMessage("main branch deleted") wantBuild.SetCommit("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("main") wantBuild.SetRef("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetBaseRef("") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, Build: wantBuild, @@ -287,7 +291,7 @@ func TestGithub_ProcessWebhook_Push_Tag_Delete(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -297,7 +301,7 @@ func TestGithub_ProcessWebhook_Push_Tag_Delete(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics([]string{"go", "vela"}) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("delete") wantBuild.SetEventAction("tag") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") @@ -306,13 +310,14 @@ func TestGithub_ProcessWebhook_Push_Tag_Delete(t *testing.T) { wantBuild.SetMessage("v0.1 tag deleted") wantBuild.SetCommit("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("21031067+Codertocat@users.noreply.github.com") wantBuild.SetBranch("v0.1") wantBuild.SetRef("d3d9188fc87a6977343e922c128f162a86018d76") wantBuild.SetBaseRef("") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, Build: wantBuild, @@ -346,7 +351,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -356,7 +361,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("pull_request") wantBuild.SetEventAction("opened") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") @@ -365,6 +370,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild.SetMessage("Update the README with new information") wantBuild.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -372,17 +378,68 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { wantBuild.SetBaseRef("main") wantBuild.SetHeadRef("changes") + wantBuild2 := new(api.Build) + wantBuild2.SetEvent("pull_request") + wantBuild2.SetEventAction("labeled") + wantBuild2.SetClone("https://github.com/Codertocat/Hello-World.git") + wantBuild2.SetSource("https://github.com/Codertocat/Hello-World/pull/1") + wantBuild2.SetTitle("pull_request received from https://github.com/Codertocat/Hello-World") + wantBuild2.SetMessage("Update the README with new information") + wantBuild2.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") + wantBuild2.SetSender("Codertocat") + wantBuild2.SetSenderSCMID("21031067") + wantBuild2.SetAuthor("Codertocat") + wantBuild2.SetEmail("") + wantBuild2.SetBranch("main") + wantBuild2.SetRef("refs/pull/1/head") + wantBuild2.SetBaseRef("main") + wantBuild2.SetHeadRef("changes") + + wantBuild3 := new(api.Build) + wantBuild3.SetEvent("pull_request") + wantBuild3.SetEventAction("unlabeled") + wantBuild3.SetClone("https://github.com/Codertocat/Hello-World.git") + wantBuild3.SetSource("https://github.com/Codertocat/Hello-World/pull/1") + wantBuild3.SetTitle("pull_request received from https://github.com/Codertocat/Hello-World") + wantBuild3.SetMessage("Update the README with new information") + wantBuild3.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") + wantBuild3.SetSender("Codertocat") + wantBuild3.SetSenderSCMID("21031067") + wantBuild3.SetAuthor("Codertocat") + wantBuild3.SetEmail("") + wantBuild3.SetBranch("main") + wantBuild3.SetRef("refs/pull/1/head") + wantBuild3.SetBaseRef("main") + wantBuild3.SetHeadRef("changes") + + wantBuild4 := new(api.Build) + wantBuild4.SetEvent("pull_request") + wantBuild4.SetEventAction("edited") + wantBuild4.SetClone("https://github.com/Codertocat/Hello-World.git") + wantBuild4.SetSource("https://github.com/Codertocat/Hello-World/pull/1") + wantBuild4.SetTitle("pull_request received from https://github.com/Codertocat/Hello-World") + wantBuild4.SetMessage("Update the README with new information") + wantBuild4.SetCommit("34c5c7793cb3b279e22454cb6750c80560547b3a") + wantBuild4.SetSender("Codertocat") + wantBuild4.SetSenderSCMID("21031067") + wantBuild4.SetAuthor("Codertocat") + wantBuild4.SetEmail("") + wantBuild4.SetBranch("main") + wantBuild4.SetRef("refs/pull/1/head") + wantBuild4.SetBaseRef("main") + wantBuild4.SetHeadRef("changes") + tests := []struct { name string testData string - want *types.Webhook + want *internal.Webhook wantErr bool }{ { name: "success", testData: "testdata/hooks/pull_request.json", - want: &types.Webhook{ - PullRequest: types.PullRequest{ + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ Number: wantHook.GetNumber(), IsFromFork: false, }, @@ -394,8 +451,8 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { { name: "fork", testData: "testdata/hooks/pull_request_fork.json", - want: &types.Webhook{ - PullRequest: types.PullRequest{ + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ Number: wantHook.GetNumber(), IsFromFork: true, }, @@ -407,8 +464,8 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { { name: "fork same repo", testData: "testdata/hooks/pull_request_fork_same-repo.json", - want: &types.Webhook{ - PullRequest: types.PullRequest{ + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ Number: wantHook.GetNumber(), IsFromFork: false, }, @@ -420,7 +477,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { { name: "closed action", testData: "testdata/hooks/pull_request_closed_action.json", - want: &types.Webhook{ + want: &internal.Webhook{ Hook: wantHook, Repo: nil, Build: nil, @@ -429,12 +486,54 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) { { name: "closed state", testData: "testdata/hooks/pull_request_closed_state.json", - want: &types.Webhook{ + want: &internal.Webhook{ Hook: wantHook, Repo: nil, Build: nil, }, }, + { + name: "labeled documentation", + testData: "testdata/hooks/pull_request_labeled.json", + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ + Number: wantHook.GetNumber(), + IsFromFork: false, + Labels: []string{"documentation"}, + }, + Hook: wantHook, + Repo: wantRepo, + Build: wantBuild2, + }, + }, + { + name: "unlabeled documentation", + testData: "testdata/hooks/pull_request_unlabeled.json", + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ + Number: wantHook.GetNumber(), + IsFromFork: false, + Labels: []string{"documentation"}, + }, + Hook: wantHook, + Repo: wantRepo, + Build: wantBuild3, + }, + }, + { + name: "edited while labeled documentation", + testData: "testdata/hooks/pull_request_edited_while_labeled.json", + want: &internal.Webhook{ + PullRequest: internal.PullRequest{ + Number: wantHook.GetNumber(), + IsFromFork: false, + Labels: []string{"documentation", "enhancement"}, + }, + Hook: wantHook, + Repo: wantRepo, + Build: wantBuild4, + }, + }, } for _, tt := range tests { @@ -483,10 +582,11 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { wantHook.SetBranch("main") wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") wantHook.SetHost("github.com") - wantHook.SetEvent("deployment") + wantHook.SetEvent(constants.EventDeploy) + wantHook.SetEventAction(constants.ActionCreated) wantHook.SetStatus(constants.StatusSuccess) - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -496,8 +596,9 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - wantBuild := new(library.Build) - wantBuild.SetEvent("deployment") + wantBuild := new(api.Build) + wantBuild.SetEvent(constants.EventDeploy) + wantBuild.SetEventAction(constants.ActionCreated) wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") wantBuild.SetDeploy("production") wantBuild.SetDeployNumber(145988746) @@ -506,6 +607,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { wantBuild.SetMessage("") wantBuild.SetCommit("f95f852bd8fca8fcc58a9a2d6c842781e32a215e") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -525,8 +627,8 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { type args struct { file string hook *library.Hook - repo *library.Repo - build *library.Build + repo *api.Repo + build *api.Build deploymentPayload raw.StringSliceMap deployment *library.Deployment } @@ -562,7 +664,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) { client, _ := NewTest(s.URL) wantBuild.SetDeployPayload(tt.args.deploymentPayload) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: tt.args.hook, Repo: tt.args.repo, Build: tt.args.build, @@ -616,10 +718,11 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) { wantHook.SetBranch("main") wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") wantHook.SetHost("github.com") - wantHook.SetEvent("deployment") + wantHook.SetEvent(constants.EventDeploy) + wantHook.SetEventAction(constants.ActionCreated) wantHook.SetStatus(constants.StatusSuccess) - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -629,8 +732,9 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - wantBuild := new(library.Build) - wantBuild.SetEvent("deployment") + wantBuild := new(api.Build) + wantBuild.SetEvent(constants.EventDeploy) + wantBuild.SetEventAction(constants.ActionCreated) wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") wantBuild.SetDeploy("production") wantBuild.SetDeployNumber(145988746) @@ -639,6 +743,7 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) { wantBuild.SetMessage("") wantBuild.SetCommit("f95f852bd8fca8fcc58a9a2d6c842781e32a215e") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("21031067") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetBranch("main") @@ -656,7 +761,7 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) { wantDeployment.SetCreatedAt(time.Now().UTC().Unix()) wantDeployment.SetCreatedBy("Codertocat") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, Build: wantBuild, @@ -709,7 +814,7 @@ func TestGithub_ProcessWebhook_BadGithubEvent(t *testing.T) { wantHook.SetEvent("foobar") wantHook.SetStatus(constants.StatusSuccess) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: nil, Build: nil, @@ -762,7 +867,7 @@ func TestGithub_ProcessWebhook_BadContentType(t *testing.T) { wantHook.SetEvent("pull_request") wantHook.SetStatus(constants.StatusSuccess) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: nil, Build: nil, @@ -806,7 +911,7 @@ func TestGithub_VerifyWebhook_EmptyRepo(t *testing.T) { client, _ := NewTest(s.URL) // run test - err = client.VerifyWebhook(context.TODO(), request, new(library.Repo)) + err = client.VerifyWebhook(context.TODO(), request, new(api.Repo)) if err != nil { t.Errorf("VerifyWebhook should have returned err") } @@ -817,7 +922,7 @@ func TestGithub_VerifyWebhook_NoSecret(t *testing.T) { s := httptest.NewServer(http.NotFoundHandler()) defer s.Close() - r := new(library.Repo) + r := new(api.Repo) r.SetOrg("Codertocat") r.SetName("Hello-World") r.SetFullName("Codertocat/Hello-World") @@ -889,7 +994,7 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") wantRepo.SetFullName("Codertocat/Hello-World") @@ -899,7 +1004,7 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - wantBuild := new(library.Build) + wantBuild := new(api.Build) wantBuild.SetEvent("comment") wantBuild.SetEventAction("created") wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") @@ -907,12 +1012,13 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { wantBuild.SetTitle("comment received from https://github.com/Codertocat/Hello-World") wantBuild.SetMessage("Update the README with new information") wantBuild.SetSender("Codertocat") + wantBuild.SetSenderSCMID("2172") wantBuild.SetAuthor("Codertocat") wantBuild.SetEmail("") wantBuild.SetRef("refs/pull/1/head") - want := &types.Webhook{ - PullRequest: types.PullRequest{ + want := &internal.Webhook{ + PullRequest: internal.PullRequest{ Comment: "ok to test", Number: wantHook.GetNumber(), }, @@ -927,8 +1033,8 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) { t.Errorf("ProcessWebhook returned err: %v", err) } - if !reflect.DeepEqual(got, want) { - t.Errorf("ProcessWebhook is %v, want %v", got, want) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ProcessWebhook() mismatch (-want +got):\n%s", diff) } } @@ -968,36 +1074,8 @@ func TestGithub_ProcessWebhook_IssueComment_Created(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) - wantRepo.SetOrg("Codertocat") - wantRepo.SetName("Hello-World") - wantRepo.SetFullName("Codertocat/Hello-World") - wantRepo.SetLink("https://github.com/Codertocat/Hello-World") - wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git") - wantRepo.SetBranch("main") - wantRepo.SetPrivate(false) - wantRepo.SetTopics(nil) - - wantBuild := new(library.Build) - wantBuild.SetEvent("comment") - wantBuild.SetEventAction("created") - wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git") - wantBuild.SetSource("https://github.com/Codertocat/Hello-World/issues/1") - wantBuild.SetTitle("comment received from https://github.com/Codertocat/Hello-World") - wantBuild.SetMessage("Update the README with new information") - wantBuild.SetSender("Codertocat") - wantBuild.SetAuthor("Codertocat") - wantBuild.SetEmail("") - wantBuild.SetRef("refs/heads/main") - - want := &types.Webhook{ - PullRequest: types.PullRequest{ - Comment: "ok to test", - Number: 0, - }, - Hook: wantHook, - Repo: wantRepo, - Build: wantBuild, + want := &internal.Webhook{ + Hook: wantHook, } got, err := client.ProcessWebhook(context.TODO(), request) @@ -1047,7 +1125,7 @@ func TestGithub_ProcessWebhook_IssueComment_Deleted(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, } @@ -1098,7 +1176,7 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetActive(true) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") @@ -1109,7 +1187,7 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, } @@ -1161,7 +1239,7 @@ func TestGitHub_ProcessWebhook_RepositoryTransfer(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetActive(true) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") @@ -1172,7 +1250,7 @@ func TestGitHub_ProcessWebhook_RepositoryTransfer(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, } @@ -1224,7 +1302,7 @@ func TestGitHub_ProcessWebhook_RepositoryArchived(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetActive(false) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") @@ -1235,7 +1313,7 @@ func TestGitHub_ProcessWebhook_RepositoryArchived(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, } @@ -1287,7 +1365,7 @@ func TestGitHub_ProcessWebhook_RepositoryEdited(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetActive(true) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") @@ -1298,7 +1376,7 @@ func TestGitHub_ProcessWebhook_RepositoryEdited(t *testing.T) { wantRepo.SetTopics([]string{"cloud", "security"}) wantRepo.SetPrivate(false) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, } @@ -1350,7 +1428,7 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) { wantHook.SetStatus(constants.StatusSuccess) wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks") - wantRepo := new(library.Repo) + wantRepo := new(api.Repo) wantRepo.SetActive(true) wantRepo.SetOrg("Codertocat") wantRepo.SetName("Hello-World") @@ -1361,7 +1439,7 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) { wantRepo.SetPrivate(false) wantRepo.SetTopics(nil) - want := &types.Webhook{ + want := &internal.Webhook{ Hook: wantHook, Repo: wantRepo, } @@ -1400,7 +1478,7 @@ func TestGithub_Redeliver_Webhook(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("octocat") u.SetToken("foo") @@ -1412,7 +1490,7 @@ func TestGithub_Redeliver_Webhook(t *testing.T) { _hook.SetNumber(1) _hook.SetWebhookID(1234) - _repo := new(library.Repo) + _repo := new(api.Repo) _repo.SetID(1) _repo.SetName("bar") _repo.SetOrg("foo") @@ -1444,7 +1522,7 @@ func TestGithub_GetDeliveryID(t *testing.T) { defer s.Close() // setup types - u := new(library.User) + u := new(api.User) u.SetName("octocat") u.SetToken("foo") @@ -1456,7 +1534,7 @@ func TestGithub_GetDeliveryID(t *testing.T) { _hook.SetNumber(1) _hook.SetWebhookID(1234) - _repo := new(library.Repo) + _repo := new(api.Repo) _repo.SetID(1) _repo.SetName("bar") _repo.SetOrg("foo") diff --git a/scm/scm.go b/scm/scm.go index 1614d80ea..e45b33576 100644 --- a/scm/scm.go +++ b/scm/scm.go @@ -5,9 +5,9 @@ package scm import ( "fmt" - "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // New creates and returns a Vela service capable of diff --git a/scm/service.go b/scm/service.go index df4672f3e..e36595de0 100644 --- a/scm/service.go +++ b/scm/service.go @@ -6,7 +6,8 @@ import ( "context" "net/http" - "github.com/go-vela/types" + api "github.com/go-vela/server/api/types" + "github.com/go-vela/server/internal" "github.com/go-vela/types/library" ) @@ -26,11 +27,11 @@ type Service interface { Authorize(context.Context, string) (string, error) // Authenticate defines a function that completes // the OAuth workflow for the session. - Authenticate(context.Context, http.ResponseWriter, *http.Request, string) (*library.User, error) + Authenticate(context.Context, http.ResponseWriter, *http.Request, string) (*api.User, error) // AuthenticateToken defines a function that completes // the OAuth workflow for the session using PAT Token - AuthenticateToken(context.Context, *http.Request) (*library.User, error) + AuthenticateToken(context.Context, *http.Request) (*api.User, error) // ValidateOAuthToken defines a function that validates // an OAuth access token was created by Vela @@ -40,26 +41,32 @@ type Service interface { // the OAuth workflow for the session. Login(context.Context, http.ResponseWriter, *http.Request) (string, error) + // User SCM Interface Functions + + // GetUserID defines a function that captures + // the scm user id attached to the username. + GetUserID(context.Context, string, string) (string, error) + // Access SCM Interface Functions // OrgAccess defines a function that captures // the user's access level for an org. - OrgAccess(context.Context, *library.User, string) (string, error) + OrgAccess(context.Context, *api.User, string) (string, error) // RepoAccess defines a function that captures // the user's access level for a repo. RepoAccess(context.Context, string, string, string, string) (string, error) // TeamAccess defines a function that captures // the user's access level for a team. - TeamAccess(context.Context, *library.User, string, string) (string, error) + TeamAccess(context.Context, *api.User, string, string) (string, error) // RepoContributor defines a function that captures // whether the user is a contributor for a repo. - RepoContributor(context.Context, *library.User, string, string, string) (bool, error) + RepoContributor(context.Context, *api.User, string, string, string) (bool, error) // Teams SCM Interface Functions // ListUsersTeamsForOrg defines a function that captures // the user's teams for an org - ListUsersTeamsForOrg(context.Context, *library.User, string) ([]string, error) + ListUsersTeamsForOrg(context.Context, *api.User, string) ([]string, error) // Changeset SCM Interface Functions @@ -67,86 +74,89 @@ type Service interface { // of files changed for a commit. // // https://en.wikipedia.org/wiki/Changeset. - Changeset(context.Context, *library.User, *library.Repo, string) ([]string, error) + Changeset(context.Context, *api.Repo, string) ([]string, error) // ChangesetPR defines a function that captures the list // of files changed for a pull request. // // https://en.wikipedia.org/wiki/Changeset. - ChangesetPR(context.Context, *library.User, *library.Repo, int) ([]string, error) + ChangesetPR(context.Context, *api.Repo, int) ([]string, error) // Deployment SCM Interface Functions // GetDeployment defines a function that // gets a deployment by number and repo. - GetDeployment(context.Context, *library.User, *library.Repo, int64) (*library.Deployment, error) + GetDeployment(context.Context, *api.User, *api.Repo, int64) (*library.Deployment, error) // GetDeploymentCount defines a function that // counts a list of all deployment for a repo. - GetDeploymentCount(context.Context, *library.User, *library.Repo) (int64, error) + GetDeploymentCount(context.Context, *api.User, *api.Repo) (int64, error) // GetDeploymentList defines a function that gets // a list of all deployments for a repo. - GetDeploymentList(context.Context, *library.User, *library.Repo, int, int) ([]*library.Deployment, error) + GetDeploymentList(context.Context, *api.User, *api.Repo, int, int) ([]*library.Deployment, error) // CreateDeployment defines a function that // creates a new deployment. - CreateDeployment(context.Context, *library.User, *library.Repo, *library.Deployment) error + CreateDeployment(context.Context, *api.User, *api.Repo, *library.Deployment) error // Repo SCM Interface Functions // Config defines a function that captures // the pipeline configuration from a repo. - Config(context.Context, *library.User, *library.Repo, string) ([]byte, error) + Config(context.Context, *api.User, *api.Repo, string) ([]byte, error) // ConfigBackoff is a truncated constant backoff wrapper for Config. // Retry again in five seconds if Config fails to retrieve yaml/yml file. // Will return an error after five failed attempts. - ConfigBackoff(context.Context, *library.User, *library.Repo, string) ([]byte, error) + ConfigBackoff(context.Context, *api.User, *api.Repo, string) ([]byte, error) // Disable defines a function that deactivates // a repo by destroying the webhook. - Disable(context.Context, *library.User, string, string) error + Disable(context.Context, *api.User, string, string) error // Enable defines a function that activates // a repo by creating the webhook. - Enable(context.Context, *library.User, *library.Repo, *library.Hook) (*library.Hook, string, error) + Enable(context.Context, *api.User, *api.Repo, *library.Hook) (*library.Hook, string, error) // Update defines a function that updates // a webhook for a specified repo. - Update(context.Context, *library.User, *library.Repo, int64) (bool, error) + Update(context.Context, *api.User, *api.Repo, int64) (bool, error) // Status defines a function that sends the // commit status for the given SHA from a repo. - Status(context.Context, *library.User, *library.Build, string, string) error + Status(context.Context, *api.User, *api.Build, string, string) error + // StepStatus defines a function that sends the + // commit status for the given SHA for a specified step context. + StepStatus(context.Context, *api.User, *api.Build, *library.Step, string, string) error // ListUserRepos defines a function that retrieves // all repos with admin rights for the user. - ListUserRepos(context.Context, *library.User) ([]*library.Repo, error) + ListUserRepos(context.Context, *api.User) ([]*api.Repo, error) // GetBranch defines a function that retrieves // a branch for a repo. - GetBranch(context.Context, *library.User, *library.Repo, string) (string, string, error) + GetBranch(context.Context, *api.Repo, string) (string, string, error) // GetPullRequest defines a function that retrieves // a pull request for a repo. - GetPullRequest(context.Context, *library.User, *library.Repo, int) (string, string, string, string, error) + GetPullRequest(context.Context, *api.Repo, int) (string, string, string, string, error) // GetRepo defines a function that retrieves // details for a repo. - GetRepo(context.Context, *library.User, *library.Repo) (*library.Repo, int, error) + GetRepo(context.Context, *api.User, *api.Repo) (*api.Repo, int, error) // GetOrgAndRepoName defines a function that retrieves // the name of the org and repo in the SCM. - GetOrgAndRepoName(context.Context, *library.User, string, string) (string, string, error) + GetOrgAndRepoName(context.Context, *api.User, string, string) (string, string, error) // GetOrg defines a function that retrieves // the name for an org in the SCM. - GetOrgName(context.Context, *library.User, string) (string, error) + GetOrgName(context.Context, *api.User, string) (string, error) // GetHTMLURL defines a function that retrieves // a repository file's html_url. - GetHTMLURL(context.Context, *library.User, string, string, string, string) (string, error) + GetHTMLURL(context.Context, *api.User, string, string, string, string) (string, error) // TODO: add comments - CreateChecks(context.Context, *library.Repo, string, string, string) (int64, error) - UpdateChecks(context.Context, *library.Repo, *library.Step, string, string) error + CreateChecks(context.Context, *api.Repo, string, string, string) (int64, error) + UpdateChecks(context.Context, *api.Repo, *library.Step, string, string) error // Webhook SCM Interface Functions // ProcessWebhook defines a function that // parses the webhook from a repo. - ProcessWebhook(context.Context, *http.Request) (*types.Webhook, error) + ProcessWebhook(context.Context, *http.Request) (*internal.Webhook, error) // VerifyWebhook defines a function that // verifies the webhook from a repo. - VerifyWebhook(context.Context, *http.Request, *library.Repo) error + VerifyWebhook(context.Context, *http.Request, *api.Repo) error // RedeliverWebhook defines a function that // redelivers the webhook from the SCM. - RedeliverWebhook(context.Context, *library.User, *library.Repo, *library.Hook) error + RedeliverWebhook(context.Context, *api.User, *api.Repo, *library.Hook) error // TODO: Add convert functions to interface? } diff --git a/scm/setup.go b/scm/setup.go index 92c5d0324..3af48f8c5 100644 --- a/scm/setup.go +++ b/scm/setup.go @@ -6,10 +6,10 @@ import ( "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/scm/github" "github.com/go-vela/types/constants" - - "github.com/sirupsen/logrus" ) // Setup represents the configuration necessary for diff --git a/secret/context_test.go b/secret/context_test.go index 4e92573e7..becf8cea1 100644 --- a/secret/context_test.go +++ b/secret/context_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" "github.com/go-vela/server/secret/native" ) diff --git a/secret/native/count.go b/secret/native/count.go index d76da83cc..460ee077b 100644 --- a/secret/native/count.go +++ b/secret/native/count.go @@ -6,9 +6,10 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" + "github.com/go-vela/types/constants" ) // Count counts a list of secrets. @@ -31,7 +32,7 @@ func (c *client) Count(ctx context.Context, sType, org, name string, teams []str }).Tracef("counting native %s secrets for %s/%s", sType, org, name) // create the repo with the information available - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(org) r.SetName(name) r.SetFullName(fmt.Sprintf("%s/%s", org, name)) diff --git a/secret/native/count_test.go b/secret/native/count_test.go index 33ab448be..e080ec03a 100644 --- a/secret/native/count_test.go +++ b/secret/native/count_test.go @@ -20,7 +20,6 @@ func TestNative_Count(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetCreatedAt(1) sec.SetUpdatedAt(1) diff --git a/secret/native/create.go b/secret/native/create.go index 7d0f026c8..698b25740 100644 --- a/secret/native/create.go +++ b/secret/native/create.go @@ -6,9 +6,10 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Create creates a new secret. diff --git a/secret/native/create_test.go b/secret/native/create_test.go index 0463e33ff..c7c44c22d 100644 --- a/secret/native/create_test.go +++ b/secret/native/create_test.go @@ -22,9 +22,9 @@ func TestNative_Create_Org(t *testing.T) { want.SetValue("baz") want.SetType("org") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) want.SetAllowEvents(library.NewEventsFromMask(1)) want.SetAllowCommand(false) + want.SetAllowSubstitution(false) want.SetCreatedAt(1) want.SetCreatedBy("user") want.SetUpdatedAt(1) @@ -70,9 +70,9 @@ func TestNative_Create_Repo(t *testing.T) { want.SetValue("foob") want.SetType("repo") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) want.SetAllowEvents(library.NewEventsFromMask(1)) want.SetAllowCommand(false) + want.SetAllowSubstitution(false) want.SetCreatedAt(1) want.SetCreatedBy("user") want.SetUpdatedAt(1) @@ -118,9 +118,9 @@ func TestNative_Create_Shared(t *testing.T) { want.SetValue("foob") want.SetType("shared") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) want.SetAllowEvents(library.NewEventsFromMask(1)) want.SetAllowCommand(false) + want.SetAllowSubstitution(false) want.SetCreatedAt(1) want.SetCreatedBy("user") want.SetUpdatedAt(1) @@ -166,9 +166,9 @@ func TestNative_Create_Invalid(t *testing.T) { sec.SetValue("foob") sec.SetType("invalid") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowEvents(library.NewEventsFromMask(1)) sec.SetAllowCommand(false) + sec.SetAllowSubstitution(false) sec.SetCreatedAt(1) sec.SetCreatedBy("user") sec.SetUpdatedAt(1) diff --git a/secret/native/delete.go b/secret/native/delete.go index 54662a9f6..d0fbdc458 100644 --- a/secret/native/delete.go +++ b/secret/native/delete.go @@ -6,8 +6,9 @@ import ( "context" "fmt" - "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // Delete deletes a secret. diff --git a/secret/native/delete_test.go b/secret/native/delete_test.go index 96838d575..ad486b2a1 100644 --- a/secret/native/delete_test.go +++ b/secret/native/delete_test.go @@ -21,7 +21,6 @@ func TestNative_Delete(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowCommand(false) sec.SetCreatedAt(1) sec.SetUpdatedAt(1) diff --git a/secret/native/get.go b/secret/native/get.go index ae80f1d09..da22376e6 100644 --- a/secret/native/get.go +++ b/secret/native/get.go @@ -6,9 +6,11 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Get captures a secret. @@ -33,7 +35,7 @@ func (c *client) Get(ctx context.Context, sType, org, name, path string) (*libra }).Tracef("getting native %s secret %s for %s/%s", sType, path, org, name) // create the repo with the information available - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(org) r.SetName(name) r.SetFullName(fmt.Sprintf("%s/%s", org, name)) diff --git a/secret/native/get_test.go b/secret/native/get_test.go index a835e1143..f20dcdf5b 100644 --- a/secret/native/get_test.go +++ b/secret/native/get_test.go @@ -22,9 +22,9 @@ func TestNative_Get(t *testing.T) { want.SetValue("foob") want.SetType("repo") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) want.SetAllowEvents(library.NewEventsFromMask(1)) want.SetAllowCommand(false) + want.SetAllowSubstitution(false) want.SetCreatedAt(1) want.SetCreatedBy("user") want.SetUpdatedAt(1) diff --git a/secret/native/list.go b/secret/native/list.go index 6e153c422..f1acfc069 100644 --- a/secret/native/list.go +++ b/secret/native/list.go @@ -6,9 +6,11 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // List captures a list of secrets. @@ -36,7 +38,7 @@ func (c *client) List(ctx context.Context, sType, org, name string, page, perPag }).Tracef("listing native %s secrets for %s/%s", sType, org, name) // create the repo with the information available - r := new(library.Repo) + r := new(api.Repo) r.SetOrg(org) r.SetName(name) r.SetFullName(fmt.Sprintf("%s/%s", org, name)) diff --git a/secret/native/list_test.go b/secret/native/list_test.go index 41dbf1691..b201eda73 100644 --- a/secret/native/list_test.go +++ b/secret/native/list_test.go @@ -22,9 +22,9 @@ func TestNative_List(t *testing.T) { sOne.SetValue("foob") sOne.SetType("repo") sOne.SetImages([]string{"foo", "bar"}) - sOne.SetEvents([]string{"foo", "bar"}) sOne.SetAllowEvents(library.NewEventsFromMask(1)) sOne.SetAllowCommand(false) + sOne.SetAllowSubstitution(false) sOne.SetCreatedAt(1) sOne.SetCreatedBy("user") sOne.SetUpdatedAt(1) @@ -39,9 +39,9 @@ func TestNative_List(t *testing.T) { sTwo.SetValue("baz") sTwo.SetType("repo") sTwo.SetImages([]string{"foo", "bar"}) - sTwo.SetEvents([]string{"foo", "bar"}) sTwo.SetAllowEvents(library.NewEventsFromMask(1)) sTwo.SetAllowCommand(false) + sTwo.SetAllowSubstitution(false) sTwo.SetCreatedAt(1) sTwo.SetCreatedBy("user") sTwo.SetUpdatedAt(1) diff --git a/secret/native/native.go b/secret/native/native.go index fb4319673..10b62659a 100644 --- a/secret/native/native.go +++ b/secret/native/native.go @@ -3,8 +3,9 @@ package native import ( - "github.com/go-vela/server/database" "github.com/sirupsen/logrus" + + "github.com/go-vela/server/database" ) // client represents a struct to hold native secret setup. diff --git a/secret/native/update.go b/secret/native/update.go index ece04c5b9..43bfcc011 100644 --- a/secret/native/update.go +++ b/secret/native/update.go @@ -6,9 +6,10 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" ) // Update updates an existing secret. @@ -19,11 +20,6 @@ func (c *client) Update(ctx context.Context, sType, org, name string, s *library return nil, err } - // update the events if set - if len(s.GetEvents()) > 0 { - secret.SetEvents(s.GetEvents()) - } - // update allow events if set if s.GetAllowEvents().ToDatabase() > 0 { secret.SetAllowEvents(s.GetAllowEvents()) @@ -44,6 +40,11 @@ func (c *client) Update(ctx context.Context, sType, org, name string, s *library secret.SetAllowCommand(s.GetAllowCommand()) } + // update allow_substitution if set + if s.AllowSubstitution != nil { + secret.SetAllowSubstitution(s.GetAllowSubstitution()) + } + // update updated_at if set secret.SetUpdatedAt(s.GetUpdatedAt()) diff --git a/secret/native/update_test.go b/secret/native/update_test.go index b5dc82c97..1ec2d597a 100644 --- a/secret/native/update_test.go +++ b/secret/native/update_test.go @@ -23,9 +23,9 @@ func TestNative_Update(t *testing.T) { original.SetValue("secretValue") original.SetType("repo") original.SetImages([]string{"foo", "baz"}) - original.SetEvents([]string{"foob", "bar"}) original.SetAllowEvents(library.NewEventsFromMask(1)) original.SetAllowCommand(true) + original.SetAllowSubstitution(true) original.SetCreatedAt(1) original.SetCreatedBy("user") original.SetUpdatedAt(time.Now().UTC().Unix()) @@ -40,9 +40,9 @@ func TestNative_Update(t *testing.T) { want.SetValue("foob") want.SetType("repo") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) want.SetAllowEvents(library.NewEventsFromMask(3)) want.SetAllowCommand(false) + want.SetAllowSubstitution(false) want.SetCreatedAt(1) want.SetCreatedBy("user") want.SetUpdatedAt(time.Now().UTC().Unix()) diff --git a/secret/secret.go b/secret/secret.go index 75496e449..7dadc4b05 100644 --- a/secret/secret.go +++ b/secret/secret.go @@ -5,9 +5,9 @@ package secret import ( "fmt" - "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" ) // New creates and returns a Vela service capable of diff --git a/secret/setup.go b/secret/setup.go index 2e6e4b6b7..8a9917c0a 100644 --- a/secret/setup.go +++ b/secret/setup.go @@ -7,11 +7,12 @@ import ( "strings" "time" + "github.com/sirupsen/logrus" + "github.com/go-vela/server/database" "github.com/go-vela/server/secret/native" "github.com/go-vela/server/secret/vault" "github.com/go-vela/types/constants" - "github.com/sirupsen/logrus" ) // Setup represents the configuration necessary for diff --git a/secret/vault/count.go b/secret/vault/count.go index a6995d2ee..907165cea 100644 --- a/secret/vault/count.go +++ b/secret/vault/count.go @@ -7,10 +7,10 @@ import ( "fmt" "strings" + "github.com/hashicorp/vault/api" "github.com/sirupsen/logrus" "github.com/go-vela/types/constants" - "github.com/hashicorp/vault/api" ) // Count counts a list of secrets. @@ -34,7 +34,6 @@ func (c *client) Count(ctx context.Context, sType, org, name string, _ []string) c.Logger.WithFields(fields).Tracef("counting vault %s secrets for %s/%s", sType, org, name) - //nolint:staticcheck // ignore false positive vault := new(api.Secret) count := 0 diff --git a/secret/vault/create_test.go b/secret/vault/create_test.go index bcb41b60a..1de42e2bf 100644 --- a/secret/vault/create_test.go +++ b/secret/vault/create_test.go @@ -9,9 +9,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestVault_Create_Org(t *testing.T) { @@ -51,7 +51,13 @@ func TestVault_Create_Org(t *testing.T) { sec.SetValue("baz") sec.SetType("org") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -135,7 +141,13 @@ func TestVault_Create_Repo(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(3)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -220,7 +232,13 @@ func TestVault_Create_Shared(t *testing.T) { sec.SetValue("foob") sec.SetType("shared") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(false) + sec.SetAllowSubstitution(false) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -300,7 +318,6 @@ func TestVault_Create_InvalidSecret(t *testing.T) { sec.SetValue("") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowCommand(false) type args struct { @@ -355,7 +372,6 @@ func TestVault_Create_InvalidType(t *testing.T) { sec.SetValue("foob") sec.SetType("invalid") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowCommand(false) // setup mock server @@ -409,7 +425,6 @@ func TestVault_Create_ClosedServer(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowCommand(false) // setup mock server diff --git a/secret/vault/get.go b/secret/vault/get.go index eeba1e996..fcd083006 100644 --- a/secret/vault/get.go +++ b/secret/vault/get.go @@ -7,11 +7,11 @@ import ( "fmt" "strings" + "github.com/hashicorp/vault/api" "github.com/sirupsen/logrus" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/hashicorp/vault/api" ) // Get captures a secret. diff --git a/secret/vault/get_test.go b/secret/vault/get_test.go index 66f582727..4b64b8ed7 100644 --- a/secret/vault/get_test.go +++ b/secret/vault/get_test.go @@ -9,9 +9,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestVault_Get_Org(t *testing.T) { @@ -51,7 +51,13 @@ func TestVault_Get_Org(t *testing.T) { want.SetValue("baz") want.SetType("org") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) + want.SetAllowCommand(true) + want.SetAllowSubstitution(true) + want.SetAllowEvents(library.NewEventsFromMask(1)) + want.SetCreatedAt(1563474077) + want.SetCreatedBy("octocat") + want.SetUpdatedAt(1563474079) + want.SetUpdatedBy("octocat2") type args struct { version string @@ -135,7 +141,13 @@ func TestVault_Get_Repo(t *testing.T) { want.SetValue("foob") want.SetType("repo") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) + want.SetAllowCommand(true) + want.SetAllowSubstitution(true) + want.SetAllowEvents(library.NewEventsFromMask(3)) + want.SetCreatedAt(1563474077) + want.SetCreatedBy("octocat") + want.SetUpdatedAt(1563474079) + want.SetUpdatedBy("octocat2") type args struct { version string @@ -219,7 +231,13 @@ func TestVault_Get_Shared(t *testing.T) { want.SetValue("foob") want.SetType("shared") want.SetImages([]string{"foo", "bar"}) - want.SetEvents([]string{"foo", "bar"}) + want.SetAllowCommand(false) + want.SetAllowSubstitution(false) + want.SetAllowEvents(library.NewEventsFromMask(1)) + want.SetCreatedAt(1563474077) + want.SetCreatedBy("octocat") + want.SetUpdatedAt(1563474079) + want.SetUpdatedBy("octocat2") type args struct { version string diff --git a/secret/vault/list.go b/secret/vault/list.go index be5638e41..b42d00d66 100644 --- a/secret/vault/list.go +++ b/secret/vault/list.go @@ -7,11 +7,11 @@ import ( "fmt" "strings" + "github.com/hashicorp/vault/api" "github.com/sirupsen/logrus" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" - "github.com/hashicorp/vault/api" ) // List captures a list of secrets. @@ -42,7 +42,7 @@ func (c *client) List(ctx context.Context, sType, org, name string, _, _ int, _ var err error s := []*library.Secret{} - //nolint:staticcheck // ignore false positive + vault := new(api.Secret) // capture the list of secrets from the Vault service diff --git a/secret/vault/list_test.go b/secret/vault/list_test.go index 127a127a2..990c4f020 100644 --- a/secret/vault/list_test.go +++ b/secret/vault/list_test.go @@ -9,9 +9,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestVault_List_Org(t *testing.T) { @@ -66,7 +66,13 @@ func TestVault_List_Org(t *testing.T) { sec.SetValue("baz") sec.SetType("org") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") want := []*library.Secret{sec} @@ -197,7 +203,13 @@ func TestVault_List_Repo(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(3)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") want := []*library.Secret{sec} @@ -313,7 +325,13 @@ func TestVault_List_Shared(t *testing.T) { sec.SetValue("foob") sec.SetType("shared") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(false) + sec.SetAllowSubstitution(false) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") want := []*library.Secret{sec} diff --git a/secret/vault/testdata/v1/org.json b/secret/vault/testdata/v1/org.json index f73f1769e..bfae2f798 100644 --- a/secret/vault/testdata/v1/org.json +++ b/secret/vault/testdata/v1/org.json @@ -16,7 +16,14 @@ "org": "foo", "repo": "*", "type": "org", - "value": "baz" + "value": "baz", + "allow_command": true, + "allow_substitution": true, + "allow_events": 1, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "wrap_info": null, "warnings": null, diff --git a/secret/vault/testdata/v1/repo.json b/secret/vault/testdata/v1/repo.json index f0ef1d417..a4b983696 100644 --- a/secret/vault/testdata/v1/repo.json +++ b/secret/vault/testdata/v1/repo.json @@ -4,19 +4,20 @@ "renewable": false, "lease_duration": 2764800, "data": { - "events": [ - "foo", - "bar" - ], - "images": [ - "foo", - "bar" - ], + "events": ["foo", "bar"], + "images": ["foo", "bar"], "name": "baz", "org": "foo", "repo": "bar", "type": "repo", - "value": "foob" + "value": "foob", + "allow_command": true, + "allow_substitution": true, + "allow_events": 3, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "wrap_info": null, "warnings": null, diff --git a/secret/vault/testdata/v1/shared.json b/secret/vault/testdata/v1/shared.json index 6d070ae6f..e070dff61 100644 --- a/secret/vault/testdata/v1/shared.json +++ b/secret/vault/testdata/v1/shared.json @@ -4,19 +4,20 @@ "renewable": false, "lease_duration": 2764800, "data": { - "events": [ - "foo", - "bar" - ], - "images": [ - "foo", - "bar" - ], + "events": ["foo", "bar"], + "images": ["foo", "bar"], "name": "baz", "org": "foo", "team": "bar", "type": "shared", - "value": "foob" + "value": "foob", + "allow_command": false, + "allow_substitution": false, + "allow_events": 1, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "wrap_info": null, "warnings": null, diff --git a/secret/vault/testdata/v2/org.json b/secret/vault/testdata/v2/org.json index 8aabdab2c..0ae9c8bd1 100644 --- a/secret/vault/testdata/v2/org.json +++ b/secret/vault/testdata/v2/org.json @@ -5,19 +5,20 @@ "renewable": false, "data": { "data": { - "events": [ - "foo", - "bar" - ], - "images": [ - "foo", - "bar" - ], + "events": ["foo", "bar"], + "images": ["foo", "bar"], "name": "bar", "org": "foo", "repo": "*", "type": "org", - "value": "baz" + "value": "baz", + "allow_command": true, + "allow_substitution": true, + "allow_events": 1, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "metadata": { "created_time": "2020-08-14T15:43:44.3462581Z", diff --git a/secret/vault/testdata/v2/repo.json b/secret/vault/testdata/v2/repo.json index bec309b84..29b3e7569 100644 --- a/secret/vault/testdata/v2/repo.json +++ b/secret/vault/testdata/v2/repo.json @@ -5,19 +5,20 @@ "renewable": false, "data": { "data": { - "events": [ - "foo", - "bar" - ], - "images": [ - "foo", - "bar" - ], + "events": ["foo", "bar"], + "images": ["foo", "bar"], "name": "baz", "org": "foo", "repo": "bar", "type": "repo", - "value": "foob" + "value": "foob", + "allow_command": true, + "allow_substitution": true, + "allow_events": 3, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "metadata": { "created_time": "2020-08-14T15:43:44.3462581Z", diff --git a/secret/vault/testdata/v2/shared.json b/secret/vault/testdata/v2/shared.json index ffb6c84f0..961da3c6b 100644 --- a/secret/vault/testdata/v2/shared.json +++ b/secret/vault/testdata/v2/shared.json @@ -5,19 +5,20 @@ "renewable": false, "data": { "data": { - "events": [ - "foo", - "bar" - ], - "images": [ - "foo", - "bar" - ], + "events": ["foo", "bar"], + "images": ["foo", "bar"], "name": "baz", "org": "foo", "team": "bar", "type": "shared", - "value": "foob" + "value": "foob", + "allow_command": false, + "allow_substitution": false, + "allow_events": 1, + "created_at": 1563474077, + "created_by": "octocat", + "updated_at": 1563474079, + "updated_by": "octocat2" }, "metadata": { "created_time": "2020-08-14T15:43:44.3462581Z", diff --git a/secret/vault/update.go b/secret/vault/update.go index 9082b2ff4..a269bd8f3 100644 --- a/secret/vault/update.go +++ b/secret/vault/update.go @@ -45,8 +45,9 @@ func (c *client) Update(ctx context.Context, sType, org, name string, s *library // convert the Vault secret our secret vault := vaultFromSecret(sec) - if len(s.GetEvents()) > 0 { - vault.Data["events"] = s.GetEvents() + + if s.GetAllowEvents().ToDatabase() != 0 { + vault.Data["allow_events"] = s.GetAllowEvents().ToDatabase() } if s.Images != nil { @@ -61,6 +62,10 @@ func (c *client) Update(ctx context.Context, sType, org, name string, s *library vault.Data["allow_command"] = s.GetAllowCommand() } + if s.AllowSubstitution != nil { + vault.Data["allow_substitution"] = s.GetAllowSubstitution() + } + // validate the secret err = database.SecretFromLibrary(secretFromVault(vault)).Validate() if err != nil { diff --git a/secret/vault/update_test.go b/secret/vault/update_test.go index c17c2c936..39fea7d41 100644 --- a/secret/vault/update_test.go +++ b/secret/vault/update_test.go @@ -9,9 +9,9 @@ import ( "reflect" "testing" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + + "github.com/go-vela/types/library" ) func TestVault_Update_Org(t *testing.T) { @@ -66,7 +66,13 @@ func TestVault_Update_Org(t *testing.T) { sec.SetValue("baz") sec.SetType("org") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -166,7 +172,13 @@ func TestVault_Update_Repo(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(true) + sec.SetAllowSubstitution(true) + sec.SetAllowEvents(library.NewEventsFromMask(3)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -266,7 +278,13 @@ func TestVault_Update_Shared(t *testing.T) { sec.SetValue("foob") sec.SetType("shared") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) + sec.SetAllowCommand(false) + sec.SetAllowSubstitution(false) + sec.SetAllowEvents(library.NewEventsFromMask(1)) + sec.SetCreatedAt(1563474077) + sec.SetCreatedBy("octocat") + sec.SetUpdatedAt(1563474079) + sec.SetUpdatedBy("octocat2") type args struct { version string @@ -351,7 +369,6 @@ func TestVault_Update_InvalidSecret(t *testing.T) { sec.SetValue("") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) sec.SetAllowCommand(false) type args struct { @@ -405,7 +422,6 @@ func TestVault_Update_InvalidType(t *testing.T) { sec.SetValue("foob") sec.SetType("invalid") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) // setup mock server fake := httptest.NewServer(http.NotFoundHandler()) @@ -457,7 +473,6 @@ func TestVault_Update_ClosedServer(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) // setup mock server fake := httptest.NewServer(http.NotFoundHandler()) @@ -531,7 +546,6 @@ func TestVault_Update_NoWrite(t *testing.T) { sec.SetValue("foob") sec.SetType("repo") sec.SetImages([]string{"foo", "bar"}) - sec.SetEvents([]string{"foo", "bar"}) type args struct { version string diff --git a/secret/vault/vault.go b/secret/vault/vault.go index 977ea60f9..63745833f 100644 --- a/secret/vault/vault.go +++ b/secret/vault/vault.go @@ -3,14 +3,17 @@ package vault import ( + "encoding/json" "fmt" "time" "github.com/aws/aws-sdk-go/service/sts/stsiface" - "github.com/go-vela/types/library" "github.com/hashicorp/vault/api" "github.com/pkg/errors" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" ) const ( @@ -142,25 +145,44 @@ func secretFromVault(vault *api.Secret) *library.Secret { data = vault.Data } - // set events if found in Vault secret - v, ok := data["events"] + // set allow_events if found in Vault secret + v, ok := data["allow_events"] if ok { - events, ok := v.([]interface{}) + maskJSON, ok := v.(json.Number) + if ok { + mask, err := maskJSON.Int64() + if err == nil { + s.SetAllowEvents(library.NewEventsFromMask(mask)) + } + } + } else { + // if not found, convert events to allow_events + // this happens when vault secret has not been updated since before v0.23 + events, ok := data["events"] if ok { - for _, element := range events { + allowEventsMask := int64(0) + + for _, element := range events.([]interface{}) { event, ok := element.(string) if ok { - s.SetEvents(append(s.GetEvents(), event)) + switch event { + case constants.EventPush: + allowEventsMask |= constants.AllowPushBranch + case constants.EventPull: + allowEventsMask |= constants.AllowPullOpen | constants.AllowPullReopen | constants.AllowPullSync + case constants.EventComment: + allowEventsMask |= constants.AllowCommentCreate | constants.AllowCommentEdit + case constants.EventDeploy: + allowEventsMask |= constants.AllowDeployCreate + case constants.EventTag: + allowEventsMask |= constants.AllowPushTag + case constants.EventSchedule: + allowEventsMask |= constants.AllowSchedule + } } } - } - } - v, ok = data["allow_events"] - if ok { - mask, ok := v.(int64) - if ok { - s.SetAllowEvents(library.NewEventsFromMask(mask)) + s.SetAllowEvents(library.NewEventsFromMask(allowEventsMask)) } } @@ -241,12 +263,33 @@ func secretFromVault(vault *api.Secret) *library.Secret { } } + // set allow_substitution if found in Vault secret + v, ok = data["allow_substitution"] + if ok { + substitution, ok := v.(bool) + if ok { + s.SetAllowSubstitution(substitution) + } + } else { + // set allow_substitution to allow_command value if not found in Vault secret + cmd, ok := data["allow_command"] + if ok { + command, ok := cmd.(bool) + if ok { + s.SetAllowSubstitution(command) + } + } + } + // set created_at if found in Vault secret v, ok = data["created_at"] if ok { - createdAt, ok := v.(int64) + createdAtJSON, ok := v.(json.Number) if ok { - s.SetCreatedAt(createdAt) + createdAt, err := createdAtJSON.Int64() + if err == nil { + s.SetCreatedAt(createdAt) + } } } @@ -262,9 +305,12 @@ func secretFromVault(vault *api.Secret) *library.Secret { // set updated_at if found in Vault secret v, ok = data["updated_at"] if ok { - updatedAt, ok := v.(int64) + updatedAtJSON, ok := v.(json.Number) if ok { - s.SetUpdatedAt(updatedAt) + updatedAt, err := updatedAtJSON.Int64() + if err == nil { + s.SetUpdatedAt(updatedAt) + } } } @@ -286,11 +332,6 @@ func vaultFromSecret(s *library.Secret) *api.Secret { vault := new(api.Secret) vault.Data = data - // set events if found in Vela secret - if len(s.GetEvents()) > 0 { - vault.Data["events"] = s.GetEvents() - } - // set allow events to mask if s.GetAllowEvents().ToDatabase() != 0 { vault.Data["allow_events"] = s.GetAllowEvents().ToDatabase() @@ -336,6 +377,11 @@ func vaultFromSecret(s *library.Secret) *api.Secret { vault.Data["allow_command"] = s.GetAllowCommand() } + // set allow_substitution if found in Vela secret + if s.AllowSubstitution != nil { + vault.Data["allow_substitution"] = s.GetAllowSubstitution() + } + // set created_at if found in Vela secret if s.GetCreatedAt() > 0 { vault.Data["created_at"] = s.GetCreatedAt() diff --git a/secret/vault/vault_test.go b/secret/vault/vault_test.go index 9cee893d9..a60d7bd75 100644 --- a/secret/vault/vault_test.go +++ b/secret/vault/vault_test.go @@ -3,13 +3,17 @@ package vault import ( + "encoding/json" "net/http" "net/http/httptest" "reflect" + "strings" "testing" - "github.com/go-vela/types/library" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/vault/api" + + "github.com/go-vela/types/library" ) func TestVault_New(t *testing.T) { @@ -93,29 +97,20 @@ func TestVault_New_Error(t *testing.T) { func TestVault_secretFromVault(t *testing.T) { // setup types inputV1 := &api.Secret{ + Data: testVaultSecretData(), + } + + inputV2 := &api.Secret{ Data: map[string]interface{}{ - "events": []interface{}{"foo", "bar"}, - "allow_events": int64(1), - "images": []interface{}{"foo", "bar"}, - "name": "bar", - "org": "foo", - "repo": "*", - "team": "foob", - "type": "org", - "value": "baz", - "allow_command": true, - "created_at": int64(1563474077), - "created_by": "octocat", - "updated_at": int64(1563474079), - "updated_by": "octocat2", + "data": testVaultSecretData(), }, } - inputV2 := &api.Secret{ + // test vault secret from pre-v0.23 release + inputLegacy := &api.Secret{ Data: map[string]interface{}{ "data": map[string]interface{}{ - "events": []interface{}{"foo", "bar"}, - "allow_events": int64(1), + "events": []interface{}{"push", "tag", "deployment"}, "images": []interface{}{"foo", "bar"}, "name": "bar", "org": "foo", @@ -124,9 +119,9 @@ func TestVault_secretFromVault(t *testing.T) { "type": "org", "value": "baz", "allow_command": true, - "created_at": int64(1563474077), + "created_at": json.Number("1563474077"), "created_by": "octocat", - "updated_at": int64(1563474079), + "updated_at": json.Number("1563474079"), "updated_by": "octocat2", }, }, @@ -139,10 +134,10 @@ func TestVault_secretFromVault(t *testing.T) { want.SetName("bar") want.SetValue("baz") want.SetType("org") - want.SetEvents([]string{"foo", "bar"}) - want.SetAllowEvents(library.NewEventsFromMask(1)) + want.SetAllowEvents(library.NewEventsFromMask(8195)) want.SetImages([]string{"foo", "bar"}) want.SetAllowCommand(true) + want.SetAllowSubstitution(true) want.SetCreatedAt(1563474077) want.SetCreatedBy("octocat") want.SetUpdatedAt(1563474079) @@ -158,6 +153,7 @@ func TestVault_secretFromVault(t *testing.T) { }{ {"v1", args{secret: inputV1}}, {"v2", args{secret: inputV2}}, + {"legacy", args{secret: inputLegacy}}, } for _, tt := range tests { @@ -180,10 +176,10 @@ func TestVault_vaultFromSecret(t *testing.T) { s.SetName("bar") s.SetValue("baz") s.SetType("org") - s.SetEvents([]string{"foo", "bar"}) s.SetAllowEvents(library.NewEventsFromMask(1)) s.SetImages([]string{"foo", "bar"}) s.SetAllowCommand(true) + s.SetAllowSubstitution(true) s.SetCreatedAt(1563474077) s.SetCreatedBy("octocat") s.SetUpdatedAt(1563474079) @@ -191,27 +187,73 @@ func TestVault_vaultFromSecret(t *testing.T) { want := &api.Secret{ Data: map[string]interface{}{ - "events": []string{"foo", "bar"}, - "allow_events": int64(1), - "images": []string{"foo", "bar"}, - "name": "bar", - "org": "foo", - "repo": "*", - "team": "foob", - "type": "org", - "value": "baz", - "allow_command": true, - "created_at": int64(1563474077), - "created_by": "octocat", - "updated_at": int64(1563474079), - "updated_by": "octocat2", + "allow_events": int64(1), + "images": []string{"foo", "bar"}, + "name": "bar", + "org": "foo", + "repo": "*", + "team": "foob", + "type": "org", + "value": "baz", + "allow_command": true, + "allow_substitution": true, + "created_at": int64(1563474077), + "created_by": "octocat", + "updated_at": int64(1563474079), + "updated_by": "octocat2", }, } // run test got := vaultFromSecret(s) - if !reflect.DeepEqual(got, want) { - t.Errorf("vaultFromSecret is %v, want %v", got, want) + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("vaultFromSecret() mismatch (-got +want):\n%s", diff) + } +} + +func TestVault_AccurateSecretFields(t *testing.T) { + testSecret := library.Secret{} + + tSecret := reflect.TypeOf(testSecret) + + vaultSecret := testVaultSecretData() + + for i := 0; i < tSecret.NumField(); i++ { + field := tSecret.Field(i) + + jsonTag := field.Tag.Get("json") + if jsonTag == "" { + continue + } + + jsonTag = strings.Split(jsonTag, ",")[0] + if strings.EqualFold(jsonTag, "id") { + continue // skip id field + } + + if vaultSecret[jsonTag] == nil { + t.Errorf("vaultSecret missing field with JSON tag %s", jsonTag) + } + } +} + +// helper function to return a test Vault secret data. +func testVaultSecretData() map[string]interface{} { + return map[string]interface{}{ + "allow_events": json.Number("8195"), + "images": []interface{}{"foo", "bar"}, + "name": "bar", + "org": "foo", + "repo": "*", + "team": "foob", + "type": "org", + "value": "baz", + "allow_command": true, + "allow_substitution": true, + "created_at": json.Number("1563474077"), + "created_by": "octocat", + "updated_at": json.Number("1563474079"), + "updated_by": "octocat2", } } diff --git a/util/encryption.go b/util/encryption.go new file mode 100644 index 000000000..06124d357 --- /dev/null +++ b/util/encryption.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +package util + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" +) + +// Decrypt is a helper function to Decrypt values. First +// a AES-256 Galois Counter Mode cipher block is created +// from the encryption key to Decrypt the value. Then, we +// verify the value isn't smaller than the nonce which +// would indicate the value isn't encrypted. Finally the +// cipher block and nonce is used to Decrypt the value. +func Decrypt(key string, value []byte) ([]byte, error) { + // create a new cipher block from the encryption key + // + // the key should have a length of 64 bits to ensure + // we are using the AES-256 standard + // + // https://en.wikipedia.org/wiki/Advanced_Encryption_Standard + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return value, err + } + + // creates a new Galois Counter Mode cipher block + gcm, err := cipher.NewGCM(block) + if err != nil { + return value, err + } + + // nonce is an arbitrary number used to to ensure that + // old communications cannot be reused in replay attacks. + // + // https://en.wikipedia.org/wiki/Cryptographic_nonce + nonceSize := gcm.NonceSize() + + // verify the value has a length greater than the nonce + // + // if the value is less than the nonce size, then we + // can assume the value hasn't been encrypted yet. + if len(value) < nonceSize { + return value, fmt.Errorf("invalid value length for decrypt provided: %d", len(value)) + } + + // capture nonce and ciphertext from the value + nonce, ciphertext := value[:nonceSize], value[nonceSize:] + + // decrypt the value from the ciphertext + return gcm.Open(nil, nonce, ciphertext, nil) +} + +// Encrypt is a helper function to Encrypt values. First +// a AES-256 Galois Counter Mode cipher block is created +// from the encryption key to Encrypt the value. Then, +// we create the nonce from a cryptographically secure +// random number generator. Finally, the cipher block +// and nonce is used to Encrypt the value. +func Encrypt(key string, value []byte) ([]byte, error) { + // create a new cipher block from the encryption key + // + // the key should have a length of 64 bits to ensure + // we are using the AES-256 standard + // + // https://en.wikipedia.org/wiki/Advanced_Encryption_Standard + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return value, err + } + + // creates a new Galois Counter Mode cipher block + gcm, err := cipher.NewGCM(block) + if err != nil { + return value, err + } + + // nonce is an arbitrary number used to to ensure that + // old communications cannot be reused in replay attacks. + // + // https://en.wikipedia.org/wiki/Cryptographic_nonce + nonce := make([]byte, gcm.NonceSize()) + + // set nonce from a cryptographically secure random number generator + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return value, err + } + + // encrypt the value with the randomly generated nonce + return gcm.Seal(nonce, nonce, value, nil), nil +} diff --git a/util/encryption_test.go b/util/encryption_test.go new file mode 100644 index 000000000..efe2c8791 --- /dev/null +++ b/util/encryption_test.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 + +package util + +import ( + "testing" +) + +func TestDatabase_decrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + value := []byte("abc") + + encrypted, err := Encrypt(key, value) + if err != nil { + t.Errorf("unable to encrypt value: %v", err) + } + + // setup tests + tests := []struct { + failure bool + key string + value []byte + }{ + { + failure: false, + key: key, + value: encrypted, + }, + { + failure: true, + key: "", + value: encrypted, + }, + { + failure: true, + key: key, + value: value, + }, + } + + // run tests + for _, test := range tests { + _, err := Decrypt(test.key, test.value) + + if test.failure { + if err == nil { + t.Errorf("decrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("decrypt returned err: %v", err) + } + } +} + +func TestDatabase_encrypt(t *testing.T) { + // setup types + key := "C639A572E14D5075C526FDDD43E4ECF6" + value := []byte("abc") + + // setup tests + tests := []struct { + failure bool + key string + value []byte + }{ + { + failure: false, + key: key, + value: value, + }, + { + failure: true, + key: "", + value: value, + }, + } + + // run tests + for _, test := range tests { + _, err := Encrypt(test.key, test.value) + + if test.failure { + if err == nil { + t.Errorf("encrypt should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("encrypt returned err: %v", err) + } + } +} diff --git a/util/util.go b/util/util.go index 840dbc04f..b4a1a29ae 100644 --- a/util/util.go +++ b/util/util.go @@ -3,21 +3,32 @@ package util import ( + "context" "html" + "net/url" "strings" - "github.com/go-vela/types/library" - "github.com/gin-gonic/gin" + "github.com/microcosm-cc/bluemonday" + + api "github.com/go-vela/server/api/types" "github.com/go-vela/types" ) // HandleError appends the error to the handler chain for logging and outputs it. -func HandleError(c *gin.Context, status int, err error) { +func HandleError(c context.Context, status int, err error) { msg := err.Error() - //nolint:errcheck // ignore checking error - c.Error(err) - c.AbortWithStatusJSON(status, types.Error{Message: &msg}) + + switch ctx := c.(type) { + case *gin.Context: + //nolint:errcheck // ignore checking error + ctx.Error(err) + ctx.AbortWithStatusJSON(status, types.Error{Message: &msg}) + + return + default: + return + } } // MaxInt is a helper function to clamp the integer which @@ -106,7 +117,7 @@ func Unique(stringSlice []string) []string { // allowlist are specified. // // a single entry of '*' allows any repo to be enabled. -func CheckAllowlist(r *library.Repo, allowlist []string) bool { +func CheckAllowlist(r *api.Repo, allowlist []string) bool { // check if all repos are allowed to be enabled if len(allowlist) == 1 && allowlist[0] == "*" { return true @@ -128,3 +139,34 @@ func CheckAllowlist(r *library.Repo, allowlist []string) bool { return false } + +// Sanitize is a helper function to verify the provided input +// field does not contain HTML content. If the input field +// does contain HTML, then the function will sanitize and +// potentially remove the HTML if deemed malicious. +func Sanitize(field string) string { + // create new HTML input microcosm-cc/bluemonday policy + p := bluemonday.StrictPolicy() + + // create a URL query unescaped string from the field + queryUnescaped, err := url.QueryUnescape(field) + if err != nil { + // overwrite URL query unescaped string with field + queryUnescaped = field + } + + // create an HTML escaped string from the field + htmlEscaped := html.EscapeString(queryUnescaped) + + // create a microcosm-cc/bluemonday escaped string from the field + bluemondayEscaped := p.Sanitize(queryUnescaped) + + // check if the field contains html + if !strings.EqualFold(htmlEscaped, bluemondayEscaped) { + // create new HTML input microcosm-cc/bluemonday policy + return bluemondayEscaped + } + + // return the unmodified field + return field +} diff --git a/util/util_test.go b/util/util_test.go new file mode 100644 index 000000000..bf4c458f1 --- /dev/null +++ b/util/util_test.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +package util + +import ( + "testing" +) + +func TestUtil_Sanitize(t *testing.T) { + // setup tests + tests := []struct { + name string + value string + want string + }{ + { + name: "percent", + value: `%`, + want: `%`, + }, + { + name: "quoted", + value: `"hello"`, + want: `"hello"`, + }, + { + name: "email", + value: `OctoKitty@github.com`, + want: `OctoKitty@github.com`, + }, + { + name: "url", + value: `https://github.com/go-vela`, + want: `https://github.com/go-vela`, + }, + { + name: "encoded", + value: `+ added foo %25 + updated bar %22 +`, + want: `+ added foo %25 + updated bar %22 +`, + }, + { + name: "html with headers", + value: `Merge pull request #1 from me/patch-1\n\n