You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
4.3 KiB
111 lines
4.3 KiB
"""
|
|
GitLab source provider implementation (Example for extensibility)
|
|
"""
|
|
import requests
|
|
from typing import List, Dict, Optional
|
|
from ..base import SourceProvider, Repository, ProviderError, ConfigurationError
|
|
|
|
|
|
class GitLabSourceProvider(SourceProvider):
|
|
"""GitLab source provider implementation"""
|
|
|
|
def _validate_config(self) -> None:
|
|
"""Validate GitLab-specific configuration"""
|
|
required_keys = ['url', 'token', 'username']
|
|
missing = [key for key in required_keys if not self.config.get(key)]
|
|
|
|
if missing:
|
|
raise ConfigurationError(f"Missing GitLab configuration: {', '.join(missing)}")
|
|
|
|
self.base_url = self.config['url'].rstrip('/')
|
|
self.token = self.config['token']
|
|
self.username = self.config['username']
|
|
|
|
# Setup HTTP session
|
|
self.session = requests.Session()
|
|
# GitLab uses different token formats - try Private-Token first
|
|
if self.token.startswith('glpat-'):
|
|
# Personal Access Token
|
|
self.session.headers.update({
|
|
'Private-Token': self.token,
|
|
'Content-Type': 'application/json'
|
|
})
|
|
else:
|
|
# OAuth token or other format
|
|
self.session.headers.update({
|
|
'Authorization': f'Bearer {self.token}',
|
|
'Content-Type': 'application/json'
|
|
})
|
|
|
|
def get_user_repositories(self) -> List[Repository]:
|
|
"""Get repositories owned by the authenticated user"""
|
|
repositories = self.get_accessible_repositories()
|
|
return [repo for repo in repositories if repo.owner == self.username]
|
|
|
|
def get_accessible_repositories(self) -> List[Repository]:
|
|
"""Get all repositories accessible to the authenticated user"""
|
|
url = f"{self.base_url}/api/v4/projects"
|
|
params = {
|
|
'membership': 'true',
|
|
'per_page': 100,
|
|
'page': 1
|
|
}
|
|
|
|
all_repos = []
|
|
|
|
while True:
|
|
try:
|
|
response = self.session.get(url, params=params)
|
|
response.raise_for_status()
|
|
repos_data = response.json()
|
|
|
|
if not repos_data:
|
|
break
|
|
|
|
repos = [self._parse_repository(repo_data) for repo_data in repos_data]
|
|
all_repos.extend(repos)
|
|
|
|
# Check if there are more pages
|
|
if 'X-Next-Page' not in response.headers:
|
|
break
|
|
|
|
params['page'] += 1
|
|
|
|
except requests.RequestException as e:
|
|
raise ProviderError(f"Failed to fetch repositories from GitLab: {e}")
|
|
|
|
return all_repos
|
|
|
|
def get_repository_info(self, owner: str, name: str) -> Optional[Repository]:
|
|
"""Get information about a specific repository"""
|
|
# GitLab uses project ID or namespace/project format
|
|
project_path = f"{owner}/{name}"
|
|
url = f"{self.base_url}/api/v4/projects/{requests.utils.quote(project_path, safe='')}"
|
|
|
|
try:
|
|
response = self.session.get(url)
|
|
response.raise_for_status()
|
|
return self._parse_repository(response.json())
|
|
except requests.RequestException:
|
|
return None
|
|
|
|
def get_authenticated_clone_url(self, repository: Repository) -> str:
|
|
"""Get authenticated clone URL for a repository"""
|
|
base_url = self.base_url.replace('https://', '').replace('http://', '')
|
|
return f"https://oauth2:{self.token}@{base_url}/{repository.owner}/{repository.name}.git"
|
|
|
|
def _parse_repository(self, repo_data: Dict) -> Repository:
|
|
"""Parse repository data from GitLab API response"""
|
|
namespace = repo_data['namespace']
|
|
owner = namespace['path'] if namespace['kind'] == 'user' else namespace['full_path']
|
|
|
|
return Repository(
|
|
name=repo_data['name'],
|
|
owner=owner,
|
|
description=repo_data.get('description', ''),
|
|
private=repo_data.get('visibility') == 'private',
|
|
clone_url=repo_data['http_url_to_repo'],
|
|
ssh_url=repo_data.get('ssh_url_to_repo'),
|
|
web_url=repo_data.get('web_url'),
|
|
default_branch=repo_data.get('default_branch', 'main')
|
|
) |