feat: support charmcraft.yaml format as meta for testing.Context#2296
Conversation
6b900f1 to
488c5fa
Compare
Co-authored-by: Dima Tisnek <dimaqq@gmail.com>
This PR drops Scenario's build dependency on [setuptools_scm](https://pypi.org/project/setuptools-scm/), which we don't need, as it's for versioning a package based on git tags. This resolves the current build issue, example identified by David [here](https://github.com/canonical/operator/actions/runs/21809969678/job/62920050703#step:5:22). The build issue can be reproduced locally on `main` with `uv build --all`. I've verified that the build succeeds with `setuptools_scm` removed. The root cause of why builds started failing isn't clear though.
The Juju `credential-get` hook command is available for K8s models as of Juju 3.6.10, so adjust Scenario's consistency checker to match. Fixes canonical#2304
This is an automated PR to update the best practices documentation. Co-authored-by: David Wilding <david.wilding@canonical.com>
This PR prepares the release of version 3.5.2. --------- Co-authored-by: Tony Meyer <tony.meyer@gmail.com>
This PR updates the version files after the release.
Bumps `cryptography` from 46.0.2 to 46.0.5 to address [this security issue](https://github.com/canonical/operator/security/dependabot/23). The package is used by `pyjwt`, which is used by the release script, so we aren't really exposed by this issue, but it's simple to pull in the latest fixes and clear the security warning.
The 2.23 maintenance branch does not have the versions.md doc. Handle that when releasing.
This PR splits [How to run workloads with a Kubernetes charm](https://documentation.ubuntu.com/ops/latest/howto/run-workloads-with-a-charm-kubernetes/) into a subcategory of the how-to guides. I've redistributed all the content without making significant changes. We can do further improvements in follow-on PRs. Main parts to focus on reviewing: - The introductory descriptions on [Manage containers](https://canonical-ubuntu-documentation-library--2309.com.readthedocs.build/ops/2309/howto/manage-containers/) and the scope of each page in that subcategory. I moved "Manage Pebble metrics" into this subcategory because I feel it fits better here, rather than as a top-level how-to guide (this move isn't in the plan I prepared internally). - The same descriptions at [How-to guides > Managing containers](https://canonical-ubuntu-documentation-library--2309.com.readthedocs.build/ops/2309/howto/#managing-containers). I'm copying the approach of "Legacy guides" - listing each of the pages in the subcategory instead of only listing the subcategory itself. I'm open to changing the approach if we don't like the duplication. I also fixed `test_add_layer()` in [How-to guides > Manage the workload container > Write unit tests](https://canonical-ubuntu-documentation-library--2309.com.readthedocs.build/ops/2309/howto/manage-containers/manage-the-workload-container/#write-unit-tests). The test previously defined `container` but used `container_in`. I've switched the test to use `container_in` and `container_out` for clarity.
…nical#2315) Fixes canonical#2312 When `ops.hookcmds` has landed, we forgot to add the submodule to the setuptools config in pyproject.toml and now we get warnings. The PR addresses that.
As decided at the standup, bumping the default Juju version in Scenario Context to the version of the current Juju LTS, 3.6.14.
Merge branch 'main' into 26-01+feat+support-charmcraft-yaml-as-meta-for-testing-context
dimaqq
left a comment
There was a problem hiding this comment.
Let's discuss 3x deepcopy.
tonyandrewmeyer
left a comment
There was a problem hiding this comment.
I feel like it may be better to raise an error if there's an actions/config dict provided both in the meta argument and separately, rather than let one of them 'win'.
I wonder if we should add an example somewhere in the documentation (maybe in one of the library docs that isn't going to be moved to charmlibs?). In most cases, charms are ideally never using this and always auto-spec'ing. However, if they do use it, I would rather we encourage:
ctx = Context(MyCharm, charmcraft_dict)rather than
ctx = Context(MyCharm, meta=charmcraft_dict)
And reserve the meta= keyword passing for when it's specifically the metadata dict.
I haven't tried running this since it's unclear what the exact implementation of the core piece will be, but I'm happy to approve trusting that it'll work at that point.
testing/tests/test_context.py
Outdated
|
|
||
| def test_init_with_bad_meta(): | ||
| ctx = Context(MyCharm, meta={'a truth universally acknowledged': 'it'}) | ||
| with pytest.raises((UncaughtCharmError, KeyError)): |
There was a problem hiding this comment.
With #2314 coming, it's probably easier to preemptively use that approach here.
There was a problem hiding this comment.
TBH I think this is lower weight than adding monkeypatch and using monkeypatch.setenv.
Let's debate and make a decision on this. |
Merge branch 'main' into 26-01+feat+support-charmcraft-yaml-as-meta-for-testing-context
| @pytest.fixture | ||
| def secrets_context(secrets_charm_meta: dict[str, Any]): | ||
| return Context(SecretsCharm, meta=secrets_charm_meta, actions=secrets_charm_meta['actions']) | ||
| return Context(SecretsCharm, meta=secrets_charm_meta) |
There was a problem hiding this comment.
Making it an error to pass actions both as a separate argument and as an entry in meta actually broke this fixture. It loads secrets_charm_meta from a charmcraft.yaml file, and passes ['actions'] explicitly (since it was written before this PR :)).
I think this is a sign this change might be too breaking.
There was a problem hiding this comment.
Could we try it across our inventory of charms?
There was a problem hiding this comment.
Good idea, I'm running it now, but I'm not confident in my ability to actually detect new failures from the output. Would you mind running it too?
There was a problem hiding this comment.
I get the same number of successes with the branch, so it seems safe to me.
However, I'm open to making it a warning (maybe a deprecation warning?) instead if you think it's better to be safe.
There was a problem hiding this comment.
Since this doesn't break any charm tests we can detect, let's keep it as an error.
|
Requesting re-review as things have changed significantly:
|
tonyandrewmeyer
left a comment
There was a problem hiding this comment.
I'm fine with this, although I agree that there's some risk and I think we should investigate more what the impact might be. Perhaps we should only be warning when there's a collision.
| @pytest.fixture | ||
| def secrets_context(secrets_charm_meta: dict[str, Any]): | ||
| return Context(SecretsCharm, meta=secrets_charm_meta, actions=secrets_charm_meta['actions']) | ||
| return Context(SecretsCharm, meta=secrets_charm_meta) |
There was a problem hiding this comment.
Could we try it across our inventory of charms?
testing/src/scenario/context.py
Outdated
| meta = copy.deepcopy(meta) if meta is not None else {'name': str(charm_type.__name__)} | ||
| if actions is not None and 'actions' in meta: | ||
| raise ValueError('Cannot specify actions in both charmcraft.yaml and separately') | ||
| actions = copy.deepcopy(actions) if actions is not None else meta.pop('actions', None) | ||
| if config is not None and 'config' in meta: | ||
| raise ValueError('Cannot specify config in both charmcraft.yaml and separately') | ||
| config = copy.deepcopy(config) if config is not None else meta.pop('config', None) | ||
| spec = _CharmSpec(charm_type=charm_type, meta=meta, actions=actions, config=config) |
There was a problem hiding this comment.
Freel free to push back on this; it's pretty subjective. I don't find this very readable.
Some of it maybe ruff pushes us into, like always using the ternary operator? If so then I would say that the consistency gained through tooling-enforced rules wins out.
Maybe a comment would help? Maybe if it was in a loop? (but it's only two items so ...)
I find my argument weakening by not being able to immediately see how I would write a nicer version of this.
There was a problem hiding this comment.
Maybe comments to divide into logical sections and explain what's up? Maybe flipping the ternary to use is rather than is not?
# Metadata: may be in charmcraft.yaml format
meta = {'name': str(charm_type.__name__)} if meta is None else copy.deepcopy(meta)
# Actions: it is an error if actions is specified both separately and in charmcraft.yaml
if actions is not None and 'actions' in meta:
raise ValueError('Cannot specify actions in both charmcraft.yaml and separately')
actions = meta.pop('actions', None) if actions is None else copy.deepcopy(actions)
# Config: it is an error if config is specified both separately and in charmcraft.yaml
if config is not None and 'config' in meta:
raise ValueError('Cannot specify config in both charmcraft.yaml and separately')
config = meta.pop('config', None) if config is None else copy.deepcopy(config)
# Charm spec from user specified charm type and metadata
spec = _CharmSpec(charm_type=charm_type, meta=meta, actions=actions, config=config)There was a problem hiding this comment.
Maybe:
meta = copy.deepcopy(meta) if meta else {'name': str(charm_type.__name__)}
# actions
if actions is None:
actions = meta.pop('actions', None)
elif 'actions' in meta:
raise ValueError('Cannot specify actions in both charmcraft.yaml and separately')
else:
actions = copy.deepcopy(actions)
# config
if config is None:
config = meta.pop('config', None)
elif 'config' in meta:
raise ValueError('Cannot specify config in both charmcraft.yaml and separately')
else:
config = copy.deepcopy(config)
spec = _CharmSpec(charm_type=charm_type, meta=meta, actions=actions, config=config)Merge branch 'main' into 26-01+feat+support-charmcraft-yaml-as-meta-for-testing-context
|
Sorry I lost track about what we decided for copying stuff exactly (same pr? separate? specific copies? whack-all deepcopy?). |
We decided on doing the deep copies in: This PR doesn't introduce any new copying, just pulls |
testing/src/scenario/context.py
Outdated
| actions=copy.deepcopy(actions), | ||
| config=copy.deepcopy(config), | ||
| ) | ||
| meta = copy.deepcopy(meta) if meta is not None else {'name': str(charm_type.__name__)} |
There was a problem hiding this comment.
This is a subtle change for the case of explicit meta={} and supplying a non-empty actions or config.
I'm not sure why someone would do that, so I think it's OK.
I wonder if the code on line 691 should be changed to:
if not any(x is not None for x in (meta, actions, config)):
Though against, I've no clue why someone may override action or config with an empty dict (no actions or no config), and yet supply a not-None, yet-empty meta.
So maybe current code is fine?
Please double-check my logic.
There was a problem hiding this comment.
You're right about the subtle behaviour change, and I'm not 100% sure why I made the decision to test meta for None instead of falsiness other than perhaps for symmetry with config and actions. I'll revert to the original behaviour for meta.
Merge branch 'main' into 26-01+feat+support-charmcraft-yaml-as-meta-for-testing-context
This PR adds support for providing
charmcraft.yamlformatted data to themetaargument oftesting.Context.It is an error to specify
actionsorconfigboth as separate arguments and as keys inmeta.Resolves #1424