Skip to content

Commit 29a2722

Browse files
committed
Initial Commit
1 parent dccf39e commit 29a2722

21 files changed

+513
-1
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Orleon Batista
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,58 @@
1-
# migration-gitlab
1+
# GitLab Migration Tool
2+
3+
## Overview
4+
The GitLab Migration Tool is a versatile Python program designed to facilitate seamless migration and management of projects between GitLab instances. Whether you're transferring projects from one GitLab environment to another or managing project variables, this tool streamlines the process with efficiency and reliability.
5+
6+
## Key Features
7+
8+
1. **Rate Limit Manager**: The Rate Limit Manager ensures optimal performance by controlling the number of API requests made within specified intervals, preventing the program from exceeding GitLab API rate limits.
9+
10+
2. **GitLab Integration**: Seamlessly integrate with GitLab APIs to export and import projects between GitLab instances. Enjoy smooth project migration while preserving data integrity.
11+
12+
3. **Project Export/Import**: Effortlessly export projects from a source GitLab instance and import them into a destination instance. Transfer code, issues, milestones, and more between GitLab environments with ease.
13+
14+
4. **Project Variable Management**: Manage project variables effectively by transferring variables between GitLab projects. Customize project configurations and settings during migration or replication.
15+
16+
5. **Shared Runners Management**: Enable shared runners for projects.
17+
18+
6. **Configurable Settings**: Easily configure the program's behavior using the `config.json` file. Specify GitLab URLs, access tokens, group IDs, and project mappings to adapt to different GitLab environments.
19+
20+
7. **Testing Framework**: Ensure code quality and reliability with the included testing framework. Verify functionality and prevent regressions during development with comprehensive testing capabilities.
21+
22+
8. **Cross-Platform Compatibility**: Enjoy the versatility of running the program on multiple platforms, including Linux, macOS, and Windows. Experience broad compatibility for diverse computing environments.
23+
24+
## Installation
25+
1. Clone the repository: `git clone https://github.com/username/repository.git`
26+
2. Navigate to the project directory: `cd project_name`
27+
3. Install dependencies: `pip install -r requirements.txt`
28+
29+
## Usage
30+
1. Create a new `config.json` file in the root directory of the project.
31+
2. Add the necessary configuration parameters to `config.json`.
32+
3. Run the main script: `python src/main.py`
33+
34+
## Configuration File (`config.json`)
35+
Example `config.json` structure:
36+
```json
37+
{
38+
"token_info": {
39+
"src_gitlab_url": "https://source.gitlab.com",
40+
"src_gitlab_access_token": "your-source-access-token",
41+
"dest_gitlab_url": "https://destination.gitlab.com",
42+
"dest_gitlab_access_token": "your-destination-access-token",
43+
"group_id": 123
44+
},
45+
"projects": [
46+
{
47+
"src_project_id": 456
48+
},
49+
{
50+
"src_project_id": 789
51+
}
52+
]
53+
}
54+
```
55+
56+
## License
57+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
58+

__init__.py

Whitespace-only changes.

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
requests==2.26.0
2+
pytest==6.2.4
3+
json

src/GitLab.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
from RateLimitManager import RateLimitManager
3+
4+
class GitLab:
5+
"""
6+
Class for interacting with GitLab.
7+
"""
8+
9+
def __init__(self, gitlab_url, gitlab_access_token):
10+
"""
11+
Initializes the GitLab class with GitLab URL and access token.
12+
13+
Args:
14+
gitlab_url (str): The GitLab URL.
15+
gitlab_access_token (str): The GitLab access token.
16+
"""
17+
self.gitlab_url = gitlab_url
18+
self.gitlab_access_token = gitlab_access_token
19+
20+
def import_project(self, file_path, group_id):
21+
"""
22+
Imports a project to GitLab.
23+
24+
Args:
25+
file_path (str): The path of the exported file.
26+
group_id (int): The ID of the group to import the project.
27+
28+
Returns:
29+
dict: The imported project data.
30+
"""
31+
base_url = f"{self.gitlab_url}/api/v4"
32+
import_url = f"{base_url}/projects/import"
33+
params = {"path": os.path.basename(file_path).split(".")[0], "namespace": group_id}
34+
headers = {"PRIVATE-TOKEN": self.gitlab_access_token}
35+
files = {"file": open(file_path, "rb")}
36+
37+
response = RateLimitManager().make_request(import_url, "POST", headers=headers, data=params, files=files)
38+
39+
if response.status_code == 201:
40+
print(f"Project imported successfully. It will be available in a few seconds. Status code: {response.status_code}")
41+
return response.json()
42+
else:
43+
print(f"Failed to import project. Status code: {response.status_code}")
44+
return None

