-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
207 lines (170 loc) · 6.73 KB
/
Copy pathutils.py
File metadata and controls
207 lines (170 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python3
import git
import tempfile
import shutil
import urllib.parse
import os
import re
import hashlib
from typing import Dict, Optional, Tuple, List
from datetime import datetime
from clients import get_secret_data
def get_workdir_path(
namespace: str, name: str, repo_name: str, commit_hash: str
) -> str:
"""Get predictable working directory path"""
base_path = "/tmp/nixos-config"
workdir = f"{base_path}/{namespace}/{name}/{repo_name}@{commit_hash}"
os.makedirs(workdir, exist_ok=True)
return workdir
def parse_flake_reference(flake_ref: str) -> Tuple[str, str, str]:
"""
Parse flake reference and return (repo_name, repo_url, commit_hash)
Supported formats:
- github:owner/repo#host
- github:owner/repo/v1.0#host
- github:owner/repo/abcdef123456#host
- .#host (local)
"""
if flake_ref.startswith("."):
# Local flake
return "local", ".", "local"
# Extract part before #
flake_parts = flake_ref.split("#", 1)
flake_source = flake_parts[0]
# Parse source
if flake_source.startswith("github:"):
# github:owner/repo or github:owner/repo/ref
parts = flake_source[7:].split("/")
owner = parts[0]
repo = parts[1]
repo_name = f"{owner}/{repo}"
# Determine ref (branch/tag/commit)
if len(parts) > 2:
ref = parts[2]
# Check if ref is a commit (40-character hash)
if re.match(r"^[a-f0-9]{40}$", ref):
commit_hash = ref
else:
# This is a branch or tag - commit will be determined later
commit_hash = "floating"
else:
# Default to main branch
commit_hash = "floating"
repo_url = f"https://github.com/{owner}/{repo}.git"
return repo_name, repo_url, commit_hash
# For other sources, parsing can be added
return "unknown", flake_source, "unknown"
def extract_repo_name_from_url(git_url: str) -> str:
"""Extract repository name from Git URL"""
# Remove protocol and .git
clean_url = re.sub(r"^https?://", "", git_url)
clean_url = re.sub(r"\.git$", "", clean_url)
# Extract owner/repo
parts = clean_url.split("/")
if len(parts) >= 2:
return f"{parts[-2]}/{parts[-1]}"
return clean_url
def calculate_directory_hash(directory_path: str) -> str:
"""Calculate SHA256 hash of directory contents"""
if not os.path.exists(directory_path):
return ""
hash_obj = hashlib.sha256()
for root, dirs, files in os.walk(directory_path):
# Sort for determinism
dirs.sort()
files.sort()
for file in files:
file_path = os.path.join(root, file)
# Add relative path to hash
rel_path = os.path.relpath(file_path, directory_path)
hash_obj.update(rel_path.encode("utf-8"))
# Add file content
try:
with open(file_path, "rb") as f:
while chunk := f.read(8192):
hash_obj.update(chunk)
except Exception:
# If file cannot be read, skip it
pass
return hash_obj.hexdigest()
async def clone_git_repo(
git_url: str,
credentials_ref: Optional[Dict] = None,
namespace: str = "default",
target_path: Optional[str] = None,
) -> Tuple[str, str]:
"""Clone Git repository and return path and commit hash"""
if target_path:
work_dir = target_path
# If directory already exists, use it
if os.path.exists(work_dir):
try:
repo = git.Repo(work_dir)
commit_hash = repo.head.commit.hexsha
return work_dir, commit_hash
except Exception:
# If repository is corrupted, remove and clone again
shutil.rmtree(work_dir, ignore_errors=True)
else:
# Old mode with temporary directories (for backward compatibility)
work_dir = tempfile.mkdtemp(prefix="nixos-operator-")
try:
# Prepare credentials if available
git_kwargs = {}
if credentials_ref:
secret_data = await get_secret_data(credentials_ref["name"], namespace)
# Assume secret contains ssh-privatekey or token
if "ssh-privatekey" in secret_data:
ssh_key = secret_data["ssh-privatekey"]
git_kwargs["env"] = {"GIT_SSH_COMMAND": f"ssh -i {ssh_key}"}
elif "token" in secret_data:
# For HTTPS with token
parsed_url = urllib.parse.urlparse(git_url)
auth_url = f"{parsed_url.scheme}://token:{secret_data['token']}@{parsed_url.netloc}{parsed_url.path}"
git_url = auth_url
# Clone repository
repo = git.Repo.clone_from(git_url, work_dir, **git_kwargs)
commit_hash = repo.head.commit.hexsha
return work_dir, commit_hash
except Exception as e:
if not target_path: # Remove only temporary directories
shutil.rmtree(work_dir, ignore_errors=True)
raise
async def get_remote_commit_hash(
git_url: str,
ref: str,
credentials_ref: Optional[Dict] = None,
namespace: str = "default",
) -> str:
"""Get commit hash for specified branch/tag from remote repository"""
try:
# Create temporary directory for ls-remote
temp_dir = tempfile.mkdtemp(prefix="nixos-operator-lsremote-")
try:
# Prepare credentials if available
git_kwargs = {}
if credentials_ref:
secret_data = await get_secret_data(credentials_ref["name"], namespace)
if "ssh-privatekey" in secret_data:
ssh_key = secret_data["ssh-privatekey"]
git_kwargs["env"] = {"GIT_SSH_COMMAND": f"ssh -i {ssh_key}"}
elif "token" in secret_data:
parsed_url = urllib.parse.urlparse(git_url)
auth_url = f"{parsed_url.scheme}://token:{secret_data['token']}@{parsed_url.netloc}{parsed_url.path}"
git_url = auth_url
# Execute git ls-remote
repo = git.Repo.init(temp_dir)
origin = repo.create_remote("origin", git_url)
origin.fetch(**git_kwargs)
# Search for ref
for ref_info in repo.git.ls_remote(git_url, ref).split("\n"):
if ref_info:
parts = ref_info.split()
if len(parts) >= 2:
return parts[0] # Commit hash
raise Exception(f"Ref '{ref}' not found in repository")
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
except Exception as e:
raise Exception(f"Failed to get remote commit hash for {ref}: {e}")