Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secrets #840

Closed
wants to merge 45 commits into from
Closed

Secrets #840

wants to merge 45 commits into from

Conversation

PietroPasotti
Copy link
Contributor

@PietroPasotti PietroPasotti commented Oct 7, 2022

NOTE: new PR is #861. Note that if you're building charms, you'll need to update your branch to benhoyt/operator@secrets, so update your charm's requirements.txt to the following:

git+https://github.com/benhoyt/operator@secrets#egg=ops

ops wrappers for Juju Secrets (3.0.53.0.2+)

QA steps

A couple of tester/demo charms that can be deployed to try the functionality out:
https://github.com/PietroPasotti/secrets-demo-charms

Documentation changes

Added:

Bug reference

#836

Changelog

Added support for juju secrets.

rwcarlsen and others added 16 commits September 9, 2022 09:14
* Finish secret hook tool calls,
* Create secret add/get methods,
* mods to accomadate spec evolution
* Add in secret-event creation plumbing
The idea is to implement the secret hook tools in the testing backend as
normal, but to have this pseudo-secret object that represents the juju
secret semantics - e.g. tracking which units are on which revisions,
whether a secret is expired, etc.  This allows a test writer to do
things like force-expire a secret at a desired point in time and then
see how a charm reacts to it in various hook calls.  Or to control the
contents or access granting of a secret that is being consumed, and not
explicitly controlled by the charm being tested.
@sed-i
Copy link
Contributor

sed-i commented Oct 7, 2022

This is exciting stuff @PietroPasotti!
Are you aware of any sequnce/activity diagrams for secrets? There's quite a bit of new terminology (revision, owner, label, id, update, prune, remove, rotate, expired, changed, grant, revoke) and it could be handy to be able to see in a glance where all pieces fit.

@PietroPasotti
Copy link
Contributor Author

@sed-i not yet I'm afraid, the coming weeks while I wait for some juju bugs to be fixed and the implementation on their side to settle, I'll get working on the documentation.

@benhoyt
Copy link
Collaborator

benhoyt commented Oct 11, 2022

Pre-review comment: it'd be great to fix the formatting and tests before going too much further.

I'm also getting errors when running tox -e docs, but it seems like a Sphinx error on Python 3.10 -- odd.

$ tox -e docs
docs installed: alabaster==0.7.12,Babel==2.10.3,certifi==2022.9.24,charset-normalizer==2.1.1,docutils==0.16,idna==3.4,imagesize==1.4.1,Jinja2==3.1.2,MarkupSafe==2.1.1,packaging==21.3,Pygments==2.13.0,pyparsing==3.0.9,pytz==2022.4,PyYAML==6.0,requests==2.28.1,snowballstemmer==2.2.0,Sphinx==3.5.4,sphinx-rtd-theme==1.0.0,sphinxcontrib-applehelp==1.0.2,sphinxcontrib-devhelp==1.0.2,sphinxcontrib-htmlhelp==2.0.0,sphinxcontrib-jsmath==1.0.1,sphinxcontrib-qthelp==1.0.3,sphinxcontrib-serializinghtml==1.1.5,urllib3==1.26.12
docs run-test-pre: PYTHONHASHSEED='2153479159'
docs run-test: commands[0] | sphinx-build -M html docs/ docs/_build
Traceback (most recent call last):
  File "/home/ben/w/operator/.tox/docs/bin/sphinx-build", line 5, in <module>
    from sphinx.cmd.build import main
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/cmd/build.py", line 25, in <module>
    from sphinx.application import Sphinx
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/application.py", line 32, in <module>
    from sphinx.config import Config
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/config.py", line 23, in <module>
    from sphinx.util import logging
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/util/__init__.py", line 35, in <module>
    from sphinx.util import smartypants  # noqa
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/util/smartypants.py", line 33, in <module>
    from sphinx.util.docutils import __version_info__ as docutils_version
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/util/docutils.py", line 31, in <module>
    from sphinx.util.typing import RoleFunction
  File "/home/ben/w/operator/.tox/docs/lib/python3.10/site-packages/sphinx/util/typing.py", line 34, in <module>
    from types import Union as types_Union
ImportError: cannot import name 'Union' from 'types' (/usr/lib/python3.10/types.py)
ERROR: InvocationError for command /home/ben/w/operator/.tox/docs/bin/sphinx-build -M html docs/ docs/_build (exited with code 1)
_________________________________________________________________________ summary _________________________________________________________________________
ERROR:   docs: commands failed

