misc.python.materialize.ci_util
Utility functions only useful in CI.
1# Copyright Materialize, Inc. and contributors. All rights reserved. 2# 3# Use of this software is governed by the Business Source License 4# included in the LICENSE file at the root of this repository. 5# 6# As of the Change Date specified in that file, in accordance with 7# the Business Source License, use of this software will be governed 8# by the Apache License, Version 2.0. 9 10"""Utility functions only useful in CI.""" 11 12import os 13import time 14from pathlib import Path 15from typing import Any 16 17import requests 18from semver.version import VersionInfo 19 20from materialize import MZ_ROOT, buildkite, cargo, git, ui 21from materialize.rustc_flags import Sanitizer 22 23 24def junit_report_filename(suite: str) -> Path: 25 """Compute the JUnit report filename for the specified test suite. 26 27 See also `upload_test_report`. In CI, the filename will include the 28 Buildkite job ID. 29 30 Args: 31 suite: The identifier for the test suite in Buildkite Test Analytics. 32 """ 33 filename = f"junit_{suite}" 34 if "BUILDKITE_JOB_ID" in os.environ: 35 filename += "_" + os.environ["BUILDKITE_JOB_ID"] 36 return Path(f"{filename}.xml") 37 38 39def get_artifacts() -> Any: 40 """Get artifact informations from Buildkite. Outside of CI, this function does nothing.""" 41 42 if not buildkite.is_in_buildkite(): 43 return [] 44 45 ui.section("Getting artifact informations from Buildkite") 46 build = os.environ["BUILDKITE_BUILD_NUMBER"] 47 build_id = os.environ["BUILDKITE_BUILD_ID"] 48 job = os.environ["BUILDKITE_JOB_ID"] 49 token = os.environ["BUILDKITE_AGENT_ACCESS_TOKEN"] 50 51 payload = { 52 "query": "*", 53 "step": job, 54 "build": build, 55 "state": "finished", 56 "includeRetriedJobs": "false", 57 "includeDuplicates": "false", 58 } 59 60 attempts = 10 61 res = None 62 for attempt in range(attempts): 63 try: 64 res = requests.get( 65 f"https://agent.buildkite.com/v3/builds/{build_id}/artifacts/search", 66 params=payload, 67 headers={"Authorization": f"Token {token}"}, 68 timeout=30, 69 ) 70 res.raise_for_status() 71 break 72 except Exception as e: 73 # Artifact info only supplies links for annotations, so on repeated 74 # failure degrade to no artifacts rather than crashing the caller 75 # and losing all annotations. A bare except would also swallow 76 # KeyboardInterrupt. 77 print(f"Failed to get artifacts (attempt {attempt + 1}/{attempts}): {e}") 78 if attempt == attempts - 1: 79 return [] 80 time.sleep(5) 81 82 assert res 83 if res.status_code != 200: 84 print(f"Failed to get artifacts: {res.status_code} {res.text}") 85 return [] 86 87 return res.json() 88 89 90def get_mz_version(workspace: cargo.Workspace | None = None) -> VersionInfo: 91 """Get the current Materialize version from Cargo.toml.""" 92 93 if not workspace: 94 workspace = cargo.Workspace(MZ_ROOT) 95 return VersionInfo.parse(workspace.crates["mz-environmentd"].version_string) 96 97 98def dev_docker_tag() -> str: 99 """The Docker tag under which this commit's images are published. 100 101 Both the publishing side (`ci/test/dev_tag.py`) and anything that pulls 102 those images have to derive the tag the same way, or the pull looks for an 103 image that was never pushed. 104 """ 105 # Ideally we'd use SemVer metadata (e.g., `v1.0.0+metadata`), but `+` is not 106 # a valid character in Docker tags, so we use `--` instead. 107 sanitizer = Sanitizer[os.getenv("CI_SANITIZER", "none")] 108 suffix = "pr" if sanitizer == Sanitizer.none else f"pr-{sanitizer}" 109 return f"v{get_mz_version()}--{suffix}.g{git.rev_parse('HEAD')}"
def
junit_report_filename(suite: str) -> pathlib._local.Path:
25def junit_report_filename(suite: str) -> Path: 26 """Compute the JUnit report filename for the specified test suite. 27 28 See also `upload_test_report`. In CI, the filename will include the 29 Buildkite job ID. 30 31 Args: 32 suite: The identifier for the test suite in Buildkite Test Analytics. 33 """ 34 filename = f"junit_{suite}" 35 if "BUILDKITE_JOB_ID" in os.environ: 36 filename += "_" + os.environ["BUILDKITE_JOB_ID"] 37 return Path(f"{filename}.xml")
Compute the JUnit report filename for the specified test suite.
See also upload_test_report. In CI, the filename will include the
Buildkite job ID.
Args: suite: The identifier for the test suite in Buildkite Test Analytics.
def
get_artifacts() -> Any:
40def get_artifacts() -> Any: 41 """Get artifact informations from Buildkite. Outside of CI, this function does nothing.""" 42 43 if not buildkite.is_in_buildkite(): 44 return [] 45 46 ui.section("Getting artifact informations from Buildkite") 47 build = os.environ["BUILDKITE_BUILD_NUMBER"] 48 build_id = os.environ["BUILDKITE_BUILD_ID"] 49 job = os.environ["BUILDKITE_JOB_ID"] 50 token = os.environ["BUILDKITE_AGENT_ACCESS_TOKEN"] 51 52 payload = { 53 "query": "*", 54 "step": job, 55 "build": build, 56 "state": "finished", 57 "includeRetriedJobs": "false", 58 "includeDuplicates": "false", 59 } 60 61 attempts = 10 62 res = None 63 for attempt in range(attempts): 64 try: 65 res = requests.get( 66 f"https://agent.buildkite.com/v3/builds/{build_id}/artifacts/search", 67 params=payload, 68 headers={"Authorization": f"Token {token}"}, 69 timeout=30, 70 ) 71 res.raise_for_status() 72 break 73 except Exception as e: 74 # Artifact info only supplies links for annotations, so on repeated 75 # failure degrade to no artifacts rather than crashing the caller 76 # and losing all annotations. A bare except would also swallow 77 # KeyboardInterrupt. 78 print(f"Failed to get artifacts (attempt {attempt + 1}/{attempts}): {e}") 79 if attempt == attempts - 1: 80 return [] 81 time.sleep(5) 82 83 assert res 84 if res.status_code != 200: 85 print(f"Failed to get artifacts: {res.status_code} {res.text}") 86 return [] 87 88 return res.json()
Get artifact informations from Buildkite. Outside of CI, this function does nothing.
def
get_mz_version( workspace: materialize.cargo.Workspace | None = None) -> semver.version.Version:
91def get_mz_version(workspace: cargo.Workspace | None = None) -> VersionInfo: 92 """Get the current Materialize version from Cargo.toml.""" 93 94 if not workspace: 95 workspace = cargo.Workspace(MZ_ROOT) 96 return VersionInfo.parse(workspace.crates["mz-environmentd"].version_string)
Get the current Materialize version from Cargo.toml.
def
dev_docker_tag() -> str:
99def dev_docker_tag() -> str: 100 """The Docker tag under which this commit's images are published. 101 102 Both the publishing side (`ci/test/dev_tag.py`) and anything that pulls 103 those images have to derive the tag the same way, or the pull looks for an 104 image that was never pushed. 105 """ 106 # Ideally we'd use SemVer metadata (e.g., `v1.0.0+metadata`), but `+` is not 107 # a valid character in Docker tags, so we use `--` instead. 108 sanitizer = Sanitizer[os.getenv("CI_SANITIZER", "none")] 109 suffix = "pr" if sanitizer == Sanitizer.none else f"pr-{sanitizer}" 110 return f"v{get_mz_version()}--{suffix}.g{git.rev_parse('HEAD')}"
The Docker tag under which this commit's images are published.
Both the publishing side (ci/test/dev_tag.py) and anything that pulls
those images have to derive the tag the same way, or the pull looks for an
image that was never pushed.