mirror of
https://github.com/ngrok/ngrok-operator.git
synced 2026-05-17 16:50:44 +00:00
[Breaking Change!] Remove Deprecated CRDs (#664)
* chore!: Remove TCPEdge CRD * chore!: Remove TLSEdge CRD * chore!: Remove HTTPSEdge, Tunnel, and NgrokModuleSet CRDs
This commit is contained in:
@@ -58,9 +58,6 @@ jobs:
|
||||
run: |
|
||||
kubectl get tunnels.ingress.k8s.ngrok.com
|
||||
kubectl get domains.ingress.k8s.ngrok.com
|
||||
kubectl get httpsedges.ingress.k8s.ngrok.com
|
||||
kubectl get tlsedges.ingress.k8s.ngrok.com
|
||||
kubectl get tcpedges.ingress.k8s.ngrok.com
|
||||
kubectl get cloudendpoints.ngrok.k8s.ngrok.com -A
|
||||
kubectl get agentendpoints.ngrok.k8s.ngrok.com -A
|
||||
- name: Show logs of the ngrok-operator
|
||||
|
||||
@@ -18,33 +18,6 @@ resources:
|
||||
kind: Domain
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: k8s.ngrok.com
|
||||
group: ingress
|
||||
kind: Tunnel
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: k8s.ngrok.com
|
||||
group: ingress
|
||||
kind: TCPEdge
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: k8s.ngrok.com
|
||||
group: ingress
|
||||
kind: HTTPSEdge
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
@@ -54,22 +27,6 @@ resources:
|
||||
kind: IPPolicy
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
domain: k8s.ngrok.com
|
||||
group: ingress
|
||||
kind: NgrokModule
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
domain: k8s.ngrok.com
|
||||
group: ingress
|
||||
kind: NgrokModuleSet
|
||||
path: github.com/ngrok/ngrok-operator/api/ingress/v1alpha1
|
||||
version: v1alpha1
|
||||
- controller: true
|
||||
domain: k8s.ngrok.com
|
||||
group: gateway
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
type HTTPSEdgeRouteSpec struct {
|
||||
ngrokAPICommon `json:",inline"`
|
||||
|
||||
// MatchType is the type of match to use for this route. Valid values are:
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=exact_path;path_prefix
|
||||
MatchType string `json:"matchType"`
|
||||
|
||||
// Match is the value to match against the request path
|
||||
// +kubebuilder:validation:Required
|
||||
Match string `json:"match"`
|
||||
|
||||
// Backend is the definition for the tunnel group backend
|
||||
// that serves traffic for this edge
|
||||
// +kubebuilder:validation:Required
|
||||
Backend TunnelGroupBackend `json:"backend,omitempty"`
|
||||
|
||||
// CircuitBreaker is a circuit breaker configuration to apply to this route
|
||||
CircuitBreaker *EndpointCircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
|
||||
// Compression is whether or not to enable compression for this route
|
||||
Compression *EndpointCompression `json:"compression,omitempty"`
|
||||
|
||||
// IPRestriction is an IPRestriction to apply to this route
|
||||
IPRestriction *EndpointIPPolicy `json:"ipRestriction,omitempty"`
|
||||
|
||||
// Headers are request/response headers to apply to this route
|
||||
Headers *EndpointHeaders `json:"headers,omitempty"`
|
||||
|
||||
// OAuth configuration to apply to this route
|
||||
OAuth *EndpointOAuth `json:"oauth,omitempty"`
|
||||
|
||||
// OIDC is the OpenID Connect configuration to apply to this route
|
||||
OIDC *EndpointOIDC `json:"oidc,omitempty"`
|
||||
|
||||
// SAML is the SAML configuration to apply to this route
|
||||
SAML *EndpointSAML `json:"saml,omitempty"`
|
||||
|
||||
// WebhookVerification is webhook verification configuration to apply to this route
|
||||
WebhookVerification *EndpointWebhookVerification `json:"webhookVerification,omitempty"`
|
||||
|
||||
// TrafficPolicy is the raw json policy string that was applied to the ngrok API
|
||||
// +kubebuilder:validation:Schemaless
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
// +kubebuilder:validation:Type=object
|
||||
Policy json.RawMessage `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// HTTPSEdgeSpec defines the desired state of HTTPSEdge
|
||||
type HTTPSEdgeSpec struct {
|
||||
ngrokAPICommon `json:",inline"`
|
||||
|
||||
// Hostports is a list of hostports served by this edge
|
||||
// +kubebuilder:validation:Required
|
||||
Hostports []string `json:"hostports,omitempty"`
|
||||
|
||||
// Routes is a list of routes served by this edge
|
||||
Routes []HTTPSEdgeRouteSpec `json:"routes,omitempty"`
|
||||
|
||||
// TLSTermination is the TLS termination configuration for this edge
|
||||
TLSTermination *EndpointTLSTerminationAtEdge `json:"tlsTermination,omitempty"`
|
||||
MutualTLS *EndpointMutualTLS `json:"mutualTLS,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPSEdgeRouteStatus struct {
|
||||
// ID is the unique identifier for this route
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// URI is the URI for this route
|
||||
URI string `json:"uri,omitempty"`
|
||||
|
||||
Match string `json:"match,omitempty"`
|
||||
|
||||
MatchType string `json:"matchType,omitempty"`
|
||||
|
||||
// Backend stores the status of the tunnel group backend,
|
||||
// mainly the ID of the backend
|
||||
Backend TunnelGroupBackendStatus `json:"backend,omitempty"`
|
||||
}
|
||||
|
||||
// HTTPSEdgeStatus defines the observed state of HTTPSEdge
|
||||
type HTTPSEdgeStatus struct {
|
||||
// ID is the unique identifier for this edge
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// URI is the URI for this edge
|
||||
URI string `json:"uri,omitempty"`
|
||||
|
||||
Routes []HTTPSEdgeRouteStatus `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="HTTPSEdge is deprecated and will be removed in a future release. Please migrate to CloudEndpoint or AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654 for more information."
|
||||
|
||||
// HTTPSEdge is the Schema for the httpsedges API
|
||||
type HTTPSEdge struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec HTTPSEdgeSpec `json:"spec,omitempty"`
|
||||
Status HTTPSEdgeStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// HTTPSEdgeList contains a list of HTTPSEdge
|
||||
type HTTPSEdgeList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []HTTPSEdge `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&HTTPSEdge{}, &HTTPSEdgeList{})
|
||||
}
|
||||
|
||||
// Equal returns true if the two HTTPSEdge objects are equal
|
||||
// It only checks the top level attributes like hostports and metadata
|
||||
// It does not check the routes or the tunnel group backend
|
||||
func (e *HTTPSEdge) Equal(edge *ngrok.HTTPSEdge) bool {
|
||||
if e == nil && edge == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if e == nil || edge == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the metadata matches
|
||||
if e.Spec.Metadata != edge.Metadata {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the hostports match
|
||||
if !slices.Equal(e.Spec.Hostports, edge.Hostports) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if TLSTermination & mutualTLS matches
|
||||
return e.tlsTerminationEqual(edge) &&
|
||||
e.mutualTLSEqual(edge)
|
||||
}
|
||||
|
||||
func (e *HTTPSEdge) tlsTerminationEqual(edge *ngrok.HTTPSEdge) bool {
|
||||
if e.Spec.TLSTermination == nil && edge.TlsTermination == nil {
|
||||
return true
|
||||
}
|
||||
if (e.Spec.TLSTermination == nil && edge.TlsTermination != nil) || (e.Spec.TLSTermination != nil && edge.TlsTermination == nil) {
|
||||
// one is nil and the other is not so they don't match
|
||||
return false
|
||||
}
|
||||
if e.Spec.TLSTermination.MinVersion != *edge.TlsTermination.MinVersion {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *HTTPSEdge) mutualTLSEqual(edge *ngrok.HTTPSEdge) bool {
|
||||
if e.Spec.MutualTLS == nil && edge.MutualTls == nil {
|
||||
return true
|
||||
}
|
||||
if (e.Spec.MutualTLS == nil && edge.MutualTls != nil) || (e.Spec.MutualTLS != nil && edge.MutualTls == nil) {
|
||||
// one is nil and the other is not so they don't match
|
||||
return false
|
||||
}
|
||||
edgeCAIDs := make([]string, len(e.Spec.MutualTLS.CertificateAuthorities))
|
||||
for i, ca := range edge.MutualTls.CertificateAuthorities {
|
||||
edgeCAIDs[i] = ca.ID
|
||||
}
|
||||
|
||||
return slices.Equal(e.Spec.MutualTLS.CertificateAuthorities, edgeCAIDs)
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
)
|
||||
|
||||
func TestHTTPSEdgeEqual(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
a *HTTPSEdge
|
||||
b *ngrok.HTTPSEdge
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nils",
|
||||
a: nil,
|
||||
b: nil,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "a nil",
|
||||
a: nil,
|
||||
b: &ngrok.HTTPSEdge{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "b nil",
|
||||
a: &HTTPSEdge{},
|
||||
b: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "metadata different",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
ngrokAPICommon: ngrokAPICommon{
|
||||
Metadata: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
Metadata: "b",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "metadata same",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
ngrokAPICommon: ngrokAPICommon{
|
||||
Metadata: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
Metadata: "a",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "hostports different",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
Hostports: []string{"a"},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
Hostports: []string{"b"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "hostports same",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
Hostports: []string{"a"},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
Hostports: []string{"a"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "tls termination both nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: nil,
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: nil,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "tls termination a nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: nil,
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: &ngrok.EndpointTLSTermination{},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "tls termination b nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: &EndpointTLSTerminationAtEdge{},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: nil,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "tls termination both empty",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: nil,
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: nil,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "tls termination different",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: &EndpointTLSTerminationAtEdge{
|
||||
MinVersion: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: &ngrok.EndpointTLSTermination{
|
||||
MinVersion: ngrok.String("b"),
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "tls termination same",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
TLSTermination: &EndpointTLSTerminationAtEdge{
|
||||
MinVersion: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
TlsTermination: &ngrok.EndpointTLSTermination{
|
||||
MinVersion: ngrok.String("a"),
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "mtls both nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
MutualTLS: nil,
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
MutualTls: nil,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "mtls a nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
MutualTLS: nil,
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
MutualTls: &ngrok.EndpointMutualTLS{},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "mtls b nil",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
MutualTLS: &EndpointMutualTLS{},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{MutualTls: nil},
|
||||
},
|
||||
{
|
||||
name: "tls termination different",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
MutualTLS: &EndpointMutualTLS{
|
||||
CertificateAuthorities: []string{"a"},
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
MutualTls: &ngrok.EndpointMutualTLS{
|
||||
CertificateAuthorities: []ngrok.Ref{{
|
||||
ID: "b",
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "tls termination same",
|
||||
a: &HTTPSEdge{
|
||||
Spec: HTTPSEdgeSpec{
|
||||
MutualTLS: &EndpointMutualTLS{
|
||||
CertificateAuthorities: []string{"a123"},
|
||||
},
|
||||
},
|
||||
},
|
||||
b: &ngrok.HTTPSEdge{
|
||||
MutualTls: &ngrok.EndpointMutualTLS{
|
||||
CertificateAuthorities: []ngrok.Ref{{
|
||||
ID: "a123",
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.a.Equal(c.b) != c.expected {
|
||||
t.Errorf("expected %v, got %v", c.expected, !c.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,6 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// common ngrok API/Dashboard fields
|
||||
@@ -18,453 +14,6 @@ type ngrokAPICommon struct {
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Route Module Types
|
||||
|
||||
type EndpointCompression struct {
|
||||
// Enabled is whether or not to enable compression for this endpoint
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointIPPolicy struct {
|
||||
IPPolicies []string `json:"policies,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointRequestHeaders is the configuration for a HTTPSEdgeRoute's request headers
|
||||
// to be added or removed from the request before it is sent to the backend service.
|
||||
type EndpointRequestHeaders struct {
|
||||
// a map of header key to header value that will be injected into the HTTP Request
|
||||
// before being sent to the upstream application server
|
||||
Add map[string]string `json:"add,omitempty"`
|
||||
// a list of header names that will be removed from the HTTP Request before being
|
||||
// sent to the upstream application server
|
||||
Remove []string `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointResponseHeaders is the configuration for a HTTPSEdgeRoute's response headers
|
||||
// to be added or removed from the response before it is sent to the client.
|
||||
type EndpointResponseHeaders struct {
|
||||
// a map of header key to header value that will be injected into the HTTP Response
|
||||
// returned to the HTTP client
|
||||
Add map[string]string `json:"add,omitempty"`
|
||||
// a list of header names that will be removed from the HTTP Response returned to
|
||||
// the HTTP client
|
||||
Remove []string `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointHeaders struct {
|
||||
// Request headers are the request headers module configuration or null
|
||||
Request *EndpointRequestHeaders `json:"request,omitempty"`
|
||||
// Response headers are the response headers module configuration or null
|
||||
Response *EndpointResponseHeaders `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointMutualTLS struct {
|
||||
// List of CA IDs that will be used to validate incoming connections to the
|
||||
// edge.
|
||||
CertificateAuthorities []string `json:"certificateAuthorities,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointTLSTermination struct {
|
||||
// TerminateAt determines where the TLS connection should be terminated.
|
||||
// "edge" if the ngrok edge should terminate TLS traffic, "upstream" if TLS
|
||||
// traffic should be passed through to the upstream ngrok agent /
|
||||
// application server for termination.
|
||||
// +kubebuilder:validation:Enum=edge;agent;upstream
|
||||
// +kubebuilder:default:=edge
|
||||
TerminateAt string `json:"terminateAt,omitempty"`
|
||||
// MinVersion is the minimum TLS version to allow for connections to the edge
|
||||
MinVersion *string `json:"minVersion,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointTLSTerminationAtEdge struct {
|
||||
// MinVersion is the minimum TLS version to allow for connections to the edge
|
||||
MinVersion string `json:"minVersion,omitempty"`
|
||||
}
|
||||
|
||||
type SecretKeyRef struct {
|
||||
// Name of the Kubernetes secret
|
||||
Name string `json:"name,omitempty"`
|
||||
// Key in the secret to use
|
||||
Key string `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointWebhookVerification struct {
|
||||
// a string indicating which webhook provider will be sending webhooks to this
|
||||
// endpoint. Value must be one of the supported providers defined at
|
||||
// https://ngrok.com/docs/http/webhook-verification/#supported-providers
|
||||
Provider string `json:"provider,omitempty"`
|
||||
// SecretRef is a reference to a secret containing the secret used to validate
|
||||
// requests from the given provider. All providers except AWS SNS require a secret
|
||||
SecretRef *SecretKeyRef `json:"secret,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointCircuitBreaker struct {
|
||||
// Duration after which the circuit is tripped to wait before re-evaluating upstream health
|
||||
// +kubebuilder:validation:Format=duration
|
||||
TrippedDuration v1.Duration `json:"trippedDuration,omitempty"`
|
||||
|
||||
// Statistical rolling window duration that metrics are retained for.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
RollingWindow v1.Duration `json:"rollingWindow,omitempty"`
|
||||
|
||||
// Integer number of buckets into which metrics are retained. Max 128.
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:validation:Maximum=128
|
||||
NumBuckets uint32 `json:"numBuckets,omitempty"`
|
||||
|
||||
// Integer number of requests in a rolling window that will trip the circuit.
|
||||
// Helpful if traffic volume is low.
|
||||
VolumeThreshold uint32 `json:"volumeThreshold,omitempty"`
|
||||
|
||||
// Error threshold percentage should be between 0 - 1.0, not 0-100.0
|
||||
ErrorThresholdPercentage resource.Quantity `json:"errorThresholdPercentage,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointOIDC struct {
|
||||
// Do not enforce authentication on HTTP OPTIONS requests. necessary if you are
|
||||
// supporting CORS.
|
||||
OptionsPassthrough bool `json:"optionsPassthrough,omitempty"`
|
||||
// the prefix of the session cookie that ngrok sets on the http client to cache
|
||||
// authentication. default is 'ngrok.'
|
||||
CookiePrefix string `json:"cookiePrefix,omitempty"`
|
||||
// Duration of inactivity after which if the user has not accessed
|
||||
// the endpoint, their session will time out and they will be forced to
|
||||
// reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
InactivityTimeout v1.Duration `json:"inactivityTimeout,omitempty"`
|
||||
// The maximum duration of an authenticated session.
|
||||
// After this period is exceeded, a user must reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
MaximumDuration v1.Duration `json:"maximumDuration,omitempty"`
|
||||
// URL of the OIDC "OpenID provider". This is the base URL used for discovery.
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
// The OIDC app's client ID and OIDC audience.
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
// The OIDC app's client secret.
|
||||
ClientSecret SecretKeyRef `json:"clientSecret,omitempty"`
|
||||
// The set of scopes to request from the OIDC identity provider.
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointSAML struct {
|
||||
// Do not enforce authentication on HTTP OPTIONS requests. necessary if you are
|
||||
// supporting CORS.
|
||||
OptionsPassthrough bool `json:"optionsPassthrough,omitempty"`
|
||||
// the prefix of the session cookie that ngrok sets on the http client to cache
|
||||
// authentication. default is 'ngrok.'
|
||||
CookiePrefix string `json:"cookiePrefix,omitempty"`
|
||||
// Duration of inactivity after which if the user has not accessed
|
||||
// the endpoint, their session will time out and they will be forced to
|
||||
// reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
InactivityTimeout v1.Duration `json:"inactivityTimeout,omitempty"`
|
||||
// The maximum duration of an authenticated session.
|
||||
// After this period is exceeded, a user must reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
MaximumDuration v1.Duration `json:"maximumDuration,omitempty"`
|
||||
// The full XML IdP EntityDescriptor. Your IdP may provide this to you as a a file
|
||||
// to download or as a URL.
|
||||
IdPMetadata string `json:"idpMetadata,omitempty"`
|
||||
// If true, indicates that whenever we redirect a user to the IdP for
|
||||
// authentication that the IdP must prompt the user for authentication credentials
|
||||
// even if the user already has a valid session with the IdP.
|
||||
ForceAuthn bool `json:"forceAuthn,omitempty"`
|
||||
// If true, the IdP may initiate a login directly (e.g. the user does not need to
|
||||
// visit the endpoint first and then be redirected). The IdP should set the
|
||||
// RelayState parameter to the target URL of the resource they want the user to be
|
||||
// redirected to after the SAML login assertion has been processed.
|
||||
AllowIdPInitiated *bool `json:"allowIdpInitiated,omitempty"`
|
||||
// If present, only users who are a member of one of the listed groups may access
|
||||
// the target endpoint.
|
||||
AuthorizedGroups []string `json:"authorizedGroups,omitempty"`
|
||||
// Defines the name identifier format the SP expects the IdP to use in its
|
||||
// assertions to identify subjects. If unspecified, a default value of
|
||||
// urn:oasis:names:tc:SAML:2.0:nameid-format:persistent will be used. A subset of
|
||||
// the allowed values enumerated by the SAML specification are supported.
|
||||
NameIDFormat string `json:"nameidFormat,omitempty"`
|
||||
}
|
||||
|
||||
type OAuthProviderCommon struct {
|
||||
// Do not enforce authentication on HTTP OPTIONS requests. necessary if you are
|
||||
// supporting CORS.
|
||||
OptionsPassthrough bool `json:"optionsPassthrough,omitempty"`
|
||||
// the prefix of the session cookie that ngrok sets on the http client to cache
|
||||
// authentication. default is 'ngrok.'
|
||||
CookiePrefix string `json:"cookiePrefix,omitempty"`
|
||||
// Duration of inactivity after which if the user has not accessed
|
||||
// the endpoint, their session will time out and they will be forced to
|
||||
// reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
InactivityTimeout v1.Duration `json:"inactivityTimeout,omitempty"`
|
||||
// Integer number of seconds of the maximum duration of an authenticated session.
|
||||
// After this period is exceeded, a user must reauthenticate.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
MaximumDuration v1.Duration `json:"maximumDuration,omitempty"`
|
||||
// Duration after which ngrok guarantees it will refresh user
|
||||
// state from the identity provider and recheck whether the user is still
|
||||
// authorized to access the endpoint. This is the preferred tunable to use to
|
||||
// enforce a minimum amount of time after which a revoked user will no longer be
|
||||
// able to access the resource.
|
||||
// +kubebuilder:validation:Format=duration
|
||||
AuthCheckInterval v1.Duration `json:"authCheckInterval,omitempty"`
|
||||
// the OAuth app client ID. retrieve it from the identity provider's dashboard
|
||||
// where you created your own OAuth app. optional. if unspecified, ngrok will use
|
||||
// its own managed oauth application which has additional restrictions. see the
|
||||
// OAuth module docs for more details. if present, clientSecret must be present as
|
||||
// well.
|
||||
ClientID *string `json:"clientId,omitempty"`
|
||||
// the OAuth app client secret. retrieve if from the identity provider's dashboard
|
||||
// where you created your own OAuth app. optional, see all of the caveats in the
|
||||
// docs for clientId.
|
||||
ClientSecret *SecretKeyRef `json:"clientSecret,omitempty"`
|
||||
// a list of provider-specific OAuth scopes with the permissions your OAuth app
|
||||
// would like to ask for. these may not be set if you are using the ngrok-managed
|
||||
// oauth app (i.e. you must pass both client_id and client_secret to set scopes)
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
// a list of email addresses of users authenticated by identity provider who are
|
||||
// allowed access to the endpoint
|
||||
EmailAddresses []string `json:"emailAddresses,omitempty"`
|
||||
// a list of email domains of users authenticated by identity provider who are
|
||||
// allowed access to the endpoint
|
||||
EmailDomains []string `json:"emailDomains,omitempty"`
|
||||
}
|
||||
|
||||
func (opc OAuthProviderCommon) toNgrokEndpointOauth() *ngrok.EndpointOAuth {
|
||||
return &ngrok.EndpointOAuth{
|
||||
OptionsPassthrough: opc.OptionsPassthrough,
|
||||
CookiePrefix: opc.CookiePrefix,
|
||||
InactivityTimeout: uint32(opc.InactivityTimeout.Duration.Seconds()),
|
||||
MaximumDuration: uint32(opc.MaximumDuration.Duration.Seconds()),
|
||||
AuthCheckInterval: uint32(opc.AuthCheckInterval.Duration.Seconds()),
|
||||
Provider: ngrok.EndpointOAuthProvider{},
|
||||
}
|
||||
}
|
||||
|
||||
func (opc OAuthProviderCommon) ClientSecretKeyRef() *SecretKeyRef {
|
||||
return opc.ClientSecret
|
||||
}
|
||||
|
||||
type EndpointOAuth struct {
|
||||
// configuration for using github as the identity provider
|
||||
Github *EndpointOAuthGitHub `json:"github,omitempty"`
|
||||
// configuration for using facebook as the identity provider
|
||||
Facebook *EndpointOAuthFacebook `json:"facebook,omitempty"`
|
||||
// configuration for using microsoft as the identity provider
|
||||
Microsoft *EndpointOAuthMicrosoft `json:"microsoft,omitempty"`
|
||||
// configuration for using google as the identity provider
|
||||
Google *EndpointOAuthGoogle `json:"google,omitempty"`
|
||||
// configuration for using linkedin as the identity provider
|
||||
Linkedin *EndpointOAuthLinkedIn `json:"linkedin,omitempty"`
|
||||
// configuration for using gitlab as the identity provider
|
||||
Gitlab *EndpointOAuthGitLab `json:"gitlab,omitempty"`
|
||||
// configuration for using twitch as the identity provider
|
||||
Twitch *EndpointOAuthTwitch `json:"twitch,omitempty"`
|
||||
// configuration for using amazon as the identity provider
|
||||
Amazon *EndpointOAuthAmazon `json:"amazon,omitempty"`
|
||||
}
|
||||
|
||||
type EndpointOAuthGitHub struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
// a list of github teams identifiers. users will be allowed access to the endpoint
|
||||
// if they are a member of any of these teams. identifiers should be in the 'slug'
|
||||
// format qualified with the org name, e.g. org-name/team-name
|
||||
Teams []string `json:"teams,omitempty"`
|
||||
// a list of github org identifiers. users who are members of any of the listed
|
||||
// organizations will be allowed access. identifiers should be the organization's
|
||||
// 'slug'
|
||||
Organizations []string `json:"organizations,omitempty"`
|
||||
}
|
||||
|
||||
func (github *EndpointOAuthGitHub) Provided() bool {
|
||||
return github != nil
|
||||
}
|
||||
|
||||
func (github *EndpointOAuthGitHub) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if github == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := github.toNgrokEndpointOauth()
|
||||
mod.Provider.Github = &ngrok.EndpointOAuthGitHub{
|
||||
ClientID: github.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: github.Scopes,
|
||||
EmailAddresses: github.EmailAddresses,
|
||||
EmailDomains: github.EmailDomains,
|
||||
Teams: github.Teams,
|
||||
Organizations: github.Organizations,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthFacebook struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (facebook *EndpointOAuthFacebook) Provided() bool {
|
||||
return facebook != nil
|
||||
}
|
||||
|
||||
func (facebook *EndpointOAuthFacebook) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if facebook == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := facebook.toNgrokEndpointOauth()
|
||||
mod.Provider.Facebook = &ngrok.EndpointOAuthFacebook{
|
||||
ClientID: facebook.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: facebook.Scopes,
|
||||
EmailAddresses: facebook.EmailAddresses,
|
||||
EmailDomains: facebook.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthMicrosoft struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (microsoft *EndpointOAuthMicrosoft) Provided() bool {
|
||||
return microsoft != nil
|
||||
}
|
||||
|
||||
func (microsoft *EndpointOAuthMicrosoft) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if microsoft == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := microsoft.toNgrokEndpointOauth()
|
||||
mod.Provider.Microsoft = &ngrok.EndpointOAuthMicrosoft{
|
||||
ClientID: microsoft.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: microsoft.Scopes,
|
||||
EmailAddresses: microsoft.EmailAddresses,
|
||||
EmailDomains: microsoft.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthGoogle struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (google *EndpointOAuthGoogle) Provided() bool {
|
||||
return google != nil
|
||||
}
|
||||
|
||||
func (google *EndpointOAuthGoogle) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if google == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := google.toNgrokEndpointOauth()
|
||||
mod.Provider.Google = &ngrok.EndpointOAuthGoogle{
|
||||
ClientID: google.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: google.Scopes,
|
||||
EmailAddresses: google.EmailAddresses,
|
||||
EmailDomains: google.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthLinkedIn struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (linkedin *EndpointOAuthLinkedIn) Provided() bool {
|
||||
return linkedin != nil
|
||||
}
|
||||
|
||||
func (linkedin *EndpointOAuthLinkedIn) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if linkedin == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := linkedin.toNgrokEndpointOauth()
|
||||
mod.Provider.Linkedin = &ngrok.EndpointOAuthLinkedIn{
|
||||
ClientID: linkedin.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: linkedin.Scopes,
|
||||
EmailAddresses: linkedin.EmailAddresses,
|
||||
EmailDomains: linkedin.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthGitLab struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (gitlab *EndpointOAuthGitLab) Provided() bool {
|
||||
return gitlab != nil
|
||||
}
|
||||
|
||||
func (gitlab *EndpointOAuthGitLab) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if gitlab == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := gitlab.toNgrokEndpointOauth()
|
||||
mod.Provider.Gitlab = &ngrok.EndpointOAuthGitLab{
|
||||
ClientID: gitlab.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: gitlab.Scopes,
|
||||
EmailAddresses: gitlab.EmailAddresses,
|
||||
EmailDomains: gitlab.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthTwitch struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (twitch *EndpointOAuthTwitch) Provided() bool {
|
||||
return twitch != nil
|
||||
}
|
||||
|
||||
func (twitch *EndpointOAuthTwitch) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if twitch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := twitch.toNgrokEndpointOauth()
|
||||
mod.Provider.Twitch = &ngrok.EndpointOAuthTwitch{
|
||||
ClientID: twitch.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: twitch.Scopes,
|
||||
EmailAddresses: twitch.EmailAddresses,
|
||||
EmailDomains: twitch.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointOAuthAmazon struct {
|
||||
OAuthProviderCommon `json:",inline"`
|
||||
}
|
||||
|
||||
func (amazon *EndpointOAuthAmazon) Provided() bool {
|
||||
return amazon != nil
|
||||
}
|
||||
|
||||
func (amazon *EndpointOAuthAmazon) ToNgrok(clientSecret *string) *ngrok.EndpointOAuth {
|
||||
if amazon == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := amazon.toNgrokEndpointOauth()
|
||||
mod.Provider.Amazon = &ngrok.EndpointOAuthAmazon{
|
||||
ClientID: amazon.ClientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: amazon.Scopes,
|
||||
EmailAddresses: amazon.EmailAddresses,
|
||||
EmailDomains: amazon.EmailDomains,
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
type EndpointPolicy struct {
|
||||
// Determines if the rule will be applied to traffic
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type oauthProvider interface {
|
||||
Provided() bool
|
||||
}
|
||||
|
||||
func TestOAuthCommonProvided(t *testing.T) {
|
||||
oauth := EndpointOAuth{}
|
||||
|
||||
providers := []oauthProvider{
|
||||
oauth.Amazon,
|
||||
oauth.Facebook,
|
||||
oauth.Github,
|
||||
oauth.Gitlab,
|
||||
oauth.Google,
|
||||
oauth.Linkedin,
|
||||
oauth.Microsoft,
|
||||
oauth.Twitch,
|
||||
}
|
||||
|
||||
// When no provider config is present, all should return false for Provided()
|
||||
for _, p := range providers {
|
||||
assert.False(t, p.Provided())
|
||||
}
|
||||
|
||||
microsoft := &EndpointOAuthMicrosoft{}
|
||||
microsoft.ClientID = ptr.To("a")
|
||||
|
||||
oauth = EndpointOAuth{
|
||||
Microsoft: microsoft,
|
||||
}
|
||||
// When a provider is present, it should return true for Provided() and ones
|
||||
// that are not provided should return false.
|
||||
assert.True(t, oauth.Microsoft.Provided())
|
||||
assert.False(t, oauth.Google.Provided())
|
||||
assert.False(t, oauth.Twitch.Provided())
|
||||
assert.False(t, oauth.Github.Provided())
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type NgrokModuleSetModules struct {
|
||||
// CircuitBreaker configuration for this module set
|
||||
CircuitBreaker *EndpointCircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
// Compression configuration for this module set
|
||||
Compression *EndpointCompression `json:"compression,omitempty"`
|
||||
// Header configuration for this module set
|
||||
Headers *EndpointHeaders `json:"headers,omitempty"`
|
||||
// IPRestriction configuration for this module set
|
||||
IPRestriction *EndpointIPPolicy `json:"ipRestriction,omitempty"`
|
||||
// OAuth configuration for this module set
|
||||
OAuth *EndpointOAuth `json:"oauth,omitempty"`
|
||||
// Policy module is deprecated, use the `NgrokTrafficPolicy` CRD or the Gateway API instead
|
||||
Policy *EndpointPolicy `json:"policy,omitempty"`
|
||||
// OIDC configuration for this module set
|
||||
OIDC *EndpointOIDC `json:"oidc,omitempty"`
|
||||
// SAML configuration for this module set
|
||||
SAML *EndpointSAML `json:"saml,omitempty"`
|
||||
// TLSTermination configuration for this module set
|
||||
TLSTermination *EndpointTLSTermination `json:"tlsTermination,omitempty"`
|
||||
// MutualTLS configuration for this module set
|
||||
MutualTLS *EndpointMutualTLS `json:"mutualTLS,omitempty"`
|
||||
// WebhookVerification configuration for this module set
|
||||
WebhookVerification *EndpointWebhookVerification `json:"webhookVerification,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="NgrokModuleSet is deprecated and will be removed in a future release. Please migrate to traffic policies instead. See https://github.com/ngrok/ngrok-operator/discussions/654 for more information."
|
||||
|
||||
// NgrokModuleSet is the Schema for the ngrokmodules API
|
||||
type NgrokModuleSet struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Modules NgrokModuleSetModules `json:"modules,omitempty"`
|
||||
}
|
||||
|
||||
func (ms *NgrokModuleSet) IsEmpty() bool {
|
||||
if ms == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
modules := ms.Modules
|
||||
return modules.CircuitBreaker == nil &&
|
||||
modules.Compression == nil &&
|
||||
modules.Headers == nil &&
|
||||
modules.IPRestriction == nil &&
|
||||
modules.OAuth == nil &&
|
||||
modules.Policy == nil &&
|
||||
modules.OIDC == nil &&
|
||||
modules.SAML == nil &&
|
||||
modules.TLSTermination == nil &&
|
||||
modules.MutualTLS == nil &&
|
||||
modules.WebhookVerification == nil
|
||||
}
|
||||
|
||||
func (ms *NgrokModuleSet) Merge(o *NgrokModuleSet) {
|
||||
if o.IsEmpty() { // Empty, nothing to merge
|
||||
return
|
||||
}
|
||||
|
||||
msmod := &ms.Modules
|
||||
omod := o.Modules
|
||||
|
||||
if omod.CircuitBreaker != nil {
|
||||
msmod.CircuitBreaker = omod.CircuitBreaker
|
||||
}
|
||||
if omod.Compression != nil {
|
||||
msmod.Compression = omod.Compression
|
||||
}
|
||||
if omod.Headers != nil {
|
||||
msmod.Headers = omod.Headers
|
||||
}
|
||||
if omod.IPRestriction != nil {
|
||||
msmod.IPRestriction = omod.IPRestriction
|
||||
}
|
||||
if omod.OAuth != nil {
|
||||
msmod.OAuth = omod.OAuth
|
||||
}
|
||||
if omod.Policy != nil {
|
||||
msmod.Policy = omod.Policy
|
||||
}
|
||||
if omod.OIDC != nil {
|
||||
msmod.OIDC = omod.OIDC
|
||||
}
|
||||
if omod.SAML != nil {
|
||||
msmod.SAML = omod.SAML
|
||||
}
|
||||
if omod.TLSTermination != nil {
|
||||
msmod.TLSTermination = omod.TLSTermination
|
||||
}
|
||||
if omod.MutualTLS != nil {
|
||||
msmod.MutualTLS = omod.MutualTLS
|
||||
}
|
||||
if omod.WebhookVerification != nil {
|
||||
msmod.WebhookVerification = omod.WebhookVerification
|
||||
}
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// NgrokModuleSetList contains a list of NgrokModule
|
||||
type NgrokModuleSetList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []NgrokModuleSet `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&NgrokModuleSet{}, &NgrokModuleSetList{})
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNgrokModuleSetIsEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ms *NgrokModuleSet
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
ms: nil,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
ms: &NgrokModuleSet{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-empty",
|
||||
ms: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Headers: &EndpointHeaders{
|
||||
Request: &EndpointRequestHeaders{
|
||||
Add: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.ms.IsEmpty(); got != tt.want {
|
||||
t.Errorf("NgrokModuleSet.IsEmpty() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNgrokModuleSetMerge(t *testing.T) {
|
||||
addHeaders := &EndpointHeaders{
|
||||
Request: &EndpointRequestHeaders{
|
||||
Add: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
compression := &EndpointCompression{
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
m *NgrokModuleSet
|
||||
other *NgrokModuleSet
|
||||
want *NgrokModuleSet
|
||||
}{
|
||||
{
|
||||
name: "both_nil",
|
||||
m: nil,
|
||||
other: nil,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "b_nil",
|
||||
m: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Headers: addHeaders,
|
||||
},
|
||||
},
|
||||
other: nil,
|
||||
want: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Headers: addHeaders,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "neither_nil",
|
||||
m: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Compression: compression,
|
||||
},
|
||||
},
|
||||
other: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Headers: addHeaders,
|
||||
},
|
||||
},
|
||||
want: &NgrokModuleSet{
|
||||
Modules: NgrokModuleSetModules{
|
||||
Headers: addHeaders,
|
||||
Compression: compression,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Merge(tt.other)
|
||||
|
||||
assert.Equal(t, tt.m, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// TCPEdgeSpec defines the desired state of TCPEdge
|
||||
type TCPEdgeSpec struct {
|
||||
ngrokAPICommon `json:",inline"`
|
||||
|
||||
// Backend is the definition for the tunnel group backend
|
||||
// that serves traffic for this edge
|
||||
// +kubebuilder:validation:Required
|
||||
Backend TunnelGroupBackend `json:"backend,omitempty"`
|
||||
|
||||
// IPRestriction is an IPRestriction to apply to this edge
|
||||
IPRestriction *EndpointIPPolicy `json:"ipRestriction,omitempty"`
|
||||
|
||||
// Policy is the raw json policy string that was applied to the ngrok API
|
||||
// +kubebuilder:validation:Schemaless
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
// +kubebuilder:validation:Type=object
|
||||
Policy json.RawMessage `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// TCPEdgeStatus defines the observed state of TCPEdge
|
||||
type TCPEdgeStatus struct {
|
||||
// ID is the unique identifier for this edge
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// URI is the URI of the edge
|
||||
URI string `json:"uri,omitempty"`
|
||||
|
||||
// Hostports served by this edge
|
||||
Hostports []string `json:"hostports,omitempty"`
|
||||
|
||||
// Backend stores the status of the tunnel group backend,
|
||||
// mainly the ID of the backend
|
||||
Backend TunnelGroupBackendStatus `json:"backend,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="TCPEdge is deprecated and will be removed in a future release. Please migrate to CloudEndpoint or AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654 for more information."
|
||||
// +kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.id`,description="Domain ID"
|
||||
// +kubebuilder:printcolumn:name="Hostports",type=string,JSONPath=`.status.hostports`,description="Hostports"
|
||||
// +kubebuilder:printcolumn:name="Backend ID",type=string,JSONPath=`.status.backend.id`,description="Tunnel Group Backend ID"
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age"
|
||||
|
||||
// TCPEdge is the Schema for the tcpedges API
|
||||
type TCPEdge struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TCPEdgeSpec `json:"spec,omitempty"`
|
||||
Status TCPEdgeStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TCPEdgeList contains a list of TCPEdge
|
||||
type TCPEdgeList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []TCPEdge `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&TCPEdge{}, &TCPEdgeList{})
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// TLSEdgeSpec defines the desired state of TLSEdge
|
||||
type TLSEdgeSpec struct {
|
||||
ngrokAPICommon `json:",inline"`
|
||||
|
||||
// Backend is the definition for the tunnel group backend
|
||||
// that serves traffic for this edge
|
||||
// +kubebuilder:validation:Required
|
||||
Backend TunnelGroupBackend `json:"backend,omitempty"`
|
||||
|
||||
// Hostports is a list of hostports served by this edge
|
||||
// +kubebuilder:validation:Required
|
||||
Hostports []string `json:"hostports,omitempty"`
|
||||
|
||||
// IPRestriction is an IPRestriction to apply to this edge
|
||||
IPRestriction *EndpointIPPolicy `json:"ipRestriction,omitempty"`
|
||||
|
||||
TLSTermination *EndpointTLSTermination `json:"tlsTermination,omitempty"`
|
||||
|
||||
MutualTLS *EndpointMutualTLS `json:"mutualTls,omitempty"`
|
||||
|
||||
// Policy is the raw json policy string that was applied to the ngrok API
|
||||
// +kubebuilder:validation:Schemaless
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
// +kubebuilder:validation:Type=object
|
||||
Policy json.RawMessage `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// TLSEdgeStatus defines the observed state of TLSEdge
|
||||
type TLSEdgeStatus struct {
|
||||
// ID is the unique identifier for this edge
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// URI is the URI of the edge
|
||||
URI string `json:"uri,omitempty"`
|
||||
|
||||
// Hostports served by this edge
|
||||
Hostports []string `json:"hostports,omitempty"`
|
||||
|
||||
// Backend stores the status of the tunnel group backend,
|
||||
// mainly the ID of the backend
|
||||
Backend TunnelGroupBackendStatus `json:"backend,omitempty"`
|
||||
|
||||
// Map of hostports to the ngrok assigned CNAME targets
|
||||
CNAMETargets map[string]string `json:"cnameTargets,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="TLSEdge is deprecated and will be removed in a future release. Please migrate to CloudEndpoint or AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654 for more information."
|
||||
// +kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.id`,description="Domain ID"
|
||||
// +kubebuilder:printcolumn:name="Hostports",type=string,JSONPath=`.status.hostports`,description="Hostports"
|
||||
// +kubebuilder:printcolumn:name="Backend ID",type=string,JSONPath=`.status.backend.id`,description="Tunnel Group Backend ID"
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age"
|
||||
|
||||
// TLSEdge is the Schema for the tlsedges API
|
||||
type TLSEdge struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TLSEdgeSpec `json:"spec,omitempty"`
|
||||
Status TLSEdgeStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TLSEdgeList contains a list of TLSEdge
|
||||
type TLSEdgeList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []TLSEdge `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&TLSEdge{}, &TLSEdgeList{})
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
commonv1alpha1 "github.com/ngrok/ngrok-operator/api/common/v1alpha1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// TunnelSpec defines the desired state of Tunnel
|
||||
type TunnelSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
|
||||
// ForwardsTo is the name and port of the service to forward traffic to
|
||||
// +kubebuilder:validation:Required
|
||||
ForwardsTo string `json:"forwardsTo,omitempty"`
|
||||
|
||||
// Labels are key/value pairs that are attached to the tunnel
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
// The configuration for backend connections to services
|
||||
BackendConfig *BackendConfig `json:"backend,omitempty"`
|
||||
|
||||
// Specifies the protocol to use when connecting to the backend. Currently only http1 and http2 are supported
|
||||
// with prior knowledge (defaulting to http1).
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:validation:Enum=http1;http2
|
||||
AppProtocol *commonv1alpha1.ApplicationProtocol `json:"appProtocol,omitempty"`
|
||||
}
|
||||
|
||||
// BackendConfig defines the configuration for backend connections to services.
|
||||
// This can be extended to include ServerName, InsecureSkipVerify, etc. down the road.
|
||||
type BackendConfig struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
}
|
||||
|
||||
// TunnelStatus defines the observed state of Tunnel
|
||||
type TunnelStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:deprecatedversion:warning="Tunnel is deprecated and will be removed in a future release. Please migrate to AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654 for more information."
|
||||
// +kubebuilder:printcolumn:name="ForwardsTo",type=string,JSONPath=`.spec.forwardsTo`,description="Service/port to forward to"
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age"
|
||||
|
||||
// Tunnel is the Schema for the tunnels API
|
||||
type Tunnel struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TunnelSpec `json:"spec,omitempty"`
|
||||
Status TunnelStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TunnelList contains a list of Tunnel
|
||||
type TunnelList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Tunnel `json:"items"`
|
||||
}
|
||||
|
||||
type TunnelGroupBackend struct {
|
||||
ngrokAPICommon `json:",inline"`
|
||||
|
||||
// Labels to watch for tunnels on this backend
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
type TunnelGroupBackendStatus struct {
|
||||
// ID is the unique identifier for this backend
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Tunnel{}, &TunnelList{})
|
||||
}
|
||||
-1227
File diff suppressed because it is too large
Load Diff
@@ -177,17 +177,6 @@ func runAgentController(ctx context.Context, opts agentManagerOpts) error {
|
||||
// register healthcheck for tunnel driver
|
||||
healthcheck.RegisterHealthChecker(td)
|
||||
|
||||
if err = (&agentcontroller.TunnelReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("tunnel"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("tunnel-controller"),
|
||||
TunnelDriver: td,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Tunnel")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&agentcontroller.AgentEndpointReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("agentendpoint"),
|
||||
|
||||
+6
-52
@@ -60,7 +60,6 @@ import (
|
||||
common "github.com/ngrok/ngrok-operator/api/common/v1alpha1"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
bindingscontroller "github.com/ngrok/ngrok-operator/internal/controller/bindings"
|
||||
gatewaycontroller "github.com/ngrok/ngrok-operator/internal/controller/gateway"
|
||||
ingresscontroller "github.com/ngrok/ngrok-operator/internal/controller/ingress"
|
||||
@@ -526,13 +525,12 @@ func getK8sResourceDriver(ctx context.Context, mgr manager.Manager, options apiM
|
||||
// enableIngressFeatureSet enables the Ingress feature set for the operator
|
||||
func enableIngressFeatureSet(_ context.Context, opts apiManagerOpts, mgr ctrl.Manager, driver *managerdriver.Driver, ngrokClientset ngrokapi.Clientset, defaultDomainReclaimPolicy ingressv1alpha1.DomainReclaimPolicy) error {
|
||||
if err := (&ingresscontroller.IngressReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ingress"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("ingress-controller"),
|
||||
Namespace: opts.namespace,
|
||||
AnnotationsExtractor: annotations.NewAnnotationsExtractor(),
|
||||
Driver: driver,
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ingress"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("ingress-controller"),
|
||||
Namespace: opts.namespace,
|
||||
Driver: driver,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
return fmt.Errorf("unable to create ingress controller: %w", err)
|
||||
}
|
||||
@@ -564,39 +562,6 @@ func enableIngressFeatureSet(_ context.Context, opts apiManagerOpts, mgr ctrl.Ma
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ingresscontroller.TCPEdgeReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("tcp-edge"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("tcp-edge-controller"),
|
||||
NgrokClientset: ngrokClientset,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "TCPEdge")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ingresscontroller.TLSEdgeReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("tls-edge"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("tls-edge-controller"),
|
||||
NgrokClientset: ngrokClientset,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "TLSEdge")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ingresscontroller.HTTPSEdgeReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("https-edge"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("https-edge-controller"),
|
||||
NgrokClientset: ngrokClientset,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "HTTPSEdge")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ingresscontroller.IPPolicyReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ip-policy"),
|
||||
@@ -609,17 +574,6 @@ func enableIngressFeatureSet(_ context.Context, opts apiManagerOpts, mgr ctrl.Ma
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ingresscontroller.ModuleSetReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ngrok-module-set"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("ngrok-module-set-controller"),
|
||||
Driver: driver,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "NgrokModuleSet")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := (&ngrokcontroller.NgrokTrafficPolicyReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("traffic-policy"),
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
This is the ngrok ingress controller. It can be deployed and operated to a cluster and operated by a team allowing others to create ingress objects to dynamically self service ingress to their apps and services using a shared ngrok account. This is a great way to get started with ngrok and Kubernetes.
|
||||
|
||||
The controller watches for [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) objects and creates the corresponding ngrok tunnels and edges. More details on how these are derived can be found [here](./user-guide/ingress-to-edge-relationship.md). Other ngrok features such as TCP Edges can be configured via [CRDs](./user-guide/crds.md).
|
||||
The controller watches for [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) objects and creates the corresponding ngrok tunnels and edges. More details on how these are derived can be found [here](./user-guide/ingress-to-edge-relationship.md). Other ngrok features such as Cloud or Agent Endpoints can be configured via [CRDs](./user-guide/crds.md).
|
||||
|
||||
If you are looking to install the controller for the first time, see our [deployment-guide](./deployment-guide/README.md).
|
||||
If it's already installed and you are looking to configure ingress for an app or service, see our [user-guide](./user-guide/README.md).
|
||||
|
||||
@@ -39,7 +39,6 @@ The following controllers for the most part manage a single resource and reflect
|
||||
- [IP Policy Controller](../../internal/controller/ingress/ippolicy_controller.go): It simply watches these CRDs and reflects the changes in the ngrok API.
|
||||
- [Domain Controller](../../internal/controller/ingress/domain_controller.go): It will watch for domain CRDs and reflect those changes in the ngrok API. It will also update the domain CRD objects' status fields with the current state of the domain in the ngrok API, such as a CNAME target if it's a white label domain.
|
||||
- [HTTPS Edge Controller](../../internal/controller/ingress/httpsedge_controller.go): This CRD contains all the data necessary to build not just the edge, but also all routes, backends, and route modules by calling various ngrok APIs to combine resources. The HTTPSEdge CRD is the common type other controllers can create based on different source inputs like Ingress objects or Gateway objects.
|
||||
- [TCP Edge Controller](../../internal/controller/ingress/tcpedge_controller.go): This CRD contains all the data necessary to build the edge and any edge modules configured. It will likely be a first class CRD used by consumers of the controller to create TCP edges because Kubernetes Ingress does not support TCP.
|
||||
|
||||
The following controllers are more complex and manage multiple resources and reflect those changes in the ngrok API.
|
||||
|
||||
@@ -75,8 +74,6 @@ The API manager is a leader elected manager that runs the following controllers
|
||||
* Domain Controller
|
||||
* IP Policy Controller
|
||||
* HTTPS Edge Controller
|
||||
* TCP Edge Controller
|
||||
* TLS Edge Controller
|
||||
* Service Controller
|
||||
* NgrokModuleSet Controller
|
||||
* Gateway Controller
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
@@ -1,4 +0,0 @@
|
||||
config.yaml:
|
||||
paths:
|
||||
"/https-echo-plain": "HTTP/2 200"
|
||||
"/https-echo-tls": "HTTP/2 200"
|
||||
@@ -1,68 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress-https
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /https-echo-plain
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: https-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
- path: /https-echo-tls
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: https-echo-svc
|
||||
port:
|
||||
number: 443
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: https-echo-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: https-echo-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: https-echo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: https-echo-path
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: plain
|
||||
- containerPort: 8443
|
||||
name: tls
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: https-echo-svc
|
||||
labels:
|
||||
app: https-echo-app
|
||||
annotations:
|
||||
k8s.ngrok.com/app-protocols: '{"https-echo-app-tls-port":"HtTpS","https-echo-app-plain-port":"HtTp"}'
|
||||
spec:
|
||||
ports:
|
||||
- name: https-echo-app-plain-port
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
- name: https-echo-app-tls-port
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 8443
|
||||
selector:
|
||||
app: https-echo-app
|
||||
@@ -1,10 +0,0 @@
|
||||
resources:
|
||||
- hello-https-ingress.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: minimal-ingress-https
|
||||
path: config.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
@@ -1,8 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
- op: replace
|
||||
path: /spec/rules/1/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
@@ -1,3 +0,0 @@
|
||||
config.yaml:
|
||||
paths:
|
||||
"/": "HTTP/2 200"
|
||||
@@ -1,149 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
k8s.ngrok.com/response-headers-add: |
|
||||
{
|
||||
"X-SEND-TO-CLIENT": "Value1"
|
||||
}
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
- path: /nginx
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx
|
||||
port:
|
||||
number: 80
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /test
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
- path: /nginx
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress-pt2
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
k8s.ngrok.com/response-headers-add: |
|
||||
{
|
||||
"X-SEND-TO-CLIENT": "Value2"
|
||||
}
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /second
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx
|
||||
port:
|
||||
number: 80
|
||||
- path: /nginx-second
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo-path2
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-svc
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
ports:
|
||||
- name: http-echo-app
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-app
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.21.3
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
ports:
|
||||
- name: nginx
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: nginx
|
||||
@@ -1,16 +0,0 @@
|
||||
resources:
|
||||
- hello-world-ingress.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: minimal-ingress
|
||||
path: config.yaml
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: minimal-ingress-pt2
|
||||
path: config-pt2.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
@@ -1,3 +0,0 @@
|
||||
config.yaml:
|
||||
paths:
|
||||
"/": "HTTP/2 200"
|
||||
@@ -1,62 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: other
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress-2-namespaced
|
||||
namespace: other
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-namespaced-svc
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-namespaced-deply
|
||||
namespace: other
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-namespaced-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-namespaced-app
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo-namespaced-app
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-namespaced-svc
|
||||
namespace: other
|
||||
labels:
|
||||
app: http-echo-namespaced-app
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-namespaced-app
|
||||
@@ -1,10 +0,0 @@
|
||||
resources:
|
||||
- hello-world-namespaced.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: minimal-ingress-2-namespaced
|
||||
path: config.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN 2>
|
||||
@@ -1,5 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
@@ -1,6 +0,0 @@
|
||||
config.yaml:
|
||||
paths:
|
||||
"/": "HTTP/2 200"
|
||||
config-different.yaml:
|
||||
paths:
|
||||
"/": "HTTP/2 404"
|
||||
@@ -1,109 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-class-example
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: ingress-class.ngrok.app
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc-matching-ingress-class
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: different-ingress-class-example
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
spec:
|
||||
ingressClassName: nginx # Example of using a different class than ngrok
|
||||
rules:
|
||||
- host: different-ingress-class.ngrok.app
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc-non-matching-ingress-class
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-deploy-matching-ingress-class
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-app-matching-ingress-class
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-app-matching-ingress-class
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo-path2
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-svc-matching-ingress-class
|
||||
labels:
|
||||
app: http-echo-app-matching-ingress-class
|
||||
spec:
|
||||
ports:
|
||||
- name: http-echo-app
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-app-matching-ingress-class
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-deploy-non-matching-ingress-class
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-app-non-matching-ingress-class
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-app-non-matching-ingress-class
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo-path2
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-svc-non-matching-ingress-class
|
||||
labels:
|
||||
app: http-echo-app-non-matching-ingress-class
|
||||
spec:
|
||||
ports:
|
||||
- name: http-echo-app
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-app-non-matching-ingress-class
|
||||
@@ -1,16 +0,0 @@
|
||||
resources:
|
||||
- ingress-class.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: ingress-class-example
|
||||
path: config.yaml
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: different-ingress-class-example
|
||||
path: config-different.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
# e2e-expected: HTTP/2 200
|
||||
@@ -1,6 +0,0 @@
|
||||
config.yaml:
|
||||
paths:
|
||||
"/": "HTTP/2 200"
|
||||
"/path2": "HTTP/2 200"
|
||||
"/path2/": "HTTP/2 200"
|
||||
"/path2/50x.html": "HTTP/2 200"
|
||||
@@ -1,10 +0,0 @@
|
||||
resources:
|
||||
- multiple-paths.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: multiple-paths
|
||||
path: config.yaml
|
||||
@@ -1,96 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: multiple-paths
|
||||
annotations:
|
||||
k8s.ngrok.com/tls-min-version: "1.3"
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
- path: /path2
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-path2-svc
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-svc
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-app
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-path2-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-path2-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-path2-app
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo-path2
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-path2-svc
|
||||
labels:
|
||||
app: http-echo-path2-app
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-path2-app
|
||||
@@ -1,6 +0,0 @@
|
||||
# 1. Copy this file to ./config.yaml
|
||||
# 2. Set you unique FQDN in the "value" field below
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: <UNIQUE DOMAIN>
|
||||
# e2e-expected: HTTP/2 200
|
||||
@@ -1,10 +0,0 @@
|
||||
resources:
|
||||
- route-modules.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: networking.k8s.io
|
||||
version: v1
|
||||
kind: Ingress
|
||||
name: route-modules
|
||||
path: config.yaml
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
kind: IPPolicy
|
||||
apiVersion: ingress.k8s.ngrok.com/v1alpha1
|
||||
metadata:
|
||||
name: route-module-policy
|
||||
spec:
|
||||
rules:
|
||||
- action: deny
|
||||
cidr: "83.25.12.0/24"
|
||||
---
|
||||
kind: NgrokModuleSet
|
||||
apiVersion: ingress.k8s.ngrok.com/v1alpha1
|
||||
metadata:
|
||||
name: route-module
|
||||
modules:
|
||||
tlsTermination:
|
||||
minVersion: "1.3"
|
||||
compression:
|
||||
enabled: true
|
||||
ipRestriction:
|
||||
policies:
|
||||
- route-module-policy
|
||||
oauth:
|
||||
google:
|
||||
emailDomains:
|
||||
- "ngrok.com"
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: route-modules
|
||||
annotations:
|
||||
k8s.ngrok.com/modules: route-module
|
||||
spec:
|
||||
ingressClassName: ngrok
|
||||
rules:
|
||||
- host: SET-VIA-config.yaml
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: http-echo-svc
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: http-echo-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: http-echo-app
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
containers:
|
||||
- name: http-echo
|
||||
image: mendhak/http-https-echo:24
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: http-echo-svc
|
||||
labels:
|
||||
app: http-echo-app
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: http-echo-app
|
||||
@@ -7,7 +7,6 @@ require (
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/ngrok/ngrok-api-go/v7 v7.3.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.36.3
|
||||
@@ -23,7 +22,6 @@ require (
|
||||
k8s.io/api v0.32.1
|
||||
k8s.io/apimachinery v0.32.1
|
||||
k8s.io/client-go v0.32.1
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
|
||||
sigs.k8s.io/controller-runtime v0.20.2
|
||||
sigs.k8s.io/gateway-api v1.2.1
|
||||
@@ -57,6 +55,7 @@ require (
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible // indirect
|
||||
github.com/inconshreveable/log15/v3 v3.0.0-testing.5 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
@@ -100,6 +99,7 @@ require (
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.32.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230103223931-056869c967cd // indirect
|
||||
sigs.k8s.io/controller-tools v0.16.3 // indirect
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
-1018
File diff suppressed because it is too large
Load Diff
@@ -1,136 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: tcpedges.ingress.k8s.ngrok.com
|
||||
spec:
|
||||
group: ingress.k8s.ngrok.com
|
||||
names:
|
||||
kind: TCPEdge
|
||||
listKind: TCPEdgeList
|
||||
plural: tcpedges
|
||||
singular: tcpedge
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Domain ID
|
||||
jsonPath: .status.id
|
||||
name: ID
|
||||
type: string
|
||||
- description: Hostports
|
||||
jsonPath: .status.hostports
|
||||
name: Hostports
|
||||
type: string
|
||||
- description: Tunnel Group Backend ID
|
||||
jsonPath: .status.backend.id
|
||||
name: Backend ID
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
deprecated: true
|
||||
deprecationWarning: TCPEdge is deprecated and will be removed in a future release.
|
||||
Please migrate to CloudEndpoint or AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654
|
||||
for more information.
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TCPEdge is the Schema for the tcpedges API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TCPEdgeSpec defines the desired state of TCPEdge
|
||||
properties:
|
||||
backend:
|
||||
description: |-
|
||||
Backend is the definition for the tunnel group backend
|
||||
that serves traffic for this edge
|
||||
properties:
|
||||
description:
|
||||
default: Created by kubernetes-ingress-controller
|
||||
description: Description is a human-readable description of the
|
||||
object in the ngrok API/Dashboard
|
||||
type: string
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Labels to watch for tunnels on this backend
|
||||
type: object
|
||||
metadata:
|
||||
default: '{"owned-by":"kubernetes-ingress-controller"}'
|
||||
description: Metadata is a string of arbitrary data associated
|
||||
with the object in the ngrok API/Dashboard
|
||||
type: string
|
||||
type: object
|
||||
description:
|
||||
default: Created by kubernetes-ingress-controller
|
||||
description: Description is a human-readable description of the object
|
||||
in the ngrok API/Dashboard
|
||||
type: string
|
||||
ipRestriction:
|
||||
description: IPRestriction is an IPRestriction to apply to this edge
|
||||
properties:
|
||||
policies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
metadata:
|
||||
default: '{"owned-by":"kubernetes-ingress-controller"}'
|
||||
description: Metadata is a string of arbitrary data associated with
|
||||
the object in the ngrok API/Dashboard
|
||||
type: string
|
||||
policy:
|
||||
description: Policy is the raw json policy string that was applied
|
||||
to the ngrok API
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
status:
|
||||
description: TCPEdgeStatus defines the observed state of TCPEdge
|
||||
properties:
|
||||
backend:
|
||||
description: |-
|
||||
Backend stores the status of the tunnel group backend,
|
||||
mainly the ID of the backend
|
||||
properties:
|
||||
id:
|
||||
description: ID is the unique identifier for this backend
|
||||
type: string
|
||||
type: object
|
||||
hostports:
|
||||
description: Hostports served by this edge
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
id:
|
||||
description: ID is the unique identifier for this edge
|
||||
type: string
|
||||
uri:
|
||||
description: URI is the URI of the edge
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,175 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: tlsedges.ingress.k8s.ngrok.com
|
||||
spec:
|
||||
group: ingress.k8s.ngrok.com
|
||||
names:
|
||||
kind: TLSEdge
|
||||
listKind: TLSEdgeList
|
||||
plural: tlsedges
|
||||
singular: tlsedge
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Domain ID
|
||||
jsonPath: .status.id
|
||||
name: ID
|
||||
type: string
|
||||
- description: Hostports
|
||||
jsonPath: .status.hostports
|
||||
name: Hostports
|
||||
type: string
|
||||
- description: Tunnel Group Backend ID
|
||||
jsonPath: .status.backend.id
|
||||
name: Backend ID
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
deprecated: true
|
||||
deprecationWarning: TLSEdge is deprecated and will be removed in a future release.
|
||||
Please migrate to CloudEndpoint or AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654
|
||||
for more information.
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TLSEdge is the Schema for the tlsedges API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TLSEdgeSpec defines the desired state of TLSEdge
|
||||
properties:
|
||||
backend:
|
||||
description: |-
|
||||
Backend is the definition for the tunnel group backend
|
||||
that serves traffic for this edge
|
||||
properties:
|
||||
description:
|
||||
default: Created by kubernetes-ingress-controller
|
||||
description: Description is a human-readable description of the
|
||||
object in the ngrok API/Dashboard
|
||||
type: string
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Labels to watch for tunnels on this backend
|
||||
type: object
|
||||
metadata:
|
||||
default: '{"owned-by":"kubernetes-ingress-controller"}'
|
||||
description: Metadata is a string of arbitrary data associated
|
||||
with the object in the ngrok API/Dashboard
|
||||
type: string
|
||||
type: object
|
||||
description:
|
||||
default: Created by kubernetes-ingress-controller
|
||||
description: Description is a human-readable description of the object
|
||||
in the ngrok API/Dashboard
|
||||
type: string
|
||||
hostports:
|
||||
description: Hostports is a list of hostports served by this edge
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ipRestriction:
|
||||
description: IPRestriction is an IPRestriction to apply to this edge
|
||||
properties:
|
||||
policies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
metadata:
|
||||
default: '{"owned-by":"kubernetes-ingress-controller"}'
|
||||
description: Metadata is a string of arbitrary data associated with
|
||||
the object in the ngrok API/Dashboard
|
||||
type: string
|
||||
mutualTls:
|
||||
properties:
|
||||
certificateAuthorities:
|
||||
description: |-
|
||||
List of CA IDs that will be used to validate incoming connections to the
|
||||
edge.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
policy:
|
||||
description: Policy is the raw json policy string that was applied
|
||||
to the ngrok API
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
tlsTermination:
|
||||
properties:
|
||||
minVersion:
|
||||
description: MinVersion is the minimum TLS version to allow for
|
||||
connections to the edge
|
||||
type: string
|
||||
terminateAt:
|
||||
default: edge
|
||||
description: |-
|
||||
TerminateAt determines where the TLS connection should be terminated.
|
||||
"edge" if the ngrok edge should terminate TLS traffic, "upstream" if TLS
|
||||
traffic should be passed through to the upstream ngrok agent /
|
||||
application server for termination.
|
||||
enum:
|
||||
- edge
|
||||
- agent
|
||||
- upstream
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: TLSEdgeStatus defines the observed state of TLSEdge
|
||||
properties:
|
||||
backend:
|
||||
description: |-
|
||||
Backend stores the status of the tunnel group backend,
|
||||
mainly the ID of the backend
|
||||
properties:
|
||||
id:
|
||||
description: ID is the unique identifier for this backend
|
||||
type: string
|
||||
type: object
|
||||
cnameTargets:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Map of hostports to the ngrok assigned CNAME targets
|
||||
type: object
|
||||
hostports:
|
||||
description: Hostports served by this edge
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
id:
|
||||
description: ID is the unique identifier for this edge
|
||||
type: string
|
||||
uri:
|
||||
description: URI is the URI of the edge
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: tunnels.ingress.k8s.ngrok.com
|
||||
spec:
|
||||
group: ingress.k8s.ngrok.com
|
||||
names:
|
||||
kind: Tunnel
|
||||
listKind: TunnelList
|
||||
plural: tunnels
|
||||
singular: tunnel
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Service/port to forward to
|
||||
jsonPath: .spec.forwardsTo
|
||||
name: ForwardsTo
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
deprecated: true
|
||||
deprecationWarning: Tunnel is deprecated and will be removed in a future release.
|
||||
Please migrate to AgentEndpoint instead. See https://github.com/ngrok/ngrok-operator/discussions/654
|
||||
for more information.
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Tunnel is the Schema for the tunnels API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TunnelSpec defines the desired state of Tunnel
|
||||
properties:
|
||||
appProtocol:
|
||||
description: |-
|
||||
Specifies the protocol to use when connecting to the backend. Currently only http1 and http2 are supported
|
||||
with prior knowledge (defaulting to http1).
|
||||
enum:
|
||||
- http1
|
||||
- http2
|
||||
type: string
|
||||
backend:
|
||||
description: The configuration for backend connections to services
|
||||
properties:
|
||||
protocol:
|
||||
type: string
|
||||
type: object
|
||||
forwardsTo:
|
||||
description: ForwardsTo is the name and port of the service to forward
|
||||
traffic to
|
||||
type: string
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Labels are key/value pairs that are attached to the tunnel
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: TunnelStatus defines the observed state of Tunnel
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,27 +0,0 @@
|
||||
# permissions for end users to edit httpsedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-httpsedge-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,23 +0,0 @@
|
||||
# permissions for end users to view httpsedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-httpsedge-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,27 +0,0 @@
|
||||
# permissions for end users to edit ngrokmodulesets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-ngrokmoduleset-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,23 +0,0 @@
|
||||
# permissions for end users to view ngrokmodulesets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-ngrokmoduleset-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets/status
|
||||
verbs:
|
||||
- get
|
||||
-112
@@ -228,32 +228,6 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
@@ -280,92 +254,6 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# permissions for end users to edit tcpedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tcpedge-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,23 +0,0 @@
|
||||
# permissions for end users to view tcpedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tcpedge-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,27 +0,0 @@
|
||||
# permissions for end users to edit tlsedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tlsedge-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,23 +0,0 @@
|
||||
# permissions for end users to view tlsedges.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tlsedge-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,27 +0,0 @@
|
||||
# permissions for end users to edit tunnels.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tunnel-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,23 +0,0 @@
|
||||
# permissions for end users to view tunnels.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "ngrok-operator.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rbac
|
||||
name: {{ include "ngrok-operator.fullname" . }}-tunnel-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/status
|
||||
verbs:
|
||||
- get
|
||||
+4
-228
@@ -4,7 +4,7 @@ Should match all-options snapshot:
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/controller-role: 7a9dd902b42a29f69ffbd6c3b6dddda72b68e4f0ee3af37b60dc4dfd103d4ac4
|
||||
checksum/controller-role: e108215aa508729e0a9d9a12bed3b67d1fef8007aecee1e86b8d3ea8f872b829
|
||||
checksum/rbac: 5d27f1783f54a2ab8e69f9bfce35eef2348fda3f6455526619973781d9549322
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
@@ -26,7 +26,7 @@ Should match all-options snapshot:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/controller-role: 7a9dd902b42a29f69ffbd6c3b6dddda72b68e4f0ee3af37b60dc4dfd103d4ac4
|
||||
checksum/controller-role: e108215aa508729e0a9d9a12bed3b67d1fef8007aecee1e86b8d3ea8f872b829
|
||||
checksum/rbac: 5d27f1783f54a2ab8e69f9bfce35eef2348fda3f6455526619973781d9549322
|
||||
checksum/secret: 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
|
||||
prometheus.io/path: /metrics
|
||||
@@ -442,32 +442,6 @@ Should match all-options snapshot:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
@@ -494,92 +468,6 @@ Should match all-options snapshot:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
@@ -716,7 +604,7 @@ Should match default snapshot:
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/controller-role: 7a9dd902b42a29f69ffbd6c3b6dddda72b68e4f0ee3af37b60dc4dfd103d4ac4
|
||||
checksum/controller-role: e108215aa508729e0a9d9a12bed3b67d1fef8007aecee1e86b8d3ea8f872b829
|
||||
checksum/rbac: 5d27f1783f54a2ab8e69f9bfce35eef2348fda3f6455526619973781d9549322
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
@@ -738,7 +626,7 @@ Should match default snapshot:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/controller-role: 7a9dd902b42a29f69ffbd6c3b6dddda72b68e4f0ee3af37b60dc4dfd103d4ac4
|
||||
checksum/controller-role: e108215aa508729e0a9d9a12bed3b67d1fef8007aecee1e86b8d3ea8f872b829
|
||||
checksum/rbac: 5d27f1783f54a2ab8e69f9bfce35eef2348fda3f6455526619973781d9549322
|
||||
checksum/secret: 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
|
||||
prometheus.io/path: /metrics
|
||||
@@ -1141,32 +1029,6 @@ Should match default snapshot:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- httpsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
@@ -1193,92 +1055,6 @@ Should match default snapshot:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- ngrokmodulesets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tcpedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tlsedges/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ingress.k8s.ngrok.com
|
||||
resources:
|
||||
- tunnels/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
|
||||
@@ -20,17 +20,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/compression"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/headers"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/ip_policies"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/tls"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/webhook_verification"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
networking "k8s.io/api/networking/v1"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
@@ -69,8 +60,6 @@ const (
|
||||
type MappingStrategy string
|
||||
|
||||
const (
|
||||
MappingStrategy_Edges MappingStrategy = "edges"
|
||||
|
||||
// The default strategy when translating resources into AgentEndpoint / CloudEndpoint that prioritizes collapsing into a single public AgentEndpoint when possible
|
||||
MappingStrategy_EndpointsDefault MappingStrategy = "endpoints"
|
||||
|
||||
@@ -78,77 +67,6 @@ const (
|
||||
MappingStrategy_EndpointsVerbose MappingStrategy = "endpoints-verbose"
|
||||
)
|
||||
|
||||
type RouteModules struct {
|
||||
Compression *ingressv1alpha1.EndpointCompression
|
||||
Headers *ingressv1alpha1.EndpointHeaders
|
||||
IPRestriction *ingressv1alpha1.EndpointIPPolicy
|
||||
TLSTermination *ingressv1alpha1.EndpointTLSTerminationAtEdge
|
||||
WebhookVerification *ingressv1alpha1.EndpointWebhookVerification
|
||||
}
|
||||
|
||||
type Extractor struct {
|
||||
annotations map[string]parser.Annotation
|
||||
}
|
||||
|
||||
func NewAnnotationsExtractor() Extractor {
|
||||
return Extractor{
|
||||
annotations: map[string]parser.Annotation{
|
||||
"Compression": compression.NewParser(),
|
||||
"Headers": headers.NewParser(),
|
||||
"IPRestriction": ip_policies.NewParser(),
|
||||
"TLSTermination": tls.NewParser(),
|
||||
"WebhookVerification": webhook_verification.NewParser(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Extract extracts the annotations from an Ingress
|
||||
func (e Extractor) Extract(ing *networking.Ingress) *RouteModules {
|
||||
pia := &RouteModules{}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
for name, annotationParser := range e.annotations {
|
||||
val, err := annotationParser.Parse(ing)
|
||||
klog.V(5).InfoS("Parsing Ingress annotation", "name", name, "ingress", klog.KObj(ing), "value", val)
|
||||
if err != nil {
|
||||
if errors.IsMissingAnnotations(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !errors.IsLocationDenied(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
_, alreadyDenied := data[DeniedKeyName]
|
||||
if !alreadyDenied {
|
||||
errString := err.Error()
|
||||
data[DeniedKeyName] = &errString
|
||||
klog.ErrorS(err, "error reading Ingress annotation", "name", name, "ingress", klog.KObj(ing))
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(5).ErrorS(err, "error reading Ingress annotation", "name", name, "ingress", klog.KObj(ing))
|
||||
}
|
||||
|
||||
if val != nil {
|
||||
data[name] = val
|
||||
}
|
||||
}
|
||||
|
||||
err := mergo.MapWithOverwrite(pia, data)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "unexpected error merging extracted annotations")
|
||||
}
|
||||
|
||||
return pia
|
||||
}
|
||||
|
||||
// Extracts a list of module set names from the annotation
|
||||
// k8s.ngrok.com/modules: "module1,module2"
|
||||
func ExtractNgrokModuleSetsFromAnnotations(obj client.Object) ([]string, error) {
|
||||
return parser.GetStringSliceAnnotation("modules", obj)
|
||||
}
|
||||
|
||||
// Extracts a single traffic policy str from the annotation
|
||||
// k8s.ngrok.com/traffic-policy: "module1"
|
||||
func ExtractNgrokTrafficPolicyFromAnnotations(obj client.Object) (string, error) {
|
||||
@@ -169,19 +87,6 @@ func ExtractNgrokTrafficPolicyFromAnnotations(obj client.Object) (string, error)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Whether or not we should use edges in building the ngrok model for resources. Extracts the value
|
||||
// from the annotation "k8s.ngrok.com/mapping-strategy" if it is present. Otherwise, it defaults to false
|
||||
func ExtractUseEdges(obj client.Object) (bool, error) {
|
||||
val, err := parser.GetStringAnnotation(MappingStrategyAnnotationKey, obj)
|
||||
if err != nil {
|
||||
if errors.IsMissingAnnotations(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return strings.EqualFold(val, string(MappingStrategy_Edges)), nil
|
||||
}
|
||||
|
||||
// Whether or not we should use endpoint pooling
|
||||
// from the annotation "k8s.ngrok.com/pooling-enabled" if it is present. Otherwise, it defaults to false
|
||||
func ExtractUseEndpointPooling(obj client.Object) (bool, error) {
|
||||
|
||||
@@ -81,67 +81,6 @@ func TestExtractNgrokTrafficPolicyFromAnnotations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUseEdges(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations map[string]string
|
||||
expected bool
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "Valid mapping strategy: edges",
|
||||
annotations: map[string]string{
|
||||
"k8s.ngrok.com/mapping-strategy": "edges",
|
||||
},
|
||||
expected: true,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Valid mapping strategy: endpoints",
|
||||
annotations: map[string]string{
|
||||
"k8s.ngrok.com/mapping-strategy": "endpoints",
|
||||
},
|
||||
expected: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "No annotations (default)",
|
||||
annotations: nil,
|
||||
expected: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Invalid mapping strategy",
|
||||
annotations: map[string]string{
|
||||
"k8s.ngrok.com/mapping-strategy": "invalid",
|
||||
},
|
||||
expected: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
obj := &networking.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ingress",
|
||||
Namespace: "default",
|
||||
Annotations: tc.annotations,
|
||||
},
|
||||
}
|
||||
|
||||
useEdges, err := annotations.ExtractUseEdges(obj)
|
||||
if tc.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, useEdges)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUseEndpointPooling(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type compression struct{}
|
||||
|
||||
func NewParser() parser.Annotation {
|
||||
return compression{}
|
||||
}
|
||||
|
||||
// Parse parses the annotations contained in the ingress and returns a
|
||||
// compression configuration or an error. If no compression annotations are
|
||||
// found, the returned error an errors.ErrMissingAnnotations.
|
||||
func (c compression) Parse(obj client.Object) (interface{}, error) {
|
||||
v, err := parser.GetBoolAnnotation("https-compression", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: v,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/testutil"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCompressionWhenNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
ing.SetAnnotations(map[string]string{})
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestCompressionWhenSuppliedAndTrue(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("https-compression")] = "true"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
|
||||
compression, ok := parsed.(*ingressv1alpha1.EndpointCompression)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ingressv1alpha1.EndpointCompression, got %T", parsed)
|
||||
}
|
||||
assert.Equal(t, true, compression.Enabled)
|
||||
}
|
||||
|
||||
func TestCompressionWhenSuppliedAndFalse(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("https-compression")] = "false"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
|
||||
compression, ok := parsed.(*ingressv1alpha1.EndpointCompression)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ingressv1alpha1.EndpointCompression, got %T", parsed)
|
||||
}
|
||||
assert.Equal(t, false, compression.Enabled)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type EndpointHeaders = ingressv1alpha1.EndpointHeaders
|
||||
type EndpointRequestHeaders = ingressv1alpha1.EndpointRequestHeaders
|
||||
type EndpointResponseHeaders = ingressv1alpha1.EndpointResponseHeaders
|
||||
|
||||
type headers struct{}
|
||||
|
||||
func NewParser() parser.Annotation {
|
||||
return headers{}
|
||||
}
|
||||
|
||||
func (h headers) Parse(obj client.Object) (interface{}, error) {
|
||||
parsed := &EndpointHeaders{}
|
||||
|
||||
v, err := parser.GetStringSliceAnnotation("request-headers-remove", obj)
|
||||
if err != nil {
|
||||
if !errors.IsMissingAnnotations(err) {
|
||||
return parsed, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(v) > 0 {
|
||||
if parsed.Request == nil {
|
||||
parsed.Request = &EndpointRequestHeaders{}
|
||||
}
|
||||
parsed.Request.Remove = v
|
||||
}
|
||||
|
||||
m, err := parser.GetStringMapAnnotation("request-headers-add", obj)
|
||||
if err != nil {
|
||||
if !errors.IsMissingAnnotations(err) {
|
||||
return parsed, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) > 0 {
|
||||
if parsed.Request == nil {
|
||||
parsed.Request = &EndpointRequestHeaders{}
|
||||
}
|
||||
parsed.Request.Add = m
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !errors.IsMissingAnnotations(err) {
|
||||
return parsed, err
|
||||
}
|
||||
}
|
||||
|
||||
v, err = parser.GetStringSliceAnnotation("response-headers-remove", obj)
|
||||
if err != nil {
|
||||
if !errors.IsMissingAnnotations(err) {
|
||||
return parsed, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(v) > 0 {
|
||||
if parsed.Response == nil {
|
||||
parsed.Response = &EndpointResponseHeaders{}
|
||||
}
|
||||
parsed.Response.Remove = v
|
||||
}
|
||||
|
||||
m, err = parser.GetStringMapAnnotation("response-headers-add", obj)
|
||||
if err != nil {
|
||||
if !errors.IsMissingAnnotations(err) {
|
||||
return parsed, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) > 0 {
|
||||
if parsed.Response == nil {
|
||||
parsed.Response = &EndpointResponseHeaders{}
|
||||
}
|
||||
parsed.Response.Add = m
|
||||
}
|
||||
|
||||
// If none of the annotations are present, return the missing annotations error
|
||||
if parsed.Request == nil && parsed.Response == nil {
|
||||
return nil, errors.ErrMissingAnnotations
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/testutil"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHeadersWhenNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
ing.SetAnnotations(map[string]string{})
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestHeadersWhenRequestHeadersSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("request-headers-remove")] = "Server"
|
||||
annotations[parser.GetAnnotationWithPrefix("request-headers-add")] = `{"X-Request-Header": "value"}`
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, parsed)
|
||||
|
||||
endpointHeaders, ok := parsed.(*EndpointHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("expected *EndpointHeaders, got %T", parsed)
|
||||
}
|
||||
|
||||
assert.Nil(t, endpointHeaders.Response)
|
||||
assert.Equal(t, []string{"Server"}, endpointHeaders.Request.Remove)
|
||||
assert.Equal(t, map[string]string{"X-Request-Header": "value"}, endpointHeaders.Request.Add)
|
||||
}
|
||||
|
||||
func TestHeadersWhenResponseHeadersSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("response-headers-remove")] = "Server"
|
||||
annotations[parser.GetAnnotationWithPrefix("response-headers-add")] = `{"X-Response-Header": "value"}`
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, parsed)
|
||||
|
||||
endpointHeaders, ok := parsed.(*EndpointHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("expected *EndpointHeaders, got %T", parsed)
|
||||
}
|
||||
assert.Nil(t, endpointHeaders.Request)
|
||||
assert.Equal(t, []string{"Server"}, endpointHeaders.Response.Remove)
|
||||
assert.Equal(t, map[string]string{"X-Response-Header": "value"}, endpointHeaders.Response.Add)
|
||||
}
|
||||
|
||||
func TestInvalidRequestHeadersAdd(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
// Not valid JSON
|
||||
annotations[parser.GetAnnotationWithPrefix("request-headers-add")] = `{X-Request-Header: value}`
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
_, err := NewParser().Parse(ing)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsInvalidContent(err))
|
||||
}
|
||||
|
||||
func TestInvalidResponseHeadersAdd(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
// Not valid JSON
|
||||
annotations[parser.GetAnnotationWithPrefix("response-headers-add")] = `{X-Response-Header: value}`
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
_, err := NewParser().Parse(ing)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsInvalidContent(err))
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package ip_policies
|
||||
|
||||
import (
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type ipPolicy struct{}
|
||||
|
||||
func NewParser() parser.Annotation {
|
||||
return ipPolicy{}
|
||||
}
|
||||
|
||||
func (p ipPolicy) Parse(obj client.Object) (interface{}, error) {
|
||||
v, err := parser.GetStringSliceAnnotation("ip-policies", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ingressv1alpha1.EndpointIPPolicy{IPPolicies: v}, nil
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package ip_policies
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParsesIPPolicies(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("ip-policies")] = "abcd1234,some-test-policy"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
policies, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, policies)
|
||||
assert.Equal(t, policies, &ingressv1alpha1.EndpointIPPolicy{
|
||||
IPPolicies: []string{"abcd1234", "some-test-policy"},
|
||||
})
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type EndpointTLSTerminationAtEdge = ingressv1alpha1.EndpointTLSTerminationAtEdge
|
||||
|
||||
type tls struct{}
|
||||
|
||||
func NewParser() parser.Annotation {
|
||||
return tls{}
|
||||
}
|
||||
|
||||
// Parse parses the annotations contained in the ingress and returns a
|
||||
// tls configuration or an error. If no tls annotations are
|
||||
// found, the returned error an errors.ErrMissingAnnotations.
|
||||
func (t tls) Parse(obj client.Object) (interface{}, error) {
|
||||
v, err := parser.GetStringAnnotation("tls-min-version", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &EndpointTLSTerminationAtEdge{MinVersion: v}, nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/testutil"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTLSTerminationWhenNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
ing.SetAnnotations(map[string]string{})
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestTLSTerminationWhenSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("tls-min-version")] = "1.3"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tlsTermination, ok := parsed.(*EndpointTLSTerminationAtEdge)
|
||||
if !ok {
|
||||
t.Fatalf("expected *EndpointTLSTerminationAtEdge, got %T", parsed)
|
||||
}
|
||||
assert.Equal(t, "1.3", tlsTermination.MinVersion)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package webhook_verification
|
||||
|
||||
import (
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type EndpointWebhookVerification = ingressv1alpha1.EndpointWebhookVerification
|
||||
type SecretKeyRef = ingressv1alpha1.SecretKeyRef
|
||||
|
||||
type webhookVerification struct{}
|
||||
|
||||
func NewParser() parser.Annotation {
|
||||
return webhookVerification{}
|
||||
}
|
||||
|
||||
func (wv webhookVerification) Parse(obj client.Object) (interface{}, error) {
|
||||
provider, err := parser.GetStringAnnotation("webhook-verification-provider", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if provider == "sns" {
|
||||
return &EndpointWebhookVerification{Provider: provider}, nil
|
||||
}
|
||||
|
||||
secretName, err := parser.GetStringAnnotation("webhook-verification-secret-name", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretKey, err := parser.GetStringAnnotation("webhook-verification-secret-key", obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EndpointWebhookVerification{
|
||||
Provider: provider,
|
||||
SecretRef: &SecretKeyRef{
|
||||
Name: secretName,
|
||||
Key: secretKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package webhook_verification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/testutil"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWebhookVerificationWhenNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
ing.SetAnnotations(map[string]string{})
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestWebhookVerificationWhenSecretRefDataNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-provider")] = "github"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestWebhookVerificationWhenSecretRefNameNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-provider")] = "github"
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-secret-name")] = "my-webhook-secret"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestWebhookVerificationWhenSecretRefKeyNotSupplied(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-provider")] = "github"
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-secret-key")] = "SECRET_TOKEN"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.Nil(t, parsed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsMissingAnnotations(err))
|
||||
}
|
||||
|
||||
func TestWebhookVerificationWhenAnnotationsAreProvided(t *testing.T) {
|
||||
ing := testutil.NewIngress()
|
||||
annotations := map[string]string{}
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-provider")] = "github"
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-secret-name")] = "my-webhook-secret"
|
||||
annotations[parser.GetAnnotationWithPrefix("webhook-verification-secret-key")] = "SECRET_TOKEN"
|
||||
ing.SetAnnotations(annotations)
|
||||
|
||||
parsed, err := NewParser().Parse(ing)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, parsed)
|
||||
|
||||
webhookVerification, ok := parsed.(*ingressv1alpha1.EndpointWebhookVerification)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ingressv1alpha1.WebhookVerification, got %T", parsed)
|
||||
}
|
||||
|
||||
assert.Equal(t, "github", webhookVerification.Provider)
|
||||
assert.Equal(t, "my-webhook-secret", webhookVerification.SecretRef.Name)
|
||||
assert.Equal(t, "SECRET_TOKEN", webhookVerification.SecretRef.Key)
|
||||
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/controller"
|
||||
"github.com/ngrok/ngrok-operator/pkg/tunneldriver"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
)
|
||||
|
||||
// TunnelReconciler reconciles a Tunnel object
|
||||
type TunnelReconciler struct {
|
||||
client.Client
|
||||
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
TunnelDriver *tunneldriver.TunnelDriver
|
||||
|
||||
controller *controller.BaseController[*ingressv1alpha1.Tunnel]
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager
|
||||
func (r *TunnelReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
var err error
|
||||
|
||||
if r.TunnelDriver == nil {
|
||||
return errors.New("TunnelDriver is nil")
|
||||
}
|
||||
|
||||
r.controller = &controller.BaseController[*ingressv1alpha1.Tunnel]{
|
||||
Kube: r.Client,
|
||||
Log: r.Log,
|
||||
Recorder: r.Recorder,
|
||||
|
||||
Update: r.update,
|
||||
Delete: r.delete,
|
||||
StatusID: r.statusID,
|
||||
}
|
||||
|
||||
cont, err := controllerruntime.NewUnmanaged("tunnel-controller", mgr, controllerruntime.Options{
|
||||
Reconciler: r,
|
||||
LogConstructor: func(_ *reconcile.Request) logr.Logger {
|
||||
return r.Log
|
||||
},
|
||||
NeedLeaderElection: ptr.To(false),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cont.Watch(source.Kind[client.Object](
|
||||
mgr.GetCache(),
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
&handler.EnqueueRequestForObject{},
|
||||
predicate.Or(
|
||||
predicate.AnnotationChangedPredicate{},
|
||||
predicate.GenerationChangedPredicate{},
|
||||
),
|
||||
)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mgr.Add(cont)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tunnels,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tunnels/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tunnels/finalizers,verbs=update
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
//
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
func (r *TunnelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
return r.controller.Reconcile(ctx, req, new(ingressv1alpha1.Tunnel))
|
||||
}
|
||||
|
||||
func (r *TunnelReconciler) update(ctx context.Context, tunnel *ingressv1alpha1.Tunnel) error {
|
||||
tunnelName := r.statusID(tunnel)
|
||||
return r.TunnelDriver.CreateTunnel(ctx, tunnelName, tunnel.Spec)
|
||||
}
|
||||
|
||||
func (r *TunnelReconciler) delete(ctx context.Context, tunnel *ingressv1alpha1.Tunnel) error {
|
||||
tunnelName := r.statusID(tunnel)
|
||||
return r.TunnelDriver.DeleteTunnel(ctx, tunnelName)
|
||||
}
|
||||
|
||||
func (r *TunnelReconciler) statusID(tunnel *ingressv1alpha1.Tunnel) string {
|
||||
return fmt.Sprintf("%s/%s", tunnel.Namespace, tunnel.Name)
|
||||
}
|
||||
@@ -373,9 +373,6 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
&gatewayv1.HTTPRoute{},
|
||||
// &corev1.Service{},
|
||||
&ingressv1alpha1.Domain{},
|
||||
&ingressv1alpha1.HTTPSEdge{},
|
||||
// &ingressv1alpha1.Tunnel{},
|
||||
// &ingressv1alpha1.NgrokModuleSet{},
|
||||
}
|
||||
|
||||
bldr := ctrl.NewControllerManagedBy(mgr).For(&gatewayv1.Gateway{})
|
||||
|
||||
@@ -133,9 +133,6 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
&gatewayv1.GatewayClass{},
|
||||
&corev1.Service{},
|
||||
&ingressv1alpha1.Domain{},
|
||||
&ingressv1alpha1.HTTPSEdge{},
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
// &ingressv1alpha1.NgrokModuleSet{},
|
||||
}
|
||||
|
||||
builder := ctrl.NewControllerManagedBy(mgr).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("HTTPSEdgeController", func() {
|
||||
DescribeTable("isMigratingAuthProviders", func(current *ngrok.HTTPSEdgeRoute, desired *ingressv1alpha1.HTTPSEdgeRouteSpec, expected bool) {
|
||||
Expect(isMigratingAuthProviders(current, desired)).To(Equal(expected))
|
||||
},
|
||||
Entry("no auth", &ngrok.HTTPSEdgeRoute{}, &ingressv1alpha1.HTTPSEdgeRouteSpec{}, false),
|
||||
Entry("Same Auth: Oauth", &ngrok.HTTPSEdgeRoute{OAuth: &ngrok.EndpointOAuth{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OAuth: &ingressv1alpha1.EndpointOAuth{}}, false),
|
||||
Entry("Same Auth: SAML", &ngrok.HTTPSEdgeRoute{SAML: &ngrok.EndpointSAML{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{SAML: &ingressv1alpha1.EndpointSAML{}}, false),
|
||||
Entry("Same Auth: OIDC", &ngrok.HTTPSEdgeRoute{OIDC: &ngrok.EndpointOIDC{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OIDC: &ingressv1alpha1.EndpointOIDC{}}, false),
|
||||
Entry("Added Oauth", &ngrok.HTTPSEdgeRoute{}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OAuth: &ingressv1alpha1.EndpointOAuth{}}, false),
|
||||
Entry("Added SAML", &ngrok.HTTPSEdgeRoute{}, &ingressv1alpha1.HTTPSEdgeRouteSpec{SAML: &ingressv1alpha1.EndpointSAML{}}, false),
|
||||
Entry("Added OIDC", &ngrok.HTTPSEdgeRoute{}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OIDC: &ingressv1alpha1.EndpointOIDC{}}, false),
|
||||
Entry("Removed Oauth", &ngrok.HTTPSEdgeRoute{OAuth: &ngrok.EndpointOAuth{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{}, false),
|
||||
Entry("Removed SAML", &ngrok.HTTPSEdgeRoute{SAML: &ngrok.EndpointSAML{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{}, false),
|
||||
Entry("Removed OIDC", &ngrok.HTTPSEdgeRoute{OIDC: &ngrok.EndpointOIDC{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{}, false),
|
||||
Entry("Removed Oauth and Added Saml", &ngrok.HTTPSEdgeRoute{OAuth: &ngrok.EndpointOAuth{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{SAML: &ingressv1alpha1.EndpointSAML{}}, true),
|
||||
Entry("Removed Oauth and Added OIDC", &ngrok.HTTPSEdgeRoute{OAuth: &ngrok.EndpointOAuth{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OIDC: &ingressv1alpha1.EndpointOIDC{}}, true),
|
||||
Entry("Removed SAML and Added Oauth", &ngrok.HTTPSEdgeRoute{SAML: &ngrok.EndpointSAML{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OAuth: &ingressv1alpha1.EndpointOAuth{}}, true),
|
||||
Entry("Removed SAML and Added OIDC", &ngrok.HTTPSEdgeRoute{SAML: &ngrok.EndpointSAML{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OIDC: &ingressv1alpha1.EndpointOIDC{}}, true),
|
||||
Entry("Removed OIDC and Added Oauth", &ngrok.HTTPSEdgeRoute{OIDC: &ngrok.EndpointOIDC{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{OAuth: &ingressv1alpha1.EndpointOAuth{}}, true),
|
||||
Entry("Removed OIDC and Added SAML", &ngrok.HTTPSEdgeRoute{OIDC: &ngrok.EndpointOIDC{}}, &ingressv1alpha1.HTTPSEdgeRouteSpec{SAML: &ingressv1alpha1.EndpointSAML{}}, true),
|
||||
)
|
||||
})
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/go-logr/logr"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
"github.com/ngrok/ngrok-operator/internal/controller"
|
||||
internalerrors "github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/ngrok/ngrok-operator/pkg/managerdriver"
|
||||
@@ -22,12 +21,11 @@ import (
|
||||
// https://pkg.go.dev/sigs.k8s.io/controller-runtime#section-readme
|
||||
type IngressReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
Namespace string
|
||||
AnnotationsExtractor annotations.Extractor
|
||||
Driver *managerdriver.Driver
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
Namespace string
|
||||
Driver *managerdriver.Driver
|
||||
}
|
||||
|
||||
func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
@@ -35,9 +33,6 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
&netv1.IngressClass{},
|
||||
&corev1.Service{},
|
||||
&ingressv1alpha1.Domain{},
|
||||
&ingressv1alpha1.HTTPSEdge{},
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
&ingressv1alpha1.NgrokModuleSet{},
|
||||
&ngrokv1alpha1.NgrokTrafficPolicy{},
|
||||
}
|
||||
|
||||
@@ -58,7 +53,6 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// +kubebuilder:rbac:groups="networking.k8s.io",resources=ingresses,verbs=get;list;watch;update
|
||||
// +kubebuilder:rbac:groups="networking.k8s.io",resources=ingresses/status,verbs=get;list;watch;update
|
||||
// +kubebuilder:rbac:groups="networking.k8s.io",resources=ingressclasses,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=ngrokmodulesets,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=ngrok.k8s.ngrok.com,resources=ngroktrafficpolicies,verbs=get;list;watch
|
||||
|
||||
// This reconcile function is called by the controller-runtime manager.
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/pkg/managerdriver"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
)
|
||||
|
||||
type ModuleSetReconciler struct {
|
||||
client.Client
|
||||
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
Driver *managerdriver.Driver
|
||||
}
|
||||
|
||||
func (r *ModuleSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&ingressv1alpha1.NgrokModuleSet{}).
|
||||
WithEventFilter(predicate.Or(
|
||||
predicate.AnnotationChangedPredicate{},
|
||||
predicate.GenerationChangedPredicate{},
|
||||
)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=ngrokmodulesets,verbs=get;list;watch
|
||||
|
||||
// This reconcile function is called by the controller-runtime manager.
|
||||
// It is invoked whenever there is an event that occurs for a resource
|
||||
// being watched (in our case, NgrokModuleSets). If you tail the controller
|
||||
// logs and delete, update, edit ngrokmoduleset objects, you see the events come in.
|
||||
func (r *ModuleSetReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) {
|
||||
if err := r.Driver.SyncEdges(ctx, r.Client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if err := r.Driver.SyncEndpoints(ctx, r.Client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import (
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
common "github.com/ngrok/ngrok-operator/api/common/v1alpha1"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations/parser"
|
||||
@@ -45,7 +44,6 @@ import (
|
||||
"github.com/ngrok/ngrok-operator/internal/ngrokapi"
|
||||
"github.com/ngrok/ngrok-operator/internal/resolvers"
|
||||
"github.com/ngrok/ngrok-operator/internal/trafficpolicy"
|
||||
"github.com/ngrok/ngrok-operator/internal/util"
|
||||
"github.com/ngrok/ngrok-operator/pkg/managerdriver"
|
||||
"golang.org/x/sync/errgroup"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -106,9 +104,6 @@ func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
}
|
||||
|
||||
owns := []client.Object{
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
&ingressv1alpha1.TCPEdge{},
|
||||
&ingressv1alpha1.TLSEdge{},
|
||||
&ngrokv1alpha1.AgentEndpoint{},
|
||||
&ngrokv1alpha1.CloudEndpoint{},
|
||||
}
|
||||
@@ -125,11 +120,6 @@ func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return shouldHandleService(svc)
|
||||
},
|
||||
}).
|
||||
// Watch modulesets for changes
|
||||
Watches(
|
||||
&ingressv1alpha1.NgrokModuleSet{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.findServicesForModuleSet),
|
||||
).
|
||||
// Watch traffic policies for changes
|
||||
Watches(
|
||||
&ngrokv1alpha1.NgrokTrafficPolicy{},
|
||||
@@ -156,23 +146,8 @@ func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Index the services by the module set(s) they reference
|
||||
err := mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Service{}, ModuleSetPath, func(obj client.Object) []string {
|
||||
moduleSets, err := annotations.ExtractNgrokModuleSetsFromAnnotations(obj)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: We are returning a slice of strings here for the field indexer. Checking for equality later, means
|
||||
// that only one of the module sets needs to match for the service to be returned.
|
||||
return moduleSets
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Index the services by the traffic policy they reference
|
||||
err = mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Service{}, TrafficPolicyPath, func(obj client.Object) []string {
|
||||
err := mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Service{}, TrafficPolicyPath, func(obj client.Object) []string {
|
||||
policy, err := annotations.ExtractNgrokTrafficPolicyFromAnnotations(obj)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -192,11 +167,7 @@ func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;update
|
||||
// +kubebuilder:rbac:groups="",resources=services/status,verbs=get;list;watch;patch;update
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=ngrokmodulesets,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=ngrok.k8s.ngrok.com,resources=ngroktrafficpolicies,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tunnels,verbs=get;list;watch;create;update;delete
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tcpedges,verbs=get;list;watch;create;update;delete
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tlsedges,verbs=get;list;watch;create;update;delete
|
||||
|
||||
// This reconcile function is called by the controller-runtime manager.
|
||||
// It is invoked whenever there is an event that occurs for a resource
|
||||
@@ -213,9 +184,6 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
||||
}
|
||||
|
||||
subResourceReconcilers := serviceSubresourceReconcilers{
|
||||
newServiceTCPEdgeReconciler(),
|
||||
newServiceTLSEdgeReconciler(),
|
||||
newServiceTunnelReconciler(),
|
||||
newServiceCloudEndpointReconciler(),
|
||||
newServiceAgentEndpointReconciler(),
|
||||
}
|
||||
@@ -267,7 +235,7 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
||||
}
|
||||
}
|
||||
|
||||
// Once we clean up the tunnels and TCP edges, we can remove the finalizer if it exists. We don't
|
||||
// Once we clean up the Cloud/Agent Endpoints, we can remove the finalizer if it exists. We don't
|
||||
// care about registering a finalizer since we only care about load balancer services
|
||||
if err := controller.RemoveAndSyncFinalizer(ctx, r.Client, svc); err != nil {
|
||||
log.Error(err, "Failed to remove finalizer")
|
||||
@@ -289,29 +257,17 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
||||
|
||||
var desired []client.Object
|
||||
mappingStrategy, err := managerdriver.MappingStrategyAnnotationToIR(svc)
|
||||
// If the annotation is not valid, we still return a reasonable default mapping strategy. This error
|
||||
// is not fatal, so just log it and an event and continue
|
||||
if err != nil {
|
||||
log.Error(err, fmt.Sprintf("Failed to get %q annotation", annotations.MappingStrategyAnnotation))
|
||||
// TODO: Add an event to the service
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Best effort to try to use endpoints(if configured via annotation and eventually as a global default).
|
||||
// If the conversion of modulesets -> trafficpolicy fails or there is some other error such that we can't
|
||||
// build the desired endpoints correctly, we will fall back to using tunnels and edges
|
||||
// and just bubble up the error as an event on the service
|
||||
if mappingStrategy != ir.IRMappingStrategy_Edges {
|
||||
desired, err = r.buildEndpoints(ctx, svc, mappingStrategy)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to build desired endpoints")
|
||||
r.Recorder.Event(svc, corev1.EventTypeWarning, "FailedToBuildEndpoints", err.Error())
|
||||
desired, err = r.buildTunnelAndEdge(ctx, svc)
|
||||
}
|
||||
} else {
|
||||
desired, err = r.buildTunnelAndEdge(ctx, svc)
|
||||
log.Error(err, "Failed to get mapping strategy annotation")
|
||||
r.Recorder.Event(svc, corev1.EventTypeWarning, "FailedToGetMappingStrategy", err.Error())
|
||||
}
|
||||
|
||||
desired, err = r.buildEndpoints(ctx, svc, mappingStrategy)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to build desired resources")
|
||||
log.Error(err, "Failed to build desired endpoints")
|
||||
r.Recorder.Event(svc, corev1.EventTypeWarning, "FailedToBuildEndpoints", err.Error())
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
@@ -336,39 +292,6 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ServiceReconciler) findServicesForModuleSet(ctx context.Context, moduleSet client.Object) []reconcile.Request {
|
||||
log := r.Log
|
||||
|
||||
moduleSetNamespace := moduleSet.GetNamespace()
|
||||
moduleSetName := moduleSet.GetName()
|
||||
|
||||
log.V(3).Info("Finding services for module set", "namespace", moduleSetNamespace, "name", moduleSetName)
|
||||
services := &corev1.ServiceList{}
|
||||
listOpts := &client.ListOptions{
|
||||
Namespace: moduleSetNamespace,
|
||||
FieldSelector: fields.OneTermEqualSelector(ModuleSetPath, moduleSetName),
|
||||
}
|
||||
err := r.Client.List(ctx, services, listOpts)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list services for module set")
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
requests := make([]reconcile.Request, len(services.Items))
|
||||
for i, svc := range services.Items {
|
||||
svcNamespace := svc.GetNamespace()
|
||||
svcName := svc.GetName()
|
||||
requests[i] = reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: svcNamespace,
|
||||
Name: svcName,
|
||||
},
|
||||
}
|
||||
log.V(3).Info("Triggering reconciliation for service", "namespace", svcNamespace, "name", svcName)
|
||||
}
|
||||
return requests
|
||||
}
|
||||
|
||||
func (r *ServiceReconciler) findServicesForTrafficPolicy(ctx context.Context, policy client.Object) []reconcile.Request {
|
||||
log := r.Log
|
||||
|
||||
@@ -447,22 +370,6 @@ func (r *ServiceReconciler) buildEndpoints(ctx context.Context, svc *corev1.Serv
|
||||
// The final traffic policy that will be applied to the listener endpoint
|
||||
tp := trafficpolicy.NewTrafficPolicy()
|
||||
|
||||
// Get the modules from the service annotations
|
||||
moduleSet, err := getNgrokModuleSetForService(ctx, r.Client, svc)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to get module sets")
|
||||
return objects, err
|
||||
}
|
||||
|
||||
// If there are modulesets defined on the service, create a traffic policy from them
|
||||
// and merge it with the existing traffic policy
|
||||
moduleSetsTrafficPolicy, err := util.NewTrafficPolicyFromModuleset(ctx, moduleSet, r.SecretResolver, r.IPPolicyResolver)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to create traffic policy from module set", "moduleSet", moduleSet)
|
||||
return objects, err
|
||||
}
|
||||
tp.Merge(moduleSetsTrafficPolicy)
|
||||
|
||||
// If an explicit traffic policy is defined on the service, merge it with the existing traffic policy
|
||||
// before adding the forward-internal action.
|
||||
// TODO: We still need to handle legacy traffic policy conversion
|
||||
@@ -679,115 +586,6 @@ func (r *ServiceReconciler) getListenerURL(svc *corev1.Service) (string, error)
|
||||
return "tcp://", nil
|
||||
}
|
||||
|
||||
func (r *ServiceReconciler) buildTunnelAndEdge(ctx context.Context, svc *corev1.Service) ([]client.Object, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
port := svc.Spec.Ports[0].Port
|
||||
objects := make([]client.Object, 0)
|
||||
|
||||
backendLabels := map[string]string{
|
||||
"k8s.ngrok.com/namespace": svc.Namespace,
|
||||
"k8s.ngrok.com/service": svc.Name,
|
||||
"k8s.ngrok.com/service-uid": string(svc.UID),
|
||||
"k8s.ngrok.com/port": strconv.Itoa(int(port)),
|
||||
}
|
||||
|
||||
tunnel := &ingressv1alpha1.Tunnel{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: svc.Name + "-",
|
||||
Namespace: svc.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(svc, corev1.SchemeGroupVersion.WithKind("Service")),
|
||||
},
|
||||
},
|
||||
Spec: ingressv1alpha1.TunnelSpec{
|
||||
ForwardsTo: fmt.Sprintf("%s.%s.%s:%d", svc.Name, svc.Namespace, r.ClusterDomain, port),
|
||||
Labels: backendLabels,
|
||||
},
|
||||
}
|
||||
objects = append(objects, tunnel)
|
||||
|
||||
// Get the modules from the service annotations
|
||||
moduleSets, err := getNgrokModuleSetForService(ctx, r.Client, svc)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to get module sets")
|
||||
return objects, err
|
||||
}
|
||||
|
||||
policy, err := getNgrokTrafficPolicyForService(ctx, r.Client, svc)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to get traffic policy")
|
||||
return objects, err
|
||||
}
|
||||
|
||||
listenerURL, err := r.getListenerURL(svc)
|
||||
if err != nil {
|
||||
r.Recorder.Event(svc, corev1.EventTypeWarning, "FailedToGetListenerURL", err.Error())
|
||||
return objects, err
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(listenerURL)
|
||||
if err != nil {
|
||||
return objects, err
|
||||
}
|
||||
|
||||
if parsedURL.Scheme == "tcp" {
|
||||
edge := &ingressv1alpha1.TCPEdge{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: svc.Name + "-",
|
||||
Namespace: svc.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(svc, corev1.SchemeGroupVersion.WithKind("Service")),
|
||||
},
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: ingressv1alpha1.TCPEdgeSpec{
|
||||
Backend: ingressv1alpha1.TunnelGroupBackend{
|
||||
Labels: backendLabels,
|
||||
},
|
||||
},
|
||||
}
|
||||
if moduleSets != nil {
|
||||
edge.Spec.IPRestriction = moduleSets.Modules.IPRestriction
|
||||
}
|
||||
if policy != nil {
|
||||
edge.Spec.Policy = policy.Spec.Policy
|
||||
}
|
||||
|
||||
objects = append(objects, edge)
|
||||
} else {
|
||||
edge := &ingressv1alpha1.TLSEdge{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: svc.Name + "-",
|
||||
Namespace: svc.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(svc, corev1.SchemeGroupVersion.WithKind("Service")),
|
||||
},
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: ingressv1alpha1.TLSEdgeSpec{
|
||||
Backend: ingressv1alpha1.TunnelGroupBackend{
|
||||
Labels: backendLabels,
|
||||
},
|
||||
Hostports: []string{
|
||||
fmt.Sprintf("%s:443", parsedURL.Hostname()),
|
||||
},
|
||||
},
|
||||
}
|
||||
if moduleSets != nil {
|
||||
edge.Spec.IPRestriction = moduleSets.Modules.IPRestriction
|
||||
edge.Spec.MutualTLS = moduleSets.Modules.MutualTLS
|
||||
edge.Spec.TLSTermination = moduleSets.Modules.TLSTermination
|
||||
}
|
||||
if policy != nil {
|
||||
edge.Spec.Policy = policy.Spec.Policy
|
||||
}
|
||||
objects = append(objects, edge)
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func shouldHandleService(svc *corev1.Service) bool {
|
||||
return svc.Spec.Type == corev1.ServiceTypeLoadBalancer &&
|
||||
ptr.Deref(svc.Spec.LoadBalancerClass, "") == NgrokLoadBalancerClass
|
||||
@@ -896,7 +694,7 @@ func (r *baseSubresourceReconciler[T, PT]) Reconcile(ctx context.Context, c clie
|
||||
}
|
||||
|
||||
// We only support one desired resource of a particular type for now
|
||||
// If there are cases where we need to create multiple edges or tunnels, we will need to change this handling
|
||||
// If there are cases where we need to create multiple cloud or agent endpoints, we will need to change this handling
|
||||
if len(desired) > 1 {
|
||||
return errors.New("multiple desired resources not supported")
|
||||
}
|
||||
@@ -947,123 +745,6 @@ func (r *baseSubresourceReconciler[T, PT]) UpdateServiceStatus(ctx context.Conte
|
||||
return r.updateStatus(ctx, c, svc, v)
|
||||
}
|
||||
|
||||
func newServiceTCPEdgeReconciler() serviceSubresourceReconciler {
|
||||
return &baseSubresourceReconciler[ingressv1alpha1.TCPEdge, *ingressv1alpha1.TCPEdge]{
|
||||
listOwned: func(ctx context.Context, c client.Client, opts ...client.ListOption) ([]ingressv1alpha1.TCPEdge, error) {
|
||||
edges := &ingressv1alpha1.TCPEdgeList{}
|
||||
if err := c.List(ctx, edges, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return edges.Items, nil
|
||||
},
|
||||
matches: func(desired, existing ingressv1alpha1.TCPEdge) bool {
|
||||
return reflect.DeepEqual(existing.Spec, desired.Spec)
|
||||
},
|
||||
mergeExisting: func(desired ingressv1alpha1.TCPEdge, existing *ingressv1alpha1.TCPEdge) {
|
||||
existing.Spec = desired.Spec
|
||||
},
|
||||
updateStatus: func(ctx context.Context, c client.Client, svc *corev1.Service, edge *ingressv1alpha1.TCPEdge) error {
|
||||
clearIngressStatus := func(svc *corev1.Service) error {
|
||||
svc.Status.LoadBalancer.Ingress = nil
|
||||
return c.Status().Update(ctx, svc)
|
||||
}
|
||||
|
||||
if len(edge.Status.Hostports) == 0 {
|
||||
return clearIngressStatus(svc)
|
||||
}
|
||||
host, port, err := parseHostAndPort(edge.Status.Hostports[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svc.Status.LoadBalancer.Ingress = []corev1.LoadBalancerIngress{
|
||||
{
|
||||
Hostname: host,
|
||||
Ports: []corev1.PortStatus{
|
||||
{
|
||||
Port: port,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return c.Status().Update(ctx, svc)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newServiceTLSEdgeReconciler() serviceSubresourceReconciler {
|
||||
return &baseSubresourceReconciler[ingressv1alpha1.TLSEdge, *ingressv1alpha1.TLSEdge]{
|
||||
listOwned: func(ctx context.Context, c client.Client, opts ...client.ListOption) ([]ingressv1alpha1.TLSEdge, error) {
|
||||
edges := &ingressv1alpha1.TLSEdgeList{}
|
||||
if err := c.List(ctx, edges, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return edges.Items, nil
|
||||
},
|
||||
matches: func(desired, existing ingressv1alpha1.TLSEdge) bool {
|
||||
return reflect.DeepEqual(existing.Spec, desired.Spec)
|
||||
},
|
||||
mergeExisting: func(desired ingressv1alpha1.TLSEdge, existing *ingressv1alpha1.TLSEdge) {
|
||||
existing.Spec = desired.Spec
|
||||
},
|
||||
updateStatus: func(ctx context.Context, c client.Client, svc *corev1.Service, edge *ingressv1alpha1.TLSEdge) error {
|
||||
clearIngressStatus := func(svc *corev1.Service) error {
|
||||
svc.Status.LoadBalancer.Ingress = nil
|
||||
return c.Status().Update(ctx, svc)
|
||||
}
|
||||
|
||||
domain, err := parser.GetStringAnnotation("domain", svc)
|
||||
if err != nil {
|
||||
if errors.IsMissingAnnotations(err) {
|
||||
return clearIngressStatus(svc)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
hostname, ok := edge.Status.CNAMETargets[domain]
|
||||
if !ok {
|
||||
hostname = domain // ngrok managed domain case
|
||||
}
|
||||
|
||||
svc.Status.LoadBalancer.Ingress = []corev1.LoadBalancerIngress{
|
||||
{
|
||||
Hostname: hostname,
|
||||
Ports: []corev1.PortStatus{
|
||||
{
|
||||
Port: 443,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return c.Status().Update(ctx, svc)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newServiceTunnelReconciler() serviceSubresourceReconciler {
|
||||
return &baseSubresourceReconciler[ingressv1alpha1.Tunnel, *ingressv1alpha1.Tunnel]{
|
||||
listOwned: func(ctx context.Context, c client.Client, opts ...client.ListOption) ([]ingressv1alpha1.Tunnel, error) {
|
||||
tunnels := &ingressv1alpha1.TunnelList{}
|
||||
if err := c.List(ctx, tunnels, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tunnels.Items, nil
|
||||
},
|
||||
matches: func(desired, existing ingressv1alpha1.Tunnel) bool {
|
||||
return reflect.DeepEqual(existing.Spec, desired.Spec)
|
||||
},
|
||||
mergeExisting: func(desired ingressv1alpha1.Tunnel, existing *ingressv1alpha1.Tunnel) {
|
||||
existing.Spec = desired.Spec
|
||||
},
|
||||
updateStatus: func(_ context.Context, _ client.Client, _ *corev1.Service, _ *ingressv1alpha1.Tunnel) error {
|
||||
// Tunnels don't interact with the service status
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newServiceCloudEndpointReconciler() serviceSubresourceReconciler {
|
||||
return &baseSubresourceReconciler[ngrokv1alpha1.CloudEndpoint, *ngrokv1alpha1.CloudEndpoint]{
|
||||
listOwned: func(ctx context.Context, c client.Client, opts ...client.ListOption) ([]ngrokv1alpha1.CloudEndpoint, error) {
|
||||
@@ -1160,35 +841,6 @@ func newServiceAgentEndpointReconciler() serviceSubresourceReconciler {
|
||||
}
|
||||
}
|
||||
|
||||
// Given a service, it will resolve any ngrok modulesets defined on the service to the
|
||||
// CRDs and then will merge them in to a single moduleset
|
||||
func getNgrokModuleSetForService(ctx context.Context, c client.Client, svc *corev1.Service) (*ingressv1alpha1.NgrokModuleSet, error) {
|
||||
computedModSet := &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: svc.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
modules, err := annotations.ExtractNgrokModuleSetsFromAnnotations(svc)
|
||||
if err != nil {
|
||||
if errors.IsMissingAnnotations(err) {
|
||||
return computedModSet, nil
|
||||
}
|
||||
return computedModSet, err
|
||||
}
|
||||
|
||||
for _, module := range modules {
|
||||
// TODO: watch these and cache them so we don't have to make tons of requests
|
||||
resolvedMod := &ingressv1alpha1.NgrokModuleSet{}
|
||||
if err := c.Get(ctx, client.ObjectKey{Namespace: svc.Namespace, Name: module}, resolvedMod); err != nil {
|
||||
return computedModSet, err
|
||||
}
|
||||
computedModSet.Merge(resolvedMod)
|
||||
}
|
||||
|
||||
return computedModSet, nil
|
||||
}
|
||||
|
||||
func getNgrokTrafficPolicyForService(ctx context.Context, c client.Client, svc *corev1.Service) (*ngrokv1alpha1.NgrokTrafficPolicy, error) {
|
||||
policyName, err := annotations.ExtractNgrokTrafficPolicyFromAnnotations(svc)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/controller"
|
||||
"github.com/ngrok/ngrok-operator/internal/events"
|
||||
"github.com/ngrok/ngrok-operator/internal/ngrokapi"
|
||||
"github.com/ngrok/ngrok-operator/internal/resolvers"
|
||||
"github.com/ngrok/ngrok-operator/internal/util"
|
||||
)
|
||||
|
||||
// TCPEdgeReconciler reconciles a TCPEdge object
|
||||
type TCPEdgeReconciler struct {
|
||||
client.Client
|
||||
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
|
||||
IPPolicyResolver resolvers.IPPolicyResolver
|
||||
|
||||
NgrokClientset ngrokapi.Clientset
|
||||
|
||||
controller *controller.BaseController[*ingressv1alpha1.TCPEdge]
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *TCPEdgeReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
if r.IPPolicyResolver == nil {
|
||||
r.IPPolicyResolver = resolvers.NewDefaultIPPolicyResolver(mgr.GetClient())
|
||||
}
|
||||
|
||||
r.controller = &controller.BaseController[*ingressv1alpha1.TCPEdge]{
|
||||
Kube: r.Client,
|
||||
Log: r.Log,
|
||||
Recorder: r.Recorder,
|
||||
|
||||
StatusID: func(cr *ingressv1alpha1.TCPEdge) string { return cr.Status.ID },
|
||||
Create: r.create,
|
||||
Update: r.update,
|
||||
Delete: r.delete,
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&ingressv1alpha1.TCPEdge{}).
|
||||
Watches(
|
||||
&ingressv1alpha1.IPPolicy{},
|
||||
r.controller.NewEnqueueRequestForMapFunc(r.listTCPEdgesForIPPolicy),
|
||||
).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tcpedges,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tcpedges/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tcpedges/finalizers,verbs=update
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
//
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
func (r *TCPEdgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
return r.controller.Reconcile(ctx, req, new(ingressv1alpha1.TCPEdge))
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) create(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if err := r.reconcileTunnelGroupBackend(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reserveAddrIfEmpty(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to find the edge by the backend labels
|
||||
resp, err := r.findEdgeByBackendLabels(ctx, edge.Spec.Backend.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
// No edge has been created for this edge, create one
|
||||
log.Info("Creating new TCPEdge", "namespace", edge.Namespace, "name", edge.Name)
|
||||
resp, err = r.NgrokClientset.TCPEdges().Create(ctx, &ngrok.TCPEdgeCreate{
|
||||
Description: edge.Spec.Description,
|
||||
Metadata: edge.Spec.Metadata,
|
||||
Backend: &ngrok.EndpointBackendMutate{
|
||||
BackendID: edge.Status.Backend.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Created new TCPEdge", "edge.ID", resp.ID, "name", edge.Name, "namespace", edge.Namespace)
|
||||
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) update(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if err := r.reconcileTunnelGroupBackend(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reserveAddrIfEmpty(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := r.NgrokClientset.TCPEdges().Get(ctx, edge.Status.ID)
|
||||
if err != nil {
|
||||
// If we can't find the edge in the ngrok API, it's been deleted, so clear the ID
|
||||
// and requeue the edge. When it gets reconciled again, it will be recreated.
|
||||
if ngrok.IsNotFound(err) {
|
||||
log.Info("TCPEdge not found, clearing ID and requeuing", "edge.ID", edge.Status.ID)
|
||||
edge.Status.ID = ""
|
||||
//nolint:errcheck
|
||||
r.Status().Update(ctx, edge)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// If the backend or hostports do not match, update the edge with the desired backend and hostports
|
||||
if resp.Backend.Backend.ID != edge.Status.Backend.ID ||
|
||||
!slices.Equal(resp.Hostports, edge.Status.Hostports) {
|
||||
resp, err = r.NgrokClientset.TCPEdges().Update(ctx, &ngrok.TCPEdgeUpdate{
|
||||
ID: resp.ID,
|
||||
Description: ptr.To(edge.Spec.Description),
|
||||
Metadata: ptr.To(edge.Spec.Metadata),
|
||||
Hostports: edge.Status.Hostports,
|
||||
Backend: &ngrok.EndpointBackendMutate{
|
||||
BackendID: edge.Status.Backend.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) delete(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error {
|
||||
err := r.NgrokClientset.TCPEdges().Delete(ctx, edge.Status.ID)
|
||||
if err == nil || ngrok.IsNotFound(err) {
|
||||
edge.Status.ID = ""
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) reconcileTunnelGroupBackend(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
specBackend := edge.Spec.Backend
|
||||
// First make sure the tunnel group backend matches
|
||||
if edge.Status.Backend.ID != "" {
|
||||
log.WithValues("TunnelGroupBackend.ID", edge.Status.Backend.ID)
|
||||
|
||||
// A backend has already been created for this edge, make sure the labels match
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Get(ctx, edge.Status.Backend.ID)
|
||||
if err != nil {
|
||||
if ngrok.IsNotFound(err) {
|
||||
log.Info("TunnelGroupBackend not found, clearing ID and requeuing")
|
||||
edge.Status.Backend.ID = ""
|
||||
//nolint:errcheck
|
||||
r.Status().Update(ctx, edge)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// If the labels don't match, update the backend with the desired labels
|
||||
if !maps.Equal(backend.Labels, specBackend.Labels) {
|
||||
_, err = r.NgrokClientset.TunnelGroupBackends().Update(ctx, &ngrok.TunnelGroupBackendUpdate{
|
||||
ID: backend.ID,
|
||||
Metadata: ptr.To(specBackend.Metadata),
|
||||
Description: ptr.To(specBackend.Description),
|
||||
Labels: specBackend.Labels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.V(3).Info("Existing TunnelGroupBackend has matching labels", "labels", specBackend.Labels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// No backend has been created for this edge, create one
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Create(ctx, &ngrok.TunnelGroupBackendCreate{
|
||||
Metadata: edge.Spec.Backend.Metadata,
|
||||
Description: edge.Spec.Backend.Description,
|
||||
Labels: edge.Spec.Backend.Labels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edge.Status.Backend.ID = backend.ID
|
||||
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) findEdgeByBackendLabels(ctx context.Context, backendLabels map[string]string) (*ngrok.TCPEdge, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
log.Info("Searching for existing TCPEdge with backend labels", "labels", backendLabels)
|
||||
|
||||
iter := r.NgrokClientset.TCPEdges().List(&ngrok.Paging{})
|
||||
for iter.Next(ctx) {
|
||||
edge := iter.Item()
|
||||
if edge.Backend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Get(ctx, edge.Backend.Backend.ID)
|
||||
if err != nil {
|
||||
// The backend ID on the edge is invalid and no longer exists in the ngrok API,
|
||||
// so we'll skip this edge check the next one.
|
||||
if ngrok.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We've go an error besides not found. Return the error and
|
||||
// hopefully the next reconcile will fix it.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if backend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if maps.Equal(backend.Labels, backendLabels) {
|
||||
log.Info("Found existing TCPEdge with matching backend labels", "labels", backendLabels, "edge.ID", edge.ID)
|
||||
return edge, nil
|
||||
}
|
||||
}
|
||||
return nil, iter.Err()
|
||||
}
|
||||
|
||||
// Update the edge status and modules, called from both create and update.
|
||||
func (r *TCPEdgeReconciler) updateEdge(ctx context.Context, edge *ingressv1alpha1.TCPEdge, remoteEdge *ngrok.TCPEdge) error {
|
||||
if err := r.updateEdgeStatus(ctx, edge, remoteEdge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.updateIPRestrictionModule(ctx, edge, remoteEdge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.updatePolicyModule(ctx, edge, remoteEdge)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) updateEdgeStatus(ctx context.Context, edge *ingressv1alpha1.TCPEdge, remoteEdge *ngrok.TCPEdge) error {
|
||||
edge.Status.ID = remoteEdge.ID
|
||||
edge.Status.URI = remoteEdge.URI
|
||||
edge.Status.Hostports = remoteEdge.Hostports
|
||||
edge.Status.Backend.ID = remoteEdge.Backend.Backend.ID
|
||||
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) reserveAddrIfEmpty(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if len(edge.Status.Hostports) == 0 {
|
||||
metadata := ReservedAddrMetadata{
|
||||
Namespace: edge.Namespace,
|
||||
Name: edge.Name,
|
||||
OwnerRef: metav1.GetControllerOf(edge),
|
||||
}
|
||||
|
||||
log.V(3).Info("No hostports assigned to edge, assigning one or using existing one")
|
||||
addr, err := r.findAddrWithMatchingMetadata(ctx, metadata)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to find addr with matching metadata")
|
||||
return err
|
||||
}
|
||||
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we found an addr with matching metadata, use it
|
||||
if addr != nil {
|
||||
description := r.descriptionForEdge(edge)
|
||||
metadata := string(metadataBytes)
|
||||
// Update the addr description & metadata. We know the metadata matches, but the name for the edge may have changed
|
||||
_, _ = r.NgrokClientset.TCPAddresses().Update(ctx, &ngrok.ReservedAddrUpdate{
|
||||
ID: addr.ID,
|
||||
Description: &description,
|
||||
Metadata: &metadata,
|
||||
})
|
||||
|
||||
log.V(3).Info("Found existing addr with matching metadata", "reservedAddr.ID", addr.ID, "reservedAddr.Addr", addr.Addr)
|
||||
edge.Status.Hostports = []string{addr.Addr}
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
// No hostports have been assigned to this edge, assign one
|
||||
log.V(3).Info("Creating new reserved addr for edge")
|
||||
addr, err = r.NgrokClientset.TCPAddresses().Create(ctx, &ngrok.ReservedAddrCreate{
|
||||
Description: r.descriptionForEdge(edge),
|
||||
Metadata: string(metadataBytes),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edge.Status.Hostports = []string{addr.Addr}
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
log.V(3).Info("Hostports already assigned to edge", "hostports", edge.Status.Hostports)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) findAddrWithMatchingMetadata(ctx context.Context, metadata ReservedAddrMetadata) (*ngrok.ReservedAddr, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
iter := r.NgrokClientset.TCPAddresses().List(&ngrok.Paging{})
|
||||
for iter.Next(ctx) {
|
||||
addr := iter.Item()
|
||||
if addr.Metadata == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
addrMetadata := ReservedAddrMetadata{}
|
||||
// if we can't unmarshal the metadata, we can't match it, but just continue
|
||||
if err := json.Unmarshal([]byte(addr.Metadata), &addrMetadata); err != nil {
|
||||
log.V(3).Info("Failed to unmarshal addr metadata", "addr.ID", addr.ID, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if metadata.Matches(addrMetadata) {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
return nil, iter.Err()
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) descriptionForEdge(edge *ingressv1alpha1.TCPEdge) string {
|
||||
return fmt.Sprintf("Reserved for %s/%s", edge.Namespace, edge.Name)
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) updateIPRestrictionModule(ctx context.Context, edge *ingressv1alpha1.TCPEdge, _ *ngrok.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if edge.Spec.IPRestriction == nil || len(edge.Spec.IPRestriction.IPPolicies) == 0 {
|
||||
return r.NgrokClientset.EdgeModules().TCP().IPRestriction().Delete(ctx, edge.Status.ID)
|
||||
}
|
||||
policyIds, err := r.IPPolicyResolver.ResolveIPPolicyNamesorIds(ctx, edge.Namespace, edge.Spec.IPRestriction.IPPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Resolved IP Policy NamesOrIDs to IDs", "policyIds", policyIds)
|
||||
|
||||
_, err = r.NgrokClientset.EdgeModules().TCP().IPRestriction().Replace(ctx, &ngrok.EdgeIPRestrictionReplace{
|
||||
ID: edge.Status.ID,
|
||||
Module: ngrok.EndpointIPPolicyMutate{
|
||||
IPPolicyIDs: policyIds,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) listTCPEdgesForIPPolicy(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
log.Info("Listing TCPEdges for ip policy to determine if they need to be reconciled")
|
||||
policy, ok := obj.(*ingressv1alpha1.IPPolicy)
|
||||
if !ok {
|
||||
log.Error(nil, "failed to convert object to IPPolicy", "object", obj)
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
edges := &ingressv1alpha1.TCPEdgeList{}
|
||||
if err := r.Client.List(ctx, edges); err != nil {
|
||||
log.Error(err, "failed to list TCPEdges for ippolicy", "name", policy.Name, "namespace", policy.Namespace)
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
recs := []reconcile.Request{}
|
||||
|
||||
for _, edge := range edges.Items {
|
||||
if edge.Spec.IPRestriction == nil {
|
||||
continue
|
||||
}
|
||||
for _, edgePolicyID := range edge.Spec.IPRestriction.IPPolicies {
|
||||
if edgePolicyID == policy.Name || edgePolicyID == policy.Status.ID {
|
||||
recs = append(recs, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: edge.GetName(),
|
||||
Namespace: edge.GetNamespace(),
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("IPPolicy change triggered TCPEdge reconciliation", "count", len(recs), "policy", policy.Name, "namespace", policy.Namespace)
|
||||
return recs
|
||||
}
|
||||
|
||||
func (r *TCPEdgeReconciler) updatePolicyModule(ctx context.Context, edge *ingressv1alpha1.TCPEdge, remoteEdge *ngrok.TCPEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
client := r.NgrokClientset.EdgeModules().TCP().TrafficPolicy()
|
||||
|
||||
trafficPolicy := edge.Spec.Policy
|
||||
|
||||
// Early return if nothing to be done
|
||||
if trafficPolicy == nil {
|
||||
if remoteEdge.TrafficPolicy == nil {
|
||||
log.Info("Module matches desired state, skipping update", "module", "Traffic Policy", "comparison", routeModuleComparisonBothNil)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Deleting Policy module")
|
||||
return client.Delete(ctx, edge.Status.ID)
|
||||
}
|
||||
|
||||
parsedTrafficPolicy, err := util.NewTrafficPolicyFromJson(trafficPolicy)
|
||||
if err != nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.TrafficPolicyParseFailed, "Failed to parse Traffic Policy, possibly malformed.")
|
||||
return err
|
||||
}
|
||||
|
||||
if parsedTrafficPolicy.IsLegacyPolicy() {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.PolicyDeprecation, "Traffic Policy is using legacy directions: ['inbound', 'outbound']. Update to new phases: ['on_tcp_connect', 'on_http_request', 'on_http_response']")
|
||||
}
|
||||
|
||||
if parsedTrafficPolicy.Enabled() != nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.PolicyDeprecation, "Traffic Policy has 'enabled' set. This is a legacy option that will stop being supported soon.")
|
||||
}
|
||||
|
||||
apiTrafficPolicy, err := parsedTrafficPolicy.ToAPIJson()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Recorder.Eventf(edge, v1.EventTypeNormal, "Update", "Updating Traffic Policy on edge.")
|
||||
_, err = client.Replace(ctx, &ngrok.EdgeTrafficPolicyReplace{
|
||||
ID: remoteEdge.ID,
|
||||
Module: ngrok.EndpointTrafficPolicy{
|
||||
Enabled: parsedTrafficPolicy.Enabled(),
|
||||
Value: string(apiTrafficPolicy),
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeNormal, "Update", "Traffic Policy successfully updated.")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type ReservedAddrMetadata struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
OwnerRef *metav1.OwnerReference `json:"ownerRef,omitempty"`
|
||||
}
|
||||
|
||||
// Matches returns true if the metadata is a match for the other metadata
|
||||
func (m ReservedAddrMetadata) Matches(other ReservedAddrMetadata) bool {
|
||||
// If the namespaces don't match, they're automatically not a match
|
||||
if m.Namespace != other.Namespace {
|
||||
return false
|
||||
}
|
||||
|
||||
// If either have the owner reference set, we'll use those to compare
|
||||
if m.OwnerRef != nil || other.OwnerRef != nil {
|
||||
return reflect.DeepEqual(m.OwnerRef, other.OwnerRef)
|
||||
}
|
||||
|
||||
return m.Name == other.Name
|
||||
}
|
||||
@@ -1,656 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/controller"
|
||||
ierr "github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/ngrok/ngrok-operator/internal/events"
|
||||
"github.com/ngrok/ngrok-operator/internal/ngrokapi"
|
||||
"github.com/ngrok/ngrok-operator/internal/resolvers"
|
||||
"github.com/ngrok/ngrok-operator/internal/util"
|
||||
)
|
||||
|
||||
// TLSEdgeReconciler reconciles a TLSEdge object
|
||||
type TLSEdgeReconciler struct {
|
||||
client.Client
|
||||
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
|
||||
IPPolicyResolver resolvers.IPPolicyResolver
|
||||
|
||||
NgrokClientset ngrokapi.Clientset
|
||||
|
||||
controller *controller.BaseController[*ingressv1alpha1.TLSEdge]
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *TLSEdgeReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
if r.IPPolicyResolver == nil {
|
||||
r.IPPolicyResolver = resolvers.NewDefaultIPPolicyResolver(mgr.GetClient())
|
||||
}
|
||||
|
||||
r.controller = &controller.BaseController[*ingressv1alpha1.TLSEdge]{
|
||||
Kube: r.Client,
|
||||
Log: r.Log,
|
||||
Recorder: r.Recorder,
|
||||
|
||||
StatusID: func(cr *ingressv1alpha1.TLSEdge) string { return cr.Status.ID },
|
||||
Create: r.create,
|
||||
Update: r.update,
|
||||
Delete: r.delete,
|
||||
ErrResult: func(_ controller.BaseControllerOp, _ *ingressv1alpha1.TLSEdge, err error) (ctrl.Result, error) {
|
||||
if errors.As(err, &ierr.ErrInvalidConfiguration{}) {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
if ngrok.IsErrorCode(err,
|
||||
7117, // https://ngrok.com/docs/errors/err_ngrok_7117, domain not found
|
||||
7132, // https://ngrok.com/docs/errors/err_ngrok_7132, hostport already in use
|
||||
) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return controller.CtrlResultForErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
controller := ctrl.NewControllerManagedBy(mgr).
|
||||
For(&ingressv1alpha1.TLSEdge{}).
|
||||
Watches(
|
||||
&ingressv1alpha1.IPPolicy{},
|
||||
r.controller.NewEnqueueRequestForMapFunc(r.listTLSEdgesForIPPolicy),
|
||||
).
|
||||
Watches(
|
||||
&ingressv1alpha1.Domain{},
|
||||
r.controller.NewEnqueueRequestForMapFunc(r.listTLSEdgesForDomain),
|
||||
)
|
||||
|
||||
return controller.Complete(r)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tlsedges,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tlsedges/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=tlsedges/finalizers,verbs=update
|
||||
// +kubebuilder:rbac:groups=ingress.k8s.ngrok.com,resources=domains,verbs=get;list;watch;create;update
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
//
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
func (r *TLSEdgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
return r.controller.Reconcile(ctx, req, new(ingressv1alpha1.TLSEdge))
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) create(ctx context.Context, edge *ingressv1alpha1.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if err := r.reconcileDomains(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reconcileTunnelGroupBackend(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to find the edge by the backend labels
|
||||
resp, err := r.findEdgeByBackendLabels(ctx, edge.Spec.Backend.Labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
// No edge has been created for this edge, create one
|
||||
log.Info("Creating new TLSEdge", "namespace", edge.Namespace, "name", edge.Name)
|
||||
resp, err = r.NgrokClientset.TLSEdges().Create(ctx, &ngrok.TLSEdgeCreate{
|
||||
Hostports: edge.Spec.Hostports,
|
||||
Description: edge.Spec.Description,
|
||||
Metadata: edge.Spec.Metadata,
|
||||
Backend: &ngrok.EndpointBackendMutate{
|
||||
BackendID: edge.Status.Backend.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Created new TLSEdge", "edge.ID", resp.ID, "name", edge.Name, "namespace", edge.Namespace)
|
||||
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) update(ctx context.Context, edge *ingressv1alpha1.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx).WithValues("edge.ID", edge.Status.ID)
|
||||
|
||||
if err := r.reconcileDomains(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reconcileTunnelGroupBackend(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Fetching TLSEdge from ngrok API")
|
||||
resp, err := r.NgrokClientset.TLSEdges().Get(ctx, edge.Status.ID)
|
||||
if err != nil {
|
||||
// If we can't find the edge in the ngrok API, it's been deleted, so clear the ID
|
||||
// and requeue the edge. When it gets reconciled again, it will be recreated.
|
||||
if ngrok.IsNotFound(err) {
|
||||
log.Info("TLSEdge not found, clearing ID and requeuing")
|
||||
edge.Status.ID = ""
|
||||
//nolint:errcheck
|
||||
r.Status().Update(ctx, edge)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// If the backend or hostports do not match, update the edge with the desired backend and hostports
|
||||
if resp.Backend.Backend.ID != edge.Status.Backend.ID ||
|
||||
!slices.Equal(resp.Hostports, edge.Spec.Hostports) {
|
||||
log.Info("Backend or hostports do not match, updating edge",
|
||||
"expected.backend.ID", edge.Status.Backend.ID,
|
||||
"actual.backend.ID", resp.Backend.Backend.ID,
|
||||
"expected.hostports", edge.Spec.Hostports,
|
||||
"actual.hostports", resp.Hostports,
|
||||
)
|
||||
|
||||
resp, err = r.NgrokClientset.TLSEdges().Update(ctx, &ngrok.TLSEdgeUpdate{
|
||||
ID: resp.ID,
|
||||
Description: ptr.To(edge.Spec.Description),
|
||||
Metadata: ptr.To(edge.Spec.Metadata),
|
||||
Hostports: edge.Spec.Hostports,
|
||||
Backend: &ngrok.EndpointBackendMutate{
|
||||
BackendID: edge.Status.Backend.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return r.updateEdge(ctx, edge, resp)
|
||||
}
|
||||
|
||||
// Update the edge status and modules, called from both create and update.
|
||||
func (r *TLSEdgeReconciler) updateEdge(ctx context.Context, edge *ingressv1alpha1.TLSEdge, resp *ngrok.TLSEdge) error {
|
||||
if err := r.updateEdgeStatus(ctx, edge, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.setTLSTermination(ctx, resp, edge.Spec.TLSTermination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.setMutualTLS(ctx, resp, edge.Spec.MutualTLS); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.updateIPRestrictionModule(ctx, edge, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.updatePolicyModule(ctx, edge, resp)
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) delete(ctx context.Context, edge *ingressv1alpha1.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
edgeID := edge.Status.ID
|
||||
log.Info("Deleting TLSEdge", "edge.ID", edgeID)
|
||||
err := r.NgrokClientset.TLSEdges().Delete(ctx, edgeID)
|
||||
if err == nil || ngrok.IsNotFound(err) {
|
||||
log.Info("Deleted TLSEdge", "edge.ID", edgeID)
|
||||
edge.Status.ID = ""
|
||||
if err := r.Client.Status().Update(ctx, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) reconcileTunnelGroupBackend(ctx context.Context, edge *ingressv1alpha1.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
specBackend := edge.Spec.Backend
|
||||
// First make sure the tunnel group backend matches
|
||||
if edge.Status.Backend.ID != "" {
|
||||
// A backend has already been created for this edge, make sure the labels match
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Get(ctx, edge.Status.Backend.ID)
|
||||
if err != nil {
|
||||
if ngrok.IsNotFound(err) {
|
||||
log.Info("TunnelGroupBackend not found, clearing ID and requeuing", "TunnelGroupBackend.ID", edge.Status.Backend.ID)
|
||||
edge.Status.Backend.ID = ""
|
||||
//nolint:errcheck
|
||||
r.Status().Update(ctx, edge)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// If the labels don't match, update the backend with the desired labels
|
||||
if !maps.Equal(backend.Labels, specBackend.Labels) {
|
||||
_, err = r.NgrokClientset.TunnelGroupBackends().Update(ctx, &ngrok.TunnelGroupBackendUpdate{
|
||||
ID: backend.ID,
|
||||
Metadata: ptr.To(specBackend.Metadata),
|
||||
Description: ptr.To(specBackend.Description),
|
||||
Labels: specBackend.Labels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// No backend has been created for this edge, create one
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Create(ctx, &ngrok.TunnelGroupBackendCreate{
|
||||
Metadata: edge.Spec.Backend.Metadata,
|
||||
Description: edge.Spec.Backend.Description,
|
||||
Labels: edge.Spec.Backend.Labels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edge.Status.Backend.ID = backend.ID
|
||||
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) setMutualTLS(ctx context.Context, edge *ngrok.TLSEdge, mutualTls *ingressv1alpha1.EndpointMutualTLS) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
client := r.NgrokClientset.EdgeModules().TLS().MutualTLS()
|
||||
if mutualTls == nil {
|
||||
if edge.MutualTls == nil {
|
||||
log.V(1).Info("Edge Mutual TLS matches spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Deleting Edge Mutual TLS")
|
||||
return client.Delete(ctx, edge.ID)
|
||||
}
|
||||
|
||||
_, err := client.Replace(ctx, &ngrok.EdgeMutualTLSReplace{
|
||||
ID: edge.ID,
|
||||
Module: ngrok.EndpointMutualTLSMutate{
|
||||
CertificateAuthorityIDs: mutualTls.CertificateAuthorities,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) setTLSTermination(ctx context.Context, edge *ngrok.TLSEdge, tlsTermination *ingressv1alpha1.EndpointTLSTermination) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
client := r.NgrokClientset.EdgeModules().TLS().TLSTermination()
|
||||
if tlsTermination == nil {
|
||||
if edge.TlsTermination == nil {
|
||||
log.V(1).Info("Edge TLS termination matches spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Deleting Edge TLS termination")
|
||||
return client.Delete(ctx, edge.ID)
|
||||
}
|
||||
|
||||
_, err := client.Replace(ctx, &ngrok.EdgeTLSTerminationReplace{
|
||||
ID: edge.ID,
|
||||
Module: ngrok.EndpointTLSTermination{
|
||||
TerminateAt: tlsTermination.TerminateAt,
|
||||
MinVersion: tlsTermination.MinVersion,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) findEdgeByBackendLabels(ctx context.Context, backendLabels map[string]string) (*ngrok.TLSEdge, error) {
|
||||
log := ctrl.LoggerFrom(ctx).WithValues("labels", backendLabels)
|
||||
|
||||
log.Info("Searching for existing TLSEdge with backend labels")
|
||||
iter := r.NgrokClientset.TLSEdges().List(&ngrok.Paging{})
|
||||
for iter.Next(ctx) {
|
||||
edge := iter.Item()
|
||||
if edge.Backend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
backend, err := r.NgrokClientset.TunnelGroupBackends().Get(ctx, edge.Backend.Backend.ID)
|
||||
if err != nil {
|
||||
// The backend ID on the edge is invalid and no longer exists in the ngrok API,
|
||||
// so we'll skip this edge check the next one.
|
||||
if ngrok.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We've go an error besides not found. Return the error and
|
||||
// hopefully the next reconcile will fix it.
|
||||
return nil, err
|
||||
}
|
||||
if backend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if maps.Equal(backend.Labels, backendLabels) {
|
||||
log.Info("Found existing TLSEdge with matching backend labels", "edge.ID", edge.ID)
|
||||
return edge, nil
|
||||
}
|
||||
}
|
||||
return nil, iter.Err()
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) updateEdgeStatus(ctx context.Context, edge *ingressv1alpha1.TLSEdge, remoteEdge *ngrok.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
log.V(1).Info("Updating Status", "edge.ID", remoteEdge.ID, "edge.hostports", remoteEdge.Hostports)
|
||||
|
||||
domains := &ingressv1alpha1.DomainList{}
|
||||
if err := r.Client.List(ctx, domains, client.InNamespace(edge.Namespace)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edgeDomainMap := make(map[string]bool)
|
||||
for _, hp := range remoteEdge.Hostports {
|
||||
host, _, err := parseHostAndPort(hp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edgeDomainMap[host] = true
|
||||
}
|
||||
|
||||
edge.Status.CNAMETargets = map[string]string{}
|
||||
for _, domain := range domains.Items {
|
||||
// We don't care about domains that aren't part of this edge
|
||||
if _, ok := edgeDomainMap[domain.Spec.Domain]; !ok {
|
||||
log.V(3).Info("Skipping domain not part of edge", "domain", domain.Spec.Domain)
|
||||
continue
|
||||
}
|
||||
|
||||
if domain.Status.CNAMETarget != nil {
|
||||
log.V(3).Info("Adding CNAME target to status", "domain", domain.Spec.Domain, "cname", *domain.Status.CNAMETarget)
|
||||
edge.Status.CNAMETargets[domain.Spec.Domain] = *domain.Status.CNAMETarget
|
||||
}
|
||||
}
|
||||
|
||||
edge.Status.ID = remoteEdge.ID
|
||||
edge.Status.URI = remoteEdge.URI
|
||||
edge.Status.Hostports = remoteEdge.Hostports
|
||||
edge.Status.Backend.ID = remoteEdge.Backend.Backend.ID
|
||||
|
||||
return r.Status().Update(ctx, edge)
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) updateIPRestrictionModule(ctx context.Context, edge *ingressv1alpha1.TLSEdge, _ *ngrok.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
if edge.Spec.IPRestriction == nil || len(edge.Spec.IPRestriction.IPPolicies) == 0 {
|
||||
return r.NgrokClientset.EdgeModules().TLS().IPRestriction().Delete(ctx, edge.Status.ID)
|
||||
}
|
||||
policyIds, err := r.IPPolicyResolver.ResolveIPPolicyNamesorIds(ctx, edge.Namespace, edge.Spec.IPRestriction.IPPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Resolved IP Policy NamesOrIDs to IDs", "policyIds", policyIds)
|
||||
|
||||
_, err = r.NgrokClientset.EdgeModules().TLS().IPRestriction().Replace(ctx, &ngrok.EdgeIPRestrictionReplace{
|
||||
ID: edge.Status.ID,
|
||||
Module: ngrok.EndpointIPPolicyMutate{
|
||||
IPPolicyIDs: policyIds,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) listTLSEdgesForIPPolicy(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
log.Info("Listing TLSEdges for ip policy to determine if they need to be reconciled")
|
||||
policy, ok := obj.(*ingressv1alpha1.IPPolicy)
|
||||
if !ok {
|
||||
log.Error(nil, "failed to convert object to IPPolicy", "object", obj)
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
edges := &ingressv1alpha1.TLSEdgeList{}
|
||||
if err := r.Client.List(ctx, edges); err != nil {
|
||||
log.Error(err, "failed to list TLSEdges for ippolicy", "name", policy.Name, "namespace", policy.Namespace)
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
recs := []reconcile.Request{}
|
||||
|
||||
for _, edge := range edges.Items {
|
||||
if edge.Spec.IPRestriction == nil {
|
||||
continue
|
||||
}
|
||||
for _, edgePolicyID := range edge.Spec.IPRestriction.IPPolicies {
|
||||
if edgePolicyID == policy.Name || edgePolicyID == policy.Status.ID {
|
||||
recs = append(recs, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: edge.GetName(),
|
||||
Namespace: edge.GetNamespace(),
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("IPPolicy change triggered TLSEdge reconciliation", "count", len(recs), "policy", policy.Name, "namespace", policy.Namespace)
|
||||
return recs
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) listTLSEdgesForDomain(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
log.Info("Listing TLSEdges for domain to determine if they need to be reconciled")
|
||||
domain, ok := obj.(*ingressv1alpha1.Domain)
|
||||
if !ok {
|
||||
log.Error(nil, "failed to convert object to Domain", "object", obj)
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
log = log.WithValues("domain", domain.Name, "namespace", domain.Namespace)
|
||||
|
||||
edges := &ingressv1alpha1.TLSEdgeList{}
|
||||
if err := r.Client.List(ctx, edges); err != nil {
|
||||
log.Error(err, "failed to list TLSEdges for domain")
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
recs := []reconcile.Request{}
|
||||
|
||||
for _, edge := range edges.Items {
|
||||
for _, hostport := range edge.Spec.Hostports {
|
||||
host, _, err := parseHostAndPort(hostport)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to parse host and port", "hostport", hostport)
|
||||
continue
|
||||
}
|
||||
if host == domain.Spec.Domain {
|
||||
log.V(1).Info("Found edge with matching hostport to reconcile for domain change", "edge", edge.Name)
|
||||
recs = append(recs, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: edge.GetName(),
|
||||
Namespace: edge.GetNamespace(),
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.V(1).Info("Domain change triggered TLSEdge reconciliation", "reconcile_requests", recs)
|
||||
return recs
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) updatePolicyModule(ctx context.Context, edge *ingressv1alpha1.TLSEdge, remoteEdge *ngrok.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
client := r.NgrokClientset.EdgeModules().TLS().TrafficPolicy()
|
||||
|
||||
trafficPolicy := edge.Spec.Policy
|
||||
|
||||
// Early return if nothing to be done
|
||||
if trafficPolicy == nil {
|
||||
if remoteEdge.TrafficPolicy == nil {
|
||||
log.Info("Module matches desired state, skipping update", "module", "Traffic Policy", "comparison", routeModuleComparisonBothNil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Deleting Traffic Policy module")
|
||||
return client.Delete(ctx, edge.Status.ID)
|
||||
}
|
||||
|
||||
parsedTrafficPolicy, err := util.NewTrafficPolicyFromJson(trafficPolicy)
|
||||
if err != nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.TrafficPolicyParseFailed, "Failed to parse Traffic Policy, possibly malformed.")
|
||||
return err
|
||||
}
|
||||
|
||||
if parsedTrafficPolicy.IsLegacyPolicy() {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.PolicyDeprecation, "Traffic Policy is using legacy directions: ['inbound', 'outbound']. Update to new phases: ['on_tcp_connect', 'on_http_request', 'on_http_response']")
|
||||
}
|
||||
|
||||
if parsedTrafficPolicy.Enabled() != nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeWarning, events.PolicyDeprecation, "Traffic Policy has 'enabled' set. This is a legacy option that will stop being supported soon.")
|
||||
}
|
||||
|
||||
apiTrafficPolicy, err := parsedTrafficPolicy.ToAPIJson()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Recorder.Eventf(edge, v1.EventTypeNormal, "Update", "Updating Traffic Policy on edge.")
|
||||
_, err = client.Replace(ctx, &ngrok.EdgeTrafficPolicyReplace{
|
||||
ID: remoteEdge.ID,
|
||||
Module: ngrok.EndpointTrafficPolicy{
|
||||
Enabled: parsedTrafficPolicy.Enabled(),
|
||||
Value: string(apiTrafficPolicy),
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
r.Recorder.Eventf(edge, v1.EventTypeNormal, "Update", "Traffic Policy successfully updated.")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) reconcileDomains(ctx context.Context, edge *ingressv1alpha1.TLSEdge) error {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
existing := make(map[string]bool)
|
||||
domainList := &ingressv1alpha1.DomainList{}
|
||||
if err := r.Client.List(ctx, domainList, client.InNamespace(edge.Namespace)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, domain := range domainList.Items {
|
||||
existing[domain.Spec.Domain] = true
|
||||
}
|
||||
|
||||
// Get the desired domains
|
||||
desiredDomains, err := r.getDesiredDomains(ctx, edge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, domain := range desiredDomains {
|
||||
// Already exists, skip
|
||||
if _, ok := existing[domain.Spec.Domain]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Doesn't exist, create
|
||||
log.Info("Creating domain", "name", domain.Name, "namespace", domain.Namespace)
|
||||
if err := r.Client.Create(ctx, &domain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TLSEdgeReconciler) getDesiredDomains(ctx context.Context, edge *ingressv1alpha1.TLSEdge) ([]ingressv1alpha1.Domain, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
log.V(3).Info("Calculating desired domains")
|
||||
desired := []ingressv1alpha1.Domain{}
|
||||
for _, hostport := range edge.Spec.Hostports {
|
||||
host, _, err := parseHostAndPort(hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domain := ingressv1alpha1.Domain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: strings.ReplaceAll(host, ".", "-"),
|
||||
Namespace: edge.Namespace,
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: ingressv1alpha1.DomainSpec{
|
||||
Domain: host,
|
||||
},
|
||||
}
|
||||
desired = append(desired, domain)
|
||||
}
|
||||
return desired, nil
|
||||
}
|
||||
|
||||
// parses the ngrok hostport string into a hostname and port
|
||||
func parseHostAndPort(hostport string) (string, int32, error) {
|
||||
pieces := strings.SplitN(hostport, ":", 2)
|
||||
if len(pieces) != 2 {
|
||||
return "", 0, errors.New("invalid hostport")
|
||||
}
|
||||
|
||||
port, err := strconv.ParseInt(pieces[1], 10, 32)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return pieces[0], int32(port), nil
|
||||
}
|
||||
@@ -85,13 +85,7 @@ func (r *NgrokTrafficPolicyReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
r.Recorder.Eventf(policy, v1.EventTypeWarning, events.PolicyDeprecation, "Traffic Policy has 'enabled' set. This is a legacy option that will stop being supported soon.")
|
||||
}
|
||||
|
||||
if err := r.Driver.SyncEdges(ctx, r.Client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if err := r.Driver.SyncEndpoints(ctx, r.Client); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
return ctrl.Result{}, r.Driver.SyncEndpoints(ctx, r.Client)
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
|
||||
@@ -4,11 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
tunnel_group_backends "github.com/ngrok/ngrok-api-go/v7/backends/tunnel_group"
|
||||
https_edges "github.com/ngrok/ngrok-api-go/v7/edges/https"
|
||||
https_edge_routes "github.com/ngrok/ngrok-api-go/v7/edges/https_routes"
|
||||
tcp_edges "github.com/ngrok/ngrok-api-go/v7/edges/tcp"
|
||||
tls_edges "github.com/ngrok/ngrok-api-go/v7/edges/tls"
|
||||
"github.com/ngrok/ngrok-api-go/v7/endpoints"
|
||||
"github.com/ngrok/ngrok-api-go/v7/ip_policies"
|
||||
"github.com/ngrok/ngrok-api-go/v7/ip_policy_rules"
|
||||
@@ -19,49 +14,31 @@ import (
|
||||
|
||||
type Clientset interface {
|
||||
Domains() DomainClient
|
||||
EdgeModules() EdgeModulesClientset
|
||||
Endpoints() EndpointsClient
|
||||
HTTPSEdges() HTTPSEdgeClient
|
||||
HTTPSEdgeRoutes() HTTPSEdgeRoutesClient
|
||||
IPPolicies() IPPoliciesClient
|
||||
IPPolicyRules() IPPolicyRulesClient
|
||||
KubernetesOperators() KubernetesOperatorsClient
|
||||
TCPAddresses() TCPAddressesClient
|
||||
TCPEdges() TCPEdgesClient
|
||||
TLSEdges() TLSEdgesClient
|
||||
TunnelGroupBackends() TunnelGroupBackendsClient
|
||||
}
|
||||
|
||||
type DefaultClientset struct {
|
||||
domainsClient *reserved_domains.Client
|
||||
edgeModulesClientset *defaultEdgeModulesClientset
|
||||
endpointsClient *endpoints.Client
|
||||
httpsEdgesClient *https_edges.Client
|
||||
httpsEdgeRoutesClient *https_edge_routes.Client
|
||||
ipPoliciesClient *ip_policies.Client
|
||||
ipPolicyRulesClient *ip_policy_rules.Client
|
||||
kubernetesOperatorsClient *kubernetes_operators.Client
|
||||
tcpAddrsClient *reserved_addrs.Client
|
||||
tcpEdgesClient *tcp_edges.Client
|
||||
tlsEdgesClient *tls_edges.Client
|
||||
tunnelGroupBackendsClient *tunnel_group_backends.Client
|
||||
}
|
||||
|
||||
// NewClientSet creates a new ClientSet from an ngrok client config.
|
||||
func NewClientSet(config *ngrok.ClientConfig) *DefaultClientset {
|
||||
return &DefaultClientset{
|
||||
domainsClient: reserved_domains.NewClient(config),
|
||||
edgeModulesClientset: newEdgeModulesClientset(config),
|
||||
endpointsClient: endpoints.NewClient(config),
|
||||
httpsEdgesClient: https_edges.NewClient(config),
|
||||
httpsEdgeRoutesClient: https_edge_routes.NewClient(config),
|
||||
ipPoliciesClient: ip_policies.NewClient(config),
|
||||
ipPolicyRulesClient: ip_policy_rules.NewClient(config),
|
||||
kubernetesOperatorsClient: kubernetes_operators.NewClient(config),
|
||||
tcpAddrsClient: reserved_addrs.NewClient(config),
|
||||
tcpEdgesClient: tcp_edges.NewClient(config),
|
||||
tlsEdgesClient: tls_edges.NewClient(config),
|
||||
tunnelGroupBackendsClient: tunnel_group_backends.NewClient(config),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +74,6 @@ func (c *DefaultClientset) Domains() DomainClient {
|
||||
return c.domainsClient
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) EdgeModules() EdgeModulesClientset {
|
||||
return c.edgeModulesClientset
|
||||
}
|
||||
|
||||
type EndpointsClient interface {
|
||||
Creator[*ngrok.EndpointCreate, *ngrok.Endpoint]
|
||||
Reader[*ngrok.Endpoint]
|
||||
@@ -113,29 +86,6 @@ func (c *DefaultClientset) Endpoints() EndpointsClient {
|
||||
return c.endpointsClient
|
||||
}
|
||||
|
||||
type HTTPSEdgeClient interface {
|
||||
Creator[*ngrok.HTTPSEdgeCreate, *ngrok.HTTPSEdge]
|
||||
Reader[*ngrok.HTTPSEdge]
|
||||
Updater[*ngrok.HTTPSEdgeUpdate, *ngrok.HTTPSEdge]
|
||||
Deletor
|
||||
Lister[*ngrok.HTTPSEdge]
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) HTTPSEdges() HTTPSEdgeClient {
|
||||
return c.httpsEdgesClient
|
||||
}
|
||||
|
||||
type HTTPSEdgeRoutesClient interface {
|
||||
Creator[*ngrok.HTTPSEdgeRouteCreate, *ngrok.HTTPSEdgeRoute]
|
||||
Get(context.Context, *ngrok.EdgeRouteItem) (*ngrok.HTTPSEdgeRoute, error)
|
||||
Updater[*ngrok.HTTPSEdgeRouteUpdate, *ngrok.HTTPSEdgeRoute]
|
||||
Delete(context.Context, *ngrok.EdgeRouteItem) error
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) HTTPSEdgeRoutes() HTTPSEdgeRoutesClient {
|
||||
return c.httpsEdgeRoutesClient
|
||||
}
|
||||
|
||||
type IPPoliciesClient interface {
|
||||
Creator[*ngrok.IPPolicyCreate, *ngrok.IPPolicy]
|
||||
Reader[*ngrok.IPPolicy]
|
||||
@@ -180,38 +130,3 @@ type TCPAddressesClient interface {
|
||||
func (c *DefaultClientset) TCPAddresses() TCPAddressesClient {
|
||||
return c.tcpAddrsClient
|
||||
}
|
||||
|
||||
type TLSEdgesClient interface {
|
||||
Creator[*ngrok.TLSEdgeCreate, *ngrok.TLSEdge]
|
||||
Reader[*ngrok.TLSEdge]
|
||||
Updater[*ngrok.TLSEdgeUpdate, *ngrok.TLSEdge]
|
||||
Deletor
|
||||
Lister[*ngrok.TLSEdge]
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) TLSEdges() TLSEdgesClient {
|
||||
return c.tlsEdgesClient
|
||||
}
|
||||
|
||||
type TCPEdgesClient interface {
|
||||
Creator[*ngrok.TCPEdgeCreate, *ngrok.TCPEdge]
|
||||
Reader[*ngrok.TCPEdge]
|
||||
Updater[*ngrok.TCPEdgeUpdate, *ngrok.TCPEdge]
|
||||
Deletor
|
||||
Lister[*ngrok.TCPEdge]
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) TCPEdges() TCPEdgesClient {
|
||||
return c.tcpEdgesClient
|
||||
}
|
||||
|
||||
type TunnelGroupBackendsClient interface {
|
||||
Creator[*ngrok.TunnelGroupBackendCreate, *ngrok.TunnelGroupBackend]
|
||||
Reader[*ngrok.TunnelGroupBackend]
|
||||
Updater[*ngrok.TunnelGroupBackendUpdate, *ngrok.TunnelGroupBackend]
|
||||
Lister[*ngrok.TunnelGroupBackend]
|
||||
}
|
||||
|
||||
func (c *DefaultClientset) TunnelGroupBackends() TunnelGroupBackendsClient {
|
||||
return c.tunnelGroupBackendsClient
|
||||
}
|
||||
|
||||
@@ -19,8 +19,4 @@ func ExampleClientset() {
|
||||
cs := NewClientSet(config)
|
||||
// Access a client for the domains API.
|
||||
cs.Domains()
|
||||
// Access a client for TCP Edge modules
|
||||
cs.EdgeModules().TCP()
|
||||
// Access a client for HTTPS Edge Route Modules
|
||||
cs.EdgeModules().HTTPS().Routes().Compression()
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package ngrokapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
)
|
||||
|
||||
type EdgeModulesClientset interface {
|
||||
TCP() TCPEdgeModulesClientset
|
||||
HTTPS() HTTPSEdgeModulesClientset
|
||||
TLS() TLSEdgeModulesClientset
|
||||
}
|
||||
|
||||
type edgeModulesClient[R, T any] interface {
|
||||
Deletor
|
||||
Replace(context.Context, R) (T, error)
|
||||
}
|
||||
|
||||
type EdgeRouteModulesDeletor interface {
|
||||
Delete(context.Context, *ngrok.EdgeRouteItem) error
|
||||
}
|
||||
|
||||
type EdgeRouteModulesReplacer[R, T any] interface {
|
||||
Replace(context.Context, R) (T, error)
|
||||
}
|
||||
|
||||
type defaultEdgeModulesClientset struct {
|
||||
tcp *defaultTCPEdgeModulesClientset
|
||||
https *defaultHTTPSEdgeModulesClientset
|
||||
tls *defaultTLSEdgeModulesClientset
|
||||
}
|
||||
|
||||
func newEdgeModulesClientset(config *ngrok.ClientConfig) *defaultEdgeModulesClientset {
|
||||
return &defaultEdgeModulesClientset{
|
||||
tcp: newTCPEdgeModulesClientset(config),
|
||||
https: newHTTPSEdgeModulesClientset(config),
|
||||
tls: newTLSEdgeModulesClientset(config),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultEdgeModulesClientset) TCP() TCPEdgeModulesClientset {
|
||||
return c.tcp
|
||||
}
|
||||
|
||||
func (c *defaultEdgeModulesClientset) HTTPS() HTTPSEdgeModulesClientset {
|
||||
return c.https
|
||||
}
|
||||
|
||||
func (c *defaultEdgeModulesClientset) TLS() TLSEdgeModulesClientset {
|
||||
return c.tls
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package ngrokapi
|
||||
|
||||
import (
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_mutual_tls"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_backend"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_circuit_breaker"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_compression"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_ip_restriction"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_oauth"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_oidc"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_request_headers"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_response_headers"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_saml"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_traffic_policy"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_webhook_verification"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_route_websocket_tcp_converter"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/https_edge_tls_termination"
|
||||
)
|
||||
|
||||
type HTTPSEdgeModulesClientset interface {
|
||||
MutualTLS() HTTPSEdgeModulesMutualTLSClient
|
||||
Routes() HTTPSEdgeRouteModulesClientset
|
||||
TLSTermination() HTTPSEdgeModulesTLSTerminationClient
|
||||
}
|
||||
|
||||
type (
|
||||
HTTPSEdgeModulesMutualTLSClient = edgeModulesClient[*ngrok.EdgeMutualTLSReplace, *ngrok.EndpointMutualTLS]
|
||||
HTTPSEdgeModulesTLSTerminationClient = edgeModulesClient[*ngrok.EdgeTLSTerminationAtEdgeReplace, *ngrok.EndpointTLSTermination]
|
||||
)
|
||||
|
||||
type defaultHTTPSEdgeModulesClientset struct {
|
||||
mutualTLS *https_edge_mutual_tls.Client
|
||||
routes *defaultHTTPSEdgeRouteModulesClientset
|
||||
tlsTermination *https_edge_tls_termination.Client
|
||||
}
|
||||
|
||||
func newHTTPSEdgeModulesClientset(config *ngrok.ClientConfig) *defaultHTTPSEdgeModulesClientset {
|
||||
return &defaultHTTPSEdgeModulesClientset{
|
||||
mutualTLS: https_edge_mutual_tls.NewClient(config),
|
||||
routes: newHTTPSEdgeRouteModulesClient(config),
|
||||
tlsTermination: https_edge_tls_termination.NewClient(config),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeModulesClientset) MutualTLS() HTTPSEdgeModulesMutualTLSClient {
|
||||
return c.mutualTLS
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeModulesClientset) Routes() HTTPSEdgeRouteModulesClientset {
|
||||
return c.routes
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeModulesClientset) TLSTermination() HTTPSEdgeModulesTLSTerminationClient {
|
||||
return c.tlsTermination
|
||||
}
|
||||
|
||||
type HTTPSEdgeRouteModulesClientset interface {
|
||||
Backend() HTTPSEdgeRouteBackendClient
|
||||
CircuitBreaker() HTTPSEdgeRouteCircuitBreakerClient
|
||||
Compression() HTTPSEdgeRouteCompressionClient
|
||||
IPRestriction() HTTPSEdgeRouteIPRestrictionClient
|
||||
OAuth() HTTPSEdgeRouteOAuthClient
|
||||
OIDC() HTTPSEdgeRouteOIDCClient
|
||||
RequestHeaders() HTTPSEdgeRouteRequestHeadersClient
|
||||
ResponseHeaders() HTTPSEdgeRouteResponseHeadersClient
|
||||
SAML() HTTPSEdgeRouteSAMLClient
|
||||
TrafficPolicy() HTTPSEdgeRouteTrafficPolicyClient
|
||||
WebhookVerification() HTTPSEdgeRouteWebhookVerificationClient
|
||||
WebsocketTCPConverter() HTTPSEdgeRouteWebsocketTCPConverterClient
|
||||
}
|
||||
|
||||
type defaultHTTPSEdgeRouteModulesClientset struct {
|
||||
backend *https_edge_route_backend.Client
|
||||
circuitBreaker *https_edge_route_circuit_breaker.Client
|
||||
compression *https_edge_route_compression.Client
|
||||
ipRestriction *https_edge_route_ip_restriction.Client
|
||||
oauth *https_edge_route_oauth.Client
|
||||
trafficPolicy *https_edge_route_traffic_policy.Client
|
||||
oidc *https_edge_route_oidc.Client
|
||||
requestHeaders *https_edge_route_request_headers.Client
|
||||
responseHeaders *https_edge_route_response_headers.Client
|
||||
saml *https_edge_route_saml.Client
|
||||
webhookVerification *https_edge_route_webhook_verification.Client
|
||||
websocketTCPConverter *https_edge_route_websocket_tcp_converter.Client
|
||||
}
|
||||
|
||||
func newHTTPSEdgeRouteModulesClient(config *ngrok.ClientConfig) *defaultHTTPSEdgeRouteModulesClientset {
|
||||
return &defaultHTTPSEdgeRouteModulesClientset{
|
||||
backend: https_edge_route_backend.NewClient(config),
|
||||
circuitBreaker: https_edge_route_circuit_breaker.NewClient(config),
|
||||
compression: https_edge_route_compression.NewClient(config),
|
||||
ipRestriction: https_edge_route_ip_restriction.NewClient(config),
|
||||
oauth: https_edge_route_oauth.NewClient(config),
|
||||
trafficPolicy: https_edge_route_traffic_policy.NewClient(config),
|
||||
oidc: https_edge_route_oidc.NewClient(config),
|
||||
requestHeaders: https_edge_route_request_headers.NewClient(config),
|
||||
responseHeaders: https_edge_route_response_headers.NewClient(config),
|
||||
saml: https_edge_route_saml.NewClient(config),
|
||||
webhookVerification: https_edge_route_webhook_verification.NewClient(config),
|
||||
websocketTCPConverter: https_edge_route_websocket_tcp_converter.NewClient(config),
|
||||
}
|
||||
}
|
||||
|
||||
type httpsEdgeRouteModulesClient[R, T any] interface {
|
||||
EdgeRouteModulesDeletor
|
||||
EdgeRouteModulesReplacer[R, T]
|
||||
}
|
||||
|
||||
type (
|
||||
HTTPSEdgeRouteBackendClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteBackendReplace, *ngrok.EndpointBackend]
|
||||
HTTPSEdgeRouteCircuitBreakerClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteCircuitBreakerReplace, *ngrok.EndpointCircuitBreaker]
|
||||
HTTPSEdgeRouteCompressionClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteCompressionReplace, *ngrok.EndpointCompression]
|
||||
HTTPSEdgeRouteIPRestrictionClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteIPRestrictionReplace, *ngrok.EndpointIPPolicy]
|
||||
HTTPSEdgeRouteOAuthClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteOAuthReplace, *ngrok.EndpointOAuth]
|
||||
HTTPSEdgeRouteOIDCClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteOIDCReplace, *ngrok.EndpointOIDC]
|
||||
HTTPSEdgeRouteRequestHeadersClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteRequestHeadersReplace, *ngrok.EndpointRequestHeaders]
|
||||
HTTPSEdgeRouteResponseHeadersClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteResponseHeadersReplace, *ngrok.EndpointResponseHeaders]
|
||||
HTTPSEdgeRouteSAMLClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteSAMLReplace, *ngrok.EndpointSAML]
|
||||
HTTPSEdgeRouteTrafficPolicyClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteTrafficPolicyReplace, *ngrok.EndpointTrafficPolicy]
|
||||
HTTPSEdgeRouteWebhookVerificationClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteWebhookVerificationReplace, *ngrok.EndpointWebhookValidation]
|
||||
HTTPSEdgeRouteWebsocketTCPConverterClient = httpsEdgeRouteModulesClient[*ngrok.EdgeRouteWebsocketTCPConverterReplace, *ngrok.EndpointWebsocketTCPConverter]
|
||||
)
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) Backend() HTTPSEdgeRouteBackendClient {
|
||||
return c.backend
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) CircuitBreaker() HTTPSEdgeRouteCircuitBreakerClient {
|
||||
return c.circuitBreaker
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) Compression() HTTPSEdgeRouteCompressionClient {
|
||||
return c.compression
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) IPRestriction() HTTPSEdgeRouteIPRestrictionClient {
|
||||
return c.ipRestriction
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) OAuth() HTTPSEdgeRouteOAuthClient {
|
||||
return c.oauth
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) TrafficPolicy() HTTPSEdgeRouteTrafficPolicyClient {
|
||||
return c.trafficPolicy
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) OIDC() HTTPSEdgeRouteOIDCClient {
|
||||
return c.oidc
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) RequestHeaders() HTTPSEdgeRouteRequestHeadersClient {
|
||||
return c.requestHeaders
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) ResponseHeaders() HTTPSEdgeRouteResponseHeadersClient {
|
||||
return c.responseHeaders
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) SAML() HTTPSEdgeRouteSAMLClient {
|
||||
return c.saml
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) WebhookVerification() HTTPSEdgeRouteWebhookVerificationClient {
|
||||
return c.webhookVerification
|
||||
}
|
||||
|
||||
func (c *defaultHTTPSEdgeRouteModulesClientset) WebsocketTCPConverter() HTTPSEdgeRouteWebsocketTCPConverterClient {
|
||||
return c.websocketTCPConverter
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package ngrokapi
|
||||
|
||||
import (
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tcp_edge_backend"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tcp_edge_ip_restriction"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tcp_edge_traffic_policy"
|
||||
)
|
||||
|
||||
type TCPEdgeModulesClientset interface {
|
||||
Backend() TCPEdgeModulesBackendClient
|
||||
IPRestriction() TCPEdgeModulesIPRestrictionClient
|
||||
TrafficPolicy() TCPEdgeModulesTrafficPolicyClient
|
||||
}
|
||||
|
||||
type (
|
||||
TCPEdgeModulesBackendClient = edgeModulesClient[*ngrok.EdgeBackendReplace, *ngrok.EndpointBackend]
|
||||
TCPEdgeModulesIPRestrictionClient = edgeModulesClient[*ngrok.EdgeIPRestrictionReplace, *ngrok.EndpointIPPolicy]
|
||||
TCPEdgeModulesTrafficPolicyClient = edgeModulesClient[*ngrok.EdgeTrafficPolicyReplace, *ngrok.EndpointTrafficPolicy]
|
||||
)
|
||||
|
||||
type defaultTCPEdgeModulesClientset struct {
|
||||
backend *tcp_edge_backend.Client
|
||||
ipRestriction *tcp_edge_ip_restriction.Client
|
||||
trafficPolicy *tcp_edge_traffic_policy.Client
|
||||
}
|
||||
|
||||
func newTCPEdgeModulesClientset(config *ngrok.ClientConfig) *defaultTCPEdgeModulesClientset {
|
||||
return &defaultTCPEdgeModulesClientset{
|
||||
backend: tcp_edge_backend.NewClient(config),
|
||||
ipRestriction: tcp_edge_ip_restriction.NewClient(config),
|
||||
trafficPolicy: tcp_edge_traffic_policy.NewClient(config),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultTCPEdgeModulesClientset) Backend() TCPEdgeModulesBackendClient {
|
||||
return c.backend
|
||||
}
|
||||
|
||||
func (c *defaultTCPEdgeModulesClientset) IPRestriction() TCPEdgeModulesIPRestrictionClient {
|
||||
return c.ipRestriction
|
||||
}
|
||||
|
||||
func (c *defaultTCPEdgeModulesClientset) TrafficPolicy() TCPEdgeModulesTrafficPolicyClient {
|
||||
return c.trafficPolicy
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package ngrokapi
|
||||
|
||||
import (
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tls_edge_backend"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tls_edge_ip_restriction"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tls_edge_mutual_tls"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tls_edge_tls_termination"
|
||||
"github.com/ngrok/ngrok-api-go/v7/edge_modules/tls_edge_traffic_policy"
|
||||
)
|
||||
|
||||
type TLSEdgeModulesClientset interface {
|
||||
Backend() TLSEdgeModulesBackendClient
|
||||
IPRestriction() TLSEdgeModulesIPRestrictionClient
|
||||
MutualTLS() TLSEdgeModulesMutualTLSClient
|
||||
TLSTermination() TLSEdgeModulesTLSTerminationClient
|
||||
TrafficPolicy() TLSEdgeModulesTrafficPolicyClient
|
||||
}
|
||||
|
||||
type (
|
||||
TLSEdgeModulesBackendClient = edgeModulesClient[*ngrok.EdgeBackendReplace, *ngrok.EndpointBackend]
|
||||
TLSEdgeModulesIPRestrictionClient = edgeModulesClient[*ngrok.EdgeIPRestrictionReplace, *ngrok.EndpointIPPolicy]
|
||||
TLSEdgeModulesMutualTLSClient = edgeModulesClient[*ngrok.EdgeMutualTLSReplace, *ngrok.EndpointMutualTLS]
|
||||
TLSEdgeModulesTLSTerminationClient = edgeModulesClient[*ngrok.EdgeTLSTerminationReplace, *ngrok.EndpointTLSTermination]
|
||||
TLSEdgeModulesTrafficPolicyClient = edgeModulesClient[*ngrok.EdgeTrafficPolicyReplace, *ngrok.EndpointTrafficPolicy]
|
||||
)
|
||||
|
||||
type defaultTLSEdgeModulesClientset struct {
|
||||
backend *tls_edge_backend.Client
|
||||
ipRestriction *tls_edge_ip_restriction.Client
|
||||
mutualTLS *tls_edge_mutual_tls.Client
|
||||
tlsTermination *tls_edge_tls_termination.Client
|
||||
trafficPolicy *tls_edge_traffic_policy.Client
|
||||
}
|
||||
|
||||
func newTLSEdgeModulesClientset(config *ngrok.ClientConfig) *defaultTLSEdgeModulesClientset {
|
||||
return &defaultTLSEdgeModulesClientset{
|
||||
backend: tls_edge_backend.NewClient(config),
|
||||
ipRestriction: tls_edge_ip_restriction.NewClient(config),
|
||||
mutualTLS: tls_edge_mutual_tls.NewClient(config),
|
||||
tlsTermination: tls_edge_tls_termination.NewClient(config),
|
||||
trafficPolicy: tls_edge_traffic_policy.NewClient(config),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultTLSEdgeModulesClientset) Backend() TLSEdgeModulesBackendClient {
|
||||
return c.backend
|
||||
}
|
||||
|
||||
func (c *defaultTLSEdgeModulesClientset) IPRestriction() TLSEdgeModulesIPRestrictionClient {
|
||||
return c.ipRestriction
|
||||
}
|
||||
|
||||
func (c *defaultTLSEdgeModulesClientset) MutualTLS() TLSEdgeModulesMutualTLSClient {
|
||||
return c.mutualTLS
|
||||
}
|
||||
|
||||
func (c *defaultTLSEdgeModulesClientset) TLSTermination() TLSEdgeModulesTLSTerminationClient {
|
||||
return c.tlsTermination
|
||||
}
|
||||
|
||||
func (c *defaultTLSEdgeModulesClientset) TrafficPolicy() TLSEdgeModulesTrafficPolicyClient {
|
||||
return c.trafficPolicy
|
||||
}
|
||||
@@ -51,9 +51,6 @@ type CacheStores struct {
|
||||
|
||||
// Ngrok Stores
|
||||
DomainV1 cache.Store
|
||||
TunnelV1 cache.Store
|
||||
HTTPSEdgeV1 cache.Store
|
||||
NgrokModuleV1 cache.Store
|
||||
NgrokTrafficPolicyV1 cache.Store
|
||||
AgentEndpointV1 cache.Store
|
||||
CloudEndpointV1 cache.Store
|
||||
@@ -81,9 +78,6 @@ func NewCacheStores(logger logr.Logger) CacheStores {
|
||||
ReferenceGrant: cache.NewStore(keyFunc),
|
||||
// Ngrok Stores
|
||||
DomainV1: cache.NewStore(keyFunc),
|
||||
TunnelV1: cache.NewStore(keyFunc),
|
||||
HTTPSEdgeV1: cache.NewStore(keyFunc),
|
||||
NgrokModuleV1: cache.NewStore(keyFunc),
|
||||
NgrokTrafficPolicyV1: cache.NewStore(keyFunc),
|
||||
AgentEndpointV1: cache.NewStore(keyFunc),
|
||||
CloudEndpointV1: cache.NewStore(keyFunc),
|
||||
@@ -152,12 +146,6 @@ func (c CacheStores) Get(obj runtime.Object) (item interface{}, exists bool, err
|
||||
// ----------------------------------------------------------------------------
|
||||
case *ingressv1alpha1.Domain:
|
||||
return c.DomainV1.Get(obj)
|
||||
case *ingressv1alpha1.Tunnel:
|
||||
return c.TunnelV1.Get(obj)
|
||||
case *ingressv1alpha1.HTTPSEdge:
|
||||
return c.HTTPSEdgeV1.Get(obj)
|
||||
case *ingressv1alpha1.NgrokModuleSet:
|
||||
return c.NgrokModuleV1.Get(obj)
|
||||
case *ngrokv1alpha1.NgrokTrafficPolicy:
|
||||
return c.NgrokTrafficPolicyV1.Get(obj)
|
||||
case *ngrokv1alpha1.AgentEndpoint:
|
||||
@@ -213,12 +201,6 @@ func (c CacheStores) Add(obj runtime.Object) error {
|
||||
// ----------------------------------------------------------------------------
|
||||
case *ingressv1alpha1.Domain:
|
||||
return c.DomainV1.Add(obj)
|
||||
case *ingressv1alpha1.Tunnel:
|
||||
return c.TunnelV1.Add(obj)
|
||||
case *ingressv1alpha1.HTTPSEdge:
|
||||
return c.HTTPSEdgeV1.Add(obj)
|
||||
case *ingressv1alpha1.NgrokModuleSet:
|
||||
return c.NgrokModuleV1.Add(obj)
|
||||
case *ngrokv1alpha1.NgrokTrafficPolicy:
|
||||
return c.NgrokTrafficPolicyV1.Add(obj)
|
||||
case *ngrokv1alpha1.AgentEndpoint:
|
||||
@@ -275,12 +257,6 @@ func (c CacheStores) Delete(obj runtime.Object) error {
|
||||
// ----------------------------------------------------------------------------
|
||||
case *ingressv1alpha1.Domain:
|
||||
return c.DomainV1.Delete(obj)
|
||||
case *ingressv1alpha1.Tunnel:
|
||||
return c.TunnelV1.Delete(obj)
|
||||
case *ingressv1alpha1.HTTPSEdge:
|
||||
return c.HTTPSEdgeV1.Delete(obj)
|
||||
case *ingressv1alpha1.NgrokModuleSet:
|
||||
return c.NgrokModuleV1.Delete(obj)
|
||||
case *ngrokv1alpha1.NgrokTrafficPolicy:
|
||||
return c.NgrokTrafficPolicyV1.Delete(obj)
|
||||
case *ngrokv1alpha1.AgentEndpoint:
|
||||
|
||||
+1
-58
@@ -24,7 +24,6 @@ import (
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -53,7 +52,6 @@ type Storer interface {
|
||||
GetNamespaceV1(name string) (*corev1.Namespace, error)
|
||||
GetConfigMapV1(name, namespace string) (*corev1.ConfigMap, error)
|
||||
GetNgrokIngressV1(name, namespace string) (*netv1.Ingress, error)
|
||||
GetNgrokModuleSetV1(name, namespace string) (*ingressv1alpha1.NgrokModuleSet, error)
|
||||
GetNgrokTrafficPolicyV1(name, namespace string) (*ngrokv1alpha1.NgrokTrafficPolicy, error)
|
||||
GetGateway(name string, namespace string) (*gatewayv1.Gateway, error)
|
||||
GetGatewayClass(name string) (*gatewayv1.GatewayClass, error)
|
||||
@@ -75,9 +73,6 @@ type Storer interface {
|
||||
ListReferenceGrants() []*gatewayv1beta1.ReferenceGrant
|
||||
|
||||
ListDomainsV1() []*ingressv1alpha1.Domain
|
||||
ListTunnelsV1() []*ingressv1alpha1.Tunnel
|
||||
ListHTTPSEdgesV1() []*ingressv1alpha1.HTTPSEdge
|
||||
ListNgrokModuleSetsV1() []*ingressv1alpha1.NgrokModuleSet
|
||||
}
|
||||
|
||||
// Store implements Storer and can be used to list Ingress, Services
|
||||
@@ -160,10 +155,6 @@ func (s Store) GetNgrokIngressV1(name, namespace string) (*netv1.Ingress, error)
|
||||
return ing, nil
|
||||
}
|
||||
|
||||
func (s Store) GetNgrokModuleSetV1(name, namespace string) (*ingressv1alpha1.NgrokModuleSet, error) {
|
||||
return genericGetByKey[ingressv1alpha1.NgrokModuleSet](s.stores.NgrokModuleV1, getKey(name, namespace))
|
||||
}
|
||||
|
||||
func (s Store) GetNgrokTrafficPolicyV1(name, namespace string) (*ngrokv1alpha1.NgrokTrafficPolicy, error) {
|
||||
return genericGetByKey[ngrokv1alpha1.NgrokTrafficPolicy](s.stores.NgrokTrafficPolicyV1, getKey(name, namespace))
|
||||
}
|
||||
@@ -285,21 +276,6 @@ func (s Store) ListDomainsV1() []*ingressv1alpha1.Domain {
|
||||
return genericListSorted[ingressv1alpha1.Domain](s.log, s.stores.DomainV1)
|
||||
}
|
||||
|
||||
// ListTunnelsV1 returns the list of Tunnels in the Tunnel v1 store.
|
||||
func (s Store) ListTunnelsV1() []*ingressv1alpha1.Tunnel {
|
||||
return genericListSorted[ingressv1alpha1.Tunnel](s.log, s.stores.TunnelV1)
|
||||
}
|
||||
|
||||
// ListHTTPSEdgesV1 returns the list of HTTPSEdges in the HTTPSEdge v1 store.
|
||||
func (s Store) ListHTTPSEdgesV1() []*ingressv1alpha1.HTTPSEdge {
|
||||
return genericListSorted[ingressv1alpha1.HTTPSEdge](s.log, s.stores.HTTPSEdgeV1)
|
||||
}
|
||||
|
||||
// ListNgrokModuleSetsV1 returns the list of NgrokModules in the NgrokModuleSet v1 store.
|
||||
func (s Store) ListNgrokModuleSetsV1() []*ingressv1alpha1.NgrokModuleSet {
|
||||
return genericListSorted[ingressv1alpha1.NgrokModuleSet](s.log, s.stores.NgrokModuleV1)
|
||||
}
|
||||
|
||||
func genericList[T any, PT interface {
|
||||
*T
|
||||
client.Object
|
||||
@@ -378,13 +354,6 @@ func (s Store) shouldHandleIngressCheckClass(ing *netv1.Ingress) (bool, error) {
|
||||
// shouldHandleIngressIsValid checks if the ingress spec meets controller requirements.
|
||||
func (s Store) shouldHandleIngressIsValid(ing *netv1.Ingress) (bool, error) {
|
||||
errs := errors.NewErrInvalidIngressSpec()
|
||||
useEdges, err := annotations.ExtractUseEdges(ing)
|
||||
if err != nil {
|
||||
errs.AddError(fmt.Sprintf("failed to check %q annotation. defaulting to using endpoints: %s",
|
||||
annotations.MappingStrategyAnnotation,
|
||||
err.Error(),
|
||||
))
|
||||
}
|
||||
if len(ing.Spec.Rules) == 0 {
|
||||
errs.AddError("At least one rule is required to be set")
|
||||
} else {
|
||||
@@ -392,38 +361,12 @@ func (s Store) shouldHandleIngressIsValid(ing *netv1.Ingress) (bool, error) {
|
||||
if rule.Host == "" {
|
||||
errs.AddError("A host is required to be set for each rule")
|
||||
}
|
||||
if rule.HTTP != nil {
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
switch {
|
||||
case path.Backend.Resource != nil:
|
||||
if useEdges {
|
||||
errs.AddError(fmt.Sprintf("Resource backends are not supported for ingresses with the %q: %q annotation. Ingresses provided by endpoints instead of edges do support default backends",
|
||||
annotations.MappingStrategyAnnotation,
|
||||
annotations.MappingStrategy_Edges,
|
||||
))
|
||||
}
|
||||
case path.Backend.Service == nil:
|
||||
errs.AddError(fmt.Sprintf("A valid service backend is required for this ingress since a resource backend was not provided (resource backends are only supported for ingresses without the %q: %q annotation.)",
|
||||
annotations.MappingStrategyAnnotation,
|
||||
annotations.MappingStrategy_Edges,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rule.HTTP == nil {
|
||||
errs.AddError("HTTP rules are required for ingress")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ing.Spec.DefaultBackend != nil {
|
||||
if useEdges {
|
||||
errs.AddError(fmt.Sprintf("Default backends are not supported for ingresses with the %q: %q annotation. Ingresses provided by endpoints instead of edges do support default backends",
|
||||
annotations.MappingStrategyAnnotation,
|
||||
annotations.MappingStrategy_Edges,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if errs.HasErrors() {
|
||||
return false, errs
|
||||
}
|
||||
|
||||
@@ -465,51 +465,6 @@ var _ = Describe("Store", func() {
|
||||
)
|
||||
})
|
||||
|
||||
var _ = Describe("ListNgrokModulesV1", func() {
|
||||
Context("when there are NgrokModuleSets", func() {
|
||||
BeforeEach(func() {
|
||||
m1 := testutils.NewTestNgrokModuleSet("ngrok", "test", true)
|
||||
Expect(store.Add(&m1)).To(BeNil())
|
||||
m2 := testutils.NewTestNgrokModuleSet("ngrok", "test2", true)
|
||||
Expect(store.Add(&m2)).To(BeNil())
|
||||
m3 := testutils.NewTestNgrokModuleSet("test", "test", true)
|
||||
Expect(store.Add(&m3)).To(BeNil())
|
||||
})
|
||||
It("returns the NgrokModuleSet", func() {
|
||||
modules := store.ListNgrokModuleSetsV1()
|
||||
Expect(len(modules)).To(Equal(3))
|
||||
})
|
||||
})
|
||||
Context("when there are no NgrokModuleSets", func() {
|
||||
It("doesn't error", func() {
|
||||
modules := store.ListNgrokModuleSetsV1()
|
||||
Expect(len(modules)).To(Equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("GetNgrokModuleSetV1", func() {
|
||||
Context("when the NgrokModuleSet exists", func() {
|
||||
BeforeEach(func() {
|
||||
m := testutils.NewTestNgrokModuleSet("ngrok", "test", true)
|
||||
Expect(store.Add(&m)).To(BeNil())
|
||||
})
|
||||
It("returns the NgrokModuleSet", func() {
|
||||
modset, err := store.GetNgrokModuleSetV1("ngrok", "test")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(modset.Modules.Compression.Enabled).To(Equal(true))
|
||||
})
|
||||
})
|
||||
Context("when the NgrokModuleSet does not exist", func() {
|
||||
It("returns an error", func() {
|
||||
modset, err := store.GetNgrokModuleSetV1("does-not-exist", "does-not-exist")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(errors.IsErrorNotFound(err)).To(Equal(true))
|
||||
Expect(modset).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("GetNgrokTrafficPolicyV1", func() {
|
||||
Context("when the NgrokTrafficPolicy exists", func() {
|
||||
BeforeEach(func() {
|
||||
@@ -662,9 +617,8 @@ var _ = Describe("Store", func() {
|
||||
},
|
||||
}
|
||||
ok, err := store.shouldHandleIngressIsValid(ing)
|
||||
Expect(ok).To(BeFalse())
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Default backends are not supported"))
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -118,6 +118,11 @@ func RandomName(prefix string) string {
|
||||
return prefix + "-" + rand.String(5)
|
||||
}
|
||||
|
||||
// RandomURL generates a random URL.
|
||||
func RandomURL() string {
|
||||
return "https://" + RandomName("test-url") + ".ngrok.io"
|
||||
}
|
||||
|
||||
// NewGatewayClass creates a new GatewayClass with a random name to be used in tests. If
|
||||
// isManaged is true, the controller name will be set to the ngrok gateway controller name.
|
||||
// If isManaged is false, the controller name will be set to a different value.
|
||||
@@ -249,29 +254,6 @@ func NewReferenceGrant(name string, namespace string) gatewayv1beta1.ReferenceGr
|
||||
}
|
||||
}
|
||||
|
||||
func NewHTTPSEdge(name string, namespace string) ingressv1alpha1.HTTPSEdge {
|
||||
return ingressv1alpha1.HTTPSEdge{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestNgrokModuleSet(name string, namespace string, compressionEnabled bool) ingressv1alpha1.NgrokModuleSet {
|
||||
return ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: compressionEnabled,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestNgrokTrafficPolicy(name string, namespace string, policyStr string) ngrokv1alpha1.NgrokTrafficPolicy {
|
||||
return ngrokv1alpha1.NgrokTrafficPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -283,3 +265,46 @@ func NewTestNgrokTrafficPolicy(name string, namespace string, policyStr string)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CloudEndpointOpt func(*ngrokv1alpha1.CloudEndpoint)
|
||||
|
||||
// NewCloudEndpoint creates a new CloudEndpoint with the given name and namespace.
|
||||
// The URL is set to a random name for testing purposes.
|
||||
// You can pass additional options to customize the CloudEndpoint further.
|
||||
// Example usage:
|
||||
//
|
||||
// NewCloudEndpoint
|
||||
func NewCloudEndpoint(opts ...CloudEndpointOpt) *ngrokv1alpha1.CloudEndpoint {
|
||||
clep := &ngrokv1alpha1.CloudEndpoint{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: RandomName("cloud-endpoint"),
|
||||
Namespace: RandomName("namespace"),
|
||||
},
|
||||
Spec: ngrokv1alpha1.CloudEndpointSpec{
|
||||
URL: RandomName("url"),
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(clep)
|
||||
}
|
||||
return clep
|
||||
}
|
||||
|
||||
type AgentEndpointOpt func(*ngrokv1alpha1.AgentEndpoint)
|
||||
|
||||
func NewAgentEndpoint(opts ...AgentEndpointOpt) *ngrokv1alpha1.AgentEndpoint {
|
||||
aep := &ngrokv1alpha1.AgentEndpoint{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: RandomName("agent-endpoint"),
|
||||
Namespace: RandomName("namespace"),
|
||||
},
|
||||
Spec: ngrokv1alpha1.AgentEndpointSpec{
|
||||
URL: RandomURL(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(aep)
|
||||
}
|
||||
return aep
|
||||
}
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/ngrok/ngrok-operator/internal/resolvers"
|
||||
"github.com/ngrok/ngrok-operator/internal/trafficpolicy"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type ActionType string
|
||||
@@ -288,369 +278,3 @@ func mergeSinglePhase(originalTP map[string][]RawRule, addedRules []RawRule, pha
|
||||
|
||||
originalTP[phase] = addedRules
|
||||
}
|
||||
|
||||
func NewTrafficPolicyFromModuleset(ctx context.Context, ms *ingressv1alpha1.NgrokModuleSet, secretResolver resolvers.SecretResolver, ipPolicyResolver resolvers.IPPolicyResolver) (*trafficpolicy.TrafficPolicy, error) {
|
||||
if ms == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tp := trafficpolicy.NewTrafficPolicy()
|
||||
|
||||
converters := []modulesetConverterFunc{
|
||||
// On TCP Connect Rules (IP Restriction & Mutual TLS)
|
||||
convertModuleSetIPRestriction(ipPolicyResolver),
|
||||
convertModuleSetTLS,
|
||||
// On HTTP Request Rules
|
||||
convertModuleSetOAuth(secretResolver),
|
||||
convertModuleSetOIDC(secretResolver),
|
||||
convertModuleSetSAML,
|
||||
convertModuleSetCompression,
|
||||
convertModuleSetCircuitBreaker,
|
||||
convertModuleSetHeaders,
|
||||
convertModuleSetWebhookVerification(secretResolver),
|
||||
}
|
||||
|
||||
for _, converter := range converters {
|
||||
if err := converter(ctx, *ms, tp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if tp.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
type modulesetConverterFunc func(ctx context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error
|
||||
|
||||
func convertModuleSetTLS(_ context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.TLSTermination == nil && ms.Modules.MutualTLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsConfig := trafficpolicy.TLSTerminationConfig{}
|
||||
|
||||
if ms.Modules.TLSTermination != nil {
|
||||
tlsConfig.MinVersion = ms.Modules.TLSTermination.MinVersion
|
||||
}
|
||||
|
||||
if ms.Modules.MutualTLS != nil {
|
||||
tlsConfig.MutualTLSCertificateAuthorities = ms.Modules.MutualTLS.CertificateAuthorities
|
||||
}
|
||||
|
||||
rule := trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewTerminateTLSAction(tlsConfig),
|
||||
},
|
||||
}
|
||||
tp.AddRuleOnTCPConnect(rule)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertModuleSetIPRestriction(ipPolicyResolver resolvers.IPPolicyResolver) modulesetConverterFunc {
|
||||
return func(ctx context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.IPRestriction == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipPolicies, err := ipPolicyResolver.ResolveIPPolicyNamesorIds(ctx, ms.Namespace, ms.Modules.IPRestriction.IPPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tp.AddRuleOnTCPConnect(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewRestricIPsActionFromIPPolicies(ipPolicies),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func convertModuleSetCompression(_ context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.Compression == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tp.AddRuleOnHTTPResponse(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewCompressResponseAction(nil),
|
||||
},
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertModuleSetCircuitBreaker(_ context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.CircuitBreaker == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var volumeThreshold *uint32
|
||||
if ms.Modules.CircuitBreaker.VolumeThreshold != 0 {
|
||||
volumeThreshold = ptr.To(ms.Modules.CircuitBreaker.VolumeThreshold)
|
||||
}
|
||||
|
||||
var windowDuration *time.Duration
|
||||
if ms.Modules.CircuitBreaker.RollingWindow.Duration != 0 {
|
||||
windowDuration = ptr.To(ms.Modules.CircuitBreaker.RollingWindow.Duration)
|
||||
}
|
||||
|
||||
var trippedDuration *time.Duration
|
||||
if ms.Modules.CircuitBreaker.TrippedDuration.Duration != 0 {
|
||||
trippedDuration = ptr.To(ms.Modules.CircuitBreaker.TrippedDuration.Duration)
|
||||
}
|
||||
|
||||
tp.AddRuleOnHTTPRequest(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewCircuitBreakerAction(
|
||||
ms.Modules.CircuitBreaker.ErrorThresholdPercentage.AsApproximateFloat64(),
|
||||
volumeThreshold,
|
||||
windowDuration,
|
||||
trippedDuration,
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertModuleSetHeaders(_ context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.Headers == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ms.Modules.Headers.Request != nil {
|
||||
actions := []trafficpolicy.Action{}
|
||||
|
||||
if len(ms.Modules.Headers.Request.Remove) > 0 {
|
||||
actions = append(actions, trafficpolicy.NewRemoveHeadersAction(ms.Modules.Headers.Request.Remove))
|
||||
}
|
||||
|
||||
if len(ms.Modules.Headers.Request.Add) > 0 {
|
||||
actions = append(actions, trafficpolicy.NewAddHeadersAction(ms.Modules.Headers.Request.Add))
|
||||
}
|
||||
|
||||
if len(actions) > 0 {
|
||||
tp.AddRuleOnHTTPRequest(trafficpolicy.Rule{Actions: actions})
|
||||
}
|
||||
}
|
||||
|
||||
if ms.Modules.Headers.Response != nil {
|
||||
actions := []trafficpolicy.Action{}
|
||||
if len(ms.Modules.Headers.Response.Remove) > 0 {
|
||||
actions = append(actions, trafficpolicy.NewRemoveHeadersAction(ms.Modules.Headers.Response.Remove))
|
||||
}
|
||||
|
||||
if len(ms.Modules.Headers.Response.Add) > 0 {
|
||||
actions = append(actions, trafficpolicy.NewAddHeadersAction(ms.Modules.Headers.Response.Add))
|
||||
}
|
||||
|
||||
if len(actions) > 0 {
|
||||
tp.AddRuleOnHTTPResponse(trafficpolicy.Rule{Actions: actions})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func convertModuleSetWebhookVerification(secretResolver resolvers.SecretResolver) modulesetConverterFunc {
|
||||
return func(ctx context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.WebhookVerification == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
provider := ms.Modules.WebhookVerification.Provider
|
||||
secretRef := ms.Modules.WebhookVerification.SecretRef
|
||||
|
||||
// resolve the secretRef to a secret
|
||||
secret, err := secretResolver.GetSecret(ctx, ms.Namespace, secretRef.Name, secretRef.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tp.AddRuleOnHTTPRequest(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewWebhookVerificationAction(provider, secret),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func convertModuleSetSAML(_ context.Context, ms ingressv1alpha1.NgrokModuleSet, _ *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.SAML == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewErrModulesetNotConvertibleToTrafficPolicy("SAML module is not supported at this time")
|
||||
}
|
||||
|
||||
func convertModuleSetOIDC(secretResolver resolvers.SecretResolver) modulesetConverterFunc {
|
||||
return func(ctx context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.OIDC == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mod := ms.Modules.OIDC
|
||||
var config trafficpolicy.OIDCConfig
|
||||
|
||||
config.IssuerURL = mod.Issuer
|
||||
config.Scopes = mod.Scopes
|
||||
if mod.OptionsPassthrough {
|
||||
config.AllowCORSPreflight = &mod.OptionsPassthrough
|
||||
}
|
||||
|
||||
if mod.ClientID != "" {
|
||||
config.ClientID = &mod.ClientID
|
||||
}
|
||||
|
||||
if mod.ClientSecret.Name != "" && mod.ClientSecret.Key != "" {
|
||||
secret, err := secretResolver.GetSecret(ctx, ms.Namespace, mod.ClientSecret.Name, mod.ClientSecret.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.ClientSecret = &secret
|
||||
}
|
||||
|
||||
if mod.MaximumDuration.Duration > 0 {
|
||||
config.MaxSessionDuration = &mod.MaximumDuration.Duration
|
||||
}
|
||||
|
||||
if mod.InactivityTimeout.Duration > 0 {
|
||||
config.IdleSessionTimeout = &mod.InactivityTimeout.Duration
|
||||
}
|
||||
|
||||
tp.AddRuleOnHTTPRequest(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewOIDCAction(config),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func convertModuleSetOAuth(secretRsolver resolvers.SecretResolver) modulesetConverterFunc {
|
||||
return func(ctx context.Context, ms ingressv1alpha1.NgrokModuleSet, tp *trafficpolicy.TrafficPolicy) error {
|
||||
if ms.Modules.OAuth == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var config trafficpolicy.OAuthConfig
|
||||
var common ingressv1alpha1.OAuthProviderCommon
|
||||
|
||||
switch {
|
||||
case ms.Modules.OAuth.Amazon != nil:
|
||||
config.Provider = "amazon"
|
||||
common = ms.Modules.OAuth.Amazon.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Facebook != nil:
|
||||
config.Provider = "facebook"
|
||||
common = ms.Modules.OAuth.Facebook.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Github != nil:
|
||||
config.Provider = "github"
|
||||
common = ms.Modules.OAuth.Github.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Gitlab != nil:
|
||||
config.Provider = "gitlab"
|
||||
common = ms.Modules.OAuth.Gitlab.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Google != nil:
|
||||
config.Provider = "google"
|
||||
common = ms.Modules.OAuth.Google.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Microsoft != nil:
|
||||
config.Provider = "microsoft"
|
||||
common = ms.Modules.OAuth.Microsoft.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Linkedin != nil:
|
||||
config.Provider = "linkedin"
|
||||
common = ms.Modules.OAuth.Linkedin.OAuthProviderCommon
|
||||
case ms.Modules.OAuth.Twitch != nil:
|
||||
config.Provider = "twitch"
|
||||
common = ms.Modules.OAuth.Twitch.OAuthProviderCommon
|
||||
default:
|
||||
return errors.NewErrModulesetNotConvertibleToTrafficPolicy("Unable to determine OAuth provider")
|
||||
}
|
||||
|
||||
config.ClientID = common.ClientID
|
||||
if common.ClientSecret != nil {
|
||||
secret, err := secretRsolver.GetSecret(ctx, ms.Namespace, common.ClientSecret.Name, common.ClientSecret.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.ClientSecret = &secret
|
||||
}
|
||||
|
||||
if common.OptionsPassthrough {
|
||||
config.AllowCORSPreflight = &common.OptionsPassthrough
|
||||
}
|
||||
|
||||
config.Scopes = common.Scopes
|
||||
|
||||
if common.InactivityTimeout.Duration > 0 {
|
||||
config.IdleSessionTimeout = &common.InactivityTimeout.Duration
|
||||
}
|
||||
|
||||
if common.MaximumDuration.Duration > 0 {
|
||||
config.MaxSessionDuration = &common.MaximumDuration.Duration
|
||||
}
|
||||
|
||||
if common.AuthCheckInterval.Duration > 0 {
|
||||
config.UserinfoRefreshInterval = &common.AuthCheckInterval.Duration
|
||||
}
|
||||
|
||||
tp.AddRuleOnHTTPRequest(
|
||||
trafficpolicy.Rule{
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewOAuthAction(config),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
emailExpressions := []string{}
|
||||
if len(common.EmailAddresses) > 0 {
|
||||
emails := make([]string, len(common.EmailAddresses))
|
||||
for i, email := range common.EmailAddresses {
|
||||
emails[i] = fmt.Sprintf("'%s'", email)
|
||||
}
|
||||
cond := fmt.Sprintf("!actions.ngrok.oauth.identity.email in [%s]", strings.Join(emails, ","))
|
||||
emailExpressions = append(emailExpressions, cond)
|
||||
}
|
||||
|
||||
if len(common.EmailDomains) > 0 {
|
||||
domains := make([]string, len(common.EmailDomains))
|
||||
for i, domain := range common.EmailDomains {
|
||||
domains[i] = fmt.Sprintf("'%s'", domain)
|
||||
}
|
||||
cond := fmt.Sprintf("![%s].exists(d, actions.ngrok.oauth.identity.email.endsWith(d))", strings.Join(domains, ","))
|
||||
emailExpressions = append(emailExpressions, cond)
|
||||
}
|
||||
|
||||
// If there are email expressions, filtering to only allow certain emails or domains, return a 403 Forbidden
|
||||
// response for any requests that don't match
|
||||
if len(emailExpressions) > 0 {
|
||||
tp.AddRuleOnHTTPRequest(
|
||||
trafficpolicy.Rule{
|
||||
Expressions: []string{
|
||||
strings.Join(emailExpressions, " || "),
|
||||
},
|
||||
Actions: []trafficpolicy.Action{
|
||||
trafficpolicy.NewCustomResponseAction(403, "Forbidden", nil),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,9 @@ package util
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
"github.com/ngrok/ngrok-operator/internal/resolvers"
|
||||
"github.com/ngrok/ngrok-operator/internal/trafficpolicy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestIsLegacyPolicy(t *testing.T) {
|
||||
@@ -595,395 +588,3 @@ func newBaseTrafficPolicy(t *testing.T, enabled *bool) *trafficPolicyImpl {
|
||||
enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrafficPolicyFromModuleset(t *testing.T) {
|
||||
namespace := "default"
|
||||
secretName := "webhook-secret"
|
||||
secretKey := "my-key"
|
||||
secretValue := "shhhhhhhhh"
|
||||
|
||||
secretResolver := resolvers.NewStaticSecretResolver()
|
||||
secretResolver.AddSecret(namespace, secretName, secretKey, secretValue)
|
||||
|
||||
ipPolicyResolver := resolvers.NewStaticIPPolicyResolver()
|
||||
ipPolicyResolver.AddIPPolicy(namespace, "my-ip-policy", "ipp_123456789012345678901234567")
|
||||
|
||||
msObjectMeta := metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: "my-custom-moduleset",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
moduleset *ingressv1alpha1.NgrokModuleSet
|
||||
tp *trafficpolicy.TrafficPolicy
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "nil moduleset",
|
||||
moduleset: nil,
|
||||
tp: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "moduleset with ngrok managed OAuth",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
OAuth: &ingressv1alpha1.EndpointOAuth{
|
||||
Google: &ingressv1alpha1.EndpointOAuthGoogle{
|
||||
OAuthProviderCommon: ingressv1alpha1.OAuthProviderCommon{
|
||||
EmailAddresses: []string{"testuser@example.com"},
|
||||
EmailDomains: []string{"ngrok.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_OAuth,
|
||||
Config: map[string]string{
|
||||
"provider": "google",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expressions: []string{
|
||||
"!actions.ngrok.oauth.identity.email in ['testuser@example.com'] || !['ngrok.com'].exists(d, actions.ngrok.oauth.identity.email.endsWith(d))",
|
||||
},
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_CustomResponse,
|
||||
Config: map[string]interface{}{
|
||||
"status_code": 403,
|
||||
"content": "Forbidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "moduleset with custom OAuth",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
OAuth: &ingressv1alpha1.EndpointOAuth{
|
||||
Google: &ingressv1alpha1.EndpointOAuthGoogle{
|
||||
OAuthProviderCommon: ingressv1alpha1.OAuthProviderCommon{
|
||||
ClientID: ptr.To("my-client-id"),
|
||||
ClientSecret: &ingressv1alpha1.SecretKeyRef{Name: secretName, Key: secretKey},
|
||||
EmailAddresses: []string{"testuser@example.com"},
|
||||
EmailDomains: []string{"ngrok.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_OAuth,
|
||||
Config: map[string]string{
|
||||
"provider": "google",
|
||||
"client_id": "my-client-id",
|
||||
"client_secret": secretValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expressions: []string{
|
||||
"!actions.ngrok.oauth.identity.email in ['testuser@example.com'] || !['ngrok.com'].exists(d, actions.ngrok.oauth.identity.email.endsWith(d))",
|
||||
},
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_CustomResponse,
|
||||
Config: map[string]interface{}{
|
||||
"status_code": 403,
|
||||
"content": "Forbidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "moduleset with saml",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
SAML: &ingressv1alpha1.EndpointSAML{
|
||||
CookiePrefix: "something",
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: nil,
|
||||
err: errors.NewErrModulesetNotConvertibleToTrafficPolicy("SAML module is not supported at this time"),
|
||||
},
|
||||
{
|
||||
name: "moduleset with oidc",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
OIDC: &ingressv1alpha1.EndpointOIDC{
|
||||
Issuer: "https://acme.com/oauth/v2/oauth-anonymous",
|
||||
ClientID: "12345",
|
||||
ClientSecret: ingressv1alpha1.SecretKeyRef{
|
||||
Name: secretName,
|
||||
Key: secretKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_OIDC,
|
||||
Config: map[string]string{
|
||||
"issuer_url": "https://acme.com/oauth/v2/oauth-anonymous",
|
||||
"client_id": "12345",
|
||||
"client_secret": secretValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "moduleset with webhook verification",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
WebhookVerification: &ingressv1alpha1.EndpointWebhookVerification{
|
||||
Provider: "github",
|
||||
SecretRef: &ingressv1alpha1.SecretKeyRef{
|
||||
Name: secretName,
|
||||
Key: secretKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_VerifyWebhook,
|
||||
Config: map[string]string{
|
||||
"provider": "github",
|
||||
"secret": secretValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "moduleset with headers",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Headers: &ingressv1alpha1.EndpointHeaders{
|
||||
Request: &ingressv1alpha1.EndpointRequestHeaders{
|
||||
Add: map[string]string{
|
||||
"X-Header-1": "value1",
|
||||
},
|
||||
Remove: []string{"X-Header-2"},
|
||||
},
|
||||
Response: &ingressv1alpha1.EndpointResponseHeaders{
|
||||
Add: map[string]string{
|
||||
"X-Header-3": "value3",
|
||||
},
|
||||
Remove: []string{"X-Header-4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_RemoveHeaders,
|
||||
Config: map[string]interface{}{
|
||||
"headers": []string{"X-Header-2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: trafficpolicy.ActionType_AddHeaders,
|
||||
Config: map[string]interface{}{
|
||||
"headers": map[string]string{
|
||||
"X-Header-1": "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OnHTTPResponse: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_RemoveHeaders,
|
||||
Config: map[string]interface{}{
|
||||
"headers": []string{"X-Header-4"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: trafficpolicy.ActionType_AddHeaders,
|
||||
Config: map[string]interface{}{
|
||||
"headers": map[string]string{
|
||||
"X-Header-3": "value3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "multiple modules",
|
||||
moduleset: &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: msObjectMeta,
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: true,
|
||||
},
|
||||
CircuitBreaker: &ingressv1alpha1.EndpointCircuitBreaker{
|
||||
ErrorThresholdPercentage: *resource.NewMilliQuantity(100, resource.DecimalSI),
|
||||
TrippedDuration: metav1.Duration{Duration: 2 * time.Minute},
|
||||
RollingWindow: metav1.Duration{Duration: 1 * time.Minute},
|
||||
},
|
||||
IPRestriction: &ingressv1alpha1.EndpointIPPolicy{
|
||||
IPPolicies: []string{"my-ip-policy", "ipp_123456789012345678901234568"},
|
||||
},
|
||||
OAuth: &ingressv1alpha1.EndpointOAuth{
|
||||
Google: &ingressv1alpha1.EndpointOAuthGoogle{
|
||||
OAuthProviderCommon: ingressv1alpha1.OAuthProviderCommon{
|
||||
EmailDomains: []string{"ngrok.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLSTermination: &ingressv1alpha1.EndpointTLSTermination{
|
||||
TerminateAt: "edge",
|
||||
MinVersion: ptr.To("1.2"),
|
||||
},
|
||||
MutualTLS: &ingressv1alpha1.EndpointMutualTLS{
|
||||
CertificateAuthorities: []string{"ca1", "ca2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
tp: &trafficpolicy.TrafficPolicy{
|
||||
OnTCPConnect: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_RestrictIPs,
|
||||
Config: map[string]interface{}{
|
||||
"ip_policies": []string{"ipp_123456789012345678901234567", "ipp_123456789012345678901234568"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_TerminateTLS,
|
||||
Config: map[string]interface{}{
|
||||
"min_version": "1.2",
|
||||
"mutual_tls_certificate_authorities": []string{"ca1", "ca2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OnHTTPRequest: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_OAuth,
|
||||
Config: map[string]interface{}{
|
||||
"provider": "google",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Expressions: []string{
|
||||
"!['ngrok.com'].exists(d, actions.ngrok.oauth.identity.email.endsWith(d))",
|
||||
},
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_CustomResponse,
|
||||
Config: map[string]interface{}{
|
||||
"status_code": 403,
|
||||
"content": "Forbidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_CircuitBreaker,
|
||||
Config: map[string]interface{}{
|
||||
"error_threshold": 0.1,
|
||||
"window_duration": 1 * time.Minute,
|
||||
"tripped_duration": 2 * time.Minute,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OnHTTPResponse: []trafficpolicy.Rule{
|
||||
{
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: trafficpolicy.ActionType_CompressResponse,
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tp, err := NewTrafficPolicyFromModuleset(t.Context(), tt.moduleset, secretResolver, ipPolicyResolver)
|
||||
assert.Equal(t, tt.err, err)
|
||||
|
||||
jsonTP, err := json.Marshal(tp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jsonExpectedTP, err := json.Marshal(tt.tp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.JSONEq(t, string(jsonExpectedTP), string(jsonTP))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2
-2920
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,8 @@ package managerdriver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
"golang.org/x/sync/errgroup"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -16,8 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// ingressToDomains constructs domains for edges/endpoints from an input ingress
|
||||
func ingressToDomains(log logr.Logger, in *netv1.Ingress, newDomainMetadata string, existingDomains map[string]ingressv1alpha1.Domain) (edgeDomains map[string]ingressv1alpha1.Domain, endpointDomains map[string]ingressv1alpha1.Domain) {
|
||||
edgeDomains = make(map[string]ingressv1alpha1.Domain)
|
||||
func ingressToDomains(in *netv1.Ingress, newDomainMetadata string, existingDomains map[string]ingressv1alpha1.Domain) (endpointDomains map[string]ingressv1alpha1.Domain) {
|
||||
endpointDomains = make(map[string]ingressv1alpha1.Domain)
|
||||
|
||||
for _, rule := range in.Spec.Rules {
|
||||
@@ -41,24 +37,13 @@ func ingressToDomains(log logr.Logger, in *netv1.Ingress, newDomainMetadata stri
|
||||
},
|
||||
}
|
||||
domain.Spec.Metadata = newDomainMetadata
|
||||
|
||||
// Check the annotation to see if an edge or endpoint is desired from this ingress resource
|
||||
useEdges, err := annotations.ExtractUseEdges(in)
|
||||
if err != nil {
|
||||
log.Error(err, fmt.Sprintf("failed to check %q annotation. defaulting to using endpoints", annotations.MappingStrategyAnnotation))
|
||||
}
|
||||
if !useEdges {
|
||||
endpointDomains[domainName] = domain
|
||||
} else {
|
||||
edgeDomains[domainName] = domain
|
||||
}
|
||||
endpointDomains[domainName] = domain
|
||||
}
|
||||
return edgeDomains, endpointDomains
|
||||
return endpointDomains
|
||||
}
|
||||
|
||||
// gatewayToDomains constructs domains for edges/endpoints from an input Gateway
|
||||
func gatewayToDomains(log logr.Logger, in *gatewayv1.Gateway, newDomainMetadata string, existingDomains map[string]ingressv1alpha1.Domain) (edgeDomains map[string]ingressv1alpha1.Domain, endpointDomains map[string]ingressv1alpha1.Domain) {
|
||||
edgeDomains = make(map[string]ingressv1alpha1.Domain)
|
||||
func gatewayToDomains(in *gatewayv1.Gateway, newDomainMetadata string, existingDomains map[string]ingressv1alpha1.Domain) (endpointDomains map[string]ingressv1alpha1.Domain) {
|
||||
endpointDomains = make(map[string]ingressv1alpha1.Domain)
|
||||
for _, listener := range in.Spec.Listeners {
|
||||
if listener.Hostname == nil {
|
||||
@@ -86,18 +71,10 @@ func gatewayToDomains(log logr.Logger, in *gatewayv1.Gateway, newDomainMetadata
|
||||
}
|
||||
domain.Spec.Metadata = newDomainMetadata
|
||||
|
||||
// Check the annotation to see if an edge or endpoint is desired from this ingress resource
|
||||
useEdges, err := annotations.ExtractUseEdges(in)
|
||||
if err != nil {
|
||||
log.Error(err, fmt.Sprintf("failed to check %q annotation. defaulting to using endpoints", annotations.MappingStrategyAnnotation))
|
||||
}
|
||||
if !useEdges {
|
||||
endpointDomains[domainName] = domain
|
||||
} else {
|
||||
edgeDomains[domainName] = domain
|
||||
}
|
||||
endpointDomains[domainName] = domain
|
||||
|
||||
}
|
||||
return edgeDomains, endpointDomains
|
||||
return endpointDomains
|
||||
}
|
||||
|
||||
// applyDomains takes a set of the desired domains and current domains, creates any missing desired domains, and updated existing domains if needed
|
||||
@@ -145,11 +122,6 @@ type domainSet struct {
|
||||
endpointIngressDomains map[string]ingressv1alpha1.Domain
|
||||
endpointGatewayDomains map[string]ingressv1alpha1.Domain
|
||||
|
||||
// The following two domain maps track domains for ingress/gateway resources that have opted to
|
||||
// use edges
|
||||
edgeIngressDomains map[string]ingressv1alpha1.Domain
|
||||
edgeGatewayDomains map[string]ingressv1alpha1.Domain
|
||||
|
||||
// totalDomains tracks all domains regardless of source
|
||||
totalDomains map[string]ingressv1alpha1.Domain
|
||||
}
|
||||
@@ -158,19 +130,13 @@ func (d *Driver) calculateDomainSet() *domainSet {
|
||||
ret := &domainSet{
|
||||
endpointIngressDomains: make(map[string]ingressv1alpha1.Domain),
|
||||
endpointGatewayDomains: make(map[string]ingressv1alpha1.Domain),
|
||||
edgeIngressDomains: make(map[string]ingressv1alpha1.Domain),
|
||||
edgeGatewayDomains: make(map[string]ingressv1alpha1.Domain),
|
||||
totalDomains: make(map[string]ingressv1alpha1.Domain),
|
||||
}
|
||||
|
||||
// Calculate domains from ingress resources
|
||||
ingresses := d.store.ListNgrokIngressesV1()
|
||||
for _, ingress := range ingresses {
|
||||
edgeDomains, endpointDomains := ingressToDomains(d.log, ingress, d.ingressNgrokMetadata, nil)
|
||||
for key, val := range edgeDomains {
|
||||
ret.totalDomains[key] = val
|
||||
ret.edgeIngressDomains[key] = val
|
||||
}
|
||||
endpointDomains := ingressToDomains(ingress, d.ingressNgrokMetadata, nil)
|
||||
for key, val := range endpointDomains {
|
||||
ret.totalDomains[key] = val
|
||||
ret.endpointIngressDomains[key] = val
|
||||
@@ -180,11 +146,7 @@ func (d *Driver) calculateDomainSet() *domainSet {
|
||||
// Calculate domains from gateway resources
|
||||
gateways := d.store.ListGateways()
|
||||
for _, gateway := range gateways {
|
||||
edgeDomains, endpointDomains := gatewayToDomains(d.log, gateway, d.gatewayNgrokMetadata, ret.totalDomains)
|
||||
for key, val := range edgeDomains {
|
||||
ret.totalDomains[key] = val
|
||||
ret.edgeGatewayDomains[key] = val
|
||||
}
|
||||
endpointDomains := gatewayToDomains(gateway, d.gatewayNgrokMetadata, ret.totalDomains)
|
||||
for key, val := range endpointDomains {
|
||||
ret.totalDomains[key] = val
|
||||
ret.endpointGatewayDomains[key] = val
|
||||
|
||||
@@ -241,18 +241,6 @@ func listObjectsForType(ctx context.Context, client client.Reader, v interface{}
|
||||
domains := &ingressv1alpha1.DomainList{}
|
||||
err := client.List(ctx, domains)
|
||||
return util.ToClientObjects(domains.Items), err
|
||||
case *ingressv1alpha1.HTTPSEdge:
|
||||
edges := &ingressv1alpha1.HTTPSEdgeList{}
|
||||
err := client.List(ctx, edges)
|
||||
return util.ToClientObjects(edges.Items), err
|
||||
case *ingressv1alpha1.Tunnel:
|
||||
tunnels := &ingressv1alpha1.TunnelList{}
|
||||
err := client.List(ctx, tunnels)
|
||||
return util.ToClientObjects(tunnels.Items), err
|
||||
case *ingressv1alpha1.NgrokModuleSet:
|
||||
modules := &ingressv1alpha1.NgrokModuleSetList{}
|
||||
err := client.List(ctx, modules)
|
||||
return util.ToClientObjects(modules.Items), err
|
||||
case *ngrokv1alpha1.NgrokTrafficPolicy:
|
||||
policies := &ngrokv1alpha1.NgrokTrafficPolicyList{}
|
||||
err := client.List(ctx, policies)
|
||||
@@ -301,9 +289,6 @@ func (d *Driver) Seed(ctx context.Context, c client.Reader) error {
|
||||
&corev1.ConfigMap{},
|
||||
// CRDs
|
||||
&ingressv1alpha1.Domain{},
|
||||
&ingressv1alpha1.HTTPSEdge{},
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
&ingressv1alpha1.NgrokModuleSet{},
|
||||
&ngrokv1alpha1.NgrokTrafficPolicy{},
|
||||
&ngrokv1alpha1.AgentEndpoint{},
|
||||
&ngrokv1alpha1.CloudEndpoint{},
|
||||
@@ -578,8 +563,6 @@ func (d *Driver) Sync(ctx context.Context, c client.Client) error {
|
||||
|
||||
// TODO (Alice): move domains, edges, tunnels to translator
|
||||
domains := d.calculateDomainSet()
|
||||
desiredEdges := d.calculateHTTPSEdges(domains)
|
||||
desiredTunnels := d.calculateTunnels()
|
||||
|
||||
translator := NewTranslator(
|
||||
d.log,
|
||||
@@ -592,25 +575,9 @@ func (d *Driver) Sync(ctx context.Context, c client.Client) error {
|
||||
)
|
||||
translationResult := translator.Translate()
|
||||
|
||||
currEdges := &ingressv1alpha1.HTTPSEdgeList{}
|
||||
currTunnels := &ingressv1alpha1.TunnelList{}
|
||||
currAgentEndpoints := &ngrokv1alpha1.AgentEndpointList{}
|
||||
currCloudEndpoints := &ngrokv1alpha1.CloudEndpointList{}
|
||||
|
||||
if err := c.List(ctx, currEdges, client.MatchingLabels{
|
||||
labelControllerNamespace: d.managerName.Namespace,
|
||||
labelControllerName: d.managerName.Name,
|
||||
}); err != nil {
|
||||
d.log.Error(err, "error listing edges")
|
||||
return err
|
||||
}
|
||||
if err := c.List(ctx, currTunnels, client.MatchingLabels{
|
||||
labelControllerNamespace: d.managerName.Namespace,
|
||||
labelControllerName: d.managerName.Name,
|
||||
}); err != nil {
|
||||
d.log.Error(err, "error listing tunnels")
|
||||
return err
|
||||
}
|
||||
if err := c.List(ctx, currAgentEndpoints, client.MatchingLabels{
|
||||
labelControllerNamespace: d.managerName.Namespace,
|
||||
labelControllerName: d.managerName.Name,
|
||||
@@ -630,10 +597,6 @@ func (d *Driver) Sync(ctx context.Context, c client.Client) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.applyHTTPSEdges(ctx, c, desiredEdges, currEdges.Items); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.applyAgentEndpoints(ctx, c, translationResult.AgentEndpoints, currAgentEndpoints.Items); err != nil {
|
||||
d.log.Error(err, "applying agent endpoints")
|
||||
return err
|
||||
@@ -644,10 +607,6 @@ func (d *Driver) Sync(ctx context.Context, c client.Client) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.applyTunnels(ctx, c, desiredTunnels, currTunnels.Items); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update Statuses
|
||||
return d.updateStatuses(ctx, c)
|
||||
}
|
||||
@@ -848,37 +807,6 @@ func (d *Driver) updateHTTPRouteStatuses(_ context.Context, _ client.Client) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTrafficPolicyJSON retrieves the traffic policy for an ingress and falls back to the modSet policy if it doesn't exist.
|
||||
func (d *Driver) getTrafficPolicyJSON(ingress *netv1.Ingress, modSet *ingressv1alpha1.NgrokModuleSet) (json.RawMessage, error) {
|
||||
var err error
|
||||
var policyJSON json.RawMessage
|
||||
|
||||
trafficPolicy, err := getNgrokTrafficPolicyForIngress(ingress, d.store)
|
||||
|
||||
if err != nil {
|
||||
d.log.Error(err, "error getting ngrok traffic policy for ingress", "ingress", ingress)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if modSet.IsEmpty() {
|
||||
if trafficPolicy != nil {
|
||||
return trafficPolicy.Spec.Policy, nil
|
||||
}
|
||||
return policyJSON, nil
|
||||
}
|
||||
|
||||
if modSet.Modules.Policy != nil && trafficPolicy != nil {
|
||||
return nil, fmt.Errorf("cannot have both a traffic policy and a moduleset policy on ingress: %s", ingress.Name)
|
||||
}
|
||||
|
||||
if policyJSON, err = json.Marshal(modSet.Modules.Policy); err != nil {
|
||||
d.log.Error(err, "cannot convert module-set policy json", "ingress", ingress, "Policy", modSet.Modules.Policy)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policyJSON, nil
|
||||
}
|
||||
|
||||
func (d *Driver) createEndpointPolicyForGateway(rule *gatewayv1.HTTPRouteRule, namespace string) (json.RawMessage, error) {
|
||||
pathPrefixMatches := []string{}
|
||||
|
||||
@@ -1309,41 +1237,12 @@ func (d *Driver) handleRequestRedirectFilter(filter *gatewayv1.HTTPRequestRedire
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) findBackendRefServicePort(backendRef gatewayv1.BackendRef, namespace string) (*corev1.Service, *corev1.ServicePort, error) {
|
||||
service, err := d.store.GetServiceV1(string(backendRef.Name), namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
servicePort, err := findBackendRefServicesPort(d.log, service, &backendRef)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return service, servicePort, nil
|
||||
}
|
||||
|
||||
func (d *Driver) findBackendServicePort(backendSvc netv1.IngressServiceBackend, namespace string) (*corev1.Service, *corev1.ServicePort, error) {
|
||||
service, err := d.store.GetServiceV1(backendSvc.Name, namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
servicePort, err := findServicesPort(d.log, service, backendSvc.Port)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return service, servicePort, nil
|
||||
}
|
||||
|
||||
// MigrateKubernetesIngressControllerLabelsToNgrokOperator migrates the labels from the old Kubernetes Ingress Controller to the new ngrok operator labels
|
||||
// so that the ngrok operator can take over management of items previously managed by the Kubernetes Ingress Controller.
|
||||
// TODO: Delete this function after users have migrated from the ngrok Kubernetes Ingress Controller to the ngrok Operator.
|
||||
func (d *Driver) MigrateKubernetesIngressControllerLabelsToNgrokOperator(ctx context.Context, k8sClient client.Client) error {
|
||||
typesToMigrate := []interface{}{
|
||||
&ingressv1alpha1.Domain{},
|
||||
&ingressv1alpha1.Tunnel{},
|
||||
&ingressv1alpha1.HTTPSEdge{},
|
||||
}
|
||||
|
||||
for _, t := range typesToMigrate {
|
||||
|
||||
@@ -69,9 +69,9 @@ var _ = Describe("Driver", func() {
|
||||
ic2 := testutils.NewTestIngressClass("test-ingress-class-2", true, true)
|
||||
d1 := testutils.NewDomainV1("test-domain.com", "test-namespace")
|
||||
d2 := testutils.NewDomainV1("test-domain-2.com", "test-namespace")
|
||||
e1 := testutils.NewHTTPSEdge("test-edge", "test-namespace")
|
||||
e2 := testutils.NewHTTPSEdge("test-edge-2", "test-namespace")
|
||||
obs := []runtime.Object{ic1, ic2, i1, i2, d1, d2, &e1, &e2}
|
||||
c1 := testutils.NewCloudEndpoint()
|
||||
c2 := testutils.NewCloudEndpoint()
|
||||
obs := []runtime.Object{ic1, ic2, i1, i2, d1, d2, c1, c2}
|
||||
|
||||
c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obs...).Build()
|
||||
err := driver.Seed(GinkgoT().Context(), c)
|
||||
@@ -119,19 +119,19 @@ var _ = Describe("Driver", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(domains.Items).To(HaveLen(0))
|
||||
|
||||
edges := &ingressv1alpha1.HTTPSEdgeList{}
|
||||
err = c.List(GinkgoT().Context(), &ingressv1alpha1.HTTPSEdgeList{})
|
||||
agentendpoints := &ngrokv1alpha1.AgentEndpointList{}
|
||||
err = c.List(GinkgoT().Context(), &ngrokv1alpha1.AgentEndpointList{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(edges.Items).To(HaveLen(0))
|
||||
Expect(agentendpoints.Items).To(HaveLen(0))
|
||||
|
||||
tunnels := &ingressv1alpha1.TunnelList{}
|
||||
err = c.List(GinkgoT().Context(), &ingressv1alpha1.TunnelList{})
|
||||
cloudendpoints := &ngrokv1alpha1.CloudEndpointList{}
|
||||
err = c.List(GinkgoT().Context(), &ngrokv1alpha1.CloudEndpointList{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(tunnels.Items).To(HaveLen(0))
|
||||
Expect(cloudendpoints.Items).To(HaveLen(0))
|
||||
})
|
||||
})
|
||||
Context("When there are just edge ingresses and edges need to be created", func() {
|
||||
It("Should create the CRDs", func() {
|
||||
Context("When the old edges mapping-strategy is used, it defaults to endpoint", func() {
|
||||
It("Should create AgentEndpoints", func() {
|
||||
i1 := testutils.NewTestIngressV1("test-ingress", "test-namespace")
|
||||
if i1.Annotations == nil {
|
||||
i1.Annotations = map[string]string{}
|
||||
@@ -167,25 +167,12 @@ var _ = Describe("Driver", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(foundDomain.Spec.Domain).To(Equal(i1.Spec.Rules[0].Host))
|
||||
|
||||
foundEdges := &ingressv1alpha1.HTTPSEdgeList{}
|
||||
err = c.List(GinkgoT().Context(), foundEdges)
|
||||
agentEndpoints := &ngrokv1alpha1.AgentEndpointList{}
|
||||
err = c.List(GinkgoT().Context(), agentEndpoints, client.InNamespace("test-namespace"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(foundEdges.Items)).To(Equal(1))
|
||||
foundEdge := foundEdges.Items[0]
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(foundEdge.Spec.Hostports[0]).To(ContainSubstring(i1.Spec.Rules[0].Host))
|
||||
Expect(foundEdge.Namespace).To(Equal("test-namespace"))
|
||||
Expect(foundEdge.Name).To(HavePrefix("example-com-"))
|
||||
Expect(foundEdge.Labels["k8s.ngrok.com/controller-name"]).To(Equal(defaultManagerName))
|
||||
|
||||
foundTunnels := &ingressv1alpha1.TunnelList{}
|
||||
err = c.List(GinkgoT().Context(), foundTunnels)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(foundTunnels.Items)).To(Equal(1))
|
||||
foundTunnel := foundTunnels.Items[0]
|
||||
Expect(foundTunnel.Namespace).To(Equal("test-namespace"))
|
||||
Expect(foundTunnel.Name).To(HavePrefix("example-80-"))
|
||||
Expect(foundTunnel.Labels["k8s.ngrok.com/controller-name"]).To(Equal(defaultManagerName))
|
||||
Expect(len(agentEndpoints.Items)).To(Equal(1))
|
||||
agentEndpoint := agentEndpoints.Items[0]
|
||||
Expect(agentEndpoint.Spec.URL).To(Equal("https://" + i1.Spec.Rules[0].Host))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -196,7 +183,8 @@ var _ = Describe("Driver", func() {
|
||||
ingress *netv1.Ingress
|
||||
c client.WithWatch
|
||||
namespace = "app-proto-namespace"
|
||||
foundTunnels *ingressv1alpha1.TunnelList
|
||||
agentEndpoints *ngrokv1alpha1.AgentEndpointList
|
||||
cloudEndpoints *ngrokv1alpha1.CloudEndpointList
|
||||
ic = testutils.NewTestIngressClass("app-proto-ingress-class", true, true)
|
||||
setIngressTargetService = func(i *netv1.Ingress, s *v1.Service) {
|
||||
// Modify the ingress to include the service
|
||||
@@ -286,9 +274,14 @@ var _ = Describe("Driver", func() {
|
||||
Expect(driver.Seed(GinkgoT().Context(), c)).To(BeNil())
|
||||
Expect(driver.Sync(GinkgoT().Context(), c)).To(BeNil())
|
||||
|
||||
// Find the tunnels in this namespace
|
||||
foundTunnels = &ingressv1alpha1.TunnelList{}
|
||||
err := c.List(GinkgoT().Context(), foundTunnels, client.InNamespace(namespace))
|
||||
// Find the agent endpoints in this namespace
|
||||
agentEndpoints = &ngrokv1alpha1.AgentEndpointList{}
|
||||
err := c.List(GinkgoT().Context(), agentEndpoints, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Find the cloud endpoints in this namespace
|
||||
cloudEndpoints = &ngrokv1alpha1.CloudEndpointList{}
|
||||
err = c.List(GinkgoT().Context(), cloudEndpoints, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
@@ -301,14 +294,14 @@ var _ = Describe("Driver", func() {
|
||||
})
|
||||
|
||||
It("Should ignore the unknown appProtocol", func() {
|
||||
// We expect one tunnel to be created
|
||||
Expect(len(foundTunnels.Items)).To(Equal(1))
|
||||
// We expect one agent endpoint to be created
|
||||
Expect(len(agentEndpoints.Items)).To(Equal(1))
|
||||
|
||||
By("Creating a tunnel with no appProtocol and the correct upstream")
|
||||
foundTunnel := foundTunnels.Items[0]
|
||||
Expect(foundTunnel.Spec.AppProtocol).To(BeNil())
|
||||
Expect(foundTunnel.Spec.ForwardsTo).To(Equal("http-service.app-proto-namespace.svc.cluster.local:80"))
|
||||
Expect(foundTunnel.Spec.BackendConfig.Protocol).To(Equal("HTTP"))
|
||||
By("Creating an agent endpoint with no appProtocol and the correct upstream")
|
||||
foundAgentEndpoint := agentEndpoints.Items[0]
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.ProxyProtocolVersion).To(BeNil())
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.URL).To(Equal("http://http-service.app-proto-namespace:80"))
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.Protocol).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -320,15 +313,14 @@ var _ = Describe("Driver", func() {
|
||||
setIngressTargetService(ingress, httpService)
|
||||
})
|
||||
|
||||
It("Should create a tunnel with appProtocol http1", func() {
|
||||
// We expect one tunnel to be created
|
||||
Expect(len(foundTunnels.Items)).To(Equal(1))
|
||||
It("Should create an AgentEndpoint with appProtocol http1", func() {
|
||||
// We expect one AgentEndpoint to be created
|
||||
Expect(len(agentEndpoints.Items)).To(Equal(1))
|
||||
|
||||
By("Creating a tunnel with appProtocol http1")
|
||||
foundTunnel := foundTunnels.Items[0]
|
||||
Expect(foundTunnel.Spec.AppProtocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP1)))
|
||||
Expect(foundTunnel.Spec.ForwardsTo).To(Equal("http-service.app-proto-namespace.svc.cluster.local:80"))
|
||||
Expect(foundTunnel.Spec.BackendConfig.Protocol).To(Equal("HTTP"))
|
||||
By("Creating an AgentEndpoint with appProtocol http1")
|
||||
foundAgentEndpoint := agentEndpoints.Items[0]
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.Protocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP1)))
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.URL).To(Equal("http://http-service.app-proto-namespace:80"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -341,15 +333,14 @@ var _ = Describe("Driver", func() {
|
||||
setIngressTargetService(ingress, httpsService)
|
||||
})
|
||||
|
||||
It("Should create a tunnel with appProtocol http2", func() {
|
||||
// We expect one tunnel to be created
|
||||
Expect(len(foundTunnels.Items)).To(Equal(1))
|
||||
It("Should create an AgentEndpoint with an upstream protocol of http2", func() {
|
||||
// We expect one AgentEndpoint to be created
|
||||
Expect(len(agentEndpoints.Items)).To(Equal(1))
|
||||
|
||||
By("Creating a tunnel with appProtocol http2")
|
||||
foundTunnel := foundTunnels.Items[0]
|
||||
Expect(foundTunnel.Spec.AppProtocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP2)))
|
||||
Expect(foundTunnel.Spec.ForwardsTo).To(Equal("https-service.app-proto-namespace.svc.cluster.local:443"))
|
||||
Expect(foundTunnel.Spec.BackendConfig.Protocol).To(Equal("HTTPS"))
|
||||
By("Creating an AgentEndpoint with appProtocol http2")
|
||||
foundAgentEndpoint := agentEndpoints.Items[0]
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.Protocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP2)))
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.URL).To(Equal("https://https-service.app-proto-namespace:443"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -362,25 +353,14 @@ var _ = Describe("Driver", func() {
|
||||
setIngressTargetService(ingress, httpsService)
|
||||
})
|
||||
|
||||
It("Should create a tunnel with appProtocol http2", func() {
|
||||
// We expect one tunnel to be created
|
||||
Expect(len(foundTunnels.Items)).To(Equal(1))
|
||||
It("Should create an AgentEndpoint with appProtocol http2", func() {
|
||||
// We expect one AgentEndpoint to be created
|
||||
Expect(len(agentEndpoints.Items)).To(Equal(1))
|
||||
|
||||
By("Creating a tunnel with appProtocol http2")
|
||||
foundTunnel := foundTunnels.Items[0]
|
||||
Expect(foundTunnel.Spec.AppProtocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP2)))
|
||||
Expect(foundTunnel.Spec.ForwardsTo).To(Equal("https-service.app-proto-namespace.svc.cluster.local:443"))
|
||||
Expect(foundTunnel.Spec.BackendConfig.Protocol).To(Equal("HTTPS"))
|
||||
})
|
||||
})
|
||||
|
||||
When("The Ingress does not specify mapping-strategy: edges", func() {
|
||||
BeforeEach(func() {
|
||||
ingress.Annotations = map[string]string{} // Removing the edges annotation should cause the translation to be endpoints instead.
|
||||
})
|
||||
|
||||
It("Should not have any tunnels", func() {
|
||||
Expect(len(foundTunnels.Items)).To(Equal(0))
|
||||
By("Creating an AgentEndpoint with appProtocol http2")
|
||||
foundAgentEndpoint := agentEndpoints.Items[0]
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.Protocol).To(Equal(ptr.To(common.ApplicationProtocol_HTTP2)))
|
||||
Expect(foundAgentEndpoint.Spec.Upstream.URL).To(Equal("https://https-service.app-proto-namespace:443"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -392,7 +372,6 @@ var _ = Describe("Driver", func() {
|
||||
httpService *v1.Service
|
||||
ingress *netv1.Ingress
|
||||
trafficPolicy *ngrokv1alpha1.NgrokTrafficPolicy
|
||||
foundHTTPEdges *ingressv1alpha1.HTTPSEdgeList
|
||||
foundAgentEndpoints *ngrokv1alpha1.AgentEndpointList
|
||||
foundCloudEndpoints *ngrokv1alpha1.CloudEndpointList
|
||||
ic = testutils.NewTestIngressClass("edge-tp-ingress-class", true, true)
|
||||
@@ -481,10 +460,6 @@ var _ = Describe("Driver", func() {
|
||||
Expect(driver.Seed(GinkgoT().Context(), c)).To(Succeed())
|
||||
Expect(driver.Sync(GinkgoT().Context(), c)).To(Succeed())
|
||||
|
||||
// Find the HTTPSEdges in this namespace
|
||||
foundHTTPEdges = &ingressv1alpha1.HTTPSEdgeList{}
|
||||
Expect(c.List(GinkgoT().Context(), foundHTTPEdges, client.InNamespace(namespace))).To(Succeed())
|
||||
|
||||
// Find the AgentEndpoints in this namespace
|
||||
foundAgentEndpoints = &ngrokv1alpha1.AgentEndpointList{}
|
||||
Expect(c.List(GinkgoT().Context(), foundAgentEndpoints, client.InNamespace(namespace))).To(Succeed())
|
||||
@@ -494,16 +469,15 @@ var _ = Describe("Driver", func() {
|
||||
Expect(c.List(GinkgoT().Context(), foundCloudEndpoints, client.InNamespace(namespace))).To(Succeed())
|
||||
})
|
||||
|
||||
When("The the ingress is using the edges mapping strategy", func() {
|
||||
When("The the ingress is using the old edges mapping strategy", func() {
|
||||
BeforeEach(func() {
|
||||
controller.AddAnnotations(ingress, map[string]string{
|
||||
"k8s.ngrok.com/mapping-strategy": "edges",
|
||||
})
|
||||
})
|
||||
|
||||
It("Should only create an HTTPSEdge", func() {
|
||||
Expect(len(foundHTTPEdges.Items)).To(Equal(1))
|
||||
Expect(len(foundAgentEndpoints.Items)).To(Equal(0))
|
||||
It("Should create an AgentEndpoint", func() {
|
||||
Expect(len(foundAgentEndpoints.Items)).To(Equal(1))
|
||||
Expect(len(foundCloudEndpoints.Items)).To(Equal(0))
|
||||
})
|
||||
|
||||
@@ -515,13 +489,24 @@ var _ = Describe("Driver", func() {
|
||||
})
|
||||
|
||||
It("Should use the traffic policy", func() {
|
||||
edge := foundHTTPEdges.Items[0]
|
||||
Expect(edge.Spec.Routes).To(HaveLen(1))
|
||||
foundAgentEndpoint := foundAgentEndpoints.Items[0]
|
||||
|
||||
By("Having the traffic policy on all routes on the edge")
|
||||
for _, route := range edge.Spec.Routes {
|
||||
Expect(route.Policy).To(Equal(trafficPolicy.Spec.Policy))
|
||||
}
|
||||
By("Having the traffic policy on the AgentEndpoint")
|
||||
Expect(foundAgentEndpoint.Spec.TrafficPolicy.Inline).ToNot(BeNil())
|
||||
pol, err := trafficpolicy.NewTrafficPolicyFromJSON(foundAgentEndpoint.Spec.TrafficPolicy.Inline)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pol.OnHTTPRequest).To(ContainElement(
|
||||
trafficpolicy.Rule{
|
||||
Name: "test-name",
|
||||
Actions: []trafficpolicy.Action{
|
||||
{
|
||||
Type: "compress-response",
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -531,7 +516,6 @@ var _ = Describe("Driver", func() {
|
||||
It("Should only create an AgentEndpoint", func() {
|
||||
Expect(len(foundAgentEndpoints.Items)).To(Equal(1))
|
||||
Expect(len(foundCloudEndpoints.Items)).To(Equal(0))
|
||||
Expect(len(foundHTTPEdges.Items)).To(Equal(0))
|
||||
})
|
||||
|
||||
When("The traffic policy exists", func() {
|
||||
@@ -871,85 +855,6 @@ var _ = Describe("Driver", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getNgrokModuleSetForIngress", func() {
|
||||
var ms1, ms2, ms3 *ingressv1alpha1.NgrokModuleSet
|
||||
|
||||
BeforeEach(func() {
|
||||
ms1 = &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ms1", Namespace: "test"},
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
ms2 = &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ms2", Namespace: "test"},
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: false,
|
||||
},
|
||||
IPRestriction: &ingressv1alpha1.EndpointIPPolicy{
|
||||
IPPolicies: []string{"policy1", "policy2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
ms3 = &ingressv1alpha1.NgrokModuleSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ms3", Namespace: "test"},
|
||||
Modules: ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(driver.store.Add(ms1)).To(BeNil())
|
||||
Expect(driver.store.Add(ms2)).To(BeNil())
|
||||
Expect(driver.store.Add(ms3)).To(BeNil())
|
||||
})
|
||||
|
||||
It("Should return an empty module set if the ingress has no modules annotaion", func() {
|
||||
ing := testutils.NewTestIngressV1("test-ingress", "test")
|
||||
Expect(driver.store.Add(ing)).To(BeNil())
|
||||
|
||||
ms, err := getNgrokModuleSetForObject(ing, driver.store)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(ms.Modules.Compression).To(BeNil())
|
||||
Expect(ms.Modules.Headers).To(BeNil())
|
||||
Expect(ms.Modules.IPRestriction).To(BeNil())
|
||||
Expect(ms.Modules.TLSTermination).To(BeNil())
|
||||
Expect(ms.Modules.WebhookVerification).To(BeNil())
|
||||
})
|
||||
|
||||
It("Should return the matching module set if the ingress has a modules annotaion", func() {
|
||||
ing := testutils.NewTestIngressV1("test-ingress", "test")
|
||||
ing.SetAnnotations(map[string]string{"k8s.ngrok.com/modules": "ms1"})
|
||||
Expect(driver.store.Add(ing)).To(BeNil())
|
||||
|
||||
ms, err := getNgrokModuleSetForObject(ing, driver.store)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(ms.Modules).To(Equal(ms1.Modules))
|
||||
})
|
||||
|
||||
It("merges modules with the last one winning if multiple module sets are specified", func() {
|
||||
ing := testutils.NewTestIngressV1("test-ingress", "test")
|
||||
ing.SetAnnotations(map[string]string{"k8s.ngrok.com/modules": "ms1,ms2,ms3"})
|
||||
Expect(driver.store.Add(ing)).To(BeNil())
|
||||
|
||||
ms, err := getNgrokModuleSetForObject(ing, driver.store)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(ms.Modules).To(Equal(
|
||||
ingressv1alpha1.NgrokModuleSetModules{
|
||||
Compression: &ingressv1alpha1.EndpointCompression{
|
||||
Enabled: true, // From ms3
|
||||
},
|
||||
IPRestriction: &ingressv1alpha1.EndpointIPPolicy{
|
||||
IPPolicies: []string{"policy1", "policy2"}, // From ms2
|
||||
},
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("createEndpointPolicyForGateway", func() {
|
||||
var rule *gatewayv1.HTTPRouteRule
|
||||
var namespace string
|
||||
@@ -1264,12 +1169,11 @@ var _ = Describe("Driver", func() {
|
||||
Expect(driver.Seed(GinkgoT().Context(), c)).To(BeNil())
|
||||
|
||||
domainSet := driver.calculateDomainSet()
|
||||
Expect(domainSet.edgeIngressDomains).To(HaveLen(4))
|
||||
Expect(domainSet.edgeIngressDomains).To(HaveKey("a.customdomain.com"))
|
||||
Expect(domainSet.edgeIngressDomains).To(HaveKey("b.customdomain.com"))
|
||||
Expect(domainSet.edgeIngressDomains).To(HaveKey("c.customdomain.com"))
|
||||
Expect(domainSet.edgeIngressDomains).To(HaveKey("d.customdomain.com"))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveLen(0))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveLen(4))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveKey("a.customdomain.com"))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveKey("b.customdomain.com"))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveKey("c.customdomain.com"))
|
||||
Expect(domainSet.endpointIngressDomains).To(HaveKey("d.customdomain.com"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
package managerdriver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/annotations"
|
||||
"github.com/ngrok/ngrok-operator/internal/errors"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func (d *Driver) SyncEdges(ctx context.Context, c client.Client) error {
|
||||
if !d.syncAllowConcurrent {
|
||||
proceed, wait := d.syncStart(true)
|
||||
if !proceed {
|
||||
return wait(ctx)
|
||||
}
|
||||
defer d.syncDone()
|
||||
}
|
||||
|
||||
d.log.Info("syncing edges state!!")
|
||||
domains := d.calculateDomainSet()
|
||||
desiredEdges := d.calculateHTTPSEdges(domains)
|
||||
currEdges := &ingressv1alpha1.HTTPSEdgeList{}
|
||||
if err := c.List(ctx, currEdges, client.MatchingLabels{
|
||||
labelControllerNamespace: d.managerName.Namespace,
|
||||
labelControllerName: d.managerName.Name,
|
||||
}); err != nil {
|
||||
d.log.Error(err, "error listing edges")
|
||||
return err
|
||||
}
|
||||
|
||||
return d.applyHTTPSEdges(ctx, c, desiredEdges, currEdges.Items)
|
||||
}
|
||||
|
||||
func (d *Driver) applyHTTPSEdges(ctx context.Context, c client.Client, desiredEdges map[string]ingressv1alpha1.HTTPSEdge, currentEdges []ingressv1alpha1.HTTPSEdge) error {
|
||||
// update or delete edge we don't need anymore
|
||||
for _, currEdge := range currentEdges {
|
||||
hostports := currEdge.Spec.Hostports
|
||||
|
||||
// If one of the controller-owned edges has more than one hostport, log an error and skip it
|
||||
// because we can't determine what to do with it.
|
||||
if len(hostports) != 1 {
|
||||
d.log.Error(nil, "Existing owned edge has more than 1 hostport", "edge", currEdge, "hostports", hostports)
|
||||
continue
|
||||
}
|
||||
|
||||
// ngrok only supports https on port 443 and all domains are on port 443
|
||||
// so we can safely trim the port from the hostport to get the domain
|
||||
domain := strings.TrimSuffix(hostports[0], ":443")
|
||||
|
||||
if desiredEdge, ok := desiredEdges[domain]; ok {
|
||||
needsUpdate := false
|
||||
|
||||
if !reflect.DeepEqual(desiredEdge.Spec, currEdge.Spec) {
|
||||
currEdge.Spec = desiredEdge.Spec
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
if err := c.Update(ctx, &currEdge); err != nil {
|
||||
d.log.Error(err, "error updating edge", "desiredEdge", desiredEdge, "currEdge", currEdge)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// matched and updated the edge, no longer desired
|
||||
delete(desiredEdges, domain)
|
||||
} else {
|
||||
if err := c.Delete(ctx, &currEdge); client.IgnoreNotFound(err) != nil {
|
||||
d.log.Error(err, "error deleting edge", "edge", currEdge)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the set of desired edges now only contains new edges, create them
|
||||
for _, edge := range desiredEdges {
|
||||
if err := c.Create(ctx, &edge); err != nil {
|
||||
d.log.Error(err, "error creating edge", "edge", edge)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) calculateHTTPSEdges(domains *domainSet) map[string]ingressv1alpha1.HTTPSEdge {
|
||||
edgeMap := make(map[string]ingressv1alpha1.HTTPSEdge, len(domains.edgeIngressDomains))
|
||||
for _, domain := range domains.edgeIngressDomains {
|
||||
edge := ingressv1alpha1.HTTPSEdge{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: domain.Name + "-",
|
||||
Namespace: domain.Namespace,
|
||||
Labels: d.edgeLabels(),
|
||||
},
|
||||
Spec: ingressv1alpha1.HTTPSEdgeSpec{
|
||||
Hostports: []string{domain.Spec.Domain + ":443"},
|
||||
},
|
||||
}
|
||||
edge.Spec.Metadata = d.ingressNgrokMetadata
|
||||
edgeMap[domain.Spec.Domain] = edge
|
||||
}
|
||||
d.calculateHTTPSEdgesFromIngress(edgeMap)
|
||||
|
||||
// No gateway domains, return early
|
||||
if len(domains.edgeGatewayDomains) == 0 {
|
||||
return edgeMap
|
||||
}
|
||||
|
||||
if d.gatewayEnabled {
|
||||
gatewayEdgeMap := make(map[string]ingressv1alpha1.HTTPSEdge)
|
||||
httproutes := d.store.ListHTTPRoutes()
|
||||
gateways := d.store.ListGateways()
|
||||
for _, gtw := range gateways {
|
||||
gatewayDomains := make(map[string]string)
|
||||
for _, listener := range gtw.Spec.Listeners {
|
||||
if listener.Hostname == nil {
|
||||
continue
|
||||
}
|
||||
if listener.Protocol != gatewayv1.HTTPSProtocolType || int(listener.Port) != 443 {
|
||||
continue
|
||||
}
|
||||
if _, hasDomain := domains.edgeGatewayDomains[string(*listener.Hostname)]; !hasDomain {
|
||||
continue
|
||||
}
|
||||
gatewayDomains[string(*listener.Hostname)] = string(*listener.Hostname)
|
||||
}
|
||||
if len(gatewayDomains) == 0 {
|
||||
d.log.Info("no usable domains in gateway, may be missing https listener", "gateway", gtw.Name)
|
||||
continue
|
||||
}
|
||||
for _, httproute := range httproutes {
|
||||
var routeDomains []string
|
||||
for _, parent := range httproute.Spec.ParentRefs {
|
||||
if string(parent.Name) != gtw.Name {
|
||||
continue
|
||||
}
|
||||
var domainOverlap []string
|
||||
for _, hostname := range httproute.Spec.Hostnames {
|
||||
domain := string(hostname)
|
||||
if _, hasDomain := gatewayDomains[domain]; hasDomain {
|
||||
domainOverlap = append(domainOverlap, domain)
|
||||
}
|
||||
}
|
||||
if len(domainOverlap) == 0 {
|
||||
// no hostnames overlap with gateway
|
||||
continue
|
||||
}
|
||||
routeDomains = append(routeDomains, domainOverlap...)
|
||||
}
|
||||
if len(routeDomains) == 0 {
|
||||
// no usable domains in route
|
||||
continue
|
||||
}
|
||||
var hostPorts []string
|
||||
|
||||
for _, domain := range routeDomains {
|
||||
hostPorts = append(hostPorts, domain+":443")
|
||||
}
|
||||
edge := ingressv1alpha1.HTTPSEdge{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: httproute.Name + "-",
|
||||
Namespace: httproute.Namespace,
|
||||
Labels: d.edgeLabels(),
|
||||
},
|
||||
Spec: ingressv1alpha1.HTTPSEdgeSpec{
|
||||
Hostports: hostPorts,
|
||||
},
|
||||
}
|
||||
edge.Spec.Metadata = d.gatewayNgrokMetadata
|
||||
gatewayEdgeMap[routeDomains[0]] = edge
|
||||
|
||||
}
|
||||
}
|
||||
d.calculateHTTPSEdgesFromGateway(gatewayEdgeMap)
|
||||
|
||||
// merge edge maps
|
||||
for k, v := range gatewayEdgeMap {
|
||||
edgeMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return edgeMap
|
||||
}
|
||||
|
||||
func (d *Driver) calculateHTTPSEdgesFromIngress(edgeMap map[string]ingressv1alpha1.HTTPSEdge) {
|
||||
ingresses := d.store.ListNgrokIngressesV1()
|
||||
for _, ingress := range ingresses {
|
||||
useEdges, err := annotations.ExtractUseEdges(ingress)
|
||||
if err != nil {
|
||||
d.log.Error(err, fmt.Sprintf("failed to check %q annotation. defaulting to using endpoints", annotations.MappingStrategyAnnotation))
|
||||
}
|
||||
if !useEdges {
|
||||
d.log.Info("the following ingress will be provided by ngrok endpoints instead of edges",
|
||||
"ingress", fmt.Sprintf("%s.%s", ingress.Name, ingress.Namespace),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
modSet, err := getNgrokModuleSetForObject(ingress, d.store)
|
||||
if err != nil {
|
||||
d.log.Error(err, "error getting ngrok moduleset for ingress", "ingress", ingress)
|
||||
continue
|
||||
}
|
||||
|
||||
policyJSON, err := d.getTrafficPolicyJSON(ingress, modSet)
|
||||
if err != nil {
|
||||
d.log.Error(err, "error marshalling JSON Policy for ingress", "ingress", ingress)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
edge, ok := edgeMap[rule.Host]
|
||||
if !ok {
|
||||
d.log.Error(err, "could not find edge associated with rule", "host", rule.Host)
|
||||
continue
|
||||
}
|
||||
|
||||
if modSet.Modules.TLSTermination != nil && modSet.Modules.TLSTermination.MinVersion != nil {
|
||||
edge.Spec.TLSTermination = &ingressv1alpha1.EndpointTLSTerminationAtEdge{
|
||||
MinVersion: ptr.Deref(modSet.Modules.TLSTermination.MinVersion, ""),
|
||||
}
|
||||
}
|
||||
|
||||
if modSet.Modules.MutualTLS != nil {
|
||||
edge.Spec.MutualTLS = modSet.Modules.MutualTLS
|
||||
}
|
||||
|
||||
// If any rule for an ingress matches, then it applies to this ingress
|
||||
for _, httpIngressPath := range rule.HTTP.Paths {
|
||||
matchType := "path_prefix"
|
||||
if httpIngressPath.PathType != nil {
|
||||
switch *httpIngressPath.PathType {
|
||||
case netv1.PathTypePrefix:
|
||||
matchType = "path_prefix"
|
||||
case netv1.PathTypeExact:
|
||||
matchType = "exact_path"
|
||||
case netv1.PathTypeImplementationSpecific:
|
||||
matchType = "path_prefix" // Path Prefix seems like a sane default for most cases
|
||||
default:
|
||||
d.log.Error(errors.New("unknown path type"), "unknown path type", "pathType", *httpIngressPath.PathType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// We only support service backends right now. TODO: support resource backends
|
||||
if httpIngressPath.Backend.Service == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
serviceName := httpIngressPath.Backend.Service.Name
|
||||
serviceUID, servicePort, err := d.getIngressBackend(*httpIngressPath.Backend.Service, ingress.Namespace)
|
||||
if err != nil {
|
||||
d.log.Error(err, "could not find port for service", "namespace", ingress.Namespace, "service", serviceName)
|
||||
continue
|
||||
}
|
||||
|
||||
route := ingressv1alpha1.HTTPSEdgeRouteSpec{
|
||||
Match: httpIngressPath.Path,
|
||||
MatchType: matchType,
|
||||
Backend: ingressv1alpha1.TunnelGroupBackend{
|
||||
Labels: ngrokLabels(ingress.Namespace, serviceUID, serviceName, servicePort),
|
||||
},
|
||||
CircuitBreaker: modSet.Modules.CircuitBreaker,
|
||||
Compression: modSet.Modules.Compression,
|
||||
IPRestriction: modSet.Modules.IPRestriction,
|
||||
Headers: modSet.Modules.Headers,
|
||||
OAuth: modSet.Modules.OAuth,
|
||||
Policy: policyJSON,
|
||||
OIDC: modSet.Modules.OIDC,
|
||||
SAML: modSet.Modules.SAML,
|
||||
WebhookVerification: modSet.Modules.WebhookVerification,
|
||||
}
|
||||
route.Metadata = d.ingressNgrokMetadata
|
||||
|
||||
// Loop through existing routes and check if any match the path and match type
|
||||
// If they do, warn about it and continue replacing it
|
||||
for _, existingRoute := range edge.Spec.Routes {
|
||||
if existingRoute.Match == route.Match && existingRoute.MatchType == route.MatchType {
|
||||
d.log.Info("replacing existing route", "route", existingRoute.Match, "newRoute", route.Match)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
edge.Spec.Routes = append(edge.Spec.Routes, route)
|
||||
}
|
||||
|
||||
edgeMap[rule.Host] = edge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) calculateHTTPSEdgesFromGateway(edgeMap map[string]ingressv1alpha1.HTTPSEdge) {
|
||||
gateways := d.store.ListGateways()
|
||||
for _, gtw := range gateways {
|
||||
useEdges, err := annotations.ExtractUseEdges(gtw)
|
||||
if err != nil {
|
||||
d.log.Error(err, fmt.Sprintf("failed to check %q annotation. defaulting to using endpoints", annotations.MappingStrategyAnnotation))
|
||||
}
|
||||
if !useEdges {
|
||||
d.log.Info("the following gateway will be provided by ngrok endpoints",
|
||||
"gateway", fmt.Sprintf("%s.%s", gtw.Name, gtw.Namespace),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, listener := range gtw.Spec.Listeners {
|
||||
if listener.Hostname == nil {
|
||||
continue
|
||||
}
|
||||
allowedRoutes := listener.AllowedRoutes.Kinds
|
||||
if len(allowedRoutes) > 0 {
|
||||
createHttpsedge := false
|
||||
for _, routeKind := range allowedRoutes {
|
||||
if routeKind.Kind == "HTTPRoute" {
|
||||
createHttpsedge = true
|
||||
}
|
||||
}
|
||||
if !createHttpsedge {
|
||||
continue
|
||||
}
|
||||
}
|
||||
domainName := string(*listener.Hostname)
|
||||
edge, ok := edgeMap[domainName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// TODO: Calculate routes from httpRoutes
|
||||
// TODO: skip if no backend services
|
||||
httproutes := d.store.ListHTTPRoutes()
|
||||
for _, httproute := range httproutes {
|
||||
for _, parent := range httproute.Spec.ParentRefs {
|
||||
if string(parent.Name) != gtw.Name {
|
||||
// not our gateway so skip
|
||||
continue
|
||||
}
|
||||
|
||||
if listener.AllowedRoutes != nil && listener.AllowedRoutes.Namespaces.From != nil {
|
||||
switch *listener.AllowedRoutes.Namespaces.From {
|
||||
case gatewayv1.NamespacesFromAll:
|
||||
case gatewayv1.NamespacesFromSame:
|
||||
if httproute.Namespace != gtw.Namespace {
|
||||
continue
|
||||
}
|
||||
case gatewayv1.NamespacesFromSelector:
|
||||
if httproute.Namespace != listener.AllowedRoutes.Namespaces.Selector.String() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches our gateway
|
||||
for _, hostname := range httproute.Spec.Hostnames {
|
||||
if string(hostname) != string(*listener.Hostname) {
|
||||
// doesn't match this listener
|
||||
continue
|
||||
}
|
||||
// matches gateway and listener
|
||||
for _, rule := range httproute.Spec.Rules {
|
||||
// TODO: resolve rule.Matches
|
||||
// TODO: resolve rule.Filters
|
||||
// for v0 we will only resolve the first backendRef
|
||||
pathMatch := "/"
|
||||
pathMatchType := "path_prefix"
|
||||
// first match with a path will be accepted as the route's path
|
||||
for _, match := range rule.Matches {
|
||||
if match.Path != nil {
|
||||
pathMatch = *match.Path.Value
|
||||
if *match.Path.Type == gatewayv1.PathMatchExact {
|
||||
pathMatchType = "exact_path"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
route := ingressv1alpha1.HTTPSEdgeRouteSpec{
|
||||
Match: pathMatch, // change based on the rule.match
|
||||
MatchType: pathMatchType, // change based on rule.Matches
|
||||
}
|
||||
|
||||
// TODO: set with values from rules.Filters + rules.Matches
|
||||
// this HTTPRouteRule comes direct from gateway api yaml, and func returns the policy,
|
||||
// which goes directly into the edge route in ngrok.
|
||||
policy, err := d.createEndpointPolicyForGateway(&rule, httproute.Namespace)
|
||||
if err != nil {
|
||||
d.log.Error(err, "error creating policy from HTTPRouteRule", "rule", rule)
|
||||
continue
|
||||
}
|
||||
|
||||
route.Policy = policy
|
||||
|
||||
for idx, backendref := range rule.BackendRefs {
|
||||
// currently the ingress controller doesn't support weighted backends
|
||||
// so we'll only support one backendref per rule
|
||||
// TODO: remove when tested with multiple backends
|
||||
if idx > 0 {
|
||||
break
|
||||
}
|
||||
// handle backendref
|
||||
refKind := string(*backendref.Kind)
|
||||
if refKind != "Service" {
|
||||
// only support services currently
|
||||
continue
|
||||
}
|
||||
|
||||
refName := string(backendref.Name)
|
||||
serviceUID, servicePort, err := d.getEdgeBackendRef(backendref.BackendRef, httproute.Namespace)
|
||||
if err != nil {
|
||||
d.log.Error(err, "could not find port for service", "namespace", httproute.Namespace, "service", refName)
|
||||
continue
|
||||
}
|
||||
|
||||
route.Backend = ingressv1alpha1.TunnelGroupBackend{
|
||||
Labels: ngrokLabels(httproute.Namespace, serviceUID, refName, servicePort),
|
||||
}
|
||||
|
||||
}
|
||||
route.Metadata = d.gatewayNgrokMetadata
|
||||
|
||||
edge.Spec.Routes = append(edge.Spec.Routes, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edgeMap[domainName] = edge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) getIngressBackend(backendSvc netv1.IngressServiceBackend, namespace string) (string, int32, error) {
|
||||
service, servicePort, err := d.findBackendServicePort(backendSvc, namespace)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return string(service.UID), servicePort.Port, nil
|
||||
}
|
||||
|
||||
func (d *Driver) getEdgeBackendRef(backendRef gatewayv1.BackendRef, namespace string) (string, int32, error) {
|
||||
if backendRef.Namespace != nil && string(*backendRef.Namespace) != namespace {
|
||||
return "", 0, fmt.Errorf("namespace %s not supported", string(*backendRef.Namespace))
|
||||
}
|
||||
service, servicePort, err := d.findBackendRefServicePort(backendRef, namespace)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return string(service.UID), servicePort.Port, nil
|
||||
}
|
||||
|
||||
func (d *Driver) edgeLabels() map[string]string {
|
||||
return map[string]string{
|
||||
labelControllerNamespace: d.managerName.Namespace,
|
||||
labelControllerName: d.managerName.Name,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user