src/GitLabProject.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os
2+
import time
3+
from RateLimitManager import RateLimitManager
4+
5+
class GitLabProject:
6+
"""
7+
Class to manage GitLab projects.
8+
"""
9+
10+
def __init__(self, project_id, gitlab_url, gitlab_access_token):
11+
"""
12+
Initializes the GitLabProject class with project ID, GitLab URL, and access token.
13+
14+
Args:
15+
project_id (int): The project ID.
16+
gitlab_url (str): The GitLab URL.
17+
gitlab_access_token (str): The GitLab access token.
18+
"""
19+
self.project_id = project_id
20+
self.gitlab_url = gitlab_url
21+
self.gitlab_access_token = gitlab_access_token
22+
23+
def export_project(self):
24+
"""
25+
Exports a project from GitLab.
26+
27+
Returns:
28+
str: The path of the exported file.
29+
"""
30+
base_url = f"{self.gitlab_url}/api/v4"
31+
export_url = f"{base_url}/projects/{self.project_id}/export"
32+
payload = {"upload[http_method]": "PUT"}
33+
headers = {"PRIVATE-TOKEN": self.gitlab_access_token}
34+
35+
response = RateLimitManager().make_request(export_url, "POST", headers=headers, data=payload)
36+
37+
if response.status_code == 202:
38+
print("Project export initiated successfully.")
39+
else:
40+
print(f"Failed to initiate project export. Status code: {response.status_code}")
41+
return None
42+
43+
export_status_url = f"{base_url}/projects/{self.project_id}/export"
44+
export_status = None
45+
46+
while export_status != "finished":
47+
time.sleep(5)
48+
status_response = RateLimitManager().make_request(export_status_url, "GET", headers=headers)
49+
50+
if status_response.status_code == 200:
51+
export_status = status_response.json().get("export_status")
52+
if export_status == "finished":
53+
download_url = status_response.json().get("_links").get("api_url")
54+
project_name = status_response.json().get("name")
55+
break
56+
else:
57+
print("Project export still in progress. Status:", export_status)
58+
else:
59+
print(f"Failed to get export status. Status code: {status_response.status_code}")
60+
return None
61+
62+
export_dir = "./exports"
63+
if not os.path.exists(export_dir):
64+
os.makedirs(export_dir)
65+
66+
print("Project export completed. Downloading the exported file...")
67+
68+
export_response = RateLimitManager().make_request(download_url, "GET", headers=headers)
69+
if export_response.status_code == 200:
70+
file_path = os.path.join(export_dir, f"{project_name}.tar.gz")
71+
with open(file_path, "wb") as file:
72+
file.write(export_response.content)
73+
74+
print(f"Exported file downloaded as '{file_path}'.")
75+
return file_path
76+
else:
77+
print(f"Failed to download the exported file. Status code: {export_response.status_code}")
78+
return None

