Skip to content

Commit 021f234

Browse files
GitHub extraction (#122)
Add utilities required to extract sources from github and move some existing scripts to different locations in the repository.
1 parent ae30ca1 commit 021f234

8 files changed

Lines changed: 208 additions & 43 deletions

File tree

github_scripts/get_git_sources.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# *****************************COPYRIGHT*******************************
2+
# (C) Crown copyright Met Office. All rights reserved.
3+
# For further details please refer to the file COPYRIGHT.txt
4+
# which you should have received as part of this distribution.
5+
# *****************************COPYRIGHT*******************************
6+
"""
7+
Clone sources for a rose-stem run for use with git bdiff module in scripts
8+
"""
9+
10+
import re
11+
import subprocess
12+
from typing import Optional
13+
from pathlib import Path
14+
from shutil import rmtree
15+
16+
17+
def run_command(
18+
command: str, rval: bool = False
19+
) -> Optional[subprocess.CompletedProcess]:
20+
"""
21+
Run a subprocess command and return the result object
22+
Inputs:
23+
- command, str with command to run
24+
Outputs:
25+
- result object from subprocess.run
26+
"""
27+
command = command.split()
28+
result = subprocess.run(
29+
command,
30+
capture_output=True,
31+
text=True,
32+
timeout=300,
33+
shell=False,
34+
check=False,
35+
)
36+
if result.returncode:
37+
print(result.stdout, end="\n\n\n")
38+
raise RuntimeError(
39+
f"[FAIL] Issue found running command {command}\n\n{result.stderr}"
40+
)
41+
if rval:
42+
return result
43+
44+
45+
def clone_repo_mirror(
46+
source: str, repo_ref: str, parent: str, mirror_loc: Path, loc: Path
47+
) -> None:
48+
"""
49+
Clone a repo source using a local git mirror.
50+
Assume the mirror is set up as per the Met Office
51+
"""
52+
53+
# Remove if this clone already exists
54+
if loc.exists():
55+
rmtree(loc)
56+
57+
command = f"git clone {mirror_loc} {loc}"
58+
run_command(command)
59+
60+
# If not provided a ref, return
61+
if not repo_ref:
62+
return
63+
64+
source = source.removeprefix("git@github.com:")
65+
user = source.split("/")[0]
66+
# Check that the user is different to the Upstream User
67+
if user in parent.split("/")[0]:
68+
user = None
69+
70+
# If the ref is a hash then we don't need the fork user as part of the fetch.
71+
# Equally, if the user is the Upstream User, it's not needed
72+
if not user or re.match(r"^\s*([0-9a-f]{40})\s*$", repo_ref):
73+
fetch = repo_ref
74+
else:
75+
fetch = f"{user}/{repo_ref}"
76+
commands = (
77+
f"git -C {loc} fetch origin {fetch}",
78+
f"git -C {loc} checkout FETCH_HEAD",
79+
)
80+
for command in commands:
81+
run_command(command)
82+
83+
84+
def clone_repo(repo_source: str, repo_ref: str, loc: Path) -> None:
85+
"""
86+
Clone the repo and checkout the provided ref
87+
Only if a remote source
88+
"""
89+
90+
# Remove if this clone already exists
91+
if loc.exists():
92+
rmtree(loc)
93+
94+
# Create a clean clone location
95+
loc.mkdir(parents=True)
96+
97+
commands = (
98+
f"git -C {loc} init",
99+
f"git -C {loc} remote add origin {repo_source}",
100+
f"git -C {loc} fetch origin {repo_ref}",
101+
f"git -C {loc} checkout FETCH_HEAD",
102+
)
103+
for command in commands:
104+
run_command(command)
105+
106+
107+
def sync_repo(repo_source: str, repo_ref: str, loc: Path) -> None:
108+
"""
109+
Rsync a local git clone and checkout the provided ref
110+
"""
111+
112+
# Remove if this clone already exists
113+
if loc.exists():
114+
rmtree(loc)
115+
116+
# Create a clean clone location
117+
loc.mkdir(parents=True)
118+
119+
# Trailing slash required for rsync
120+
command = f"rsync -av {repo_source}/ {loc}"
121+
run_command(command)
122+
if repo_ref:
123+
command = f"git -C {loc} checkout {repo_ref}"
124+
run_command(command)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
# *****************************COPYRIGHT*******************************
3+
# (C) Crown copyright Met Office. All rights reserved.
4+
# For further details please refer to the file COPYRIGHT.txt
5+
# which you should have received as part of this distribution.
6+
# *****************************COPYRIGHT*******************************
7+
"""
8+
Clone sources for a rose-stem run for use with git bdiff module in scripts
9+
Only intended for use with rose-stem suites that have provided appropriate environment
10+
variables
11+
"""
12+
13+
import os
14+
from datetime import datetime
15+
from pathlib import Path
16+
from ast import literal_eval
17+
from get_git_sources import clone_repo, clone_repo_mirror, sync_repo
18+
from typing import Dict
19+
20+
21+
def set_https(dependencies: Dict) -> Dict:
22+
"""
23+
Change sources in a dependencies dictions to use https instead of ssh
24+
"""
25+
26+
print("Modifying Dependencies")
27+
for dependency, values in dependencies.items():
28+
if values["source"].startswith("git@github.com:"):
29+
source = dependencies[dependency]["source"]
30+
dependencies[dependency]["source"] = source.replace(
31+
"git@github.com:", "https://github.com/"
32+
)
33+
34+
return dependencies
35+
36+
37+
def main() -> None:
38+
39+
clone_loc = Path(os.environ["SOURCE_DIRECTORY"])
40+
41+
dependencies: Dict = literal_eval(os.environ["DEPENDENCIES"])
42+
43+
if os.environ.get("USE_TOKENS", "False") == "True":
44+
dependencies = set_https(dependencies)
45+
46+
for dependency, values in dependencies.items():
47+
48+
print(
49+
f"Extracting {dependency} at time {datetime.now()} "
50+
f"using source {values['source']} and ref {values['ref']}"
51+
)
52+
53+
loc = clone_loc / dependency
54+
55+
if ".git" in values["source"]:
56+
if os.environ.get("USE_MIRRORS", "False") == "True":
57+
mirror_loc = Path(os.environ["GIT_MIRROR_LOC"]) / values["parent"]
58+
clone_repo_mirror(
59+
values["source"], values["ref"], values["parent"], mirror_loc, loc
60+
)
61+
else:
62+
clone_repo(values["source"], values["ref"], loc)
63+
else:
64+
sync_repo(values["source"], values["ref"], loc)
65+
66+
67+
if __name__ == "__main__":
68+
main()
Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
Class containing helper methods for gathering data needed for a SuiteReport object
99
"""
1010

11-
import sys
12-
13-
sys.path.append("../")
1411
import re
1512
import shutil
1613
import sqlite3
@@ -19,18 +16,8 @@
1916
from collections import defaultdict
2017
from pathlib import Path
2118
from typing import Dict, List, Optional, Set, Union
22-
23-
try:
24-
from bdiff.git_bdiff import GitBDiff, GitInfo
25-
except ImportError:
26-
try:
27-
from git_bdiff import GitBDiff, GitInfo
28-
except ImportError as err:
29-
raise ImportError(
30-
"Unable to import from git_bdiff module. This is included in the same "
31-
"repository as this script and included with a relative import. Ensure "
32-
"this script is being called from the correct place."
33-
) from err
19+
from git_bdiff import GitBDiff, GitInfo
20+
from get_git_sources import clone_repo, sync_repo
3421

3522

3623
class SuiteData:
@@ -217,20 +204,9 @@ def clone_sources(self) -> None:
217204
for dependency, data in self.dependencies.items():
218205
loc = self.temp_directory / dependency
219206
if data["source"].endswith(".git"):
220-
commands = [
221-
f"git clone {data['source']} {loc}",
222-
f"git -C {loc} checkout {data['ref']}",
223-
]
224-
for command in commands:
225-
self.run_command(command)
207+
clone_repo(data["source"], data["ref"], loc)
226208
else:
227-
source = data["source"]
228-
if not source.endswith("/"):
229-
source = source + "/"
230-
command = (
231-
f'rsync -e "ssh -o StrictHostKeyChecking=no" -avl {source} {loc}'
232-
)
233-
self.run_command(command, shell=True)
209+
sync_repo(data["source"], data["ref"], loc)
234210

235211
def determine_primary_source(self) -> str:
236212
"""
@@ -298,21 +274,18 @@ def read_rose_conf(self) -> Dict[str, str]:
298274

299275
def find_unknown_dependency(self, dependency: str) -> str:
300276
"""
301-
TEMPORARY
302277
The primary dependency may be unset in the dependencies file. In this case find
303-
it from the *_SOURCE variable in the rose-suite.conf.
304-
TODO: Once cylc provides the location of the source code itself, this method
305-
should be changed to use that instead, as then the _SOURCE variable will be
306-
removed
307-
"""
308-
309-
var = f"{dependency.upper()}_SOURCE".replace('"', "")
310-
if var not in self.rose_data:
311-
raise RuntimeError(f"Cant determine source for {dependency}")
312-
rval = self.rose_data[var]
313-
if "$ROSE_ORIG_HOST" in rval:
314-
rval = rval.replace("$ROSE_ORIG_HOST", self.rose_data["ROSE_ORIG_HOST"])
315-
return rval
278+
it from the CYLC_WORKFLOW_SRC_DIR variable that gets set in the
279+
flow-processed.cylc file
280+
"""
281+
282+
pattern = re.compile(rf"{dependency.upper()} SOURCE CLONE=(\S+)")
283+
log_file = self.suite_path / "log" / "scheduler" / "log"
284+
with open(log_file, "r") as f:
285+
for line in f:
286+
if match := pattern.search(line):
287+
return match.group(1).rstrip("/")
288+
raise RuntimeError(f"Unable to find source for dependency {dependency}")
316289

317290
def read_dependencies(self) -> Dict[str, Dict]:
318291
"""
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,8 @@ def main() -> None:
373373

374374
args = parse_args()
375375

376+
suite_report = SuiteReport(args.suite_path)
376377
try:
377-
suite_report = SuiteReport(args.suite_path)
378378
suite_report.create_log()
379379
suite_report.write_log(args.log_path)
380380
finally:

0 commit comments

Comments
 (0)