def make_image(self, options, harpoon_options=None): config_root = self.make_temp_dir() if harpoon_options is None: harpoon_options = {} harpoon_options["docker_context"] = self.docker_client harpoon_options["no_intervention"] = True harpoon_options["docker_context_maker"] = self.new_docker_client harpoon = HarpoonSpec().harpoon_spec.normalise(Meta({}, []), harpoon_options) if "harpoon" not in options: options["harpoon"] = harpoon everything = MergedOptions.using({"harpoon": harpoon, "mtime": mtime, "config_root": config_root}) everything.update({"images": {"awesome_image": options}}) def make_options(): base = everything.wrapped() base.update(options) base["configuration"] = everything return base meta = Meta(everything, []).at("images").at("awesome_image") harpoon_converter = Converter(convert=lambda *args: harpoon, convert_path=["harpoon"]) image_converter = Converter( convert=lambda *args: HarpoonSpec().image_spec.normalise(meta, make_options()) , convert_path=["images", "awesome_image"] ) everything.add_converter(harpoon_converter) everything.add_converter(image_converter) everything.converters.activate() return everything[["images", "awesome_image"]]
def make_image(self, options, harpoon_options=None): config_root = self.make_temp_dir() if harpoon_options is None: harpoon_options = {} harpoon_options["docker_context"] = self.docker_client harpoon = HarpoonSpec().harpoon_spec.normalise(Meta({}, []), harpoon_options) if "harpoon" not in options: options["harpoon"] = harpoon everything = {"harpoon": harpoon, "mtime": mtime, "_key_name_1": "awesome_image", "config_root": config_root} return HarpoonSpec().image_spec.normalise(Meta(everything, []), options)
def convert_image(path, val): log.info("Converting %s", path) everything = path.configuration.root().wrapped() meta = Meta(everything, []) configuration.converters.started(path) base = path.configuration.root().wrapped() base.update(configuration.as_dict(ignore=["images"])) base.update(val.as_dict(ignore=["images"])) base["__image__"] = base everything["__image__"] = base base["harpoon"] = configuration["harpoon"] base["configuration"] = configuration return harpoon_spec.image_spec.normalise(meta.at("images").at(image), base)
def __call__(self, namespace, entry_point_name, collector, known=None): if namespace not in self.namespaces: log.warning( "Unknown plugin namespace\tnamespace=%s\tentry_point=%s\tavailable=%s", namespace, entry_point_name, sorted(self.namespaces.keys())) return entry_point_full_name = "{0}.{1}".format(namespace, entry_point_name) entry_points = self.find_entry_points(namespace, entry_point_name, entry_point_full_name) def result_maker(**data): return self.namespaces[namespace][0].normalise( Meta(data, []), data) resolver, extras = self.resolve_entry_points( namespace, entry_point_name, collector, result_maker, entry_points, entry_point_full_name, known) return self.namespaces[namespace][1].normalise( Meta({}, []), { "namespace": namespace, "name": entry_point_name, "resolver": resolver, "extras": extras })
def pull_arbitrary(collector, image, **kwargs): """Pull an arbitrary image""" image_index_of = lambda image: urlparse("https://{0}".format(image)).netloc if image.startswith("file://"): parsed = urlparse(image) filename = parsed.netloc + parsed.path if not os.path.exists(filename): raise HarpoonError("Provided file doesn't exist!", wanted=image) with open(filename) as fle: image_indexes = [(line.strip(), image_index_of(line.strip())) for line in fle] else: image_indexes = [(image, image_index_of(image))] authentication = collector.configuration.get("authentication", NotSpecified) for index, (image, image_index) in enumerate(image_indexes): image = { "image_name": image, "harpoon": collector.configuration["harpoon"], "commands": ["FROM scratch"], "image_index": image_index, "assume_role": NotSpecified, "authentication": authentication } meta = Meta(collector.configuration, []).at("images").at("__arbitrary_{0}__".format(index)) image = HarpoonSpec().image_spec.normalise(meta, image) Syncer().pull(image)
def get_from_env(wanted): """Get environment variables from the env""" env = sb.listof(env_spec()).normalise(Meta({}, []), wanted) missing = [e.env_name for e in env if e.missing] if missing: raise BespinError("Missing environment variables", missing=missing) return dict(e.pair for e in env)
def setup_addon_register(self, photons_app, __main__): """Setup our addon register""" # Create the addon getter and register the crosshair namespace self.addon_getter = AddonGetter() self.addon_getter.add_namespace("lifx.photons", Result.FieldSpec(), Addon.FieldSpec()) # Initiate the addons from our configuration register = Register(self.addon_getter, self) if "addons" in photons_app: addons = photons_app["addons"] if type(addons) in (MergedOptions, dict) or getattr( addons, "is_dict", False): spec = sb.dictof(sb.string_spec(), sb.listof(sb.string_spec())) meta = Meta(photons_app, []).at("addons") for namespace, adns in spec.normalise(meta, addons).items(): register.add_pairs(*[(namespace, adn) for adn in adns]) elif photons_app.get("default_activate_all_modules"): register.add_pairs(("lifx.photons", "__all__")) if __main__ is not None: register.add_pairs(("lifx.photons", "__main__")) # Import our addons register.recursive_import_known() # Resolve our addons register.recursive_resolve_imported() return register
def convert_passwords(path, val): log.info("Converting %s", path) password = str(path)[len("passwords."):] configuration.converters.started(path) environment = configuration['bespin'].environment val_as_dict = configuration["passwords"][password].as_dict() if not environment: raise BespinError("No environment was provided", available=list( configuration["environments"].keys())) password_environment_as_dict = {} if ["passwords", password, environment] in configuration: password_environment_as_dict = configuration[[ "passwords", password, environment ]].as_dict() base = MergedOptions(dont_prefix=path.configuration.dont_prefix, converters=path.configuration.converters) everything = path.configuration.root().wrapped() base.update(val_as_dict) everything[path] = val_as_dict base.update(password_environment_as_dict) everything[path].update(password_environment_as_dict) for thing in (base, everything): thing["__password__"] = val thing["__environment__"] = configuration["environments"][ environment] meta = Meta(everything, [("passwords", ""), (password, "")]) return bespin_spec.password_spec.normalise(meta, base)
def add_configuration(self, configuration, collect_another_source, done, result, src): """Used to add a file to the configuration, result here is the yaml.load of the src""" def make_mtime_func(source): """Lazily calculate the mtime to avoid wasted computation""" return lambda context: self.get_committime_or_mtime( context, source) if "harpoon" in result: if "extra_files" in result["harpoon"]: spec = sb.listof( sb.formatted(sb.string_spec(), formatter=MergedOptionStringFormatter)) meta = Meta(MergedOptions.using(result), []).at("harpoon").at("extra_files") for extra in spec.normalise(meta, result["harpoon"]["extra_files"]): if os.path.abspath(extra) not in done: if not os.path.exists(extra): raise BadConfiguration( "Specified extra file doesn't exist", extra=extra, source=src) collect_another_source(extra) if "images" in result and "__images_from__" in result["images"]: images_from_path = result["images"]["__images_from__"] if isinstance(images_from_path, six.string_types): images_from_path = [images_from_path] for ifp in images_from_path: if not ifp.startswith("/"): ifp = os.path.join(os.path.dirname(src), ifp) if not os.path.exists(ifp) or not os.path.isdir(ifp): raise self.BadConfigurationErrorKls( "Specified folder for other configuration files points to a folder that doesn't exist", path="images.__images_from__", value=ifp) for root, dirs, files in os.walk(ifp): for fle in files: location = os.path.join(root, fle) if fle.endswith(".yml") or fle.endswith(".yaml"): collect_another_source( location, prefix=[ "images", os.path.splitext(os.path.basename(fle))[0] ], extra={"mtime": make_mtime_func(location)}) del result["images"]["__images_from__"] if "mtime" not in result: result["mtime"] = make_mtime_func(src) configuration.update(result, source=src)
def __init__(self, extras=sb.NotSpecified, post_register=False): self.post_register = post_register if post_register and extras not in (None, {}, sb.NotSpecified): msg = "Sorry, can't specify ``extras`` and ``post_register`` at the same time" raise ProgrammerError(msg) spec = sb.listof( sb.tuple_spec(sb.string_spec(), sb.listof(sb.string_spec()))) self.extras = spec.normalise(Meta({}, []), extras)
def converter(p, v): log.info("Converting %s", p) meta = Meta(p.configuration, [(thing, "")]) configuration.converters.started(p) if thing in by_name: return by_name[thing].normalise(meta, v) else: return getattr(aws_syncr_spec, "{0}_spec".format(thing)).normalise(meta, v)
def convert_image(path, val): log.info("Converting %s", path) everything = path.configuration.root().wrapped() meta = Meta(everything, []) configuration.converters.started(path) base = path.configuration.root().wrapped() base.update(configuration.as_dict(ignore=["images"])) base.update(val.as_dict(ignore=["images"])) base["__image__"] = base everything["__image__"] = base base["harpoon"] = configuration["harpoon"] base["configuration"] = configuration return harpoon_spec.image_spec.normalise( meta.at("images").at(image), base)
def convert_tasks(path, val): spec = bespin_spec.tasks_spec(available_actions) meta = Meta(path.configuration.root(), [('stacks', ""), (stack, ""), ('tasks', "")]) configuration.converters.started(path) tasks = spec.normalise(meta, val) for task in tasks.values(): task.stack = stack return tasks
def convert_tasks(path, val): spec = harpoon_spec.tasks_spec(available_actions) meta = Meta(path.configuration.root(), [('images', ""), (image, ""), ('tasks', "")]) configuration.converters.started(path) tasks = spec.normalise(meta, val) for task in tasks.values(): task.image = image return tasks
def from_options(kls, options): """Create a Filter based on the provided dictionary""" if isinstance(options, dict): for option in options: if option not in kls.fields: log.warning( hp.lc("Unknown option provided for filter", wanted=option)) return kls.FieldSpec().normalise(Meta.empty(), options)
def get_string(self, key): """Get a string from all_options""" if key not in self.all_options: kwargs = {} if len(self.chain) > 1: kwargs['source'] = Meta(self.all_options, self.chain[-2]).source raise BadOptionFormat("Can't find key in options", key=key, chain=self.chain, **kwargs) val = self.all_options[key] return val
def pack_payload(kls, message_type, data, messages_register=None): """ Given some payload data as a dictionary and it's ``pkt_type``, return a hexlified string of the payload. """ for k in (messages_register or [kls]): if int(message_type) in k.by_type: return k.by_type[int(message_type)].Payload.normalise( Meta.empty(), data).pack() raise BadConversion("Unknown message type!", pkt_type=message_type)
def convert_stack(path, val): log.info("Converting %s", path) configuration.converters.started(path) environment = configuration['bespin'].environment config_as_dict = configuration.as_dict(ignore=["stacks"]) val_as_dict = val.as_dict(ignore=["stacks"]) if not environment or environment is NotSpecified: raise BespinError("No environment was provided", available=list( configuration["environments"].keys())) env = configuration[["environments", environment]] if isinstance(env, six.string_types): environment_as_dict = configuration[["environments", env]].as_dict() env = configuration[["environments", env]] else: environment_as_dict = configuration[[ "environments", environment ]].as_dict() stack_environment = {} stack_environment_as_dict = {} if ["stacks", stack, environment] in configuration: stack_environment = configuration[[ "stacks", stack, environment ]] stack_environment_as_dict = stack_environment.as_dict() # `base` is used for the majority of the values base = path.configuration.root().wrapped() base.update(config_as_dict) base.update(val_as_dict) base.update(environment_as_dict) base.update(stack_environment_as_dict) # `everything` is used for formatting options # Ideally it matches base # The difference here is that we want to maintain source information everything = path.configuration.root().wrapped() everything[path] = MergedOptions.using(configuration, val, env, stack_environment) for thing in (base, everything): thing["bespin"] = configuration["bespin"] thing["environment"] = environment thing["configuration"] = configuration thing["__stack__"] = val thing["__environment__"] = configuration["environments"][ environment] thing["__stack_name__"] = stack meta = Meta(everything, [("stacks", ""), (stack, "")]) return bespin_spec.stack_spec.normalise(meta, base)
async def set_chain_state(collector, target, reference, artifact, **kwargs): """ Set the state of colors on your tile ``lan:set_chain_state d073d5f09124 -- '{"colors": [[[0, 0, 0, 3500], [0, 0, 0, 3500], ...], [[0, 0, 1, 3500], ...], ...], "tile_index": 1, "length": 1, "x": 0, "y": 0, "width": 8}'`` Where the colors is a grid of 8 rows of 8 ``[h, s, b, k]`` values. """ options = collector.configuration["photons_app"].extra_as_json if "colors" in options: spec = sb.listof( sb.listof( list_spec(sb.integer_spec(), sb.float_spec(), sb.float_spec(), sb.integer_spec()))) colors = spec.normalise(Meta.empty().at("colors"), options["colors"]) row_lengths = [len(row) for row in colors] if len(set(row_lengths)) != 1: raise PhotonsAppError( "Please specify colors as a grid with the same length rows", got=row_lengths) num_cells = sum(len(row) for row in colors) if num_cells != 64: raise PhotonsAppError("Please specify 64 colors", got=num_cells) cells = [] for row in colors: for col in row: cells.append({ "hue": col[0], "saturation": col[1], "brightness": col[2], "kelvin": col[3] }) options["colors"] = cells else: raise PhotonsAppError( "Please specify colors in options after -- as a grid of [h, s, b, k]" ) missing = [] for field in TileMessages.SetState64.Payload.Meta.all_names: if field not in options and field not in ("duration", "reserved6"): missing.append(field) if missing: raise PhotonsAppError("Missing options for the SetTileState message", missing=missing) options["res_required"] = False msg = TileMessages.SetState64.empty_normalise(**options) await target.script(msg).run_with_all(reference)
def make_image(self, options, harpoon_options=None): config_root = self.make_temp_dir() if harpoon_options is None: harpoon_options = {} harpoon_options["docker_context"] = self.docker_client harpoon_options["docker_context_maker"] = self.new_docker_client harpoon = HarpoonSpec().harpoon_spec.normalise(Meta({}, []), harpoon_options) if "harpoon" not in options: options["harpoon"] = harpoon everything = MergedOptions.using({"harpoon": harpoon, "mtime": mtime, "_key_name_1": "awesome_image", "config_root": config_root}) harpoon_converter = Converter(convert=lambda *args: harpoon, convert_path=["harpoon"]) everything.add_converter(harpoon_converter) everything.converters.activate() if "configuration" not in options: options["configuration"] = everything return HarpoonSpec().image_spec.normalise(Meta(everything, []), options)
def get_string(self, key): """Get a string from all_options""" if key not in self.all_options: kwargs = {} if len(self.chain) > 1: kwargs['source'] = Meta(self.all_options, self.chain[-2]).source raise BadOptionFormat("Can't find key in options", key=key, chain=self.chain, **kwargs) return super(MergedOptionStringFormatter, self).get_string(key)
def pack(kls, data, protocol_register, unknown_ok=False): """ Return a hexlified string of the data. This uses ``pkt_type`` and ``protocol`` in the data, along with the protocol_register to find the appropriate class to use to perform the packing. """ if "pkt_type" in data: message_type = data["pkt_type"] elif "pkt_type" in data.get("protocol_header", {}): message_type = data["protocol_header"]["pkt_type"] else: raise BadConversion( "Don't know how to pack this dictionary, it doesn't specify a pkt_type!" ) if "protocol" in data: protocol = data["protocol"] elif "frame_header" in data and "protocol" in data["frame_header"]: protocol = data["frame_header"]["protocol"] else: raise BadConversion( "Don't know how to pack this dictionary, it doesn't specify a protocol!" ) prot = protocol_register.get(protocol) if prot is None: raise BadConversion("Unknown packet protocol", wanted=protocol, available=list(protocol_register)) Packet, messages_register = prot for k in (messages_register or [kls]): if message_type in k.by_type: return k.by_type[message_type].normalise(Meta.empty(), data).pack() if unknown_ok: return Packet.normalise(Meta.empty(), data).pack() raise BadConversion("Unknown message type!", pkt_type=message_type)
def get_string(self, key): """Get a string from all_options""" if key not in self.all_options: kwargs = {} if len(self.chain) > 1: kwargs['source'] = Meta(self.all_options, self.chain[-2]).source raise BadOptionFormat("Can't find key in options", key=key, chain=self.chain, **kwargs) # Make sure we special case the "content" option if type(key) is str and key.startswith("content."): return self.no_format(self.all_options["content"][key[8:]]) if type(key) is list and len(key) is 2 and key[0] == "content": return self.no_format(self.all_options[key]) return super(MergedOptionStringFormatter, self).get_string(key)
async def set_attr(collector, target, reference, artifact, broadcast=False, **kwargs): """ Set attributes on your globes ``target:set_attr d073d5000000 color -- '{"hue": 360, "saturation": 1, "brightness": 1}'`` This does the same thing as ``get_attr`` but will look for ``Set<Attr>`` message and initiates it with the options found after the ``--``. So in this case it will create ``SetColor(hue=360, saturation=1, brightness=1)`` and send that to the device. """ protocol_register = collector.configuration["protocol_register"] if artifact in (None, "", NotSpecified): raise BadOption( "Please specify what you want to get\nUsage: {0} <target>:set_attr <reference> <attr_to_get> -- '{{<options>}}'" .format(sys.argv[0])) kls_name = "Set{0}".format("".join(part.capitalize() for part in artifact.split("_"))) setter = None for messages in protocol_register.message_register(1024): for kls in messages.by_type.values(): if kls.__name__ == kls_name: setter = kls break if setter is None: raise BadOption( "Sorry, couldn't find the message type {0}".format(kls_name)) photons_app = collector.configuration["photons_app"] extra = photons_app.extra_as_json if "extra_payload_kwargs" in kwargs: extra.update(kwargs["extra_payload_kwargs"]) script = target.script(setter.normalise(Meta.empty(), extra)) async for pkt, _, _ in script.run_with(reference, broadcast=broadcast): print("{0}: {1}".format(pkt.serial, repr(pkt.payload)))
async def get_attr(collector, target, reference, artifact, **kwargs): """ Get attributes from your globes ``target:get_attr d073d5000000 color`` Where ``d073d5000000`` is replaced with the serial of the device you are addressing and ``color`` is replaced with the attribute you want. This task works by looking at all the loaded LIFX binary protocol messages defined for the 1024 protocol and looks for ``Get<Attr>``. So if you want the ``color`` attribute, it will look for the ``GetColor`` message and send that to the device and print out the reply packet we get back. """ protocol_register = collector.configuration["protocol_register"] if artifact in (None, "", NotSpecified): raise BadOption( "Please specify what you want to get\nUsage: {0} <target>:get_attr <reference> <attr_to_get>" .format(sys.argv[0])) kls_name = "Get{0}".format("".join(part.capitalize() for part in artifact.split("_"))) getter = None for messages in protocol_register.message_register(1024): for kls in messages.by_type.values(): if kls.__name__ == kls_name: getter = kls break if getter is None: raise BadOption( "Sorry, couldn't find the message type {0}".format(kls_name)) photons_app = collector.configuration["photons_app"] extra = photons_app.extra_as_json if "extra_payload_kwargs" in kwargs: extra.update(kwargs["extra_payload_kwargs"]) script = target.script(getter.normalise(Meta.empty(), extra)) async for pkt, _, _ in script.run_with(reference, **kwargs): print("{0}: {1}".format(pkt.serial, repr(pkt.payload)))
def encrypt_password(collector, stack, artifact, **kwargs): """Convert plain text password into crypto text""" if artifact is None: key = stack else: key = artifact configuration = collector.configuration key = valid_password_key().normalise(Meta(configuration, []), key) password_options = configuration["passwords"][key] log.info("Generating a crypto_text for the %s password using %s as the KMS key id", key, password_options.KMSMasterKey) plain_text = getpass("Specify the password: "******"bespin"].credentials.kms.encrypt(password_options.KMSMasterKey, plain_text, password_options.encryption_context, password_options.grant_tokens) log.info("Generated crypto text for %s", key) print(base64.b64encode(res["CiphertextBlob"]).decode('utf-8'))
def extra_prepare(self, configuration, args_dict): """Called before the configuration.converters are activated""" harpoon = MergedOptions.using( configuration.get('harpoon', MergedOptions()).as_dict(), dict(args_dict.get("harpoon", MergedOptions()).items())).as_dict() # Args_dict may itself be a MergedOptions while "harpoon" in args_dict: del args_dict["harpoon"] # Create the addon getter and register the crosshair namespace self.addon_getter = AddonGetter() self.addon_getter.add_namespace("harpoon.crosshairs", Result.FieldSpec(), Addon.FieldSpec()) # Initiate the addons from our configuration self.register = Register(self.addon_getter, self) if ("addons" in harpoon) and ( type(harpoon["addons"]) in (MergedOptions, dict) or getattr(harpoon["addons"], "is_dict", False)): for namespace, adns in sb.dictof( sb.string_spec(), sb.listof(sb.string_spec())).normalise( Meta(harpoon, []).at("addons"), harpoon["addons"]).items(): self.register.add_pairs(*[(namespace, adn) for adn in adns]) # Import our addons self.register.recursive_import_known() # Resolve our addons self.register.recursive_resolve_imported() # Make sure images is started if "images" not in self.configuration: self.configuration["images"] = {} # Add our special stuff to the configuration self.configuration.update( { "$@": harpoon.get("extra", ""), "bash": args_dict["bash"] or NotSpecified, "harpoon": harpoon, "command": args_dict['command'] or NotSpecified, "assume_role": args_dict["assume_role"] or NotSpecified }, source="<args_dict>")
def add_configuration(self, configuration, collect_another_source, done, result, src): """ Used to add a file to the configuration, result here is the yaml.load of the src. If the configuration we're reading in has ``photons_app.extra_files`` then this is treated as a list of strings of other files to collect. """ # Make sure to maintain the original config_root if "config_root" in configuration: # if we already have a config root then we only keep new config root if it's not the home location # i.e. if it is the home configuration, we don't delete the new config_root if configuration["config_root"] != os.path.dirname( self.home_dir_configuration_location()): if "config_root" in result: del result["config_root"] config_root = configuration.get("config_root") if config_root and src.startswith(config_root): src = "{{config_root}}/{0}".format(src[len(config_root) + 1:]) configuration.update(result, source=src) if "photons_app" in result: if "extra_files" in result["photons_app"]: spec = sb.listof( sb.formatted(sb.string_spec(), formatter=MergedOptionStringFormatter)) config_root = { "config_root": result.get("config_root", configuration.get("config_root")) } meta = Meta(MergedOptions.using(result, config_root), []).at("photons_app").at("extra_files") for extra in spec.normalise( meta, result["photons_app"]["extra_files"]): if os.path.abspath(extra) not in done: if not os.path.exists(extra): raise BadConfiguration( "Specified extra file doesn't exist", extra=extra, source=src) collect_another_source(extra)
def downtime(collector, stack, method="downtime", **kwargs): """Downtime this stack in alerting systems""" if stack.downtimer_options is NotSpecified: raise BespinError("Nothing to downtime!") env = sb.listof(env_spec()).normalise(Meta({}, []), ["USER", "DURATION", "COMMENT"]) missing = [e.env_name for e in env if e.missing] if missing: raise BespinError("Missing environment variables", missing=missing) provided_env = dict(e.pair for e in env) author = provided_env["USER"] comment = provided_env["COMMENT"] duration = provided_env["DURATION"] downtimer = Downtimer(stack.downtimer_options, dry_run=collector.configuration["bespin"].dry_run) for system, options in stack.alerting_systems.items(): downtimer.register_system(system, options) getattr(downtimer, method)(duration, author, comment)
async def apply_theme(collector, target, reference, artifact, **kwargs): """ Apply a theme to specified device ``lan:apply_theme d073d5000001 -- `{"colors": <colors>, "theme": "SPLOTCH"}'`` If you don't specify serials, then the theme will apply to all devices found on the network. colors must be an array of ``{"hue": <hue>, "saturation": <saturation>, "brightness": <brightness>, "kelvin": <kelvin>}`` theme must be a valid theme type and defaults to SPLOTCH You may also specify ``duration`` which is how long to take to apply in seconds. And you may also supply ``hue``, ``saturation``, ``brightness`` and ``kelvin`` to override the specified colors. """ options = Options.FieldSpec().normalise( Meta.empty(), collector.configuration["photons_app"].extra_as_json) async with target.session() as afr: await do_apply_theme(target, reference, afr, options)
def add_configuration(self, configuration, collect_another_source, done, result, src): """Used to add a file to the configuration, result here is the yaml.load of the src""" if "bespin" in result: if "extra_files" in result.get("bespin", {}): spec = sb.listof( sb.formatted(sb.string_spec(), formatter=MergedOptionStringFormatter)) for extra in spec.normalise( Meta(configuration, []).at("bespin").at("extra_files"), result["bespin"]["extra_files"]): extra = os.path.join(result['config_root'], extra) if os.path.abspath(extra) not in done: if not os.path.exists(extra): raise BadConfiguration( "Specified extra file doesn't exist", extra=extra, source=src) collect_another_source(extra) configuration.update(result, dont_prefix=[dictobj], source=src)
assert not hasattr(changed, "three") assert not hasattr(changed, "four") self.assertEqual(changed.one, sb.NotSpecified) self.assertEqual(changed.two, sb.NotSpecified) it "allows setting all values to be required": class Original(dictobj.Spec): one = dictobj.Field(sb.string_spec) two = dictobj.Field(sb.integer_spec) three = dictobj.Field(sb.string_spec) four = dictobj.Field(sb.any_spec) Changed = Original.selection("Changed", ["one", "two"], all_required=True) m = Meta.empty() error1 = BadSpecValue("Expected a value but got none", meta=m.at("two")) error2 = BadSpecValue("Expected a value but got none", meta=m.at("one")) with self.fuzzyAssertRaisesError(BadSpecValue, _errors=[error1, error2]): changed = Changed.FieldSpec().normalise(m, {}) it "can override all_required with optional": class Original(dictobj.Spec): one = dictobj.Field(sb.string_spec) two = dictobj.Field(sb.integer_spec) three = dictobj.Field(sb.string_spec) four = dictobj.Field(sb.any_spec) Changed = Original.selection("Changed", ["one", "two"], all_required=True, optional=["two"])
def empty_normalise(self, **kwargs): """Normalise val with the spec from self.make_spec""" return self.normalise(Meta.empty(), kwargs)
blah_image = self.unique_val() md5 = hashlib.md5(json.dumps({"content": content}).encode('utf-8')).hexdigest() md52 = hashlib.md5(json.dumps({"content": {"image": "blah2", "path": "/tmp/stuff"}}, sort_keys=True).encode("utf-8")).hexdigest() blah2_image = mock.Mock(name="blah2", image_name="blah2", from_name="somewhere-blah2") blah3_image = mock.Mock(name="blah3", image_name="blah3", from_name="somewhere-blah3") everything = MergedOptions.using( { "one": 1, "two": 2, "three": 3, "harpoon": self.harpoon, "config_root": "." , "images": { "blah2": blah2_image , "blah3": blah3_image } } ) meta = Meta(everything, []) commands = [ "FROM somewhere as base" , ["FROM", blah_image] , "ADD something /somewhere" , "COPY --from=blah something /somewhere" , ["ENV ONE", "{one}"] , ["ENV", ["TWO {two}", "THREE {three}"]] , ["ADD", {"get": ["blah", "and", "stuff"], "prefix": "/projects"}] , {"ADD": {"content": content, "dest": "the_destination"}} , {"ADD": {"content": content, "dest": "the_destination2"}} , {"ADD": {"content": {"image": "{images.blah2}", "path": "/tmp/stuff"}, "dest": "the_destination3"}} , {"COPY": {"from": "{images.blah2}", "path": "/tmp/stuff", "to": "copy_destination"}} , {"COPY": {"from": 1, "path": "/tmp/stuff", "to": "copy_destination"}} , ["FROM", "{images.blah3}", "as wat"]