Skip to content

Commit 0347fc3

Browse files
committed
Add functionality to check unused store paths
1 parent 7bd17fb commit 0347fc3

File tree

3 files changed

+105
-82
lines changed

3 files changed

+105
-82
lines changed

src/deploy/__main__.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import click
77
from pathlib import Path
8+
from dataclasses import dataclass
89

910
from deploy.build import Build
1011
from deploy.config import load_config
@@ -13,35 +14,45 @@
1314
from deploy.sync import do_sync
1415

1516

16-
USE_SYSTEM: bool = False
17+
@dataclass
18+
class _Args:
19+
config_dir: Path = Path()
20+
use_system: bool = False
21+
22+
23+
Args = _Args()
1724

1825

1926
@click.group()
27+
@click.option(
28+
"--config-dir",
29+
"-C",
30+
help="Directory of config.yaml [default=.]",
31+
default=".",
32+
)
2033
@click.option(
2134
"--system",
2235
"-s",
2336
is_flag=True,
2437
help="Install to /prog/pflotran instead of ~/cirrus",
2538
default=False,
2639
)
27-
def cli(system: bool) -> None:
28-
global USE_SYSTEM
29-
30-
USE_SYSTEM = system
40+
def cli(config_dir: str, system: bool) -> None:
41+
Args.config_dir = Path(config_dir).resolve()
42+
Args.use_system = system
3143

3244

3345
@cli.command(help="Check locations")
3446
def check() -> None:
35-
configpath = Path.cwd()
36-
config = load_config(configpath)
47+
config = load_config(Path(Args.config_dir))
3748
do_check(config)
3849

3950

4051
@cli.command(help="Synchronise all locations")
4152
def sync() -> None:
4253
configpath = Path.cwd()
4354
config = load_config(configpath)
44-
do_sync(config, system=USE_SYSTEM)
55+
do_sync(config, system=Args.use_system)
4556

4657

4758
@cli.command(help="Build Cirrus and dependencies")
@@ -58,15 +69,15 @@ def build(force: bool) -> None:
5869

5970
configpath = Path.cwd()
6071
config = load_config(configpath)
61-
builder = Build(configpath, config, force=force, system=USE_SYSTEM)
72+
builder = Build(configpath, config, force=force, system=Args.use_system)
6273
builder.build()
6374

6475

6576
@cli.command(help="Generate symlinks from ./symlinks.json")
6677
def links() -> None:
6778
configpath = Path.cwd()
6879
config = load_config(configpath)
69-
make_links(config, system=USE_SYSTEM)
80+
make_links(config, system=Args.use_system)
7081

7182

7283
@cli.command(help="Run tests in ./deploy_tests using pytest")
@@ -76,7 +87,7 @@ def test(args: tuple[str, ...]) -> None:
7687

7788
configpath = Path.cwd()
7889
config = load_config(configpath)
79-
builder = Build(configpath, config, system=USE_SYSTEM)
90+
builder = Build(configpath, config, system=Args.use_system)
8091

8192
testpath = Path("./deploy_tests")
8293
if not testpath.is_dir():

src/deploy/_check.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/python3.6
2+
"""This script is meant to run standalone on the cluster which uses RHEL8 and
3+
only has Python 3.6"""
4+
5+
import sys
6+
from typing import List, Sequence, Dict, Any
7+
from pathlib import Path
8+
import json
9+
import argparse
10+
11+
12+
def get_unused_store_paths(store: Path, versions: Sequence[Path]) -> List[str]:
13+
used = {
14+
path.replace("/prog/replace", "/prog/cirrus")
15+
for vpath in versions
16+
for manifest in vpath.glob("*/manifest")
17+
for path in manifest.read_text().splitlines()
18+
}
19+
20+
available = {str(p) for p in store.glob("*")}
21+
return list(sorted(available - used))
22+
23+
24+
def details(store: Path, versions: Sequence[Path]) -> Dict[str, Any]:
25+
return {
26+
"unused_store_paths": get_unused_store_paths(store, versions),
27+
}
28+
29+
30+
def main() -> None:
31+
ap = argparse.ArgumentParser()
32+
ap.add_argument("--store", help="Location of the store", required=True)
33+
ap.add_argument(
34+
"--versions",
35+
nargs="+",
36+
help="Locations of 'versions' directories",
37+
required=True,
38+
)
39+
args = ap.parse_args()
40+
41+
store = Path(args.store)
42+
versions = [Path(p) for p in args.versions]
43+
44+
json.dump(details(store, versions), sys.stdout)
45+
46+
47+
if __name__ == "__main__":
48+
main()

src/deploy/check.py

