gloo_ingress_adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +133 -0
- data/lib/gloo_ingress_adapter/client_factory.rb +42 -0
- data/lib/gloo_ingress_adapter/controller.rb +235 -0
- data/lib/gloo_ingress_adapter/resource_observer.rb +87 -0
- data/lib/gloo_ingress_adapter/route_table_builder.rb +102 -0
- data/lib/gloo_ingress_adapter/util/resource_extensions.rb +19 -0
- data/lib/gloo_ingress_adapter/version.rb +5 -0
- data/lib/gloo_ingress_adapter.rb +13 -0
- metadata +317 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 97de21875d8934bcca64e1ab8e1b8462fc1f3db4a53f372c7d3aa0c711038bdc
         | 
| 4 | 
            +
              data.tar.gz: c873372e2f8ee77d2be7c531d09f24cda4ed190b635a334123b677dc1e58902c
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: c79aa9f16640a0c6a78b324e6e8bd6db916b3b9567fa3ad5b622793878d571cb0ecc017552d1452da5a6f788f44e16bf29cebb144bccb99d29476d6b397b5f63
         | 
| 7 | 
            +
              data.tar.gz: 22dc8fa2050e06649ccd4787ebf14c32d70baad5bfd82cd8fb25092c20dd54471d4ea1b7811c7358c23b85a906c488274cea59733865834d902d94d0e7e59e8c
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,133 @@ | |
| 1 | 
            +
            # Project
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Use [Gloo Edge][gloo] gateways with Kubernetes ingress resources. Gloo Edge supports ingress, however it requires you
         | 
| 4 | 
            +
            to deploy a separate gateway for this. With this adapter, you can integrate your Kubernetes ingresses into an existing
         | 
| 5 | 
            +
            gateway.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            The adapter works by monitoring ingress resources and automatically creating a Gloo route table for each ingress. You
         | 
| 8 | 
            +
            can then mount the created route tables automatically in your gateway.
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            [gloo]: https://docs.solo.io/gloo-edge/latest/
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ## Install
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            Install a Gloo Edge gateway:
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ```bash
         | 
| 17 | 
            +
            kubectl create namespace gloo-system
         | 
| 18 | 
            +
            helm install gloo gloo --namespace gloo-system --repo https://storage.googleapis.com/solo-public-helm \
         | 
| 19 | 
            +
              --set gateway.readGatewaysFromAllNamespaces=true
         | 
| 20 | 
            +
            ```
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            You can also have a look at the [Gloo documentation][gloo-install] for detailed installation instructions.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            [gloo-install]: https://docs.solo.io/gloo-edge/master/installation/gateway/kubernetes/
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            Install the ingress adapter into namespace `gloo-system`:
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            ```bash
         | 
| 29 | 
            +
            git checkout git@github.com:CaperWhite/gloo-ingress-adapter.git
         | 
| 30 | 
            +
            cd gloo-ingress-adapter
         | 
| 31 | 
            +
            helm --namespace gloo-system install gloo-ingress-adapter charts
         | 
| 32 | 
            +
            ```
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            After this, the ingress adapter should be up and running, and monitoring ingress resources. By default it will create
         | 
| 35 | 
            +
            route tables for ingress resources with ingress class name `gloo-route`.
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ## Verify installation
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            Deploy a test service:
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            ```bash
         | 
| 42 | 
            +
            kubectl create namespace ingress-test
         | 
| 43 | 
            +
            kubectl --namespace ingress-test apply --filename examples/test.yaml
         | 
| 44 | 
            +
            ```
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Create an ingress resource for the test service:
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ```bash
         | 
| 49 | 
            +
            cat <<-INGRESS | kubectl apply --namespace ingress-test --filename -
         | 
| 50 | 
            +
            apiVersion: networking.k8s.io/v1
         | 
| 51 | 
            +
            kind: Ingress
         | 
| 52 | 
            +
            metadata:
         | 
| 53 | 
            +
              name: ingress-test
         | 
| 54 | 
            +
            spec:
         | 
| 55 | 
            +
              ingressClassName: gloo-route
         | 
| 56 | 
            +
              rules:
         | 
| 57 | 
            +
              - http:
         | 
| 58 | 
            +
                  paths:
         | 
| 59 | 
            +
                  - path: /
         | 
| 60 | 
            +
                    pathType: Prefix
         | 
| 61 | 
            +
                    backend:
         | 
| 62 | 
            +
                      service:
         | 
| 63 | 
            +
                        name: ingress-test
         | 
| 64 | 
            +
                        port:
         | 
| 65 | 
            +
                          number: 8080
         | 
| 66 | 
            +
            INGRESS
         | 
| 67 | 
            +
            ```
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            Verify a route table has been created:
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            ```bash
         | 
| 72 | 
            +
            kubectl --namespace ingress-test get routetable ingress-test -o yaml
         | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            Create a virtual service resource that automatically mounts the created route tables:
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            ```bash
         | 
