Skip to content

Commit

Permalink
apply suggestion + add update and remove
Browse files Browse the repository at this point in the history
- add update function
- add remove function
  • Loading branch information
rgildein committed Jul 9, 2023
1 parent bfe9d5f commit 2fea075
Show file tree
Hide file tree
Showing 2 changed files with 295 additions and 21 deletions.
135 changes: 118 additions & 17 deletions lib/charms/operator_libs_linux/v0/grub.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import logging
import os
import subprocess
from pathlib import Path
from typing import Dict, List, Mapping, Optional, Set

Expand Down Expand Up @@ -56,10 +57,35 @@
# info -f grub -n 'Simple configuration'
"""

# TODO: add better description to lib


class ValidationError(ValueError):
"""Exception representing value validation error."""

def __init__(self, key: str, message: str) -> None:
self._key = key
self._message = message
super().__init__(message)

@property
def key(self) -> str:
"""Get name of key, which cause this error."""
return self._key

@property
def message(self) -> str:
"""Get error message."""
return self._message


class IsContainerError(Exception):
"""Exception if local machine is container."""


class ApplyError(Exception):
"""Exception if applying new config failed."""


def _parse_config(data: str) -> Dict[str, str]:
"""Parse config file lines.
Expand Down Expand Up @@ -109,6 +135,12 @@ def _save_config(path: Path, config: Dict[str, str], header: str = CONFIG_HEADER
logger.info("grub config file %s was saved", path)


def is_container() -> bool:
"""Help function to see if local machine is container."""
# TODO: implement it
return False


class GrubConfig(Mapping[str, str]):
"""Grub config object.
Expand All @@ -122,6 +154,10 @@ class GrubConfig(Mapping[str, str]):

_lazy_data: Optional[Dict[str, str]] = None

def __init__(self, charm_name: Optional[str] = None) -> None:
"""Initialize the grub config."""
self._charm_name = charm_name

def __contains__(self, key: str) -> bool:
"""Check if key is in config."""
return key in self._data
Expand All @@ -147,6 +183,25 @@ def _data(self) -> Dict[str, str]:

return data

def _apply(self):
"""Check if an update to /boot/grub/grub.cfg is available."""
try:
subprocess.check_call(f"grub-mkconfig -o {GRUB_CONFIG}", stderr=subprocess.STDOUT)
subprocess.check_call(["/usr/sbin/update-grub"], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as error:
logger.exception(error)
raise ApplyError("New config check failed.") from error

def _save_grub_configuration(self) -> None:
"""Save current gru configuration."""
logger.info("saving new grub config to %s", GRUB_CONFIG)
applied_configs = {self.path, *self.applied_configs} # using set to drop duplicity
registered_configs = os.linesep.join(
FILE_LINE_IN_DESCRIPTION.format(path=path) for path in applied_configs
)
header = CONFIG_HEADER + CONFIG_DESCRIPTION.format(configs=registered_configs)
_save_config(GRUB_CONFIG, self._data, header)

def _set_value(self, key: str, value: str) -> bool:
"""Set new value for key."""
logger.debug("setting new value %s for key %s", value, key)
Expand All @@ -162,31 +217,77 @@ def _set_value(self, key: str, value: str) -> bool:
current_value,
value,
)
raise ValidationError(f"key {key} already exists and its value is {current_value}")
raise ValidationError(
f"key {key} already exists and its value is {current_value}", key
)

return False

@property
def registered_configs(self) -> List[Path]:
"""Return list of charms which registered config here."""
return [config.absolute() for config in LIB_CONFIG_DIRECTORY.glob("*") if config.is_file()]

def update(self, charm_name: str, config: Dict[str, str]) -> Set[str]:
"""Update the Grub configuration."""
def _update(self, config: Dict[str, str]) -> Set[str]:
"""Update data in object."""
logger.debug("updating current config")
changed_keys = set()
for key, value in config.items():
changed = self._set_value(key, value)
if changed:
changed_keys.add(key)

return changed_keys

@property
def charm_name(self) -> str:
"""Get charm name or use value obtained from JUJU_UNIT_NAME env."""
if self._charm_name is None:
self._charm_name, *_ = os.getenv("JUJU_UNIT_NAME", "unknown").split("/")

return self._charm_name

@property
def path(self) -> Path:
"""Return path for charm config."""
return LIB_CONFIG_DIRECTORY / self.charm_name

@property
def applied_configs(self) -> List[Path]:
"""Return list of charms which registered config here."""
return [config.absolute() for config in LIB_CONFIG_DIRECTORY.glob("*") if config.is_file()]

def remove(self) -> None:
"""Remove config for charm.
This function will remove config file for charm and re-create the `95-juju-charm.cfg`
grub config file without changes made by this charm.
"""
self.path.unlink()
logger.info("charm config file %s was removed", self.path)
config = {}
for path in self.applied_configs:
_config = _load_config(path)
logger.debug("load config file %s", path)
config.update(_config)

self._lazy_data = config
self._save_grub_configuration()
self._apply()

def update(self, config: Dict[str, str]) -> Set[str]:
"""Update the Grub configuration."""
if is_container():
raise IsContainerError("Could not configure grub config on container.")

snapshot = self._data.copy()
try:
changed_keys = self._update(config)
self._save_grub_configuration()
self._apply()
except ValidationError:
self._lazy_data = snapshot
raise
except ApplyError:
self._lazy_data = snapshot
self._save_grub_configuration() # save snapshot copy of grub config
raise

logger.debug("saving copy of charm config to %s", LIB_CONFIG_DIRECTORY)
_save_config(LIB_CONFIG_DIRECTORY / charm_name, config)
# TODO: check if config is valid `grub-mkconfig -o <path>``
logger.info("saving new grub config to %s", GRUB_CONFIG)
registered_configs = os.linesep.join(
FILE_LINE_IN_DESCRIPTION.format(path=path) for path in self.registered_configs
)
header = CONFIG_HEADER + CONFIG_DESCRIPTION.format(configs=registered_configs)
_save_config(GRUB_CONFIG, self._data, header)
# TODO: update grub, https://git.launchpad.net/charm-sysconfig/tree/src/lib/lib_sysconfig.py
_save_config(LIB_CONFIG_DIRECTORY / self.charm_name, config)
return changed_keys
Loading

0 comments on commit 2fea075

Please sign in to comment.