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