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

Expose in ops a jhack-eval-like entrypoint #1449

Open
PietroPasotti opened this issue Nov 6, 2024 · 5 comments
Open

Expose in ops a jhack-eval-like entrypoint #1449

PietroPasotti opened this issue Nov 6, 2024 · 5 comments
Labels
feature New feature or request

Comments

@PietroPasotti
Copy link
Contributor

PietroPasotti commented Nov 6, 2024

jhack eval is a command that allows you to evaluate python expressions in the context of a charm, without having to wait for the charm to receive an event.

jhack eval traefik/0 "self.unit.name" will print to stdout traefik/0.

This has proved to be extremely useful in debugging and troubleshooting.
It is implemented by scp'ing a file into the unit and then juju-exec'ing it. The file contains much of the machinery in ops.main.main, but never calls the _emit_charm_event function; instead, it eval's the user-provided expression while binding the following globals:

  • self: the charm instance
  • ops: the ops module, to let the user evaluate expressions such as setattr(self.unit.status, ops.ActiveStatus())
  • json: the json module, to let the user output json instead of plain str/repr.

In certain environments where jhack is not available, it would be handy to have a way to ask the user to evaluate an expression for debugging purposes, to inspect the charm's current state outside of what the logging output shows.
It would be fantastic if we could offer a way for the user to achieve the same functionality without much hackery.

My proposal is to add to ops.main logic to skip event emission if the charm is being executed with a special envvar set.

The user would need to run:
juju exec --unit traefik/0 OPS_EVAL_EXPR="self.unit.name" ./dispatch

(or something like that).

When called with that envvar set, ops.main will not emit an event on the charm, but instead will evaluate the provided exception while binding the same globals that jhack exposes.

see #1450 for a proof of concept implementation

@dimaqq
Copy link
Contributor

dimaqq commented Nov 7, 2024

I would have thought that setattr(self.unit.status, ops.ActiveStatus()) wouldn't actually work outside the context of a hook. Am I missing something?

@benhoyt
Copy link
Collaborator

benhoyt commented Nov 7, 2024

@dimaqq No, it looks like the status-set hook tool works fine outside the context of a hook (at least with juju exec):

$ juju exec --unit snappass-test/0 -- status-set blocked 'blocky!'
$ juju status
Model  Controller          Cloud/Region        Version    SLA          Timestamp
t      microk8s-localhost  microk8s/localhost  3.6-beta1  unsupported  15:47:44+13:00

App            Version  Status   Scale  Charm          Channel        Rev  Address         Exposed  Message
snappass-test           waiting      1  snappass-test  latest/stable    9  10.152.183.112  no       installing agent

Unit              Workload  Agent  Address       Ports  Message
snappass-test/0*  blocked   idle   10.1.164.163         blocky!

@benhoyt
Copy link
Collaborator

benhoyt commented Nov 7, 2024

This is an interesting idea, though I wonder about the UI. On the one hand, it's nice that

juju exec --unit traefik/0 OPS_EVAL='self.unit.name' ./dispatch

would work out of the box. But that command is also non-obvious (documentation could solve that) and awkward (why the environment variable? what is ./dispatch doing there?).

What if it was a two-step thing, where you jhack install-ops-eval first once, and then you could run:

juju exec --unit traefik/0 ops-eval 1+1

Alternatively, what if charmcraft included an ops-eval script in every charm? Is that even feasible? (CC: @lengau) Then you'd have to use ./ I think:

juju exec --unit traefik/0 ./ops-eval 1+1

The other question I had is whether it's eval or exec that you want. setattr(self.unit.status, ops.ActiveStatus()) (eval) is an awkward way to say self.unit.status = ops.ActiveStatus() (exec). If we had ops-exec instead of ops-eval, then you'd just have to wrap expressions in print(...), but the other way around is harder.

What expressions/commands do you (or others) typically evaluate/run with jhack eval?

@PietroPasotti
Copy link
Contributor Author

The whole point of this exercise is to allow people to 'eval' without needing jhack since the environment could be locked down.
so jhack install-ops-eval is a no-go; and also the only thing that does is juju scp a python script into the unit, so at that point we could also simply instruct to do that manually.

As to including it in the packed charms by default, that's not a bad idea but also it's not ideal that it wouldn't work with existing deployments.

Common workflows:

  • jhack eval foo/0 json.dumps(self._generate_some_config(), indent=2) and inspect the resulting json
  • jhack eval foo/0 self._refresh_all_relations() and watch the charm update its databags
  • jhack eval foo/0 self.workload_manager.restart() to restart the services
  • jhack eval foo/0 self._some_private_object.foo to inspect the values of variables

@benhoyt
Copy link
Collaborator

benhoyt commented Nov 7, 2024

As to including it in the packed charms by default, that's not a bad idea but also it's not ideal that it wouldn't work with existing deployments.

But isn't that true of any update to ops? Any changes to ops will require a newly-packed charm, right?

Thanks for the list of common workflows.

@tonyandrewmeyer tonyandrewmeyer added the feature New feature or request label Nov 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants