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)
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"))
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"))
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"))
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"))
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 []
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)