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: []
|