Skip to content

Commit

Permalink
Merge branch 'main' into scott/token-validation
Browse files Browse the repository at this point in the history
* heads/main:
  simplify the ternary operator
  amend branch names to be forked-slug:branch-name
  we need the pr number for the PR API request to know if the command runs in a fork PR
  chore: Fix helper text in label analysis (#315)
  taking decoding slug out of is_fork_pr, cause it doesn't feel right to be there
  adding tests
  check if the PR is a fork PR
  chore: remove smart-open (#326)
  • Loading branch information
scott-codecov committed Nov 15, 2023
2 parents 68c3f4f + e1fbc08 commit f554264
Show file tree
Hide file tree
Showing 18 changed files with 444 additions and 57 deletions.
8 changes: 3 additions & 5 deletions codecov_cli/commands/labelanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,16 @@
"--dry-run",
"dry_run",
help=(
"Print list of tests to run AND tests skipped (and options that need to be added to the test runner) to stdout. "
+ "Also prints the same information in JSON format. "
+ "JSON will have keys 'ats_tests_to_run', 'ats_tests_to_skip' and 'runner_options'. "
+ "List of tests to run is prefixed with ATS_TESTS_TO_RUN= "
+ "List of tests to skip is prefixed with ATS_TESTS_TO_SKIP="
"Print list of tests to run AND tests skipped AND options that need to be added to the test runner to stdout. "
+ "Choose format with --dry-run-format option. Default is JSON. "
),
is_flag=True,
)
@click.option(
"--dry-run-format",
"dry_run_format",
type=click.Choice(["json", "space-separated-list"]),
help="Format in which --dry-run data is printed. Default is JSON.",
default="json",
)
@click.pass_context
Expand Down
19 changes: 18 additions & 1 deletion codecov_cli/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
@click.option(
"--code", help="The code of the report. If unsure, leave default", default="default"
)
@click.option(
"-P",
"--pr",
"--pull-request-number",
"pull_request_number",
help="Specify the pull request number mannually. Used to override pre-existing CI environment variables",
cls=CodecovOption,
fallback_field=FallbackFieldEnum.pull_request_number,
)
@global_options
@click.pass_context
def create_report(
Expand All @@ -22,6 +31,7 @@ def create_report(
git_service: str,
token: str,
fail_on_error: bool,
pull_request_number: int,
):
enterprise_url = ctx.obj.get("enterprise_url")
logger.debug(
Expand All @@ -38,7 +48,14 @@ def create_report(
),
)
res = create_report_logic(
commit_sha, code, slug, git_service, token, enterprise_url, fail_on_error
commit_sha,
code,
slug,
git_service,
token,
enterprise_url,
pull_request_number,
fail_on_error,
)
if not res.error:
logger.info(
Expand Down
20 changes: 20 additions & 0 deletions codecov_cli/helpers/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

slug_without_subgroups_regex = re.compile(r"[^/\s]+\/[^/\s]+$")
slug_with_subgroups_regex = re.compile(r"[^/\s]+(\/[^/\s]+)+$")
encoded_slug_regex = re.compile(r"[^:\s]+(:::[^:\s]+)*(::::[^:\s]+){1}$")


def encode_slug(slug: str):
Expand All @@ -13,6 +14,16 @@ def encode_slug(slug: str):
return encoded_slug


def decode_slug(slug: str):
if slug_encoded_incorrectly(slug):
raise ValueError("The slug is not encoded correctly")

owner, repo = slug.split("::::", 1)
decoded_owner = "/".join(owner.split(":::"))
decoded_slug = "/".join([decoded_owner, repo])
return decoded_slug


def slug_without_subgroups_is_invalid(slug: str):
"""
Checks if slug is in the form of owner/repo
Expand All @@ -27,3 +38,12 @@ def slug_with_subgroups_is_invalid(slug: str):
Returns True if it's invalid, otherwise return False
"""
return not slug or not slug_with_subgroups_regex.match(slug)


def slug_encoded_incorrectly(slug: str):
"""
Checks if slug is encoded incorrectly based on the encoding mechanism we use.
Checks if slug is in the form of owner:::subowner::::repo or owner::::repo
Returns True if invalid, otherwise returns False
"""
return not slug or not encoded_slug_regex.match(slug)
27 changes: 27 additions & 0 deletions codecov_cli/helpers/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from enum import Enum
from urllib.parse import urlparse

from codecov_cli.helpers.encoder import decode_slug
from codecov_cli.helpers.git_services.github import Github

slug_regex = re.compile(r"[^/\s]+\/[^/\s]+$")

logger = logging.getLogger("codecovcli")
Expand All @@ -17,6 +20,11 @@ class GitService(Enum):
BITBUCKET_SERVER = "bitbucket_server"


def get_git_service(git):
if git == "github":
return Github()


def parse_slug(remote_repo_url: str):
"""
Extracts a slug from git remote urls. returns None if the url is invalid
Expand Down Expand Up @@ -82,3 +90,22 @@ def parse_git_service(remote_repo_url: str):
extra=dict(remote_repo_url=remote_repo_url),
)
return None


def is_fork_pr(pull_dict):
"""
takes in dict: pull_dict
returns true if PR is made in a fork context, false if not.
"""
return pull_dict and pull_dict["head"]["slug"] != pull_dict["base"]["slug"]


def get_pull(service, slug, pr_num):
"""
takes in str git service e.g. github, gitlab etc., slug in the owner/repo format, and the pull request number
returns the pull request info gotten from the git service provider if successful, None if not
"""
git_service = get_git_service(service)
if git_service:
pull_dict = git_service.get_pull_request(slug, pr_num)
return pull_dict
Empty file.
32 changes: 32 additions & 0 deletions codecov_cli/helpers/git_services/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import json

import requests


class Github:
api_url = "https://api.github.com"
api_version = "2022-11-28"

def get_pull_request(self, slug, pr_number):
pull_url = f"/repos/{slug}/pulls/{pr_number}"
url = self.api_url + pull_url
headers = {"X-GitHub-Api-Version": self.api_version}
response = requests.get(url, headers=headers)
if response.status_code == 200:
res = json.loads(response.text)
return {
"url": res["url"],
"head": {
"sha": res["head"]["sha"],
"label": res["head"]["label"],
"ref": res["head"]["ref"],
"slug": res["head"]["repo"]["full_name"],
},
"base": {
"sha": res["base"]["sha"],
"label": res["base"]["label"],
"ref": res["base"]["ref"],
"slug": res["base"]["repo"]["full_name"],
},
}
return None
1 change: 0 additions & 1 deletion codecov_cli/plugins/compress_pycoverage_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from typing import Any, List

import ijson
from smart_open import open

from codecov_cli.plugins.types import PreparationPluginReturn

Expand Down
14 changes: 12 additions & 2 deletions codecov_cli/services/commit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import typing

from codecov_cli.helpers.config import CODECOV_API_URL
from codecov_cli.helpers.encoder import encode_slug
from codecov_cli.helpers.encoder import decode_slug, encode_slug
from codecov_cli.helpers.git import get_git_service, get_pull, is_fork_pr
from codecov_cli.helpers.request import (
get_token_header_or_fail,
log_warnings_and_errors_if_any,
Expand Down Expand Up @@ -42,13 +43,22 @@ def create_commit_logic(
def send_commit_data(
commit_sha, parent_sha, pr, branch, slug, token, service, enterprise_url
):
decoded_slug = decode_slug(slug)
pull_dict = get_pull(service, decoded_slug, pr) if not token else None
if is_fork_pr(pull_dict):
headers = {}
branch = pull_dict["head"]["slug"] + ":" + branch
logger.info("The PR is happening in a forked repo. Using tokenless upload.")
else:
headers = get_token_header_or_fail(token)

data = {
"commitid": commit_sha,
"parent_commit_id": parent_sha,
"pullid": pr,
"branch": branch,
}
headers = get_token_header_or_fail(token)

upload_url = enterprise_url or CODECOV_API_URL
url = f"{upload_url}/upload/{service}/{slug}/commits"
return send_post_request(url=url, data=data, headers=headers)
22 changes: 18 additions & 4 deletions codecov_cli/services/report/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from codecov_cli.helpers import request
from codecov_cli.helpers.config import CODECOV_API_URL
from codecov_cli.helpers.encoder import encode_slug
from codecov_cli.helpers.encoder import decode_slug, encode_slug
from codecov_cli.helpers.git import get_pull, is_fork_pr
from codecov_cli.helpers.request import (
get_token_header_or_fail,
log_warnings_and_errors_if_any,
Expand All @@ -25,21 +26,34 @@ def create_report_logic(
service: str,
token: str,
enterprise_url: str,
pull_request_number: int,
fail_on_error: bool = False,
):
encoded_slug = encode_slug(slug)
sending_result = send_create_report_request(
commit_sha, code, service, token, encoded_slug, enterprise_url
commit_sha,
code,
service,
token,
encoded_slug,
enterprise_url,
pull_request_number,
)
log_warnings_and_errors_if_any(sending_result, "Report creating", fail_on_error)
return sending_result


def send_create_report_request(
commit_sha, code, service, token, encoded_slug, enterprise_url
commit_sha, code, service, token, encoded_slug, enterprise_url, pull_request_number
):
data = {"code": code}
headers = get_token_header_or_fail(token)
decoded_slug = decode_slug(encoded_slug)
pull_dict = (
get_pull(service, decoded_slug, pull_request_number) if not token else None
)
headers = (
{} if not token and is_fork_pr(pull_dict) else get_token_header_or_fail(token)
)
upload_url = enterprise_url or CODECOV_API_URL
url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports"
return send_post_request(url=url, headers=headers, data=data)
Expand Down
10 changes: 9 additions & 1 deletion codecov_cli/services/upload/upload_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from codecov_cli import __version__ as codecov_cli_version
from codecov_cli.helpers.config import CODECOV_API_URL
from codecov_cli.helpers.encoder import encode_slug
from codecov_cli.helpers.git import get_pull, is_fork_pr
from codecov_cli.helpers.request import (
get_token_header_or_fail,
send_post_request,
Expand Down Expand Up @@ -52,7 +53,14 @@ def send_upload_data(
}

# Data to upload to Codecov
headers = get_token_header_or_fail(token)
pull_dict = (
get_pull(git_service, slug, pull_request_number) if not token else None
)
headers = (
{}
if not token and is_fork_pr(pull_dict)
else get_token_header_or_fail(token)
)
encoded_slug = encode_slug(slug)
upload_url = enterprise_url or CODECOV_API_URL
url = f"{upload_url}/upload/{git_service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/uploads"
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ requests==2.31.0
responses==0.21.0
# via codecov-cli (setup.py)
rfc3986[idna2008]==1.5.0
# via httpx
smart-open==6.4.0
# via codecov-cli (setup.py)
# via
# httpx
# rfc3986
sniffio==1.3.0
# via
# anyio
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"ijson==3.*",
"pyyaml==6.*",
"responses==0.21.*",
"smart-open==6.*",
"tree-sitter==0.20.*",
],
entry_points={
Expand Down
32 changes: 0 additions & 32 deletions tests/commands/test_invoke_labelanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,38 +171,6 @@ def test__dry_run_space_separated_list_output(self):


class TestLabelAnalysisCommand(object):
def test_labelanalysis_help(self, mocker, fake_ci_provider):
mocker.patch("codecov_cli.main.get_ci_adapter", return_value=fake_ci_provider)
runner = CliRunner()

result = runner.invoke(cli, ["label-analysis", "--help"], obj={})
assert result.exit_code == 0
print(result.output)
assert result.output.split("\n") == [
"Usage: cli label-analysis [OPTIONS]",
"",
"Options:",
" --token TEXT The static analysis token (NOT the same token",
" as upload) [required]",
" --head-sha TEXT Commit SHA (with 40 chars) [required]",
" --base-sha TEXT Commit SHA (with 40 chars) [required]",
" --runner-name, --runner TEXT Runner to use",
" --max-wait-time INTEGER Max time (in seconds) to wait for the label",
" analysis result before falling back to running",
" all tests. Default is to wait forever.",
" --dry-run Print list of tests to run AND tests skipped",
" (and options that need to be added to the test",
" runner) to stdout. Also prints the same",
" information in JSON format. JSON will have",
" keys 'ats_tests_to_run', 'ats_tests_to_skip'",
" and 'runner_options'. List of tests to run is",
" prefixed with ATS_TESTS_TO_RUN= List of tests",
" to skip is prefixed with ATS_TESTS_TO_SKIP=",
" --dry-run-format [json|space-separated-list]",
" -h, --help Show this message and exit.",
"",
]

def test_invoke_label_analysis_missing_token(self, mocker, fake_ci_provider):
mocker.patch("codecov_cli.main.get_ci_adapter", return_value=fake_ci_provider)
runner = CliRunner()
Expand Down
Loading

0 comments on commit f554264

Please sign in to comment.