src/ProjectVariableManager.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from RateLimitManager import RateLimitManager
2+
3+
class ProjectVariableManager:
4+
"""
5+
Class to manage project variables in GitLab.
6+
"""
7+
8+
def __init__(self, src_gitlab_url, src_gitlab_access_token, dest_gitlab_url, dest_gitlab_access_token):
9+
"""
10+
Initializes the ProjectVariableManager class with source and destination GitLab URLs and access tokens.
11+
12+
Args:
13+
src_gitlab_url (str): The source GitLab URL.
14+
src_gitlab_access_token (str): The source GitLab access token.
15+
dest_gitlab_url (str): The destination GitLab URL.
16+
dest_gitlab_access_token (str): The destination GitLab access token.
17+
"""
18+
self.src_gitlab_url = src_gitlab_url
19+
self.src_gitlab_access_token = src_gitlab_access_token
20+
self.dest_gitlab_url = dest_gitlab_url
21+
self.dest_gitlab_access_token = dest_gitlab_access_token
22+
23+
def get_project_variables(self, project_id):
24+
"""
25+
Gets variables of a project.
26+
27+
Args:
28+
project_id (int): The project ID.
29+
30+
Returns:
31+
list: A list of project variables.
32+
"""
33+
url = f"{self.src_gitlab_url}/api/v4/projects/{project_id}/variables"
34+
headers = {"PRIVATE-TOKEN": self.src_gitlab_access_token}
35+
response = RateLimitManager().make_request(url, "GET", headers=headers)
36+
if response.status_code == 200:
37+
return response.json()
38+
else:
39+
print("Failed to fetch project variables.")
40+
return None
41+
42+
def create_project_variable(self, project_id, variable):
43+
"""
44+
Creates a variable in a project.
45+
46+
Args:
47+
project_id (int): The project ID.
48+
variable (dict): The data of the variable to be created.
49+
"""
50+
url = f"{self.dest_gitlab_url}/api/v4/projects/{project_id}/variables"
51+
headers = {"PRIVATE-TOKEN": self.dest_gitlab_access_token}
52+
response = RateLimitManager().make_request(url, "POST", headers=headers, data=variable)
53+
if response.status_code == 201:
54+
print(f"Variable {variable['key']} created successfully.")
55+
else:
56+
print(f"Failed to create variable {variable['key']}.")

src/RateLimitManager.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import time
2+
import requests
3+
4+
class RateLimitManager:
5+
"""
6+
Class to manage rate limits for API requests.
7+
"""
8+
9+
interval = 60 # 1-minute interval
10+
max_requests = 5 # Maximum number of requests per interval
11+
requests = 0
12+
last_check = time.time()
13+
14+
@classmethod
15+
def check_rate_limit(cls):
16+
current_time = time.time()
17+
if current_time - cls.last_check > cls.interval:
18+
cls.requests = 0
19+
cls.last_check = current_time
20+
21+
@classmethod
22+
def make_request(cls, url, method, headers=None, data=None, files=None):
23+
cls.check_rate_limit()
24+
if cls.requests >= cls.max_requests:
25+
wait_time = cls.interval - (time.time() - cls.last_check)
26+
print(f"Waiting {wait_time:.1f} seconds to avoid rate limit exceedance...")
27+
time.sleep(wait_time)
28+
cls.check_rate_limit()
29+
30+
response = requests.request(method, url, headers=headers, data=data, files=files)
31+
cls.requests += 1
32+
return response

src/RunnerManager.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
from RateLimitManager import RateLimitManager
3+
4+
class RunnerManager:
5+
"""
6+
Class to manage shared runners for GitLab projects.
7+
"""
8+
9+
def __init__(self, gitlab_url, access_token):
10+
"""
11+
Initializes the RunnerManager class with the GitLab URL and access token.
12+
13+
Args:
14+
gitlab_url (str): The GitLab URL.
15+
access_token (str): The GitLab access token.
16+
"""
17+
self.gitlab_url = gitlab_url
18+
self.access_token = access_token
19+
20+
def enable_shared_runners(self, project_id):
21+
"""
22+
Enable shared runners for a specified project.
23+
24+
Args:
25+
project_id (int): The project ID.
26+
27+
Returns:
28+
dict: The response from the GitLab API.
29+
"""
30+
url = f"{self.gitlab_url}/api/v4/projects/{project_id}"
31+
headers = {
32+
"PRIVATE-TOKEN": self.access_token,
33+
"Content-Type": "application/json"
34+
}
35+
data = {
36+
"shared_runners_enabled": True
37+
}
38+
39+
response = RateLimitManager().make_request(url, "PUT", headers=headers, data=json.dumps(data))
40+
41+
if response.status_code == 200:
42+
print("Shared runners enabled successfully.")
43+
return response
44+
else:
45+
print(f"Failed to enable shared runners: {response.status_code} - {response.reason}")
46+
return None

src/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)