-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(strict-deps): fail if venv is inconsistent (#1784)
This runs `pip check` to ensure that all the packages in the charm virtualenv have all of their required dependencies, failing the build if they do not. Fixes #1781
- Loading branch information
Showing
10 changed files
with
272 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Copyright 2024 Canonical Ltd. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# For further info, check https://github.com/canonical/charmcraft | ||
"""Integration tests for CharmBuilder.""" | ||
|
||
|
||
import pathlib | ||
|
||
import pytest | ||
|
||
from charmcraft import charm_builder | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"requirements", | ||
[ | ||
["ops==2.15.0"], # Requires pyyaml and websocket-client | ||
], | ||
) | ||
def test_install_strict_dependencies_pip_check_error( | ||
new_path: pathlib.Path, requirements: list[str] | ||
): | ||
build_dir = new_path / "build" | ||
install_dir = new_path / "install" | ||
entrypoint = build_dir / "entrypoint.py" | ||
|
||
build_dir.mkdir() | ||
install_dir.mkdir() | ||
|
||
requirements_file = build_dir / "requirements.txt" | ||
requirements_file.write_text("\n".join(requirements)) | ||
|
||
builder = charm_builder.CharmBuilder( | ||
builddir=build_dir, | ||
installdir=install_dir, | ||
entrypoint=entrypoint, | ||
requirements=[requirements_file], | ||
strict_dependencies=True, | ||
) | ||
|
||
with pytest.raises(RuntimeError, match="failed with retcode 1"): | ||
builder.handle_dependencies() | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"requirements", | ||
[ | ||
["craft-platforms==0.1.0"], # No dependencies | ||
], | ||
) | ||
def test_install_strict_dependencies_pip_check_success( | ||
new_path: pathlib.Path, requirements: list[str] | ||
): | ||
build_dir = new_path / "build" | ||
install_dir = new_path / "install" | ||
entrypoint = build_dir / "entrypoint.py" | ||
|
||
build_dir.mkdir() | ||
install_dir.mkdir() | ||
|
||
requirements_file = build_dir / "requirements.txt" | ||
requirements_file.write_text("\n".join(requirements)) | ||
|
||
builder = charm_builder.CharmBuilder( | ||
builddir=build_dir, | ||
installdir=install_dir, | ||
entrypoint=entrypoint, | ||
requirements=[requirements_file], | ||
strict_dependencies=True, | ||
) | ||
|
||
with pytest.raises(RuntimeError, match="failed with retcode 1"): | ||
builder.handle_dependencies() |
14 changes: 14 additions & 0 deletions
14
tests/spread/dependencies/strict-dependencies-errors/missing-packages/charmcraft.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
name: charm | ||
type: charm | ||
title: Strict dependencies test charm | ||
summary: a test charm for checking strict dependency build failures. | ||
description: | | ||
This charm fails to build because it has strict dependencies enabled but does not | ||
include the full dependency tree in its requirements file. | ||
bases: | ||
- name: ubuntu | ||
channel: "22.04" | ||
|
||
parts: | ||
charm: | ||
charm-strict-dependencies: true |
46 changes: 46 additions & 0 deletions
46
tests/spread/dependencies/strict-dependencies-errors/missing-packages/pyproject.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Testing tools configuration | ||
[tool.coverage.run] | ||
branch = true | ||
|
||
[tool.coverage.report] | ||
show_missing = true | ||
|
||
[tool.pytest.ini_options] | ||
minversion = "6.0" | ||
log_cli_level = "INFO" | ||
|
||
# Formatting tools configuration | ||
[tool.black] | ||
line-length = 99 | ||
target-version = ["py38"] | ||
|
||
# Linting tools configuration | ||
[tool.ruff] | ||
line-length = 99 | ||
lint.select = ["E", "W", "F", "C", "N", "D", "I001"] | ||
lint.extend-ignore = [ | ||
"D203", | ||
"D204", | ||
"D213", | ||
"D215", | ||
"D400", | ||
"D404", | ||
"D406", | ||
"D407", | ||
"D408", | ||
"D409", | ||
"D413", | ||
] | ||
lint.ignore = ["E501", "D107"] | ||
extend-exclude = ["__pycache__", "*.egg_info"] | ||
lint.per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} | ||
|
||
[tool.ruff.lint.mccabe] | ||
max-complexity = 10 | ||
|
||
[tool.codespell] | ||
skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" | ||
|
||
[tool.pyright] | ||
include = ["src/**.py"] | ||
|
1 change: 1 addition & 0 deletions
1
tests/spread/dependencies/strict-dependencies-errors/missing-packages/requirements.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pytest-time==0.3.1 # missing pytest |
103 changes: 103 additions & 0 deletions
103
tests/spread/dependencies/strict-dependencies-errors/missing-packages/src/charm.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright 2023 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
# | ||
# Learn more at: https://juju.is/docs/sdk | ||
|
||
"""Charm the service. | ||
Refer to the following tutorial that will help you | ||
develop a new k8s charm using the Operator Framework: | ||
https://juju.is/docs/sdk/create-a-minimal-kubernetes-charm | ||
""" | ||
|
||
import logging | ||
|
||
import ops | ||
|
||
# Log messages can be retrieved using juju debug-log | ||
logger = logging.getLogger(__name__) | ||
|
||
VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] | ||
|
||
|
||
class CharmCharm(ops.CharmBase): | ||
"""Charm the service.""" | ||
|
||
def __init__(self, *args): | ||
super().__init__(*args) | ||
self.framework.observe(self.on["httpbin"].pebble_ready, self._on_httpbin_pebble_ready) | ||
self.framework.observe(self.on.config_changed, self._on_config_changed) | ||
|
||
def _on_httpbin_pebble_ready(self, event: ops.PebbleReadyEvent): | ||
"""Define and start a workload using the Pebble API. | ||
Change this example to suit your needs. You'll need to specify the right entrypoint and | ||
environment configuration for your specific workload. | ||
Learn more about interacting with Pebble at at https://juju.is/docs/sdk/pebble. | ||
""" | ||
# Get a reference the container attribute on the PebbleReadyEvent | ||
container = event.workload | ||
# Add initial Pebble config layer using the Pebble API | ||
container.add_layer("httpbin", self._pebble_layer, combine=True) | ||
# Make Pebble reevaluate its plan, ensuring any services are started if enabled. | ||
container.replan() | ||
# Learn more about statuses in the SDK docs: | ||
# https://juju.is/docs/sdk/constructs#heading--statuses | ||
self.unit.status = ops.ActiveStatus() | ||
|
||
def _on_config_changed(self, event: ops.ConfigChangedEvent): | ||
"""Handle changed configuration. | ||
Change this example to suit your needs. If you don't need to handle config, you can remove | ||
this method. | ||
Learn more about config at https://juju.is/docs/sdk/config | ||
""" | ||
# Fetch the new config value | ||
log_level = self.model.config["log-level"].lower() | ||
|
||
# Do some validation of the configuration option | ||
if log_level in VALID_LOG_LEVELS: | ||
# The config is good, so update the configuration of the workload | ||
container = self.unit.get_container("httpbin") | ||
# Verify that we can connect to the Pebble API in the workload container | ||
if container.can_connect(): | ||
# Push an updated layer with the new config | ||
container.add_layer("httpbin", self._pebble_layer, combine=True) | ||
container.replan() | ||
|
||
logger.debug("Log level for gunicorn changed to '%s'", log_level) | ||
self.unit.status = ops.ActiveStatus() | ||
else: | ||
# We were unable to connect to the Pebble API, so we defer this event | ||
event.defer() | ||
self.unit.status = ops.WaitingStatus("waiting for Pebble API") | ||
else: | ||
# In this case, the config option is bad, so block the charm and notify the operator. | ||
self.unit.status = ops.BlockedStatus("invalid log level: '{log_level}'") | ||
|
||
@property | ||
def _pebble_layer(self) -> ops.pebble.LayerDict: | ||
"""Return a dictionary representing a Pebble layer.""" | ||
return { | ||
"summary": "httpbin layer", | ||
"description": "pebble config layer for httpbin", | ||
"services": { | ||
"httpbin": { | ||
"override": "replace", | ||
"summary": "httpbin", | ||
"command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", | ||
"startup": "enabled", | ||
"environment": { | ||
"GUNICORN_CMD_ARGS": f"--log-level {self.model.config['log-level']}" | ||
}, | ||
} | ||
}, | ||
} | ||
|
||
|
||
if __name__ == "__main__": # pragma: nocover | ||
ops.main(CharmCharm) # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
tests/spread/dependencies/strict-dependencies/charm/requirements.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
ops==2.5.1 | ||
pyyaml==6.0.1 | ||
websocket-client==1.8.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters