Exemple #1
0
    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
Exemple #2
0
async def transform(collector, target, reference, **kwargs):
    """
    Do a http-api like transformation over whatever target you specify

    ``target:transform d073d5000000 -- '{"color": "red", "effect": "pulse"}'``

    It takes in ``color``, ``effect``, ``power`` and valid options for a
    ``SetWaveformOptional``.

    You may also specify ``transform_options`` that change how the transform works.

    keep_brightness
        Ignore brightness options in the request

    transition_color
        If the light is off and we power on, setting this to True will mean the
        color of the light is not set to the new color before we make it appear
        to be on. This defaults to False, which means it will appear to turn on
        with the new color
    """
    extra = collector.photons_app.extra_as_json
    extra = sb.dictionary_spec().normalise(Meta.empty(), extra)

    transform_options = sb.set_options(
        transform_options=sb.dictionary_spec()).normalise(
            Meta.empty(), extra)["transform_options"]

    msg = Transformer.using(extra, **transform_options)

    if not msg:
        raise PhotonsAppError(
            'Please specify valid options after --. For example ``transform -- \'{"power": "on", "color": "red"}\'``'
        )

    await target.send(msg, reference)
Exemple #3
0
    async def execute_task(self, **kwargs):
        options = sb.set_options(
            indication=sb.required(sb.boolean()),
            duration_s=sb.required(sb.integer_spec()),
        ).normalise(Meta.empty(), self.photons_app.extra_as_json)

        await self.target.send(SetCleanConfig(**options), self.reference,
                               **kwargs)
Exemple #4
0
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}")
Exemple #5
0
    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)
Exemple #6
0
    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}"')
Exemple #7
0
    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
Exemple #8
0
        spec = scene_spec.hsbk()

        with assertRaises(
            BadSpecValue, "Number must be between min and max", minimum=1500, maximum=9000
        ):
            spec.normalise(meta, [1, 0, 0, 1000])

        with assertRaises(
            BadSpecValue, "Number must be between min and max", minimum=1500, maximum=9000
        ):
            spec.normalise(meta, [360, 1, 1, 9001])

describe "json_string_spec":
    describe "storing":
        it "loads if the val is a string and returns as dumps", meta:
            spec = sb.set_options(one=sb.integer_spec())
            spec = scene_spec.json_string_spec(spec, True)
            got = spec.normalise(meta, '{"one": 2, "two": 3}')
            assert got == '{"one": 2}'

        it "doesn't loads if not a string and returns as dumps", meta:
            spec = sb.set_options(one=sb.integer_spec())
            spec = scene_spec.json_string_spec(spec, True)
            got = spec.normalise(meta, {"one": 2, "two": 3})
            assert got == '{"one": 2}'

        it "complains if string is not valid json", meta:
            spec = sb.set_options(one=sb.integer_spec())
            spec = scene_spec.json_string_spec(spec, True)
            with assertRaises(BadSpecValue, "Value was not valid json"):
                spec.normalise(meta, "{")
 def setup(self):
     self.spec = sb.dictof(
         service_type_spec(),
         sb.set_options(host=sb.required(sb.string_spec()),
                        port=sb.required(sb.integer_spec())),
     )