Skip to content
Draft
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
36 changes: 34 additions & 2 deletions connect/cli/plugins/project/extension/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
get_event_definitions,
get_pypi_runner_requirements,
get_pypi_runner_version,
get_pypi_runner_version_by_connect_version,
initialize_git_repository,
)
from connect.cli.plugins.project.extension.wizard import (
Expand Down Expand Up @@ -246,11 +245,37 @@ def _update_docker_file_runner_from(dockerfile: str, latest_version: str):
return to_be_replaced


def _update_pyproject_runner_deps(pyproject_file, latest_version):
connect_eaas_core_version, python_version = get_pypi_runner_requirements(latest_version)
new_values = {'python': python_version, 'connect-eaas-core': connect_eaas_core_version}
to_be_replaced = False
section = None
lines = []
with open(pyproject_file, 'r') as f:
for line in f.readlines():
stripped = line.strip()
if stripped.startswith('['):
section = stripped
elif section == '[tool.poetry.dependencies]':
# ponytail: handles the plain `name = "spec"` form bootstrap emits; a
# table form ({ version = ... }) gets rewritten to the plain form
key = stripped.split('=', 1)[0].strip()
new_value = new_values.get(key)
if new_value and stripped != f'{key} = "{new_value}"':
line = f'{key} = "{new_value}"\n'
to_be_replaced = True
lines.append(line)
if to_be_replaced:
with open(pyproject_file, 'w') as f:
f.writelines(lines)
return to_be_replaced


def bump_runner_extension_project(project_dir: str): # noqa: CCR001
console.secho(f'Bumping runner version on project {project_dir}...\n', fg='blue')

updated_files = set()
latest_version = get_pypi_runner_version_by_connect_version()
latest_version = get_pypi_runner_version()
latest_runner_version = f'cloudblueconnect/connect-extension-runner:{latest_version}'
docker_compose_file = os.path.join(project_dir, 'docker-compose.yml')
if not os.path.isfile(docker_compose_file):
Expand Down Expand Up @@ -294,6 +319,13 @@ def bump_runner_extension_project(project_dir: str): # noqa: CCR001
f'Error: {error}',
)

pyproject_file = os.path.join(project_dir, 'pyproject.toml')
if os.path.isfile(pyproject_file) and _update_pyproject_runner_deps(
pyproject_file,
latest_version,
):
updated_files.add(pyproject_file)

