Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions sunbeam-python/sunbeam/commands/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from sunbeam.features.interface.v1.base import is_maas_deployment
from sunbeam.steps.k8s import DeployK8SApplicationStep
from sunbeam.steps.k8s_upgrade import K8SCharmUpgradeStep
from sunbeam.steps.manual_tls import ManualTLSCharmUpgradeStep
from sunbeam.steps.upgrades.base import UpgradeCoordinator
from sunbeam.steps.upgrades.inter_channel import ChannelUpgradeCoordinator
from sunbeam.steps.upgrades.intra_channel import (
Expand Down Expand Up @@ -385,3 +386,53 @@ def refresh_k8s(
if message:
click.echo(message)
click.echo("k8s refresh complete.")


@refresh.command("manual-tls-certificates-operator")
@click.option(
"-m",
"--manifest",
"manifest_path",
help="Manifest file.",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
)
@click_option_show_hints
@click.pass_context
def refresh_manual_tls_certificates(
ctx: click.Context,
manifest_path: Path | None = None,
show_hints: bool = False,
) -> None:
"""Upgrade manual-tls-certificates charm to latest channel/revision."""
deployment: Deployment = ctx.obj
client = deployment.get_client()
jhelper = JujuHelper(deployment.juju_controller)
tfhelper = deployment.get_tfhelper("openstack-plan")

manifest = None
if manifest_path:
manifest = deployment.get_manifest(manifest_path)
run_plan([AddManifestStep(client, manifest_path)], console, show_hints)

if not manifest:
LOG.debug("Getting latest manifest from cluster db")
manifest = deployment.get_manifest()

plan_results = run_plan(
[
ManualTLSCharmUpgradeStep(
deployment,
client,
manifest,
jhelper,
tfhelper,
)
],
console,
show_hints,
)

message = get_step_message(plan_results, ManualTLSCharmUpgradeStep)
if message:
click.echo(message)
click.echo("manual-tls-certificates refresh complete.")
3 changes: 2 additions & 1 deletion sunbeam-python/sunbeam/features/tls/ca.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
handle_list_outstanding_csrs,
)
from sunbeam.utils import click_option_show_hints, pass_method_obj
from sunbeam.versions import MANUAL_TLS_CERTIFICATES_CHANNEL

LOG = logging.getLogger(__name__)
console = Console()
Expand Down Expand Up @@ -149,7 +150,7 @@ def set_tfvars_on_enable(
"""Set terraform variables to enable the application."""
tfvars: dict[str, str | bool] = {
"traefik-to-tls-provider": CA_MANUAL_TLS_CERTIFICATE,
"manual-tls-certificates-channel": "1/stable",
"manual-tls-certificates-channel": MANUAL_TLS_CERTIFICATES_CHANNEL,
}
if "public" in config.endpoints:
tfvars.update({"enable-tls-for-public-endpoint": True})
Expand Down
3 changes: 2 additions & 1 deletion sunbeam-python/sunbeam/features/tls/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
WaitForApplicationsStep,
)
from sunbeam.utils import pass_method_obj
from sunbeam.versions import MANUAL_TLS_CERTIFICATES_CHANNEL

CERTIFICATE_FEATURE_KEY = "TlsProvider"
CA_MANUAL_TLS_CERTIFICATE = "manual-tls-certificates"
Expand Down Expand Up @@ -108,7 +109,7 @@ def default_software_overrides(self) -> SoftwareConfig:
return SoftwareConfig(
charms={
"manual-tls-certificates": CharmManifest(
channel="latest/stable",
channel=MANUAL_TLS_CERTIFICATES_CHANNEL,
)
}
)
Expand Down
3 changes: 2 additions & 1 deletion sunbeam-python/sunbeam/features/tls/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
vault_pki_config_key,
)
from sunbeam.utils import click_option_show_hints, pass_method_obj
from sunbeam.versions import MANUAL_TLS_CERTIFICATES_CHANNEL

