diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json index e5ad2db7..dc1510de 100644 --- a/.github/linters/.jscpd.json +++ b/.github/linters/.jscpd.json @@ -4,6 +4,7 @@ "**/ISSUE_TEMPLATE/bug_report.yml", "**/ISSUE_TEMPLATE/feature_request.yml", "**/node_modules/**", + "**/test/data/**", "**/test/linters/ansible/**", "**/test/linters/clojure", "**/test/linters/cloudformation", diff --git a/Makefile b/Makefile index f9fc715e..672d01f1 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ lint-codebase: ## Lint the entire codebase $(SUPER_LINTER_TEST_CONTAINER_URL) .phony: test-lib -test-lib: test-build-file-list ## Test super-linter +test-lib: test-build-file-list test-github-event test-validation ## Test super-linter .phony: test-build-file-list test-build-file-list: ## Test buildFileList @@ -164,6 +164,22 @@ test-build-file-list: ## Test buildFileList --entrypoint /tmp/lint/test/lib/buildFileListTest.sh \ $(SUPER_LINTER_TEST_CONTAINER_URL) +.phony: test-github-event +test-github-event: ## Test githubEvent + docker run \ + -v "$(CURDIR):/tmp/lint" \ + -w /tmp/lint \ + --entrypoint /tmp/lint/test/lib/githubEventTest.sh \ + $(SUPER_LINTER_TEST_CONTAINER_URL) + +.phony: test-validation +test-validation: ## Test validation + docker run \ + -v "$(CURDIR):/tmp/lint" \ + -w /tmp/lint \ + --entrypoint /tmp/lint/test/lib/validationTest.sh \ + $(SUPER_LINTER_TEST_CONTAINER_URL) + # Run this test against a small directory because we're only interested in # loading default configuration files. The directory that we run super-linter # against should not be .github because that includes default linter rules. @@ -228,4 +244,4 @@ release-please-dry-run: build-dev-container-image check-github-token ## Run rele --repo-url super-linter/super-linter \ --target-branch ${RELEASE_PLEASE_TARGET_BRANCH} \ --token "$(shell cat "${GITHUB_TOKEN_PATH}")" \ - --trace \ + --trace diff --git a/lib/functions/githubEvent.sh b/lib/functions/githubEvent.sh new file mode 100755 index 00000000..2664875c --- /dev/null +++ b/lib/functions/githubEvent.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +function GetGithubPushEventCommitCount() { + local GITHUB_EVENT_FILE_PATH + GITHUB_EVENT_FILE_PATH="${1}" + local GITHUB_PUSH_COMMIT_COUNT + GITHUB_PUSH_COMMIT_COUNT=$(jq -r '.commits | length' <"${GITHUB_EVENT_FILE_PATH}") + ERROR_CODE=$? + if [ ${ERROR_CODE} -ne 0 ]; then + fatal "Failed to initialize GITHUB_PUSH_COMMIT_COUNT for a push event. Error code: ${ERROR_CODE}. Output: ${GITHUB_PUSH_COMMIT_COUNT}" + fi + + if IsUnsignedInteger "${GITHUB_PUSH_COMMIT_COUNT}" && [ -n "${GITHUB_PUSH_COMMIT_COUNT}" ]; then + echo "${GITHUB_PUSH_COMMIT_COUNT}" + return 0 + else + fatal "GITHUB_PUSH_COMMIT_COUNT is not an integer: ${GITHUB_PUSH_COMMIT_COUNT}" + fi +} diff --git a/lib/functions/validation.sh b/lib/functions/validation.sh index 4b726883..8cd8420c 100755 --- a/lib/functions/validation.sh +++ b/lib/functions/validation.sh @@ -231,17 +231,53 @@ function ValidateLocalGitRepository() { debug "Git branches: $(git -C "${GITHUB_WORKSPACE}" branch -a)" } +function CheckIfGitRefExists() { + local GIT_REF=${1} + if git -C "${GITHUB_WORKSPACE}" cat-file -e "${GIT_REF}"; then + return 0 + else + return 1 + fi +} + +function IsUnsignedInteger() { + case ${1} in + '' | *[!0-9]*) + return 1 + ;; + *) + return 0 + ;; + esac +} + function ValidateGitShaReference() { debug "Git HEAD: $(git -C "${GITHUB_WORKSPACE}" show HEAD --stat)" debug "Validate that the GITHUB_SHA reference (${GITHUB_SHA}) exists in this Git repository." - if ! git -C "${GITHUB_WORKSPACE}" cat-file -e "${GITHUB_SHA}"; then + if ! CheckIfGitRefExists "${GITHUB_SHA}"; then fatal "The GITHUB_SHA reference (${GITHUB_SHA}) doesn't exist in this Git repository" else debug "The GITHUB_SHA reference (${GITHUB_SHA}) exists in this repository" fi } +function ValidateGitBeforeShaReference() { + debug "Validating GITHUB_BEFORE_SHA: ${GITHUB_BEFORE_SHA}" + if [ -z "${GITHUB_BEFORE_SHA}" ] || + [ "${GITHUB_BEFORE_SHA}" == "null" ] || + [ "${GITHUB_BEFORE_SHA}" == "0000000000000000000000000000000000000000" ]; then + fatal "Failed to get GITHUB_BEFORE_SHA: [${GITHUB_BEFORE_SHA}]" + fi + + debug "Validate that the GITHUB_BEFORE_SHA reference (${GITHUB_BEFORE_SHA}) exists in this Git repository." + if ! CheckIfGitRefExists "${GITHUB_BEFORE_SHA}"; then + fatal "The GITHUB_BEFORE_SHA reference (${GITHUB_BEFORE_SHA}) doesn't exist in this Git repository" + else + debug "The GITHUB_BEFORE_SHA reference (${GITHUB_BEFORE_SHA}) exists in this repository" + fi +} + function ValidateDefaultGitBranch() { debug "Check if the default branch (${DEFAULT_BRANCH}) exists" if ! CheckIfGitBranchExists "${DEFAULT_BRANCH}"; then diff --git a/lib/linter.sh b/lib/linter.sh index 99ddfcf8..b1eaefba 100755 --- a/lib/linter.sh +++ b/lib/linter.sh @@ -57,6 +57,8 @@ source /action/lib/functions/validation.sh # Source the function script(s) source /action/lib/functions/worker.sh # Source the function script(s) # shellcheck source=/dev/null source /action/lib/functions/setupSSH.sh # Source the function script(s) +# shellcheck source=/dev/null +source /action/lib/functions/githubEvent.sh # Initialize RUN_LOCAL early because we need it for logging DEFAULT_RUN_LOCAL='false' @@ -561,33 +563,24 @@ GetGitHubVars() { elif [ "${GITHUB_EVENT_NAME}" == "push" ]; then debug "This is a GitHub push event." - GITHUB_IS_FORCE_PUSH="$(jq -r .forced <"$GITHUB_EVENT_PATH")" - ERROR_CODE=$? - debug "GITHUB_IS_FORCE_PUSH initialization error code: ${ERROR_CODE}" - if [ ${ERROR_CODE} -ne 0 ]; then - fatal "Failed to initialize GITHUB_IS_FORCE_PUSH for a push event. Output: ${GITHUB_IS_FORCE_PUSH}" - else - debug "Initialized GITHUB_IS_FORCE_PUSH: ${GITHUB_IS_FORCE_PUSH}" + GITHUB_PUSH_COMMIT_COUNT=$(GetGithubPushEventCommitCount "$GITHUB_EVENT_PATH") + if [ -z "${GITHUB_PUSH_COMMIT_COUNT}" ]; then + fatal "Failed to get GITHUB_PUSH_COMMIT_COUNT" fi + info "Successfully found:${F[W]}[GITHUB_PUSH_COMMIT_COUNT]${F[B]}, value:${F[W]}[${GITHUB_PUSH_COMMIT_COUNT}]" - if [ "${GITHUB_IS_FORCE_PUSH}" == "true" ]; then - debug "This is a forced push. Get the hash of the previous commit from Git because the previous commit referenced in the GitHub event payload may not exist anymore" - GITHUB_BEFORE_SHA=$(git -C "${GITHUB_WORKSPACE}" rev-parse HEAD^) - else - debug "This isn't a forced push. Get the hash of the previous commit from the GitHub event that triggered this workflow" - GITHUB_BEFORE_SHA=$(jq -r .before <"$GITHUB_EVENT_PATH") - fi + # Ref: https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + debug "Get the hash of the commit to start the diff from from Git because the GitHub push event payload may not contain references to base_ref or previous commit." + # shellcheck disable=SC2086 # We checked that GITHUB_PUSH_COMMIT_COUNT is an integer + GITHUB_BEFORE_SHA=$(git -C "${GITHUB_WORKSPACE}" rev-parse HEAD~${GITHUB_PUSH_COMMIT_COUNT}) ERROR_CODE=$? debug "GITHUB_BEFORE_SHA initialization error code: ${ERROR_CODE}" if [ ${ERROR_CODE} -ne 0 ]; then fatal "Failed to initialize GITHUB_BEFORE_SHA for a push event. Output: ${GITHUB_BEFORE_SHA}" fi - if [ -z "${GITHUB_BEFORE_SHA}" ] || [ "${GITHUB_BEFORE_SHA}" == "null" ]; then - fatal "Failed to get GITHUB_BEFORE_SHA: [${GITHUB_BEFORE_SHA}]" - else - info "Successfully found:${F[W]}[GITHUB_BEFORE_SHA]${F[B]}, value:${F[W]}[${GITHUB_BEFORE_SHA}]" - fi + ValidateGitBeforeShaReference + info "Successfully found:${F[W]}[GITHUB_BEFORE_SHA]${F[B]}, value:${F[W]}[${GITHUB_BEFORE_SHA}]" fi ############################ diff --git a/test/data/github-event/github-event-push.json b/test/data/github-event/github-event-push.json new file mode 100644 index 00000000..64619229 --- /dev/null +++ b/test/data/github-event/github-event-push.json @@ -0,0 +1,216 @@ +{ + "after": "a15934149b772d82713180257853c4908f9c3519", + "base_ref": null, + "before": "0000000000000000000000000000000000000000", + "commits": [ + { + "author": { + "email": "test@example.com", + "name": "Test user", + "username": "testuser" + }, + "committer": { + "email": "test@example.com", + "name": "Test user", + "username": "testuser" + }, + "distinct": true, + "id": "a15934149b772d82713180257853c4908f9c3519", + "message": "fix: fix GITHUB_BEFORE_SHA initalization for push", + "timestamp": "2024-01-04T23:56:39+01:00", + "tree_id": "ecee91366ca7c3d196eb46428632a93cc860a29e", + "url": "https://github.com/super-linter/super-linter/commit/a15934149b772d82713180257853c4908f9c3519" + } + ], + "compare": "https://github.com/super-linter/super-linter/commit/a15934149b77", + "created": true, + "deleted": false, + "enterprise": { + "avatar_url": "https://avatars.githubusercontent.com/b/20446?v=4", + "created_at": "2023-01-21T00:51:24Z", + "description": "", + "html_url": "https://github.com/enterprises/opensource", + "id": 20446, + "name": "GitHub Open Source", + "node_id": "E_kgDNT94", + "slug": "opensource", + "updated_at": "2023-02-28T14:58:45Z", + "website_url": "https://github.com/" + }, + "forced": false, + "head_commit": { + "author": { + "email": "test@example.com", + "name": "Test user", + "username": "testuser" + }, + "committer": { + "email": "test@example.com", + "name": "Test user", + "username": "testuser" + }, + "distinct": true, + "id": "a15934149b772d82713180257853c4908f9c3519", + "message": "fix: fix GITHUB_BEFORE_SHA initalization for push", + "timestamp": "2024-01-04T23:56:39+01:00", + "tree_id": "ecee91366ca7c3d196eb46428632a93cc860a29e", + "url": "https://github.com/super-linter/super-linter/commit/a15934149b772d82713180257853c4908f9c3519" + }, + "organization": { + "avatar_url": "https://avatars.githubusercontent.com/u/74564708?v=4", + "description": "", + "events_url": "https://api.github.com/orgs/super-linter/events", + "hooks_url": "https://api.github.com/orgs/super-linter/hooks", + "id": 74564708, + "issues_url": "https://api.github.com/orgs/super-linter/issues", + "login": "super-linter", + "members_url": "https://api.github.com/orgs/super-linter/members{/member}", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc0NTY0NzA4", + "public_members_url": "https://api.github.com/orgs/super-linter/public_members{/member}", + "repos_url": "https://api.github.com/orgs/super-linter/repos", + "url": "https://api.github.com/orgs/super-linter" + }, + "pusher": { + "email": "test@example.com", + "name": "testuser" + }, + "ref": "refs/heads/fix-before-sha-pull-events", + "repository": { + "allow_forking": true, + "archive_url": "https://api.github.com/repos/super-linter/super-linter/{archive_format}{/ref}", + "archived": false, + "assignees_url": "https://api.github.com/repos/super-linter/super-linter/assignees{/user}", + "blobs_url": "https://api.github.com/repos/super-linter/super-linter/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/super-linter/super-linter/branches{/branch}", + "clone_url": "https://github.com/super-linter/super-linter.git", + "collaborators_url": "https://api.github.com/repos/super-linter/super-linter/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/super-linter/super-linter/comments{/number}", + "commits_url": "https://api.github.com/repos/super-linter/super-linter/commits{/sha}", + "compare_url": "https://api.github.com/repos/super-linter/super-linter/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/super-linter/super-linter/contents/{+path}", + "contributors_url": "https://api.github.com/repos/super-linter/super-linter/contributors", + "created_at": 1571666210, + "custom_properties": {}, + "default_branch": "main", + "deployments_url": "https://api.github.com/repos/super-linter/super-linter/deployments", + "description": "Combination of multiple linters to install as a GitHub Action", + "disabled": false, + "downloads_url": "https://api.github.com/repos/super-linter/super-linter/downloads", + "events_url": "https://api.github.com/repos/super-linter/super-linter/events", + "fork": false, + "forks": 1053, + "forks_count": 1053, + "forks_url": "https://api.github.com/repos/super-linter/super-linter/forks", + "full_name": "super-linter/super-linter", + "git_commits_url": "https://api.github.com/repos/super-linter/super-linter/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/super-linter/super-linter/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/super-linter/super-linter/git/tags{/sha}", + "git_url": "git://github.com/super-linter/super-linter.git", + "has_discussions": true, + "has_downloads": true, + "has_issues": true, + "has_pages": false, + "has_projects": false, + "has_wiki": false, + "homepage": "https://github.com/super-linter/super-linter", + "hooks_url": "https://api.github.com/repos/super-linter/super-linter/hooks", + "html_url": "https://github.com/super-linter/super-linter", + "id": 216581567, + "is_template": false, + "issue_comment_url": "https://api.github.com/repos/super-linter/super-linter/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/super-linter/super-linter/issues/events{/number}", + "issues_url": "https://api.github.com/repos/super-linter/super-linter/issues{/number}", + "keys_url": "https://api.github.com/repos/super-linter/super-linter/keys{/key_id}", + "labels_url": "https://api.github.com/repos/super-linter/super-linter/labels{/name}", + "language": "Shell", + "languages_url": "https://api.github.com/repos/super-linter/super-linter/languages", + "license": { + "key": "mit", + "name": "MIT License", + "node_id": "MDc6TGljZW5zZTEz", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit" + }, + "master_branch": "main", + "merges_url": "https://api.github.com/repos/super-linter/super-linter/merges", + "milestones_url": "https://api.github.com/repos/super-linter/super-linter/milestones{/number}", + "mirror_url": null, + "name": "super-linter", + "node_id": "MDEwOlJlcG9zaXRvcnkyMTY1ODE1Njc=", + "notifications_url": "https://api.github.com/repos/super-linter/super-linter/notifications{?since,all,participating}", + "open_issues": 37, + "open_issues_count": 37, + "organization": "super-linter", + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/74564708?v=4", + "email": null, + "events_url": "https://api.github.com/users/super-linter/events{/privacy}", + "followers_url": "https://api.github.com/users/super-linter/followers", + "following_url": "https://api.github.com/users/super-linter/following{/other_user}", + "gists_url": "https://api.github.com/users/super-linter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/super-linter", + "id": 74564708, + "login": "super-linter", + "name": "super-linter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc0NTY0NzA4", + "organizations_url": "https://api.github.com/users/super-linter/orgs", + "received_events_url": "https://api.github.com/users/super-linter/received_events", + "repos_url": "https://api.github.com/users/super-linter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/super-linter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/super-linter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/super-linter" + }, + "private": false, + "pulls_url": "https://api.github.com/repos/super-linter/super-linter/pulls{/number}", + "pushed_at": 1704409034, + "releases_url": "https://api.github.com/repos/super-linter/super-linter/releases{/id}", + "size": 27390, + "ssh_url": "git@github.com:super-linter/super-linter.git", + "stargazers": 8984, + "stargazers_count": 8984, + "stargazers_url": "https://api.github.com/repos/super-linter/super-linter/stargazers", + "statuses_url": "https://api.github.com/repos/super-linter/super-linter/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/super-linter/super-linter/subscribers", + "subscription_url": "https://api.github.com/repos/super-linter/super-linter/subscription", + "svn_url": "https://github.com/super-linter/super-linter", + "tags_url": "https://api.github.com/repos/super-linter/super-linter/tags", + "teams_url": "https://api.github.com/repos/super-linter/super-linter/teams", + "topics": [ + "actions", + "ci", + "hacktoberfest", + "linter", + "quality-check" + ], + "trees_url": "https://api.github.com/repos/super-linter/super-linter/git/trees{/sha}", + "updated_at": "2024-01-04T16:38:53Z", + "url": "https://github.com/super-linter/super-linter", + "visibility": "public", + "watchers": 8984, + "watchers_count": 8984, + "web_commit_signoff_required": false + }, + "sender": { + "avatar_url": "https://avatars.githubusercontent.com/u/621939?v=4", + "events_url": "https://api.github.com/users/testuser/events{/privacy}", + "followers_url": "https://api.github.com/users/testuser/followers", + "following_url": "https://api.github.com/users/testuser/following{/other_user}", + "gists_url": "https://api.github.com/users/testuser/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/testuser", + "id": 123456, + "login": "testuser", + "node_id": "MDQ6VXNlcjYyMTkzOQ==", + "organizations_url": "https://api.github.com/users/testuser/orgs", + "received_events_url": "https://api.github.com/users/testuser/received_events", + "repos_url": "https://api.github.com/users/testuser/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/testuser/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/testuser/subscriptions", + "type": "User", + "url": "https://api.github.com/users/testuser" + } +} diff --git a/test/inspec/super-linter/controls/super_linter.rb b/test/inspec/super-linter/controls/super_linter.rb index fb566d7d..25b0a09c 100644 --- a/test/inspec/super-linter/controls/super_linter.rb +++ b/test/inspec/super-linter/controls/super_linter.rb @@ -432,6 +432,7 @@ control "super-linter-validate-files" do "/action/lib/linter.sh", "/action/lib/functions/buildFileList.sh", "/action/lib/functions/detectFiles.sh", + "/action/lib/functions/githubEvent.sh", "/action/lib/functions/linterRules.sh", "/action/lib/functions/linterVersions.sh", "/action/lib/functions/linterVersions.txt", diff --git a/test/lib/githubEventTest.sh b/test/lib/githubEventTest.sh new file mode 100755 index 00000000..f6059a1b --- /dev/null +++ b/test/lib/githubEventTest.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck disable=SC2034 +LOG_TRACE="true" +# shellcheck disable=SC2034 +LOG_DEBUG="true" +# shellcheck disable=SC2034 +LOG_VERBOSE="true" +# shellcheck disable=SC2034 +LOG_NOTICE="true" +# shellcheck disable=SC2034 +LOG_WARN="true" +# shellcheck disable=SC2034 +LOG_ERROR="true" + +# shellcheck source=/dev/null +source "lib/functions/log.sh" + +# shellcheck disable=SC2034 +CREATE_LOG_FILE=false + +# shellcheck source=/dev/null +source "lib/functions/validation.sh" +# shellcheck source=/dev/null +source "lib/functions/githubEvent.sh" + +function GetGithubPushEventCommitCountTest() { + local GITHUB_EVENT_COMMIT_COUNT + GITHUB_EVENT_COMMIT_COUNT=$(GetGithubPushEventCommitCount "test/data/github-event/github-event-push.json") + + debug "GITHUB_EVENT_COMMIT_COUNT: ${GITHUB_EVENT_COMMIT_COUNT}" + + if [ "${GITHUB_EVENT_COMMIT_COUNT}" -ne 1 ]; then + fatal "GITHUB_EVENT_COMMIT_COUNT is not equal to 1: ${GITHUB_EVENT_COMMIT_COUNT}" + fi + + FUNCTION_NAME="${FUNCNAME[0]}" + notice "${FUNCTION_NAME} PASS" +} + +GetGithubPushEventCommitCountTest diff --git a/test/lib/validationTest.sh b/test/lib/validationTest.sh new file mode 100755 index 00000000..715ee638 --- /dev/null +++ b/test/lib/validationTest.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck disable=SC2034 +LOG_TRACE="true" +# shellcheck disable=SC2034 +LOG_DEBUG="true" +# shellcheck disable=SC2034 +LOG_VERBOSE="true" +# shellcheck disable=SC2034 +LOG_NOTICE="true" +# shellcheck disable=SC2034 +LOG_WARN="true" +# shellcheck disable=SC2034 +LOG_ERROR="true" + +# shellcheck source=/dev/null +source "lib/functions/log.sh" + +# shellcheck disable=SC2034 +CREATE_LOG_FILE=false + +# shellcheck source=/dev/null +source "lib/functions/validation.sh" + +function IsUnsignedIntegerSuccessTest() { + FUNCTION_NAME="${FUNCNAME[0]}" + + if ! IsUnsignedInteger 1; then + fatal "${FUNCTION_NAME} failed" + fi + + notice "${FUNCTION_NAME} PASS" +} + +function IsUnsignedIntegerFailureTest() { + FUNCTION_NAME="${FUNCNAME[0]}" + + if IsUnsignedInteger "test"; then + fatal "${FUNCTION_NAME} failed" + fi + + notice "${FUNCTION_NAME} PASS" +} + +IsUnsignedIntegerSuccessTest +IsUnsignedIntegerFailureTest