async def make_crontab(collector, target, reference, artifact, **kwargs): """ Make a crontab file executing our day dusk options. Usage is:: ./generate-crontab.py """ collector.register_converters( {"daydusk": DayDusk.FieldSpec(formatter=MergedOptionStringFormatter)}) daydusk = collector.configuration["daydusk"] spec = sb.set_options(path=sb.defaulted(sb.string_spec(), '/config/daydusk.crontab'), lifx_script=sb.defaulted(sb.string_spec(), '/usr/local/bin/lifx')) extra = collector.configuration["photons_app"].extra_as_json kwargs = { k: v for k, v in spec.normalise(Meta.empty(), extra).items() if v is not sb.NotSpecified } cronfile = kwargs['path'] lifx_script = kwargs['lifx_script'] if not daydusk.schedules: raise NoSchedules() cron = CronTab() extra_script_args = ["--silent"] for name, options in daydusk.schedules.items(): script_args = {**options.hsbk, **options.extra} command = [ lifx_script, options.task, options.reference, *extra_script_args, "--", json.dumps(script_args), ] command = str(" ".join([shlex.quote(part) for part in command])) job = cron.new(command=command) job.dow.on(*options.dow) job.minute.on(options.minute) job.hour.on(options.hour) if os.path.exists(cronfile): os.remove(cronfile) cron.write(cronfile) print(f"Generated crontab at {cronfile}")
async def execute_task(self, **kwargs): broadcast = True if self.artifact is not sb.NotSpecified: broadcast = self.artifact options = sb.set_options( cli_output=sb.defaulted(sb.boolean(), None), env_output=sb.defaulted(sb.boolean(), False), settings_output=sb.defaulted(sb.boolean(), False), ).normalise(Meta.empty(), self.collector.photons_app.extra_as_json) if options["env_output"] is False and options[ "settings_output"] is False: if options["cli_output"] is None: options["cli_output"] = True env_output = options["env_output"] cli_output = options["cli_output"] settings_output = options["settings_output"] ips = {} async with self.target.session() as sender: found, serials = await self.reference.find(sender, timeout=20, broadcast=broadcast) for serial in serials: services = found[binascii.unhexlify(serial)] if Services.UDP in services: ip = services[Services.UDP].host ips[serial] = ip sorted_ips = sorted(ips.items(), key=lambda item: ipaddress.ip_address(item[1])) if cli_output: for serial, ip in sorted_ips: print(f"{serial}: {ip}") if cli_output and (env_output or settings_output): print() if env_output: print(f"export HARDCODED_DISCOVERY='{json.dumps(sorted_ips)}'") if settings_output: print() if settings_output: print("discovery_options:") print(" hardcoded_discovery:") for serial, ip in sorted_ips: print(f' {serial}: "{ip}"')
def make_command(self, meta, val, existing): v = sb.set_options(path=sb.required(sb.string_spec()), allow_ws_only=sb.defaulted(sb.boolean(), False)).normalise( meta, val) path = v["path"] allow_ws_only = v["allow_ws_only"] if path not in self.paths: raise NoSuchPath(path, sorted(self.paths)) val = sb.set_options(body=sb.required( sb.set_options(args=sb.dictionary_spec(), command=sb.required(sb.string_spec())))).normalise( meta, val) args = val["body"]["args"] name = val["body"]["command"] if existing: name = val["body"]["command"] = f"{existing['path']}:{name}" extra_context = {} if existing: extra_context["_parent_command"] = existing["command"] everything = meta.everything if isinstance(meta.everything, MergedOptions): everything = meta.everything.wrapped() everything.update(extra_context) meta = Meta(everything, []).at("body") available_commands = self.paths[path] if name not in available_commands: raise BadSpecValue( "Unknown command", wanted=name, available=self.available(available_commands, allow_ws_only=allow_ws_only), meta=meta.at("command"), ) command = available_commands[name]["spec"].normalise( meta.at("args"), args) if not allow_ws_only and command.__whirlwind_ws_only__: raise BadSpecValue( "Command is for websockets only", wanted=name, available=self.available(available_commands, allow_ws_only=allow_ws_only), meta=meta.at("command"), ) return command, name
class MemoryTarget(Target): """ Knows how to talk to fake devices as if they were on the network. """ devices = dictobj.Field(sb.listof(sb.any_spec()), wrapper=sb.required) default_broadcast = dictobj.Field( sb.defaulted(sb.string_spec(), "255.255.255.255")) session_kls = makeMemorySession(NetworkSession)
class LanTarget(Target): """ Knows how to talk to a device over the local network. It's one configuration option is default_broadcast which says what address to broadcast discovery if broadcast is given to run calls as True. """ default_broadcast = dictobj.Field( sb.defaulted(sb.string_spec(), "255.255.255.255")) discovery_options = dictobj.Field(discovery_options_spec) session_kls = NetworkSession
class MemoryTarget(LanTarget): """ Knows how to talk to fake devices as if they were on the network. """ gaps = dictobj.Field( Gaps(gap_between_results=0.05, gap_between_ack_and_res=0.05, timeouts=[(0.2, 0.2)])) io_service = dictobj.Field(sb.any_spec, default=MemoryService) transport_kls = dictobj.Field(sb.any_spec, default=MemoryTransport) devices = dictobj.Field(sb.listof(sb.any_spec()), wrapper=sb.required) default_broadcast = dictobj.Field( sb.defaulted(sb.string_spec(), "255.255.255.255")) session_kls = makeMemorySession(NetworkSession)
def normalise_empty(self, meta): env = os.environ.get("NOISY_NETWORK_LIMIT") if env: if env == "true": env = 2 elif env == "null": env = 0 return sb.integer_spec().normalise(meta, env) animation_options = sb.set_options( noisy_network_limit=sb.defaulted(sb.integer_spec(), 0)).normalise( meta, meta.everything.get("animation_options") or {}) if animation_options["noisy_network_limit"]: return animation_options["noisy_network_limit"] return 0
class LanTarget(Target): """ Knows how to talk to a device over the local network. It's one configuration option is default_broadcast which says what address to broadcast discovery if broadcast is given to sender calls as True. """ gaps = dictobj.Field( Gaps( gap_between_results=0.4, gap_between_ack_and_res=0.2, timeouts=[(0.2, 0.2), (0.1, 0.5), (0.2, 1), (1, 5)], )) default_broadcast = dictobj.Field( sb.defaulted(sb.string_spec(), "255.255.255.255")) discovery_options = dictobj.Field(discovery_options_spec) session_kls = NetworkSession
def normalise_filled(self, meta, val): options_spec = sb.set_options( filename=sb.required(sb.string_spec()), optional=sb.defaulted(sb.boolean(), False) ) lst_spec = sb.listof(sb.or_spec(sb.string_spec(), options_spec)) if isinstance(val, list): val = {"after": lst_spec.normalise(meta, val)} val = sb.set_options(after=lst_spec, before=lst_spec).normalise(meta, val) formatted = sb.formatted(sb.string_spec(), formatter=MergedOptionStringFormatter) for key in ("after", "before"): result = [] for i, thing in enumerate(val[key]): filename = thing optional = False if isinstance(thing, dict): filename = thing["filename"] optional = thing["optional"] filename = formatted.normalise(meta.at(key).indexed_at(i), filename) if optional and not os.path.exists(filename): log.warning(hp.lc("Ignoring optional configuration", filename=filename)) continue if not os.path.exists(filename): raise BadConfiguration( "Specified extra file doesn't exist", source=self.source, filename=filename, meta=meta, ) result.append(filename) val[key] = result return self.extras_spec.normalise(meta, val)
it "knows about boolean": self.assertSignature(sb.boolean(), "boolean") it "knows about dictionary_spec": self.assertSignature(sb.dictionary_spec(), "dictionary") it "knows about string_choice_spec": self.assertSignature(sb.string_choice_spec(["one", "two"]), "choice of (one | two)") it "knows about optional_spec": self.assertSignature(sb.optional_spec(sb.integer_spec()), "integer (optional)") self.assertSignature(sb.optional_spec(sb.any_spec()), "(optional)") it "knows about defaulted": self.assertSignature(sb.defaulted(sb.integer_spec(), 20), "integer (default 20)") self.assertSignature(sb.defaulted(sb.any_spec(), True), "(default True)") it "knows about required": self.assertSignature(sb.required(sb.integer_spec()), "integer (required)") self.assertSignature(sb.required(sb.any_spec()), "(required)") it "knows about listof": self.assertSignature(sb.listof(sb.integer_spec()), "[ integer , ... ]") self.assertSignature(sb.listof(sb.any_spec()), "[ <item> , ... ]") it "knows about dictof": self.assertSignature(sb.dictof(sb.string_spec(), sb.integer_spec()), "{ string : integer }") self.assertSignature(sb.dictof(sb.string_spec(), sb.any_spec()), "{ string : <item> }") it "knows about container_spec":