if updated_files:
console.secho(
f'Runner version has been successfully updated to {latest_version}. The following '
Expand Down
16 changes: 0 additions & 16 deletions connect/cli/plugins/project/extension/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
from connect.client import ClientError
from packaging.requirements import Requirement

from connect.cli.core.utils import (
get_connect_version,
get_last_version_by_major,
)
from connect.cli.plugins.project.extension.constants import (
PRE_COMMIT_HOOK,
PYPI_EXTENSION_RUNNER_RELEASE_URL,
Expand Down Expand Up @@ -62,18 +58,6 @@ def get_pypi_runner_requirements(runner_version):
)


def get_pypi_runner_version_by_connect_version():
connect_version = get_connect_version()
res = requests.get(PYPI_EXTENSION_RUNNER_URL)
if res.status_code != 200 or not connect_version:
raise ClickException(
f'We can not retrieve the current connect-extension-runner version from {PYPI_EXTENSION_RUNNER_URL}.',
)
content = res.json()
version = get_last_version_by_major(content['releases'], connect_version.split('.', 1)[0])
return version or content['info']['version']


def get_extension_types(config):
if config.active.is_provider():
extension_types = [('hub', 'Hub integration')]
Expand Down
53 changes: 42 additions & 11 deletions tests/plugins/project/test_extension_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
from click import ClickException
from flake8.api import legacy as flake8

from connect.cli.core.constants import DEFAULT_ENDPOINT
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.helpers import (
bootstrap_extension_project,
bump_runner_extension_project,
Expand Down Expand Up @@ -918,17 +920,28 @@ def test_bootstrap_extension_project_if_destination_exists(mocker):
assert 'Answers cannot be saved' in str(cv.value)


def test_bump_runner_version(mocker, mocked_responses, capsys):
def test_bump_runner_version(mocked_responses, capsys):
"""`bump` must move the project to the latest published runner and keep the
pyproject pins (connect-eaas-core, python) in lockstep with it.

Bumping only the docker image would recreate the bootstrap mismatch: a new
runner with stale pyproject pins fails to resolve/run after `poetry update`.
Confidence: one bump leaves the whole project coherent with the runner.
"""
mocked_responses.add(
'GET',
DEFAULT_ENDPOINT,
status=401,
headers={'Connect-Version': '1.0.1-abc'},
PYPI_EXTENSION_RUNNER_URL,
json={'info': {'version': '1.0'}},
)
mocked_responses.add(
'GET',
PYPI_EXTENSION_RUNNER_URL,
json={'releases': {'1.0': ['release info']}},
PYPI_EXTENSION_RUNNER_RELEASE_URL.format(version='1.0'),
json={
'info': {
'requires_python': '<3.13,>=3.9',
'requires_dist': ['connect-eaas-core<38,>=37.4'],
},
},
)

with tempfile.TemporaryDirectory() as tmp_data:
Expand Down Expand Up @@ -957,13 +970,32 @@ def test_bump_runner_version(mocker, mocked_responses, capsys):
fp.write('FROM cloudblueconnect/connect-extension-runner:0.5')
with open(f'{project_dir}/OtherDockerfile', 'w') as fp:
fp.write('FROM cloudblueconnect/connect-extension-runner:0.5')
with open(f'{project_dir}/pyproject.toml', 'w') as fp:
fp.write(
'[tool.poetry]\n'
'name = "myext"\n'
'\n'
'[tool.poetry.dependencies]\n'
'python = ">=3.8,<4"\n'
'connect-eaas-core = ">=30"\n'
'\n'
'[tool.poetry.dev-dependencies]\n'
'pytest = ">=6.1.2,<8"\n',
)
bump_runner_extension_project(project_dir)
captured = capsys.readouterr()
captured_out = ''.join(captured.out.split('\n'))
assert 'Runner version has been successfully updated to 1.0' in captured_out
assert f'{os.path.join(project_dir, "docker-compose.yml")}' in captured_out
assert f'{os.path.join(project_dir, "OtherDockerfile")}' in captured_out
assert f'{os.path.join(project_dir, "Dockerfile")}' in captured_out
assert f'{os.path.join(project_dir, "pyproject.toml")}' in captured_out

pyproject = open(f'{project_dir}/pyproject.toml').read()
assert 'python = "<3.13,>=3.9"' in pyproject
assert 'connect-eaas-core = "<38,>=37.4"' in pyproject
# dev-dependencies section stays untouched
assert 'pytest = ">=6.1.2,<8"' in pyproject


def test_bump_runner_version_no_update_required(mocker, capsys):
Expand Down Expand Up @@ -1047,8 +1079,7 @@ def test_bump_runner_docker_yaml_error(mocker):
assert 'not properly formatted' in str(error.value)


def test_bump_runner_get_version_error(mocker, mocked_responses):
mocker.patch('connect.cli.plugins.project.extension.utils.get_connect_version')
def test_bump_runner_get_version_error(mocked_responses):
mocked_responses.add('GET', PYPI_EXTENSION_RUNNER_URL, status=400)
with pytest.raises(ClickException) as error:
bump_runner_extension_project('project_dir')
Expand All @@ -1057,7 +1088,7 @@ def test_bump_runner_get_version_error(mocker, mocked_responses):

def _mock_pypi_version(mocker):
mocker.patch(
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version_by_connect_version',
'connect.cli.plugins.project.extension.helpers.get_pypi_runner_version',
return_value='1.0',
)

Expand Down