about summary refs log tree commit diff
path: root/items/svc_s6rc.py
blob: c827f05b926edee5d8ab38452b860c7dac59b405 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from shlex import quote
from pathlib import Path

from bundlewrap.items import Item

S6RC_ROOT = "/etc/s6-rc"

def generate_request_path(template, service) -> str:
    return Path(S6RC_ROOT, "generate", f"{template}.{service}").as_posix()

def bundle_dependency_path(bundle, service) -> str:
    return Path(S6RC_ROOT, "src", bundle, "contents.d", service).as_posix()

def have_generate_template(node, template) -> bool:
    template_path = Path(S6RC_ROOT, "templates", template)
    try:
        result = node.run(
            f"test -d {quote(template_path.as_posix())}",
        )
        return result.return_code == 0
    except:
        return False

def is_service_generated(node, template, service) -> bool:
    try:
        result = node.run(
            f"test -f {quote(generate_request_path(template, service))}",
        )
        return result.return_code == 0
    except:
        return False

def generate_service(node, template, service) -> bool:
    """
    This doesn't really generate the service from the template. Rather it
    requests the s6-rc-generate target to do it on boot.
    """
    result = node.run(
        f"touch {quote(generate_request_path(template, service))}",
    )
    return result.return_code == 0

def is_service_registered(node, bundle, service) -> bool:
    try:
        result = node.run(
            f"test -f {quote(bundle_dependency_path(bundle, service))}",
        )
        return result.return_code == 0
    except:
        return False

def register_service(node, bundle, service) -> bool:
    result = node.run(
        f"touch {quote(bundle_dependency_path(bundle, service))}",
    )
    return result.return_code == 0

def is_service_running(node, service) -> bool:
    try:
        result = node.run(
            f"s6-rc -v2 -bt 30000 -a list | grep -q {quote(service)}",
        )
        return result.return_code == 0
    except:
        return False

def run_service(node, service) -> bool:
    result = node.run(
        f"s6-rc -v2 -bt 30000 start {quote(service)}",
    )
    return result.return_code == 0

class SvcS6RC(Item):
    """
    A service managed by s6-rc.
    """

    BUNDLE_ATTRIBUTE_NAME = "svc_s6rc"
    ITEM_ATTRIBUTES = {
        "bundle": "default",
        "generate": None,
        "running": True,
    }
    ITEM_TYPE_NAME = "svc_s6rc"
    REQUIRED_ATTRIBUTES = []

    def __repr__(self):
        return "<SvcS6RC name:{} running:{} bundle:{} generate:{}>".format(
            self.name,
            self.attributes["running"],
            self.attributes["bundle"],
            self.attributes["generate"],
        )

    def get_canned_actions(self):
        return {
            "stop": {
                "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)}",
                "needed_by": {self.id},
            },
            "restart": {
                "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)} && s6-rc -v2 -b -t 30000 start {quote(self.name)}",
                "needs": {self.id},
            },
        }

    def cdict(self):
        return {
            "running": self.attributes["running"],
            "generated": self.attributes["generate"] != None,
            "registered": self.attributes["bundle"] != None,
        }

    def sdict(self):
        return {
            "running": is_service_running(self.node, self.name),
            "generated": is_service_generated(self.node,
                                              self.attributes["generate"],
                                              self.name) if
                self.attributes["generate"] != None else False,
            "registered": is_service_registered(self.node,self.attributes["bundle"], self.name),
        }

    def fix(self, status):
        if status.correct:
            return

        if self.attributes["generate"]:
            if not have_generate_template(self.node, self.attributes["generate"]):
                raise Exception(f"No template provides `{self.attributes["generate"]}`")

        if status.cdict["generated"] != status.sdict["generated"]:
            generate_service(self.node, self.attributes["generate"], self.name)

        if status.cdict["registered"] != status.sdict["registered"]:
            register_service(self.node, self.attributes["bundle"], self.name)

        if status.cdict["running"] != status.sdict["running"]:
            run_service(self.node, self.name)