Skip to content

Commit 17259d0

Browse files
authored
CM-26623-add-support-block-pr-for-org #3
2 parents d3abbe6 + dea6e86 commit 17259d0

File tree

8 files changed

+157
-36
lines changed

8 files changed

+157
-36
lines changed

Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Use the official Python image as the base image
2+
FROM python:3.8-alpine
3+
4+
# Install necessary development tools
5+
RUN apk add --no-cache libffi-dev=3.4.4-r2 build-base=0.5-r3
6+
7+
# Set the working directory inside the container
8+
WORKDIR /app
9+
10+
# Copy all files from the build context into the container's /app/ directory
11+
COPY . /app/
12+
13+
# Install your Python dependencies
14+
RUN pip install --no-cache-dir PyGithub==1.57
15+
RUN pip install --no-cache-dir PyInquirer==1.0.3
16+
RUN pip install --no-cache-dir pydantic==1.10.2
17+
18+
# Command to run your Python console app
19+
CMD ["python", "main.py"]

README.md

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,80 @@
1-
# cycode_recovery_utils
1+
# Cycode Recovery Tool
22

3-
This script will be responsible for various recovery utils. currently supports
3+
![logo](https://e5s6t7j5.rocketcdn.me/wp-content/uploads/2020/10/Cycode_logo.svg)
44

5-
- Release blocked pr's
6-
- github
5+
## Table of Contents
76

8-
## installation
7+
- [Overview](#overview)
8+
- [Features](#features)
9+
- [Getting Started](#getting-started)
10+
- [Usage](#usage)
11+
- [License](#license)
912

10-
`pip install -r requirements.txt`
13+
## Overview
1114

12-
## usage
15+
Introducing the Cycode Recovery Tool, an internal disaster recovery solution exclusively designed to support Cycode's
16+
customers. This invaluable tool steps in during times of a disaster, ensuring that your software development workflows
17+
remain resilient even when faced with adversity.
1318

14-
`python3 main.py`
19+
## Features
1520

16-
- choose recovery action:
17-
- Release Block Pr: provide path for the configuration file
18-
![recovery action](./docs/recovery_action.png)
19-
- Release Block Pr: choose provider
20-
- Release Block Pr: choose which status checks to release
21-
![status_checks_release](./docs/status_checks_release.png)
21+
* PR Unblocking: The Cycode Recovery Tool specializes in freeing up blocked Pull Requests (PRs), currently providing
22+
support exclusively for GitHub repositories. If your development process hits a snag with a blocked PR, this tool
23+
comes to the rescue, swiftly resolving the issue and getting your workflow back on track.
24+
25+
## Getting Started
26+
27+
As easy as 1-2-3!
28+
1. Clone this repository
29+
2. Build your local docker image
30+
```
31+
docker build -t cycode_recovery_tool . --no-cache
32+
```
33+
3. Run the docker image
34+
```
35+
docker run -ti cycode_recovery_tool
36+
```
37+
38+
## Usage
39+
40+
1. Choose recovery action (currently only Release Block Pr) ![recovery action](./docs/recovery_action.png)
41+
2. Provide path for the configuration file (or use our default config.json file)
42+
3. Choose provider (currently only Github)
43+
4. Choose which status checks to release ![status_checks_release](./docs/status_checks_release.png)
2244

2345
## configuration file
2446

25-
Input is as follows:
47+
There is a sample config file added to the project
2648

27-
```[
49+
```
50+
[
2851
{
29-
"token": "token",
30-
"provider": "github",
52+
"token": "<Github token with repo scope permission>",
53+
"provider": "Github",
3154
"repositories": [
3255
{
33-
"repository_name": "ilan-repo4",
34-
"organization_name": "firecorp",
56+
"repository_name": "my_repository",
57+
"organization_name": "my_organization",
3558
"branch": "main"
3659
}
60+
],
61+
"organizations": [
62+
{
63+
"organization_name": "my_organization"
64+
},
65+
{
66+
"organization_name": "my_second_organization"
67+
}
3768
]
3869
}
39-
]
70+
]
71+
```
72+
73+
* The token should have `repo` scope permissions.
74+
* You can provide either `repositories` or `organizations`. You don't need both, but you need at least one of the two.
75+
* In case `organizations` are provided, we will update all the organization's default branch in all repositories.
76+
* You can provide multiple SCM configurations.
77+
78+
## License
79+
80+
This project is licensed under the MIT License.

config.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"token": "<Github token with repo scope permission>",
4+
"provider": "Github",
5+
"repositories": [
6+
{
7+
"repository_name": "my_repository",
8+
"organization_name": "my_organization",
9+
"branch": "main"
10+
}
11+
],
12+
"organizations": [
13+
{
14+
"organization_name": "my_organization"
15+
},
16+
{
17+
"organization_name": "my_second_organization"
18+
}
19+
]
20+
}
21+
]

main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
{
1616
'type': 'list',
1717
'name': 'menu',
18-
'message': 'What do you want to do?',
18+
'message': 'What would you like to do?',
1919
'choices': [menu for menu in menu_value_to_class.keys()],
2020

2121
},
@@ -30,14 +30,14 @@
3030
{
3131
'type': 'list',
3232
'name': 'provider',
33-
"message": "Which provider?",
33+
"message": "Which SCM provider?",
3434
"choices": providers,
3535
"when": release_block_pr_menu.menu_chosen,
3636
},
3737
{
3838
'type': 'checkbox',
3939
'name': 'contexts',
40-
"message": "Which contexts?",
40+
"message": "Which status checks?",
4141
"choices": [{"name": context} for context in contexts],
4242
"when": release_block_pr_menu.menu_chosen
4343
}

menus/release_block_pr_menu.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from logger import logger
88
from menus.menu_base import MenuBase
99
from providers.github_handler import GithubHandler
10-
from release_block_pr_config import ReleaseBlockPrConfig
10+
from release_block_pr_config import ReleaseBlockPrConfig, RepositoryConfig
1111

12-
release_block = "Release Blocked PR's"
12+
release_block = "Release Blocked Pull Requests"
1313

1414

1515
class ReleaseBlockPrMenu(MenuBase):
@@ -20,11 +20,21 @@ class ReleaseBlockPrMenu(MenuBase):
2020
def handle(self, answers):
2121
if answers.get("provider") in self.handlers:
2222
handler_class = self.handlers[answers["provider"]]
23-
repositories_to_unblock: List[ReleaseBlockPrConfig] = pydantic.parse_file_as(List[ReleaseBlockPrConfig],
24-
answers["config_file"])
25-
for tokens_to_repositories in repositories_to_unblock:
26-
handler = handler_class(tokens_to_repositories.token)
27-
for repository in tokens_to_repositories.repositories:
23+
release_block_pr_configs: List[ReleaseBlockPrConfig] = pydantic.parse_file_as(List[ReleaseBlockPrConfig],
24+
answers["config_file"])
25+
for release_block_pr_config in release_block_pr_configs:
26+
handler = handler_class(release_block_pr_config.token)
27+
repositories: List[RepositoryConfig] = []
28+
if release_block_pr_config.repositories is not None:
29+
repositories.extend(release_block_pr_config.repositories)
30+
31+
# Get all organizations repositories
32+
organization_names = [organization.organization_name for organization
33+
in release_block_pr_config.organizations]
34+
repositories.extend(handler.get_organizations_repositories(organization_names))
35+
36+
# Release branch protection from all repositories
37+
for repository in repositories:
2838
handler.release_branch_protection(repository.organization_name, repository.repository_name,
2939
repository.branch, answers["contexts"])
3040
else:
@@ -38,7 +48,10 @@ def validate_config_file(config_file):
3848
if not os.path.isfile(config_file):
3949
return "File does not exist"
4050
try:
41-
pydantic.parse_file_as(List[ReleaseBlockPrConfig], config_file)
51+
parsed_configs = pydantic.parse_file_as(List[ReleaseBlockPrConfig], config_file)
52+
for parsed_config in parsed_configs:
53+
if len(parsed_config.organizations) == 0 and len(parsed_config.repositories) == 0:
54+
return f"Invalid input, need at least one repository or organization"
4255
except Exception as e:
4356
return f"Invalid input, not in the expected format {e}"
4457
return True

providers/github_handler.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from logger import logger
66
from providers.provider_handler import ProviderHandler
7+
from release_block_pr_config import RepositoryConfig
78

89

910
class GithubHandler(ProviderHandler):
@@ -27,5 +28,22 @@ def release_branch_protection(self, organization_name, repository_name, branch,
2728
branch.edit_required_status_checks(contexts=list(contexts_to_keep))
2829
else:
2930
logger.info(f"Skipping {full_repo_name} contexts to remove are not required")
30-
except:
31-
logger.exception(f"Failed to update required status checks on repository {full_repo_name}")
31+
except Exception as e:
32+
logger.exception(f"Failed to update required status checks on repository {full_repo_name}: {e}")
33+
34+
def get_organizations_repositories(self, organization_names):
35+
repository_configs = []
36+
for organization_name in organization_names:
37+
try:
38+
organization = self.github.get_organization(organization_name)
39+
repositories = organization.get_repos()
40+
for repository in repositories:
41+
repository_config = RepositoryConfig(
42+
repository_name=repository.name,
43+
organization_name=organization_name,
44+
branch=repository.default_branch
45+
)
46+
repository_configs.append(repository_config)
47+
except Exception as e:
48+
logger.exception(f"Failed to get repositories for organization {organization_name}: {e}")
49+
return repository_configs

providers/provider_handler.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ def __init__(self, token):
1010
@abstractmethod
1111
def release_branch_protection(self, organization_name, repository_name, branch, contexts: List[str]):
1212
pass
13+
14+
@abstractmethod
15+
def get_organizations_repositories(self, organization_names):
16+
pass

release_block_pr_config.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
from typing import List
1+
from typing import List, Optional
22

33
import pydantic
44

55

6+
class OrganizationConfig(pydantic.BaseModel):
7+
organization_name: str
8+
9+
610
class RepositoryConfig(pydantic.BaseModel):
711
repository_name: str
812
organization_name: str
@@ -12,4 +16,5 @@ class RepositoryConfig(pydantic.BaseModel):
1216
class ReleaseBlockPrConfig(pydantic.BaseModel):
1317
token: str
1418
provider: str
15-
repositories: List[RepositoryConfig]
19+
repositories: Optional[List[RepositoryConfig]]
20+
organizations: Optional[List[OrganizationConfig]]

0 commit comments

Comments
 (0)