@PietroPasotti
Copy link
Contributor Author

Pre-review comment: it'd be great to fix the formatting and tests before going too much further.

Will do it first thing tomorrow.

I'm also getting errors when running tox -e docs, but it seems like a Sphinx error on Python 3.10 -- odd.

Will look into that, didn't notice.

ops/main.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
@PietroPasotti
Copy link
Contributor Author

@sed-i I started working on some docs: https://discourse.charmhub.io/t/secret-events/7191

@PietroPasotti
Copy link
Contributor Author

I was about to close for the day when I realized that I'd forgotten to add a bunch of optional 'label' args to all sorts of Secret.remove/revoke etc methods, to be used if an ID is not provided.
Fixed that and the testing framework as well.

Copy link
Collaborator

@benhoyt benhoyt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not reviewed the ops/testing.py or test/test_secrets.py changes, but the rest of this looks good to me now. Nice work!

ops/model.py Outdated Show resolved Hide resolved
ops/log.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated Show resolved Hide resolved
ops/model.py Outdated


class SecretRotateValueError(SecretsError):
"""Raised when one attempts to create a secret with a bad `rotate` value."""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also check that we cannot create a secret with an expiry time in the past

ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/charm.py Outdated Show resolved Hide resolved
ops/model.py Outdated
meta=True)
meta = typing.cast('_SecretGetMetadataDict', meta)

except SecretOwnershipError: # let other types raise

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be using "programme by exception"

ops/model.py Outdated
label = metadata.get('label')
revision = int(metadata.get('revision'))
return Secret(self._backend, secret_id, label=label,
revision=revision, is_owned_by_this_unit=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep thinking "is owned by this unit" isn't quite right. In the case of the secret being owned by the application, the leader unit is allowed to manage the secret, but it doesn't own it.

Would we be better talking in terms of "managed_by_this_unit"?

ops/model.py Outdated
... expiration=datetime.datetime(day=23, year=2045, month=12))
"""
if not self._backend._hook_is_running: # noqa
raise RuntimeError("This method is meant to be called from charm code only; "

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a standard error message used by the OF? Is there a const somewhere?

ops/model.py Outdated
"""The revision number for this secret.

This will only be visible to the owner of the secret.
Secret consumers will get ``None``.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need 2 classes:
Secret (for owners)
SecretContent (for consumers, returned from secret_get).
It seems like a suboptimal charmer experience to have to "know" what apis work and which don't.
A consumer will always only get the content. The owner always gets the full secret.

ops/model.py Outdated
Comment on lines 1130 to 1132
# fixme: ATM you can't secret-set without content (e.g. to only update the label),
# but that is a bug in juju.
# when that is fixed, `secret-set [id] --label X` will be valid.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed now

ops/model.py Outdated

def _validate_ownership(self, operation_name: str,
should_own: bool = True):
owns = self._is_owned_by_this_unit

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So checking - this "is owned" value is set if the current unit is the leader (for app owned secrets)?

ops/model.py Outdated
raise TypeError(
'cannot {} secret {} to a unit or application '
'target without specifying a relation as its scope'.format(operation, self))
if relation and isinstance(target, Application) and relation.app is not target:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Target application should come from the "remote application" property of the relation right? So we can never get this error?

ops/model.py Outdated
operation_name, self))
if not should_own and owns:
raise SecretOwnershipError(
'Cannot {} secret {!r} as an owner. Only consumers can do that.'.format(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me, when would we get this?

All this logic here is a bit convoluted and would be much simpler if we had separate classes for owners and consumers.

Tests and testing harness broken right now
@benhoyt benhoyt marked this pull request as draft November 20, 2022 22:55
@benhoyt benhoyt changed the base branch from ops-2.0 to main November 27, 2022 23:32
@benhoyt
Copy link
Collaborator

benhoyt commented Nov 28, 2022

This PR is getting too messy -- many commits over a long period of time, and many commits to main since the branch was created. Going to clean things up by deleting this branch and recreating on a new PR without the test harness for now.

@benhoyt benhoyt closed this Nov 28, 2022
@benhoyt benhoyt deleted the secrets branch November 28, 2022 03:25
benhoyt added a commit to benhoyt/operator that referenced this pull request Nov 28, 2022
Based on canonical#840 with rework for
the new API design and simplifications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants