1"""
2This is the a dummy proxy-minion designed for testing the proxy minion subsystem.
3"""
4
5import copy
6import logging
7import os
8import pprint
9from contextlib import contextmanager
10
11import salt.utils.files
12import salt.utils.msgpack
13
14# This must be present or the Salt loader won't load this module
15__proxyenabled__ = ["dummy"]
16
17log = logging.getLogger(__file__)
18
19
20# This does nothing, it's here just as an example and to provide a log
21# entry when the module is loaded.
22def __virtual__():
23    """
24    Only return if all the modules are available
25    """
26    log.debug("dummy proxy __virtual__() called...")
27    return True
28
29
30def _save_state(opts, details):
31    _id = __context__["dummy_proxy"]["id"]
32    cachefile = os.path.join(opts["cachedir"], "dummy-proxy-{}.cache".format(_id))
33    with salt.utils.files.fopen(cachefile, "wb") as pck:
34        pck.write(salt.utils.msgpack.packb(details, use_bin_type=True))
35    log.warning("Dummy Proxy Saved State(%s):\n%s", cachefile, pprint.pformat(details))
36
37
38def _load_state(opts):
39    _id = __context__["dummy_proxy"]["id"]
40    cachefile = os.path.join(opts["cachedir"], "dummy-proxy-{}.cache".format(_id))
41    try:
42        with salt.utils.files.fopen(cachefile, "rb") as pck:
43            state = salt.utils.msgpack.unpackb(pck.read(), raw=False)
44    except FileNotFoundError:
45        state = _initial_state()
46        _save_state(opts, state)
47    except Exception as exc:  # pylint: disable=broad-except
48        log.exception("Failed to load state: %s", exc, exc_info=True)
49        state = _initial_state()
50        _save_state(opts, state)
51    log.warning("Dummy Proxy Loaded State(%s):\n%s", cachefile, pprint.pformat(state))
52    return state
53
54
55@contextmanager
56def _loaded_state(opts):
57    state = _load_state(opts)
58    original = copy.deepcopy(state)
59    try:
60        yield state
61    finally:
62        if state != original:
63            _save_state(opts, state)
64
65
66def _initial_state():
67    return {
68        "services": {"apache": "running", "ntp": "running", "samba": "stopped"},
69        "packages": {
70            "coreutils": "1.0",
71            "apache": "2.4",
72            "tinc": "1.4",
73            "redbull": "999.99",
74        },
75    }
76
77
78# Every proxy module needs an 'init', though you can
79# just put DETAILS['initialized'] = True here if nothing
80# else needs to be done.
81
82
83def init(opts):
84    """
85    Required.
86    Can be used to initialize the server connection.
87    """
88    __context__["dummy_proxy"] = {"id": opts["id"]}
89    log.debug("dummy proxy init() called...")
90    with _loaded_state(opts) as state:
91        state["initialized"] = True
92
93
94def initialized():
95    """
96    Since grains are loaded in many different places and some of those
97    places occur before the proxy can be initialized, return whether
98    our init() function has been called
99    """
100    with _loaded_state(__opts__) as state:
101        return state.get("initialized", False)
102
103
104def grains():
105    """
106    Make up some grains
107    """
108    with _loaded_state(__opts__) as state:
109        if "grains_cache" not in state:
110            state["grains_cache"] = {
111                "dummy_grain_1": "one",
112                "dummy_grain_2": "two",
113                "dummy_grain_3": "three",
114            }
115        return state["grains_cache"]
116
117
118def grains_refresh():
119    """
120    Refresh the grains
121    """
122    with _loaded_state(__opts__) as state:
123        if "grains_cache" in state:
124            state.pop("grains_cache")
125    return grains()
126
127
128def fns():
129    """
130    Method called by grains module.
131    """
132    return {
133        "details": (
134            "This key is here because a function in "
135            "grains/rest_sample.py called fns() here in the proxymodule."
136        )
137    }
138
139
140def service_start(name):
141    """
142    Start a "service" on the dummy server
143    """
144    with _loaded_state(__opts__) as state:
145        state["services"][name] = "running"
146    return "running"
147
148
149def service_stop(name):
150    """
151    Stop a "service" on the dummy server
152    """
153    with _loaded_state(__opts__) as state:
154        state["services"][name] = "stopped"
155    return "stopped"
156
157
158def service_restart(name):
159    """
160    Restart a "service" on the REST server
161    """
162    return True
163
164
165def service_list():
166    """
167    List "services" on the REST server
168    """
169    with _loaded_state(__opts__) as state:
170        return list(state["services"])
171
172
173def service_status(name):
174    """
175    Check if a service is running on the REST server
176    """
177    with _loaded_state(__opts__) as state:
178        if state["services"][name] == "running":
179            return {"comment": "running"}
180        else:
181            return {"comment": "stopped"}
182
183
184def package_list():
185    """
186    List "packages" installed on the REST server
187    """
188    with _loaded_state(__opts__) as state:
189        return state["packages"]
190
191
192def package_install(name, **kwargs):
193    """
194    Install a "package" on the REST server
195    """
196    if kwargs.get("version", False):
197        version = kwargs["version"]
198    else:
199        version = "1.0"
200    with _loaded_state(__opts__) as state:
201        state["packages"][name] = version
202    return {name: version}
203
204
205def upgrade():
206    """
207    "Upgrade" packages
208    """
209    with _loaded_state(__opts__) as state:
210        for p in state["packages"]:
211            version_float = float(state["packages"][p])
212            version_float = version_float + 1.0
213            state["packages"][p] = str(version_float)
214        return state["packages"]
215
216
217def uptodate():
218    """
219    Call the REST endpoint to see if the packages on the "server" are up to date.
220    """
221    with _loaded_state(__opts__) as state:
222        return state["packages"]
223
224
225def package_remove(name):
226    """
227    Remove a "package" on the REST server
228    """
229    __context__["dummy_proxy"]["foo"] = "bar"
230    with _loaded_state(__opts__) as state:
231        state["packages"].pop(name)
232        return state["packages"]
233
234
235def package_status(name):
236    """
237    Check the installation status of a package on the REST server
238    """
239    with _loaded_state(__opts__) as state:
240        if name in state["packages"]:
241            return {name: state["packages"][name]}
242
243
244def ping():
245    """
246    Degenerate ping
247    """
248    log.debug("dummy proxy returning ping")
249    return True
250
251
252def shutdown(opts):
253    """
254    For this proxy shutdown is a no-op
255    """
256    log.debug("dummy proxy shutdown() called...")
257    with _loaded_state(__opts__) as state:
258        if "filename" in state:
259            os.unlink(state["filename"])
260
261
262def test_from_state():
263    """
264    Test function so we have something to call from a state
265    :return:
266    """
267    log.debug("test_from_state called")
268    return "testvalue"
269