| 78 | 
            +
            cat <<-SERVICE | kubectl --namespace gloo-system apply --filename -
         | 
| 79 | 
            +
            apiVersion: gateway.solo.io/v1
         | 
| 80 | 
            +
            kind: VirtualService
         | 
| 81 | 
            +
            metadata:
         | 
| 82 | 
            +
              name: ingress-test
         | 
| 83 | 
            +
            spec:
         | 
| 84 | 
            +
              virtualHost:
         | 
| 85 | 
            +
                domains:
         | 
| 86 | 
            +
                - ingress-test.local
         | 
| 87 | 
            +
                routes:
         | 
| 88 | 
            +
                - matchers:
         | 
| 89 | 
            +
                   - prefix: "/"
         | 
| 90 | 
            +
                  delegateAction:
         | 
| 91 | 
            +
                    selector:
         | 
| 92 | 
            +
                      labels:
         | 
| 93 | 
            +
                        ingress.caperwhite.com/protocol: http
         | 
| 94 | 
            +
                      namespaces:
         | 
| 95 | 
            +
                      - ingress-test
         | 
| 96 | 
            +
            SERVICE
         | 
| 97 | 
            +
            ```
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            Make the gateway accessible locally:
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            ```bash
         | 
| 102 | 
            +
            kubectl --namespace gloo-system port-forward service/gateway-proxy 8080:80
         | 
| 103 | 
            +
            ```
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            Check if you can access the service:
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ```bash
         | 
| 108 | 
            +
            curl -H 'Host: ingress-test.local' http://127.0.0.1:8080
         | 
| 109 | 
            +
            ```
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            should return
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            ```json
         | 
| 114 | 
            +
            {"result":true}
         | 
| 115 | 
            +
            ```
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            Delete the ingress:
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            ```bash
         | 
| 120 | 
            +
            kubectl --namespace ingress-test delete ingress ingress-test
         | 
| 121 | 
            +
            ```
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            Verify the route table is gone:
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            ```bash
         | 
| 126 | 
            +
            kubectl --namespace ingress-test get routetable -A
         | 
| 127 | 
            +
            ```
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            ## Acknowledgements
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            "Gloo" is a trademark of [Solo.io, Inc.][solo].
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            [solo]: https://www.solo.io
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "kubeclient"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module GlooIngressAdapter
         | 
| 6 | 
            +
              # Factory for Kubernetes clients
         | 
| 7 | 
            +
              class ClientFactory
         | 
| 8 | 
            +
                KUBERNETES_API_URL = "https://kubernetes.default.svc"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(kubeconfig:, logger:)
         | 
| 11 | 
            +
                  @kubeconfig = kubeconfig.freeze
         | 
| 12 | 
            +
                  @logger = logger
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def create_client(version:, api: nil)
         | 
| 16 | 
            +
                  suffix = api ? "/apis/#{api}" : ""
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  options = {
         | 
| 19 | 
            +
                    timeouts: { open: 30, read: 30 },
         | 
| 20 | 
            +
                  }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  url = KUBERNETES_API_URL
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  if @kubeconfig
         | 
| 25 | 
            +
                    context = Kubeclient::Config.read(@kubeconfig).context
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    url = context.api_endpoint
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    options[:auth_options] = context.auth_options
         | 
| 30 | 
            +
                    options[:ssl_options] = context.ssl_options
         | 
| 31 | 
            +
                  else
         | 
| 32 | 
            +
                    options[:auth_options] = { bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    if File.exist?("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
         | 
| 35 | 
            +
                      options[:ssl_options] = { ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" }
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  Kubeclient::Client.new("#{url}#{suffix}", version, **options)
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,235 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_support/core_ext/string"
         | 
| 4 | 
            +
            require "gloo_ingress_adapter/resource_observer"
         | 
| 5 | 
            +
            require "kubeclient"
         | 
| 6 | 
            +
            require "logger"
         | 
| 7 | 
            +
            require "retriable"
         | 
| 8 | 
            +
            require "set"
         | 
| 9 | 
            +
            require "yaml"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module GlooIngressAdapter
         | 
| 12 | 
            +
              # Controller responsible for creating virtual services from ingress resources
         | 
| 13 | 
            +
              class Controller
         | 
| 14 | 
            +
                CONTROLLER_NAME = "caperwhite.com/gloo-ingress-adapter"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                RETRY_ON = {
         | 
| 17 | 
            +
                  StandardError: nil,
         | 
| 18 | 
            +
                }.freeze
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def initialize(kubeconfig:, logger:, route_table_builder:)
         | 
| 21 | 
            +
                  @client_factory = ClientFactory.new(kubeconfig:, logger:).freeze
         | 
| 22 | 
            +
                  @logger = logger
         | 
| 23 | 
            +
                  @route_table_builder = route_table_builder
         | 
| 24 | 
            +
                  @ingress_classes = {}
         | 
| 25 | 
            +
                  @ingresses = {}
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def run
         | 
| 29 | 
            +
                  logger.info("Running ingress adapter")
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def watch
         | 
| 33 | 
            +
                  logger.info("Starting ingress adapter")
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  queue = Thread::Queue.new
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  ingress_class_observer = create_ingress_class_observer
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  ingress_class_observer.start(queue:)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  ingress_observer = create_ingress_observer
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  ingress_observer.start(queue:)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  loop do
         | 
| 46 | 
            +
                    event = queue.shift
         | 
| 47 | 
            +
                    handle_event(event)
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                rescue StandardError => e
         | 
| 50 | 
            +
                  log_exception(e)
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                protected
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                attr_reader :logger, :queue, :client_factory, :ingress_classes, :ingresses
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def update_ingress_class(ingress_class)
         | 
| 58 | 
            +
                  logger.info("Updating ingress class #{ingress_class.metadata.name}")
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  existing_ingress_class = @ingress_classes[ingress_class.metadata.uid]
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  @ingress_classes[ingress_class.metadata.uid] = ingress_class
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  if @ingress_classes.empty?
         | 
| 65 | 
            +
                    logger.warn("Now handling no ingress classes")
         | 
| 66 | 
            +
                  else
         | 
| 67 | 
            +
                    logger.info("Now handling ingress classes #{@ingress_classes.values.map { |c| c.metadata.name }.join(", ")}")
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  controller_changed = existing_ingress_class.nil? ||
         | 
| 71 | 
            +
                                       ingress_class.spec.controller != existing_ingress_class.spec.controller
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  ingress_class_name_changed = existing_ingress_class.nil? ||
         | 
| 74 | 
            +
                                               existing_ingress_class.metadata.name != ingress_class.metadata.name
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  if controller_changed
         | 
| 77 | 
            +
                    if ingress_class.spec.controller == CONTROLLER_NAME
         | 
| 78 | 
            +
                      update_ingresses(ingress_class:)
         | 
| 79 | 
            +
                    elsif existing_ingress_class && existing_ingress_class.spec.controller == CONTROLLER_NAME
         | 
| 80 | 
            +
                      delete_ingresses(ingress_class:) if controller_changed
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  elsif ingress_class_name_changed
         | 
| 83 | 
            +
                    update_ingresses(ingress_class:)
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def update_ingresses(ingress_class:)
         | 
| 88 | 
            +
                  @ingresses.values.filter do |u|
         | 
| 89 | 
            +
                    u.spec.ingressClassName == ingress_class.metadata.name
         | 
| 90 | 
            +
                  end.each do |ingress|
         | 
| 91 | 
            +
                    update_ingress(ingress)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def delete_ingressses(ingress_class:)
         | 
| 96 | 
            +
                  @ingresses.values.filter do |u|
         | 
| 97 | 
            +
                    u.spec.ingressClassName == ingress_class.metadata.name
         | 
| 98 | 
            +
                  end.each do |ingress|
         | 
| 99 | 
            +
                    networking_client.delete_ingress(ingress.metadata.name, ingress.metadata.namespace)
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def remove_ingress_class(uid:)
         | 
| 104 | 
            +
                  ingress_class = @ingress_classes.delete(uid) || raise("Unknown ingress class uid '#{uid}'")
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  logger.info("Removing ingress class #{ingress_class.metadata.name}")
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  delete_ingresses(ingress_class:) if ingress_class.spec.controller == CONTROLLER_NAME
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  ingress_class
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def update_ingress(ingress)
         | 
| 114 | 
            +
                  @ingresses[ingress.metadata.uid] = ingress
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  msg = <<~MSG.squish
         | 
| 117 | 
            +
                    #{ingress.metadata.name} (namespace: #{ingress.metadata.namespace},
         | 
| 118 | 
            +
                    ingressClass: #{ingress.spec.ingressClassName || "(none)"})
         | 
| 119 | 
            +
                  MSG
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  if ingress_class_names.include?(ingress.spec.ingressClassName)
         | 
| 122 | 
            +
                    logger.info("Updating ingress #{msg}")
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    update_route_table(ingress:)
         | 
| 125 | 
            +
                  else
         | 
| 126 | 
            +
                    logger.info("Ignoring ingress #{msg}")
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def remove_ingress(uid:)
         | 
| 131 | 
            +
                  ingress = @ingress_classes.delete(uid) || raise("Unknown ingress class uid '#{uid}'")
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  logger.info("Removed ingress #{ingress.metadata.name} (namespace: #{ingress.metadata.namespace})")
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  ingress
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                def handle_event(event)
         | 
| 139 | 
            +
                  raise("Received error event: #{event.to_nice_yaml}") if event.type == "ERROR"
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  logger.info("Handling event: #{event.object.kind} #{event.object.metadata.name} #{event.type.downcase}")
         | 
| 142 | 
            +
                  logger.debug { "Event object: #{event.to_nice_yaml}" }
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  raise "Unknown API version '#{event.object.apiVersion}'" if event.object.apiVersion != "networking.k8s.io/v1"
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  case event.object.kind
         | 
| 147 | 
            +
                  when "IngressClass"
         | 
| 148 | 
            +
                    handle_ingress_class_event(event)
         | 
| 149 | 
            +
                  when "Ingress"
         | 
| 150 | 
            +
                    handle_ingress_event(event)
         | 
| 151 | 
            +
                  else
         | 
| 152 | 
            +
                    raise "Received event for unknown object kind '#{event.object.kind}'"
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                def handle_ingress_class_event(event)
         | 
| 157 | 
            +
                  case event.type
         | 
| 158 | 
            +
                  when "ADDED", "MODIFIED"
         | 
| 159 | 
            +
                    update_ingress_class(event.object)
         | 
| 160 | 
            +
                  when "DELETED"
         | 
| 161 | 
            +
                    remove_ingress_class(event.object.metadata.name)
         | 
| 162 | 
            +
                  else
         | 
| 163 | 
            +
                    logger.error("Unknown event type '#{event.type}'")
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                def handle_ingress_event(event)
         | 
| 168 | 
            +
                  case event.type
         | 
| 169 | 
            +
                  when "ADDED", "MODIFIED"
         | 
| 170 | 
            +
                    update_ingress(event.object)
         | 
| 171 | 
            +
                  when "DELETED"
         | 
| 172 | 
            +
                    # Do nothing
         | 
| 173 | 
            +
                  else
         | 
| 174 | 
            +
                    logger.error("Unknown event type '#{event.type}'")
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def update_route_table(ingress:)
         | 
| 179 | 
            +
                  metadata = ingress.metadata
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  logger.info "Updating route table for ingress '#{metadata.name}' (namespace: '#{metadata.namespace}')"
         | 
| 182 | 
            +
                  logger.debug { ingress.to_nice_yaml }
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  route_table = @route_table_builder.build(ingress:)
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  logger.debug { route_table.to_nice_yaml }
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  gateway_client.apply_route_table(route_table, field_manager: "gloo-ingress-adapter", force: true)
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                def create_ingress_class_observer
         | 
| 192 | 
            +
                  ResourceObserver.new(
         | 
| 193 | 
            +
                    kind: "ingressclasses",
         | 
| 194 | 
            +
                    client: client_factory.create_client(version: "v1", api: "networking.k8s.io"),
         | 
| 195 | 
            +
                    logger:
         | 
| 196 | 
            +
                  )
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def create_ingress_observer
         | 
| 200 | 
            +
                  ResourceObserver.new(
         | 
| 201 | 
            +
                    kind: "ingresses",
         | 
| 202 | 
            +
                    client: client_factory.create_client(version: "v1", api: "networking.k8s.io"),
         | 
| 203 | 
            +
                    logger:
         | 
| 204 | 
            +
                  )
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def ingress_class_names
         | 
| 208 | 
            +
                  @ingress_classes.values.map { |c| c.metadata.name }
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                def log_exception(exception)
         | 
| 212 | 
            +
                  logger.error("#{exception.message} (#{exception.class}/#{exception.class.ancestors})")
         | 
| 213 | 
            +
                  logger.debug(exception.backtrace.join("\n"))
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                def failsafe
         | 
| 217 | 
            +
                  yield
         | 
| 218 | 
            +
                rescue StandardError => e
         | 
| 219 | 
            +
                  log_exception(e)
         | 
| 220 | 
            +
                  nil
         | 
| 221 | 
            +
                end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                def client
         | 
| 224 | 
            +
                  @client ||= client_factory.create_client(version: "v1")
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                def networking_client
         | 
| 228 | 
            +
                  @networking_client ||= client_factory.create_client(version: "v1", api: "networking.k8s.io")
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                def gateway_client
         | 
| 232 | 
            +
                  @gateway_client ||= client_factory.create_client(version: "v1", api: "gateway.solo.io")
         | 
| 233 | 
            +
                end
         | 
| 234 | 
            +
              end
         | 
| 235 | 
            +
            end
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "kubeclient"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module GlooIngressAdapter
         | 
| 6 | 
            +
              # Watcher for ingress resources
         | 
| 7 | 
            +
              class ResourceObserver
         | 
| 8 | 
            +
                # Thread running an observer
         | 
| 9 | 
            +
                class ObserverThread < Thread
         | 
| 10 | 
            +
                  attr_reader :observer
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def initialize(observer:, queue:)
         | 
| 13 | 
            +
                    @observer = observer
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    super { observer.watch(queue:) }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    self.abort_on_exception = true
         | 
| 18 | 
            +
                    self.name = "observer-#{observer.kind}"
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def finish
         | 
| 22 | 
            +
                    observer.watcher.finish
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                attr_reader :kind
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def initialize(kind:, client:, logger:, field_selector: nil)
         | 
| 29 | 
            +
                  @kind = kind
         | 
| 30 | 
            +
                  @client = client
         | 
| 31 | 
            +
                  @logger = logger
         | 
| 32 | 
            +
                  @field_selector = field_selector
         | 
| 33 | 
            +
                  @watcher = nil
         | 
| 34 | 
            +
                  @lock = Mutex.new
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def start(queue:)
         | 
| 38 | 
            +
                  ObserverThread.new(observer: self, queue:)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def watch(queue:)
         | 
| 42 | 
            +
                  logger.info("Watching #{kind}#{field_selector && " with #{field_selector.to_json}"}")
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  retriable do
         | 
| 45 | 
            +
                    self.watcher = client.watch_entities(kind, field_selector:, as: :ros)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    watcher.each do |event|
         | 
| 48 | 
            +
                      logger.info("Received event: #{event.object.kind} #{event.object.metadata.name} #{event.type.downcase}")
         | 
| 49 | 
            +
                      queue << event
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                rescue StandardError => e
         | 
| 53 | 
            +
                  logger.error("Error observing #{kind} resources: #{e.full_message}")
         | 
| 54 | 
            +
                  raise(e)
         | 
| 55 | 
            +
                ensure
         | 
| 56 | 
            +
                  self.watcher = nil
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def stop
         | 
| 60 | 
            +
                  watcher&.finish
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                protected
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                attr_reader :client, :logger, :field_selector
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def watcher
         | 
| 68 | 
            +
                  @lock.synchronize do
         | 
| 69 | 
            +
                    @watcher
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def watcher=(watcher)
         | 
| 74 | 
            +
                  @lock.synchronize do
         | 
| 75 | 
            +
                    @watcher = watcher
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def retriable(&)
         | 
| 80 | 
            +
                  retry_proc = proc do |exception|
         | 
| 81 | 
            +
                    logger.error(exception.full_message)
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  Retriable.retriable(tries: 10, base_interval: 1, on_retry: retry_proc, &)
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "kubeclient/resource"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module GlooIngressAdapter
         | 
| 6 | 
            +
              # Builder responsible to create a route table for an ingress
         | 
| 7 | 
            +
              class RouteTableBuilder
         | 
| 8 | 
            +
                def build(ingress:)
         | 
| 9 | 
            +
                  attributes = {
         | 
| 10 | 
            +
                    apiVersion: "gateway.solo.io/v1",
         | 
| 11 | 
            +
                    kind: "RouteTable",
         | 
| 12 | 
            +
                    metadata: {
         | 
| 13 | 
            +
                      name: ingress.metadata.name,
         | 
| 14 | 
            +
                      namespace: ingress.metadata.namespace,
         | 
| 15 | 
            +
                      labels: build_labels(ingress:),
         | 
| 16 | 
            +
                      ownerReferences: [
         | 
| 17 | 
            +
                        {
         | 
| 18 | 
            +
                          apiVersion: "networking.k8s.io/v1",
         | 
| 19 | 
            +
                          kind: "Ingress",
         | 
| 20 | 
            +
                          name: ingress.metadata.name,
         | 
| 21 | 
            +
                          uid: ingress.metadata.uid,
         | 
| 22 | 
            +
                        },
         | 
| 23 | 
            +
                      ],
         | 
| 24 | 
            +
                    },
         | 
| 25 | 
            +
                    spec: {
         | 
| 26 | 
            +
                      routes: build_routes(ingress:),
         | 
| 27 | 
            +
                    },
         | 
| 28 | 
            +
                  }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  Kubeclient::Resource.new(attributes)
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                protected
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def build_labels(ingress:)
         | 
| 36 | 
            +
                  {
         | 
| 37 | 
            +
                    "ingress.caperwhite.com/protocol" => ingress.spec.tls ? "https" : "http",
         | 
| 38 | 
            +
                  }
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def build_routes(ingress:)
         | 
| 42 | 
            +
                  ingress.spec.rules.flat_map do |rule|
         | 
| 43 | 
            +
                    rule.http&.paths&.map do |path|
         | 
| 44 | 
            +
                      build_matcher(ingress:, rule:, path:)
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end.compact
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def build_matcher(ingress:, rule:, path:)
         | 
| 50 | 
            +
                  service = path.backend.service
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  {
         | 
| 53 | 
            +
                    matchers: [
         | 
| 54 | 
            +
                      {
         | 
| 55 | 
            +
                        headers: headers_matcher(rule:),
         | 
| 56 | 
            +
                        path_type(path.pathType) => path.path,
         | 
| 57 | 
            +
                      }.compact,
         | 
| 58 | 
            +
                    ],
         | 
| 59 | 
            +
                    routeAction: {
         | 
| 60 | 
            +
                      single: {
         | 
| 61 | 
            +
                        kube: {
         | 
| 62 | 
            +
                          ref: {
         | 
| 63 | 
            +
                            name: service.name,
         | 
| 64 | 
            +
                            namespace: service.namespace || ingress.metadata.namespace,
         | 
| 65 | 
            +
                          },
         | 
| 66 | 
            +
                          port: service.port.number || service.port.name,
         | 
| 67 | 
            +
                        },
         | 
| 68 | 
            +
                      },
         | 
| 69 | 
            +
                    },
         | 
| 70 | 
            +
                  }
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                HOST_PATTERN =
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def headers_matcher(rule:)
         | 
| 76 | 
            +
                    if rule.host
         | 
| 77 | 
            +
                      unless %r{\A(?<star>\*\.)?(?<host>(?:[-a-z0-9]+\.)*[-a-z0-9]+)\z}i =~ rule.host
         | 
| 78 | 
            +
                        raise "Illegal host name '#{rule.host}'"
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      if star
         | 
| 82 | 
            +
                        [{ name: "Host", regex: true, value: "^[^.]+\\.#{host.gsub(".", "\\.")}$" }]
         | 
| 83 | 
            +
                      else
         | 
| 84 | 
            +
                        [{ name: "Host", value: host }]
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def convert_host(host); end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def path_type(type)
         | 
| 92 | 
            +
                  case type.downcase
         | 
| 93 | 
            +
                  when "exact"
         | 
| 94 | 
            +
                    :exact
         | 
| 95 | 
            +
                  when "prefix", "implementationspecific"
         | 
| 96 | 
            +
                    :prefix
         | 
| 97 | 
            +
                  else
         | 
| 98 | 
            +
                    raise "Unknown path type '#{type}'"
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_support"
         | 
| 4 | 
            +
            require "active_support/core_ext/hash"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module GlooIngressAdapter
         | 
| 7 | 
            +
              module Util
         | 
| 8 | 
            +
                # Extensions to Resource
         | 
| 9 | 
            +
                module ResourceExtensions
         | 
| 10 | 
            +
                  def to_json(*args)
         | 
| 11 | 
            +
                    to_h.to_json(*args)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def to_nice_yaml
         | 
| 15 | 
            +
                    to_h.deep_stringify_keys.to_yaml
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "kubeclient"
         | 
| 4 | 
            +
            require "zeitwerk"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Main module
         | 
| 7 | 
            +
            module GlooIngressAdapter
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            loader = Zeitwerk::Loader.for_gem
         | 
| 11 | 
            +
            loader.setup
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Kubeclient::Resource.include(GlooIngressAdapter::Util::ResourceExtensions)
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,317 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: gloo_ingress_adapter
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - CaperWhite GmbH
         | 
| 8 | 
            +
            autorequire:
         | 
| 9 | 
            +
            bindir: cmd
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2022-01-21 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: activesupport
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '7.0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '7.0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: kubeclient
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '4.9'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '4.9'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: retriable
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '3.1'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '3.1'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: zeitwerk
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '2.4'
         | 
| 62 | 
            +
              type: :runtime
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '2.4'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: cheetah
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '1.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '1.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: debase
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '0.2'
         | 
| 90 | 
            +
                - - ">="
         | 
| 91 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 92 | 
            +
                    version: 0.2.5.beta2
         | 
| 93 | 
            +
              type: :development
         | 
| 94 | 
            +
              prerelease: false
         | 
| 95 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 96 | 
            +
                requirements:
         | 
| 97 | 
            +
                - - "~>"
         | 
| 98 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 99 | 
            +
                    version: '0.2'
         | 
| 100 | 
            +
                - - ">="
         | 
| 101 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 102 | 
            +
                    version: 0.2.5.beta2
         | 
| 103 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 104 | 
            +
              name: equatable
         | 
| 105 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 106 | 
            +
                requirements:
         | 
| 107 | 
            +
                - - "~>"
         | 
| 108 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 109 | 
            +
                    version: '0.7'
         | 
| 110 | 
            +
              type: :development
         | 
| 111 | 
            +
              prerelease: false
         | 
| 112 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 113 | 
            +
                requirements:
         | 
| 114 | 
            +
                - - "~>"
         | 
| 115 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 116 | 
            +
                    version: '0.7'
         | 
| 117 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 118 | 
            +
              name: hashdiff
         | 
| 119 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 120 | 
            +
                requirements:
         | 
| 121 | 
            +
                - - "~>"
         | 
| 122 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 123 | 
            +
                    version: '1.0'
         | 
| 124 | 
            +
              type: :development
         | 
| 125 | 
            +
              prerelease: false
         | 
| 126 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 127 | 
            +
                requirements:
         | 
| 128 | 
            +
                - - "~>"
         | 
| 129 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 130 | 
            +
                    version: '1.0'
         | 
| 131 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 132 | 
            +
              name: memery
         | 
| 133 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 134 | 
            +
                requirements:
         | 
| 135 | 
            +
                - - "~>"
         | 
| 136 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 137 | 
            +
                    version: '1.4'
         | 
| 138 | 
            +
              type: :development
         | 
| 139 | 
            +
              prerelease: false
         | 
| 140 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 141 | 
            +
                requirements:
         | 
| 142 | 
            +
                - - "~>"
         | 
| 143 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 144 | 
            +
                    version: '1.4'
         | 
| 145 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 146 | 
            +
              name: pry
         | 
| 147 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 148 | 
            +
                requirements:
         | 
| 149 | 
            +
                - - "~>"
         | 
| 150 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 151 | 
            +
                    version: '0.12'
         | 
| 152 | 
            +
              type: :development
         | 
| 153 | 
            +
              prerelease: false
         | 
| 154 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 155 | 
            +
                requirements:
         | 
| 156 | 
            +
                - - "~>"
         | 
| 157 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 158 | 
            +
                    version: '0.12'
         | 
| 159 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 160 | 
            +
              name: rake
         | 
| 161 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 162 | 
            +
                requirements:
         | 
| 163 | 
            +
                - - "~>"
         | 
| 164 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 165 | 
            +
                    version: '13.0'
         | 
| 166 | 
            +
              type: :development
         | 
| 167 | 
            +
              prerelease: false
         | 
| 168 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 169 | 
            +
                requirements:
         | 
| 170 | 
            +
                - - "~>"
         | 
| 171 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 172 | 
            +
                    version: '13.0'
         | 
| 173 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 174 | 
            +
              name: rspec
         | 
| 175 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 176 | 
            +
                requirements:
         | 
| 177 | 
            +
                - - "~>"
         | 
| 178 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 179 | 
            +
                    version: '3.10'
         | 
| 180 | 
            +
              type: :development
         | 
| 181 | 
            +
              prerelease: false
         | 
| 182 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 183 | 
            +
                requirements:
         | 
| 184 | 
            +
                - - "~>"
         | 
| 185 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 186 | 
            +
                    version: '3.10'
         | 
| 187 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 188 | 
            +
              name: rubocop
         | 
| 189 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 190 | 
            +
                requirements:
         | 
| 191 | 
            +
                - - "~>"
         | 
| 192 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 193 | 
            +
                    version: '1.12'
         | 
| 194 | 
            +
              type: :development
         | 
| 195 | 
            +
              prerelease: false
         | 
| 196 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 197 | 
            +
                requirements:
         | 
| 198 | 
            +
                - - "~>"
         | 
| 199 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 200 | 
            +
                    version: '1.12'
         | 
| 201 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 202 | 
            +
              name: rubocop-rspec
         | 
| 203 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 204 | 
            +
                requirements:
         | 
| 205 | 
            +
                - - "~>"
         | 
| 206 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 207 | 
            +
                    version: '2.2'
         | 
| 208 | 
            +
              type: :development
         | 
| 209 | 
            +
              prerelease: false
         | 
| 210 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 211 | 
            +
                requirements:
         | 
| 212 | 
            +
                - - "~>"
         | 
| 213 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 214 | 
            +
                    version: '2.2'
         | 
| 215 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 216 | 
            +
              name: ruby-debug-ide
         | 
| 217 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 218 | 
            +
                requirements:
         | 
| 219 | 
            +
                - - "~>"
         | 
| 220 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 221 | 
            +
                    version: '0.7'
         | 
| 222 | 
            +
              type: :development
         | 
| 223 | 
            +
              prerelease: false
         | 
| 224 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 225 | 
            +
                requirements:
         | 
| 226 | 
            +
                - - "~>"
         | 
| 227 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 228 | 
            +
                    version: '0.7'
         | 
| 229 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 230 | 
            +
              name: simplecov
         | 
| 231 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 232 | 
            +
                requirements:
         | 
| 233 | 
            +
                - - "~>"
         | 
| 234 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 235 | 
            +
                    version: '0.21'
         | 
| 236 | 
            +
              type: :development
         | 
| 237 | 
            +
              prerelease: false
         | 
| 238 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 239 | 
            +
                requirements:
         | 
| 240 | 
            +
                - - "~>"
         | 
| 241 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 242 | 
            +
                    version: '0.21'
         | 
| 243 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 244 | 
            +
              name: solargraph
         | 
| 245 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 246 | 
            +
                requirements:
         | 
| 247 | 
            +
                - - "~>"
         | 
| 248 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 249 | 
            +
                    version: '0.40'
         | 
| 250 | 
            +
              type: :development
         | 
| 251 | 
            +
              prerelease: false
         | 
| 252 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 253 | 
            +
                requirements:
         | 
| 254 | 
            +
                - - "~>"
         | 
| 255 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 256 | 
            +
                    version: '0.40'
         | 
| 257 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 258 | 
            +
              name: toml-rb
         | 
| 259 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 260 | 
            +
                requirements:
         | 
| 261 | 
            +
                - - "~>"
         | 
| 262 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 263 | 
            +
                    version: '2.1'
         | 
| 264 | 
            +
              type: :development
         | 
| 265 | 
            +
              prerelease: false
         | 
| 266 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 267 | 
            +
                requirements:
         | 
| 268 | 
            +
                - - "~>"
         | 
| 269 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 270 | 
            +
                    version: '2.1'
         | 
| 271 | 
            +
            description: |2
         | 
| 272 | 
            +
                  This controller allows to integrate routes created from ingresses into a normal (non-ingress) Gloo gateway, thus
         | 
| 273 | 
            +
                  making deploying a separate ingress gateway obsolete. The controller does this by creating route table resources
         | 
| 274 | 
            +
                  from ingress objects in the cluster. These route tables then can be automatically mounted in a Gloo virtual service.
         | 
| 275 | 
            +
            email:
         | 
| 276 | 
            +
            - info@caperwhite.com
         | 
| 277 | 
            +
            executables: []
         | 
| 278 | 
            +
            extensions: []
         | 
| 279 | 
            +
            extra_rdoc_files: []
         | 
| 280 | 
            +
            files:
         | 
| 281 | 
            +
            - README.md
         | 
| 282 | 
            +
            - lib/gloo_ingress_adapter.rb
         | 
| 283 | 
            +
            - lib/gloo_ingress_adapter/client_factory.rb
         | 
| 284 | 
            +
            - lib/gloo_ingress_adapter/controller.rb
         | 
| 285 | 
            +
            - lib/gloo_ingress_adapter/resource_observer.rb
         | 
| 286 | 
            +
            - lib/gloo_ingress_adapter/route_table_builder.rb
         | 
| 287 | 
            +
            - lib/gloo_ingress_adapter/util/resource_extensions.rb
         | 
| 288 | 
            +
            - lib/gloo_ingress_adapter/version.rb
         | 
| 289 | 
            +
            homepage: https://github.com/CaperWhite/gloo-ingress-adapter
         | 
| 290 | 
            +
            licenses:
         | 
| 291 | 
            +
            - AGPL-3.0-or-later
         | 
| 292 | 
            +
            metadata:
         | 
| 293 | 
            +
              docker_platforms: linux/arm64,linux/amd64
         | 
| 294 | 
            +
              github_user: CaperWhite
         | 
| 295 | 
            +
              helm_repo: https://caperwhite.github.io/gloo-ingress-adapter
         | 
| 296 | 
            +
              homepage_uri: https://github.com/CaperWhite/gloo-ingress-adapter
         | 
| 297 | 
            +
              rubygems_mfa_required: 'true'
         | 
| 298 | 
            +
            post_install_message:
         | 
| 299 | 
            +
            rdoc_options: []
         | 
| 300 | 
            +
            require_paths:
         | 
| 301 | 
            +
            - lib
         | 
| 302 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 303 | 
            +
              requirements:
         | 
| 304 | 
            +
              - - ">="
         | 
| 305 | 
            +
                - !ruby/object:Gem::Version
         | 
| 306 | 
            +
                  version: 3.1.0
         | 
| 307 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 308 | 
            +
              requirements:
         | 
| 309 | 
            +
              - - ">="
         | 
| 310 | 
            +
                - !ruby/object:Gem::Version
         | 
| 311 | 
            +
                  version: '0'
         | 
| 312 | 
            +
            requirements: []
         | 
| 313 | 
            +
            rubygems_version: 3.3.3
         | 
| 314 | 
            +
            signing_key:
         | 
| 315 | 
            +
            specification_version: 4
         | 
| 316 | 
            +
            summary: Use ingress resources with Gloo gateways
         | 
| 317 | 
            +
            test_files: []
         |