Lines changed: 35 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,41 @@
11
from __future__ import annotations
2+
import sys
23
import asyncio
3-
from typing import Literal
4+
from pathlib import Path
45

56
import yaml
67
from pydantic import BaseModel
78

89
from deploy.config import Config, AreaConfig
910

10-
COLLECTOR = b"""\
11-
#!/usr/bin/env python3
12-
import os
13-
import json
14-
import re
15-
16-
17-
def gather_versions() -> None:
18-
print("versions:")
19-
base = "/prog/pflotran/versions"
20-
for name in os.listdir(base):
21-
if name[0] == ".":
22-
continue
23-
path = os.path.join(base, name)
24-
if os.path.islink(path):
25-
target = os.readlink(path)
26-
print("- type: link")
27-
print(f" name: !!str {name}")
28-
print(f" target: !!str {target}")
29-
elif os.path.isdir(path):
30-
print(f"- type: dir")
31-
print(f" name: !!str {name}")
32-
else:
33-
print(f"- type: other")
34-
print(f" name: !!str {name}")
35-
36-
37-
def gather_store() -> None:
38-
print("store:")
39-
base = "/prog/pflotran/versions/.builds"
40-
for name in os.listdir(base):
41-
print(f"- !!str {name}")
42-
43-
44-
def main() -> None:
45-
gather_versions()
46-
gather_store()
47-
48-
49-
if __name__ == "__main__":
50-
main()
51-
"""
52-
53-
54-
class VersionsLinkType(BaseModel):
55-
type: Literal["link"]
56-
name: str
57-
target: str
58-
59-
60-
class VersionsDirType(BaseModel):
61-
type: Literal["dir"]
62-
name: str
6311

12+
class Collect(BaseModel):
13+
unused_store_paths: set[str]
6414

65-
class VersionsOtherType(BaseModel):
66-
type: Literal["other"]
67-
name: str
6815

16+
SCRIPT: bytes = (Path(__file__).parent / "_check.py").read_bytes()
6917

70-
class Collect(BaseModel):
71-
versions: list[VersionsLinkType | VersionsDirType | VersionsOtherType] | None = None
72-
store: list[str] | None = None
7318

19+
async def collect(config: Config, area: AreaConfig) -> Collect:
20+
base = Path(config.paths.system_base)
21+
store = base / config.paths.store
22+
versions = [base / env.dest for env in config.envs]
7423

75-
async def collect(area: AreaConfig) -> Collect:
7624
proc = await asyncio.create_subprocess_exec(
7725
"ssh",
7826
"-T",
7927
area.host,
80-
"/usr/bin/env",
81-
"python3",
28+
"/usr/bin/python3.6",
29+
"-",
30+
"--store",
31+
store,
32+
"--versions",
33+
*versions,
8234
stdin=asyncio.subprocess.PIPE,
8335
stdout=asyncio.subprocess.PIPE,
8436
stderr=asyncio.subprocess.PIPE,
8537
)
86-
stdout, stderr = await proc.communicate(COLLECTOR)
38+
stdout, stderr = await proc.communicate(SCRIPT)
8739

8840
print(f"Finished area {area.name}")
8941

@@ -93,22 +45,34 @@ async def collect(area: AreaConfig) -> Collect:
9345
async def _check(config: Config) -> None:
9446
tasks: list[asyncio.Task[Collect]] = []
9547

48+
if not config.areas:
49+
sys.exit("No areas specified in config.yaml")
50+
9651
for area in config.areas:
97-
task = asyncio.create_task(collect(area))
52+
task = asyncio.create_task(collect(config, area))
9853
tasks.append(task)
9954

55+
collected: dict[str, Collect] = {}
56+
10057
for area, info in zip(
10158
config.areas, await asyncio.gather(*tasks, return_exceptions=True)
10259
):
103-
print(f"--- {area.name} ---")
60+
print(f"Processed {area.name}")
10461
if isinstance(info, BaseException):
10562
raise info
10663

107-
for d in info.versions or []:
108-
if isinstance(d, VersionsDirType):
109-
print(f" # {d.name}")
110-
elif isinstance(d, VersionsLinkType):
111-
print(f" - {d.name} -> {d.target}")
64+
collected[area.name] = info
65+
66+
all_areas = set(collected)
67+
all_unused_store_paths = set()
68+
for c in collected.values():
69+
all_unused_store_paths.update(c.unused_store_paths)
70+
71+
print("Unused store paths:")
72+
for path in sorted(all_unused_store_paths):
73+
which = {k for k, v in collected.items() if path in v.unused_store_paths}
74+
which_str = "" if which == all_areas else ", ".join(which)
75+
print(f"{path:<120} {which_str}")
11276

11377

11478
def do_check(config: Config) -> None:

0 commit comments

Comments
 (0)