From 8d60a255bc4f9cd007e26f747d5e8935ff571781 Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Thu, 7 Dec 2023 18:39:50 +0100 Subject: [PATCH 01/10] Regression introduced in macaroon bakery Introduce upper limit --- sunbeam-python/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sunbeam-python/requirements.txt b/sunbeam-python/requirements.txt index c7612b57..532a9e89 100644 --- a/sunbeam-python/requirements.txt +++ b/sunbeam-python/requirements.txt @@ -35,3 +35,6 @@ lightkube-models # For plugin repo jsonschema GitPython + +# Regression introduced in 1.3.3 +macaroonbakery<1.3.3 From 138b744876ab834f0d297f05a270822189a24e81 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 19 Oct 2023 09:21:28 +0000 Subject: [PATCH 02/10] Add cluster refresh command for upgrades --- sunbeam-python/sunbeam/commands/juju.py | 101 +++++- sunbeam-python/sunbeam/commands/openstack.py | 7 +- sunbeam-python/sunbeam/commands/refresh.py | 60 ++++ sunbeam-python/sunbeam/commands/terraform.py | 30 ++ .../sunbeam/commands/upgrades/__init__.py | 0 .../sunbeam/commands/upgrades/base.py | 82 +++++ .../commands/upgrades/inter_channel.py | 336 ++++++++++++++++++ .../commands/upgrades/intra_channel.py | 94 +++++ sunbeam-python/sunbeam/jobs/juju.py | 91 ++++- sunbeam-python/sunbeam/jobs/plugin.py | 16 +- sunbeam-python/sunbeam/main.py | 2 + sunbeam-python/sunbeam/plugins/dns/plugin.py | 18 + .../sunbeam/plugins/interface/v1/base.py | 2 +- .../sunbeam/plugins/interface/v1/openstack.py | 152 +++++++- sunbeam-python/sunbeam/versions.py | 78 ++++ .../tests/unit/sunbeam/commands/test_juju.py | 84 +++++ .../sunbeam/commands/upgrades/test_base.py | 94 +++++ sunbeam-python/tests/unit/sunbeam/conftest.py | 6 + 18 files changed, 1236 insertions(+), 17 deletions(-) create mode 100644 sunbeam-python/sunbeam/commands/refresh.py create mode 100644 sunbeam-python/sunbeam/commands/upgrades/__init__.py create mode 100644 sunbeam-python/sunbeam/commands/upgrades/base.py create mode 100644 sunbeam-python/sunbeam/commands/upgrades/inter_channel.py create mode 100644 sunbeam-python/sunbeam/commands/upgrades/intra_channel.py create mode 100644 sunbeam-python/sunbeam/versions.py create mode 100644 sunbeam-python/tests/unit/sunbeam/commands/upgrades/test_base.py diff --git a/sunbeam-python/sunbeam/commands/juju.py b/sunbeam-python/sunbeam/commands/juju.py index d860029f..6d0b74e6 100644 --- a/sunbeam-python/sunbeam/commands/juju.py +++ b/sunbeam-python/sunbeam/commands/juju.py @@ -22,11 +22,12 @@ import subprocess import tempfile from pathlib import Path -from typing import Optional +from typing import Optional, Union import pexpect import pwgen import yaml +from packaging import version from pyroute2 import Console from snaphelpers import Snap @@ -171,6 +172,104 @@ def add_cloud(self, cloud_type: str, cloud_name: str) -> bool: return True + def get_available_charm_revision( + self, charm_name: str, track: str, risk: str, arch: str = "amd64" + ) -> int: + """Find the latest available revision of a charm in a given channel + + :param charm_name: Name of charm to look up + :param track: Track of charm + :param risk: Risk of charm + :param arch: Arch of charm + """ + available_charm_data = self._juju_cmd(*["info", charm_name]) + channel_data = [ + d + for d in available_charm_data["channels"][track][risk] + if arch in d["architectures"] + ] + assert len(channel_data) == 1, "Unexpected candidate charms {}".format( + len(channel_data) + ) + return int(channel_data[0]["revision"]) + + def revision_update_needed( + self, application_name: str, model: str, status: Union[dict, None] = None + ) -> bool: + """Check if a revision update is available for an applicaton. + + :param application_name: Name of application to check for updates for + :param model: Model application is in + :param status: Dictionay of model status + """ + if not status: + _status = run_sync(self.jhelper.get_model_status_full(model)) + status = json.loads(_status.to_json()) + app_status = status["applications"].get(application_name, {}) + if not app_status: + LOG.debug(f"{application_name} not present in model") + return False + deployed_revision = int(self._extract_charm_revision(app_status["charm"])) + charm_name = self._extract_charm_name(app_status["charm"]) + deployed_channel = self.normalise_channel(app_status["charm-channel"]) + track = deployed_channel.split("/")[0] + risk = deployed_channel.split("/")[1] + try: + deployed_channel.split("/")[2] + LOG.debug(f"Cannot calculate upgrade for {application_name}, branch in use") + return False + except IndexError: + pass + available_revision = self.get_available_charm_revision(charm_name, track, risk) + return bool(available_revision > deployed_revision) + + def normalise_channel(self, channel: str) -> str: + """Expand channel if it is using abbreviation. + + Juju supports abbreviating latest/{risk} to {risk}. This expands it. + + :param channel: Channel string to normalise + """ + if channel in ["stable", "candidate", "beta", "edge"]: + channel = f"latest/{channel}" + return channel + + def _extract_charm_name(self, charm_url: str) -> str: + """Extract charm name from charm url. + + :param charm_url: Url to examine + """ + # XXX There must be a better way. ch:amd64/jammy/cinder-k8s-50 -> cinder-k8s + return charm_url.split("/")[-1].rsplit("-", maxsplit=1)[0] + + def _extract_charm_revision(self, charm_url: str) -> str: + """Extract charm revision from charm url. + + :param charm_url: Url to examine + """ + return charm_url.split("-")[-1] + + def channel_update_needed(self, channel: str, new_channel: str) -> bool: + """Compare two channels and see if the second is 'newer'. + + :param current_channel: Current channel + :param new_channel: Proposed new channel + """ + risks = ["stable", "candidate", "beta", "edge"] + current_channel = self.normalise_channel(channel) + current_track, current_risk = current_channel.split("/") + new_track, new_risk = new_channel.split("/") + if current_track != new_track: + try: + return version.parse(current_track) < version.parse(new_track) + except version.InvalidVersion: + LOG.error("Error: Could not compare tracks") + return False + if risks.index(current_risk) < risks.index(new_risk): + return True + else: + return False + def bootstrap_questions(): return { diff --git a/sunbeam-python/sunbeam/commands/openstack.py b/sunbeam-python/sunbeam/commands/openstack.py index f3d4e6c8..b4ffcb4e 100644 --- a/sunbeam-python/sunbeam/commands/openstack.py +++ b/sunbeam-python/sunbeam/commands/openstack.py @@ -51,6 +51,7 @@ TimeoutException, run_sync, ) +from sunbeam.versions import OPENSTACK_CHANNEL, OVN_CHANNEL, RABBITMQ_CHANNEL LOG = logging.getLogger(__name__) OPENSTACK_MODEL = "openstack" @@ -214,9 +215,9 @@ def run(self, status: Optional[Status] = None) -> Result: { "model": self.model, # Make these channel options configurable by the user - "openstack-channel": "2023.2/edge", - "ovn-channel": "23.09/edge", - "rabbitmq-channel": "3.12/edge", + "openstack-channel": OPENSTACK_CHANNEL, + "ovn-channel": OVN_CHANNEL, + "rabbitmq-channel": RABBITMQ_CHANNEL, "cloud": self.cloud, "credential": f"{self.cloud}{CREDENTIAL_SUFFIX}", "config": {"workload-storage": MICROK8S_DEFAULT_STORAGECLASS}, diff --git a/sunbeam-python/sunbeam/commands/refresh.py b/sunbeam-python/sunbeam/commands/refresh.py new file mode 100644 index 00000000..7f71b906 --- /dev/null +++ b/sunbeam-python/sunbeam/commands/refresh.py @@ -0,0 +1,60 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging + +import click +from rich.console import Console +from snaphelpers import Snap + +from sunbeam.commands.terraform import TerraformHelper +from sunbeam.commands.upgrades.inter_channel import ChannelUpgradeCoordinator +from sunbeam.commands.upgrades.intra_channel import LatestInChannelCoordinator +from sunbeam.jobs.juju import JujuHelper + +LOG = logging.getLogger(__name__) +console = Console() +snap = Snap() + + +@click.command() +@click.option( + "--upgrade-release", + is_flag=True, + show_default=True, + default=False, + help="Upgrade OpenStack release.", +) +def refresh(upgrade_release) -> None: + """Refresh deployment. + + Refresh the deployment. If --upgrade-release is supplied then charms are + upgraded the channels aligned with this snap revision + """ + tfplan = "deploy-openstack" + data_location = snap.paths.user_data + tfhelper = TerraformHelper( + path=snap.paths.user_common / "etc" / tfplan, + plan="openstack-plan", + backend="http", + data_location=data_location, + ) + jhelper = JujuHelper(data_location) + if upgrade_release: + a = ChannelUpgradeCoordinator(jhelper, tfhelper) + a.run_plan() + else: + a = LatestInChannelCoordinator(jhelper, tfhelper) + a.run_plan() + click.echo("Refresh complete.") diff --git a/sunbeam-python/sunbeam/commands/terraform.py b/sunbeam-python/sunbeam/commands/terraform.py index 36f6363c..7f8f9d56 100644 --- a/sunbeam-python/sunbeam/commands/terraform.py +++ b/sunbeam-python/sunbeam/commands/terraform.py @@ -281,6 +281,36 @@ def output(self) -> dict: LOG.warning(e.stderr) raise TerraformException(str(e)) + def sync(self) -> None: + """Sync the running state back to the Terraform state file.""" + os_env = os.environ.copy() + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + tf_log = str(self.path / f"terraform-sync-{timestamp}.log") + os_env.update({"TF_LOG_PATH": tf_log}) + os_env.setdefault("TF_LOG", "INFO") + if self.env: + os_env.update(self.env) + if self.data_location: + os_env.update(self.update_juju_provider_credentials()) + + try: + cmd = [self.terraform, "apply", "-refresh-only", "-auto-approve"] + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + cwd=self.path, + env=os_env, + ) + LOG.debug( + f"Command finished. stdout={process.stdout}, stderr={process.stderr}" + ) + except subprocess.CalledProcessError as e: + LOG.error(f"terraform sync failed: {e.output}") + LOG.error(e.stderr) + raise TerraformException(str(e)) + class TerraformInitStep(BaseStep): """Initialize Terraform with required providers.""" diff --git a/sunbeam-python/sunbeam/commands/upgrades/__init__.py b/sunbeam-python/sunbeam/commands/upgrades/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sunbeam-python/sunbeam/commands/upgrades/base.py b/sunbeam-python/sunbeam/commands/upgrades/base.py new file mode 100644 index 00000000..ee5deb48 --- /dev/null +++ b/sunbeam-python/sunbeam/commands/upgrades/base.py @@ -0,0 +1,82 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Optional + +from rich.console import Console +from rich.status import Status + +from sunbeam.commands.terraform import TerraformHelper +from sunbeam.jobs.common import BaseStep, Result, ResultType, run_plan +from sunbeam.jobs.juju import JujuHelper +from sunbeam.jobs.plugin import PluginManager + +LOG = logging.getLogger(__name__) +console = Console() + + +class UpgradePlugins(BaseStep): + def __init__( + self, + jhelper: JujuHelper, + tfhelper: TerraformHelper, + upgrade_release: bool = False, + ): + """Upgrade plugins. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :upgrade_release: Whether to upgrade channel + """ + super().__init__("Validation", "Running pre-upgrade validation") + self.jhelper = jhelper + self.tfhelper = tfhelper + self.upgrade_release = upgrade_release + + def run(self, status: Optional[Status] = None) -> Result: + PluginManager.update_plugins( + repos=["core"], upgrade_release=self.upgrade_release + ) + return Result(ResultType.COMPLETED) + + +class UpgradeCoordinator: + def __init__( + self, jhelper: JujuHelper, tfhelper: TerraformHelper, channel: str = None + ): + """Upgrade coordinator. + + Execute plan for conducting an upgrade. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :channel: OpenStack channel to upgrade charms to + """ + self.channel = channel + self.jhelper = jhelper + self.tfhelper = tfhelper + + def get_plan(self) -> list[BaseStep]: + """Return the plan for this upgrade. + + Return the steps to complete this upgrade. + """ + return [] + + def run_plan(self) -> None: + """Execute the upgrade plan.""" + plan = self.get_plan() + run_plan(plan, console) diff --git a/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py b/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py new file mode 100644 index 00000000..b5768d11 --- /dev/null +++ b/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py @@ -0,0 +1,336 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Callable, Dict, List, Optional, Union + +from rich.console import Console +from rich.status import Status + +from sunbeam.clusterd.client import Client +from sunbeam.commands.juju import JujuStepHelper +from sunbeam.commands.terraform import TerraformHelper +from sunbeam.commands.upgrades.base import UpgradeCoordinator, UpgradePlugins +from sunbeam.commands.upgrades.intra_channel import LatestInChannel +from sunbeam.jobs.common import ( + BaseStep, + Result, + ResultType, + read_config, + run_plan, + update_config, +) +from sunbeam.jobs.juju import JujuHelper, run_sync +from sunbeam.versions import ( + CHARM_VERSIONS, + MACHINE_SERVICES, + MISC_SERVICES_K8S, + OPENSTACK_SERVICES_K8S, + OVN_SERVICES_K8S, +) + +LOG = logging.getLogger(__name__) +console = Console() + + +class BaseUpgrade(BaseStep, JujuStepHelper): + def __init__(self, name, description, jhelper, tfhelper, model): + """Create instance of BaseUpgrade class. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :model: Name of model containing charms. + """ + super().__init__(name, description) + self.jhelper = jhelper + self.tfhelper = tfhelper + self.model = model + + def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: + """Return a strategy for performing the upgrade. + + The strategy is a list of dicts. Each dict consists of: + { + "upgrade_f": f, + "applications": [apps] + }, + + upgrade_f is the function to be applied to each application (in + parallel) to perform the upgrade. + + applications is a list of applications that can be upgraded in parallel. + + Currently only apps that are upgraded with the same function can be + grouped together. + """ + raise NotImplementedError + + def run(self, status: Optional[Status] = None) -> Result: + """Run control plane and machine charm upgrade.""" + self.pre_upgrade_tasks() + for step in self.get_upgrade_strategy(): + step["upgrade_f"](step["applications"], self.model) + self.post_upgrade_tasks() + return Result(ResultType.COMPLETED) + + def pre_upgrade_tasks(self) -> None: + """Tasks to run before the upgrade.""" + return + + def post_upgrade_tasks(self) -> None: + """Tasks to run after the upgrade.""" + return + + def upgrade_applications( + self, + application_list: List[str], + model: str, + expect_wls: Optional[Dict[str, str]] = None, + ) -> None: + """Upgrade applications. + + :param application_list: List of applications to be upgraded + :param model: Name of model + :param expect_wls: The expected workload status after charm upgrade. + """ + if not expect_wls: + expect_wls = {"workload": ["blocked", "active"]} + batch = {} + for app_name in application_list: + new_channel = self.get_new_channel(app_name, model) + if new_channel: + LOG.debug(f"Upgrade needed for {app_name}") + batch[app_name] = { + "channel": new_channel, + "expected_status": expect_wls, + } + else: + LOG.debug(f"{app_name} no channel upgrade needed") + run_sync(self.jhelper.update_applications_channel(model, batch)) + + def get_new_channel(self, application_name: str, model: str) -> Union[str, None]: + """Check application to see if an upgrade is needed. + + Check application to see if an upgrade is needed. A 'None' + returned indicates no upgrade is needed. + + :param application_name: Name of application + :param model: Model application is in + """ + new_channel = None + current_channel = run_sync( + self.jhelper.get_charm_channel(application_name, model) + ) + new_channel = CHARM_VERSIONS.get(application_name) + if current_channel and new_channel: + if self.channel_update_needed(current_channel, new_channel): + return new_channel + else: + return None + else: + # No current_channel indicates application is missing + return new_channel + + def terraform_sync(self, config_key: str, tfvars_delta: dict) -> None: + """Sync the running state back to the Terraform state file. + + :param config_key: The config key used to access the data in microcluster + :param tfvars_delta: The delta of changes to be applied to the terraform + vars stored in microcluster. + """ + self.client = Client() + tfvars = read_config(self.client, config_key) + tfvars.update(tfvars_delta) + update_config(self.client, config_key, tfvars) + self.tfhelper.write_tfvars(tfvars) + self.tfhelper.sync() + + +class UpgradeControlPlane(BaseUpgrade): + def __init__(self, jhelper, tfhelper, model): + """Create instance of BaseUpgrade class. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :model: Name of model containing charms. + """ + super().__init__( + "Upgrade K8S charms", + "Upgrade K8S charms channels to align with snap", + jhelper, + tfhelper, + model, + ) + + def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: + """Return a strategy for performing the upgrade. + + Upgrade all control plane applications in parallel. + """ + upgrade_strategy = [ + { + "upgrade_f": self.upgrade_applications, + "applications": list(MISC_SERVICES_K8S.keys()) + + list(OVN_SERVICES_K8S.keys()) # noqa + + list(OPENSTACK_SERVICES_K8S.keys()), # noqa + }, + ] + return upgrade_strategy + + def post_upgrade_tasks(self) -> None: + """Update channels in terraform vars db.""" + tfvars_delta = { + "openstack-channel": run_sync( + self.jhelper.get_charm_channel("keystone", "openstack") + ), + "ovn-channel": run_sync( + self.jhelper.get_charm_channel("ovn-central", "openstack") + ), + "rabbitmq-channel": run_sync( + self.jhelper.get_charm_channel("rabbitmq", "openstack") + ), + "traefik-channel": run_sync( + self.jhelper.get_charm_channel("traefik", "openstack") + ), + } + self.terraform_sync("TerraformVarsOpenstack", tfvars_delta) + + +class UpgradeMachineCharms(BaseUpgrade): + def __init__(self, jhelper, tfhelper, model): + """Create instance of BaseUpgrade class. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :model: Name of model containing charms. + """ + super().__init__( + "Upgrade Machine charms", + "Upgrade machine charms channels to align with snap", + jhelper, + tfhelper, + model, + ) + + def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: + """Return a strategy for performing the upgrade. + + Upgrade all machine applications in parallel. + """ + upgrade_strategy = [ + { + "upgrade_f": self.upgrade_applications, + "applications": MACHINE_SERVICES, + }, + ] + return upgrade_strategy + + def post_upgrade_tasks(self) -> None: + """Update channels in terraform vars db.""" + self.terraform_sync( + "TerraformVarsMicrocephPlan", + { + "microceph_channel": run_sync( + self.jhelper.get_charm_channel("microceph", "controller") + ) + }, + ) + self.terraform_sync( + "TerraformVarsSunbeamMachine", + { + "charm_channel": run_sync( + self.jhelper.get_charm_channel("sunbeam-machine", "controller") + ) + }, + ) + self.terraform_sync( + "TerraformVarsHypervisor", + { + "charm_channel": run_sync( + self.jhelper.get_charm_channel("openstack-hypervisor", "controller") + ) + }, + ) + self.terraform_sync( + "TerraformVarsMicrok8sAddons", + { + "microk8s_channel": run_sync( + self.jhelper.get_charm_channel("microk8s", "controller") + ) + }, + ) + + +class ChannelUpgradeCoordinator(UpgradeCoordinator): + def __init__(self, jhelper: JujuHelper, tfhelper: TerraformHelper): + """Upgrade coordinator. + + Execute plan for conducting an upgrade. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + """ + self.jhelper = jhelper + self.tfhelper = tfhelper + + def get_plan(self) -> list[BaseStep]: + """Return the plan for this upgrade. + + Return the steps to complete this upgrade. + """ + plan = [ + ValidationCheck(self.jhelper, self.tfhelper), + LatestInChannel(self.jhelper), + UpgradeControlPlane(self.jhelper, self.tfhelper, "openstack"), + UpgradeMachineCharms(self.jhelper, self.tfhelper, "controller"), + UpgradePlugins(self.jhelper, self.tfhelper, upgrade_release=True), + ] + return plan + + def run_plan(self) -> None: + """Execute the upgrade plan.""" + plan = self.get_plan() + run_plan(plan, console) + + +class ValidationCheck(BaseStep): + def __init__(self, jhelper: JujuHelper, tfhelper: TerraformHelper): + """Run validation on the deployment. + + Check whether the requested upgrade is possible. + + :jhelper: Helper for interacting with pylibjuju + :tfhelper: Helper for interaction with Terraform + :channel: OpenStack channel to upgrade charms to + """ + super().__init__("Validation", "Running pre-upgrade validation") + self.jhelper = jhelper + self.tfhelper = tfhelper + + def run(self, status: Optional[Status] = None) -> Result: + """Run validation check.""" + rabbit_channel = run_sync( + self.jhelper.get_charm_channel("rabbitmq", "openstack") + ) + if rabbit_channel.split("/")[0] == "3.9": + return Result( + ResultType.FAILED, + ( + "Pre-upgrade validation failed: Rabbit charm cannot be " + "upgraded from 3.9" + ), + ) + else: + return Result(ResultType.COMPLETED) diff --git a/sunbeam-python/sunbeam/commands/upgrades/intra_channel.py b/sunbeam-python/sunbeam/commands/upgrades/intra_channel.py new file mode 100644 index 00000000..02512470 --- /dev/null +++ b/sunbeam-python/sunbeam/commands/upgrades/intra_channel.py @@ -0,0 +1,94 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +from typing import Optional + +from rich.console import Console +from rich.status import Status + +from sunbeam.commands.juju import JujuStepHelper +from sunbeam.commands.upgrades.base import UpgradeCoordinator, UpgradePlugins +from sunbeam.jobs.common import BaseStep, Result, ResultType +from sunbeam.jobs.juju import run_sync +from sunbeam.versions import K8S_SERVICES, MACHINE_SERVICES + +LOG = logging.getLogger(__name__) +console = Console() + + +class LatestInChannel(BaseStep, JujuStepHelper): + def __init__(self, jhelper): + """Upgrade all charms to latest in current channel. + + :jhelper: Helper for interacting with pylibjuju + """ + super().__init__( + "In channel upgrade", "Upgrade charms to latest revision in current channel" + ) + self.jhelper = jhelper + + def get_charm_update(self, applications, model) -> list[str]: + """Return a list applications that need to be refreshed.""" + candidates = [] + _status = run_sync(self.jhelper.get_model_status_full(model)) + status = json.loads(_status.to_json()) + for app_name in applications: + if self.revision_update_needed(app_name, model, status=status): + candidates.append(app_name) + else: + LOG.debug(f"{app_name} already at latest version in current channel") + return candidates + + def get_charm_update_candidates_k8s(self) -> list[str]: + """Return a list of all k8s charms that need to be refreshed.""" + return self.get_charm_update(K8S_SERVICES.keys(), "openstack") + + def get_charm_update_candidates_machine(self) -> list[str]: + """Return a list of all machine charms that need to be refreshed.""" + return self.get_charm_update(MACHINE_SERVICES.keys(), "controller") + + def is_skip(self, status: Optional[Status] = None) -> Result: + """Step can be skipped if nothing needs refreshing.""" + if ( + self.get_charm_update_candidates_k8s() + or self.get_charm_update_candidates_machine() # noqa + ): + return Result(ResultType.COMPLETED) + else: + return Result(ResultType.SKIPPED) + + def run(self, status: Optional[Status] = None) -> Result: + """Refresh all charms identified as needing a refresh.""" + for app_name in self.get_charm_update_candidates_k8s(): + LOG.debug(f"Refreshing {app_name}") + app = run_sync(self.jhelper.get_application(app_name, "openstack")) + run_sync(app.refresh()) + for app_name in self.get_charm_update_candidates_machine(): + LOG.debug(f"Refreshing {app_name}") + app = run_sync(self.jhelper.get_application(app_name, "controller")) + run_sync(app.refresh()) + return Result(ResultType.COMPLETED) + + +class LatestInChannelCoordinator(UpgradeCoordinator): + """Coordinator for refreshing charms in their current channel.""" + + def get_plan(self) -> list[BaseStep]: + return [ + LatestInChannel(self.jhelper), + UpgradePlugins(self.jhelper, self.tfhelper, upgrade_release=False), + ] diff --git a/sunbeam-python/sunbeam/jobs/juju.py b/sunbeam-python/sunbeam/jobs/juju.py index 60ad826a..6ea709c5 100644 --- a/sunbeam-python/sunbeam/jobs/juju.py +++ b/sunbeam-python/sunbeam/jobs/juju.py @@ -18,10 +18,12 @@ import json import logging from dataclasses import asdict, dataclass +from datetime import datetime from functools import wraps from pathlib import Path from typing import Awaitable, Dict, List, Optional, TypeVar, cast +import pytz import yaml from juju import utils as juju_utils from juju.application import Application @@ -152,7 +154,8 @@ def load(cls, data_location: Path) -> "JujuAccount": return JujuAccount(**yaml.safe_load(file)) except FileNotFoundError as e: raise JujuAccountNotFound( - "Juju user account not found, is node part of sunbeam cluster yet?" + "Juju user account not found, is node part of sunbeam " + f"cluster yet? {data_file}" ) from e def write(self, data_location: Path): @@ -535,6 +538,11 @@ async def wait_application_ready( LOG.debug(f"Application {name!r} is in status: {application.status!r}") try: + LOG.debug( + "Waiting for app status to be: {} {}".format( + model_impl.applications[name].status, accepted_status + ) + ) await model_impl.block_until( lambda: model_impl.applications[name].status in accepted_status, timeout=timeout, @@ -708,3 +716,84 @@ async def set_application_config(self, model: str, app: str, config: dict): """ model_impl = await self.get_model(model) await model_impl.applications[app].set_config(config) + + @controller + async def update_applications_channel( + self, + model: str, + updates: Dict, + timeout: Optional[int] = None, + ): + """Upgrade charm to new channel + + :model: Name of the model to wait for readiness + :application: Application to update + :channel: New channel + """ + LOG.debug(f"Updates: {updates}") + model_impl = await self.get_model(model) + timestamp = pytz.UTC.localize(datetime.now()) + LOG.debug(f"Base Timestamp {timestamp}") + + coros = [ + model_impl.applications[app_name].upgrade_charm(channel=config["channel"]) + for app_name, config in updates.items() + ] + await asyncio.gather(*coros) + + def condition() -> bool: + """Computes readiness for unit""" + statuses = {} + for app_name, config in updates.items(): + _app = model_impl.applications.get( + app_name, + ) + for unit in _app.units: + statuses[unit.entity_id] = bool(unit.agent_status_since > timestamp) + if not unit.agent_status_since > timestamp: + LOG.debug( + f"Waiting on {unit.entity_id}: Upgrade start {timestamp} " + f"WLS update {unit.agent_status_since}" + ) + return all(statuses.values()) + + try: + LOG.debug("Waiting for workload status change") + await model_impl.block_until( + condition, + timeout=timeout, + ) + LOG.debug("Waiting for units ready") + for app_name, config in updates.items(): + _app = model_impl.applications.get( + app_name, + ) + for unit in _app.units: + await self.wait_unit_ready( + unit.entity_id, model, accepted_status=config["expected_status"] + ) + except asyncio.TimeoutError as e: + raise TimeoutException( + f"Timed out while waiting for unit {name!r} to be ready" + ) from e + + @controller + async def get_charm_channel(self, application_name: str, model: str) -> str: + """Get the charm-channel from a deployed application. + + :param application_list: Name of application + :param model: Name of model + """ + _status = await self.get_model_status_full(model) + status = json.loads(_status.to_json()) + return status["applications"].get(application_name, {}).get("charm-channel") + + @controller + async def charm_refresh(self, application_name: str, model: str): + """Update application to latest charm revision in current channel. + + :param application_list: Name of application + :param model: Name of model + """ + app = await self.get_application(application_name, model) + await app.refresh() diff --git a/sunbeam-python/sunbeam/jobs/plugin.py b/sunbeam-python/sunbeam/jobs/plugin.py index a575ac98..e079b41e 100644 --- a/sunbeam-python/sunbeam/jobs/plugin.py +++ b/sunbeam-python/sunbeam/jobs/plugin.py @@ -316,7 +316,9 @@ def is_plugin_version_changed(cls, plugin) -> bool: ) @classmethod - def update_plugins(cls, repos: Optional[list] = []) -> None: + def update_plugins( + cls, repos: Optional[list] = [], upgrade_release: bool = False + ) -> None: """Call plugin upgrade hooks. Get all the plugins defined in repos and call the corresponding plugin @@ -350,8 +352,16 @@ def update_plugins(cls, repos: Optional[list] = []) -> None: if ( hasattr(plugin, "enabled") and p.enabled # noqa W503 - and cls.is_plugin_version_changed(p) # noqa W503 and hasattr(plugin, "upgrade_hook") # noqa W503 ): LOG.debug(f"Upgrading plugin {p.name} defined in repo {repo}") - p.upgrade_hook() + try: + p.upgrade_hook(upgrade_release=upgrade_release) + except TypeError: + LOG.debug( + ( + f"Plugin {p.name} does not support upgrades " + "between channels" + ) + ) + p.upgrade_hook() diff --git a/sunbeam-python/sunbeam/main.py b/sunbeam-python/sunbeam/main.py index 0e9acba5..f847f83c 100644 --- a/sunbeam-python/sunbeam/main.py +++ b/sunbeam-python/sunbeam/main.py @@ -29,6 +29,7 @@ from sunbeam.commands import node as node_cmds from sunbeam.commands import openrc as openrc_cmds from sunbeam.commands import prepare_node as prepare_node_cmds +from sunbeam.commands import refresh as refresh_cmds from sunbeam.commands import resize as resize_cmds from sunbeam.commands import utils as utils_cmds from sunbeam.jobs.plugin import PluginManager @@ -101,6 +102,7 @@ def main(): cluster.add_command(node_cmds.join) cluster.add_command(node_cmds.list) cluster.add_command(node_cmds.remove) + cluster.add_command(refresh_cmds.refresh) cluster.add_command(resize_cmds.resize) cli.add_command(enable) diff --git a/sunbeam-python/sunbeam/plugins/dns/plugin.py b/sunbeam-python/sunbeam/plugins/dns/plugin.py index 97e40086..ba0cb5a4 100644 --- a/sunbeam-python/sunbeam/plugins/dns/plugin.py +++ b/sunbeam-python/sunbeam/plugins/dns/plugin.py @@ -30,6 +30,7 @@ OpenStackControlPlanePlugin, TerraformPlanLocation, ) +from sunbeam.versions import OPENSTACK_CHANNEL LOG = logging.getLogger(__name__) console = Console() @@ -171,3 +172,20 @@ def commands(self) -> dict: } ) return commands + + @property + def k8s_application_data(self): + return { + "designate": { + "channel": OPENSTACK_CHANNEL, + "tfvars_channel_var": None, + }, + "bind": { + "channel": "9/edge", + "tfvars_channel_var": None, + }, + } + + @property + def tfvars_channel_var(self): + return "designate-channel" diff --git a/sunbeam-python/sunbeam/plugins/interface/v1/base.py b/sunbeam-python/sunbeam/plugins/interface/v1/base.py index c504e60a..a108534b 100644 --- a/sunbeam-python/sunbeam/plugins/interface/v1/base.py +++ b/sunbeam-python/sunbeam/plugins/interface/v1/base.py @@ -107,7 +107,7 @@ def install_hook(self) -> None: """ pass - def upgrade_hook(self) -> None: + def upgrade_hook(self, upgrade_release: bool = False) -> None: """Upgrade hook for the plugin. snap-openstack upgrade hook handler invokes this function on all the diff --git a/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py b/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py index 8e208dc3..21ec5331 100644 --- a/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py +++ b/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py @@ -145,8 +145,7 @@ def pre_enable(self) -> None: self.pre_checks() super().pre_enable() - def run_enable_plans(self) -> None: - """Run plans to enable plugin.""" + def get_tfhelper(self): data_location = self.snap.paths.user_data tfhelper = TerraformHelper( path=self.snap.paths.user_common / "etc" / f"deploy-{self.tfplan}", @@ -154,6 +153,12 @@ def run_enable_plans(self) -> None: backend="http", data_location=data_location, ) + return tfhelper + + def run_enable_plans(self) -> None: + """Run plans to enable plugin.""" + data_location = self.snap.paths.user_data + tfhelper = self.get_tfhelper() jhelper = JujuHelper(data_location) plan = [ TerraformInitStep(tfhelper), @@ -171,12 +176,7 @@ def pre_disable(self) -> None: def run_disable_plans(self) -> None: """Run plans to disable the plugin.""" data_location = self.snap.paths.user_data - tfhelper = TerraformHelper( - path=self.snap.paths.user_common / "etc" / f"deploy-{self.tfplan}", - plan=self._get_plan_name(), - backend="http", - data_location=data_location, - ) + tfhelper = self.get_tfhelper() jhelper = JujuHelper(data_location) plan = [ TerraformInitStep(tfhelper), @@ -284,6 +284,142 @@ def remove_horizon_plugin_from_tfvars(self, plugin: str) -> dict[str, list[str]] return {"horizon-plugins": sorted(horizon_plugins)} + @property + def k8s_application_data(self) -> dict[str, dict[str, str]]: + """Mapping of k8s applications to their required channels. + + { + "": { + "channel": "", + "tfvars_channel_var": ""}, + "": { + "channel": "", + "tfvars_channel_var": ""} + } + """ + return {} + + @property + def machine_application_data(self) -> dict[str, dict[str, str]]: + """Mapping of machine applications to their required channels. + + { + "": { + "channel": "", + "tfvars_channel_var": ""}, + "": { + "channel": "", + "tfvars_channel_var": ""} + } + """ + return {} + + def upgrade_hook(self, upgrade_release: bool = False): + """Run upgrade. + + :param upgrade_release: Whether to upgrade release + """ + data_location = self.snap.paths.user_data + tfhelper = self.get_tfhelper() + jhelper = JujuHelper(data_location) + plan = [ + UpgradeApplicationStep(tfhelper, jhelper, self, upgrade_release), + ] + + run_plan(plan, console) + + +class UpgradeApplicationStep(BaseStep, JujuStepHelper): + def __init__( + self, + tfhelper: TerraformHelper, + jhelper: JujuHelper, + plugin: OpenStackControlPlanePlugin, + upgrade_release: bool = False, + ) -> None: + """Constructor for the generic plan. + + :param tfhelper: Terraform helper pointing to terraform plan + :param jhelper: Juju helper with loaded juju credentials + :param plugin: Plugin that uses this plan to perform callbacks to + plugin. + """ + super().__init__( + f"Refresh OpenStack {plugin.name}", + f"Refresh OpenStack {plugin.name} application", + ) + self.tfhelper = tfhelper + self.jhelper = jhelper + self.plugin = plugin + self.model = OPENSTACK_MODEL + self.upgrade_release = upgrade_release + + def terraform_sync(self, config_key: str, tfvars_delta: dict): + """Sync the running state back to the Terraform state file. + + :param config_key: The config key used to access the data in + microcluster + :param tfvars_delta: The delta of changes to be applied to the + terraform vars stored in microcluster. + """ + self.client = Client() + tfvars = read_config(self.client, config_key) + tfvars.update(tfvars_delta) + update_config(self.client, config_key, tfvars) + self.tfhelper.write_tfvars(tfvars) + self.tfhelper.sync() + + def upgrade_charms( + self, + application_data: dict[str, dict[str, str]], + model: str, + ): + """Upgrade applications. + + :param application_data: Mapping of applications to their required channels + :param model: Name of model applications are in + """ + for application_name, config in application_data.items(): + if self.revision_update_needed(application_name, model): + run_sync(self.jhelper.charm_refresh(application_name, model)) + if self.upgrade_release: + batch = {} + expect_wls = {"workload": ["blocked", "active"]} + for application_name, config in application_data.items(): + current_channel = run_sync( + self.jhelper.get_charm_channel(application_name, model) + ) + new_channel = config["channel"] + LOG.debug( + f"new_channel: {new_channel} current_channel: {current_channel}" + ) + if self.channel_update_needed(current_channel, new_channel): + batch[application_name] = { + "channel": new_channel, + "expected_status": expect_wls, + } + else: + LOG.debug(f"{application_name} no channel upgrade needed") + run_sync(self.jhelper.update_applications_channel(model, batch)) + + def terraform_sync_channel_updates(self, application_data): + for application_name, config in application_data.items(): + if config.get("tfvars_channel_var"): + self.terraform_sync( + self.plugin.get_tfvar_config_key(), + {config["tfvars_channel_var"]: config["channel"]}, + ) + + def run(self, status: Optional[Status] = None) -> Result: + """Run plugin upgrade.""" + self.upgrade_charms(self.plugin.k8s_application_data, "openstack") + if self.upgrade_release: + self.terraform_sync_channel_updates(self.plugin.k8s_application_data) + self.upgrade_charms(self.plugin.machine_application_data, "controller") + if self.upgrade_release: + self.terraform_sync_channel_updates(self.plugin.machine_application_data) + return Result(ResultType.COMPLETED) + class EnableOpenStackApplicationStep(BaseStep, JujuStepHelper): """Generic step to enable OpenStack application using Terraform""" diff --git a/sunbeam-python/sunbeam/versions.py b/sunbeam-python/sunbeam/versions.py new file mode 100644 index 00000000..42e20bee --- /dev/null +++ b/sunbeam-python/sunbeam/versions.py @@ -0,0 +1,78 @@ +# Copyright (c) 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +OPENSTACK_CHANNEL = "2023.2/edge" +OVN_CHANNEL = "23.09/edge" +RABBITMQ_CHANNEL = "3.12/edge" +TRAEFIK_CHANNEL = "1.0/edge" +MICROCEPH_CHANNEL = "latest/edge" +SUNBEAM_MACHINE_CHANNEL = "latest/edge" +MICROK8S_CHANNEL = "legacy/stable" +MYSQL_CHANNEL = "8.0/candidate" +CERT_AUTH_CHANNEL = "latest/beta" + +# The lists of services are needed for switching charm channels outside +# of the terraform provider. If it ok to upgrade in one big-bang and +# the juju terraform provider supports it then the upgrades can be +# done by simply updating the tfvars and these lists are not needed. +OPENSTACK_SERVICES_K8S = { + "cinder-ceph": OPENSTACK_CHANNEL, + "cinder": OPENSTACK_CHANNEL, + "glance": "2023.2/candidate", + "horizon": OPENSTACK_CHANNEL, + "keystone": OPENSTACK_CHANNEL, + "neutron": OPENSTACK_CHANNEL, + "nova": OPENSTACK_CHANNEL, + "placement": OPENSTACK_CHANNEL, +} +OVN_SERVICES_K8S = { + "ovn-central": OVN_CHANNEL, + "ovn-relay": OVN_CHANNEL, +} +MYSQL_SERVICES_K8S = { + "mysql": MYSQL_CHANNEL, + "cinder-ceph-mysql-router": MYSQL_CHANNEL, + "cinder-mysql-router": MYSQL_CHANNEL, + "glance-mysql-router": MYSQL_CHANNEL, + "horizon-mysql-router": MYSQL_CHANNEL, + "keystone-mysql-router": MYSQL_CHANNEL, + "neutron-mysql-router": MYSQL_CHANNEL, + "nova-api-mysql-router": MYSQL_CHANNEL, + "nova-cell-mysql-router": MYSQL_CHANNEL, + "nova-mysql-router": MYSQL_CHANNEL, + "placement-mysql-router": MYSQL_CHANNEL, +} +MISC_SERVICES_K8S = { + "certificate-authority": CERT_AUTH_CHANNEL, + "rabbitmq": RABBITMQ_CHANNEL, + "traefik": TRAEFIK_CHANNEL, + "traefik-public": TRAEFIK_CHANNEL, +} +MACHINE_SERVICES = { + "microceph": MICROCEPH_CHANNEL, + "microk8s": MICROK8S_CHANNEL, + "openstack-hypervisor": OPENSTACK_CHANNEL, + "sunbeam-machine": SUNBEAM_MACHINE_CHANNEL, +} + +K8S_SERVICES = {} +K8S_SERVICES |= OPENSTACK_SERVICES_K8S +K8S_SERVICES |= OVN_SERVICES_K8S +K8S_SERVICES |= MYSQL_SERVICES_K8S +K8S_SERVICES |= MISC_SERVICES_K8S + +CHARM_VERSIONS = {} +CHARM_VERSIONS |= K8S_SERVICES +CHARM_VERSIONS |= MACHINE_SERVICES diff --git a/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py b/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py index 33f64e02..0b2843a0 100644 --- a/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py +++ b/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py @@ -51,6 +51,90 @@ def mock_open(): yield p +class TestJujuStepHelper: + def test_get_available_charm_revision(self, check_output, mocker): + jsh = juju.JujuStepHelper() + cmd_out = { + "channels": { + "legacy": { + "edge": [ + { + "track": "legacy", + "risk": "stable", + "version": "121", + "revision": 121, + "released-at": "2023-08-17T12:06:46.206939+00:00", + "size": 7074285, + "architectures": ["amd64"], + "bases": [ + {"name": "ubuntu", "channel": "20.04"}, + {"name": "ubuntu", "channel": "22.04"}, + ], + } + ] + } + } + } + + with patch.object(juju.JujuStepHelper, "_juju_cmd", return_value=cmd_out): + assert jsh.get_available_charm_revision("microk8s", "legacy", "edge") == 121 + + def test_revision_update_needed(self, mocker): + jsh = juju.JujuStepHelper() + CHARMREV = {"nova-k8s": 31, "cinder-k8s": 51, "another-k8s": 70} + + def _get_available_charm_revision(charm_name, track, risk): + return CHARMREV[charm_name] + + _status = { + "applications": { + "nova": { + "charm": "ch:amd64/jammy/nova-k8s-30", + "charm-channel": "2023.2/edge/gnuoy", + }, + "cinder": { + "charm": "ch:amd64/jammy/cinder-k8s-50", + "charm-channel": "2023.2/edge", + }, + "another": { + "charm": "ch:amd64/jammy/another-k8s-70", + "charm-channel": "edge", + }, + } + } + with patch.object( + juju.JujuStepHelper, + "get_available_charm_revision", + side_effect=_get_available_charm_revision, + ): + assert jsh.revision_update_needed("cinder", "openstack", _status) + assert not jsh.revision_update_needed("nova", "openstack", _status) + assert not jsh.revision_update_needed("another", "openstack", _status) + + def test_normalise_channel(self): + jsh = juju.JujuStepHelper() + assert jsh.normalise_channel("2023.2/edge") == "2023.2/edge" + assert jsh.normalise_channel("edge") == "latest/edge" + + def test_extract_charm_name(self): + jsh = juju.JujuStepHelper() + assert jsh._extract_charm_name("ch:amd64/jammy/cinder-k8s-50") == "cinder-k8s" + + def test_extract_charm_revision(self): + jsh = juju.JujuStepHelper() + assert jsh._extract_charm_revision("ch:amd64/jammy/cinder-k8s-50") == "50" + + def test_channel_update_needed(self): + jsh = juju.JujuStepHelper() + assert jsh.channel_update_needed("2023.1/stable", "2023.2/stable") + assert jsh.channel_update_needed("2023.1/stable", "2023.1/edge") + assert jsh.channel_update_needed("latest/stable", "latest/edge") + assert not jsh.channel_update_needed("2023.1/stable", "2023.1/stable") + assert not jsh.channel_update_needed("2023.2/stable", "2023.1/stable") + assert not jsh.channel_update_needed("latest/stable", "latest/stable") + assert not jsh.channel_update_needed("foo/stable", "ba/stable") + + class TestWriteJujuStatusStep: def test_is_skip(self, jhelper): with tempfile.NamedTemporaryFile() as tmpfile: diff --git a/sunbeam-python/tests/unit/sunbeam/commands/upgrades/test_base.py b/sunbeam-python/tests/unit/sunbeam/commands/upgrades/test_base.py new file mode 100644 index 00000000..f1de50f2 --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/commands/upgrades/test_base.py @@ -0,0 +1,94 @@ +# Copyright 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest.mock import AsyncMock, Mock, patch + +from sunbeam.commands.upgrades.inter_channel import BaseUpgrade +from sunbeam.versions import ( + MYSQL_SERVICES_K8S, + OPENSTACK_SERVICES_K8S, + OVN_SERVICES_K8S, +) + + +class TestBaseUpgrade: + def setup_method(self): + self.jhelper = AsyncMock() + self.tfhelper = Mock() + self.upgrade_service = ( + list(MYSQL_SERVICES_K8S.keys()) # noqa + + list(OVN_SERVICES_K8S.keys()) # noqa + + list(OPENSTACK_SERVICES_K8S.keys()) # noqa + ) + + def test_upgrade_applications(self): + def _get_new_channel_mock(app_name, model): + channels = {"nova": "2023.2/edge", "neutron": None} + return channels[app_name] + + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + get_new_channel_mock = Mock() + get_new_channel_mock.side_effect = _get_new_channel_mock + with patch.object(BaseUpgrade, "get_new_channel", get_new_channel_mock): + upgrader.upgrade_applications(["nova"], "openstack") + self.jhelper.update_applications_channel.assert_called_once_with( + "openstack", + { + "nova": { + "channel": "2023.2/edge", + "expected_status": {"workload": ["blocked", "active"]}, + } + }, + ) + + def test_get_new_channel_os_service(self, mocker): + self.jhelper.get_charm_channel.return_value = "2023.1/edge" + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + new_channel = upgrader.get_new_channel("cinder", "openstack") + assert new_channel == "2023.2/edge" + + def test_get_new_channel_os_service_same(self, mocker): + self.jhelper.get_charm_channel.return_value = "2023.2/edge" + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + new_channel = upgrader.get_new_channel("cinder", "openstack") + assert new_channel is None + + def test_get_new_channel_os_downgrade(self, mocker): + self.jhelper.get_charm_channel.return_value = "2023.2/edge" + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + new_channel = upgrader.get_new_channel("cinder", "openstack") + assert new_channel is None + + def test_get_new_channel_nonos_service(self, mocker): + self.jhelper.get_charm_channel.return_value = "3.8/stable" + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + new_channel = upgrader.get_new_channel("rabbitmq", "openstack") + assert new_channel == "3.12/edge" + + def test_get_new_channel_unknown(self, mocker): + upgrader = BaseUpgrade( + "test name", "test description", self.jhelper, self.tfhelper, "openstack" + ) + new_channel = upgrader.get_new_channel("foo", "openstack") + assert new_channel is None diff --git a/sunbeam-python/tests/unit/sunbeam/conftest.py b/sunbeam-python/tests/unit/sunbeam/conftest.py index 638ce202..5f2ce4e4 100644 --- a/sunbeam-python/tests/unit/sunbeam/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/conftest.py @@ -58,6 +58,12 @@ def check_call(): yield p +@pytest.fixture +def check_output(): + with patch("subprocess.check_output") as p: + yield p + + @pytest.fixture def environ(): with patch("os.environ") as p: From 811a3fabc27d8763db98ed0976986c381dafd7a0 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 8 Dec 2023 11:36:42 +0000 Subject: [PATCH 03/10] Misc fixes after review feedback --- sunbeam-python/sunbeam/commands/juju.py | 38 ++------- .../sunbeam/commands/upgrades/base.py | 2 +- .../commands/upgrades/inter_channel.py | 83 ++++++++++--------- sunbeam-python/sunbeam/jobs/juju.py | 39 +++++++-- sunbeam-python/sunbeam/plugins/dns/plugin.py | 17 ++-- .../sunbeam/plugins/interface/v1/openstack.py | 57 ++++++------- .../tests/unit/sunbeam/commands/test_juju.py | 44 ++-------- .../tests/unit/sunbeam/jobs/test_juju.py | 15 +++- 8 files changed, 143 insertions(+), 152 deletions(-) diff --git a/sunbeam-python/sunbeam/commands/juju.py b/sunbeam-python/sunbeam/commands/juju.py index 6d0b74e6..3626fc4e 100644 --- a/sunbeam-python/sunbeam/commands/juju.py +++ b/sunbeam-python/sunbeam/commands/juju.py @@ -22,7 +22,7 @@ import subprocess import tempfile from pathlib import Path -from typing import Optional, Union +from typing import Optional import pexpect import pwgen @@ -172,29 +172,8 @@ def add_cloud(self, cloud_type: str, cloud_name: str) -> bool: return True - def get_available_charm_revision( - self, charm_name: str, track: str, risk: str, arch: str = "amd64" - ) -> int: - """Find the latest available revision of a charm in a given channel - - :param charm_name: Name of charm to look up - :param track: Track of charm - :param risk: Risk of charm - :param arch: Arch of charm - """ - available_charm_data = self._juju_cmd(*["info", charm_name]) - channel_data = [ - d - for d in available_charm_data["channels"][track][risk] - if arch in d["architectures"] - ] - assert len(channel_data) == 1, "Unexpected candidate charms {}".format( - len(channel_data) - ) - return int(channel_data[0]["revision"]) - def revision_update_needed( - self, application_name: str, model: str, status: Union[dict, None] = None + self, application_name: str, model: str, status: dict | None = None ) -> bool: """Check if a revision update is available for an applicaton. @@ -212,15 +191,14 @@ def revision_update_needed( deployed_revision = int(self._extract_charm_revision(app_status["charm"])) charm_name = self._extract_charm_name(app_status["charm"]) deployed_channel = self.normalise_channel(app_status["charm-channel"]) - track = deployed_channel.split("/")[0] - risk = deployed_channel.split("/")[1] - try: - deployed_channel.split("/")[2] + if len(deployed_channel.split("/")) > 2: LOG.debug(f"Cannot calculate upgrade for {application_name}, branch in use") return False - except IndexError: - pass - available_revision = self.get_available_charm_revision(charm_name, track, risk) + available_revision = run_sync( + self.jhelper.get_available_charm_revision( + model, charm_name, deployed_channel + ) + ) return bool(available_revision > deployed_revision) def normalise_channel(self, channel: str) -> str: diff --git a/sunbeam-python/sunbeam/commands/upgrades/base.py b/sunbeam-python/sunbeam/commands/upgrades/base.py index ee5deb48..da31c827 100644 --- a/sunbeam-python/sunbeam/commands/upgrades/base.py +++ b/sunbeam-python/sunbeam/commands/upgrades/base.py @@ -55,7 +55,7 @@ def run(self, status: Optional[Status] = None) -> Result: class UpgradeCoordinator: def __init__( - self, jhelper: JujuHelper, tfhelper: TerraformHelper, channel: str = None + self, jhelper: JujuHelper, tfhelper: TerraformHelper, channel: str | None = None ): """Upgrade coordinator. diff --git a/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py b/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py index b5768d11..54dacf4a 100644 --- a/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py +++ b/sunbeam-python/sunbeam/commands/upgrades/inter_channel.py @@ -14,7 +14,7 @@ # limitations under the License. import logging -from typing import Callable, Dict, List, Optional, Union +from typing import Callable, Dict, List, Optional, TypedDict, Union from rich.console import Console from rich.status import Status @@ -32,7 +32,7 @@ run_plan, update_config, ) -from sunbeam.jobs.juju import JujuHelper, run_sync +from sunbeam.jobs.juju import ChannelUpdate, JujuHelper, run_sync from sunbeam.versions import ( CHARM_VERSIONS, MACHINE_SERVICES, @@ -45,6 +45,28 @@ console = Console() +class UpgradeStrategy(TypedDict): + """A strategy for upgrading applications. + + The strategy is a list of dicts. Each dict consists of: + { + "upgrade_f": f, + "applications": [apps] + }, + + upgrade_f is the function to be applied to each application (in + parallel) to perform the upgrade. + + applications is a list of applications that can be upgraded in parallel. + + Currently only apps that are upgraded with the same function can be + grouped together. + """ + + upgrade_f: Callable[[list[str], str], None] + applications: list[str] + + class BaseUpgrade(BaseStep, JujuStepHelper): def __init__(self, name, description, jhelper, tfhelper, model): """Create instance of BaseUpgrade class. @@ -58,29 +80,15 @@ def __init__(self, name, description, jhelper, tfhelper, model): self.tfhelper = tfhelper self.model = model - def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: - """Return a strategy for performing the upgrade. - - The strategy is a list of dicts. Each dict consists of: - { - "upgrade_f": f, - "applications": [apps] - }, + def get_upgrade_strategy_steps(self) -> UpgradeStrategy: + """Return a strategy for performing the upgrade.""" - upgrade_f is the function to be applied to each application (in - parallel) to perform the upgrade. - - applications is a list of applications that can be upgraded in parallel. - - Currently only apps that are upgraded with the same function can be - grouped together. - """ raise NotImplementedError def run(self, status: Optional[Status] = None) -> Result: """Run control plane and machine charm upgrade.""" self.pre_upgrade_tasks() - for step in self.get_upgrade_strategy(): + for step in self.get_upgrade_strategy_steps(): step["upgrade_f"](step["applications"], self.model) self.post_upgrade_tasks() return Result(ResultType.COMPLETED) @@ -97,7 +105,7 @@ def upgrade_applications( self, application_list: List[str], model: str, - expect_wls: Optional[Dict[str, str]] = None, + expect_wls: Optional[Dict[str, list[str]]] = None, ) -> None: """Upgrade applications. @@ -112,10 +120,10 @@ def upgrade_applications( new_channel = self.get_new_channel(app_name, model) if new_channel: LOG.debug(f"Upgrade needed for {app_name}") - batch[app_name] = { - "channel": new_channel, - "expected_status": expect_wls, - } + batch[app_name] = ChannelUpdate( + channel=new_channel, + expected_status=expect_wls, + ) else: LOG.debug(f"{app_name} no channel upgrade needed") run_sync(self.jhelper.update_applications_channel(model, batch)) @@ -174,20 +182,20 @@ def __init__(self, jhelper, tfhelper, model): model, ) - def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: + def get_upgrade_strategy_steps(self) -> List[Dict[str, Union[Callable, List]]]: """Return a strategy for performing the upgrade. Upgrade all control plane applications in parallel. """ - upgrade_strategy = [ - { - "upgrade_f": self.upgrade_applications, - "applications": list(MISC_SERVICES_K8S.keys()) + upgrade_strategy_steps = [ + UpgradeStrategy( + upgrade_f=self.upgrade_applications, + applications=list(MISC_SERVICES_K8S.keys()) + list(OVN_SERVICES_K8S.keys()) # noqa + list(OPENSTACK_SERVICES_K8S.keys()), # noqa - }, + ), ] - return upgrade_strategy + return upgrade_strategy_steps def post_upgrade_tasks(self) -> None: """Update channels in terraform vars db.""" @@ -224,18 +232,17 @@ def __init__(self, jhelper, tfhelper, model): model, ) - def get_upgrade_strategy(self) -> List[Dict[str, Union[Callable, List]]]: + def get_upgrade_strategy_steps(self) -> List[Dict[str, Union[Callable, List]]]: """Return a strategy for performing the upgrade. Upgrade all machine applications in parallel. """ - upgrade_strategy = [ - { - "upgrade_f": self.upgrade_applications, - "applications": MACHINE_SERVICES, - }, + upgrade_strategy_steps = [ + UpgradeStrategy( + upgrade_f=self.upgrade_applications, applications=MACHINE_SERVICES + ), ] - return upgrade_strategy + return upgrade_strategy_steps def post_upgrade_tasks(self) -> None: """Update channels in terraform vars db.""" diff --git a/sunbeam-python/sunbeam/jobs/juju.py b/sunbeam-python/sunbeam/jobs/juju.py index 6ea709c5..626e087a 100644 --- a/sunbeam-python/sunbeam/jobs/juju.py +++ b/sunbeam-python/sunbeam/jobs/juju.py @@ -21,12 +21,13 @@ from datetime import datetime from functools import wraps from pathlib import Path -from typing import Awaitable, Dict, List, Optional, TypeVar, cast +from typing import Awaitable, Dict, List, Optional, TypedDict, TypeVar, cast import pytz import yaml from juju import utils as juju_utils from juju.application import Application +from juju.charmhub import CharmHub from juju.client import client as jujuClient from juju.controller import Controller from juju.errors import ( @@ -138,6 +139,20 @@ class UnsupportedKubeconfigException(JujuException): pass +class ChannelUpdate(TypedDict): + """Channel Update step. + + Defines a channel that needs updating to and the expected + state of the charm afterwards. + + channel: Channel to upgrade to + expected_status: map of accepted statuses for "workload" and "agent" + """ + + channel: str + expected_status: Dict[str, List[str]] + + @dataclass class JujuAccount: user: str @@ -721,7 +736,7 @@ async def set_application_config(self, model: str, app: str, config: dict): async def update_applications_channel( self, model: str, - updates: Dict, + updates: Dict[str, ChannelUpdate], timeout: Optional[int] = None, ): """Upgrade charm to new channel @@ -750,11 +765,6 @@ def condition() -> bool: ) for unit in _app.units: statuses[unit.entity_id] = bool(unit.agent_status_since > timestamp) - if not unit.agent_status_since > timestamp: - LOG.debug( - f"Waiting on {unit.entity_id}: Upgrade start {timestamp} " - f"WLS update {unit.agent_status_since}" - ) return all(statuses.values()) try: @@ -797,3 +807,18 @@ async def charm_refresh(self, application_name: str, model: str): """ app = await self.get_application(application_name, model) await app.refresh() + + @controller + async def get_available_charm_revision( + self, model: str, charm_name: str, channel: str + ) -> int: + """Find the latest available revision of a charm in a given channel + + :param model: Name of model + :param charm_name: Name of charm to look up + :param channel: Channel to lookup charm in + """ + model_impl = await self.get_model(model) + available_charm_data = await CharmHub(model_impl).info(charm_name, channel) + version = available_charm_data["channel-map"][channel]["revision"]["version"] + return int(version) diff --git a/sunbeam-python/sunbeam/plugins/dns/plugin.py b/sunbeam-python/sunbeam/plugins/dns/plugin.py index ba0cb5a4..c071ffcf 100644 --- a/sunbeam-python/sunbeam/plugins/dns/plugin.py +++ b/sunbeam-python/sunbeam/plugins/dns/plugin.py @@ -26,6 +26,7 @@ from sunbeam.jobs.common import run_plan from sunbeam.jobs.juju import JujuHelper, run_sync from sunbeam.plugins.interface.v1.openstack import ( + ApplicationChannelData, EnableOpenStackApplicationStep, OpenStackControlPlanePlugin, TerraformPlanLocation, @@ -176,14 +177,14 @@ def commands(self) -> dict: @property def k8s_application_data(self): return { - "designate": { - "channel": OPENSTACK_CHANNEL, - "tfvars_channel_var": None, - }, - "bind": { - "channel": "9/edge", - "tfvars_channel_var": None, - }, + "designate": ApplicationChannelData( + channel=OPENSTACK_CHANNEL, + tfvars_channel_var=None, + ), + "bind": ApplicationChannelData( + channel="9/edge", + tfvars_channel_var=None, + ), } @property diff --git a/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py b/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py index 21ec5331..c5e1c695 100644 --- a/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py +++ b/sunbeam-python/sunbeam/plugins/interface/v1/openstack.py @@ -20,7 +20,7 @@ from abc import abstractmethod from enum import Enum from pathlib import Path -from typing import Optional +from typing import Optional, TypedDict import click from packaging.version import Version @@ -51,7 +51,13 @@ run_preflight_checks, update_config, ) -from sunbeam.jobs.juju import JujuHelper, JujuWaitException, TimeoutException, run_sync +from sunbeam.jobs.juju import ( + ChannelUpdate, + JujuHelper, + JujuWaitException, + TimeoutException, + run_sync, +) from sunbeam.plugins.interface.v1.base import EnableDisablePlugin LOG = logging.getLogger(__name__) @@ -62,6 +68,17 @@ OPENSTACK_TERRAFORM_PLAN = "openstack" +class ApplicationChannelData(TypedDict): + """Application channel data. + + channel is the charm channel that is inline with this snap + tfvars_channel_var is the terraform variable used to store the channel. + """ + + channel: str + tfvars_channel_var: str | None + + class TerraformPlanLocation(Enum): """Enum to define Terraform plan location @@ -285,33 +302,13 @@ def remove_horizon_plugin_from_tfvars(self, plugin: str) -> dict[str, list[str]] return {"horizon-plugins": sorted(horizon_plugins)} @property - def k8s_application_data(self) -> dict[str, dict[str, str]]: - """Mapping of k8s applications to their required channels. - - { - "": { - "channel": "", - "tfvars_channel_var": ""}, - "": { - "channel": "", - "tfvars_channel_var": ""} - } - """ + def k8s_application_data(self) -> dict[str, ApplicationChannelData]: + """Mapping of k8s applications to their required channels.""" return {} @property - def machine_application_data(self) -> dict[str, dict[str, str]]: - """Mapping of machine applications to their required channels. - - { - "": { - "channel": "", - "tfvars_channel_var": ""}, - "": { - "channel": "", - "tfvars_channel_var": ""} - } - """ + def machine_application_data(self) -> dict[str, ApplicationChannelData]: + """Mapping of machine applications to their required channels.""" return {} def upgrade_hook(self, upgrade_release: bool = False): @@ -394,10 +391,10 @@ def upgrade_charms( f"new_channel: {new_channel} current_channel: {current_channel}" ) if self.channel_update_needed(current_channel, new_channel): - batch[application_name] = { - "channel": new_channel, - "expected_status": expect_wls, - } + batch[application_name] = ChannelUpdate( + channel=new_channel, + expected_status=expect_wls, + ) else: LOG.debug(f"{application_name} no channel upgrade needed") run_sync(self.jhelper.update_applications_channel(model, batch)) diff --git a/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py b/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py index 0b2843a0..7523d0e3 100644 --- a/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py +++ b/sunbeam-python/tests/unit/sunbeam/commands/test_juju.py @@ -52,38 +52,12 @@ def mock_open(): class TestJujuStepHelper: - def test_get_available_charm_revision(self, check_output, mocker): - jsh = juju.JujuStepHelper() - cmd_out = { - "channels": { - "legacy": { - "edge": [ - { - "track": "legacy", - "risk": "stable", - "version": "121", - "revision": 121, - "released-at": "2023-08-17T12:06:46.206939+00:00", - "size": 7074285, - "architectures": ["amd64"], - "bases": [ - {"name": "ubuntu", "channel": "20.04"}, - {"name": "ubuntu", "channel": "22.04"}, - ], - } - ] - } - } - } - - with patch.object(juju.JujuStepHelper, "_juju_cmd", return_value=cmd_out): - assert jsh.get_available_charm_revision("microk8s", "legacy", "edge") == 121 - - def test_revision_update_needed(self, mocker): + def test_revision_update_needed(self, jhelper): jsh = juju.JujuStepHelper() + jsh.jhelper = jhelper CHARMREV = {"nova-k8s": 31, "cinder-k8s": 51, "another-k8s": 70} - def _get_available_charm_revision(charm_name, track, risk): + def _get_available_charm_revision(model, charm_name, deployed_channel): return CHARMREV[charm_name] _status = { @@ -102,14 +76,10 @@ def _get_available_charm_revision(charm_name, track, risk): }, } } - with patch.object( - juju.JujuStepHelper, - "get_available_charm_revision", - side_effect=_get_available_charm_revision, - ): - assert jsh.revision_update_needed("cinder", "openstack", _status) - assert not jsh.revision_update_needed("nova", "openstack", _status) - assert not jsh.revision_update_needed("another", "openstack", _status) + jhelper.get_available_charm_revision.side_effect = _get_available_charm_revision + assert jsh.revision_update_needed("cinder", "openstack", _status) + assert not jsh.revision_update_needed("nova", "openstack", _status) + assert not jsh.revision_update_needed("another", "openstack", _status) def test_normalise_channel(self): jsh = juju.JujuStepHelper() diff --git a/sunbeam-python/tests/unit/sunbeam/jobs/test_juju.py b/sunbeam-python/tests/unit/sunbeam/jobs/test_juju.py index def395b4..f7f93959 100644 --- a/sunbeam-python/tests/unit/sunbeam/jobs/test_juju.py +++ b/sunbeam-python/tests/unit/sunbeam/jobs/test_juju.py @@ -14,7 +14,7 @@ import asyncio from pathlib import Path -from unittest.mock import AsyncMock, MagicMock, Mock +from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest import yaml @@ -552,3 +552,16 @@ async def test_jhelper_wait_until_active_timed_out(jhelper: juju.JujuHelper, mod ): await jhelper.wait_until_active("control-plane") assert model.wait_for_idle.call_count == 1 + + +@pytest.mark.asyncio +async def test_get_available_charm_revision(jhelper: juju.JujuHelper, model): + cmd_out = {"channel-map": {"legacy/edge": {"revision": {"version": "121"}}}} + with patch.object(juju, "CharmHub") as p: + charmhub = AsyncMock() + charmhub.info.return_value = cmd_out + p.return_value = charmhub + revno = await jhelper.get_available_charm_revision( + "openstack", "microk8s", "legacy/edge" + ) + assert revno == 121 From 7f878d9b2db38027f92e4ad215fa9d76d20be08e Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 8 Dec 2023 11:38:25 +0000 Subject: [PATCH 04/10] Remove glance version override --- sunbeam-python/sunbeam/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunbeam-python/sunbeam/versions.py b/sunbeam-python/sunbeam/versions.py index 42e20bee..1b2cf2b4 100644 --- a/sunbeam-python/sunbeam/versions.py +++ b/sunbeam-python/sunbeam/versions.py @@ -30,7 +30,7 @@ OPENSTACK_SERVICES_K8S = { "cinder-ceph": OPENSTACK_CHANNEL, "cinder": OPENSTACK_CHANNEL, - "glance": "2023.2/candidate", + "glance": OPENSTACK_CHANNEL, "horizon": OPENSTACK_CHANNEL, "keystone": OPENSTACK_CHANNEL, "neutron": OPENSTACK_CHANNEL, From 48f9016df4c178a085a1e206c512221d2b8bd80b Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Wed, 13 Dec 2023 16:25:18 +0100 Subject: [PATCH 05/10] Avoid broken macaroon, uncomment protobuf constraint pymacaroon-bakery removed upper constraints on protobuf. Avoid broken version and follow Openstack upper constraints. Add upper constraints to tox's venv. --- sunbeam-python/requirements.txt | 2 +- sunbeam-python/tox.ini | 1 + sunbeam-python/upper-constraints.txt | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sunbeam-python/requirements.txt b/sunbeam-python/requirements.txt index 532a9e89..0b417c72 100644 --- a/sunbeam-python/requirements.txt +++ b/sunbeam-python/requirements.txt @@ -37,4 +37,4 @@ jsonschema GitPython # Regression introduced in 1.3.3 -macaroonbakery<1.3.3 +macaroonbakery!=1.3.3 diff --git a/sunbeam-python/tox.ini b/sunbeam-python/tox.ini index d4f1e842..48ef611f 100644 --- a/sunbeam-python/tox.ini +++ b/sunbeam-python/tox.ini @@ -20,6 +20,7 @@ setenv = OS_STDOUT_CAPTURE=1 deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt + -c{toxinidir}/upper-constraints.txt commands = python -m pytest {posargs} allowlist_externals = stestr diff --git a/sunbeam-python/upper-constraints.txt b/sunbeam-python/upper-constraints.txt index 2db959d9..ea311166 100644 --- a/sunbeam-python/upper-constraints.txt +++ b/sunbeam-python/upper-constraints.txt @@ -370,8 +370,7 @@ linecache2===1.0.0 oauth2client===4.1.3 idna===3.4 yamlloader===1.1.0 -# Disable due to version conflict with macaroonbakery -# protobuf===4.21.7 +protobuf===4.21.7 pyhcl===0.4.4 sushy===4.4.2 python-neutronclient===9.0.0 From 2bf6e0585f1a09f0c1f8f796f01b9cb6130e216e Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 12 Jan 2024 10:22:53 +0000 Subject: [PATCH 06/10] microk8s: Disable certificate verification We've had numerous issues with TLS certificate verification during MicroK8S cluster join operations. As MicroK8S is due to be swapped out for a new version based on microcluster which will work in a different way lets disable verification for now - this will be re-instated when we switch to the replatformed K8S. Closes-Bug: LP:2045670 --- cloud/etc/deploy-microk8s/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/etc/deploy-microk8s/main.tf b/cloud/etc/deploy-microk8s/main.tf index 60cad3ff..06bc531e 100644 --- a/cloud/etc/deploy-microk8s/main.tf +++ b/cloud/etc/deploy-microk8s/main.tf @@ -47,5 +47,6 @@ resource "juju_application" "microk8s" { addons = join(" ", [for key, value in var.addons : "${key}:${value}"]) disable_cert_reissue = true kubelet_serialize_image_pulls = false + skip_verify = true } } From 165c9518858a9bf61cc3b17359c32f6dd73c7545 Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Mon, 15 Jan 2024 17:14:17 +0100 Subject: [PATCH 07/10] Repository has been moved from openstack-snaps to canonical --- sunbeam-microcluster/api/config.go | 2 +- sunbeam-microcluster/api/jujuuser.go | 4 ++-- sunbeam-microcluster/api/nodes.go | 4 ++-- sunbeam-microcluster/api/terraform.go | 2 +- sunbeam-microcluster/cmd/sunbeamd/main.go | 6 +++--- sunbeam-microcluster/go.mod | 2 +- sunbeam-microcluster/sunbeam/config.go | 2 +- sunbeam-microcluster/sunbeam/jujuuser.go | 4 ++-- sunbeam-microcluster/sunbeam/nodes.go | 4 ++-- sunbeam-microcluster/sunbeam/terraform.go | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/sunbeam-microcluster/api/config.go b/sunbeam-microcluster/api/config.go index 810dc157..519d54fb 100644 --- a/sunbeam-microcluster/api/config.go +++ b/sunbeam-microcluster/api/config.go @@ -11,7 +11,7 @@ import ( "github.com/canonical/microcluster/state" "github.com/gorilla/mux" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/sunbeam" + "github.com/canonical/snap-openstack/sunbeam-microcluster/sunbeam" ) // /1.0/config/ endpoint. diff --git a/sunbeam-microcluster/api/jujuuser.go b/sunbeam-microcluster/api/jujuuser.go index 683aeee3..daba4d7a 100644 --- a/sunbeam-microcluster/api/jujuuser.go +++ b/sunbeam-microcluster/api/jujuuser.go @@ -11,8 +11,8 @@ import ( "github.com/canonical/microcluster/state" "github.com/gorilla/mux" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api/types" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/sunbeam" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api/types" + "github.com/canonical/snap-openstack/sunbeam-microcluster/sunbeam" ) // /1.0/jujuusers endpoint. diff --git a/sunbeam-microcluster/api/nodes.go b/sunbeam-microcluster/api/nodes.go index effc5b1e..db9d8f46 100644 --- a/sunbeam-microcluster/api/nodes.go +++ b/sunbeam-microcluster/api/nodes.go @@ -11,8 +11,8 @@ import ( "github.com/canonical/microcluster/state" "github.com/gorilla/mux" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api/types" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/sunbeam" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api/types" + "github.com/canonical/snap-openstack/sunbeam-microcluster/sunbeam" ) // /1.0/nodes endpoint. diff --git a/sunbeam-microcluster/api/terraform.go b/sunbeam-microcluster/api/terraform.go index c828ac65..70f0b63c 100644 --- a/sunbeam-microcluster/api/terraform.go +++ b/sunbeam-microcluster/api/terraform.go @@ -13,7 +13,7 @@ import ( "github.com/canonical/microcluster/state" "github.com/gorilla/mux" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/sunbeam" + "github.com/canonical/snap-openstack/sunbeam-microcluster/sunbeam" ) // /1.0/terraformstate endpoint. diff --git a/sunbeam-microcluster/cmd/sunbeamd/main.go b/sunbeam-microcluster/cmd/sunbeamd/main.go index 561dc7d4..5b8a9114 100644 --- a/sunbeam-microcluster/cmd/sunbeamd/main.go +++ b/sunbeam-microcluster/cmd/sunbeamd/main.go @@ -13,9 +13,9 @@ import ( "github.com/canonical/microcluster/state" "github.com/spf13/cobra" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/database" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/version" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api" + "github.com/canonical/snap-openstack/sunbeam-microcluster/database" + "github.com/canonical/snap-openstack/sunbeam-microcluster/version" ) // Debug indicates whether to log debug messages or not. diff --git a/sunbeam-microcluster/go.mod b/sunbeam-microcluster/go.mod index c700875a..6cf83c95 100644 --- a/sunbeam-microcluster/go.mod +++ b/sunbeam-microcluster/go.mod @@ -1,4 +1,4 @@ -module github.com/openstack-snaps/snap-openstack/sunbeam-microcluster +module github.com/canonical/snap-openstack/sunbeam-microcluster go 1.18 diff --git a/sunbeam-microcluster/sunbeam/config.go b/sunbeam-microcluster/sunbeam/config.go index b4196226..b9a162dc 100644 --- a/sunbeam-microcluster/sunbeam/config.go +++ b/sunbeam-microcluster/sunbeam/config.go @@ -9,7 +9,7 @@ import ( "github.com/canonical/microcluster/state" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/database" + "github.com/canonical/snap-openstack/sunbeam-microcluster/database" ) // GetConfig returns the ConfigItem based on key from the database diff --git a/sunbeam-microcluster/sunbeam/jujuuser.go b/sunbeam-microcluster/sunbeam/jujuuser.go index a824969c..45e5b444 100644 --- a/sunbeam-microcluster/sunbeam/jujuuser.go +++ b/sunbeam-microcluster/sunbeam/jujuuser.go @@ -7,8 +7,8 @@ import ( "github.com/canonical/microcluster/state" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api/types" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/database" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api/types" + "github.com/canonical/snap-openstack/sunbeam-microcluster/database" ) // ListJujuUsers returns the jujuusers from the database diff --git a/sunbeam-microcluster/sunbeam/nodes.go b/sunbeam-microcluster/sunbeam/nodes.go index 31fae099..640e73b3 100644 --- a/sunbeam-microcluster/sunbeam/nodes.go +++ b/sunbeam-microcluster/sunbeam/nodes.go @@ -9,8 +9,8 @@ import ( "github.com/canonical/microcluster/state" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api/types" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/database" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api/types" + "github.com/canonical/snap-openstack/sunbeam-microcluster/database" ) // ListNodes return all the nodes, filterable by role (Optional) diff --git a/sunbeam-microcluster/sunbeam/terraform.go b/sunbeam-microcluster/sunbeam/terraform.go index 3385e191..febf695b 100644 --- a/sunbeam-microcluster/sunbeam/terraform.go +++ b/sunbeam-microcluster/sunbeam/terraform.go @@ -8,7 +8,7 @@ import ( "github.com/canonical/lxd/shared/api" "github.com/canonical/microcluster/state" - "github.com/openstack-snaps/snap-openstack/sunbeam-microcluster/api/types" + "github.com/canonical/snap-openstack/sunbeam-microcluster/api/types" ) const tfstatePrefix = "tfstate-" From 1a3ed507b5db48d9134b61f78e70a09cd1bf2835 Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Mon, 15 Jan 2024 17:18:09 +0100 Subject: [PATCH 08/10] Upgrade golang version to latest supported 1.19 is not supported anymore, upgrade to 1.21 (which was the version already to build inside the snap) Pin the snap version to 1.21/stable --- .github/workflows/build-golang.yml | 2 +- .github/workflows/test-golang.yml | 4 ++-- snap/snapcraft.yaml | 2 +- sunbeam-microcluster/README.md | 2 +- sunbeam-microcluster/go.mod | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-golang.yml b/.github/workflows/build-golang.yml index d7e1bf82..969a773c 100644 --- a/.github/workflows/build-golang.yml +++ b/.github/workflows/build-golang.yml @@ -13,7 +13,7 @@ jobs: - name: Setup GO uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.21' - name: Install dependencies run: | sudo add-apt-repository -y ppa:dqlite/dev diff --git a/.github/workflows/test-golang.yml b/.github/workflows/test-golang.yml index 47ded432..59682425 100644 --- a/.github/workflows/test-golang.yml +++ b/.github/workflows/test-golang.yml @@ -13,7 +13,7 @@ jobs: - name: Setup GO uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.21' - name: Install dependencies run: | sudo add-apt-repository -y ppa:dqlite/dev @@ -31,7 +31,7 @@ jobs: - name: Setup GO uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.21' - name: Install dependencies run: | sudo add-apt-repository -y ppa:dqlite/dev diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 758db163..74ed8363 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -110,7 +110,7 @@ parts: source: ./sunbeam-microcluster source-type: local build-snaps: - - go + - go/1.21/stable build-environment: - GOFLAGS: "-mod=readonly" override-build: | diff --git a/sunbeam-microcluster/README.md b/sunbeam-microcluster/README.md index 224ab809..8576b3ab 100644 --- a/sunbeam-microcluster/README.md +++ b/sunbeam-microcluster/README.md @@ -11,7 +11,7 @@ This requires a few dependencies to be installed: sudo add-apt-repository -y ppa:dqlite/dev sudo apt install gcc make dqlite-tools libdqlite-dev libraft-dev -y - sudo snap install --channel 1.19 --classic go + sudo snap install --channel 1.21 --classic go after which is possible to build sunbeam-microcluster: diff --git a/sunbeam-microcluster/go.mod b/sunbeam-microcluster/go.mod index 6cf83c95..7bc82599 100644 --- a/sunbeam-microcluster/go.mod +++ b/sunbeam-microcluster/go.mod @@ -1,6 +1,6 @@ module github.com/canonical/snap-openstack/sunbeam-microcluster -go 1.18 +go 1.21 require ( github.com/canonical/lxd v0.0.0-20230705090120-570f7071eeb2 From 4292862079c68950c7bdd245e2092e2a59a08a6d Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Mon, 15 Jan 2024 17:34:51 +0100 Subject: [PATCH 09/10] Upgrade dependencies --- sunbeam-microcluster/cmd/sunbeamd/main.go | 12 ++-- sunbeam-microcluster/go.mod | 48 ++++++++-------- sunbeam-microcluster/go.sum | 68 +++++++++++++++++++++++ 3 files changed, 98 insertions(+), 30 deletions(-) diff --git a/sunbeam-microcluster/cmd/sunbeamd/main.go b/sunbeam-microcluster/cmd/sunbeamd/main.go index 5b8a9114..ec6937a0 100644 --- a/sunbeam-microcluster/cmd/sunbeamd/main.go +++ b/sunbeam-microcluster/cmd/sunbeamd/main.go @@ -69,7 +69,7 @@ func (c *cmdDaemon) Run(_ *cobra.Command, _ []string) error { // Placeholder for post-action hooks that can be run by MicroCluster. h := &config.Hooks{ // OnBootstrap is run after the daemon is initialized and bootstrapped. - OnBootstrap: func(s *state.State) error { + OnBootstrap: func(s *state.State, initConfig map[string]string) error { logger.Info("This is a hook that runs after the daemon is initialized and bootstrapped") return nil @@ -83,28 +83,28 @@ func (c *cmdDaemon) Run(_ *cobra.Command, _ []string) error { }, // PostJoin is run after the daemon is initialized and joins a cluster. - PostJoin: func(s *state.State) error { + PostJoin: func(s *state.State, initConfig map[string]string) error { logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, after OnNewMember runs on all peers") return nil }, // PreJoin is run after the daemon is initialized and joins a cluster. - PreJoin: func(s *state.State) error { + PreJoin: func(s *state.State, initConfig map[string]string) error { logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, before OnNewMember runs on all peers") return nil }, // PostRemove is run after the daemon is removed from a cluster. - PostRemove: func(s *state.State) error { + PostRemove: func(s *state.State, force bool) error { logger.Infof("This is a hook that is run on peer %q after a cluster member is removed", s.Name()) return nil }, // PreRemove is run before the daemon is removed from the cluster. - PreRemove: func(s *state.State) error { + PreRemove: func(s *state.State, force bool) error { logger.Infof("This is a hook that is run on peer %q just before it is removed", s.Name()) return nil @@ -129,7 +129,7 @@ func (c *cmdDaemon) Run(_ *cobra.Command, _ []string) error { } func init() { - rand.Seed(time.Now().UnixNano()) + rand.New(rand.NewSource(time.Now().UnixNano())) } func main() { diff --git a/sunbeam-microcluster/go.mod b/sunbeam-microcluster/go.mod index 7bc82599..ae296673 100644 --- a/sunbeam-microcluster/go.mod +++ b/sunbeam-microcluster/go.mod @@ -3,37 +3,37 @@ module github.com/canonical/snap-openstack/sunbeam-microcluster go 1.21 require ( - github.com/canonical/lxd v0.0.0-20230705090120-570f7071eeb2 - github.com/canonical/microcluster v0.0.0-20230705140256-7726061a60bb - github.com/gorilla/mux v1.8.0 - github.com/spf13/cobra v1.7.0 + github.com/canonical/lxd v0.0.0-20240115081802-6605050f32f5 + github.com/canonical/microcluster v0.0.0-20231129081021-39c3a6d3e1b6 + github.com/gorilla/mux v1.8.1 + github.com/spf13/cobra v1.8.0 ) require ( github.com/Rican7/retry v0.3.1 // indirect - github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a // indirect - github.com/canonical/go-dqlite v1.20.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/armon/go-proxyproto v0.1.0 // indirect + github.com/canonical/go-dqlite v1.21.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 // indirect github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/renameio v1.0.1 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/gorilla/schema v1.2.0 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/schema v1.2.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/juju/webbrowser v1.0.0 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/fs v0.1.0 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/muhlemmer/gu v0.3.1 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pkg/sftp v1.13.5 // indirect + github.com/pkg/sftp v1.13.6 // indirect github.com/pkg/xattr v0.4.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect @@ -41,16 +41,16 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 // indirect - github.com/zitadel/oidc/v2 v2.6.4 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/oauth2 v0.9.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + github.com/zitadel/oidc/v2 v2.12.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/httprequest.v1 v1.2.1 // indirect gopkg.in/macaroon.v2 v2.1.0 // indirect diff --git a/sunbeam-microcluster/go.sum b/sunbeam-microcluster/go.sum index 33c08d07..b9e0069e 100644 --- a/sunbeam-microcluster/go.sum +++ b/sunbeam-microcluster/go.sum @@ -47,15 +47,23 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a h1:AP/vsCIvJZ129pdm9Ek7bH7yutN3hByqsMoNrWAxRQc= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/armon/go-proxyproto v0.1.0 h1:TWWcSsjco7o2itn6r25/5AqKBiWmsiuzsUDLT/MTl7k= +github.com/armon/go-proxyproto v0.1.0/go.mod h1:Xj90dce2VKbHzRAeiVQAMBtj4M5oidoXJ8lmgyW21mw= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/canonical/go-dqlite v1.20.0 h1:pnkn0oS0hPXWeODjvjWONKGb5KYh8kK0aruDPzZLwmU= github.com/canonical/go-dqlite v1.20.0/go.mod h1:Uvy943N8R4CFUAs59A1NVaziWY9nJ686lScY7ywurfg= +github.com/canonical/go-dqlite v1.21.0 h1:4gLDdV2GF+vg0yv9Ff+mfZZNQ1JGhnQ3GnS2GeZPHfA= +github.com/canonical/go-dqlite v1.21.0/go.mod h1:Uvy943N8R4CFUAs59A1NVaziWY9nJ686lScY7ywurfg= github.com/canonical/lxd v0.0.0-20230705090120-570f7071eeb2 h1:pOkOFlpAzO+aHKTQ2igNNLUX7wVZ35SXgj76edsP8TQ= github.com/canonical/lxd v0.0.0-20230705090120-570f7071eeb2/go.mod h1:iRepjqZoVVmu2NXI55FgUAnCgoqggPTKTYMqTA4P6Qk= +github.com/canonical/lxd v0.0.0-20240115081802-6605050f32f5 h1:kqdTovfRGAXcAkAEW2kZRY34SikNQO6dl9tqfQyV0+k= +github.com/canonical/lxd v0.0.0-20240115081802-6605050f32f5/go.mod h1:VNAUq+uJCP9eL4EJmkivAutaz/r3xzXHiiJGVEXl7SY= github.com/canonical/microcluster v0.0.0-20230705140256-7726061a60bb h1:rTBytbpzzHmWkptalBUDt5xtaA+Wi2TCNtuzJectBC0= github.com/canonical/microcluster v0.0.0-20230705140256-7726061a60bb/go.mod h1:vrMOrNO0Iu0lgGaFlKBwZdLXYNdogOgk6WqD4SneSDI= +github.com/canonical/microcluster v0.0.0-20231129081021-39c3a6d3e1b6 h1:z9bEXCNTYyckqgxykZyU+Jy1R+Vf5Qr3HsOq7+Xy260= +github.com/canonical/microcluster v0.0.0-20231129081021-39c3a6d3e1b6/go.mod h1:pqKGCjymAfdqHmUC77AMSKq9sRTazucRyPLjFDI2muM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -68,9 +76,12 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -89,6 +100,8 @@ github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebP github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -167,17 +180,27 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM= +github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -235,6 +258,8 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -261,6 +286,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -289,17 +316,22 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -308,8 +340,11 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zitadel/oidc/v2 v2.6.4 h1:bruA+KOFHcGpxr++WgtvR82ZlH54kKituu5xE4wpF7o= github.com/zitadel/oidc/v2 v2.6.4/go.mod h1:owrsdzRqGvIZjBCY9LY1ZUYJ0mRUbGkQpZ3OskXL4wM= +github.com/zitadel/oidc/v2 v2.12.0 h1:4aMTAy99/4pqNwrawEyJqhRb3yY3PtcDxnoDSryhpn4= +github.com/zitadel/oidc/v2 v2.12.0/go.mod h1:LrRav74IiThHGapQgCHZOUNtnqJG0tcZKHro/91rtLw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -331,9 +366,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -369,6 +408,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -407,8 +447,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -423,6 +467,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -434,8 +480,11 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -482,13 +531,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -497,8 +555,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -554,6 +617,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -588,6 +652,8 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -663,6 +729,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1fb35ba4ea03187262cefd05d170fc741139d19d Mon Sep 17 00:00:00 2001 From: Guillaume Boutry Date: Mon, 15 Jan 2024 17:35:22 +0100 Subject: [PATCH 10/10] Strip sunbeamd binary Add flags to strip sunbeamd binary, gaining ~10MB --- snap/snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 74ed8363..afc03f0d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -112,14 +112,14 @@ parts: build-snaps: - go/1.21/stable build-environment: - - GOFLAGS: "-mod=readonly" + - GOFLAGS: -mod=readonly -ldflags=-s override-build: | set -ex # Setup build environment export CGO_CFLAGS="-I${SNAPCRAFT_STAGE}/include/ -I${SNAPCRAFT_STAGE}/usr/local/include/" export CGO_LDFLAGS="-L${SNAPCRAFT_STAGE}/lib/ -L${SNAPCRAFT_STAGE}/usr/local/lib/" - export CGO_LDFLAGS_ALLOW="(-Wl,-wrap,pthread_create)|(-Wl,-z,now)" + export CGO_LDFLAGS_ALLOW="(-Wl,-wrap,pthread_create)|(-Wl,-z,now)|(-s)" # Build the binaries go build -o "${SNAPCRAFT_PART_INSTALL}/bin/sunbeamd" -tags=libsqlite3 ./cmd/sunbeamd