Skip to content

Commit b5e4beb

Browse files
author
Anonymous Committer
committed
Use basic auth for OpenAPI sync
1 parent 95b1c54 commit b5e4beb

5 files changed

Lines changed: 53 additions & 33 deletions

File tree

.github/workflows/sync-openapi.yml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,11 @@ jobs:
2828
ruby-version: "3.2"
2929
bundler-cache: true
3030

31-
- name: Validate sync secret
32-
env:
33-
JUSTSERPAPI_OPENAPI_API_KEY: ${{ secrets.JUSTSERPAPI_OPENAPI_API_KEY }}
34-
run: |
35-
if [ -z "${JUSTSERPAPI_OPENAPI_API_KEY}" ]; then
36-
echo "Missing JUSTSERPAPI_OPENAPI_API_KEY secret" >&2
37-
exit 1
38-
fi
39-
4031
- name: Refresh OpenAPI and generated runtime
4132
env:
42-
JUSTSERPAPI_OPENAPI_API_KEY: ${{ secrets.JUSTSERPAPI_OPENAPI_API_KEY }}
33+
JUSTSERPAPI_OPENAPI_USERNAME: ${{ secrets.JUSTSERPAPI_OPENAPI_USERNAME }}
34+
JUSTSERPAPI_OPENAPI_PASSWORD: ${{ secrets.JUSTSERPAPI_OPENAPI_PASSWORD }}
35+
JUSTSERPAPI_OPENAPI_URL: ${{ secrets.JUSTSERPAPI_OPENAPI_URL }}
4336
run: python3 scripts/sdkctl.py sync
4437

