This repository was archived by the owner on Jul 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 391
Expand file tree
/
Copy pathservices
More file actions
executable file
·196 lines (156 loc) · 6.26 KB
/
services
File metadata and controls
executable file
·196 lines (156 loc) · 6.26 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
#!/usr/bin/env python3
import argparse
import copy
import os
import subprocess
from typing import Dict, List, Optional
import yaml
ROOT = os.environ.get("ROOT", os.getcwd())
# If any file in these folders are changed, rebuild all services
REBUILD_FOLDERS = [
"shared"
]
# If any file in these service folders are changed, ignore it.
# If the folder does not have a `metadata.yaml` file, the folder is ignored
# anyway.
IGNORE_FOLDERS = []
def get_args():
"""
Retrieve arguments from the commandline
"""
parser = argparse.ArgumentParser()
parser.add_argument("--env-only", default=False, action="store_true")
parser.add_argument("--graph", default=False, action="store_true")
parser.add_argument("--reverse", default=False, action="store_true")
parser.add_argument("--exclude", action="append")
parser.add_argument("--deps-of", action="append")
parser.add_argument("--changed-since", default="0")
return parser.parse_args()
def get_metadata(service_name: str) -> dict:
"""
Get metadata for a service
"""
metafile = os.path.join(ROOT, service_name, "metadata.yaml")
if not os.path.isfile(metafile):
raise ValueError("Metadata file not found for {}".format(service_name))
with open(metafile) as fp:
metadata = yaml.load(fp, Loader=yaml.SafeLoader)
return metadata
def get_services(
env_only: bool = False,
deps_of: Optional[List[str]] = None,
exclude: Optional[List[str]] = None
) -> Dict[str, dict]:
"""
Return the list of services
env_only: only return services that support environments (after parsing dependencies)
deps_of: only return services that are a dependency of another service (including itself)
exclude: exclude these services from the list (after parsing dependencies)
"""
# Gather all services
if deps_of is None:
services = {
# Mapping name: reverse dependencies
name: {"deps": [], "rdeps": [], "metadata": get_metadata(name)}
for name in os.listdir(ROOT)
if os.path.isfile(os.path.join(ROOT, name, "metadata.yaml"))
}
else:
services = {}
to_scan = deps_of
while len(to_scan) > 0:
name = to_scan.pop(0)
services[name] = {"deps": [], "rdeps": [], "metadata": get_metadata(name)}
for dependency in services[name]["metadata"].get("dependencies", []):
if dependency not in services and dependency not in to_scan:
to_scan.append(dependency)
# If needed, remove services which don't support environments
if env_only:
for name in list(services.keys()):
if not services[name]["metadata"].get("flags", {}).get("environment", True):
del services[name]
# Filter out excluded names
return {k: v for k, v in services.items() if k not in IGNORE_FOLDERS + (exclude or [])}
def make_service_graph(
services: Dict[str, dict],
changed_since: str = "0"
) -> List[str]:
"""
Retrieve services in dependency order
"""
to_scan = []
# Parse dependencies
for name in services.keys():
if len(services[name]["metadata"].get("dependencies", [])) == 0:
to_scan.append(name)
for dep in services[name]["metadata"].get("dependencies", []):
if name == dep:
continue
# This service depends on all the services
elif dep == "*":
for dname in services.keys():
# Only inject if this is not the same service
if dname != name:
services[name]["deps"].append(dname)
services[dname]["rdeps"].append(name)
elif dep not in services:
raise ValueError("Dependency {} of {} not found".format(dep, name))
else:
services[name]["deps"].append(dep)
# Inject as a reverse dependency
services[dep]["rdeps"].append(name)
# Scan all services until there are no more services to scan.
count = 0
retval = []
while len(to_scan) > 0:
retval_line = copy.deepcopy(to_scan)
to_scan = []
for name in retval_line:
for rname in services[name]["rdeps"]:
services[rname]["deps"].remove(name)
if len(services[rname]["deps"]) == 0:
to_scan.append(rname)
count += len(retval_line)
retval.append(retval_line)
# If there is a discrepancy, this means that there is a circular
# dependency.
if count != len(services):
for key, value in services.items():
print(key, value)
raise ValueError("Potential circular dependency found: mismatch between number of services: {} vs {}".format(len(retval), len(services)))
# If `--changed-since` is absent or does not have a valid commit ID, return the list of services
if changed_since == "0":
return retval
# If `--changed-since` has a valid commit ID, we need to check which
process = subprocess.run(["git", "diff", changed_since, "--name-only"], capture_output=True, text=True)
process.check_returncode()
changed = []
for filename in process.stdout.split("\n"):
basename = filename.split("/", 1)[0]
# If one file from the special folders has changed, all services need
# to be rebuilt.
if basename in REBUILD_FOLDERS:
return retval
if basename in services:
changed.append(basename)
updated_services = copy.deepcopy(retval)
for index, service_line in enumerate(retval):
for service in service_line:
if service not in changed:
updated_services[index].remove(service)
# Only return each service once
return [service_line for service_line in updated_services if service_line]
if __name__ == "__main__":
args = get_args()
services = make_service_graph(
get_services(args.env_only, args.deps_of, args.exclude),
args.changed_since
)
if args.reverse:
services = services[::-1]
for service_line in services:
if args.graph:
print(",".join(service_line))
else:
for service in service_line:
print(service)