diff --git a/build-aux/Dockerfile b/build-aux/Dockerfile index fb14096936..209295246d 100644 --- a/build-aux/Dockerfile +++ b/build-aux/Dockerfile @@ -46,9 +46,7 @@ COPY --from=envoy /usr/local/bin/envoy-static-stripped /usr/local/bin/envoy ENV KUBECONFIG=/buildroot/kubeconfig.yaml # XXX: this will go away -RUN mkdir -p /ambassador/sidecars && \ - ln -s /buildroot/ambassador/python/post_update.py /ambassador/post_update.py && \ - ln -s /buildroot/ambassador/python/watch_hook.py /ambassador/watch_hook.py +RUN mkdir -p /ambassador/sidecars RUN adduser dw --disabled-password # SUDO_USERS HOSTS=(AS_USER) TAGS COMMANDS @@ -103,9 +101,7 @@ ADD post-compile.sh post-compile.sh RUN bash post-compile.sh # XXX: this will go away -RUN mkdir -p /ambassador/sidecars && \ - ln -s /buildroot/ambassador/python/post_update.py /ambassador/post_update.py && \ - ln -s /buildroot/ambassador/python/watch_hook.py /ambassador/watch_hook.py +RUN mkdir -p /ambassador/sidecars # These will be extracted into the optimized image later ADD manifests/emissary/emissary-crds.yaml.in manifests/emissary/emissary-crds.yaml @@ -127,10 +123,6 @@ RUN apk --no-cache add bash curl python3=${py_version} libcap htop RUN apk upgrade --no-cache COPY --from=artifacts /usr/lib/libyaml* /usr/lib/ -# Other installers -COPY --from=artifacts /opt/image-build /opt/image-build -RUN /opt/image-build/install.sh - # External Python packages we use COPY --from=artifacts /usr/lib/python3.11/site-packages /usr/lib/python3.11/site-packages diff --git a/build-aux/install.sh b/build-aux/install.sh deleted file mode 100755 index 1be4fef134..0000000000 --- a/build-aux/install.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Expections -# - Exist as /opt/image-build/install.sh -# - Get called from post-install.sh -# - Run all scripts in /opt/image-build/installers/ -# - Be run once as part of prod docker build -# - Be run repeatedly in the builder container -# See also: installers/README.md - -set -e - -cd /opt/image-build/installers -shopt -s nullglob -for installer in /opt/image-build/installers/*; do - if [ -x "$installer" ]; then - echo Installing $(basename "$installer") - "$installer" - fi -done diff --git a/build-aux/installers/README.md b/build-aux/installers/README.md deleted file mode 100644 index 65c67f8b55..0000000000 --- a/build-aux/installers/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Installers - -Executables (scripts) in this directory are intended to install and configure additional packages needed by non-OSS modules. AES uses this to add a few features. - -These installers are run - -- During `docker build` of the production image as an early step -- From `post-compile.sh` (which is run after every compilation of Go code) repeatedly, to keep the builder container up-to-date - -OSS Ambassador doesn't need any installers here because this is the base module: everything it needs to install is listed directly in the Dockerfile. diff --git a/cmd/k8sregistryctl/main.go b/cmd/k8sregistryctl/main.go deleted file mode 100644 index 239789632e..0000000000 --- a/cmd/k8sregistryctl/main.go +++ /dev/null @@ -1,202 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "text/template" - "time" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/emissary-ingress/emissary/v3/pkg/k8s" - "github.com/emissary-ingress/emissary/v3/pkg/kubeapply" -) - -var tmpl = template.Must(template. - New("docker-registry.yaml"). - Parse(`--- -apiVersion: v1 -kind: Namespace -metadata: - name: docker-registry ---- -apiVersion: v1 -kind: Service -metadata: - namespace: docker-registry - name: registry -spec: - type: NodePort - selector: - app: registry - ports: - - port: 5000 - nodePort: 31000 ---- -apiVersion: apps/v1 -# XXX: Avoid using a StatefulSet if possible, because kubeapply -# doesn't know how to wait for them. -kind: {{ if eq .Storage "pvc" }}StatefulSet{{ else }}Deployment{{ end }} -metadata: - namespace: docker-registry - name: registry -spec: - replicas: 1 -{{ if eq .Storage "pvc" }} # XXX: StatefulSet - serviceName: registry -{{ end }} - selector: - matchLabels: - app: registry - template: - metadata: - name: registry - labels: - app: registry - spec: - containers: - - name: registry - image: docker.io/library/registry:2 - ports: - - containerPort: 5000 - volumeMounts: - - mountPath: /var/lib/registry - name: registry-data - volumes: - - name: registry-data -{{ if eq .Storage "pvc" | not }} - # On Kubeception clusters, there is only 1 node, so a - # hostPath is fine. - hostPath: - path: /var/lib/registry -{{ else }} - persistentVolumeClaim: - claimName: registry-data ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: registry-data - namespace: docker-registry -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi -{{ end }} -`)) - -func main() { - argparser := &cobra.Command{ - Use: os.Args[0], - Short: "Manage a private in-cluster registry", - SilenceErrors: true, - SilenceUsage: true, - } - argparser.AddCommand(func() *cobra.Command { - var ( - argStorage string - ) - subparser := &cobra.Command{ - Use: "up", - Short: "Initialize the registry, and create a port-forward to it", - Args: cobra.ExactArgs(0), - RunE: func(cobraCmd *cobra.Command, _ []string) error { - var kpfTarget string - switch argStorage { - case "pvc": - kpfTarget = "statefulset/registry" - case "hostPath": - kpfTarget = "deployment/registry" - default: - return errors.Errorf("invalid --storage=%q: must be one of 'pvc' or 'hostPath'", argStorage) - } - - kubeinfo := k8s.NewKubeInfo("", "", "") - - // Part 1: Apply the YAML - // - // pkg/kubeapply is annoyingly oriented around actual physical files >:( - tmpdir, err := ioutil.TempDir("", filepath.Base(os.Args[0])) - if err != nil { - return err - } - defer os.RemoveAll(tmpdir) - yamlFile, err := os.OpenFile(filepath.Join(tmpdir, "docker-registry.yaml"), os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - return err - } - err = tmpl.Execute(yamlFile, map[string]interface{}{ - "Storage": argStorage, - }) - yamlFile.Close() - if err != nil { - return err - } - err = kubeapply.Kubeapply( - cobraCmd.Context(), // context - kubeinfo, // kubeinfo - time.Minute, // perPhaseTimeout - false, // debug - false, // dryRun - yamlFile.Name(), // files - ) - if err != nil { - return err - } - - // Part 2: Set up the port-forward - args, err := kubeinfo.GetKubectlArray("port-forward", - "--namespace=docker-registry", - kpfTarget, - "31000:5000") - if err != nil { - return err - } - cmd := exec.Command("kubectl", args...) - cmd.Stdout, err = os.OpenFile(filepath.Join(os.TempDir(), filepath.Base(os.Args[0])+".log"), os.O_CREATE|os.O_WRONLY, 0666) - if err != nil { - return err - } - cmd.Stderr = cmd.Stdout - if err := cmd.Start(); err != nil { - return err - } - for { - _, httpErr := http.Get("http://localhost:31000/") - if httpErr == nil { - fmt.Fprintln(os.Stderr, "port-forward ready") - break - } else { - fmt.Fprintln(os.Stderr, "waiting for port-forward to become ready...") - time.Sleep(time.Second) - } - } - return nil - }, - } - subparser.Flags().StringVar(&argStorage, "storage", "", "Which type of storage to use ('pvc' or 'hostPath')") - return subparser - }()) - argparser.AddCommand(&cobra.Command{ - Use: "down", - Short: "Shut down the port-forward to the registry", - Args: cobra.ExactArgs(0), - RunE: func(_ *cobra.Command, _ []string) error { - cmd := exec.Command("killall", "kubectl") // XXX - cmd.Stdout = os.Stderr - cmd.Stderr = os.Stderr - return cmd.Run() - }, - }) - if err := argparser.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%s: error: %v\n", os.Args[0], err) - os.Exit(1) - } -} diff --git a/post-compile.sh b/post-compile.sh index 73973307f2..250821498b 100644 --- a/post-compile.sh +++ b/post-compile.sh @@ -3,7 +3,6 @@ set -e busyprograms=( kubestatus - watt apiext ) sudo install -D -t /opt/ambassador/bin/ /buildroot/bin/busyambassador @@ -17,18 +16,3 @@ for busyprogram in "${busyprograms[@]}"; do done sudo install /buildroot/bin/capabilities_wrapper /opt/ambassador/bin/wrapper - -# Copy installer support into /opt/image-build to be run at docker build for the -# production image. Then run the installers for the builder container. -# Note: When this (ambassador's) post-compile runs, it always runs first, and -# every other post-compile runs as well. So this is the place to recreate the -# /opt/image-build tree from scratch so the builder container stays valid. -sudo rm -rf /opt/image-build -sudo install -D -t /opt/image-build /buildroot/ambassador/build-aux/install.sh -sudo cp -a /buildroot/ambassador/build-aux/installers /opt/image-build/ -sudo /opt/image-build/install.sh - -# run any extra, local post-compile task -if [ -f post-compile.local.sh ] ; then - bash post-compile.local.sh -fi diff --git a/python/ambassador/fetch/fetcher.py b/python/ambassador/fetch/fetcher.py index 4be8bc0b56..1054d19445 100644 --- a/python/ambassador/fetch/fetcher.py +++ b/python/ambassador/fetch/fetcher.py @@ -430,7 +430,6 @@ def handle_consul_service(self, consul_rkey: str, consul_object: AnyDict) -> Non # services and endpoints together (as it should!!). # # Note that we currently trust the association ID to contain the datacenter name. - # That's a function of the watch_hook putting it there. normalized_endpoints: Dict[str, List[Dict[str, Any]]] = {} diff --git a/python/ambassador/utils.py b/python/ambassador/utils.py index 5ea066e5e8..487dfb5a10 100644 --- a/python/ambassador/utils.py +++ b/python/ambassador/utils.py @@ -859,10 +859,6 @@ def still_needed(self, resource: "IRResource", secret_name: str, namespace: str) still_needed: remember that a given secret is still needed, so that we can tell watt to keep paying attention to it. - The default implementation doesn't do much of anything, because it assumes that we're - not running in the watch_hook, so watt has already been told everything it needs to be - told. This should be OK for everything that's not the watch_hook. - :param resource: referencing resource :param secret_name: name of the secret :param namespace: namespace of the secret @@ -879,8 +875,6 @@ def cache_secret(self, resource: "IRResource", secret_info: SecretInfo) -> Saved cache_secret: stash the SecretInfo from load_secret into Ambassador’s internal cache, so that we don’t have to call load_secret again if we need it again. - The default implementation should be usable by everything that's not the watch_hook. - :param resource: referencing resource :param secret_info: SecretInfo returned from load_secret :return: SavedSecret diff --git a/python/ambassador_cli/grab_snapshots.py b/python/ambassador_cli/grab_snapshots.py deleted file mode 100644 index 727bdcb310..0000000000 --- a/python/ambassador_cli/grab_snapshots.py +++ /dev/null @@ -1,188 +0,0 @@ -#!python - -# Copyright 2019-2020 Datawire. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -######## -# This is a debugging tool that can grab snapshots and Envoy configs from -# Ambassador's configuration directory, sanitize secrets out of the snapshots, -# and hand back a compressed tarfile that the user can hand back to Datawire. -######## - -import functools -import glob -import json -import os -import sys -import tarfile - -import click - -from ambassador.utils import dump_json - -# Use this instead of click.option -click_option = functools.partial(click.option, show_default=True) -click_option_no_default = functools.partial(click.option, show_default=False) - - -def sanitize_snapshot(snapshot: dict): - sanitized = {} - - # Consul is pretty easy. Just sort, using service-dc as the sort key. - consul_elements = snapshot.get("Consul") - - if consul_elements: - csorted = {} - - for key, value in consul_elements.items(): - csorted[key] = sorted(value, key=lambda x: f'{x["Service"]-x["Id"]}') - - sanitized["Consul"] = csorted - - # Make sure we grab Deltas and Invalid -- these should really be OK as-is. - - for key in ["Deltas", "Invalid"]: - if key in snapshot: - sanitized[key] = snapshot[key] - - # Kube is harder because we need to sanitize Kube secrets. - kube_elements = snapshot.get("Kubernetes") - - if kube_elements: - ksorted = {} - - for key, value in kube_elements.items(): - if not value: - continue - - if key == "secret": - for secret in value: - if "data" in secret: - data = secret["data"] - - for k in data.keys(): - data[k] = f"-sanitized-{k}-" - - metadata = secret.get("metadata", {}) - annotations = metadata.get("annotations", {}) - - # Wipe the last-applied-configuration annotation, too, because it - # often contains the secret data. - if "kubectl.kubernetes.io/last-applied-configuration" in annotations: - annotations[ - "kubectl.kubernetes.io/last-applied-configuration" - ] = "--sanitized--" - - # All the sanitization above happened in-place in value, so we can just - # sort it. - ksorted[key] = sorted(value, key=lambda x: x.get("metadata", {}).get("name")) - - sanitized["Kubernetes"] = ksorted - - return sanitized - - -# Helper to open a snapshot.yaml and sanitize it. -def helper_snapshot(path: str) -> str: - snapshot = json.loads(open(path, "r").read()) - - return dump_json(sanitize_snapshot(snapshot)) - - -# Helper to open a problems.json and sanitize the snapshot it contains. -def helper_problems(path: str) -> str: - bad_dict = json.loads(open(path, "r").read()) - - bad_dict["snapshot"] = sanitize_snapshot(bad_dict["snapshot"]) - - return dump_json(bad_dict) - - -# Helper to just copy a file. -def helper_copy(path: str) -> str: - return open(path, "r").read() - - -# Open a tarfile for output... -@click.command(help="Grab, and sanitize, Ambassador snapshots for later debugging") -@click_option("--debug/--no-debug", default=True, help="enable debugging") -@click_option( - "-o", - "--output-path", - "--output", - type=click.Path(writable=True), - default="sanitized.tgz", - help="output path", -) -@click_option( - "-s", - "--snapshot-dir", - "--snapshot", - type=click.Path(exists=True, dir_okay=True, file_okay=False), - help="snapshot directory to read", -) -def main(snapshot_dir: str, debug: bool, output_path: str) -> None: - if not snapshot_dir: - config_base_dir = os.environ.get("AMBASSADOR_CONFIG_BASE_DIR", "/ambassador") - snapshot_dir = os.path.join(config_base_dir, "snapshots") - - if debug: - print(f"Saving sanitized snapshots from {snapshot_dir} to {output_path}") - - with tarfile.open(output_path, "w:gz") as archive: - # ...then iterate any snapshots, sanitize, and stuff 'em in the tarfile. - # Note that the '.yaml' on the snapshot file name is a misnomer: when - # watt is involved, they're actually JSON. It's a long story. - - some_found = False - - interesting_things = [ - ("snap*yaml", helper_snapshot), - ("problems*json", helper_problems), - ("econf*json", helper_copy), - ("diff*txt", helper_copy), - ] - - for pattern, helper in interesting_things: - for path in glob.glob(os.path.join(snapshot_dir, pattern)): - some_found = True - - # The tarfile can be flat, rather than embedding everything - # in a directory with a fixed name. - b = os.path.basename(path) - - if debug: - print(f"...{b}") - - sanitized = helper(path) - - if sanitized: - _, ext = os.path.splitext(path) - sanitized_name = f"sanitized{ext}" - - with open(sanitized_name, "w") as tmp: - tmp.write(sanitized) - - archive.add(sanitized_name, arcname=b) - os.unlink(sanitized_name) - - if not some_found: - sys.stderr.write(f"No snapshots found in {snapshot_dir}?\n") - sys.exit(1) - - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/python/post_update.py b/python/post_update.py deleted file mode 100644 index 4033b24db7..0000000000 --- a/python/post_update.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import sys - -import requests - -from ambassador.utils import parse_bool - - -def usage(program): - sys.stderr.write(f"Usage: {program} [--watt|--k8s|--fs] UPDATE_URL\n") - sys.stderr.write( - "Notify `diagd` (and `amb-sidecar`, if AES) that a new WATT snapshot is available at UPDATE_URL.\n" - ) - sys.exit(1) - - -base_host = os.environ.get("DEV_AMBASSADOR_EVENT_HOST", "http://localhost:8877") -base_path = os.environ.get("DEV_AMBASSADOR_EVENT_PATH", "_internal/v0") - -sidecar_host = os.environ.get("DEV_AMBASSADOR_SIDECAR_HOST", "http://localhost:8500") -sidecar_path = os.environ.get("DEV_AMBASSADOR_SIDECAR_PATH", "_internal/v0") - -url_type = "update" -arg_key = "url" - -program = os.path.basename(sys.argv[0]) -args = sys.argv[1:] - -while args and args[0].startswith("--"): - arg = args.pop(0) - - if arg == "--k8s": - # Already set up. - pass - elif arg == "--watt": - url_type = "watt" - elif arg == "--fs": - url_type = "fs" - arg_key = "path" - else: - usage(program) - -if len(args) != 1: - usage(program) - -urls = [f"{base_host}/{base_path}/{url_type}"] - -if parse_bool(os.environ.get("EDGE_STACK", "false")) or os.path.exists("/ambassador/.edge_stack"): - urls.append(f"{sidecar_host}/{sidecar_path}/{url_type}") - -exitcode = 0 - -for url in urls: - r = requests.post(url, params={arg_key: args[0]}) - - if r.status_code != 200: - sys.stderr.write("failed to update %s: %d: %s" % (r.url, r.status_code, r.text)) - exitcode = 1 - -sys.exit(exitcode) diff --git a/python/setup.py b/python/setup.py index 1c18158f44..6e4a15c3e3 100644 --- a/python/setup.py +++ b/python/setup.py @@ -54,7 +54,6 @@ def collect_data_files(dirpath): "console_scripts": [ "ambassador=ambassador_cli.ambassador:main", "diagd=ambassador_diag.diagd:main", - "grab-snapshots=ambassador_cli.grab_snapshots:main", ] }, author="datawire.io", diff --git a/python/watch_hook.py b/python/watch_hook.py deleted file mode 100644 index 32cdd7bc25..0000000000 --- a/python/watch_hook.py +++ /dev/null @@ -1,398 +0,0 @@ -#!/usr/bin/python - -import logging -import os -import sys -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple - -from ambassador import IR, Config -from ambassador.fetch import ResourceFetcher -from ambassador.utils import ParsedService as Service -from ambassador.utils import SavedSecret, SecretHandler, SecretInfo, dump_json - -if TYPE_CHECKING: - from ambassador.ir.irresource import IRResource # pragma: no cover - -# default AES's Secret name -# (by default, we assume it will be in the same namespace as Ambassador) -DEFAULT_AES_SECRET_NAME = "ambassador-edge-stack" - -# the name of some env vars that can be used for overriding -# the AES's Secret name/namespace -ENV_AES_SECRET_NAME = "AMBASSADOR_AES_SECRET_NAME" -ENV_AES_SECRET_NAMESPACE = "AMBASSADOR_AES_SECRET_NAMESPACE" - -# the name of some env vars that can be used for overriding -# the Cloud Connect Token resource name/namespace -ENV_CLOUD_CONNECT_TOKEN_RESOURCE_NAME = "AGENT_CONFIG_RESOURCE_NAME" -ENV_CLOUD_CONNECT_TOKEN_RESOURCE_NAMESPACE = "AGENT_NAMESPACE" -DEFAULT_CLOUD_CONNECT_TOKEN_RESOURCE_NAME = "ambassador-agent-cloud-token" - -# Fake SecretHandler for our fake IR, below. - - -class SecretRecorder(SecretHandler): - def __init__(self, logger: logging.Logger) -> None: - super().__init__(logger, "-source_root-", "-cache_dir-", "0") - self.needed: Dict[Tuple[str, str], SecretInfo] = {} - - # Record what was requested, and always return success. - def load_secret( - self, resource: "IRResource", secret_name: str, namespace: str - ) -> Optional[SecretInfo]: - self.logger.debug( - "SecretRecorder (%s %s): load secret %s in namespace %s" - % (resource.kind, resource.name, secret_name, namespace) - ) - - return self.record_secret(secret_name, namespace) - - def record_secret(self, secret_name: str, namespace: str) -> Optional[SecretInfo]: - secret_key = (secret_name, namespace) - - if secret_key not in self.needed: - self.needed[secret_key] = SecretInfo( - secret_name, namespace, "needed-secret", "-crt-", "-key-", decode_b64=False - ) - return self.needed[secret_key] - - # Secrets that're still needed also get recorded. - def still_needed(self, resource: "IRResource", secret_name: str, namespace: str) -> None: - self.logger.debug( - "SecretRecorder (%s %s): secret %s in namespace %s is still needed" - % (resource.kind, resource.name, secret_name, namespace) - ) - - self.record_secret(secret_name, namespace) - - # Never cache anything. - def cache_secret(self, resource: "IRResource", secret_info: SecretInfo): - self.logger.debug( - "SecretRecorder (%s %s): skipping cache step for secret %s in namespace %s" - % (resource.kind, resource.name, secret_info.name, secret_info.namespace) - ) - - return SavedSecret( - secret_info.name, - secret_info.namespace, - "-crt-path-", - "-key-path-", - "-user-path-", - "-root-crt-path", - {"tls.crt": "-crt-", "tls.key": "-key-", "user.key": "-user-"}, - ) - - -# XXX Sooooo there's some ugly stuff here. -# -# We need to do a little bit of the same work that the IR does for things like -# managing Resolvers and parsing service names. However, we really don't want to -# do all the work of instantiating an IR. -# -# The solution here is to subclass the IR and take advantage of the watch_only -# initialization keyword, which skips the hard parts of building an IR. - - -class FakeIR(IR): - def __init__(self, aconf: Config, logger=None) -> None: - # If we're asked about a secret, record interest in that secret. - self.secret_recorder = SecretRecorder(logger) - - # If we're asked about a file, it's good. - file_checker = lambda path: True - - super().__init__( - aconf, - logger=logger, - watch_only=True, - secret_handler=self.secret_recorder, - file_checker=file_checker, - ) - - # Don't bother actually saving resources that come up when working with - # the faked modules. - def save_resource(self, resource: "IRResource") -> "IRResource": - return resource - - -class WatchHook: - def __init__(self, logger, yaml_stream) -> None: - # Watch management - - self.logger = logger - - self.consul_watches: List[Dict[str, str]] = [] - self.kube_watches: List[Dict[str, str]] = [] - - self.load_yaml(yaml_stream) - - def add_kube_watch( - self, - what: str, - kind: str, - namespace: Optional[str], - field_selector: Optional[str] = None, - label_selector: Optional[str] = None, - ) -> None: - watch = {"kind": kind} - - if namespace: - watch["namespace"] = namespace - - if field_selector: - watch["field-selector"] = field_selector - - if label_selector: - watch["label-selector"] = label_selector - - self.logger.debug(f"{what}: add watch {watch}") - self.kube_watches.append(watch) - - def load_yaml(self, yaml_stream): - self.aconf = Config() - - fetcher = ResourceFetcher(self.logger, self.aconf, watch_only=True) - fetcher.parse_watt(yaml_stream.read()) - - self.aconf.load_all(fetcher.sorted()) - - # We can lift mappings straight from the aconf... - mappings = self.aconf.get_config("mappings") or {} - - # ...but we need the fake IR to deal with resolvers and TLS contexts. - self.fake = FakeIR(self.aconf, logger=self.logger) - - self.logger.debug("IR: %s" % self.fake.as_json()) - - resolvers = self.fake.resolvers - contexts = self.fake.tls_contexts - - self.logger.debug(f"mappings: {len(mappings)}") - self.logger.debug(f"resolvers: {len(resolvers)}") - self.logger.debug(f"contexts: {len(contexts)}") - - global_resolver = self.fake.ambassador_module.get("resolver", None) - - global_label_selector = os.environ.get("AMBASSADOR_LABEL_SELECTOR", "") - self.logger.debug("label-selector: %s" % global_label_selector) - - cloud_connect_token_resource_name = os.getenv( - ENV_CLOUD_CONNECT_TOKEN_RESOURCE_NAME, DEFAULT_CLOUD_CONNECT_TOKEN_RESOURCE_NAME - ) - cloud_connect_token_resource_namespace = os.getenv( - ENV_CLOUD_CONNECT_TOKEN_RESOURCE_NAMESPACE, Config.ambassador_namespace - ) - self.logger.debug( - f"cloud-connect-token: need configmap/secret {cloud_connect_token_resource_name}.{cloud_connect_token_resource_namespace}" - ) - self.add_kube_watch( - f"ConfigMap {cloud_connect_token_resource_name}", - "configmap", - namespace=cloud_connect_token_resource_namespace, - field_selector=f"metadata.name={cloud_connect_token_resource_name}", - ) - self.add_kube_watch( - f"Secret {cloud_connect_token_resource_name}", - "secret", - namespace=cloud_connect_token_resource_namespace, - field_selector=f"metadata.name={cloud_connect_token_resource_name}", - ) - - # watch the AES Secret if the edge stack is running - if self.fake.edge_stack_allowed: - aes_secret_name = os.getenv(ENV_AES_SECRET_NAME, DEFAULT_AES_SECRET_NAME) - aes_secret_namespace = os.getenv(ENV_AES_SECRET_NAMESPACE, Config.ambassador_namespace) - self.logger.debug( - f"edge stack detected: need secret {aes_secret_name}.{aes_secret_namespace}" - ) - self.add_kube_watch( - f"Secret {aes_secret_name}", - "secret", - namespace=aes_secret_namespace, - field_selector=f"metadata.name={aes_secret_name}", - ) - - # Walk hosts. - for host in self.fake.get_hosts(): - sel = host.get("selector") or {} - match_labels = sel.get("matchLabels") or {} - - label_selectors: List[str] = [] - - if global_label_selector: - label_selectors.append(global_label_selector) - - if match_labels: - label_selectors += [f"{l}={v}" for l, v in match_labels.items()] - - label_selector = ",".join(label_selectors) if label_selectors else None - - for wanted_kind in ["service", "secret"]: - self.add_kube_watch( - f"Host {host.name}", wanted_kind, host.namespace, label_selector=label_selector - ) - - for mname, mapping in mappings.items(): - res_name = mapping.get("resolver", None) - res_source = "mapping" - - if not res_name: - res_name = global_resolver - res_source = "defaults" - - ctx_name = mapping.get("tls", None) - - self.logger.debug( - f"Mapping {mname}: resolver {res_name} from {res_source}, service {mapping.service}, tls {ctx_name}" - ) - - if res_name: - resolver = resolvers.get(res_name, None) - self.logger.debug(f"-> resolver {resolver}") - - if resolver: - svc = Service(logger, mapping.service, ctx_name) - - if resolver.kind == "ConsulResolver": - self.logger.debug(f"Mapping {mname} uses Consul resolver {res_name}") - - # At the moment, we stuff the resolver's datacenter into the association - # ID for this watch. The ResourceFetcher relies on that. - - assert resolver.datacenter - assert resolver.address - assert svc.hostname - - self.consul_watches.append( - { - "id": resolver.datacenter, - "consul-address": resolver.address, - "datacenter": resolver.datacenter, - "service-name": svc.hostname, - } - ) - elif resolver.kind == "KubernetesEndpointResolver": - hostname = svc.hostname - namespace = Config.ambassador_namespace - - if not hostname: - # This is really kind of impossible. - self.logger.error( - f"KubernetesEndpointResolver {res_name} has no 'hostname'" - ) - continue - - if "." in hostname: - (hostname, namespace) = hostname.split(".", 2)[0:2] - - self.logger.debug( - f"...kube endpoints: svc {svc.hostname} -> host {hostname} namespace {namespace}" - ) - - self.add_kube_watch( - f"endpoint", - "endpoints", - namespace, - label_selector=global_label_selector, - field_selector=f"metadata.name={hostname}", - ) - - for secret_key, secret_info in self.fake.secret_recorder.needed.items(): - self.logger.debug(f"need secret {secret_info.name}.{secret_info.namespace}") - - self.add_kube_watch( - f"needed secret", - "secret", - secret_info.namespace, - label_selector=global_label_selector, - field_selector=f"metadata.name={secret_info.name}", - ) - - if self.fake.edge_stack_allowed: - # If the edge stack is allowed, make sure we watch for our fallback context. - self.add_kube_watch( - "Fallback TLSContext", "TLSContext", namespace=Config.ambassador_namespace - ) - - ambassador_basedir = os.environ.get("AMBASSADOR_CONFIG_BASE_DIR", "/ambassador") - - if os.path.exists(os.path.join(ambassador_basedir, ".ambassadorinstallations_ok")): - self.add_kube_watch( - "AmbassadorInstallations", - "ambassadorinstallations.getambassador.io", - Config.ambassador_namespace, - ) - - ambassador_knative_requested = ( - os.environ.get("AMBASSADOR_KNATIVE_SUPPORT", "-unset-").lower() == "true" - ) - - if ambassador_knative_requested: - self.logger.debug("Looking for Knative support...") - - if os.path.exists(os.path.join(ambassador_basedir, ".knative_clusteringress_ok")): - # Watch for clusteringresses.networking.internal.knative.dev in any namespace and with any labels. - - self.logger.debug("watching for clusteringresses.networking.internal.knative.dev") - self.add_kube_watch( - "Knative clusteringresses", - "clusteringresses.networking.internal.knative.dev", - None, - ) - - if os.path.exists(os.path.join(ambassador_basedir, ".knative_ingress_ok")): - # Watch for ingresses.networking.internal.knative.dev in any namespace and - # with any labels. - - self.add_kube_watch( - "Knative ingresses", "ingresses.networking.internal.knative.dev", None - ) - - self.watchset: Dict[str, List[Dict[str, str]]] = { - "kubernetes-watches": self.kube_watches, - "consul-watches": self.consul_watches, - } - - save_dir = os.environ.get("AMBASSADOR_WATCH_DIR", "/tmp") - - if save_dir: - watchset = dump_json(self.watchset) - with open(os.path.join(save_dir, "watch.json"), "w") as output: - output.write(watchset) - - -#### Mainline. - -if __name__ == "__main__": - loglevel = logging.INFO - - args = sys.argv[1:] - - if args: - if args[0] == "--debug": - loglevel = logging.DEBUG - args.pop(0) - elif args[0].startswith("--"): - raise Exception(f"Usage: {os.path.basename(sys.argv[0])} [--debug] [path]") - - logging.basicConfig( - level=loglevel, - format="%(asctime)s watch-hook %(levelname)s: %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - - alogger = logging.getLogger("ambassador") - alogger.setLevel(logging.INFO) - - logger = logging.getLogger("watch_hook") - logger.setLevel(loglevel) - - yaml_stream = sys.stdin - - if args: - yaml_stream = open(args[0], "r") - - wh = WatchHook(logger, yaml_stream) - - watchset = dump_json(wh.watchset) - sys.stdout.write(watchset) diff --git a/watt.json b/watt.json deleted file mode 100644 index fa9f1964c5..0000000000 --- a/watt.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Watches": [ - { - "Type": "kubernetes", - "Spec": { - "Namespace": "default", - "Kinds": [""] - } - }, - { - "Type": "consul.service-nodes", - "Spec": { - "Service": "" - } - } - ] -} \ No newline at end of file