4538
- name: Run control-plane unit tests
@@ -66,4 +59,3 @@ jobs:
6659
labels: |
6760
automation
6861
openapi
69-

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ client = JustSerpApi::Client.new(
6565
Fetch the live OpenAPI document, normalize it, regenerate the SDK, and sync the committed generated runtime:
6666

6767
```bash
68-
JUSTSERPAPI_OPENAPI_API_KEY=... python3 scripts/sdkctl.py sync
68+
JUSTSERPAPI_OPENAPI_USERNAME=... JUSTSERPAPI_OPENAPI_PASSWORD=... python3 scripts/sdkctl.py sync
6969
```
7070

71+
The default source URL is `https://api.justserpapi.com/v3/api-docs/gateway`. Override it with `JUSTSERPAPI_OPENAPI_URL` if needed.
72+
7173
Rebuild from the checked-in raw spec without hitting the network:
7274

7375
```bash
@@ -109,4 +111,3 @@ The upstream spec currently lacks rich per-endpoint response schemas, so the nor
109111
5. GitHub Actions publishes the gem to RubyGems.org and creates a GitHub Release.
110112

111113
RubyGems Trusted Publishing setup is documented in `docs/publishing.md`.
112-

docs/publishing.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ The release workflow publishes this SDK to RubyGems.org through Trusted Publishi
1818
- The release workflow uses the `release` GitHub Actions environment.
1919
- No long-lived RubyGems API key is required.
2020
- The workflow needs `contents: write` and `id-token: write`.
21+
- For the scheduled OpenAPI sync workflow, configure:
22+
- `JUSTSERPAPI_OPENAPI_USERNAME`
23+
- `JUSTSERPAPI_OPENAPI_PASSWORD`
24+
- `JUSTSERPAPI_OPENAPI_URL` only if the docs URL itself changed
2125

2226
## Release Procedure
2327

@@ -32,4 +36,3 @@ The release workflow publishes this SDK to RubyGems.org through Trusted Publishi
3236

3337
4. Create and push a matching Git tag, for example `v0.1.0`.
3438
5. The `release.yml` workflow will build the gem, publish it to RubyGems.org, and create the GitHub Release.
35-

scripts/sdkctl.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
import argparse
7+
import base64
78
import copy
89
import filecmp
910
import json
@@ -17,7 +18,6 @@
1718
from dataclasses import dataclass
1819
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
1920
from urllib.error import HTTPError, URLError
20-
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
2121
from urllib.request import Request, urlopen
2222

2323

@@ -102,27 +102,29 @@ def load_package_version(version_path: pathlib.Path) -> str:
102102

103103

104104
def resolve_source_url(manifest: Dict[str, Any], explicit_url: Optional[str]) -> str:
105-
source_url = explicit_url or os.getenv("JUSTSERPAPI_OPENAPI_URL") or manifest["spec"]["source_url"]
106-
parsed = urlparse(source_url)
107-
query = parse_qsl(parsed.query, keep_blank_values=True)
108-
has_api_key = any(key == "api_key" for key, _ in query)
109-
env_api_key = os.getenv("JUSTSERPAPI_OPENAPI_API_KEY")
110-
if env_api_key and not has_api_key:
111-
query.append(("api_key", env_api_key))
112-
return urlunparse(parsed._replace(query=urlencode(query)))
105+
return explicit_url or os.getenv("JUSTSERPAPI_OPENAPI_URL") or manifest["spec"]["source_url"]
106+
107+
108+
def resolve_fetch_headers() -> Dict[str, str]:
109+
headers = {
110+
"Accept": "application/json",
111+
"User-Agent": "justserpapi-ruby-sdkctl/0.1",
112+
}
113+
114+
username = os.getenv("JUSTSERPAPI_OPENAPI_USERNAME")
115+
password = os.getenv("JUSTSERPAPI_OPENAPI_PASSWORD")
116+
if username is not None and password is not None:
117+
encoded = base64.b64encode(("%s:%s" % (username, password)).encode("utf-8")).decode("ascii")
118+
headers["Authorization"] = "Basic %s" % encoded
119+
120+
return headers
113121

114122

115123
def fetch_spec_command(args: argparse.Namespace, manifest: Dict[str, Any]) -> None:
116124
source_url = resolve_source_url(manifest, args.source_url)
117125
output_path = resolve_path(args.output or manifest["spec"]["raw_path"])
118126

119-
request = Request(
120-
source_url,
121-
headers={
122-
"Accept": "application/json",
123-
"User-Agent": "justserpapi-ruby-sdkctl/0.1"
124-
},
125-
)
127+
request = Request(source_url, headers=resolve_fetch_headers())
126128

127129
log("Fetching spec from %s" % source_url)
128130
try:
@@ -131,7 +133,8 @@ def fetch_spec_command(args: argparse.Namespace, manifest: Dict[str, Any]) -> No
131133
except HTTPError as exc:
132134
if exc.code == 401:
133135
raise CLIError(
134-
"Spec fetch returned HTTP 401. Set JUSTSERPAPI_OPENAPI_API_KEY or JUSTSERPAPI_OPENAPI_URL."
136+
"Spec fetch returned HTTP 401. Set JUSTSERPAPI_OPENAPI_USERNAME and "
137+
"JUSTSERPAPI_OPENAPI_PASSWORD, or JUSTSERPAPI_OPENAPI_URL if the docs URL changed."
135138
) from exc
136139
raise CLIError("Unable to fetch spec from %s: HTTP %s" % (source_url, exc.code)) from exc
137140
except URLError as exc:
@@ -560,4 +563,3 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
560563

561564
if __name__ == "__main__":
562565
raise SystemExit(main())
563-

scripts/tests/test_sdkctl.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import copy
2+
import os
23
import unittest
4+
from unittest import mock
35

46
from scripts import sdkctl
57

@@ -114,6 +116,26 @@ def test_detect_breaking_changes_reports_newly_required_param(self):
114116
)
115117

116118

119+
class FetchAuthResolutionTest(unittest.TestCase):
120+
def test_resolve_fetch_headers_supports_basic_auth(self):
121+
with mock.patch.dict(
122+
os.environ,
123+
{
124+
"JUSTSERPAPI_OPENAPI_USERNAME": "demo-user",
125+
"JUSTSERPAPI_OPENAPI_PASSWORD": "demo-pass",
126+
},
127+
clear=False,
128+
):
129+
headers = sdkctl.resolve_fetch_headers()
130+
131+
self.assertEqual("Basic ZGVtby11c2VyOmRlbW8tcGFzcw==", headers["Authorization"])
132+
133+
def test_resolve_fetch_headers_omits_authorization_without_credentials(self):
134+
with mock.patch.dict(os.environ, {}, clear=True):
135+
headers = sdkctl.resolve_fetch_headers()
136+
137+
self.assertNotIn("Authorization", headers)
138+
139+
117140
if __name__ == "__main__":
118141
unittest.main()
119-

0 commit comments

Comments
 (0)