def add_mapping(self, aconf: Config, mapping: IRBaseMapping) -> None:
        mismatches = []

        for k in IRHTTPMappingGroup.CoreMappingKeys:
            if (k in mapping) and ((k not in self) or (mapping[k] != self[k])):
                mismatches.append((k, mapping[k], self.get(k, '-unset-')))

        if mismatches:
            self.post_error(
                "cannot accept new mapping %s with mismatched %s" %
                (mapping.name, ", ".join(
                    ["%s: %s != %s" % (x, y, z) for x, y, z in mismatches])))
            return

        # self.ir.logger.debug("%s: add mapping %s" % (self, mapping.as_json()))

        # Per the schema, host_redirect and shadow are Booleans. They won't be _saved_ as
        # Booleans, though: instead we just save the Mapping that they're a part of.
        host_redirect = mapping.get('host_redirect', False)
        shadow = mapping.get('shadow', False)

        # First things first: if both shadow and host_redirect are set in this Mapping,
        # we're going to let shadow win. Kill the host_redirect part.

        if shadow and host_redirect:
            errstr = "At most one of host_redirect and shadow may be set; ignoring host_redirect"
            aconf.post_error(RichStatus.fromError(errstr), resource=mapping)

            mapping.pop('host_redirect', None)
            mapping.pop('path_redirect', None)

        # OK. Is this a shadow Mapping?
        if shadow:
            # Yup. Make sure that we don't have multiple shadows.
            if self.shadows:
                errstr = "cannot accept %s as second shadow after %s" % \
                         (mapping.name, self.shadows[0].name)
                aconf.post_error(RichStatus.fromError(errstr), resource=self)
            else:
                # All good. Save it.
                self.shadows.append(mapping)
        elif host_redirect:
            # Not a shadow, but a host_redirect. Make sure we don't have multiples of
            # those either.

            if self.host_redirect:
                errstr = "cannot accept %s as second host_redirect after %s" % \
                         (mapping.name, typecast(IRBaseMapping, self.host_redirect).name)
                aconf.post_error(RichStatus.fromError(errstr), resource=self)
            else:
                # All good. Save it.
                self.host_redirect = mapping
        else:
            # Neither shadow nor host_redirect: save it.
            self.mappings.append(mapping)

            if mapping.route_weight > self.group_weight:
                self.group_weight = mapping.group_weight

        self.referenced_by(mapping)
Exemple #2
0
    def __init__(self, ir: 'IR', aconf: Config,
                 rkey: str,      # REQUIRED
                 name: str,      # REQUIRED
                 location: str,  # REQUIRED

                 kind: str="IRMapping",
                 apiVersion: str="ambassador/v1",   # Not a typo! See below.
                 precedence: int=0,
                 rewrite: str="/",
                 **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource. So. Shallow copy the keys
        # we're going to allow from the incoming kwargs...

        new_args = {x: kwargs[x] for x in kwargs.keys() if x in IRHTTPMapping.AllowedKeys}

        # ...then set up the headers (since we need them to compute our group ID).
        hdrs = []
        add_request_hdrs = kwargs.get('add_request_headers', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(Header(name))
                else:
                    hdrs.append(Header(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(Header(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(Header(":authority", kwargs['host'], kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'service' in kwargs:
            svc = Service(ir.logger, kwargs['service'])

            if 'add_linkerd_headers' in kwargs:
                if kwargs['add_linkerd_headers'] is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                if 'add_linkerd_headers' in ir.ambassador_module and ir.ambassador_module.add_linkerd_headers is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        if 'method' in kwargs:
            hdrs.append(Header(":method", kwargs['method'], kwargs.get('method_regex', False)))


        # ...and then init the superclass.
        super().__init__(
            ir=ir, aconf=aconf, rkey=rkey, location=location,
            kind=kind, name=name, apiVersion=apiVersion,
            headers=hdrs, add_request_headers=add_request_hdrs,
            precedence=precedence, rewrite=rewrite,
            **new_args
        )

        if 'outlier_detection' in kwargs:
            self.post_error(RichStatus.fromError("outlier_detection is not supported"))
Exemple #3
0
    def __init__(self, ir: 'IR', aconf: Config,
                 rkey: str,      # REQUIRED
                 name: str,      # REQUIRED
                 location: str,  # REQUIRED

                 kind: str="IRMapping",
                 apiVersion: str="ambassador/v1",   # Not a typo! See below.
                 precedence: int=0,
                 rewrite: str="/",
                 **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource. So. Shallow copy the keys
        # we're going to allow from the incoming kwargs...

        new_args = { x: kwargs[x] for x in kwargs.keys() if x in IRMapping.AllowedKeys }

        # ...then set up the headers (since we need them to compute our group ID).
        hdrs = []

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(Header(name))
                else:
                    hdrs.append(Header(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(Header(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(Header(":authority", kwargs['host'], kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'method' in kwargs:
            hdrs.append(Header(":method", kwargs['method'], kwargs.get('method_regex', False)))

        # ...and then init the superclass.
        super().__init__(
            ir=ir, aconf=aconf, rkey=rkey, location=location,
            kind=kind, name=name, apiVersion=apiVersion,
            headers=hdrs, precedence=precedence, rewrite=rewrite,
            **new_args
        )

        # OK. After all that we can compute the group ID...
        self.group_id = self._group_id()

        # ...and the route weight.
        self.route_weight = self._route_weight()

        if ('circuit_breaker' in kwargs) or ('outlier_detection' in kwargs):
            self.post_error(RichStatus.fromError("circuit_breaker and outlier_detection are not supported"))
Exemple #4
0
    def __init__(
            self,
            ir: 'IR',
            aconf: Config,
            rkey: str,  # REQUIRED
            name: str,  # REQUIRED
            location: str,  # REQUIRED
            namespace: Optional[str] = None,
            metadata_labels: Optional[Dict[str, str]] = None,
            kind: str = "IRHTTPMapping",
            apiVersion: str = "getambassador.io/v2",  # Not a typo! See below.
            precedence: int = 0,
            rewrite: str = "/",
            cluster_tag: Optional[str] = None,
            **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource, so we shallow copy the keys
        # we're going to allow from the incoming kwargs.
        #
        # NOTE WELL: things that we explicitly process below should _not_ be listed in
        # AllowedKeys. The point of AllowedKeys is this loop below.

        new_args = {}

        # When we look up defaults, use lookup class "httpmapping"... and yeah, we need the
        # IR, too.
        self.default_class = "httpmapping"
        self.ir = ir

        for key, check_defaults in IRHTTPMapping.AllowedKeys.items():
            # Do we have a keyword arg for this key?
            if key in kwargs:
                # Yes, it wins.
                value = kwargs[key]
                self.ir.logger.info(
                    f"IRHTTPMapping: accept {key}={value} from kwargs")
                new_args[key] = value
            elif check_defaults:
                # No value in kwargs, but we're allowed to check defaults for it.
                value = self.lookup_default(key)

                if value is not None:
                    self.ir.logger.info(
                        f"IRHTTPMapping: accept {key}={value} from defaults")
                    new_args[key] = value

        # add_linkerd_headers is special, because we support setting it as a default
        # in the bare Ambassador module. We should really toss this and use the defaults
        # mechanism, but not for 1.4.3.

        if "add_linkerd_headers" not in new_args:
            # They didn't set it explicitly, so check for the older way.
            add_linkerd_headers = self.ir.ambassador_module.get(
                'add_linkerd_headers', None)

            if add_linkerd_headers != None:
                new_args["add_linkerd_headers"] = add_linkerd_headers

        # OK. On to set up the headers (since we need them to compute our group ID).
        hdrs = []
        query_parameters = []
        regex_rewrite = kwargs.get('regex_rewrite', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(KeyValueDecorator(name))
                else:
                    hdrs.append(KeyValueDecorator(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(KeyValueDecorator(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(
                KeyValueDecorator(":authority", kwargs['host'],
                                  kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'method' in kwargs:
            hdrs.append(
                KeyValueDecorator(":method", kwargs['method'],
                                  kwargs.get('method_regex', False)))

        # Next up: figure out what headers we need to add to each request. Again, if the key
        # is present in kwargs, the kwargs value wins -- this is important to allow explicitly
        # setting a value of `{}` to override a default!

        add_request_hdrs: dict

        if 'add_request_headers' in kwargs:
            add_request_hdrs = kwargs['add_request_headers']
        else:
            add_request_hdrs = self.lookup_default('add_request_headers', {})

        # Remember that we may need to add the Linkerd headers, too.
        add_linkerd_headers = new_args.get('add_linkerd_headers', False)

        if add_linkerd_headers:
            # Yup. We need the service info for this...

            if 'service' in kwargs:
                svc = Service(ir.logger, kwargs['service'])
                add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                # Uh. This is pretty much impossible.
                self.post_error("Service is required for HTTP Mappings")

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.info(
                f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}"
            )
            new_args['tls'] = ir.agent_origination_ctx.name

        if 'query_parameters' in kwargs:
            for name, value in kwargs.get('query_parameters', {}).items():
                if value is True:
                    query_parameters.append(KeyValueDecorator(name))
                else:
                    query_parameters.append(KeyValueDecorator(name, value))

        if 'regex_query_parameters' in kwargs:
            for name, value in kwargs.get('regex_query_parameters',
                                          {}).items():
                query_parameters.append(
                    KeyValueDecorator(name, value, regex=True))

        if 'regex_rewrite' in kwargs:
            if rewrite:
                self.ir.aconf.post_notice(
                    "Cannot specify both rewrite and regex_rewrite: using regex_rewrite and ignoring rewrite"
                )
                rewrite = ""
            rewrite_items = kwargs.get('regex_rewrite', {})
            regex_rewrite = {
                'pattern': rewrite_items.get('pattern', ''),
                'substitution': rewrite_items.get('substitution', '')
            }

        # ...and then init the superclass.
        super().__init__(ir=ir,
                         aconf=aconf,
                         rkey=rkey,
                         location=location,
                         kind=kind,
                         name=name,
                         namespace=namespace,
                         metadata_labels=metadata_labels,
                         apiVersion=apiVersion,
                         headers=hdrs,
                         add_request_headers=add_request_hdrs,
                         precedence=precedence,
                         rewrite=rewrite,
                         cluster_tag=cluster_tag,
                         query_parameters=query_parameters,
                         regex_rewrite=regex_rewrite,
                         **new_args)

        if 'outlier_detection' in kwargs:
            self.post_error(
                RichStatus.fromError("outlier_detection is not supported"))
Exemple #5
0
    def __init__(
            self,
            ir: 'IR',
            aconf: Config,
            rkey: str,  # REQUIRED
            name: str,  # REQUIRED
            location: str,  # REQUIRED
            service: str,  # REQUIRED
            namespace: Optional[str] = None,
            metadata_labels: Optional[Dict[str, str]] = None,
            kind: str = "IRHTTPMapping",
            apiVersion:
        str = "x.getambassador.io/v3alpha1",  # Not a typo! See below.
            precedence: int = 0,
            rewrite: str = "/",
            cluster_tag: Optional[str] = None,
            **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource, so we shallow copy the keys
        # we're going to allow from the incoming kwargs.
        #
        # NOTE WELL: things that we explicitly process below should _not_ be listed in
        # AllowedKeys. The point of AllowedKeys is this loop below.

        new_args = {}

        # When we look up defaults, use lookup class "httpmapping"... and yeah, we need the
        # IR, too.
        self.default_class = "httpmapping"
        self.ir = ir

        for key, check_defaults in IRHTTPMapping.AllowedKeys.items():
            # Do we have a keyword arg for this key?
            if key in kwargs:
                # Yes, it wins.
                value = kwargs[key]
                new_args[key] = value
            elif check_defaults:
                # No value in kwargs, but we're allowed to check defaults for it.
                value = self.lookup_default(key)

                if value is not None:
                    new_args[key] = value

        # add_linkerd_headers is special, because we support setting it as a default
        # in the bare Ambassador module. We should really toss this and use the defaults
        # mechanism, but not for 1.4.3.

        if "add_linkerd_headers" not in new_args:
            # They didn't set it explicitly, so check for the older way.
            add_linkerd_headers = self.ir.ambassador_module.get(
                'add_linkerd_headers', None)

            if add_linkerd_headers != None:
                new_args["add_linkerd_headers"] = add_linkerd_headers

        # OK. On to set up the headers (since we need them to compute our group ID).
        hdrs = []
        query_parameters = []
        regex_rewrite = kwargs.get('regex_rewrite', {})

        # Start by assuming that nothing in our arguments mentions hosts (so no host and no host_regex).
        host = None
        host_regex = False

        # Also start self.host as unspecified.
        self.host = None

        # OK. Start by looking for a :authority header match.
        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(KeyValueDecorator(name))
                else:
                    # An exact match on the :authority header is special -- treat it like
                    # they set the "host" element (but note that we'll allow the actual
                    # "host" element to override it later).
                    if name.lower() == ':authority':
                        # This is an _exact_ match, so it mustn't contain a "*" -- that's illegal in the DNS.
                        if "*" in value:
                            # We can't call self.post_error() yet, because we're not initialized yet. So we cheat a bit
                            # and defer the error for later.
                            new_args[
                                "_deferred_error"] = f":authority exact-match '{value}' contains *, which cannot match anything."
                            ir.logger.debug(
                                "IRHTTPMapping %s: self.host contains * (%s, :authority)",
                                name, value)
                        else:
                            # No globs, just save it. (We'll end up using it as a glob later, in the Envoy
                            # config part of the world, but that's OK -- a glob with no "*" in it will always
                            # match only itself.)
                            host = value
                            ir.logger.debug(
                                "IRHTTPMapping %s: self.host == %s (:authority)",
                                name, self.host)
                            # DO NOT save the ':authority' match here -- we'll pick it up after we've checked
                            # for hostname, too.
                    else:
                        # It's not an :authority match, so we're good.
                        hdrs.append(KeyValueDecorator(name, value))

        if 'regex_headers' in kwargs:
            # DON'T do anything special with a regex :authority match: we can't
            # do host-based filtering within the IR for it anyway.
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(KeyValueDecorator(name, value, regex=True))

        if 'host' in kwargs:
            # It's deliberate that we'll allow kwargs['host'] to silently override an exact :authority
            # header match.
            host = kwargs['host']
            host_regex = kwargs.get('host_regex', False)

            # If it's not a regex, it's an exact match -- make sure it doesn't contain a '*'.
            if not host_regex:
                if "*" in host:
                    # We can't call self.post_error() yet, because we're not initialized yet. So we cheat a bit
                    # and defer the error for later.
                    new_args[
                        "_deferred_error"] = f"host exact-match {host} contains *, which cannot match anything."
                    ir.logger.debug(
                        "IRHTTPMapping %s: self.host contains * (%s, host)",
                        name, host)
                else:
                    ir.logger.debug("IRHTTPMapping %s: self.host == %s (host)",
                                    name, self.host)

        # Finally, check for 'hostname'.
        if 'hostname' in kwargs:
            # It's deliberate that we allow kwargs['hostname'] to override anything else -- even a regex host.
            # Yell about it, though.
            if host:
                ir.logger.warning(
                    "Mapping %s in namespace %s: both host and hostname are set, using hostname and ignoring host",
                    name, namespace)

            # No need to be so careful about "*" here, since hostname is defined to be a glob.
            host = kwargs['hostname']
            host_regex = False
            ir.logger.debug("IRHTTPMapping %s: self.host gl~ %s (hostname)",
                            name, self.host)

        # If we have a host, include a ":authority" match. We're treating this as if it were
        # an exact match, but that's because the ":authority" match is handling specially by
        # Envoy.
        if host:
            hdrs.append(KeyValueDecorator(":authority", host, host_regex))

            # Finally, if our host isn't a regex, save it in self.host.
            if not host_regex:
                self.host = host

        if 'method' in kwargs:
            hdrs.append(
                KeyValueDecorator(":method", kwargs['method'],
                                  kwargs.get('method_regex', False)))

        if 'use_websocket' in new_args:
            allow_upgrade = new_args.setdefault('allow_upgrade', [])
            if 'websocket' not in allow_upgrade:
                allow_upgrade.append('websocket')
            del new_args['use_websocket']

        # Next up: figure out what headers we need to add to each request. Again, if the key
        # is present in kwargs, the kwargs value wins -- this is important to allow explicitly
        # setting a value of `{}` to override a default!

        add_request_hdrs: dict
        add_response_hdrs: dict

        if 'add_request_headers' in kwargs:
            add_request_hdrs = kwargs['add_request_headers']
        else:
            add_request_hdrs = self.lookup_default('add_request_headers', {})

        if 'add_response_headers' in kwargs:
            add_response_hdrs = kwargs['add_response_headers']
        else:
            add_response_hdrs = self.lookup_default('add_response_headers', {})

        # Remember that we may need to add the Linkerd headers, too.
        add_linkerd_headers = new_args.get('add_linkerd_headers', False)

        # XXX The resolver lookup code is duplicated from IRBaseMapping.setup --
        # needs to be fixed after 1.6.1.
        resolver_name = kwargs.get(
            'resolver') or self.ir.ambassador_module.get(
                'resolver', 'kubernetes-service')

        assert (resolver_name
                )  # for mypy -- resolver_name cannot be None at this point
        resolver = self.ir.get_resolver(resolver_name)

        if resolver:
            resolver_kind = resolver.kind
        else:
            # In IRBaseMapping.setup, we post an error if the resolver is unknown.
            # Here, we just don't bother; we're only using it for service
            # qualification.
            resolver_kind = 'KubernetesBogusResolver'

        service = normalize_service_name(ir,
                                         service,
                                         namespace,
                                         resolver_kind,
                                         rkey=rkey)
        self.ir.logger.debug(
            f"Mapping {name} service qualified to {repr(service)}")

        svc = Service(ir.logger, service)

        if add_linkerd_headers:
            add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.debug(
                f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}"
            )
            new_args['tls'] = ir.agent_origination_ctx.name

        if 'query_parameters' in kwargs:
            for name, value in kwargs.get('query_parameters', {}).items():
                if value is True:
                    query_parameters.append(KeyValueDecorator(name))
                else:
                    query_parameters.append(KeyValueDecorator(name, value))

        if 'regex_query_parameters' in kwargs:
            for name, value in kwargs.get('regex_query_parameters',
                                          {}).items():
                query_parameters.append(
                    KeyValueDecorator(name, value, regex=True))

        if 'regex_rewrite' in kwargs:
            if rewrite and rewrite != "/":
                self.ir.aconf.post_notice(
                    "Cannot specify both rewrite and regex_rewrite: using regex_rewrite and ignoring rewrite"
                )
            rewrite = ""
            rewrite_items = kwargs.get('regex_rewrite', {})
            regex_rewrite = {
                'pattern': rewrite_items.get('pattern', ''),
                'substitution': rewrite_items.get('substitution', '')
            }

        # ...and then init the superclass.
        super().__init__(ir=ir,
                         aconf=aconf,
                         rkey=rkey,
                         location=location,
                         service=service,
                         kind=kind,
                         name=name,
                         namespace=namespace,
                         metadata_labels=metadata_labels,
                         apiVersion=apiVersion,
                         headers=hdrs,
                         add_request_headers=add_request_hdrs,
                         add_response_headers=add_response_hdrs,
                         precedence=precedence,
                         rewrite=rewrite,
                         cluster_tag=cluster_tag,
                         query_parameters=query_parameters,
                         regex_rewrite=regex_rewrite,
                         **new_args)

        if 'outlier_detection' in kwargs:
            self.post_error(
                RichStatus.fromError("outlier_detection is not supported"))
Exemple #6
0
    def __init__(self, ir: 'IR', aconf: Config,
                 rkey: str,      # REQUIRED
                 name: str,      # REQUIRED
                 location: str,  # REQUIRED
                 namespace: Optional[str] = None,
                 metadata_labels: Optional[Dict[str, str]] = None,
                 kind: str="IRHTTPMapping",
                 apiVersion: str="getambassador.io/v2",   # Not a typo! See below.
                 precedence: int=0,
                 rewrite: str="/",
                 cluster_tag: Optional[str]=None,
                 **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource. So. Shallow copy the keys
        # we're going to allow from the incoming kwargs...

        new_args = {x: kwargs[x] for x in kwargs.keys() if x in IRHTTPMapping.AllowedKeys}

        # ...then set up the headers (since we need them to compute our group ID).
        hdrs = []
        add_request_hdrs = kwargs.get('add_request_headers', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(Header(name))
                else:
                    hdrs.append(Header(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(Header(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(Header(":authority", kwargs['host'], kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'service' in kwargs:
            svc = Service(ir.logger, kwargs['service'])

            if 'add_linkerd_headers' in kwargs:
                if kwargs['add_linkerd_headers'] is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                if 'add_linkerd_headers' in ir.ambassador_module and ir.ambassador_module.add_linkerd_headers is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        if 'method' in kwargs:
            hdrs.append(Header(":method", kwargs['method'], kwargs.get('method_regex', False)))

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.info(f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}")
            new_args['tls'] = ir.agent_origination_ctx.name

        # ...and then init the superclass.
        super().__init__(
            ir=ir, aconf=aconf, rkey=rkey, location=location,
            kind=kind, name=name, namespace=namespace, metadata_labels=metadata_labels,
            apiVersion=apiVersion, headers=hdrs, add_request_headers=add_request_hdrs,
            precedence=precedence, rewrite=rewrite, cluster_tag=cluster_tag,
            **new_args
        )

        if 'outlier_detection' in kwargs:
            self.post_error(RichStatus.fromError("outlier_detection is not supported"))
    def finalize(self, ir: 'IR', aconf: Config) -> List[IRCluster]:
        """
        Finalize a MappingGroup based on the attributes of its Mappings. Core elements get lifted into
        the Group so we can more easily build Envoy routes; host-redirect and shadow get handled, etc.

        :param ir: the IR we're working from
        :param aconf: the Config we're working from
        :return: a list of the IRClusters this Group uses
        """

        add_request_headers: Dict[str, Any] = {}
        add_response_headers: Dict[str, Any] = {}

        for mapping in sorted(self.mappings, key=lambda m: m.route_weight):
            # if verbose:
            #     self.ir.logger.debug("%s mapping %s" % (self, mapping.as_json()))

            for k in mapping.keys():
                if k.startswith('_') or mapping.skip_key(k) or (
                        k in IRHTTPMappingGroup.DoNotFlattenKeys):
                    # if verbose:
                    #     self.ir.logger.debug("%s: don't flatten %s" % (self, k))
                    continue

                # if verbose:
                #     self.ir.logger.debug("%s: flatten %s" % (self, k))

                self[k] = mapping[k]

            add_request_headers.update(mapping.get('add_request_headers', {}))
            add_response_headers.update(mapping.get('add_response_headers',
                                                    {}))

        if add_request_headers:
            self.add_request_headers = add_request_headers
        if add_response_headers:
            self.add_response_headers = add_response_headers
        if self.get('load_balancer', None) is None:
            self['load_balancer'] = ir.ambassador_module.load_balancer
        # if verbose:
        #     self.ir.logger.debug("%s after flattening %s" % (self, self.as_json()))

        total_weight = 0.0
        unspecified_mappings = 0

        # If no rewrite was given at all, default the rewrite to "/", so /, so e.g., if we map
        # /prefix1/ to the service service1, then http://ambassador.example.com/prefix1/foo/bar
        # would effectively be written to http://service1/foo/bar
        #
        # If they did give a rewrite, leave it alone so that the Envoy config can correctly
        # handle an empty rewrite as no rewriting at all.

        if 'rewrite' not in self:
            self.rewrite = "/"

        # OK. Save some typing with local variables for default labels and our labels...
        labels: Dict[str, Any] = self.get('labels', None)

        if not labels:
            # No labels. Use the default label domain to see if we have some valid defaults.
            defaults = ir.ambassador_module.get_default_labels()

            if defaults:
                domain = ir.ambassador_module.get_default_label_domain()

                self.labels = {domain: [{'defaults': defaults}]}
        else:
            # Walk all the domains in our labels, and prepend the defaults, if any.
            # ir.logger.info("%s: labels %s" % (self.as_json(), labels))

            for domain in labels.keys():
                defaults = ir.ambassador_module.get_default_labels(domain)
                ir.logger.debug("%s: defaults %s" % (domain, defaults))

                if defaults:
                    ir.logger.debug("%s: labels %s" % (domain, labels[domain]))

                    for label in labels[domain]:
                        ir.logger.debug("%s: label %s" % (domain, label))

                        lkeys = label.keys()
                        if len(lkeys) > 1:
                            err = RichStatus.fromError(
                                "label has multiple entries (%s) instead of just one"
                                % lkeys)
                            aconf.post_error(err, self)

                        lkey = list(lkeys)[0]

                        if lkey.startswith('v0_ratelimit_'):
                            # Don't prepend defaults, as this was imported from a V0 rate_limit.
                            continue

                        label[lkey] = defaults + label[lkey]

        if self.shadows:
            # Only one shadow is supported right now.
            shadow = self.shadows[0]

            # The shadow is an IRMapping. Save the cluster for it.
            shadow.cluster = self.add_cluster_for_mapping(ir,
                                                          aconf,
                                                          shadow,
                                                          marker='shadow')

        # We don't need a cluster for host_redirect: it's just a name to redirect to.

        redir = self.get('host_redirect', None)

        if not redir:
            for mapping in self.mappings:
                mapping.cluster = self.add_cluster_for_mapping(
                    ir, aconf, mapping)

            self.logger.debug(f"Normalizing weights in mappings now...")
            if not self.normalize_weights_in_mappings():
                self.post_error(
                    f"Could not normalize mapping weights, ignoring...")
                return []

            return list([mapping.cluster for mapping in self.mappings])
        else:
            # Flatten the case_sensitive field for host_redirect if it exists
            if 'case_sensitive' in redir:
                self['case_sensitive'] = redir['case_sensitive']

            return []
Exemple #8
0
def config(config_dir_path: Parameter.REQUIRED,
           output_json_path: Parameter.REQUIRED,
           *,
           debug=False,
           debug_scout=False,
           check=False,
           k8s=False,
           ir=None,
           aconf=None,
           exit_on_error=False):
    """
    Generate an Envoy configuration

    :param config_dir_path: Configuration directory to scan for Ambassador YAML files
    :param output_json_path: Path to output envoy.json
    :param debug: If set, generate debugging output
    :param debug_scout: If set, generate debugging output when talking to Scout
    :param check: If set, generate configuration only if it doesn't already exist
    :param k8s: If set, assume configuration files are annotated K8s manifests
    :param exit_on_error: If set, will exit with status 1 on any configuration error
    :param ir: Pathname to which to dump the IR (not dumped if not present)
    :param aconf: Pathname to which to dump the aconf (not dumped if not present)
    """

    if debug:
        logger.setLevel(logging.DEBUG)

    if debug_scout:
        logging.getLogger('ambassador.scout').setLevel(logging.DEBUG)

    try:
        logger.debug("CHECK MODE  %s" % check)
        logger.debug("CONFIG DIR  %s" % config_dir_path)
        logger.debug("OUTPUT PATH %s" % output_json_path)

        dump_aconf: Optional[str] = aconf
        dump_ir: Optional[str] = ir

        # Bypass the existence check...
        output_exists = False

        if check:
            # ...oh no wait, they explicitly asked for the existence check!
            # Assume that the file exists (ie, we'll do nothing) unless we
            # determine otherwise.
            output_exists = True

            try:
                parse_json(open(output_json_path, "r").read())
            except FileNotFoundError:
                logger.debug("output file does not exist")
                output_exists = False
            except OSError:
                logger.warning("output file is not sane?")
                output_exists = False
            except json.decoder.JSONDecodeError:
                logger.warning("output file is not valid JSON")
                output_exists = False

            logger.info("Output file %s" %
                        ("exists" if output_exists else "does not exist"))

        rc = RichStatus.fromError("impossible error")

        if not output_exists:
            # Either we didn't need to check, or the check didn't turn up
            # a valid config. Regenerate.
            logger.info("Generating new Envoy configuration...")

            aconf = Config()
            fetcher = ResourceFetcher(logger, aconf)
            fetcher.load_from_filesystem(config_dir_path, k8s=k8s)
            aconf.load_all(fetcher.sorted())

            if dump_aconf:
                with open(dump_aconf, "w") as output:
                    output.write(aconf.as_json())
                    output.write("\n")

            # If exit_on_error is set, log _errors and exit with status 1
            if exit_on_error and aconf.errors:
                raise Exception("errors in: {0}".format(', '.join(
                    aconf.errors.keys())))

            secret_handler = NullSecretHandler(logger, config_dir_path,
                                               config_dir_path, "0")

            ir = IR(aconf,
                    file_checker=file_checker,
                    secret_handler=secret_handler)

            if dump_ir:
                with open(dump_ir, "w") as output:
                    output.write(ir.as_json())
                    output.write("\n")

            # clize considers kwargs with False for default value as flags,
            # resulting in the logic below.
            # https://clize.readthedocs.io/en/stable/basics.html#accepting-flags

            logger.info("Writing envoy V2 configuration")
            v2config = V2Config(ir)
            rc = RichStatus.OK(msg="huh_v2")

            if rc:
                with open(output_json_path, "w") as output:
                    output.write(v2config.as_json())
                    output.write("\n")
            else:
                logger.error("Could not generate new Envoy configuration: %s" %
                             rc.error)

        scout = Scout()
        result = scout.report(action="config", mode="cli")
        show_notices(result)
    except Exception as e:
        handle_exception("EXCEPTION from config",
                         e,
                         config_dir_path=config_dir_path,
                         output_json_path=output_json_path)

        # This is fatal.
        sys.exit(1)