import fnmatch
+import os
import re
import enum
from collections import OrderedDict
_service_spec_from_json_validate = True
+class CustomConfig:
+ """
+ Class to specify custom config files to be mounted in daemon's container
+ """
+
+ _fields = ['content', 'mount_path']
+
+ def __init__(self, content: str, mount_path: str) -> None:
+ self.content: str = content
+ self.mount_path: str = mount_path
+ self.validate()
+
+ def to_json(self) -> Dict[str, Any]:
+ return {
+ 'content': self.content,
+ 'mount_path': self.mount_path,
+ }
+
+ @classmethod
+ def from_json(cls, data: Dict[str, Any]) -> "CustomConfig":
+ for k in cls._fields:
+ if k not in data:
+ raise SpecValidationError(f'CustomConfig must have "{k}" field')
+ for k in data.keys():
+ if k not in cls._fields:
+ raise SpecValidationError(f'CustomConfig got unknown field "{k}"')
+ return cls(**data)
+
+ @property
+ def filename(self) -> str:
+ return os.path.basename(self.mount_path)
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, CustomConfig):
+ return (
+ self.content == other.content
+ and self.mount_path == other.mount_path
+ )
+ return NotImplemented
+
+ def __repr__(self) -> str:
+ return f'CustomConfig({self.mount_path})'
+
+ def validate(self) -> None:
+ if not isinstance(self.content, str):
+ raise SpecValidationError(
+ f'CustomConfig content must be a string. Got {type(self.content)}')
+ if not isinstance(self.mount_path, str):
+ raise SpecValidationError(
+ f'CustomConfig content must be a string. Got {type(self.mount_path)}')
+
+
@contextmanager
def service_spec_allow_invalid_from_json() -> Iterator[None]:
"""
networks: Optional[List[str]] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
#: See :ref:`orchestrator-cli-placement-spec`.
self.extra_container_args: Optional[List[str]] = extra_container_args
self.extra_entrypoint_args: Optional[List[str]] = extra_entrypoint_args
+ self.custom_configs: Optional[List[CustomConfig]] = custom_configs
@classmethod
@handle_type_error
for k, v in json_spec.items():
if k == 'placement':
v = PlacementSpec.from_json(v)
+ if k == 'custom_configs':
+ v = [CustomConfig.from_json(c) for c in v]
if k == 'spec':
args.update(v)
continue
ret['extra_container_args'] = self.extra_container_args
if self.extra_entrypoint_args:
ret['extra_entrypoint_args'] = self.extra_entrypoint_args
+ if self.custom_configs:
+ ret['custom_configs'] = [c.to_json() for c in self.custom_configs]
c = {}
for key, val in sorted(self.__dict__.items(), key=lambda tpl: tpl[0]):
port: Optional[int] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'nfs'
super(NFSServiceSpec, self).__init__(
'nfs', service_id=service_id,
placement=placement, unmanaged=unmanaged, preview_only=preview_only,
config=config, networks=networks, extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args)
+ extra_entrypoint_args=extra_entrypoint_args, custom_configs=custom_configs)
self.port = port
subcluster: Optional[str] = None, # legacy, only for from_json on upgrade
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'rgw', service_type
'rgw', service_id=service_id,
placement=placement, unmanaged=unmanaged,
preview_only=preview_only, config=config, networks=networks,
- extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args)
+ extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
#: The RGW realm associated with this service. Needs to be manually created
self.rgw_realm: Optional[str] = rgw_realm
networks: Optional[List[str]] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'iscsi'
super(IscsiServiceSpec, self).__init__('iscsi', service_id=service_id,
preview_only=preview_only,
config=config, networks=networks,
extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args)
+ extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
#: RADOS pool where ceph-iscsi config data is stored.
self.pool = pool
ssl: bool = False,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'ingress'
placement=placement, config=config,
networks=networks,
extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args
+ extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs
)
self.backend_service = backend_service
self.frontend_port = frontend_port
port: Optional[int] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type in ['grafana', 'node-exporter', 'prometheus', 'alertmanager']
placement=placement, unmanaged=unmanaged,
preview_only=preview_only, config=config,
networks=networks, extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args)
+ extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
self.service_type = service_type
self.port = port
secure: bool = False,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'alertmanager'
super(AlertManagerSpec, self).__init__(
'alertmanager', service_id=service_id,
placement=placement, unmanaged=unmanaged,
preview_only=preview_only, config=config, networks=networks, port=port,
- extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args)
+ extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
# Custom configuration.
#
initial_admin_password: Optional[str] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'grafana'
super(GrafanaSpec, self).__init__(
'grafana', service_id=service_id,
placement=placement, unmanaged=unmanaged,
preview_only=preview_only, config=config, networks=networks, port=port,
- extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args)
+ extra_container_args=extra_container_args, extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
self.initial_admin_password = initial_admin_password
port: Optional[int] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'snmp-gateway'
unmanaged=unmanaged,
preview_only=preview_only,
extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args)
+ extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
self.service_type = service_type
self.snmp_version = snmp_version
preview_only: bool = False,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
+ custom_configs: Optional[List[CustomConfig]] = None,
):
assert service_type == 'mds'
super(MDSSpec, self).__init__('mds', service_id=service_id,
unmanaged=unmanaged,
preview_only=preview_only,
extra_container_args=extra_container_args,
- extra_entrypoint_args=extra_entrypoint_args)
+ extra_entrypoint_args=extra_entrypoint_args,
+ custom_configs=custom_configs)
def validate(self) -> None:
super(MDSSpec, self).validate()