CA_APP_NAME = "vault"
LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -232,7 +233,7 @@ def set_tfvars_on_enable(
"""Set terraform variables to enable the application."""
tfvars: dict[str, typing.Any] = {
"traefik-to-tls-provider": CA_APP_NAME,
"manual-tls-certificates-channel": "1/stable",
"manual-tls-certificates-channel": MANUAL_TLS_CERTIFICATES_CHANNEL,
}
jhelper = JujuHelper(deployment.juju_controller)
vault_channel = self._get_vault_channel(jhelper)
Expand Down
139 changes: 139 additions & 0 deletions sunbeam-python/sunbeam/steps/manual_tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# SPDX-FileCopyrightText: 2026 - Canonical Ltd
# SPDX-License-Identifier: Apache-2.0

import logging

from sunbeam.clusterd.client import Client
from sunbeam.core.common import (
BaseStep,
Result,
ResultType,
StepContext,
)
from sunbeam.core.deployment import Deployment
from sunbeam.core.juju import (
JujuException,
JujuHelper,
JujuStepHelper,
JujuWaitException,
)
from sunbeam.core.manifest import Manifest
from sunbeam.core.openstack import OPENSTACK_MODEL
from sunbeam.core.terraform import TerraformException, TerraformHelper
from sunbeam.features.interface.v1.openstack import OPENSTACK_TERRAFORM_VARS
from sunbeam.steps.charm_upgrade import CharmRefreshDecision, check_charm_needs_refresh
from sunbeam.versions import MANUAL_TLS_CERTIFICATES_CHANNEL

LOG = logging.getLogger(__name__)

CHARM_NAME = "manual-tls-certificates"
MANUAL_TLS_UPGRADE_TIMEOUT = 300


class ManualTLSCharmUpgradeStep(BaseStep, JujuStepHelper):
"""Upgrade the manual-tls-certificates charm to latest channel/revision."""

def __init__(
self,
deployment: Deployment,
client: Client,
manifest: Manifest,
jhelper: JujuHelper,
tfhelper: TerraformHelper,
application: str = CHARM_NAME,
):
super().__init__(
"Upgrade manual-tls-certificates",
"Upgrading manual-tls-certificates to latest channel/revision",
)
self.deployment = deployment
self.client = client
self.manifest = manifest
self.jhelper = jhelper
self.tfhelper = tfhelper
self.application = application
self.tfvar_config = OPENSTACK_TERRAFORM_VARS
self._decision: CharmRefreshDecision

def is_skip(self, context: StepContext) -> Result:
"""Skip if manual-tls-certificates is not deployed or already up-to-date."""
decision = check_charm_needs_refresh(
self.jhelper,
self.manifest,
CHARM_NAME,
OPENSTACK_MODEL,
self.application,
default_channel=MANUAL_TLS_CERTIFICATES_CHANNEL,
support_track_upgrades=True,
)
self._decision = decision
if decision.result.result_type == ResultType.FAILED:
return Result(
ResultType.FAILED,
f"manual-tls-certificates upgrade failed: {decision.result.message}",
)
return decision.result

def run(self, context: StepContext) -> Result:
"""Refresh the manual-tls-certificates charm."""
target_channel = self._decision.effective_channel
revision = self._decision.effective_revision
refresh_channel = target_channel if self._decision.needs_channel_flag else None

try:
self.update_status(
context,
f"Refreshing {CHARM_NAME} to channel {target_channel}"
+ (f" revision {revision}" if revision else ""),
)
self.jhelper.charm_refresh(
self.application,
OPENSTACK_MODEL,
channel=refresh_channel,
revision=revision,
)
except JujuException as e:
LOG.error(f"Failed to refresh {CHARM_NAME}: {e}")
return Result(
ResultType.FAILED,
f"Failed to refresh {CHARM_NAME}: {e}",
)

try:
self.update_status(context, f"Waiting for {CHARM_NAME} to stabilise")
self.jhelper.wait_until_active(
OPENSTACK_MODEL,
apps=[self.application],
timeout=MANUAL_TLS_UPGRADE_TIMEOUT,
)
except (JujuWaitException, TimeoutError) as e:
LOG.error(f"Timed out waiting for {self.application}: {e}")
return Result(
ResultType.FAILED,
f"Timed out waiting for {self.application} to stabilise: {e}",
)

# Update terraform state with the channel used. Manifest takes
# precedence over override_tfvars for charm keys.
try:
self.update_status(
context,
f"Updating terraform plan with new {CHARM_NAME} channel",
)
self.tfhelper.update_tfvars_and_apply_tf(
self.client,
self.manifest,
tfvar_config=self.tfvar_config,
override_tfvars={
"manual-tls-certificates-channel": target_channel,
},
)
except TerraformException as e:
LOG.warning(
f"Failed to reapply terraform plan after {CHARM_NAME} upgrade: {e}"
)

return Result(
ResultType.COMPLETED,
f"{CHARM_NAME} upgraded successfully.",
)
2 changes: 1 addition & 1 deletion sunbeam-python/sunbeam/steps/upgrades/intra_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
LOG = logging.getLogger(__name__)
console = Console()

INFRA_APPS = ["mysql-k8s", "vault-k8s", "k8s"]
INFRA_APPS = ["mysql-k8s", "vault-k8s", "k8s", "manual-tls-certificates"]

# Snap-based charm applications that expose a refresh-snap action.
# These need to be refreshed explicitly after the charm refresh because
Expand Down
Loading
Loading