Add logic for detecting stack and associated volumes
This commit is contained in:
parent
621067b267
commit
27e9a24905
110
stackup/__main__.py
Normal file
110
stackup/__main__.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import docker.models
|
||||||
|
|
||||||
|
import stackup.__about__
|
||||||
|
import stackup.config
|
||||||
|
import stackup.errors
|
||||||
|
|
||||||
|
|
||||||
|
def determine_volumes(
|
||||||
|
client: docker.DockerClient,
|
||||||
|
local_container: docker.models.containers.Container,
|
||||||
|
is_swarm: bool,
|
||||||
|
) -> List[docker.models.volumes.Volume]:
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if is_swarm:
|
||||||
|
stack_label = "com.docker.stack.namespace"
|
||||||
|
else:
|
||||||
|
stack_label = "com.docker.compose.project"
|
||||||
|
|
||||||
|
stack = local_container.labels[stack_label]
|
||||||
|
logger.debug(
|
||||||
|
f"Identified local stack as '{stack}' from namespace label '{stack_label}' on local container {local_container.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Primary filter (via docker) is for volumes in the detected stack
|
||||||
|
# Secondary filter (via the list comp) is for volumes that have the label
|
||||||
|
# that enables them for stackup processing. The end result is that ``volumes``
|
||||||
|
# is a list of volumes that are enabled for stackup processing in the current
|
||||||
|
# stack
|
||||||
|
volumes = [
|
||||||
|
item
|
||||||
|
for item in client.volumes.list(filters={"label": f"{stack_label}={stack}"})
|
||||||
|
if item.attrs["Labels"].get("stackup.enable")
|
||||||
|
]
|
||||||
|
logger.info(
|
||||||
|
f"Identified {len(volumes)} in stack '{stack}' for backup: {', '.join([item.attrs['Name']] for item in volumes)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not volumes:
|
||||||
|
raise stackup.errors.NoVolumesEnabled
|
||||||
|
|
||||||
|
# Determine which (if any) volumes are missing from the current container
|
||||||
|
local_volumes = {item["Name"]: item for item in local_container.attrs["Mounts"]}
|
||||||
|
logger.debug(
|
||||||
|
f"Identified {len(local_volumes)} volumes mounted into local container {local_container.id}: {', '.join(local_volumes.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
missing = [
|
||||||
|
item.attrs["Name"]
|
||||||
|
for item in volumes
|
||||||
|
if item.attrs["Name"] not in local_volumes
|
||||||
|
]
|
||||||
|
if missing:
|
||||||
|
raise stackup.errors.EnabledVolumeNotMountedError(
|
||||||
|
f"One or more volumes enabled for backup in stack '{stack}' are not mounted in the current container ({local_container.id}): {', '.join(missing)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return volumes
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
config = stackup.config.StackupConfig.build()
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format="%(levelname)s: %(message)s",
|
||||||
|
level=config.log_level,
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info(
|
||||||
|
f"Starting {stackup.__about__.__title__} v{stackup.__about__.__version__}"
|
||||||
|
)
|
||||||
|
logger.debug(config)
|
||||||
|
|
||||||
|
logger.debug("Loading Docker client from local environment")
|
||||||
|
client = docker.from_env()
|
||||||
|
|
||||||
|
logger.debug(f"Connected to Docker daemon at {client.api.base_url}")
|
||||||
|
# Determine whether we're operating in swarm mode
|
||||||
|
try:
|
||||||
|
client.swarm.version
|
||||||
|
except TypeError:
|
||||||
|
is_swarm = False
|
||||||
|
logger.debug(f"Daemon at {client.api.base_url} is not bound to a swarm")
|
||||||
|
else:
|
||||||
|
is_swarm = True
|
||||||
|
logger.debug(
|
||||||
|
f"Daemon at {client.api.base_url} is bound to swarm {client.swarm.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
local_container = client.containers.get(socket.gethostname())
|
||||||
|
logger.debug(f"Identified local container as {local_container.id}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
volumes = determine_volumes(client, local_container, is_swarm)
|
||||||
|
except stackup.errors.NoVolumesEnabled:
|
||||||
|
return 0
|
||||||
|
except stackup.errors.StackupError as err:
|
||||||
|
logger.error(str(err))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
41
stackup/config.py
Normal file
41
stackup/config.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import datetime
|
||||||
|
import enum
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import wonderwords
|
||||||
|
|
||||||
|
|
||||||
|
def _name_phrase():
|
||||||
|
return "-".join(
|
||||||
|
wonderwords.RandomWord().random_words(
|
||||||
|
4,
|
||||||
|
word_min_length=4,
|
||||||
|
word_max_length=12,
|
||||||
|
include_parts_of_speech=["nouns", "adjectives"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _name_timestamp():
|
||||||
|
return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
def _name_uuid():
|
||||||
|
return uuid.uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
|
class Namers(enum.Enum):
|
||||||
|
PHRASE = _name_phrase
|
||||||
|
TIMESTAMP = _name_timestamp
|
||||||
|
UUID = _name_uuid
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StackupConfig:
|
||||||
|
log_level: str = "info"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(cls):
|
||||||
|
return cls(log_level=os.getenv("STACKUP_LOG_LEVEL", cls.log_level))
|
10
stackup/errors.py
Normal file
10
stackup/errors.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class StackupError(Exception):
|
||||||
|
"""Base application exception"""
|
||||||
|
|
||||||
|
|
||||||
|
class NoVolumesEnabled(StackupError):
|
||||||
|
"""Could not identify any volumes in the current stack that are enabled for backup"""
|
||||||
|
|
||||||
|
|
||||||
|
class EnabledVolumeNotMountedError(StackupError):
|
||||||
|
"""One or more volumes that are enabled for backup are not mounted in the current container"""
|
Loading…
Reference in New Issue
Block a user