Skip to content

Commit

Permalink
Create a charm action to run wp core update db after a wordpress upgr…
Browse files Browse the repository at this point in the history
…ade (#235)

* Added the `update-database` action. I need feedback and advice on what kind of tests I need to write.

* Added  `dry-run` parameter

* Wrote unit tests for `update-database` action

* Removed blank lines after docstring

* Fixed error message if action fails.

* Making sure stderr exists and a str in action fail

* Fixed number of commands in mock

* Updated uni tests

* Removed unnecessary else

* Better debug and auto update-database when charm upgrades

* Fix forgotten docstring

* Removed empty line after docstring.

* Better handle parameters get

* Fixed docstring

* Make linter happy.

* Wrote how-to document for charm update

* Add final new line

* Removed update function from on-upgrade event, private function rearrangement, function re-order.

* Document updates

* Initialized test fail parameter and updated docstring

* Fixed private function returning wrong type.

* Fixed mock return results for database update

* Happy linter happy life

* Better documentation
  • Loading branch information
alithethird authored Aug 6, 2024
1 parent fa772ca commit fc9c2b6
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 0 deletions.
8 changes: 8 additions & 0 deletions actions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ rotate-wordpress-secrets:
auth_key, auth_salt, logged_in_key, logged_in_salt, nonce_key, nonce_salt, secure_auth_key,
secure_auth_salt.
Users will be forced to log in again. This might be useful under security breach circumstances.
update-database:
description: >
After upgrading WordPress to a new version it is typically necessary to run 'wp core update-db'
to migrate the database schema. This action does exactly that.
params:
dry-run:
type: boolean
description: Runs the 'wp core update-db --dry-run' command.
20 changes: 20 additions & 0 deletions docs/how-to/upgrade-wordpress-charm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# How to upgrade WordPress Charm

Before updating the Charm you need to backup the database using MySQL charm's `create-backup` action.

```
juju run mysql/leader create-backup
```
Additional info can be found about backup in [the MySQL documentation](https://charmhub.io/mysql/docs/h-create-and-list-backups)

Then you can upgrade the WordPress Charm.

```
juju refresh wordpress-k8s
```

After upgrading the WordPress Charm you need to update the database schema.

```
juju run wordpress-k8s/0 update-database
```
44 changes: 44 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def __init__(self, *args, **kwargs):
self.framework.observe(
self.on.rotate_wordpress_secrets_action, self._on_rotate_wordpress_secrets_action
)
self.framework.observe(self.on.update_database_action, self._on_update_database_action)

self.framework.observe(self.on.leader_elected, self._setup_replica_data)
self.framework.observe(self.on.uploads_storage_attached, self._reconciliation)
Expand Down Expand Up @@ -267,6 +268,49 @@ def _on_rotate_wordpress_secrets_action(self, event: ActionEvent):
self._reconciliation(event)
event.set_results({"result": "ok"})

def _on_update_database_action(self, event: ActionEvent):
"""Handle the update-database action.
This action is to upgrade the database schema after the WordPress version is upgraded.
Args:
event: Used for returning result or failure of action.
"""
logger.info("Starting Database update process.")
result = self._update_database(bool(event.params.get("dry-run")))
if result.success:
logger.info("Finished Database update process.")
event.set_results({"result": result.message})
return
logger.error("Failed to update database schema: %s", result.message)
event.fail(result.message)

def _update_database(self, dry_run: bool = False) -> types_.ExecResult:
"""Update database.
Args:
dry_run (bool, optional): Runs update as a dry-run, useful to check
if update is necessary without doing the update. Defaults to False.
Returns:
Execution result.
"""
cmd = ["wp", "core", "update-db"]
if dry_run:
cmd.append("--dry-run")

result = self._run_wp_cli(cmd, timeout=600)
if result.return_code != 0:
return types_.ExecResult(
success=False,
result=None,
message=str(result.stderr) if result.stderr else "Database update failed",
)
logger.info("Finished Database update process.")
return types_.ExecResult(
success=True, result=None, message=str(result.stdout) if result.stdout else "ok"
)

@staticmethod
def _wordpress_secret_key_fields():
"""Field names of secrets required for instantiation of WordPress.
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,47 @@ def test_rotate_wordpress_secrets(
action_event_mock.fail.assert_not_called()


def test_update_database(
patch,
harness: ops.testing.Harness,
action_event_mock: unittest.mock.MagicMock,
):
"""
arrange: after charm is initialized and database ready.
act: run update-database action.
assert: update-database action should success and return "ok".
"""
harness.set_can_connect(harness.model.unit.containers["wordpress"], True)
harness.begin_with_initial_hooks()
patch.container._fail_wp_update_database = False
charm: WordpressCharm = typing.cast(WordpressCharm, harness.charm)
charm._on_update_database_action(action_event_mock)

action_event_mock.set_results.assert_called_once_with({"result": "ok"})
action_event_mock.fail.assert_not_called()


def test_update_database_fail(
patch,
harness: ops.testing.Harness,
action_event_mock: unittest.mock.MagicMock,
):
"""
arrange: after charm is initialized and database is mocked to fail.
act: run update-database action.
assert: update-database action should fail.
"""
harness.set_can_connect(harness.model.unit.containers["wordpress"], True)
harness.begin_with_initial_hooks()
patch.container._fail_wp_update_database = True
charm: WordpressCharm = typing.cast(WordpressCharm, harness.charm)
action_event_mock.configure_mock()
charm._on_update_database_action(action_event_mock)

action_event_mock.set_results.assert_not_called()
action_event_mock.fail.assert_called_once_with("Database update failed")


@pytest.mark.usefixtures("attach_storage")
def test_theme_reconciliation(
patch: WordpressPatch,
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/wordpress_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ def __init__(
self._wordpress_database_mock = wordpress_database_mock
self.installed_plugins = set(WordpressCharm._WORDPRESS_DEFAULT_PLUGINS)
self.installed_themes = set(WordpressCharm._WORDPRESS_DEFAULT_THEMES)
self._fail_wp_update_database = False

def exec(
self, cmd, user=None, group=None, working_dir=None, combine_stderr=None, timeout=None
Expand Down Expand Up @@ -645,6 +646,13 @@ def _mock_chown_uploads(self, _cmd):
"""Simulate ``chown`` command execution in the container."""
return ExecProcessMock(return_code=0, stdout="", stderr="")

@_exec_handler.register(lambda cmd: cmd[:3] == ["wp", "core", "update-db"])
def _mock_wp_update_database(self, _cmd):
"""Simulate ``wp core update-db`` command execution in the container."""
if self._fail_wp_update_database:
return ExecProcessMock(return_code=1, stdout="", stderr="Database update failed")
return ExecProcessMock(return_code=0, stdout="ok", stderr="")

def __getattr__(self, item):
"""Passthrough anything else to :class:`ops.charm.model.Container`.
Expand Down

0 comments on commit fc9c2b6

Please sign in to comment.