Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions connect/cli/plugins/project/extension/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


PYPI_EXTENSION_RUNNER_URL = 'https://pypi.org/pypi/connect-extension-runner/json'
PYPI_EXTENSION_RUNNER_RELEASE_URL = 'https://pypi.org/pypi/connect-extension-runner/{version}/json'

PRE_COMMIT_HOOK = """#! /bin/sh

Expand Down
5 changes: 4 additions & 1 deletion connect/cli/plugins/project/extension/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from connect.cli.core.terminal import console
from connect.cli.plugins.project.extension.utils import (
get_event_definitions,
get_pypi_runner_eaas_core_version,
get_pypi_runner_version,
get_pypi_runner_version_by_connect_version,
initialize_git_repository,
Expand Down Expand Up @@ -79,11 +80,13 @@ def bootstrap_extension_project( # noqa: CCR001
if not answers:
raise ClickException('Aborted by user input')

runner_version = get_pypi_runner_version()
ctx = {
'statuses_by_event': statuses_by_event[answers['extension_type']],
'interactive': [],
'current_major_version': get_version().split('.')[0],
'runner_version': get_pypi_runner_version(),
'runner_version': runner_version,
'connect_eaas_core_version': get_pypi_runner_eaas_core_version(runner_version),
}

for var, answer in answers.items():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,26 @@ readme = "./README.md"
{%- endif %}

[tool.poetry.dependencies]
python = ">=3.8,<4"
connect-eaas-core = ">=30"
python = ">=3.9,<3.13"
connect-eaas-core = "{{ connect_eaas_core_version }}"

[tool.poetry.dev-dependencies]
pytest = ">=6.1.2,<8"
pytest-cov = ">=2.10.1,<5"
pytest = ">=8,<9"
pytest-cov = ">=2.10.1,<7"
pytest-mock = "^3.3.1"
mock = { version = "^4.0.3", markers = "python_version < '3.8'" }
coverage = {extras = ["toml"], version = ">=5.3,<7"}
flake8 = ">=3.8,<6"
flake8-bugbear = ">=20,<23"
coverage = {extras = ["toml"], version = ">=5.3,<8"}
flake8 = ">=6"
flake8-bugbear = ">=24"
flake8-cognitive-complexity = "^0.1"
flake8-commas = "~2.0"
flake8-commas = "~4"
flake8-future-import = "~0.4"
flake8-import-order = "~0.18"
flake8-broken-line = ">=0.3,<0.7"
flake8-comprehensions = "^3.3.1"
flake8-broken-line = ">=1.0"
flake8-comprehensions = "^3.10"
flake8-debugger = "^4.0.0"
flake8-eradicate = "^1.0.0"
flake8-eradicate = "^1.3.0"
flake8-string-format = "^0.3.0"
pytest-asyncio = "^0.15.1"
pytest-asyncio = ">=0.23"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
32 changes: 25 additions & 7 deletions connect/cli/plugins/project/extension/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import requests
from click import ClickException
from connect.client import ClientError
from packaging.requirements import Requirement

from connect.cli import get_version
from connect.cli.core.utils import (
get_connect_version,
get_last_version_by_major,
sort_and_filter_tags,
)
from connect.cli.plugins.project.extension.constants import (
PRE_COMMIT_HOOK,
PYPI_EXTENSION_RUNNER_RELEASE_URL,
PYPI_EXTENSION_RUNNER_URL,
)

Expand All @@ -31,11 +31,29 @@ def get_pypi_runner_version():
raise ClickException(
f'We can not retrieve the current connect-extension-runner version from {PYPI_EXTENSION_RUNNER_URL}.',
)
content = res.json()
tags = sort_and_filter_tags(content['releases'], get_version().split('.', 1)[0])
if tags:
return tags.popitem()[0]
return content['info']['version']
return res.json()['info']['version']


def get_pypi_runner_eaas_core_version(runner_version):
"""Return the connect-eaas-core version specifier required by the given runner.

The bootstrapped extension must pin the same connect-eaas-core the runner image
ships, otherwise `poetry update` resolves an incompatible version and the
extension fails to run.
"""
url = PYPI_EXTENSION_RUNNER_RELEASE_URL.format(version=runner_version)
res = requests.get(url)
if res.status_code != 200:
raise ClickException(
f'We can not retrieve the connect-extension-runner {runner_version} metadata from {url}.',
)
for dist in res.json()['info'].get('requires_dist') or []:
requirement = Requirement(dist)
if requirement.name == 'connect-eaas-core':
return str(requirement.specifier)
raise ClickException(
f'connect-extension-runner {runner_version} does not declare a connect-eaas-core dependency.',
)


def get_pypi_runner_version_by_connect_version():
Expand Down
37 changes: 36 additions & 1 deletion tests/plugins/project/test_extension_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def test_bootstrap_extension_project_background(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_vendor.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -127,6 +131,8 @@ def test_bootstrap_extension_project_background(

pyproject_toml = toml.load(os.path.join(tmpdir, data['project_slug'], 'pyproject.toml'))

assert pyproject_toml['tool']['poetry']['dependencies']['connect-eaas-core'] == '>=37.4,<38'

ext_entrypoint = pyproject_toml['tool']['poetry']['plugins']['connect.eaas.ext']
assert ext_entrypoint == {
'eventsapp': f"{data['package_name']}.events:{classname_prefix}EventsApplication",
Expand Down Expand Up @@ -224,6 +230,10 @@ def test_bootstrap_extension_project_interactive(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_vendor.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -296,6 +306,8 @@ def test_bootstrap_extension_project_interactive(

pyproject_toml = toml.load(os.path.join(tmpdir, data['project_slug'], 'pyproject.toml'))

assert pyproject_toml['tool']['poetry']['dependencies']['connect-eaas-core'] == '>=37.4,<38'

ext_entrypoint = pyproject_toml['tool']['poetry']['plugins']['connect.eaas.ext']
assert ext_entrypoint == {
'eventsapp': f"{data['package_name']}.events:{classname_prefix}EventsApplication",
Expand Down Expand Up @@ -377,6 +389,10 @@ def test_bootstrap_extension_project_multiaccount(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_vendor.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -527,6 +543,10 @@ def test_bootstrap_extension_project_webapp(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_provider.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -688,6 +708,10 @@ def test_bootstrap_extension_project_tfnapp(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_provider.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -893,7 +917,6 @@ def test_bootstrap_extension_project_if_destination_exists(mocker):


def test_bump_runner_version(mocker, mocked_responses, capsys):
mocker.patch('connect.cli.plugins.project.extension.utils.get_version', return_value='0.5')
mocked_responses.add(
'GET',
DEFAULT_ENDPOINT,
Expand Down Expand Up @@ -1045,6 +1068,10 @@ def test_validate_extension_project(mocker, faker, mocked_responses, config_vend
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_vendor.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -1114,6 +1141,10 @@ def test_validate_extension_project_error_exit(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
side_effect=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_vendor.load(config_dir='/tmp')

mocked_responses.add(
Expand Down Expand Up @@ -1184,6 +1215,10 @@ def test_bootstrap_extension_project_corrupted_load_answers(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value=runner_version,
)
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_eaas_core_version',
return_value='>=37.4,<38',
)
config_provider.load(config_dir='/tmp')

mocked_responses.add(
Expand Down
88 changes: 61 additions & 27 deletions tests/plugins/project/test_extension_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from click import ClickException
from connect.client import ClientError

from connect.cli.plugins.project.extension.constants import PYPI_EXTENSION_RUNNER_URL
from connect.cli.plugins.project.extension.constants import (
PYPI_EXTENSION_RUNNER_RELEASE_URL,
PYPI_EXTENSION_RUNNER_URL,
)
from connect.cli.plugins.project.extension.utils import (
check_eventsapp_feature_not_selected,
check_extension_events_applicable,
Expand All @@ -14,37 +17,32 @@
get_default_application_types,
get_event_definitions,
get_interactive_events,
get_pypi_runner_eaas_core_version,
get_pypi_runner_version,
)


def test_get_pypi_runner_version(mocker, mocked_responses):
mocker.patch(
'connect.cli.plugins.project.extension.utils.get_version',
return_value='25.0',
)
def test_get_pypi_runner_version(mocked_responses):
"""The bootstrap must pin the latest published runner, not one tied to the CLI major.

