Skip to content

Commit 6740972

Browse files
committed
modify changes for webhooks and unit testing
1 parent 1667897 commit 6740972

File tree

8 files changed

+377
-59
lines changed

8 files changed

+377
-59
lines changed

integrations/github-cloud/.env.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ OCEAN__PORT__BASE_URL=https://api.getport.io
55
OCEAN__EVENT_LISTENER__TYPE=POLLING
66
OCEAN__INITIALIZE_PORT_RESOURCES=true
77
OCEAN__INTEGRATION__CONFIG__GITHUB_BASE_URL=https://api.github.com
8-
OCEAN__INTEGRATION__CONFIG__GITHUB_ACCESS_TOKEN=github_access_token
8+
OCEAN__INTEGRATION__CONFIG__GITHUB_ACCESS_TOKEN=github-access-token
9+
OCEAN__INTEGRATION__CONFIG__APP_HOST=app-host

integrations/github-cloud/.port/spec.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ configurations:
1515
sensitive: true
1616
description: "Token for authenticating with GitHub Cloud"
1717
required: false
18+
- name: appHost
19+
type: string
20+
description: "App Host"
21+
required: true
1822
- name: githubBaseUrl
1923
type: url
2024
description: "Base URL for GitHub API"

integrations/github-cloud/client.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class GithubHandler:
1616
def __init__(self) -> None:
1717
self.token = ocean.integration_config["github_access_token"]
1818
self.base_url = ocean.integration_config["github_base_url"]
19+
self.app_host = ocean.integration_config['app_host']
1920
self.client = http_async_client
2021
self.headers = {
2122
'Authorization': f'token {self.token}',
@@ -39,7 +40,11 @@ async def fetch_with_retry(self, url: str, retries: int = 3, backoff_factor: int
3940
response = await self.client.get(url, headers=self.headers)
4041
match response.status_code:
4142
case 200:
42-
return response.json()
43+
# Check if response.json is awaitable
44+
if asyncio.iscoroutinefunction(response.json):
45+
return await response.json()
46+
else:
47+
return response.json()
4348
case 429: # Rate limit exceeded
4449
retry_after = int(response.headers.get("Retry-After", backoff_factor))
4550
logger.warning(f"Rate limit exceeded. Retrying after {retry_after} seconds.")

integrations/github-cloud/github_cloud/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import logging
2+
import sys
3+
import os
4+
5+
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
6+
7+
from port_ocean.context.ocean import ocean
8+
from client import GithubHandler
9+
10+
# Configure logging
11+
logging.basicConfig(level=logging.INFO)
12+
logger = logging.getLogger(__name__)
13+
14+
async def manage_webhooks() -> None:
15+
"""Manage webhooks for all repositories."""
16+
handler = GithubHandler()
17+
async for repo in handler.get_repositories():
18+
owner = repo["owner"]["login"]
19+
repo_name = repo["name"]
20+
21+
if not repo_name or repo_name.startswith("-"):
22+
logger.error(f"Invalid repository name: {repo_name}")
23+
continue
24+
25+
logger.info(f"Processing repository: {repo_name} owned by {owner}")
26+
27+
try:
28+
existing_hook = await get_webhook_for_repo(handler, owner, repo_name)
29+
if existing_hook:
30+
if existing_hook.get("active") is False:
31+
logger.info(f"Reactivating webhook for {owner}/{repo_name}")
32+
await delete_webhook_for_repo(handler, owner, repo_name, existing_hook["id"])
33+
await create_webhook_for_repo(handler, owner, repo_name)
34+
else:
35+
await create_webhook_for_repo(handler, owner, repo_name)
36+
except Exception as e:
37+
logger.error(f"Error managing webhook for {owner}/{repo_name}: {e}")
38+
39+
async def get_webhook_for_repo(handler: GithubHandler, owner: str, repo: str) -> dict | None:
40+
"""Check if a webhook exists for a repository."""
41+
url = f"{handler.base_url}/repos/{owner}/{repo}/hooks"
42+
hooks = await handler.fetch_with_retry(url)
43+
app_hook_url = f"{handler.app_host}/integration/hook/github-cloud"
44+
for hook in hooks:
45+
if hook["config"].get("url") == app_hook_url:
46+
logger.info(f"Found existing webhook for {owner}/{repo} with ID {hook['id']}")
47+
return hook
48+
return None
49+
50+
async def create_webhook_for_repo(handler: GithubHandler, owner: str, repo: str) -> None:
51+
"""Create a webhook for a repository."""
52+
url = f"{handler.base_url}/repos/{owner}/{repo}/hooks"
53+
payload = {
54+
"name": "web",
55+
"active": True,
56+
"events": ["push", "pull_request", "issues"],
57+
"config": {
58+
"url": f"{handler.app_host}/integration/hook/github-cloud",
59+
"content_type": "json",
60+
"insecure_ssl": "0",
61+
},
62+
}
63+
try:
64+
response = await handler.client.post(url, headers=handler.headers, json=payload)
65+
if response.status_code == 201:
66+
logger.info(f"Webhook created for {owner}/{repo}")
67+
else:
68+
logger.error(f"Failed to create webhook for {owner}/{repo}: {response.text}")
69+
except Exception as e:
70+
logger.error(f"Error creating webhook for {owner}/{repo}: {e}")
71+
72+
async def delete_webhook_for_repo(handler: GithubHandler, owner: str, repo: str, hook_id: int) -> None:
73+
"""Delete a webhook for a repository."""
74+
url = f"{handler.base_url}/repos/{owner}/{repo}/hooks/{hook_id}"
75+
try:
76+
response = await handler.client.delete(url, headers=handler.headers)
77+
if response.status_code == 204:
78+
logger.info(f"Webhook deleted for {owner}/{repo}")
79+
else:
80+
logger.error(f"Failed to delete webhook for {owner}/{repo}: {response.text}")
81+
except Exception as e:
82+
logger.error(f"Error deleting webhook for {owner}/{repo}: {e}")

integrations/github-cloud/main.py

+68-55
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,84 @@
77

88
from port_ocean.context.ocean import ocean
99
from client import GithubHandler
10+
from github_cloud.webhooks import manage_webhooks
1011

1112
# Configure logging
1213
logging.basicConfig(level=logging.INFO)
1314
logger = logging.getLogger(__name__)
1415

15-
@ocean.on_resync('repository')
16-
async def resync_repository(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
17-
"""Resync repositories."""
18-
try:
19-
handler = GithubHandler()
20-
async for repo in handler.get_repositories():
21-
logger.info(f"Yielding repository: {repo['name']}")
22-
yield repo
23-
except Exception as e:
24-
logger.error(f"Failed to resync repository: {e}")
2516

26-
@ocean.on_resync('issue')
27-
async def resync_issues(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
28-
"""Resync issues."""
29-
try:
30-
handler = GithubHandler()
31-
async for repo in handler.get_repositories():
32-
async for issue in handler.get_issues(repo["owner"]["login"], repo["name"]):
33-
logger.info(f"Yielding issue: {issue['title']}")
34-
yield issue
35-
except Exception as e:
36-
logger.error(f"Failed to resync issues: {e}")
17+
def register_resync_handlers():
18+
"""Register resync handlers after the PortOcean context is initialized."""
3719

38-
@ocean.on_resync('pull_request')
39-
async def resync_pull_requests(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
40-
"""Resync pull requests."""
41-
try:
42-
handler = GithubHandler()
43-
async for repo in handler.get_repositories():
44-
async for pull_request in handler.get_pull_requests(repo["owner"]["login"], repo["name"]):
45-
logger.info(f"Yielding pull request: {pull_request['title']}")
46-
yield pull_request
47-
except Exception as e:
48-
logger.error(f"Failed to resync pull requests: {e}")
20+
@ocean.on_resync('repository')
21+
async def resync_repository(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
22+
"""Resync repositories."""
23+
try:
24+
handler = GithubHandler()
25+
async for repo in handler.get_repositories():
26+
logger.info(f"Yielding repository: {repo['name']}")
27+
yield repo
28+
except Exception as e:
29+
logger.error(f"Failed to resync repository: {e}")
4930

50-
@ocean.on_resync('team')
51-
async def resync_teams(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
52-
"""Resync teams."""
53-
try:
54-
handler = GithubHandler()
55-
async for org in handler.get_organizations():
56-
async for team in handler.get_teams(org["login"]):
57-
logger.info(f"Yielding team: {team['name']}")
58-
yield team
59-
except Exception as e:
60-
logger.error(f"Failed to resync teams: {e}")
31+
@ocean.on_resync('issue')
32+
async def resync_issues(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
33+
"""Resync issues."""
34+
try:
35+
handler = GithubHandler()
36+
async for repo in handler.get_repositories():
37+
async for issue in handler.get_issues(repo["owner"]["login"], repo["name"]):
38+
logger.info(f"Yielding issue: {issue['title']}")
39+
yield issue
40+
except Exception as e:
41+
logger.error(f"Failed to resync issues: {e}")
6142

62-
@ocean.on_resync('workflow')
63-
async def resync_workflows(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
64-
"""Resync workflows."""
65-
try:
66-
handler = GithubHandler()
67-
async for repo in handler.get_repositories():
68-
async for workflow in handler.get_workflows(repo["owner"]["login"], repo["name"]):
69-
logger.info(f"Yielding workflow: {workflow['name']}")
70-
yield workflow
71-
except Exception as e:
72-
logger.error(f"Failed to resync workflows: {e}")
43+
@ocean.on_resync('pull_request')
44+
async def resync_pull_requests(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
45+
"""Resync pull requests."""
46+
try:
47+
handler = GithubHandler()
48+
async for repo in handler.get_repositories():
49+
async for pull_request in handler.get_pull_requests(repo["owner"]["login"], repo["name"]):
50+
logger.info(f"Yielding pull request: {pull_request['title']}")
51+
yield pull_request
52+
except Exception as e:
53+
logger.error(f"Failed to resync pull requests: {e}")
54+
55+
@ocean.on_resync('team')
56+
async def resync_teams(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
57+
"""Resync teams."""
58+
try:
59+
handler = GithubHandler()
60+
async for org in handler.get_organizations():
61+
async for team in handler.get_teams(org["login"]):
62+
logger.info(f"Yielding team: {team['name']}")
63+
yield team
64+
except Exception as e:
65+
logger.error(f"Failed to resync teams: {e}")
66+
67+
@ocean.on_resync('workflow')
68+
async def resync_workflows(kind: str) -> AsyncGenerator[dict[Any, Any], None]:
69+
"""Resync workflows."""
70+
try:
71+
handler = GithubHandler()
72+
async for repo in handler.get_repositories():
73+
async for workflow in handler.get_workflows(repo["owner"]["login"], repo["name"]):
74+
if "name" in workflow:
75+
logger.info(f"Yielding workflow: {workflow['name']}")
76+
yield workflow
77+
else:
78+
logger.warning(f"Unexpected workflow structure: {workflow}")
79+
except Exception as e:
80+
logger.error(f"Failed to resync workflows: {e}")
81+
82+
83+
# Register resync handlers immediately after the PortOcean context is initialized
84+
register_resync_handlers()
7385

7486
@ocean.on_start()
7587
async def on_start() -> None:
7688
"""Handle integration start."""
77-
logger.info("Starting GitHub Cloud integration")
89+
logger.info("Starting GitHub Cloud integration")
90+
await manage_webhooks()

0 commit comments

Comments
 (0)