pyinfra-incus 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinfra_incus-0.1.0/.github/scripts/validate-release.py +84 -0
- pyinfra_incus-0.1.0/.github/workflows/ci.yml +47 -0
- pyinfra_incus-0.1.0/.github/workflows/integration.yml +122 -0
- pyinfra_incus-0.1.0/.github/workflows/release-dry-run.yml +48 -0
- pyinfra_incus-0.1.0/.github/workflows/release-please.yml +36 -0
- pyinfra_incus-0.1.0/.github/workflows/release.yml +113 -0
- pyinfra_incus-0.1.0/.gitignore +17 -0
- pyinfra_incus-0.1.0/.moon/toolchains.yml +2 -0
- pyinfra_incus-0.1.0/.moon/workspace.yml +12 -0
- pyinfra_incus-0.1.0/.python-version +1 -0
- pyinfra_incus-0.1.0/.release-please-manifest.json +1 -0
- pyinfra_incus-0.1.0/.session.md +233 -0
- pyinfra_incus-0.1.0/AGENTS.md +13 -0
- pyinfra_incus-0.1.0/CHANGELOG.md +21 -0
- pyinfra_incus-0.1.0/CLAUDE.md +1 -0
- pyinfra_incus-0.1.0/PKG-INFO +8 -0
- pyinfra_incus-0.1.0/mise.lock +109 -0
- pyinfra_incus-0.1.0/mise.toml +36 -0
- pyinfra_incus-0.1.0/moon.yml +111 -0
- pyinfra_incus-0.1.0/pyproject.toml +66 -0
- pyinfra_incus-0.1.0/release-please-config.json +24 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/__init__.py +75 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_client.py +113 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_control_plane.py +313 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_fact.py +158 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_images.py +347 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_instances.py +657 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_network.py +239 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_network_policy.py +457 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_operational.py +149 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_scope.py +391 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/_storage.py +415 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/facts.py +520 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/operations.py +1059 -0
- pyinfra_incus-0.1.0/src/pyinfra_incus/py.typed +0 -0
- pyinfra_incus-0.1.0/tests/conftest.py +31 -0
- pyinfra_incus-0.1.0/tests/integration/test_control_plane_workflow.py +228 -0
- pyinfra_incus-0.1.0/tests/integration/test_foundation_workflow.py +361 -0
- pyinfra_incus-0.1.0/tests/integration/test_image_workflow.py +160 -0
- pyinfra_incus-0.1.0/tests/integration/test_instance_workflow.py +306 -0
- pyinfra_incus-0.1.0/tests/integration/test_policy_workflow.py +248 -0
- pyinfra_incus-0.1.0/tests/integration/test_scope_workflow.py +283 -0
- pyinfra_incus-0.1.0/tests/integration/test_server.py +35 -0
- pyinfra_incus-0.1.0/tests/integration/test_snapshot_health_workflow.py +176 -0
- pyinfra_incus-0.1.0/tests/test_client.py +78 -0
- pyinfra_incus-0.1.0/tests/test_control_plane.py +129 -0
- pyinfra_incus-0.1.0/tests/test_facts.py +211 -0
- pyinfra_incus-0.1.0/tests/test_images.py +106 -0
- pyinfra_incus-0.1.0/tests/test_instances.py +205 -0
- pyinfra_incus-0.1.0/tests/test_network.py +99 -0
- pyinfra_incus-0.1.0/tests/test_network_policy.py +124 -0
- pyinfra_incus-0.1.0/tests/test_operational.py +93 -0
- pyinfra_incus-0.1.0/tests/test_scope.py +137 -0
- pyinfra_incus-0.1.0/tests/test_storage.py +88 -0
- pyinfra_incus-0.1.0/uv.lock +1245 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Validate the built distributions before a registry upload."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import email
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
import tarfile
|
|
9
|
+
import zipfile
|
|
10
|
+
from email.message import Message
|
|
11
|
+
|
|
12
|
+
DIST = pathlib.Path("dist")
|
|
13
|
+
PACKAGE_NAME = "pyinfra-incus"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fail(message: str) -> None:
|
|
17
|
+
raise SystemExit(message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def metadata_from_wheel(path: pathlib.Path) -> Message:
|
|
21
|
+
with zipfile.ZipFile(path) as archive:
|
|
22
|
+
metadata_files = [
|
|
23
|
+
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
|
|
24
|
+
]
|
|
25
|
+
if len(metadata_files) != 1:
|
|
26
|
+
fail(f"expected one METADATA file in {path}, found {len(metadata_files)}")
|
|
27
|
+
return email.message_from_bytes(archive.read(metadata_files[0]))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def metadata_from_sdist(path: pathlib.Path) -> Message:
|
|
31
|
+
with tarfile.open(path, "r:gz") as archive:
|
|
32
|
+
metadata_files = [
|
|
33
|
+
member for member in archive.getmembers() if member.name.endswith("/PKG-INFO")
|
|
34
|
+
]
|
|
35
|
+
if len(metadata_files) != 1:
|
|
36
|
+
fail(f"expected one PKG-INFO file in {path}, found {len(metadata_files)}")
|
|
37
|
+
metadata_file = archive.extractfile(metadata_files[0])
|
|
38
|
+
if metadata_file is None:
|
|
39
|
+
fail(f"could not read PKG-INFO from {path}")
|
|
40
|
+
return email.message_from_bytes(metadata_file.read())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_metadata(metadata: Message, expected_version: str | None) -> str:
|
|
44
|
+
name = metadata.get("Name")
|
|
45
|
+
version = metadata.get("Version")
|
|
46
|
+
if name != PACKAGE_NAME:
|
|
47
|
+
fail(f"expected package name {PACKAGE_NAME!r}, got {name!r}")
|
|
48
|
+
if not version:
|
|
49
|
+
fail("distribution metadata has no version")
|
|
50
|
+
if expected_version is not None and version != expected_version:
|
|
51
|
+
fail(f"tag version {expected_version!r} does not match package version {version!r}")
|
|
52
|
+
|
|
53
|
+
direct_references = [
|
|
54
|
+
requirement for requirement in metadata.get_all("Requires-Dist", []) if " @ " in requirement
|
|
55
|
+
]
|
|
56
|
+
if direct_references:
|
|
57
|
+
rendered = "\n".join(f"- {requirement}" for requirement in direct_references)
|
|
58
|
+
fail(f"public package indexes reject direct dependency references:\n{rendered}")
|
|
59
|
+
|
|
60
|
+
return version
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> None:
|
|
64
|
+
expected_version = sys.argv[1] if len(sys.argv) == 2 else None
|
|
65
|
+
if len(sys.argv) > 2:
|
|
66
|
+
fail("usage: validate-release.py [expected-version]")
|
|
67
|
+
|
|
68
|
+
wheels = sorted(DIST.glob("*.whl"))
|
|
69
|
+
sdists = sorted(DIST.glob("*.tar.gz"))
|
|
70
|
+
if len(wheels) != 1 or len(sdists) != 1:
|
|
71
|
+
fail(
|
|
72
|
+
f"expected one wheel and one sdist, found {len(wheels)} wheel(s) and {len(sdists)} sdist(s)"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
wheel_version = validate_metadata(metadata_from_wheel(wheels[0]), expected_version)
|
|
76
|
+
sdist_version = validate_metadata(metadata_from_sdist(sdists[0]), expected_version)
|
|
77
|
+
if wheel_version != sdist_version:
|
|
78
|
+
fail(f"wheel version {wheel_version!r} does not match sdist version {sdist_version!r}")
|
|
79
|
+
|
|
80
|
+
print(f"validated {PACKAGE_NAME} {wheel_version}: {wheels[0].name}, {sdists[0].name}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [master]
|
|
6
|
+
push:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ci:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 15
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
|
|
28
|
+
- name: Setup mise
|
|
29
|
+
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
|
|
30
|
+
with:
|
|
31
|
+
version: 2026.6.14
|
|
32
|
+
cache: true
|
|
33
|
+
|
|
34
|
+
- name: Resolve uv cache directory
|
|
35
|
+
id: uv-cache
|
|
36
|
+
run: echo "dir=$(uv cache dir)" >> "$GITHUB_OUTPUT"
|
|
37
|
+
|
|
38
|
+
- name: Cache uv downloads
|
|
39
|
+
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
|
40
|
+
with:
|
|
41
|
+
path: ${{ steps.uv-cache.outputs.dir }}
|
|
42
|
+
key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }}
|
|
43
|
+
restore-keys: |
|
|
44
|
+
${{ runner.os }}-uv-
|
|
45
|
+
|
|
46
|
+
- name: Run Moon CI
|
|
47
|
+
run: moon ci root:check --summary minimal
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
name: Integration
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [master]
|
|
6
|
+
push:
|
|
7
|
+
branches: [master]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions: {}
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
incus:
|
|
18
|
+
name: Incus ${{ matrix.label }}
|
|
19
|
+
runs-on: ubuntu-24.04
|
|
20
|
+
timeout-minutes: 25
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
include:
|
|
27
|
+
- label: 7.0 LTS
|
|
28
|
+
repository: lts-7.0
|
|
29
|
+
package_pattern: '1:7.0.*'
|
|
30
|
+
- label: stable
|
|
31
|
+
repository: stable
|
|
32
|
+
package_pattern: '1:7.*'
|
|
33
|
+
env:
|
|
34
|
+
INCUS_REPOSITORY: ${{ matrix.repository }}
|
|
35
|
+
INCUS_PACKAGE_PATTERN: ${{ matrix.package_pattern }}
|
|
36
|
+
PYINFRA_INCUS_TEST_SOCKET: /var/lib/incus/unix.socket
|
|
37
|
+
steps:
|
|
38
|
+
- name: Checkout
|
|
39
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
40
|
+
with:
|
|
41
|
+
fetch-depth: 0
|
|
42
|
+
persist-credentials: false
|
|
43
|
+
|
|
44
|
+
- name: Setup mise
|
|
45
|
+
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
|
|
46
|
+
with:
|
|
47
|
+
version: 2026.6.14
|
|
48
|
+
cache: true
|
|
49
|
+
|
|
50
|
+
- name: Resolve uv cache directory
|
|
51
|
+
id: uv-cache
|
|
52
|
+
run: echo "dir=$(uv cache dir)" >> "$GITHUB_OUTPUT"
|
|
53
|
+
|
|
54
|
+
- name: Cache uv downloads
|
|
55
|
+
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
|
56
|
+
with:
|
|
57
|
+
path: ${{ steps.uv-cache.outputs.dir }}
|
|
58
|
+
key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }}
|
|
59
|
+
restore-keys: |
|
|
60
|
+
${{ runner.os }}-uv-
|
|
61
|
+
|
|
62
|
+
- name: Check KVM availability
|
|
63
|
+
run: test -c /dev/kvm
|
|
64
|
+
|
|
65
|
+
- name: Isolate Incus client configuration
|
|
66
|
+
run: echo "INCUS_CONF=$RUNNER_TEMP/incus-user" >> "$GITHUB_ENV"
|
|
67
|
+
|
|
68
|
+
- name: Configure Zabbly package repository
|
|
69
|
+
run: |
|
|
70
|
+
curl -fsSL https://pkgs.zabbly.com/key.asc -o "$RUNNER_TEMP/zabbly.asc"
|
|
71
|
+
fingerprint=$(gpg --show-keys --with-colons "$RUNNER_TEMP/zabbly.asc" | awk -F: '$1 == "fpr" { print $10; exit }')
|
|
72
|
+
test "$fingerprint" = "4EFC590696CB15B87C73A3AD82CC8797C838DCFD"
|
|
73
|
+
|
|
74
|
+
sudo install -d -m 0755 /etc/apt/keyrings
|
|
75
|
+
sudo install -m 0644 "$RUNNER_TEMP/zabbly.asc" /etc/apt/keyrings/zabbly.asc
|
|
76
|
+
printf '%s\n' \
|
|
77
|
+
'Enabled: yes' \
|
|
78
|
+
'Types: deb' \
|
|
79
|
+
"URIs: https://pkgs.zabbly.com/incus/${INCUS_REPOSITORY}" \
|
|
80
|
+
'Suites: noble' \
|
|
81
|
+
'Components: main' \
|
|
82
|
+
'Architectures: amd64' \
|
|
83
|
+
'Signed-By: /etc/apt/keyrings/zabbly.asc' \
|
|
84
|
+
| sudo tee /etc/apt/sources.list.d/zabbly-incus.sources >/dev/null
|
|
85
|
+
|
|
86
|
+
- name: Install Incus
|
|
87
|
+
run: |
|
|
88
|
+
sudo apt-get update
|
|
89
|
+
sudo apt-get install -y acl incus incus-agent qemu-system-x86 qemu-utils ovmf virtiofsd
|
|
90
|
+
|
|
91
|
+
package_version=$(dpkg-query -W -f='${Version}' incus)
|
|
92
|
+
echo "Incus package: $package_version"
|
|
93
|
+
case "$package_version" in
|
|
94
|
+
$INCUS_PACKAGE_PATTERN) ;;
|
|
95
|
+
*)
|
|
96
|
+
echo "::error::Unexpected Incus package version: $package_version"
|
|
97
|
+
exit 1
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
sudo env INCUS_CONF=/root/.config/incus incus admin init --minimal
|
|
102
|
+
sudo env INCUS_CONF=/root/.config/incus incus image copy images:alpine/edge/amd64 local: --alias ci-base
|
|
103
|
+
sudo env INCUS_CONF=/root/.config/incus incus version
|
|
104
|
+
uv run --locked python -c 'from importlib.metadata import version; print("pyincusd", version("pyincusd"))'
|
|
105
|
+
|
|
106
|
+
sudo test -S "$PYINFRA_INCUS_TEST_SOCKET"
|
|
107
|
+
sudo setfacl -m "u:${USER}:rw" "$PYINFRA_INCUS_TEST_SOCKET"
|
|
108
|
+
incus image info images:alpine/3.23/amd64 >/dev/null
|
|
109
|
+
incus image info images:alpine/3.23/amd64 --vm >/dev/null
|
|
110
|
+
|
|
111
|
+
- name: Run Incus integration tests
|
|
112
|
+
run: moon run root:test-integration --summary minimal
|
|
113
|
+
|
|
114
|
+
- name: Diagnostics on failure
|
|
115
|
+
if: failure()
|
|
116
|
+
run: |
|
|
117
|
+
sudo env INCUS_CONF=/root/.config/incus incus list || true
|
|
118
|
+
sudo env INCUS_CONF=/root/.config/incus incus image list || true
|
|
119
|
+
sudo env INCUS_CONF=/root/.config/incus incus storage list || true
|
|
120
|
+
sudo env INCUS_CONF=/root/.config/incus incus network list || true
|
|
121
|
+
sudo journalctl -u incus --no-pager -n 200 || true
|
|
122
|
+
df -h || true
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Rehearse the publishable package on Release Please PRs without uploading it.
|
|
2
|
+
# Keeping the trigger broad ensures the check still reports as skipped on
|
|
3
|
+
# ordinary pull requests.
|
|
4
|
+
|
|
5
|
+
name: Release Dry Run
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- master
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions: {}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
package-release-dry-run:
|
|
17
|
+
name: Package Release Dry Run
|
|
18
|
+
if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please--') }}
|
|
19
|
+
runs-on: ubuntu-24.04
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
steps:
|
|
23
|
+
- name: Check out repository
|
|
24
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- name: Setup mise
|
|
30
|
+
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
|
|
31
|
+
with:
|
|
32
|
+
version: 2026.6.14
|
|
33
|
+
cache: true
|
|
34
|
+
|
|
35
|
+
- name: Build publishable distributions
|
|
36
|
+
run: uv build --no-sources --clear
|
|
37
|
+
|
|
38
|
+
- name: Validate distribution metadata
|
|
39
|
+
run: python3 .github/scripts/validate-release.py
|
|
40
|
+
|
|
41
|
+
- name: Smoke test installed wheel
|
|
42
|
+
run: |
|
|
43
|
+
wheel=$(find dist -maxdepth 1 -name '*.whl' -print -quit)
|
|
44
|
+
uv run --no-project --with "$wheel" -- \
|
|
45
|
+
python -c 'import pyinfra_incus; from importlib.metadata import version; print(version("pyinfra-incus"))'
|
|
46
|
+
|
|
47
|
+
- name: Rehearse registry upload
|
|
48
|
+
run: uv publish --dry-run dist/*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Required release app settings:
|
|
2
|
+
# - vars.MEIGMA_RELEASE_APP_ID
|
|
3
|
+
# - secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY
|
|
4
|
+
|
|
5
|
+
name: Release Please
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches:
|
|
10
|
+
- master
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions: {}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
release-please:
|
|
17
|
+
name: Release Please
|
|
18
|
+
runs-on: ubuntu-24.04
|
|
19
|
+
permissions:
|
|
20
|
+
contents: write
|
|
21
|
+
pull-requests: write
|
|
22
|
+
issues: write
|
|
23
|
+
steps:
|
|
24
|
+
- name: Create release app token
|
|
25
|
+
id: release-app
|
|
26
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
27
|
+
with:
|
|
28
|
+
app-id: ${{ vars.MEIGMA_RELEASE_APP_ID }}
|
|
29
|
+
private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }}
|
|
30
|
+
|
|
31
|
+
- name: Run Release Please
|
|
32
|
+
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
|
33
|
+
with:
|
|
34
|
+
token: ${{ steps.release-app.outputs.token }}
|
|
35
|
+
config-file: release-please-config.json
|
|
36
|
+
manifest-file: .release-please-manifest.json
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Release Please creates the tag and a draft GitHub release. This workflow
|
|
2
|
+
# independently rebuilds the tagged source and publishes it to PyPI through
|
|
3
|
+
# Trusted Publishing. The GitHub release remains a draft for human inspection.
|
|
4
|
+
|
|
5
|
+
name: Release
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- v*
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
inputs:
|
|
13
|
+
tag:
|
|
14
|
+
description: Tag to publish
|
|
15
|
+
required: true
|
|
16
|
+
type: string
|
|
17
|
+
|
|
18
|
+
permissions: {}
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
resolve-release:
|
|
22
|
+
name: Resolve Release
|
|
23
|
+
runs-on: ubuntu-24.04
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
outputs:
|
|
27
|
+
tag: ${{ steps.release-tag.outputs.tag }}
|
|
28
|
+
version: ${{ steps.release-tag.outputs.version }}
|
|
29
|
+
steps:
|
|
30
|
+
- name: Resolve release tag
|
|
31
|
+
id: release-tag
|
|
32
|
+
env:
|
|
33
|
+
DISPATCH_TAG: ${{ inputs.tag }}
|
|
34
|
+
run: |
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
|
|
37
|
+
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
|
|
38
|
+
tag="$DISPATCH_TAG"
|
|
39
|
+
else
|
|
40
|
+
tag="$GITHUB_REF_NAME"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then
|
|
44
|
+
echo "Release tag must match vX.Y.Z or vX.Y.Z-prerelease: $tag" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
49
|
+
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
|
|
50
|
+
|
|
51
|
+
- name: Wait for draft release
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ github.token }}
|
|
54
|
+
RELEASE_TAG: ${{ steps.release-tag.outputs.tag }}
|
|
55
|
+
run: |
|
|
56
|
+
for attempt in $(seq 1 30); do
|
|
57
|
+
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
echo "Draft release $RELEASE_TAG is not ready yet; retrying..."
|
|
61
|
+
sleep 10
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
echo "Draft release $RELEASE_TAG was not found or is not a draft." >&2
|
|
65
|
+
exit 1
|
|
66
|
+
|
|
67
|
+
publish-pypi:
|
|
68
|
+
name: Publish to PyPI
|
|
69
|
+
needs:
|
|
70
|
+
- resolve-release
|
|
71
|
+
runs-on: ubuntu-24.04
|
|
72
|
+
environment: pypi
|
|
73
|
+
permissions:
|
|
74
|
+
contents: read
|
|
75
|
+
id-token: write
|
|
76
|
+
steps:
|
|
77
|
+
- name: Check out repository
|
|
78
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
79
|
+
with:
|
|
80
|
+
fetch-depth: 0
|
|
81
|
+
persist-credentials: false
|
|
82
|
+
|
|
83
|
+
- name: Check out requested tag
|
|
84
|
+
if: ${{ github.event_name == 'workflow_dispatch' }}
|
|
85
|
+
env:
|
|
86
|
+
RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }}
|
|
87
|
+
run: git checkout --detach "$RELEASE_TAG"
|
|
88
|
+
|
|
89
|
+
- name: Setup mise
|
|
90
|
+
uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
|
|
91
|
+
with:
|
|
92
|
+
version: 2026.6.14
|
|
93
|
+
cache: false
|
|
94
|
+
|
|
95
|
+
- name: Run release quality gates
|
|
96
|
+
run: moon run root:check --summary minimal
|
|
97
|
+
|
|
98
|
+
- name: Build publishable distributions
|
|
99
|
+
run: uv build --no-sources --clear
|
|
100
|
+
|
|
101
|
+
- name: Validate distribution metadata
|
|
102
|
+
env:
|
|
103
|
+
RELEASE_VERSION: ${{ needs.resolve-release.outputs.version }}
|
|
104
|
+
run: python3 .github/scripts/validate-release.py "$RELEASE_VERSION"
|
|
105
|
+
|
|
106
|
+
- name: Smoke test installed wheel
|
|
107
|
+
run: |
|
|
108
|
+
wheel=$(find dist -maxdepth 1 -name '*.whl' -print -quit)
|
|
109
|
+
uv run --no-project --with "$wheel" -- \
|
|
110
|
+
python -c 'import pyinfra_incus; from importlib.metadata import version; print(version("pyinfra-incus"))'
|
|
111
|
+
|
|
112
|
+
- name: Publish distributions
|
|
113
|
+
run: uv publish --trusted-publishing always dist/*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11.14
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{".":"0.1.0"}
|