File: components.py

package info (click to toggle)
freedombox 26.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 82,976 kB
  • sloc: python: 48,504; javascript: 1,736; xml: 481; makefile: 290; sh: 167; php: 32
file content (186 lines) | stat: -rw-r--r-- 5,563 bytes parent folder | download | duplicates (4)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# SPDX-License-Identifier: AGPL-3.0-or-later
"""App component for other apps to use backup/restore functionality."""

import copy

from plinth import app

from . import privileged


def _validate_directories_and_files(section):
    """Validate directories and files keys in a section."""
    if not section:
        return

    assert isinstance(section, dict)

    if 'directories' in section:
        assert isinstance(section['directories'], list)
        for directory in section['directories']:
            assert isinstance(directory, str)

    if 'files' in section:
        assert isinstance(section['files'], list)
        for file_path in section['files']:
            assert isinstance(file_path, str)


def _validate_services(services):
    """Validate services manifest provided as list."""
    if not services:
        return

    assert isinstance(services, list)
    for service in services:
        assert isinstance(service, (str, dict))
        if isinstance(service, dict):
            _validate_service(service)


def _validate_service(service):
    """Validate a service manifest provided as a dictionary."""
    assert isinstance(service['name'], str)
    assert isinstance(service['type'], str)
    assert service['type'] in ('apache', 'uwsgi', 'system')
    if service['type'] == 'apache':
        assert service['kind'] in ('config', 'site', 'module')


def _validate_settings(settings):
    """Validate settings stored by an in kvstore."""
    if not settings:
        return

    assert isinstance(settings, list)
    for setting in settings:
        assert isinstance(setting, str)


def _validate_paths(paths):
    """Validate a list of files or directories."""
    if not paths:
        return

    assert isinstance(paths, list)
    for path in paths:
        assert isinstance(path, str)


class BackupRestore(app.FollowerComponent):
    """Component to backup/restore an app."""

    def __init__(self, component_id, config=None, data=None, secrets=None,
                 services=None, settings=None, delete_before_restore=None):
        """Initialize the backup/restore component."""
        super().__init__(component_id)

        _validate_directories_and_files(config)
        self.config = config or {}
        _validate_directories_and_files(data)
        self._data = data or {}
        _validate_directories_and_files(secrets)
        self.secrets = secrets or {}
        _validate_services(services)
        self.services = services or []
        _validate_settings(settings)
        self.settings = settings or []
        _validate_paths(delete_before_restore)
        self.delete_before_restore = delete_before_restore or []

        self.has_data = (bool(config) or bool(data) or bool(secrets)
                         or bool(settings))

    def __eq__(self, other):
        """Check if this component is same as another."""
        return self.component_id == other.component_id

    @property
    def data(self):
        """Add additional files to data files list."""
        data = copy.deepcopy(self._data)
        settings_file = self._get_settings_file()
        if settings_file:
            data.setdefault('files', []).append(settings_file)

        return data

    @property
    def manifest(self):
        """Return the backup details as a dictionary."""
        manifest = {}
        if self.config:
            manifest['config'] = self.config

        if self.secrets:
            manifest['secrets'] = self.secrets

        if self.data:
            manifest['data'] = self.data

        if self.services:
            manifest['services'] = self.services

        if self.settings:
            manifest['settings'] = self.settings

        if self.delete_before_restore:
            manifest['delete_before_restore'] = self.delete_before_restore

        return manifest

    def backup_pre(self, packet):
        """Perform any special operations before backup."""
        self._settings_backup_pre()

    def backup_post(self, packet):
        """Perform any special operations after backup."""

    def restore_pre(self, packet):
        """Perform any special operations before restore."""
        self._files_restore_pre()

    def restore_post(self, packet):
        """Perform any special operations after restore."""
        self._settings_restore_post()

    def _get_settings_file(self):
        """Return the settings file path to list of files to backup."""
        if not self.settings or not self.app_id:
            return None

        data_path = '/var/lib/plinth/backups-data/'
        return data_path + f'{self.app_id}-settings.json'

    def _settings_backup_pre(self):
        """Read keys from kvstore and store them in a file to backup."""
        if not self.settings:
            return

        from plinth import kvstore
        data = {}
        for key in self.settings:
            try:
                data[key] = kvstore.get(key)
            except Exception:
                pass

        privileged.dump_settings(self.app_id, data)

    def _files_restore_pre(self):
        """Delete some files and directories before restoring."""
        if not self.delete_before_restore:
            return

        privileged.delete_before_restore(self.app_id)

    def _settings_restore_post(self):
        """Read from a file and restore keys to kvstore."""
        if not self.settings:
            return

        data = privileged.load_settings(self.app_id)

        from plinth import kvstore
        for key, value in data.items():
            kvstore.set(key, value)