diff --git a/Directory.Build.props b/Directory.Build.props index 7050ad5c..0894c782 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ --> 0.1.0 - rc.19 + $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix) $(Version) diff --git a/tools/ci/smoke-postgresql-oidc-upgrade.sh b/tools/ci/smoke-postgresql-oidc-upgrade.sh index ba5a2880..9de51e8e 100755 --- a/tools/ci/smoke-postgresql-oidc-upgrade.sh +++ b/tools/ci/smoke-postgresql-oidc-upgrade.sh @@ -357,9 +357,11 @@ PY } exercise_server_offered_client_update() { - local version access_token agents prior_agent_id tenant_response update_request release_response + local version update_channel access_token agents prior_agent_id tenant_response update_request release_response local client_root updater_path candidate_zip attempt_response attempt_id attempt_state attempt_detail version="$(python3 tools/ci/product-version.py)" + update_channel=stable + if [[ "$version" == *-* ]]; then update_channel=prerelease; fi access_token="$(request_operator_access_token)" agents="$(curl --silent --show-error --fail \ --header "Authorization: Bearer ${access_token}" \ @@ -471,7 +473,7 @@ Environment=NetRatel_CREDENTIAL_MACHINE_ID=$upgrade_machine_identity Environment=NetRatel_CLIENT_LOG_DIR=$native_directory/logs Environment=NetRatelCLIENT__Client__ApiBaseUrl=$api_url Environment=NetRatelCLIENT__Client__AutoUpdate__Mode=Service -Environment=NetRatelCLIENT__Client__AutoUpdate__Channel=Prerelease +Environment=NetRatelCLIENT__Client__AutoUpdate__Channel=$update_channel Environment=NetRatelCLIENT__Gateway__Endpoint=https://127.0.0.1:$NETRATEL_GATEWAY_TEST_PORT Environment=SSL_CERT_FILE=$smoke_ca_certificate_path @@ -568,18 +570,18 @@ PY release_response="$(curl --silent --show-error --fail \ --header "Authorization: Bearer ${access_token}" \ "${api_url}/api/v1/client-updates/releases?runtimeId=linux-x64")" - jq -e --arg version "$version" \ - 'any(.[]; .version == $version and .channel == "prerelease" and .enabled == true)' \ + jq -e --arg version "$version" --arg channel "$update_channel" \ + 'any(.[]; .version == $version and .channel == $channel and .enabled == true)' \ <<<"$release_response" >/dev/null || { - echo "The explicitly uploaded candidate is not a published prerelease update." >&2 + echo "The explicitly uploaded candidate is not an enabled published ${update_channel} update." >&2 return 1 } tenant_response="$(curl --silent --show-error --fail \ --header "Authorization: Bearer ${access_token}" \ "${api_url}/api/v1/tenants/${upgrade_tenant_id}")" - update_request="$(jq --arg version "$version" \ + update_request="$(jq --arg version "$version" --arg channel "$update_channel" \ '{name,description,location,domains,contactPerson,contactEmail, - autoUpdate:true,autoUpdateChannel:"prerelease",autoUpdateTargetVersion:$version}' \ + autoUpdate:true,autoUpdateChannel:$channel,autoUpdateTargetVersion:$version}' \ <<<"$tenant_response")" curl --silent --show-error --fail-with-body --request PUT \ --header "Authorization: Bearer ${access_token}" --header 'Content-Type: application/json' \ diff --git a/tools/ci/test-release-validation.py b/tools/ci/test-release-validation.py index dcf58149..5976707d 100644 --- a/tools/ci/test-release-validation.py +++ b/tools/ci/test-release-validation.py @@ -1176,6 +1176,117 @@ def test_spacetimedb_runtime_dependencies_and_known_runtime_paths_are_absent(sel self.assertEqual([], existing, "Known retired runtime implementation paths must remain deleted.") +class OidcUpgradeChannelTests(unittest.TestCase): + script_path = ROOT / "tools/ci/smoke-postgresql-oidc-upgrade.sh" + + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix="netratel-upgrade-channel-") + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name) + (self.root / "tools/ci").mkdir(parents=True) + shutil.copy2(ROOT / "tools/ci/product-version.py", self.root / "tools/ci/product-version.py") + shutil.copy2(ROOT / "Directory.Build.props", self.root / "Directory.Build.props") + self.assertIsNotNone(shutil.which("jq"), "The upgrade fixture requires jq.") + + source = self.script_path.read_text() + function = source.split("exercise_server_offered_client_update() {\n", 1)[1] + self.selection = function.split(" access_token=", 1)[0] + self.catalog_and_tenant = function[function.index(' release_response="$(curl'):] + self.catalog_and_tenant = self.catalog_and_tenant.split(' attempt_id=""', 1)[0] + client_channel = re.search( + r"(?m)^Environment=NetRatelCLIENT__Client__AutoUpdate__Channel=.*$", function) + self.assertIsNotNone(client_channel, "The native Client unit must declare its update channel.") + self.client_channel_line = client_channel.group(0) + + self.bin = self.root / "bin" + self.bin.mkdir() + curl = self.bin / "curl" + curl.write_text("""#!/usr/bin/env python3 +import json, os, pathlib, sys +args = sys.argv[1:] +url = args[-1] +method = args[args.index('--request') + 1] if '--request' in args else 'GET' +with open(os.environ['REQUEST_LOG'], 'a') as log: + log.write(json.dumps({'method': method, 'url': url}) + '\\n') +if '/client-updates/releases?' in url and method == 'GET': + print(pathlib.Path(os.environ['RELEASE_CATALOG']).read_text()) +elif url.endswith('/api/v1/tenants/42') and method == 'GET': + print(json.dumps({'name': 'Synthetic upgrade tenant', 'autoUpdate': False})) +elif url.endswith('/api/v1/tenants/42') and method == 'PUT': + pathlib.Path(os.environ['TENANT_REQUEST']).write_text(args[args.index('--data') + 1]) +else: + raise SystemExit('Unexpected upgrade probe request: ' + method + ' ' + url) +""") + curl.chmod(0o755) + + def run_update_probe(self, suffix, releases): + props_path = self.root / "Directory.Build.props" + props = ET.parse(props_path) + props.find(".//VersionPrefix").text = "0.1.0" + props.find(".//VersionSuffix").text = suffix + props.write(props_path) + catalog = self.root / "releases.json" + catalog.write_text(json.dumps(releases)) + tenant_request = self.root / "tenant-request.json" + unit = self.root / "client-unit.txt" + request_log = self.root / "requests.jsonl" + for output in (tenant_request, unit, request_log): + output.unlink(missing_ok=True) + probe = ( + 'set -euo pipefail\n' + 'api_url=http://synthetic.invalid\nupgrade_tenant_id=42\n' + 'exercise_update_probe() {\n' + self.selection + + 'access_token=synthetic\n' + + 'cat > "$CLIENT_UNIT" <