The CLI major (26) diverged from the runner line (43), so filtering runner tags by
CLI major returned a stale, incompatible image. Confidence: a fresh extension always
gets the current runner.
"""
mocked_responses.add(
method='GET',
url=PYPI_EXTENSION_RUNNER_URL,
json={
'releases': {
'26.0': {},
'25.1': {},
'25.0': {},
'24.5': {},
},
'info': {'version': '43.0'},
'releases': {'43.0': {}, '42.0': {}, '26.44': {}},
},
status=200,
)

assert get_pypi_runner_version() == '25.1'
assert get_pypi_runner_version() == '43.0'


def test_get_pypi_runner_version_http_error(mocker, mocked_responses):
mocker.patch(
'connect.cli.plugins.project.extension.utils.get_version',
return_value='25.0',
)
def test_get_pypi_runner_version_http_error(mocked_responses):
mocked_responses.add(
method='GET',
url=PYPI_EXTENSION_RUNNER_URL,
Expand All @@ -56,22 +54,58 @@ def test_get_pypi_runner_version_http_error(mocker, mocked_responses):
assert 'We can not retrieve the current connect-extension-runner version' in str(ve.value)


def test_get_pypi_runner_version_no_releases(mocker, mocked_responses):
mocker.patch(
'connect.cli.plugins.project.extension.utils.get_version',
return_value='25.0',
)
def test_get_pypi_runner_eaas_core_version(mocked_responses):
"""The extension must pin the connect-eaas-core the runner image ships.

If the generated pyproject allows a newer connect-eaas-core than the runner supports,
`poetry update` resolves an incompatible version and the extension fails to run.
Confidence: the runner↔eaas-core pair is always coherent, so the extension boots.
"""
mocked_responses.add(
method='GET',
url=PYPI_EXTENSION_RUNNER_URL,
url=PYPI_EXTENSION_RUNNER_RELEASE_URL.format(version='43.0'),
status=200,
json={
'info': {'version': '26.0'},
'releases': {},
'info': {
'requires_dist': [
'logzio-python-handler>=3.1',
'connect-eaas-core<38,>=37.4',
],
},
},
)

assert get_pypi_runner_version() == '26.0'
assert get_pypi_runner_eaas_core_version('43.0') == '<38,>=37.4'


def test_get_pypi_runner_eaas_core_version_http_error(mocked_responses):
mocked_responses.add(
method='GET',
url=PYPI_EXTENSION_RUNNER_RELEASE_URL.format(version='43.0'),
status=500,
)
with pytest.raises(ClickException) as ve:
get_pypi_runner_eaas_core_version('43.0')

assert 'We can not retrieve the connect-extension-runner 43.0 metadata' in str(ve.value)


def test_get_pypi_runner_eaas_core_version_missing_dependency(mocked_responses):
"""A runner that omits connect-eaas-core must fail loudly, not silently mis-pin.

Confidence: we never generate a pyproject with an empty/invalid eaas-core constraint.
"""
mocked_responses.add(
method='GET',
url=PYPI_EXTENSION_RUNNER_RELEASE_URL.format(version='43.0'),
status=200,
json={'info': {'requires_dist': ['logzio-python-handler>=3.1']}},
)

with pytest.raises(ClickException) as ve:
get_pypi_runner_eaas_core_version('43.0')

assert 'does not declare a connect-eaas-core dependency' in str(ve.value)


def test_get_event_definitions_client_error(mocker):
Expand Down
Loading