diff --git a/README.md b/README.md index 51a53711..5f876dbb 100644 --- a/README.md +++ b/README.md @@ -1376,9 +1376,19 @@ After running this action, the following environment variables are available: Verify that SonarQube SCA (Software Composition Analysis) ran for the project. -The action discovers project keys from `.sonarlint/connectedMode.json`, `sonar-project.properties`, `pom.xml`, `build.gradle(.kts)`, and -`GITHUB_REPOSITORY`, then polls the Next, SonarCloud US, and SonarCloud EU instances. It fails if SCA data is missing on every -platform after the timeout. +The action discovers project keys from `.github/repo-metadata.yaml`, `.sonarlint/connectedMode.json`, `sonar-project.properties`, +`pom.xml`, `build.gradle(.kts)`, and `GITHUB_REPOSITORY`, then polls the Next, SonarCloud US, and SonarCloud EU instances. It fails +if SCA data is missing on every platform after the timeout. + +### Manually setting the project key for the check + +For repos that don't have standard SonarQube config files (e.g. repos using SonarCloud Automatic Analysis), create +`.github/repo-metadata.yaml` with the project key under the `check-sca` section: + +```yaml +check-sca: + project-key: your-project-key +``` ### Requirements diff --git a/check-sca/check-sca.sh b/check-sca/check-sca.sh index e933e475..4913d99b 100755 --- a/check-sca/check-sca.sh +++ b/check-sca/check-sca.sh @@ -34,6 +34,20 @@ PLATFORMS=( "sqc-eu:SQC_EU_URL:SQC_EU_TOKEN" ) +# Parse a key=value property from a file. Returns the trimmed value or empty string. +parse_property_value() { + local file="$1" key="$2" + grep -E "^${key}=" "$file" | head -1 | cut -d= -f2- | tr -d '[:space:]' +} + +# Read a value from .github/repo-metadata.yaml under a given top-level section. +# Usage: read_repo_metadata
+# Example: read_repo_metadata repo-metadata.yaml "check-sca" "project-key" +read_repo_metadata() { + local file="$1" section="$2" key="$3" + sed -n "/^${section}:/,/^[^ ]/{ /^ ${key}:/{ s/^ ${key}:[ ]*//; s/^['\"]//; s/['\"]$//; p; q; }; }" "$file" | tr -d '[:space:]' +} + # Discover candidate SonarQube project keys from config files. # Returns one key per line, deduplicated, in priority order. discover_project_keys() { @@ -45,7 +59,21 @@ discover_project_keys() { keys+=("$PROJECT_KEY_INPUT") fi - # 2. .sonarlint/connectedMode.json + # 2. .github/repo-metadata.yaml or .yml (always at repo root, not working-directory) + local repo_root="${GITHUB_WORKSPACE:-$work_dir}" + local metadata_file + for metadata_file in "$repo_root/.github/repo-metadata.yaml" "$repo_root/.github/repo-metadata.yml"; do + if [[ -f "$metadata_file" ]]; then + local key + key=$(read_repo_metadata "$metadata_file" "check-sca" "project-key") + if [[ -n "$key" ]]; then + keys+=("$key") + fi + break + fi + done + + # 3. .sonarlint/connectedMode.json local sonarlint_file="$work_dir/.sonarlint/connectedMode.json" if [[ -f "$sonarlint_file" ]]; then local key @@ -55,20 +83,20 @@ discover_project_keys() { fi fi - # 3. sonar-project.properties + # 4. sonar-project.properties local sonar_props="$work_dir/sonar-project.properties" if [[ -f "$sonar_props" ]]; then local key - key=$(grep -E '^sonar\.projectKey=' "$sonar_props" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]') + key=$(parse_property_value "$sonar_props" 'sonar\.projectKey') if [[ -n "$key" ]]; then keys+=("$key") fi fi - # 4. pom.xml + # 5. pom.xml local pom_file="$work_dir/pom.xml" if [[ -f "$pom_file" ]]; then - # 4a. Explicit sonar.projectKey property (highest priority within pom.xml) + # 5a. Explicit sonar.projectKey property (highest priority within pom.xml) local key key=$(perl -0777 -ne ' if (/([^<]+)/s) { @@ -81,7 +109,7 @@ discover_project_keys() { keys+=("$key") fi - # 4b. Derive groupId:artifactId (Maven default project key) + # 5b. Derive groupId:artifactId (Maven default project key) local maven_key maven_key=$(perl -0777 -ne ' sub trim { @@ -111,7 +139,7 @@ discover_project_keys() { fi fi - # 5. build.gradle / build.gradle.kts + # 6. build.gradle / build.gradle.kts local gradle_file for gradle_file in "$work_dir/build.gradle" "$work_dir/build.gradle.kts"; do if [[ -f "$gradle_file" ]]; then @@ -129,7 +157,7 @@ discover_project_keys() { fi done - # 6. Derive from GITHUB_REPOSITORY (e.g. SonarSource/repo-name -> SonarSource_repo-name) + # 7. Derive from GITHUB_REPOSITORY (e.g. SonarSource/repo-name -> SonarSource_repo-name) if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then local derived_key="${GITHUB_REPOSITORY/\//_}" keys+=("$derived_key") diff --git a/spec/check-sca_spec.sh b/spec/check-sca_spec.sh index 9552c20d..b29cee69 100644 --- a/spec/check-sca_spec.sh +++ b/spec/check-sca_spec.sh @@ -46,11 +46,13 @@ Describe 'discover_project_keys()' setup() { TEST_DIR=$(mktemp -d) export WORKING_DIRECTORY="$TEST_DIR" + export GITHUB_WORKSPACE="$TEST_DIR" return 0 } # shellcheck disable=SC2329 # Function invoked indirectly by AfterEach teardown() { rm -rf "$TEST_DIR" + unset GITHUB_WORKSPACE return 0 } @@ -74,6 +76,81 @@ Describe 'discover_project_keys()' The output should include "FromSonarlint" End + It 'reads from .github/repo-metadata.yaml' + export PROJECT_KEY_INPUT="" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: FromMetadata\n' > "$TEST_DIR/.github/repo-metadata.yaml" + When call discover_project_keys + The output should include "FromMetadata" + End + + It 'reads from .github/repo-metadata.yml' + export PROJECT_KEY_INPUT="" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: FromYml\n' > "$TEST_DIR/.github/repo-metadata.yml" + When call discover_project_keys + The output should include "FromYml" + End + + It 'prefers .yaml over .yml when both exist' + export PROJECT_KEY_INPUT="" + export GITHUB_REPOSITORY="" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: FromYaml\n' > "$TEST_DIR/.github/repo-metadata.yaml" + printf 'check-sca:\n project-key: FromYml\n' > "$TEST_DIR/.github/repo-metadata.yml" + When call discover_project_keys + The line 1 should equal "FromYaml" + End + + It 'reads quoted values from .github/repo-metadata.yaml' + export PROJECT_KEY_INPUT="" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: "QuotedKey"\n' > "$TEST_DIR/.github/repo-metadata.yaml" + When call discover_project_keys + The output should include "QuotedKey" + End + + It 'ignores .github/repo-metadata.yaml when check-sca section is missing' + export PROJECT_KEY_INPUT="" + export GITHUB_REPOSITORY="" + mkdir -p "$TEST_DIR/.github" + printf 'jira:\n project-key: BUILD\n' > "$TEST_DIR/.github/repo-metadata.yaml" + When call discover_project_keys + The output should equal "" + End + + It 'prioritizes .github/repo-metadata.yaml over sonar-project.properties' + export PROJECT_KEY_INPUT="" + export GITHUB_REPOSITORY="" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: FromMetadata\n' > "$TEST_DIR/.github/repo-metadata.yaml" + echo "sonar.projectKey=FromProperties" > "$TEST_DIR/sonar-project.properties" + When call discover_project_keys + The line 1 should equal "FromMetadata" + The line 2 should equal "FromProperties" + End + + It 'keeps both explicit input and .github/repo-metadata.yaml keys' + export PROJECT_KEY_INPUT="explicit-key" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: FromMetadata\n' > "$TEST_DIR/.github/repo-metadata.yaml" + When call discover_project_keys + The line 1 should equal "explicit-key" + The line 2 should equal "FromMetadata" + End + + It 'reads .github/repo-metadata.yaml from repo root when working-directory is a subdirectory' + export PROJECT_KEY_INPUT="" + export GITHUB_REPOSITORY="" + export GITHUB_WORKSPACE="$TEST_DIR" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: RootKey\n' > "$TEST_DIR/.github/repo-metadata.yaml" + mkdir -p "$TEST_DIR/services/my-app" + export WORKING_DIRECTORY="$TEST_DIR/services/my-app" + When call discover_project_keys + The output should include "RootKey" + End + It 'reads from sonar-project.properties' export PROJECT_KEY_INPUT="" echo "sonar.projectKey=FromProperties" > "$TEST_DIR/sonar-project.properties" @@ -164,15 +241,18 @@ Describe 'discover_project_keys()' It 'returns keys from multiple sources in priority order' export PROJECT_KEY_INPUT="explicit-key" + mkdir -p "$TEST_DIR/.github" + printf 'check-sca:\n project-key: metadata-key\n' > "$TEST_DIR/.github/repo-metadata.yaml" mkdir -p "$TEST_DIR/.sonarlint" echo '{"projectKey": "sonarlint-key"}' > "$TEST_DIR/.sonarlint/connectedMode.json" echo "sonar.projectKey=props-key" > "$TEST_DIR/sonar-project.properties" export GITHUB_REPOSITORY="SonarSource/test-repo" When call discover_project_keys The line 1 should equal "explicit-key" - The line 2 should equal "sonarlint-key" - The line 3 should equal "props-key" - The line 4 should equal "SonarSource_test-repo" + The line 2 should equal "metadata-key" + The line 3 should equal "sonarlint-key" + The line 4 should equal "props-key" + The line 5 should equal "SonarSource_test-repo" End It 'returns empty when no sources available'