Repository navigation
Expand file tree
/
Copy pathvalidate_deployment.py
More file actions
133 lines (109 loc) · 4.08 KB
/
Copy pathvalidate_deployment.py
File metadata and controls
133 lines (109 loc) · 4.08 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
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
from pathlib import Path
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit, urlunsplit
from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1]
DOCS = ROOT / "docs"
WORKSHOP = ROOT / "workshop"
def public_paths() -> list[str]:
site_files = (
path.relative_to(DOCS).as_posix()
for path in DOCS.rglob("*")
if path.is_file() and "tests" not in path.relative_to(DOCS).parts
)
lessons = (
f"workshop/{path.name}"
for path in WORKSHOP.glob("*.md")
)
return ["", *sorted(site_files), *sorted(lessons)]
def normalize_base_url(value: str) -> str:
parsed = urlsplit(value)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError("site URL must be an absolute HTTP or HTTPS URL")
if parsed.query or parsed.fragment:
raise ValueError("site URL must not contain a query string or fragment")
path = parsed.path if parsed.path.endswith("/") else f"{parsed.path}/"
return urlunsplit((parsed.scheme, parsed.netloc, path, "", ""))
def check_url(url: str, timeout: float) -> str | None:
request = Request(url, headers={"User-Agent": "copilot-sdk-workshop-deployment-validator"})
try:
with urlopen(request, timeout=timeout) as response:
if response.status != 200:
return f"HTTP {response.status}"
if not response.read(1):
return "empty response"
except HTTPError as error:
return f"HTTP {error.code}"
except URLError as error:
return str(error.reason)
except TimeoutError as error:
return str(error) or "timed out"
return None
def validate(base_url: str, attempts: int, delay: float, timeout: float) -> list[str]:
paths = public_paths()
failures = {path: "not checked" for path in paths}
for attempt in range(1, attempts + 1):
for path in list(failures):
url = f"{base_url}{quote(path, safe='/')}"
error = check_url(url, timeout)
if error is None:
del failures[path]
else:
failures[path] = error
if not failures:
return []
if attempt < attempts:
print(
f"Attempt {attempt}/{attempts}: {len(failures)} URL(s) unavailable; retrying...",
file=sys.stderr,
)
time.sleep(delay)
return [
f"{base_url}{quote(path, safe='/')}: {error}"
for path, error in failures.items()
]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Verify every file expected in the deployed workshop site."
)
parser.add_argument(
"--base-url",
default=os.environ.get("WORKSHOP_SITE_URL"),
help="deployed site root (defaults to WORKSHOP_SITE_URL)",
)
parser.add_argument("--attempts", type=int, default=6)
parser.add_argument("--delay", type=float, default=5)
parser.add_argument("--timeout", type=float, default=10)
return parser.parse_args()
def main() -> int:
args = parse_args()
if not args.base_url:
print(
"Provide --base-url or set WORKSHOP_SITE_URL.",
file=sys.stderr,
)
return 2
if args.attempts < 1 or args.delay < 0 or args.timeout <= 0:
print("Attempts must be positive, delay non-negative, and timeout positive.", file=sys.stderr)
return 2
try:
base_url = normalize_base_url(args.base_url)
except ValueError as error:
print(error, file=sys.stderr)
return 2
failures = validate(base_url, args.attempts, args.delay, args.timeout)
if failures:
print("Deployment validation failed:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1
print(f"Validated {len(public_paths())} deployed URLs at {base_url}")
return 0
if __name__ == "__main__":
raise SystemExit(main())