Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate requirements for creating repo config PR #155

Closed
Tracked by #153
trent-codecov opened this issue Aug 2, 2023 · 4 comments
Closed
Tracked by #153

Investigate requirements for creating repo config PR #155

trent-codecov opened this issue Aug 2, 2023 · 4 comments
Assignees

Comments

@trent-codecov
Copy link
Contributor

trent-codecov commented Aug 2, 2023

In order to create the repo config PR, we will likely need to know the user's file to add config to. This will require some input from the frontend app. Work with @adrian-codecov currently to determine what this could look like. We will use this info to inform the design required and implementation details of the backend feature.

@giovanni-guidini
Copy link

giovanni-guidini commented Sep 4, 2023

To open or update a pull request in a public repository, you must have write access to the head or the source branch. For organization-owned repositories, you must be a member of the organization that owns the repository to open or update a pull request.

https://docs.github.com/en/rest/pulls?apiVersion=2022-11-28#create-a-pull-request

I have verified that Github Apps (apparently) have permission to open PRs without exgtra permissions (after all if you can see a repo you can open a PR to it)

EDIT we need more permissions to actually create files and commits and all that (Contents: Read & Write)

@giovanni-guidini
Copy link

Might not be possible to do it in BitBucket. Gitlab seems to be possible.

If we use the CLI (codecovcli init command for example) it would be a more consistent experience and would work in all git providers. Also less ideal for the end user

@giovanni-guidini
Copy link

As a little POC of how to do this in GitHub I offer the task below which generated giovanni-guidini/ats-data#5

import logging

from httpx import AsyncClient
from database.models.core import Branch, Repository
from services.repository import get_repo_provider_service
from tasks.base import BaseCodecovTask
from shared.torngit.base import TorngitBaseAdapter
from app import celery_app

from sqlalchemy.orm import Session


log = logging.getLogger(__name__)

class RepoConfigPRTask(BaseCodecovTask):

    name = 'app.tasks.repo_config_pr'

    example_yaml_content = (
    "codecov:\n" +
    "  require_ci_to_pass: false\n" +
    "notify:\n" +
    "  wait_for_ci: false\n" +
    "comment:\n"
    "  show_critical_paths: true\n"
    '  layout: "reach,diff,flags,components,tree,betaprofiling"\n'
    )

    async def _create_tree_gh(self, base_commit_sha: str, repo_provider: TorngitBaseAdapter, client: AsyncClient) -> str:
        # Git trees https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#about-git-trees
        
        url = f'/repos/{repo_provider.slug}/git/trees'
        body = dict(
                base_tree=base_commit_sha,
                tree=[
                    dict(
                        path='codecov.yml',
                        mode='100644',
                        type='blob',
                        content=self.example_yaml_content
                    )
                ]
            )
        response = await repo_provider.api(client=client, url=url, body=body, method='POST')
        return response['sha']
    
    async def _create_commit_gh(self, tree_sha: str, parent_commit_sha: str, repo_provider: TorngitBaseAdapter, client: AsyncClient):
        # Create a commit https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28
        url = f'/repos/{repo_provider.slug}/git/commits'
        body = dict(
                message="Auto-created commit",
                tree=tree_sha,
                parents=[parent_commit_sha],
            )
        response = await repo_provider.api(client=client, url=url, body=body, method='POST')
        return response['sha']
    
    async def _create_branch_gh(self, branch_name: str, commit_sha: str, repo_provider: TorngitBaseAdapter, client: AsyncClient):
        # Create a commit https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#create-a-reference
        url = f'/repos/{repo_provider.slug}/git/refs'
        body = dict(
                ref=f'refs/heads/{branch_name}',
                sha=commit_sha
            )
        response = await repo_provider.api(client=client, url=url, body=body, method='POST')
        return response
    
    async def _create_pr_gh(self, feature_branch_name, base_branch_name, repo_provider, client):
        # Create PR
            url = f'/repos/{repo_provider.slug}/pulls'
            body = dict(
                title="Test config PR",
                head=feature_branch_name,
                base=base_branch_name,
                body="This is a PR to test auto configs. IGNORE",
                maintainer_can_modify=True,
                draft=True,
            )
            response = await repo_provider.api(client=client, url=url, body=body, method='POST')
            return response

    async def _do_config_repo_pr_gh(self, dbsession: Session, repo: Repository, repo_provider: TorngitBaseAdapter):
        default_branch: Branch = dbsession.query(Branch).filter(Branch.repoid == repo.repoid, Branch.branch == repo.branch).first()
        head_commit_sha_default_branch: str = default_branch.head
        async with repo_provider.get_client() as client:
            # Create file
            tree_sha = await self._create_tree_gh(head_commit_sha_default_branch, repo_provider, client)
            # Create commit
            commit_sha = await self._create_commit_gh(tree_sha, head_commit_sha_default_branch, repo_provider, client)
            # Create branch
            branch_name = 'codecov/test-config-pr'
            await self._create_branch_gh(branch_name, commit_sha, repo_provider, client)
            # Create PR
            await self._create_pr_gh(branch_name, default_branch.branch, repo_provider, client)


    async def run_async(self, dbsession, repoid, *args, **kwargs):
        repository = dbsession.query(Repository).filter(Repository.repoid == repoid).first()
        repo_provider = get_repo_provider_service(repository)
        await self._do_config_repo_pr_gh(dbsession=dbsession, repo=repository, repo_provider=repo_provider)
        return "Finished"

RegisteredRepoConfigPRTask = celery_app.register_task(RepoConfigPRTask())
repo_config_pr = celery_app.tasks[RegisteredRepoConfigPRTask.name]

@codecov-hooky codecov-hooky bot closed this as completed Sep 8, 2023
@giovanni-guidini
Copy link

Hummm I was curious to see what would happen if we tried to include a workflow file.
That requires yet another permission (Workflow: Read & Write)

The task with the changes below created this PR: giovanni-guidini/ats-data#6

...
example_workflow_content = (
    "name: Upload-to-codecov\n" +
    "\n" +
    "on: [push]  # Run on any push event\n" +
    "\n" +
    "jobs:\n" +
    "  upload-codecov:\n" +
    "  runs-on: ubuntu-latest\n" +
    "  steps:\n"
    "    - name: Upload coverage reports to Codecov\n" +
    "      uses: codecov/codecov-action@v3\n" +
    "      env:\n"
    "        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
    )
...
async def _create_tree_gh(self, base_commit_sha: str, repo_provider: TorngitBaseAdapter, client: AsyncClient) -> str:
        # Git trees https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#about-git-trees
        
        url = f'/repos/{repo_provider.slug}/git/trees'
        body = dict(
                base_tree=base_commit_sha,
                tree=[
                    dict(
                        path='codecov.yml',
                        mode='100644',
                        type='blob',
                        content=self.example_yaml_content
                    ),
                    dict(
                        path='.github/workflows/upload_codecov.yml',
                        mode='100644',
                        type='blob',
                        content=self.example_workflow_content
                    ),
                ]
            )
        response = await repo_provider.api(client=client, url=url, body=body, method='POST')
        return response['sha']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants