Skip to content

Commit ff68a74

Browse files
authored
Fix vpngw subnet (#73)
* fix issues * Prepare release nebius-vpngw-v0.5.2 * fix issues * fix issues
1 parent 76acb7f commit ff68a74

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+10486
-3371
lines changed

.github/workflows/vpngw-ci.yml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: vpngw-ci
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
paths:
9+
- "services/vpngw/**"
10+
- ".github/workflows/vpngw-ci.yml"
11+
workflow_dispatch:
12+
13+
defaults:
14+
run:
15+
working-directory: services/vpngw
16+
17+
concurrency:
18+
group: vpngw-${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
lint:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: actions/setup-python@v5
27+
with:
28+
python-version: "3.12"
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
pip install -e ".[dev]"
33+
- name: Ruff
34+
run: python -m ruff check src tests
35+
36+
unit-tests:
37+
runs-on: ubuntu-latest
38+
needs: lint
39+
steps:
40+
- uses: actions/checkout@v4
41+
- uses: actions/setup-python@v5
42+
with:
43+
python-version: "3.12"
44+
- name: Install dependencies
45+
run: |
46+
python -m pip install --upgrade pip
47+
pip install -e ".[dev]"
48+
- name: Fast unit tests
49+
run: python -m pytest -n auto -m "not integration" tests/unit
50+
51+
build:
52+
if: github.event_name == 'pull_request'
53+
runs-on: ubuntu-latest
54+
needs: unit-tests
55+
steps:
56+
- uses: actions/checkout@v4
57+
- uses: actions/setup-python@v5
58+
with:
59+
python-version: "3.12"
60+
- name: Install dependencies
61+
run: |
62+
python -m pip install --upgrade pip
63+
pip install -e ".[dev]"
64+
- name: Build wheel
65+
run: python -m build --wheel
66+
67+
integration-tests:
68+
if: github.event_name == 'workflow_dispatch'
69+
runs-on: ubuntu-latest
70+
needs: unit-tests
71+
steps:
72+
- uses: actions/checkout@v4
73+
- uses: actions/setup-python@v5
74+
with:
75+
python-version: "3.12"
76+
- name: Install dependencies
77+
run: |
78+
python -m pip install --upgrade pip
79+
pip install -e ".[dev]"
80+
- name: Integration tests
81+
run: python -m pytest -n auto -m integration tests/integration
82+
83+
coverage:
84+
if: github.event_name == 'workflow_dispatch'
85+
runs-on: ubuntu-latest
86+
needs: unit-tests
87+
steps:
88+
- uses: actions/checkout@v4
89+
- uses: actions/setup-python@v5
90+
with:
91+
python-version: "3.12"
92+
- name: Install dependencies
93+
run: |
94+
python -m pip install --upgrade pip
95+
pip install -e ".[dev]"
96+
- name: Coverage
97+
run: python -m pytest --cov=nebius_vpngw --cov-report=term-missing --cov-report=xml tests/unit
98+
- name: Upload coverage report
99+
uses: actions/upload-artifact@v4
100+
with:
101+
name: vpngw-coverage
102+
path: services/vpngw/coverage.xml
103+
104+
packaging:
105+
if: github.event_name == 'workflow_dispatch'
106+
runs-on: ubuntu-latest
107+
needs:
108+
- integration-tests
109+
- coverage
110+
steps:
111+
- uses: actions/checkout@v4
112+
- uses: actions/setup-python@v5
113+
with:
114+
python-version: "3.12"
115+
- name: Install dependencies
116+
run: |
117+
python -m pip install --upgrade pip
118+
pip install -e ".[dev]"
119+
- name: Build wheel
120+
run: python -m build --wheel
121+
- name: Upload wheel
122+
uses: actions/upload-artifact@v4
123+
with:
124+
name: nebius-vpngw-wheel
125+
path: services/vpngw/dist/*.whl
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
name: nebius-vpngw-release-publish
2+
3+
on:
4+
push:
5+
tags:
6+
- "nebius-vpngw-v*"
7+
8+
permissions:
9+
contents: write
10+
11+
concurrency:
12+
group: nebius-vpngw-release-${{ github.ref }}
13+
cancel-in-progress: false
14+
15+
env:
16+
APP_DIR: services/vpngw
17+
TAG_PREFIX: nebius-vpngw
18+
MAIN_BRANCH: main
19+
ASSET_GLOB: "dist/*.whl"
20+
21+
jobs:
22+
release:
23+
runs-on: ubuntu-latest
24+
defaults:
25+
run:
26+
working-directory: ${{ env.APP_DIR }}
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v6
30+
with:
31+
fetch-depth: 0
32+
33+
- name: Resolve release version from tag
34+
id: version
35+
env:
36+
REF_NAME: ${{ github.ref_name }}
37+
run: |
38+
set -euo pipefail
39+
if [[ ! "${REF_NAME}" =~ ^${TAG_PREFIX}-v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
40+
echo "::error::Tag '${REF_NAME}' must match ${TAG_PREFIX}-vMAJOR.MINOR.PATCH"
41+
exit 1
42+
fi
43+
echo "tag=${REF_NAME}" >> "$GITHUB_OUTPUT"
44+
echo "version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT"
45+
46+
- name: Ensure tag commit belongs to main history
47+
run: |
48+
set -euo pipefail
49+
git fetch origin "${MAIN_BRANCH}" --depth=1
50+
if ! git merge-base --is-ancestor "${GITHUB_SHA}" "origin/${MAIN_BRANCH}"; then
51+
echo "::error::Tag commit ${GITHUB_SHA} is not in origin/${MAIN_BRANCH} history"
52+
exit 1
53+
fi
54+
55+
- name: Check release existence
56+
id: state
57+
env:
58+
GH_TOKEN: ${{ github.token }}
59+
run: |
60+
set -euo pipefail
61+
if gh release view "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
62+
echo "release_exists=true" >> "$GITHUB_OUTPUT"
63+
else
64+
echo "release_exists=false" >> "$GITHUB_OUTPUT"
65+
fi
66+
67+
- name: Set up Python
68+
if: steps.state.outputs.release_exists == 'false'
69+
uses: actions/setup-python@v6
70+
with:
71+
python-version: "3.12"
72+
73+
- name: Install dependencies
74+
if: steps.state.outputs.release_exists == 'false'
75+
run: |
76+
python -m pip install --upgrade pip
77+
pip install -e ".[dev]"
78+
79+
- name: Lint
80+
if: steps.state.outputs.release_exists == 'false'
81+
run: python -m ruff check src tests
82+
83+
- name: Test
84+
if: steps.state.outputs.release_exists == 'false'
85+
run: python -m pytest -q
86+
87+
- name: Build artifact
88+
if: steps.state.outputs.release_exists == 'false'
89+
run: |
90+
set -euo pipefail
91+
rm -rf build dist
92+
python -m build --wheel
93+
94+
- name: Resolve and verify artifact
95+
if: steps.state.outputs.release_exists == 'false'
96+
id: asset
97+
run: |
98+
set -euo pipefail
99+
shopt -s nullglob
100+
assets=( ${ASSET_GLOB} )
101+
shopt -u nullglob
102+
if [[ "${#assets[@]}" -eq 0 ]]; then
103+
echo "::error::No release asset found with glob ${ASSET_GLOB}"
104+
exit 1
105+
fi
106+
asset="${assets[0]}"
107+
108+
wheel_version="$(python - "${asset}" <<'PY'
109+
import sys
110+
import zipfile
111+
from email import message_from_bytes
112+
113+
wheel = sys.argv[1]
114+
with zipfile.ZipFile(wheel) as zf:
115+
metadata_names = [n for n in zf.namelist() if n.endswith("METADATA")]
116+
if not metadata_names:
117+
raise SystemExit("No METADATA found in wheel")
118+
metadata = message_from_bytes(zf.read(metadata_names[0]))
119+
print(metadata.get("Version", ""))
120+
PY
121+
)"
122+
123+
if [[ "${wheel_version}" != "${{ steps.version.outputs.version }}" ]]; then
124+
echo "::error::Wheel version (${wheel_version}) does not match tag version (${{ steps.version.outputs.version }})"
125+
exit 1
126+
fi
127+
128+
echo "path=${asset}" >> "$GITHUB_OUTPUT"
129+
130+
- name: Generate release notes from changelog
131+
if: steps.state.outputs.release_exists == 'false'
132+
id: notes
133+
run: |
134+
set -euo pipefail
135+
python - "${{ steps.version.outputs.tag }}" "${{ steps.version.outputs.version }}" <<'PY'
136+
import pathlib
137+
import re
138+
import sys
139+
140+
tag = sys.argv[1]
141+
version = sys.argv[2]
142+
143+
lines = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8").splitlines()
144+
patterns = [
145+
re.compile(rf"^##\s+\[?{re.escape(tag)}\]?(?:\s+-.*)?$"),
146+
re.compile(rf"^##\s+\[?{re.escape(version)}\]?(?:\s+-.*)?$"),
147+
]
148+
any_heading = re.compile(r"^##\s+")
149+
150+
capture = False
151+
notes = []
152+
for line in lines:
153+
stripped = line.strip()
154+
if not capture and any(p.match(stripped) for p in patterns):
155+
capture = True
156+
continue
157+
if capture and any_heading.match(stripped):
158+
break
159+
if capture:
160+
notes.append(line)
161+
162+
notes_text = "\n".join(notes).strip() or f"Release {tag}"
163+
pathlib.Path("release-notes.md").write_text(notes_text + "\n", encoding="utf-8")
164+
PY
165+
echo "path=release-notes.md" >> "$GITHUB_OUTPUT"
166+
167+
- name: Create GitHub release
168+
if: steps.state.outputs.release_exists == 'false'
169+
env:
170+
GH_TOKEN: ${{ github.token }}
171+
run: |
172+
set -euo pipefail
173+
gh release create "${{ steps.version.outputs.tag }}" "${{ steps.asset.outputs.path }}" \
174+
--title "${{ steps.version.outputs.tag }}" \
175+
--notes-file "${{ steps.notes.outputs.path }}"
176+
177+
- name: Write release manifest
178+
if: steps.state.outputs.release_exists == 'false'
179+
run: |
180+
set -euo pipefail
181+
jq -n \
182+
--arg actor "${GITHUB_ACTOR}" \
183+
--arg event "${GITHUB_EVENT_NAME}" \
184+
--arg ref "${GITHUB_REF}" \
185+
--arg tag "${{ steps.version.outputs.tag }}" \
186+
--arg version "${{ steps.version.outputs.version }}" \
187+
--arg sha "${GITHUB_SHA}" \
188+
--arg asset "${{ steps.asset.outputs.path }}" \
189+
--arg run_id "${GITHUB_RUN_ID}" \
190+
--arg run_attempt "${GITHUB_RUN_ATTEMPT}" \
191+
--arg repository "${GITHUB_REPOSITORY}" \
192+
'{
193+
actor: $actor,
194+
repository: $repository,
195+
event: $event,
196+
ref: $ref,
197+
tag: $tag,
198+
version: $version,
199+
sha: $sha,
200+
asset: $asset,
201+
run_id: $run_id,
202+
run_attempt: $run_attempt,
203+
timestamp_utc: now | todate
204+
}' > release-manifest.json
205+
206+
- name: Upload release manifest artifact
207+
if: steps.state.outputs.release_exists == 'false'
208+
uses: actions/upload-artifact@v7
209+
with:
210+
name: nebius-vpngw-release-manifest-${{ github.run_id }}
211+
path: ${{ env.APP_DIR }}/release-manifest.json
212+
213+
- name: Release already exists
214+
if: steps.state.outputs.release_exists == 'true'
215+
run: |
216+
echo "Release ${{ steps.version.outputs.tag }} already exists. Skipping."
217+
218+
- name: Publish run summary
219+
run: |
220+
{
221+
echo "### nebius-vpngw release publish"
222+
echo ""
223+
echo "- Tag: \`${{ steps.version.outputs.tag }}\`"
224+
echo "- Version: \`${{ steps.version.outputs.version }}\`"
225+
echo "- Commit: \`${GITHUB_SHA}\`"
226+
echo "- Release exists: \`${{ steps.state.outputs.release_exists }}\`"
227+
} >> "$GITHUB_STEP_SUMMARY"

services/vpngw/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ All notable changes to this project are tracked here. This changelog follows
1010
- Before tagging, move items from `Unreleased` into a new
1111
`## [nebius-vpngw-vX.Y.Z] - YYYY-MM-DD` section, then leave an empty `Unreleased` section.
1212
- Newer releases go above older ones; do not reorder entries within a release.
13-
- The release script (`release.sh`) automates rolling `Unreleased` into a dated `## [nebius-vpngw-vX.Y.Z] - YYYY-MM-DD` and re-adding an empty `Unreleased`.
13+
- The release helper (`publish-release.sh`) automates rolling `Unreleased` into a dated `## [nebius-vpngw-vX.Y.Z] - YYYY-MM-DD` and re-adding an empty `Unreleased`.
1414

1515
## [Unreleased]
1616

17+
## [nebius-vpngw-v0.5.2] - 2026-03-08
18+
19+
- Expanded the pytest-based test suite, split unit/integration coverage, centralized test config in `pyproject.toml`, and added `Makefile` targets plus service-scoped CI.
20+
- Hardened operational CLI commands: `restart-tunnel` now performs a full IPsec and matching-BGP reset, and `failover`/`failback` were tightened and validated against the active/passive HA flow.
21+
- Improved route management for Nebius workload subnets that inherit parent network pools, and added live BGP advertisement reconciliation so route commands reflect the current YAML instead of stale FRR state.
22+
- Switched releases to the monorepo service pattern: `publish-release.sh` now handles prep/tagging, `vpngw-ci.yml` is PR/manual only, and `vpngw-release.yml` is the dedicated tag-driven GitHub Release workflow for `nebius-vpngw-v*`.
1723
## [nebius-vpngw-v0.5.1] - 2026-02-04
1824

1925
- Fail fast when `--local-config-file` is provided but the config path does not exist.

0 commit comments

Comments
 (0)