From bfc6c7002708e2399941c19038ebd6ff22198362 Mon Sep 17 00:00:00 2001 From: Nick Young Date: Thu, 2 Apr 2020 17:01:08 +1100 Subject: [PATCH] internal/annotation: Refactor annotations code from internal/dag Updates #2388 Update #403 Refactor annotations code from `internal/dag` into `internal/annotation` so that code dealing with ingress status will be able to use it. This includes moving some generic Kubernetes structs (`Object` and `Meta`) into `k8s` from `dag`. `Meta` has also been renamed to `FullName` for greater clarity. Signed-off-by: Nick Young --- cmd/contour/serve.go | 3 +- internal/{dag => annotation}/annotations.go | 83 +++-- .../{dag => annotation}/annotations_test.go | 30 +- internal/contour/handler.go | 2 +- internal/contour/metrics.go | 2 +- internal/dag/builder.go | 111 +++--- internal/dag/builder_test.go | 63 ++-- internal/dag/cache.go | 129 ++++--- internal/dag/dag.go | 7 +- internal/dag/policy.go | 48 +-- internal/dag/policy_test.go | 3 +- internal/dag/secret.go | 2 +- internal/dag/status.go | 23 +- internal/dag/status_test.go | 322 +++++++++--------- internal/k8s/objectmeta.go | 37 ++ internal/k8s/timeout.go | 43 +++ 16 files changed, 491 insertions(+), 417 deletions(-) rename internal/{dag => annotation}/annotations.go (68%) rename internal/{dag => annotation}/annotations_test.go (94%) create mode 100644 internal/k8s/objectmeta.go create mode 100644 internal/k8s/timeout.go diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 8f18c49f517..e2320813114 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -22,6 +22,7 @@ import ( "syscall" "time" + "github.com/projectcontour/contour/internal/annotation" "github.com/projectcontour/contour/internal/contour" "github.com/projectcontour/contour/internal/dag" "github.com/projectcontour/contour/internal/debug" @@ -165,7 +166,7 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error { HTTPSAccessLog: ctx.httpsAccessLog, AccessLogType: ctx.AccessLogFormat, AccessLogFields: ctx.AccessLogFields, - MinimumProtocolVersion: dag.MinProtoVersion(ctx.TLSConfig.MinimumProtocolVersion), + MinimumProtocolVersion: annotation.MinProtoVersion(ctx.TLSConfig.MinimumProtocolVersion), RequestTimeout: ctx.RequestTimeout, }, ListenerCache: contour.NewListenerCache(ctx.statsAddr, ctx.statsPort), diff --git a/internal/dag/annotations.go b/internal/annotation/annotations.go similarity index 68% rename from internal/dag/annotations.go rename to internal/annotation/annotations.go index f35f10157bc..07aed5f859c 100644 --- a/internal/dag/annotations.go +++ b/internal/annotation/annotations.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dag +package annotation import ( "fmt" @@ -20,10 +20,12 @@ import ( "time" envoy_api_v2_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" + "github.com/projectcontour/contour/internal/k8s" "k8s.io/api/networking/v1beta1" ) -func annotationIsKnown(key string) bool { +// IsKnown checks if an annotation is one Contour knows about. +func IsKnown(key string) bool { // We should know about everything with a Contour prefix. if strings.HasPrefix(key, "projectcontour.io/") || strings.HasPrefix(key, "contour.heptio.com/") { @@ -77,7 +79,8 @@ var annotationsByKind = map[string]map[string]struct{}{ }, } -func validAnnotationForKind(kind string, key string) bool { +// ValidForKind checks if a particular annotation is valid for a given Kind. +func ValidForKind(kind string, key string) bool { if a, ok := annotationsByKind[kind]; ok { // Canonicalize the name while we still have legacy support. key = strings.Replace(key, "contour.heptio.com/", "projectcontour.io/", -1) @@ -95,10 +98,10 @@ func validAnnotationForKind(kind string, key string) bool { return true } -// compatAnnotation checks the Object for the given annotation, first with the +// CompatAnnotation checks the Object for the given annotation, first with the // "projectcontour.io/" prefix, and then with the "contour.heptio.com/" prefix // if that is not found. -func compatAnnotation(o Object, key string) string { +func CompatAnnotation(o k8s.Object, key string) string { a := o.GetObjectMeta().GetAnnotations() if val, ok := a["projectcontour.io/"+key]; ok { @@ -108,7 +111,7 @@ func compatAnnotation(o Object, key string) string { return a["contour.heptio.com/"+key] } -// parseUInt32 parses the supplied string as if it were a uint32. +// ParseUInt32 parses the supplied string as if it were a uint32. // If the value is not present, or malformed, or outside uint32's range, zero is returned. func parseUInt32(s string) uint32 { v, err := strconv.ParseUint(s, 10, 32) @@ -118,10 +121,10 @@ func parseUInt32(s string) uint32 { return uint32(v) } -// parseUpstreamProtocols parses the annotations map for contour.heptio.com/upstream-protocol.{protocol} +// ParseUpstreamProtocols parses the annotations map for contour.heptio.com/upstream-protocol.{protocol} // and projectcontour.io/upstream-protocol.{protocol} annotations. // 'protocol' identifies which protocol must be used in the upstream. -func parseUpstreamProtocols(m map[string]string) map[string]string { +func ParseUpstreamProtocols(m map[string]string) map[string]string { annotations := []string{ "contour.heptio.com/upstream-protocol", "projectcontour.io/upstream-protocol", @@ -142,19 +145,21 @@ func parseUpstreamProtocols(m map[string]string) map[string]string { return up } -// httpAllowed returns true unless the kubernetes.io/ingress.allow-http annotation is +// HTTPAllowed returns true unless the kubernetes.io/ingress.allow-http annotation is // present and set to false. -func httpAllowed(i *v1beta1.Ingress) bool { +func HTTPAllowed(i *v1beta1.Ingress) bool { return !(i.Annotations["kubernetes.io/ingress.allow-http"] == "false") } -// tlsRequired returns true if the ingress.kubernetes.io/force-ssl-redirect annotation is +// TLSRequired returns true if the ingress.kubernetes.io/force-ssl-redirect annotation is // present and set to true. -func tlsRequired(i *v1beta1.Ingress) bool { +func TLSRequired(i *v1beta1.Ingress) bool { return i.Annotations["ingress.kubernetes.io/force-ssl-redirect"] == "true" } -func websocketRoutes(i *v1beta1.Ingress) map[string]bool { +// WebsocketRoutes retrieves the details of routes that should have websockets enabled from the +// associated websocket-routes annotation. +func WebsocketRoutes(i *v1beta1.Ingress) map[string]bool { routes := make(map[string]bool) for _, v := range strings.Split(i.Annotations["projectcontour.io/websocket-routes"], ",") { route := strings.TrimSpace(v) @@ -171,23 +176,23 @@ func websocketRoutes(i *v1beta1.Ingress) map[string]bool { return routes } -// numRetries returns the number of retries specified by the "contour.heptio.com/num-retries" +// NumRetries returns the number of retries specified by the "contour.heptio.com/num-retries" // or "projectcontour.io/num-retries" annotation. -func numRetries(i *v1beta1.Ingress) uint32 { - return parseUInt32(compatAnnotation(i, "num-retries")) +func NumRetries(i *v1beta1.Ingress) uint32 { + return parseUInt32(CompatAnnotation(i, "num-retries")) } -// perTryTimeout returns the duration envoy will wait per retry cycle. -func perTryTimeout(i *v1beta1.Ingress) time.Duration { - return parseTimeout(compatAnnotation(i, "per-try-timeout")) +// PerTryTimeout returns the duration envoy will wait per retry cycle. +func PerTryTimeout(i *v1beta1.Ingress) time.Duration { + return k8s.ParseTimeout(CompatAnnotation(i, "per-try-timeout")) } -// ingressClass returns the first matching ingress class for the following +// IngressClass returns the first matching ingress class for the following // annotations: // 1. projectcontour.io/ingress.class // 2. contour.heptio.com/ingress.class // 3. kubernetes.io/ingress.class -func ingressClass(o Object) string { +func IngressClass(o k8s.Object) string { a := o.GetObjectMeta().GetAnnotations() if class, ok := a["projectcontour.io/ingress.class"]; ok { return class @@ -215,24 +220,42 @@ func MinProtoVersion(version string) envoy_api_v2_auth.TlsParameters_TlsProtocol } } -// maxConnections returns the value of the first matching max-connections +// MaxConnections returns the value of the first matching max-connections // annotation for the following annotations: // 1. projectcontour.io/max-connections // 2. contour.heptio.com/max-connections // // '0' is returned if the annotation is absent or unparseable. -func maxConnections(o Object) uint32 { - return parseUInt32(compatAnnotation(o, "max-connections")) +func MaxConnections(o k8s.Object) uint32 { + return parseUInt32(CompatAnnotation(o, "max-connections")) } -func maxPendingRequests(o Object) uint32 { - return parseUInt32(compatAnnotation(o, "max-pending-requests")) +// MaxPendingRequests returns the value of the first matching max-pending-requests +// annotation for the following annotations: +// 1. projectcontour.io/max-pending-requests +// 2. contour.heptio.com/max-pending-requests +// +// '0' is returned if the annotation is absent or unparseable. +func MaxPendingRequests(o k8s.Object) uint32 { + return parseUInt32(CompatAnnotation(o, "max-pending-requests")) } -func maxRequests(o Object) uint32 { - return parseUInt32(compatAnnotation(o, "max-requests")) +// MaxRequests returns the value of the first matching max-requests +// annotation for the following annotations: +// 1. projectcontour.io/max-requests +// 2. contour.heptio.com/max-requests +// +// '0' is returned if the annotation is absent or unparseable. +func MaxRequests(o k8s.Object) uint32 { + return parseUInt32(CompatAnnotation(o, "max-requests")) } -func maxRetries(o Object) uint32 { - return parseUInt32(compatAnnotation(o, "max-retries")) +// MaxRetries returns the value of the first matching max-retries +// annotation for the following annotations: +// 1. projectcontour.io/max-retries +// 2. contour.heptio.com/max-retries +// +// '0' is returned if the annotation is absent or unparseable. +func MaxRetries(o k8s.Object) uint32 { + return parseUInt32(CompatAnnotation(o, "max-retries")) } diff --git a/internal/dag/annotations_test.go b/internal/annotation/annotations_test.go similarity index 94% rename from internal/dag/annotations_test.go rename to internal/annotation/annotations_test.go index 16de4dfb818..f1e49639be4 100644 --- a/internal/dag/annotations_test.go +++ b/internal/annotation/annotations_test.go @@ -11,17 +11,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dag +package annotation import ( "fmt" "testing" - "github.com/projectcontour/contour/internal/k8s" - ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/projectcontour/contour/internal/assert" + "github.com/projectcontour/contour/internal/k8s" v1 "k8s.io/api/core/v1" "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -122,7 +121,7 @@ func TestParseUpstreamProtocols(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := parseUpstreamProtocols(tc.a) + got := ParseUpstreamProtocols(tc.a) assert.Equal(t, tc.want, got) }) } @@ -267,7 +266,7 @@ func TestWebsocketRoutes(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := websocketRoutes(tc.a) + got := WebsocketRoutes(tc.a) assert.Equal(t, tc.want, got) }) } @@ -317,7 +316,7 @@ func TestHttpAllowed(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := httpAllowed(tc.i) + got := HTTPAllowed(tc.i) want := tc.valid if got != want { t.Fatalf("got: %v, want: %v", got, want) @@ -374,7 +373,7 @@ func TestAnnotationCompat(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := compatAnnotation(tc.svc, "annotation") + got := CompatAnnotation(tc.svc, "annotation") want := tc.value if got != want { t.Fatalf("got: %v, want: %v", got, want) @@ -389,7 +388,7 @@ func TestAnnotationKindValidation(t *testing.T) { valid bool } tests := map[string]struct { - obj Object + obj k8s.Object annotations map[string]status }{ "service": { @@ -445,8 +444,8 @@ func TestAnnotationKindValidation(t *testing.T) { for key := range annotationsByKind[kind] { t.Run(fmt.Sprintf("%s is known and valid for %s", key, kind), func(t *testing.T) { - assert.Equal(t, true, annotationIsKnown(key)) - assert.Equal(t, true, validAnnotationForKind(kind, key)) + assert.Equal(t, true, IsKnown(key)) + assert.Equal(t, true, ValidForKind(kind, key)) }) } } @@ -455,9 +454,16 @@ func TestAnnotationKindValidation(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { for k, s := range tc.annotations { - assert.Equal(t, s.known, annotationIsKnown(k)) - assert.Equal(t, s.valid, validAnnotationForKind(k8s.KindOf(tc.obj), k)) + assert.Equal(t, s.known, IsKnown(k)) + assert.Equal(t, s.valid, ValidForKind(k8s.KindOf(tc.obj), k)) } }) } } + +func backend(name string, port intstr.IntOrString) *v1beta1.IngressBackend { + return &v1beta1.IngressBackend{ + ServiceName: name, + ServicePort: port, + } +} diff --git a/internal/contour/handler.go b/internal/contour/handler.go index e351f2f83d8..99eda630e03 100644 --- a/internal/contour/handler.go +++ b/internal/contour/handler.go @@ -228,7 +228,7 @@ func (e *EventHandler) updateDAG() { } // setStatus updates the status of objects. -func (e *EventHandler) setStatus(statuses map[dag.Meta]dag.Status) { +func (e *EventHandler) setStatus(statuses map[k8s.FullName]dag.Status) { for _, st := range statuses { switch obj := st.Object.(type) { case *ingressroutev1.IngressRoute: diff --git a/internal/contour/metrics.go b/internal/contour/metrics.go index 664f8f38521..845b28cca3b 100644 --- a/internal/contour/metrics.go +++ b/internal/contour/metrics.go @@ -56,7 +56,7 @@ func (e *EventRecorder) recordOperation(op string, obj interface{}) { e.Counter.WithLabelValues(op, kind).Inc() } -func calculateRouteMetric(statuses map[dag.Meta]dag.Status) (metrics.RouteMetric, metrics.RouteMetric) { +func calculateRouteMetric(statuses map[k8s.FullName]dag.Status) (metrics.RouteMetric, metrics.RouteMetric) { irMetricTotal := make(map[metrics.Meta]int) irMetricValid := make(map[metrics.Meta]int) irMetricInvalid := make(map[metrics.Meta]int) diff --git a/internal/dag/builder.go b/internal/dag/builder.go index 519af8e1ba9..9c5db80ce2a 100644 --- a/internal/dag/builder.go +++ b/internal/dag/builder.go @@ -27,6 +27,7 @@ import ( "github.com/google/go-cmp/cmp" ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projcontour "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/internal/annotation" "github.com/projectcontour/contour/internal/k8s" ) @@ -42,12 +43,12 @@ type Builder struct { DisablePermitInsecure bool services map[servicemeta]*Service - secrets map[Meta]*Secret + secrets map[k8s.FullName]*Secret virtualhosts map[string]*VirtualHost securevirtualhosts map[string]*SecureVirtualHost - orphaned map[Meta]bool + orphaned map[k8s.FullName]bool StatusWriter } @@ -73,25 +74,25 @@ func (b *Builder) Build() *DAG { // reset (re)inialises the internal state of the builder. func (b *Builder) reset() { b.services = make(map[servicemeta]*Service, len(b.services)) - b.secrets = make(map[Meta]*Secret, len(b.secrets)) - b.orphaned = make(map[Meta]bool, len(b.orphaned)) + b.secrets = make(map[k8s.FullName]*Secret, len(b.secrets)) + b.orphaned = make(map[k8s.FullName]bool, len(b.orphaned)) b.virtualhosts = make(map[string]*VirtualHost) b.securevirtualhosts = make(map[string]*SecureVirtualHost) - b.statuses = make(map[Meta]Status, len(b.statuses)) + b.statuses = make(map[k8s.FullName]Status, len(b.statuses)) } // lookupService returns a Service that matches the Meta and Port of the Kubernetes' Service. -func (b *Builder) lookupService(m Meta, port intstr.IntOrString) *Service { +func (b *Builder) lookupService(m k8s.FullName, port intstr.IntOrString) *Service { lookup := func() *Service { if port.Type != intstr.Int { // can't handle, give up return nil } sm := servicemeta{ - name: m.name, - namespace: m.namespace, + name: m.Name, + namespace: m.Namespace, port: int32(port.IntValue()), } return b.services[sm] @@ -124,18 +125,18 @@ func (b *Builder) addService(svc *v1.Service, port *v1.ServicePort) *Service { ServicePort: port, Protocol: upstreamProtocol(svc, port), - MaxConnections: maxConnections(svc), - MaxPendingRequests: maxPendingRequests(svc), - MaxRequests: maxRequests(svc), - MaxRetries: maxRetries(svc), + MaxConnections: annotation.MaxConnections(svc), + MaxPendingRequests: annotation.MaxPendingRequests(svc), + MaxRequests: annotation.MaxRequests(svc), + MaxRetries: annotation.MaxRetries(svc), ExternalName: externalName(svc), } - b.services[s.toMeta()] = s + b.services[s.ToFullName()] = s return s } func upstreamProtocol(svc *v1.Service, port *v1.ServicePort) string { - up := parseUpstreamProtocols(svc.Annotations) + up := annotation.ParseUpstreamProtocols(svc.Annotations) protocol := up[port.Name] if protocol == "" { protocol = up[strconv.Itoa(int(port.Port))] @@ -145,7 +146,7 @@ func upstreamProtocol(svc *v1.Service, port *v1.ServicePort) string { // lookupSecret returns a Secret if present or nil if the underlying kubernetes // secret fails validation or is missing. -func (b *Builder) lookupSecret(m Meta, validate func(*v1.Secret) bool) *Secret { +func (b *Builder) lookupSecret(m k8s.FullName, validate func(*v1.Secret) bool) *Secret { sec, ok := b.Source.secrets[m] if !ok { return nil @@ -156,7 +157,7 @@ func (b *Builder) lookupSecret(m Meta, validate func(*v1.Secret) bool) *Secret { s := &Secret{ Object: sec, } - b.secrets[toMeta(sec)] = s + b.secrets[k8s.ToFullName(sec)] = s return s } @@ -271,15 +272,15 @@ func (b *Builder) computeSecureVirtualhosts() { for _, host := range tls.Hosts { svhost := b.lookupSecureVirtualHost(host) svhost.Secret = sec - version := compatAnnotation(ing, "tls-minimum-protocol-version") - svhost.MinProtoVersion = MinProtoVersion(version) + version := annotation.CompatAnnotation(ing, "tls-minimum-protocol-version") + svhost.MinProtoVersion = annotation.MinProtoVersion(version) } } } } } -func (b *Builder) delegationPermitted(secret Meta, to string) bool { +func (b *Builder) delegationPermitted(secret k8s.FullName, to string) bool { contains := func(haystack []string, needle string) bool { if len(haystack) == 1 && haystack[0] == "*" { return true @@ -292,18 +293,18 @@ func (b *Builder) delegationPermitted(secret Meta, to string) bool { return false } - if secret.namespace == to { + if secret.Namespace == to { // secret is in the same namespace as target return true } for _, d := range b.Source.httpproxydelegations { - if d.Namespace != secret.namespace { + if d.Namespace != secret.Namespace { continue } for _, d := range d.Spec.Delegations { if contains(d.TargetNamespaces, to) { - if secret.name == d.SecretName { + if secret.Name == d.SecretName { return true } } @@ -311,12 +312,12 @@ func (b *Builder) delegationPermitted(secret Meta, to string) bool { } for _, d := range b.Source.irdelegations { - if d.Namespace != secret.namespace { + if d.Namespace != secret.Namespace { continue } for _, d := range d.Spec.Delegations { if contains(d.TargetNamespaces, to) { - if secret.name == d.SecretName { + if secret.Name == d.SecretName { return true } } @@ -350,7 +351,7 @@ func (b *Builder) computeIngressRule(ing *v1beta1.Ingress, rule v1beta1.IngressR for _, httppath := range httppaths(rule) { path := stringOrDefault(httppath.Path, "/") be := httppath.Backend - m := Meta{name: be.ServiceName, namespace: ing.Namespace} + m := k8s.FullName{Name: be.ServiceName, Namespace: ing.Namespace} s := b.lookupService(m, be.ServicePort) if s == nil { continue @@ -359,7 +360,7 @@ func (b *Builder) computeIngressRule(ing *v1beta1.Ingress, rule v1beta1.IngressR r := route(ing, path, s) // should we create port 80 routes for this ingress - if tlsRequired(ing) || httpAllowed(ing) { + if annotation.TLSRequired(ing) || annotation.HTTPAllowed(ing) { b.lookupVirtualHost(host).addRoute(r) } @@ -418,7 +419,7 @@ func (b *Builder) computeIngressRoute(ir *ingressroutev1.IngressRoute) { } svhost := b.lookupSecureVirtualHost(ir.Spec.VirtualHost.Fqdn) svhost.Secret = sec - svhost.MinProtoVersion = MinProtoVersion(ir.Spec.VirtualHost.TLS.MinimumProtocolVersion) + svhost.MinProtoVersion = annotation.MinProtoVersion(ir.Spec.VirtualHost.TLS.MinimumProtocolVersion) enforceTLS = true } // passthrough is true if tls.secretName is not present, and @@ -487,7 +488,7 @@ func (b *Builder) computeHTTPProxy(proxy *projcontour.HTTPProxy) { } svhost := b.lookupSecureVirtualHost(host) svhost.Secret = sec - svhost.MinProtoVersion = MinProtoVersion(proxy.Spec.VirtualHost.TLS.MinimumProtocolVersion) + svhost.MinProtoVersion = annotation.MinProtoVersion(proxy.Spec.VirtualHost.TLS.MinimumProtocolVersion) } if sec == nil && !tls.Passthrough { @@ -667,7 +668,7 @@ func (b *Builder) computeRoutes(sw *ObjectStatusWriter, proxy *projcontour.HTTPP namespace = proxy.Namespace } - delegate, ok := b.Source.httpproxies[Meta{name: include.Name, namespace: namespace}] + delegate, ok := b.Source.httpproxies[k8s.FullName{Name: include.Name, Namespace: namespace}] if !ok { sw.SetInvalid("include %s/%s not found", namespace, include.Name) return nil @@ -687,7 +688,7 @@ func (b *Builder) computeRoutes(sw *ObjectStatusWriter, proxy *projcontour.HTTPP commit() // dest is not an orphaned httpproxy, as there is an httpproxy that points to it - delete(b.orphaned, Meta{name: delegate.Name, namespace: delegate.Namespace}) + delete(b.orphaned, k8s.FullName{Name: delegate.Name, Namespace: delegate.Namespace}) } for _, route := range proxy.Spec.Routes { @@ -774,7 +775,7 @@ func (b *Builder) computeRoutes(sw *ObjectStatusWriter, proxy *projcontour.HTTPP sw.SetInvalid("service %q: port must be in the range 1-65535", service.Name) return nil } - m := Meta{name: service.Name, namespace: proxy.Namespace} + m := k8s.FullName{Name: service.Name, Namespace: proxy.Namespace} s := b.lookupService(m, intstr.FromInt(service.Port)) if s == nil { @@ -939,10 +940,10 @@ func (b *Builder) buildHTTPSListener() *Listener { } // setOrphaned records an IngressRoute/HTTPProxy resource as orphaned. -func (b *Builder) setOrphaned(obj Object) { - m := Meta{ - name: obj.GetObjectMeta().GetName(), - namespace: obj.GetObjectMeta().GetNamespace(), +func (b *Builder) setOrphaned(obj k8s.Object) { + m := k8s.FullName{ + Name: obj.GetObjectMeta().GetName(), + Namespace: obj.GetObjectMeta().GetNamespace(), } b.orphaned[m] = true } @@ -991,7 +992,7 @@ func (b *Builder) processIngressRoutes(sw *ObjectStatusWriter, ir *ingressroutev sw.SetInvalid("route %q: service %q: port must be in the range 1-65535", route.Match, service.Name) return } - m := Meta{name: service.Name, namespace: ir.Namespace} + m := k8s.FullName{Name: service.Name, Namespace: ir.Namespace} s := b.lookupService(m, intstr.FromInt(service.Port)) if s == nil { @@ -1039,14 +1040,14 @@ func (b *Builder) processIngressRoutes(sw *ObjectStatusWriter, ir *ingressroutev namespace = ir.Namespace } - if dest, ok := b.Source.ingressroutes[Meta{name: route.Delegate.Name, namespace: namespace}]; ok { + if dest, ok := b.Source.ingressroutes[k8s.FullName{Name: route.Delegate.Name, Namespace: namespace}]; ok { if dest.Spec.VirtualHost != nil { sw.SetInvalid("root ingressroute cannot delegate to another root ingressroute") return } // dest is not an orphaned ingress route, as there is an IR that points to it - delete(b.orphaned, Meta{name: dest.Name, namespace: dest.Namespace}) + delete(b.orphaned, k8s.FullName{Name: dest.Name, Namespace: dest.Namespace}) // ensure we are not following an edge that produces a cycle var path []string @@ -1076,7 +1077,7 @@ func (b *Builder) lookupUpstreamValidation(uv *projcontour.UpstreamValidation, n return nil, nil } - cacert := b.lookupSecret(Meta{name: uv.CACertificate, namespace: namespace}, validCA) + cacert := b.lookupSecret(k8s.FullName{Name: uv.CACertificate, Namespace: namespace}, validCA) if cacert == nil { // UpstreamValidation is requested, but cert is missing or not configured return nil, errors.New("secret not found or misconfigured") @@ -1106,7 +1107,7 @@ func (b *Builder) processIngressRouteTCPProxy(sw *ObjectStatusWriter, ir *ingres if len(tcpproxy.Services) > 0 { var proxy TCPProxy for _, service := range tcpproxy.Services { - m := Meta{name: service.Name, namespace: ir.Namespace} + m := k8s.FullName{Name: service.Name, Namespace: ir.Namespace} s := b.lookupService(m, intstr.FromInt(service.Port)) if s == nil { sw.SetInvalid("tcpproxy: service %s/%s/%d: not found", ir.Namespace, service.Name, service.Port) @@ -1136,9 +1137,9 @@ func (b *Builder) processIngressRouteTCPProxy(sw *ObjectStatusWriter, ir *ingres namespace = ir.Namespace } - if dest, ok := b.Source.ingressroutes[Meta{name: tcpproxy.Delegate.Name, namespace: namespace}]; ok { + if dest, ok := b.Source.ingressroutes[k8s.FullName{Name: tcpproxy.Delegate.Name, Namespace: namespace}]; ok { // dest is not an orphaned ingress route, as there is an IR that points to it - delete(b.orphaned, Meta{name: dest.Name, namespace: dest.Namespace}) + delete(b.orphaned, k8s.FullName{Name: dest.Name, Namespace: dest.Namespace}) // ensure we are not following an edge that produces a cycle var path []string @@ -1188,7 +1189,7 @@ func (b *Builder) processHTTPProxyTCPProxy(sw *ObjectStatusWriter, httpproxy *pr if len(tcpproxy.Services) > 0 { var proxy TCPProxy for _, service := range httpproxy.Spec.TCPProxy.Services { - m := Meta{name: service.Name, namespace: httpproxy.Namespace} + m := k8s.FullName{Name: service.Name, Namespace: httpproxy.Namespace} s := b.lookupService(m, intstr.FromInt(service.Port)) if s == nil { sw.SetInvalid("tcpproxy: service %s/%s/%d: not found", httpproxy.Namespace, service.Name, service.Port) @@ -1217,10 +1218,10 @@ func (b *Builder) processHTTPProxyTCPProxy(sw *ObjectStatusWriter, httpproxy *pr namespace = httpproxy.Namespace } - m := Meta{name: tcpProxyInclude.Name, namespace: namespace} + m := k8s.FullName{Name: tcpProxyInclude.Name, Namespace: namespace} dest, ok := b.Source.httpproxies[m] if !ok { - sw.SetInvalid("tcpproxy: include %s/%s not found", m.namespace, m.name) + sw.SetInvalid("tcpproxy: include %s/%s not found", m.Namespace, m.Name) return false } @@ -1230,7 +1231,7 @@ func (b *Builder) processHTTPProxyTCPProxy(sw *ObjectStatusWriter, httpproxy *pr } // dest is no longer an orphan - delete(b.orphaned, toMeta(dest)) + delete(b.orphaned, k8s.ToFullName(dest)) // ensure we are not following an edge that produces a cycle var path []string @@ -1264,9 +1265,9 @@ func externalName(svc *v1.Service) string { // route builds a dag.Route for the supplied Ingress. func route(ingress *v1beta1.Ingress, path string, service *Service) *Route { - wr := websocketRoutes(ingress) + wr := annotation.WebsocketRoutes(ingress) r := &Route{ - HTTPSUpgrade: tlsRequired(ingress), + HTTPSUpgrade: annotation.TLSRequired(ingress), Websocket: wr[path], TimeoutPolicy: ingressTimeoutPolicy(ingress), RetryPolicy: ingressRetryPolicy(ingress), @@ -1293,19 +1294,19 @@ func isBlank(s string) bool { // splitSecret splits a secretName into its namespace and name components. // If there is no namespace prefix, the default namespace is returned. -func splitSecret(secret, defns string) Meta { +func splitSecret(secret, defns string) k8s.FullName { v := strings.SplitN(secret, "/", 2) switch len(v) { case 1: // no prefix - return Meta{ - name: v[0], - namespace: defns, + return k8s.FullName{ + Name: v[0], + Namespace: defns, } default: - return Meta{ - name: v[1], - namespace: stringOrDefault(v[0], defns), + return k8s.FullName{ + Name: v[1], + Namespace: stringOrDefault(v[0], defns), } } } diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index bd5f06dd49b..a425e678e74 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/go-cmp/cmp" ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projcontour "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/internal/k8s" v1 "k8s.io/api/core/v1" "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -4401,7 +4402,7 @@ func TestDAGInsert(t *testing.T) { }, ), }, - "insert ingress with invalid perTryTimeout": { + "insert ingress with invalid PerTryTimeout": { objs: []interface{}{ ir15a, s1, @@ -6211,34 +6212,34 @@ func TestBuilderLookupService(t *testing.T) { }}, }, } - services := map[Meta]*v1.Service{ - {name: "service1", namespace: "default"}: s1, + services := map[k8s.FullName]*v1.Service{ + {Name: "service1", Namespace: "default"}: s1, } tests := map[string]struct { - Meta + k8s.FullName port intstr.IntOrString want *Service }{ "lookup service by port number": { - Meta: Meta{name: "service1", namespace: "default"}, - port: intstr.FromInt(8080), - want: service(s1), + FullName: k8s.FullName{Name: "service1", Namespace: "default"}, + port: intstr.FromInt(8080), + want: service(s1), }, "lookup service by port name": { - Meta: Meta{name: "service1", namespace: "default"}, - port: intstr.FromString("http"), - want: service(s1), + FullName: k8s.FullName{Name: "service1", Namespace: "default"}, + port: intstr.FromString("http"), + want: service(s1), }, "lookup service by port number (as string)": { - Meta: Meta{name: "service1", namespace: "default"}, - port: intstr.Parse("8080"), - want: service(s1), + FullName: k8s.FullName{Name: "service1", Namespace: "default"}, + port: intstr.Parse("8080"), + want: service(s1), }, "lookup service by port number (from string)": { - Meta: Meta{name: "service1", namespace: "default"}, - port: intstr.FromString("8080"), - want: service(s1), + FullName: k8s.FullName{Name: "service1", Namespace: "default"}, + port: intstr.FromString("8080"), + want: service(s1), }, } @@ -6251,7 +6252,7 @@ func TestBuilderLookupService(t *testing.T) { }, } b.reset() - got := b.lookupService(tc.Meta, tc.port) + got := b.lookupService(tc.FullName, tc.port) if diff := cmp.Diff(tc.want, got); diff != "" { t.Fatal(diff) } @@ -6666,38 +6667,38 @@ func TestEnforceRoute(t *testing.T) { func TestSplitSecret(t *testing.T) { tests := map[string]struct { secret, defns string - want Meta + want k8s.FullName }{ "no namespace": { secret: "secret", defns: "default", - want: Meta{ - name: "secret", - namespace: "default", + want: k8s.FullName{ + Name: "secret", + Namespace: "default", }, }, "with namespace": { secret: "ns1/secret", defns: "default", - want: Meta{ - name: "secret", - namespace: "ns1", + want: k8s.FullName{ + Name: "secret", + Namespace: "ns1", }, }, "missing namespace": { secret: "/secret", defns: "default", - want: Meta{ - name: "secret", - namespace: "default", + want: k8s.FullName{ + Name: "secret", + Namespace: "default", }, }, "missing secret name": { secret: "secret/", defns: "default", - want: Meta{ - name: "", - namespace: "secret", + want: k8s.FullName{ + Name: "", + Namespace: "secret", }, }, } @@ -6706,7 +6707,7 @@ func TestSplitSecret(t *testing.T) { t.Run(name, func(t *testing.T) { got := splitSecret(tc.secret, tc.defns) opts := []cmp.Option{ - cmp.AllowUnexported(Meta{}), + cmp.AllowUnexported(k8s.FullName{}), } if diff := cmp.Diff(tc.want, got, opts...); diff != "" { t.Fatal(diff) diff --git a/internal/dag/cache.go b/internal/dag/cache.go index 3dc4c6f94be..7f2740f8cf6 100644 --- a/internal/dag/cache.go +++ b/internal/dag/cache.go @@ -14,6 +14,7 @@ package dag import ( + "github.com/projectcontour/contour/internal/annotation" "github.com/projectcontour/contour/internal/k8s" v1 "k8s.io/api/core/v1" @@ -26,6 +27,7 @@ import ( serviceapis "sigs.k8s.io/service-apis/api/v1alpha1" ) +// DEFAULT_INGRESS_CLASS is the Contour default. const DEFAULT_INGRESS_CLASS = "contour" // A KubernetesCache holds Kubernetes objects and associated configuration and produces @@ -40,38 +42,25 @@ type KubernetesCache struct { // If not set, defaults to DEFAULT_INGRESS_CLASS. IngressClass string - ingresses map[Meta]*v1beta1.Ingress - ingressroutes map[Meta]*ingressroutev1.IngressRoute - httpproxies map[Meta]*projectcontour.HTTPProxy - secrets map[Meta]*v1.Secret - irdelegations map[Meta]*ingressroutev1.TLSCertificateDelegation - httpproxydelegations map[Meta]*projectcontour.TLSCertificateDelegation - services map[Meta]*v1.Service - gatewayclasses map[Meta]*serviceapis.GatewayClass - gateways map[Meta]*serviceapis.Gateway - httproutes map[Meta]*serviceapis.HTTPRoute - tcproutes map[Meta]*serviceapis.TcpRoute + ingresses map[k8s.FullName]*v1beta1.Ingress + ingressroutes map[k8s.FullName]*ingressroutev1.IngressRoute + httpproxies map[k8s.FullName]*projectcontour.HTTPProxy + secrets map[k8s.FullName]*v1.Secret + irdelegations map[k8s.FullName]*ingressroutev1.TLSCertificateDelegation + httpproxydelegations map[k8s.FullName]*projectcontour.TLSCertificateDelegation + services map[k8s.FullName]*v1.Service + gatewayclasses map[k8s.FullName]*serviceapis.GatewayClass + gateways map[k8s.FullName]*serviceapis.Gateway + httproutes map[k8s.FullName]*serviceapis.HTTPRoute + tcproutes map[k8s.FullName]*serviceapis.TcpRoute logrus.FieldLogger } -// Meta holds the name and namespace of a Kubernetes object. -type Meta struct { - name, namespace string -} - -func toMeta(obj Object) Meta { - m := obj.GetObjectMeta() - return Meta{ - name: m.GetName(), - namespace: m.GetNamespace(), - } -} - // matchesIngressClass returns true if the given Kubernetes object // belongs to the Ingress class that this cache is using. -func (kc *KubernetesCache) matchesIngressClass(obj Object) bool { - objectClass := ingressClass(obj) +func (kc *KubernetesCache) matchesIngressClass(obj k8s.Object) bool { + objectClass := annotation.IngressClass(obj) switch objectClass { case kc.IngressClass: @@ -102,15 +91,15 @@ func (kc *KubernetesCache) matchesIngressClass(obj Object) bool { // is not interesting to the cache. If an object with a matching type, name, // and namespace exists, it will be overwritten. func (kc *KubernetesCache) Insert(obj interface{}) bool { - if obj, ok := obj.(Object); ok { + if obj, ok := obj.(k8s.Object); ok { kind := k8s.KindOf(obj) for key := range obj.GetObjectMeta().GetAnnotations() { // Emit a warning if this is a known annotation that has // been applied to an invalid object kind. Note that we // only warn for known annotations because we want to // allow users to add arbitrary orthogonal annotations - // to object that we inspect. - if annotationIsKnown(key) && !validAnnotationForKind(kind, key) { + // to objects that we inspect. + if annotation.IsKnown(key) && !annotation.ValidForKind(kind, key) { // TODO(jpeach): this should be exposed // to the user as a status condition. om := obj.GetObjectMeta() @@ -139,98 +128,98 @@ func (kc *KubernetesCache) Insert(obj interface{}) bool { return false } - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.secrets == nil { - kc.secrets = make(map[Meta]*v1.Secret) + kc.secrets = make(map[k8s.FullName]*v1.Secret) } kc.secrets[m] = obj return kc.secretTriggersRebuild(obj) case *v1.Service: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.services == nil { - kc.services = make(map[Meta]*v1.Service) + kc.services = make(map[k8s.FullName]*v1.Service) } kc.services[m] = obj return kc.serviceTriggersRebuild(obj) case *v1beta1.Ingress: if kc.matchesIngressClass(obj) { - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.ingresses == nil { - kc.ingresses = make(map[Meta]*v1beta1.Ingress) + kc.ingresses = make(map[k8s.FullName]*v1beta1.Ingress) } kc.ingresses[m] = obj return true } case *ingressroutev1.IngressRoute: if kc.matchesIngressClass(obj) { - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.ingressroutes == nil { - kc.ingressroutes = make(map[Meta]*ingressroutev1.IngressRoute) + kc.ingressroutes = make(map[k8s.FullName]*ingressroutev1.IngressRoute) } kc.ingressroutes[m] = obj return true } case *projectcontour.HTTPProxy: if kc.matchesIngressClass(obj) { - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.httpproxies == nil { - kc.httpproxies = make(map[Meta]*projectcontour.HTTPProxy) + kc.httpproxies = make(map[k8s.FullName]*projectcontour.HTTPProxy) } kc.httpproxies[m] = obj return true } case *ingressroutev1.TLSCertificateDelegation: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.irdelegations == nil { - kc.irdelegations = make(map[Meta]*ingressroutev1.TLSCertificateDelegation) + kc.irdelegations = make(map[k8s.FullName]*ingressroutev1.TLSCertificateDelegation) } kc.irdelegations[m] = obj return true case *projectcontour.TLSCertificateDelegation: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.httpproxydelegations == nil { - kc.httpproxydelegations = make(map[Meta]*projectcontour.TLSCertificateDelegation) + kc.httpproxydelegations = make(map[k8s.FullName]*projectcontour.TLSCertificateDelegation) } kc.httpproxydelegations[m] = obj return true case *serviceapis.GatewayClass: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.gatewayclasses == nil { - kc.gatewayclasses = make(map[Meta]*serviceapis.GatewayClass) + kc.gatewayclasses = make(map[k8s.FullName]*serviceapis.GatewayClass) } // TODO(youngnick): Remove this once service-apis actually have behavior // other than being added to the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Adding GatewayClass") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Adding GatewayClass") kc.gatewayclasses[m] = obj return true case *serviceapis.Gateway: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.gateways == nil { - kc.gateways = make(map[Meta]*serviceapis.Gateway) + kc.gateways = make(map[k8s.FullName]*serviceapis.Gateway) } // TODO(youngnick): Remove this once service-apis actually have behavior // other than being added to the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Adding Gateway") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Adding Gateway") kc.gateways[m] = obj return true case *serviceapis.HTTPRoute: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.httproutes == nil { - kc.httproutes = make(map[Meta]*serviceapis.HTTPRoute) + kc.httproutes = make(map[k8s.FullName]*serviceapis.HTTPRoute) } // TODO(youngnick): Remove this once service-apis actually have behavior // other than being added to the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Adding HTTPRoute") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Adding HTTPRoute") kc.httproutes[m] = obj return true case *serviceapis.TcpRoute: - m := toMeta(obj) + m := k8s.ToFullName(obj) if kc.tcproutes == nil { - kc.tcproutes = make(map[Meta]*serviceapis.TcpRoute) + kc.tcproutes = make(map[k8s.FullName]*serviceapis.TcpRoute) } // TODO(youngnick): Remove this once service-apis actually have behavior // other than being added to the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Adding TcpRoute") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Adding TcpRoute") kc.tcproutes[m] = obj return true @@ -257,70 +246,70 @@ func (kc *KubernetesCache) Remove(obj interface{}) bool { func (kc *KubernetesCache) remove(obj interface{}) bool { switch obj := obj.(type) { case *v1.Secret: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.secrets[m] delete(kc.secrets, m) return ok case *v1.Service: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.services[m] delete(kc.services, m) return ok case *v1beta1.Ingress: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.ingresses[m] delete(kc.ingresses, m) return ok case *ingressroutev1.IngressRoute: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.ingressroutes[m] delete(kc.ingressroutes, m) return ok case *projectcontour.HTTPProxy: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.httpproxies[m] delete(kc.httpproxies, m) return ok case *ingressroutev1.TLSCertificateDelegation: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.irdelegations[m] delete(kc.irdelegations, m) return ok case *projectcontour.TLSCertificateDelegation: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.httpproxydelegations[m] delete(kc.httpproxydelegations, m) return ok case *serviceapis.GatewayClass: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.gatewayclasses[m] // TODO(youngnick): Remove this once service-apis actually have behavior // other than being removed from the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Removing GatewayClass") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Removing GatewayClass") delete(kc.gatewayclasses, m) return ok case *serviceapis.Gateway: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.gateways[m] // TODO(youngnick): Remove this once service-apis actually have behavior // other than being removed from the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Removing Gateway") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Removing Gateway") delete(kc.gateways, m) return ok case *serviceapis.HTTPRoute: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.httproutes[m] // TODO(youngnick): Remove this once service-apis actually have behavior // other than being removed from the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Removing HTTPRoute") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Removing HTTPRoute") delete(kc.httproutes, m) return ok case *serviceapis.TcpRoute: - m := toMeta(obj) + m := k8s.ToFullName(obj) _, ok := kc.tcproutes[m] // TODO(youngnick): Remove this once service-apis actually have behavior // other than being removed from the cache. - kc.WithField("experimental", "service-apis").WithField("name", m.name).WithField("namespace", m.namespace).Debug("Removing TcpRoute") + kc.WithField("experimental", "service-apis").WithField("name", m.Name).WithField("namespace", m.Namespace).Debug("Removing TcpRoute") delete(kc.tcproutes, m) return ok diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 61e721657c5..f48ea56c127 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -21,6 +21,7 @@ import ( "time" envoy_api_v2_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" + "github.com/projectcontour/contour/internal/k8s" v1 "k8s.io/api/core/v1" ) @@ -32,7 +33,7 @@ type DAG struct { roots []Vertex // status computed while building this dag. - statuses map[Meta]Status + statuses map[k8s.FullName]Status } // Visit calls fn on each root of this DAG. @@ -44,7 +45,7 @@ func (d *DAG) Visit(fn func(Vertex)) { // Statuses returns a slice of Status objects associated with // the computation of this DAG. -func (d *DAG) Statuses() map[Meta]Status { +func (d *DAG) Statuses() map[k8s.FullName]Status { return d.statuses } @@ -364,7 +365,7 @@ type servicemeta struct { port int32 } -func (s *Service) toMeta() servicemeta { +func (s *Service) ToFullName() servicemeta { return servicemeta{ name: s.Name, namespace: s.Namespace, diff --git a/internal/dag/policy.go b/internal/dag/policy.go index 5337bc40044..f7e968fc1b7 100644 --- a/internal/dag/policy.go +++ b/internal/dag/policy.go @@ -20,6 +20,8 @@ import ( ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projcontour "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/internal/annotation" + "github.com/projectcontour/contour/internal/k8s" "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" @@ -91,30 +93,30 @@ func headersPolicy(policy *projcontour.HeadersPolicy, allowHostRewrite bool) (*H // ingressRetryPolicy builds a RetryPolicy from ingress annotations. func ingressRetryPolicy(ingress *v1beta1.Ingress) *RetryPolicy { - retryOn := compatAnnotation(ingress, "retry-on") + retryOn := annotation.CompatAnnotation(ingress, "retry-on") if len(retryOn) < 1 { return nil } // if there is a non empty retry-on annotation, build a RetryPolicy manually. return &RetryPolicy{ RetryOn: retryOn, - // TODO(dfc) numRetries may parse as 0, which is inconsistent with + // TODO(dfc) k8s.NumRetries may parse as 0, which is inconsistent with // retryPolicyIngressRoute()'s default value of 1. - NumRetries: numRetries(ingress), - // TODO(dfc) perTryTimeout will parse to -1, infinite, in the case of + NumRetries: annotation.NumRetries(ingress), + // TODO(dfc) k8s.PerTryTimeout will parse to -1, infinite, in the case of // invalid data, this is inconsistent with retryPolicyIngressRoute()'s default value // of 0 duration. - PerTryTimeout: perTryTimeout(ingress), + PerTryTimeout: annotation.PerTryTimeout(ingress), } } func ingressTimeoutPolicy(ingress *v1beta1.Ingress) *TimeoutPolicy { - response := compatAnnotation(ingress, "response-timeout") + response := annotation.CompatAnnotation(ingress, "response-timeout") if len(response) == 0 { // Note: due to a misunderstanding the name of the annotation is // request timeout, but it is actually applied as a timeout on // the response body. - response = compatAnnotation(ingress, "request-timeout") + response = annotation.CompatAnnotation(ingress, "request-timeout") if len(response) == 0 { return nil } @@ -134,7 +136,7 @@ func ingressrouteTimeoutPolicy(tp *ingressroutev1.TimeoutPolicy) *TimeoutPolicy // due to a misunderstanding the name of the field ingressroute is // Request, however the timeout applies to the response resulting from // a request. - ResponseTimeout: parseTimeout(tp.Request), + ResponseTimeout: k8s.ParseTimeout(tp.Request), } } @@ -143,8 +145,8 @@ func timeoutPolicy(tp *projcontour.TimeoutPolicy) *TimeoutPolicy { return nil } return &TimeoutPolicy{ - ResponseTimeout: parseTimeout(tp.Response), - IdleTimeout: parseTimeout(tp.Idle), + ResponseTimeout: k8s.ParseTimeout(tp.Response), + IdleTimeout: k8s.ParseTimeout(tp.Idle), } } func ingressrouteHealthCheckPolicy(hc *ingressroutev1.HealthCheck) *HTTPHealthCheckPolicy { @@ -205,32 +207,6 @@ func loadBalancerPolicy(lbp *projcontour.LoadBalancerPolicy) string { } } -func parseTimeout(timeout string) time.Duration { - if timeout == "" { - // Blank is interpreted as no timeout specified, use envoy defaults - // By default envoy applies a 15 second timeout to all backend requests. - // The explicit value 0 turns off the timeout, implying "never time out" - // https://www.envoyproxy.io/docs/envoy/v1.5.0/api-v2/rds.proto#routeaction - return 0 - } - - // Interpret "infinity" explicitly as an infinite timeout, which envoy config - // expects as a timeout of 0. This could be specified with the duration string - // "0s" but want to give an explicit out for operators. - if timeout == "infinity" { - return -1 - } - - d, err := time.ParseDuration(timeout) - if err != nil { - // TODO(cmalonty) plumb a logger in here so we can log this error. - // Assuming infinite duration is going to surprise people less for - // a not-parseable duration than a implicit 15 second one. - return -1 - } - return d -} - func max(a, b uint32) uint32 { if a > b { return a diff --git a/internal/dag/policy_test.go b/internal/dag/policy_test.go index 77f1445346b..9f6e79b8bcc 100644 --- a/internal/dag/policy_test.go +++ b/internal/dag/policy_test.go @@ -20,6 +20,7 @@ import ( ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projcontour "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/projectcontour/contour/internal/assert" + "github.com/projectcontour/contour/internal/k8s" "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -412,7 +413,7 @@ func TestParseTimeout(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := parseTimeout(tc.duration) + got := k8s.ParseTimeout(tc.duration) assert.Equal(t, tc.want, got) }) } diff --git a/internal/dag/secret.go b/internal/dag/secret.go index 68ea8c7033e..441c24db805 100644 --- a/internal/dag/secret.go +++ b/internal/dag/secret.go @@ -24,7 +24,7 @@ import ( v1 "k8s.io/api/core/v1" ) -// CaCertificateKey is the key name for accessing TLS CA certificate bundles in Kubernetes Secrets. +// CACertificateKey is the key name for accessing TLS CA certificate bundles in Kubernetes Secrets. const CACertificateKey = "ca.crt" // isValidSecret returns true if the secret is interesting and well diff --git a/internal/dag/status.go b/internal/dag/status.go index ae5e499a5f9..601bb2087f5 100644 --- a/internal/dag/status.go +++ b/internal/dag/status.go @@ -19,39 +19,34 @@ import ( ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1" projcontour "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/projectcontour/contour/internal/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Status contains the status for an IngressRoute (valid / invalid / orphan, etc) type Status struct { - Object Object + Object k8s.Object Status string Description string Vhost string } type StatusWriter struct { - statuses map[Meta]Status -} - -type Object interface { - metav1.ObjectMetaAccessor + statuses map[k8s.FullName]Status } type ObjectStatusWriter struct { sw *StatusWriter - obj Object + obj k8s.Object values map[string]string } // WithObject returns an ObjectStatusWriter that can be used to set the state of -// the Object. The state can be set as many times as necessary. The state of the +// the object. The state can be set as many times as necessary. The state of the // object can be made permanent by calling the commit function returned from WithObject. // The caller should pass the ObjectStatusWriter to functions interested in writing status, // but keep the commit function for itself. The commit function should be either called // via a defer, or directly if statuses are being set in a loop (as defers will not fire // until the end of the function). -func (sw *StatusWriter) WithObject(obj Object) (_ *ObjectStatusWriter, commit func()) { +func (sw *StatusWriter) WithObject(obj k8s.Object) (_ *ObjectStatusWriter, commit func()) { osw := &ObjectStatusWriter{ sw: sw, obj: obj, @@ -68,9 +63,9 @@ func (sw *StatusWriter) commit(osw *ObjectStatusWriter) { return } - m := Meta{ - name: osw.obj.GetObjectMeta().GetName(), - namespace: osw.obj.GetObjectMeta().GetNamespace(), + m := k8s.FullName{ + Name: osw.obj.GetObjectMeta().GetName(), + Namespace: osw.obj.GetObjectMeta().GetNamespace(), } if _, ok := sw.statuses[m]; !ok { // only record the first status event @@ -106,7 +101,7 @@ func (osw *ObjectStatusWriter) SetValid() { // ObjectStatusWriter's values, including its status if set. This is convenient if // the object shares a relationship with its parent. The caller should arrange for // the commit function to be called to write the final status of the object. -func (osw *ObjectStatusWriter) WithObject(obj Object) (_ *ObjectStatusWriter, commit func()) { +func (osw *ObjectStatusWriter) WithObject(obj k8s.Object) (_ *ObjectStatusWriter, commit func()) { m := make(map[string]string) for k, v := range osw.values { m[k] = v diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go index c6483073f59..cc86c4ff2f0 100644 --- a/internal/dag/status_test.go +++ b/internal/dag/status_test.go @@ -1886,43 +1886,43 @@ func TestDAGIngressRouteStatus(t *testing.T) { tests := map[string]struct { objs []interface{} - want map[Meta]Status + want map[k8s.FullName]Status }{ "valid ingressroute": { objs: []interface{}{ir1, s4}, - want: map[Meta]Status{ - {name: ir1.Name, namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir1.Name, Namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, }, }, "invalid port in service": { objs: []interface{}{ir2}, - want: map[Meta]Status{ - {name: ir2.Name, namespace: ir2.Namespace}: {Object: ir2, Status: "invalid", Description: `route "/foo": service "home": port must be in the range 1-65535`, Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir2.Name, Namespace: ir2.Namespace}: {Object: ir2, Status: "invalid", Description: `route "/foo": service "home": port must be in the range 1-65535`, Vhost: "example.com"}, }, }, "root ingressroute outside of roots namespace": { objs: []interface{}{ir3}, - want: map[Meta]Status{ - {name: ir3.Name, namespace: ir3.Namespace}: {Object: ir3, Status: "invalid", Description: "root IngressRoute cannot be defined in this namespace"}, + want: map[k8s.FullName]Status{ + {Name: ir3.Name, Namespace: ir3.Namespace}: {Object: ir3, Status: "invalid", Description: "root IngressRoute cannot be defined in this namespace"}, }, }, "delegated route's match prefix does not match parent's prefix": { objs: []interface{}{ir1, ir4, s4}, - want: map[Meta]Status{ - {name: ir1.Name, namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, - {name: ir4.Name, namespace: ir4.Namespace}: {Object: ir4, Status: "invalid", Description: `the path prefix "/doesnotmatch" does not match the parent's path prefix "/prefix"`}, + want: map[k8s.FullName]Status{ + {Name: ir1.Name, Namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, + {Name: ir4.Name, Namespace: ir4.Namespace}: {Object: ir4, Status: "invalid", Description: `the path prefix "/doesnotmatch" does not match the parent's path prefix "/prefix"`}, }, }, "root ingressroute does not specify FQDN": { objs: []interface{}{ir13}, - want: map[Meta]Status{ - {name: ir13.Name, namespace: ir13.Namespace}: {Object: ir13, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, + want: map[k8s.FullName]Status{ + {Name: ir13.Name, Namespace: ir13.Namespace}: {Object: ir13, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, }, }, "self-edge produces a cycle": { objs: []interface{}{ir6}, - want: map[Meta]Status{ - {name: ir6.Name, namespace: ir6.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir6.Name, Namespace: ir6.Namespace}: { Object: ir6, Status: "invalid", Description: "root ingressroute cannot delegate to another root ingressroute", @@ -1932,14 +1932,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "child delegates to parent, producing a cycle": { objs: []interface{}{ir7, ir8}, - want: map[Meta]Status{ - {name: ir7.Name, namespace: ir7.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir7.Name, Namespace: ir7.Namespace}: { Object: ir7, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com", }, - {name: ir8.Name, namespace: ir8.Namespace}: { + {Name: ir8.Name, Namespace: ir8.Namespace}: { Object: ir8, Status: "invalid", Description: "route creates a delegation cycle: roots/parent -> roots/child -> roots/child", @@ -1948,49 +1948,49 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "route has a list of services and also delegates": { objs: []interface{}{ir9}, - want: map[Meta]Status{ - {name: ir9.Name, namespace: ir9.Namespace}: {Object: ir9, Status: "invalid", Description: `route "/foo": cannot specify services and delegate in the same route`, Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir9.Name, Namespace: ir9.Namespace}: {Object: ir9, Status: "invalid", Description: `route "/foo": cannot specify services and delegate in the same route`, Vhost: "example.com"}, }, }, "ingressroute is an orphaned route": { objs: []interface{}{ir8}, - want: map[Meta]Status{ - {name: ir8.Name, namespace: ir8.Namespace}: {Object: ir8, Status: "orphaned", Description: "this IngressRoute is not part of a delegation chain from a root IngressRoute"}, + want: map[k8s.FullName]Status{ + {Name: ir8.Name, Namespace: ir8.Namespace}: {Object: ir8, Status: "orphaned", Description: "this IngressRoute is not part of a delegation chain from a root IngressRoute"}, }, }, "ingressroute delegates to multiple ingressroutes, one is invalid": { objs: []interface{}{ir10, ir11, ir12, s6, s7}, - want: map[Meta]Status{ - {name: ir11.Name, namespace: ir11.Namespace}: {Object: ir11, Status: "valid", Description: "valid IngressRoute"}, - {name: ir12.Name, namespace: ir12.Namespace}: {Object: ir12, Status: "invalid", Description: `route "/bar": service "foo3": port must be in the range 1-65535`}, - {name: ir10.Name, namespace: ir10.Namespace}: {Object: ir10, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir11.Name, Namespace: ir11.Namespace}: {Object: ir11, Status: "valid", Description: "valid IngressRoute"}, + {Name: ir12.Name, Namespace: ir12.Namespace}: {Object: ir12, Status: "invalid", Description: `route "/bar": service "foo3": port must be in the range 1-65535`}, + {Name: ir10.Name, Namespace: ir10.Namespace}: {Object: ir10, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, }, }, "invalid parent orphans children": { objs: []interface{}{ir14, ir11}, - want: map[Meta]Status{ - {name: ir14.Name, namespace: ir14.Namespace}: {Object: ir14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, - {name: ir11.Name, namespace: ir11.Namespace}: {Object: ir11, Status: "orphaned", Description: "this IngressRoute is not part of a delegation chain from a root IngressRoute"}, + want: map[k8s.FullName]Status{ + {Name: ir14.Name, Namespace: ir14.Namespace}: {Object: ir14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, + {Name: ir11.Name, Namespace: ir11.Namespace}: {Object: ir11, Status: "orphaned", Description: "this IngressRoute is not part of a delegation chain from a root IngressRoute"}, }, }, "multi-parent children is not orphaned when one of the parents is invalid": { objs: []interface{}{ir14, ir11, ir10, s5, s6}, - want: map[Meta]Status{ - {name: ir14.Name, namespace: ir14.Namespace}: {Object: ir14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, - {name: ir11.Name, namespace: ir11.Namespace}: {Object: ir11, Status: "valid", Description: "valid IngressRoute"}, - {name: ir10.Name, namespace: ir10.Namespace}: {Object: ir10, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir14.Name, Namespace: ir14.Namespace}: {Object: ir14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, + {Name: ir11.Name, Namespace: ir11.Namespace}: {Object: ir11, Status: "valid", Description: "valid IngressRoute"}, + {Name: ir10.Name, Namespace: ir10.Namespace}: {Object: ir10, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, }, }, "invalid FQDN contains wildcard": { objs: []interface{}{ir15}, - want: map[Meta]Status{ - {name: ir15.Name, namespace: ir15.Namespace}: {Object: ir15, Status: "invalid", Description: `Spec.VirtualHost.Fqdn "example.*.com" cannot use wildcards`, Vhost: "example.*.com"}, + want: map[k8s.FullName]Status{ + {Name: ir15.Name, Namespace: ir15.Namespace}: {Object: ir15, Status: "invalid", Description: `Spec.VirtualHost.Fqdn "example.*.com" cannot use wildcards`, Vhost: "example.*.com"}, }, }, "missing service shows invalid status": { objs: []interface{}{ir16}, - want: map[Meta]Status{ - {name: ir16.Name, namespace: ir16.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir16.Name, Namespace: ir16.Namespace}: { Object: ir16, Status: "invalid", Description: `Service [invalid:8080] is invalid or missing`, @@ -2000,8 +2000,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "insert ingressroute": { objs: []interface{}{s1, ir17}, - want: map[Meta]Status{ - {name: ir17.Name, namespace: ir17.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir17.Name, Namespace: ir17.Namespace}: { Object: ir17, Status: k8s.StatusValid, Description: "valid IngressRoute", @@ -2011,14 +2011,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "insert conflicting ingressroutes due to fqdn reuse": { objs: []interface{}{ir17, ir18}, - want: map[Meta]Status{ - {name: ir17.Name, namespace: ir17.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir17.Name, Namespace: ir17.Namespace}: { Object: ir17, Status: k8s.StatusInvalid, Description: `fqdn "example.com" is used in multiple IngressRoutes: roots/example-com, roots/other-example`, Vhost: "example.com", }, - {name: ir18.Name, namespace: ir18.Namespace}: { + {Name: ir18.Name, Namespace: ir18.Namespace}: { Object: ir18, Status: k8s.StatusInvalid, Description: `fqdn "example.com" is used in multiple IngressRoutes: roots/example-com, roots/other-example`, @@ -2028,14 +2028,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "root ingress delegating to another root": { objs: []interface{}{ir20, ir21}, - want: map[Meta]Status{ - {name: ir20.Name, namespace: ir20.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir20.Name, Namespace: ir20.Namespace}: { Object: ir20, Status: k8s.StatusInvalid, Description: `fqdn "blog.containersteve.com" is used in multiple IngressRoutes: marketing/blog, roots/root-blog`, Vhost: "blog.containersteve.com", }, - {name: ir21.Name, namespace: ir21.Namespace}: { + {Name: ir21.Name, Namespace: ir21.Namespace}: { Object: ir21, Status: k8s.StatusInvalid, Description: `fqdn "blog.containersteve.com" is used in multiple IngressRoutes: marketing/blog, roots/root-blog`, @@ -2045,14 +2045,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "root ingress delegating to another root w/ different hostname": { objs: []interface{}{ir22, ir23, s8}, - want: map[Meta]Status{ - {name: ir22.Name, namespace: ir22.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir22.Name, Namespace: ir22.Namespace}: { Object: ir22, Status: k8s.StatusInvalid, Description: "root ingressroute cannot delegate to another root ingressroute", Vhost: "blog.containersteve.com", }, - {name: ir23.Name, namespace: ir23.Namespace}: { + {Name: ir23.Name, Namespace: ir23.Namespace}: { Object: ir23, Status: k8s.StatusValid, Description: `valid IngressRoute`, @@ -2065,8 +2065,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { objs: []interface{}{ sec1, s9, i1, ir24, }, - want: map[Meta]Status{ - {name: ir24.Name, namespace: ir24.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir24.Name, Namespace: ir24.Namespace}: { Object: ir24, Status: k8s.StatusValid, Description: `valid IngressRoute`, @@ -2080,8 +2080,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { sec2, ir25, }, - want: map[Meta]Status{ - {name: ir25.Name, namespace: ir25.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir25.Name, Namespace: ir25.Namespace}: { Object: ir25, Status: k8s.StatusInvalid, Description: sec2.Namespace + "/" + sec2.Name + ": certificate delegation not permitted", @@ -2095,8 +2095,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { sec2, ir26, }, - want: map[Meta]Status{ - {name: ir26.Name, namespace: ir26.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir26.Name, Namespace: ir26.Namespace}: { Object: ir26, Status: k8s.StatusInvalid, Description: sec2.Namespace + "/" + sec2.Name + ": certificate delegation not permitted", @@ -2110,8 +2110,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { sec2, proxy19, }, - want: map[Meta]Status{ - {name: proxy19.Name, namespace: proxy19.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy19.Name, Namespace: proxy19.Namespace}: { Object: proxy19, Status: k8s.StatusInvalid, Description: sec2.Namespace + "/" + sec2.Name + ": certificate delegation not permitted", @@ -2125,8 +2125,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { s10, ir27, }, - want: map[Meta]Status{ - {name: ir27.Name, namespace: ir27.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir27.Name, Namespace: ir27.Namespace}: { Object: ir27, Status: k8s.StatusValid, Description: `valid IngressRoute`, @@ -2140,8 +2140,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { s10, ir28, }, - want: map[Meta]Status{ - {name: ir28.Name, namespace: ir28.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir28.Name, Namespace: ir28.Namespace}: { Object: ir28, Status: k8s.StatusInvalid, Description: "TLS Secret [heptio-contour/ssl-cert] not found or is malformed", @@ -2153,20 +2153,20 @@ func TestDAGIngressRouteStatus(t *testing.T) { objs: []interface{}{ s1, ir29, ir30, ir31, }, - want: map[Meta]Status{ - {name: ir29.Name, namespace: ir29.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: ir29.Name, Namespace: ir29.Namespace}: { Object: ir29, Status: "valid", Description: "valid IngressRoute", Vhost: "site1.com", }, - {name: ir30.Name, namespace: ir30.Namespace}: { + {Name: ir30.Name, Namespace: ir30.Namespace}: { Object: ir30, Status: "valid", Description: "valid IngressRoute", Vhost: "site2.com", }, - {name: ir31.Name, namespace: ir31.Namespace}: { + {Name: ir31.Name, Namespace: ir31.Namespace}: { Object: ir31, Status: "valid", Description: "valid IngressRoute", @@ -2175,32 +2175,32 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "valid proxy": { objs: []interface{}{proxy1, s4}, - want: map[Meta]Status{ - {name: proxy1.Name, namespace: proxy1.Namespace}: {Object: proxy1, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy1.Name, Namespace: proxy1.Namespace}: {Object: proxy1, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, }, }, "proxy invalid port in service": { objs: []interface{}{proxy2}, - want: map[Meta]Status{ - {name: proxy2.Name, namespace: proxy2.Namespace}: {Object: proxy2, Status: "invalid", Description: `service "home": port must be in the range 1-65535`, Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy2.Name, Namespace: proxy2.Namespace}: {Object: proxy2, Status: "invalid", Description: `service "home": port must be in the range 1-65535`, Vhost: "example.com"}, }, }, "root proxy outside of roots namespace": { objs: []interface{}{proxy3}, - want: map[Meta]Status{ - {name: proxy3.Name, namespace: proxy3.Namespace}: {Object: proxy3, Status: "invalid", Description: "root HTTPProxy cannot be defined in this namespace"}, + want: map[k8s.FullName]Status{ + {Name: proxy3.Name, Namespace: proxy3.Namespace}: {Object: proxy3, Status: "invalid", Description: "root HTTPProxy cannot be defined in this namespace"}, }, }, "root proxy does not specify FQDN": { objs: []interface{}{proxy13}, - want: map[Meta]Status{ - {name: proxy13.Name, namespace: proxy13.Namespace}: {Object: proxy13, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, + want: map[k8s.FullName]Status{ + {Name: proxy13.Name, Namespace: proxy13.Namespace}: {Object: proxy13, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, }, }, "proxy self-edge produces a cycle": { objs: []interface{}{proxy6, s1}, - want: map[Meta]Status{ - {name: proxy6.Name, namespace: proxy6.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy6.Name, Namespace: proxy6.Namespace}: { Object: proxy6, Status: "invalid", Description: "root httpproxy cannot delegate to another root httpproxy", @@ -2210,14 +2210,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy child delegates to parent, producing a cycle": { objs: []interface{}{proxy7, proxy8}, - want: map[Meta]Status{ - {name: proxy7.Name, namespace: proxy7.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy7.Name, Namespace: proxy7.Namespace}: { Object: proxy7, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com", }, - {name: proxy8.Name, namespace: proxy8.Namespace}: { + {Name: proxy8.Name, Namespace: proxy8.Namespace}: { Object: proxy8, Status: "invalid", Description: "include creates a delegation cycle: roots/parent -> roots/child -> roots/child", @@ -2226,27 +2226,27 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy orphaned route": { objs: []interface{}{proxy8}, - want: map[Meta]Status{ - {name: proxy8.Name, namespace: proxy8.Namespace}: {Object: proxy8, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy"}, + want: map[k8s.FullName]Status{ + {Name: proxy8.Name, Namespace: proxy8.Namespace}: {Object: proxy8, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy"}, }, }, "proxy invalid parent orphans children": { objs: []interface{}{proxy14, proxy11}, - want: map[Meta]Status{ - {name: proxy14.Name, namespace: proxy14.Namespace}: {Object: proxy14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, - {name: proxy11.Name, namespace: proxy11.Namespace}: {Object: proxy11, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy"}, + want: map[k8s.FullName]Status{ + {Name: proxy14.Name, Namespace: proxy14.Namespace}: {Object: proxy14, Status: "invalid", Description: "Spec.VirtualHost.Fqdn must be specified"}, + {Name: proxy11.Name, Namespace: proxy11.Namespace}: {Object: proxy11, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy"}, }, }, "proxy invalid FQDN contains wildcard": { objs: []interface{}{proxy15}, - want: map[Meta]Status{ - {name: proxy15.Name, namespace: proxy15.Namespace}: {Object: proxy15, Status: "invalid", Description: `Spec.VirtualHost.Fqdn "example.*.com" cannot use wildcards`, Vhost: "example.*.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy15.Name, Namespace: proxy15.Namespace}: {Object: proxy15, Status: "invalid", Description: `Spec.VirtualHost.Fqdn "example.*.com" cannot use wildcards`, Vhost: "example.*.com"}, }, }, "proxy missing service shows invalid status": { objs: []interface{}{proxy16}, - want: map[Meta]Status{ - {name: proxy16.Name, namespace: proxy16.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy16.Name, Namespace: proxy16.Namespace}: { Object: proxy16, Status: "invalid", Description: `Service [invalid:8080] is invalid or missing`, @@ -2256,14 +2256,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "insert conflicting proxies due to fqdn reuse": { objs: []interface{}{proxy17, proxy18}, - want: map[Meta]Status{ - {name: proxy17.Name, namespace: proxy17.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy17.Name, Namespace: proxy17.Namespace}: { Object: proxy17, Status: k8s.StatusInvalid, Description: `fqdn "example.com" is used in multiple HTTPProxies: roots/example-com, roots/other-example`, Vhost: "example.com", }, - {name: proxy18.Name, namespace: proxy18.Namespace}: { + {Name: proxy18.Name, Namespace: proxy18.Namespace}: { Object: proxy18, Status: k8s.StatusInvalid, Description: `fqdn "example.com" is used in multiple HTTPProxies: roots/example-com, roots/other-example`, @@ -2273,14 +2273,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "root proxy delegating to another root": { objs: []interface{}{proxy20, proxy21}, - want: map[Meta]Status{ - {name: proxy20.Name, namespace: proxy20.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy20.Name, Namespace: proxy20.Namespace}: { Object: proxy20, Status: k8s.StatusInvalid, Description: `fqdn "blog.containersteve.com" is used in multiple HTTPProxies: marketing/blog, roots/root-blog`, Vhost: "blog.containersteve.com", }, - {name: proxy21.Name, namespace: proxy21.Namespace}: { + {Name: proxy21.Name, Namespace: proxy21.Namespace}: { Object: proxy21, Status: k8s.StatusInvalid, Description: `fqdn "blog.containersteve.com" is used in multiple HTTPProxies: marketing/blog, roots/root-blog`, @@ -2290,14 +2290,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "root proxy delegating to another root w/ different hostname": { objs: []interface{}{proxy22, proxy23, s8}, - want: map[Meta]Status{ - {name: proxy22.Name, namespace: proxy22.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy22.Name, Namespace: proxy22.Namespace}: { Object: proxy22, Status: k8s.StatusInvalid, Description: "root httpproxy cannot delegate to another root httpproxy", Vhost: "blog.containersteve.com", }, - {name: proxy23.Name, namespace: proxy23.Namespace}: { + {Name: proxy23.Name, Namespace: proxy23.Namespace}: { Object: proxy23, Status: k8s.StatusValid, Description: `valid HTTPProxy`, @@ -2307,13 +2307,13 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy delegate to another": { objs: []interface{}{proxy24, proxy25, s1, s8}, - want: map[Meta]Status{ - {name: proxy24.Name, namespace: proxy24.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy24.Name, Namespace: proxy24.Namespace}: { Object: proxy24, Status: "valid", Description: "valid HTTPProxy", }, - {name: proxy25.Name, namespace: proxy25.Namespace}: { + {Name: proxy25.Name, Namespace: proxy25.Namespace}: { Object: proxy25, Status: "valid", Description: "valid HTTPProxy", @@ -2323,8 +2323,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with mirror": { objs: []interface{}{proxy26, s1}, - want: map[Meta]Status{ - {name: proxy26.Name, namespace: proxy26.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy26.Name, Namespace: proxy26.Namespace}: { Object: proxy26, Status: "valid", Description: "valid HTTPProxy", @@ -2334,8 +2334,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with two mirrors": { objs: []interface{}{proxy27, s1}, - want: map[Meta]Status{ - {name: proxy27.Name, namespace: proxy27.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy27.Name, Namespace: proxy27.Namespace}: { Object: proxy27, Status: "invalid", Description: "only one service per route may be nominated as mirror", @@ -2345,8 +2345,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with two prefix conditions on route": { objs: []interface{}{proxy32, s1}, - want: map[Meta]Status{ - {name: proxy32.Name, namespace: proxy32.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy32.Name, Namespace: proxy32.Namespace}: { Object: proxy32, Status: "invalid", Description: "route: more than one prefix is not allowed in a condition block", @@ -2356,13 +2356,13 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with two prefix conditions as an include": { objs: []interface{}{proxy33, proxy34, s1}, - want: map[Meta]Status{ - {name: proxy33.Name, namespace: proxy33.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy33.Name, Namespace: proxy33.Namespace}: { Object: proxy33, Status: "invalid", Description: "include: more than one prefix is not allowed in a condition block", Vhost: "example.com", - }, {name: proxy34.Name, namespace: proxy34.Namespace}: { + }, {Name: proxy34.Name, Namespace: proxy34.Namespace}: { Object: proxy34, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", @@ -2371,8 +2371,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with prefix conditions on route that does not start with slash": { objs: []interface{}{proxy35, s1}, - want: map[Meta]Status{ - {name: proxy35.Name, namespace: proxy35.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy35.Name, Namespace: proxy35.Namespace}: { Object: proxy35, Status: "invalid", Description: "route: prefix conditions must start with /, api was supplied", @@ -2382,13 +2382,13 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "proxy with include prefix that does not start with slash": { objs: []interface{}{proxy36, proxy34, s1}, - want: map[Meta]Status{ - {name: proxy36.Name, namespace: proxy36.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy36.Name, Namespace: proxy36.Namespace}: { Object: proxy36, Status: "invalid", Description: "include: prefix conditions must start with /, api was supplied", Vhost: "example.com", - }, {name: proxy34.Name, namespace: proxy34.Namespace}: { + }, {Name: proxy34.Name, Namespace: proxy34.Namespace}: { Object: proxy34, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", @@ -2397,51 +2397,51 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "duplicate route condition headers": { objs: []interface{}{proxy28, s4}, - want: map[Meta]Status{ - {name: proxy28.Name, namespace: proxy28.Namespace}: {Object: proxy28, Status: "invalid", Description: "cannot specify duplicate header 'exact match' conditions in the same route", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy28.Name, Namespace: proxy28.Namespace}: {Object: proxy28, Status: "invalid", Description: "cannot specify duplicate header 'exact match' conditions in the same route", Vhost: "example.com"}, }, }, "duplicate valid route condition headers": { objs: []interface{}{proxy31, s4}, - want: map[Meta]Status{ - {name: proxy31.Name, namespace: proxy31.Namespace}: {Object: proxy31, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy31.Name, Namespace: proxy31.Namespace}: {Object: proxy31, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, }, }, "duplicate include condition headers": { objs: []interface{}{proxy29, proxy30, s4}, - want: map[Meta]Status{ - {name: proxy29.Name, namespace: proxy29.Namespace}: {Object: proxy29, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, - {name: proxy30.Name, namespace: proxy30.Namespace}: {Object: proxy30, Status: "invalid", Description: "cannot specify duplicate header 'exact match' conditions in the same route", Vhost: ""}, + want: map[k8s.FullName]Status{ + {Name: proxy29.Name, Namespace: proxy29.Namespace}: {Object: proxy29, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, + {Name: proxy30.Name, Namespace: proxy30.Namespace}: {Object: proxy30, Status: "invalid", Description: "cannot specify duplicate header 'exact match' conditions in the same route", Vhost: ""}, }, }, "duplicate path conditions on an include": { objs: []interface{}{proxy41, proxy41a, proxy41b, s4, s11, s12}, - want: map[Meta]Status{ - {name: proxy41.Name, namespace: proxy41.Namespace}: {Object: proxy41, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, - {name: proxy41a.Name, namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, - {name: proxy41b.Name, namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + want: map[k8s.FullName]Status{ + {Name: proxy41.Name, Namespace: proxy41.Namespace}: {Object: proxy41, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, + {Name: proxy41a.Name, Namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + {Name: proxy41b.Name, Namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, }, }, "duplicate header conditions on an include": { objs: []interface{}{proxy42, proxy41a, proxy41b, s4, s11, s12}, - want: map[Meta]Status{ - {name: proxy42.Name, namespace: proxy42.Namespace}: {Object: proxy42, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, - {name: proxy41a.Name, namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, - {name: proxy41b.Name, namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + want: map[k8s.FullName]Status{ + {Name: proxy42.Name, Namespace: proxy42.Namespace}: {Object: proxy42, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, + {Name: proxy41a.Name, Namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + {Name: proxy41b.Name, Namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, }, }, "duplicate header+path conditions on an include": { objs: []interface{}{proxy43, proxy41a, proxy41b, s4, s11, s12}, - want: map[Meta]Status{ - {name: proxy43.Name, namespace: proxy43.Namespace}: {Object: proxy43, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, - {name: proxy41a.Name, namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, - {name: proxy41b.Name, namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + want: map[k8s.FullName]Status{ + {Name: proxy43.Name, Namespace: proxy43.Namespace}: {Object: proxy43, Status: "invalid", Description: "duplicate conditions defined on an include", Vhost: "example.com"}, + {Name: proxy41a.Name, Namespace: proxy41a.Namespace}: {Object: proxy41a, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, + {Name: proxy41b.Name, Namespace: proxy41b.Namespace}: {Object: proxy41b, Status: "orphaned", Description: "this HTTPProxy is not part of a delegation chain from a root HTTPProxy", Vhost: ""}, }, }, "httpproxy with invalid tcpproxy": { objs: []interface{}{proxy37, s1}, - want: map[Meta]Status{ - {name: proxy37.Name, namespace: proxy37.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy37.Name, Namespace: proxy37.Namespace}: { Object: proxy37, Status: "invalid", Description: "tcpproxy: cannot specify services and include in the same httpproxy", @@ -2451,8 +2451,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy with empty tcpproxy": { objs: []interface{}{proxy37a, s1}, - want: map[Meta]Status{ - {name: proxy37a.Name, namespace: proxy37a.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy37a.Name, Namespace: proxy37a.Namespace}: { Object: proxy37a, Status: "invalid", Description: "tcpproxy: either services or inclusion must be specified", @@ -2462,8 +2462,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy w/ missing include": { objs: []interface{}{proxy38, s1}, - want: map[Meta]Status{ - {name: proxy38.Name, namespace: proxy38.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy38.Name, Namespace: proxy38.Namespace}: { Object: proxy38, Status: "invalid", Description: "tcpproxy: include roots/foo not found", @@ -2473,14 +2473,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy w/ includes another root": { objs: []interface{}{proxy38, proxy39, s1}, - want: map[Meta]Status{ - {name: proxy38.Name, namespace: proxy38.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy38.Name, Namespace: proxy38.Namespace}: { Object: proxy38, Status: "invalid", Description: "root httpproxy cannot delegate to another root httpproxy", Vhost: "passthrough.example.com", }, - {name: proxy39.Name, namespace: proxy39.Namespace}: { + {Name: proxy39.Name, Namespace: proxy39.Namespace}: { Object: proxy39, Status: "valid", Description: "valid HTTPProxy", @@ -2490,14 +2490,14 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy w/ includes valid child": { objs: []interface{}{proxy38, proxy40, s1}, - want: map[Meta]Status{ - {name: proxy38.Name, namespace: proxy38.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy38.Name, Namespace: proxy38.Namespace}: { Object: proxy38, Status: "valid", Description: "valid HTTPProxy", Vhost: "passthrough.example.com", }, - {name: proxy40.Name, namespace: proxy40.Namespace}: { + {Name: proxy40.Name, Namespace: proxy40.Namespace}: { Object: proxy40, Status: "valid", Description: "valid HTTPProxy", @@ -2507,8 +2507,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ missing include": { objs: []interface{}{proxy44, s1}, - want: map[Meta]Status{ - {name: proxy44.Name, namespace: proxy44.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy44.Name, Namespace: proxy44.Namespace}: { Object: proxy44, Status: "invalid", Description: "include roots/child not found", @@ -2518,8 +2518,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy w/ missing service": { objs: []interface{}{proxy45}, - want: map[Meta]Status{ - {name: proxy45.Name, namespace: proxy45.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy45.Name, Namespace: proxy45.Namespace}: { Object: proxy45, Status: "invalid", Description: "tcpproxy: service roots/not-found/8080: not found", @@ -2529,8 +2529,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy missing tls": { objs: []interface{}{proxy46}, - want: map[Meta]Status{ - {name: proxy46.Name, namespace: proxy46.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy46.Name, Namespace: proxy46.Namespace}: { Object: proxy46, Status: "invalid", Description: "tcpproxy: missing tls.passthrough or tls.secretName", @@ -2540,8 +2540,8 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "httpproxy w/ tcpproxy missing service": { objs: []interface{}{sec1, s1, proxy47}, - want: map[Meta]Status{ - {name: proxy47.Name, namespace: proxy47.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy47.Name, Namespace: proxy47.Namespace}: { Object: proxy47, Status: "invalid", Description: "Service [missing:9000] is invalid or missing", @@ -2551,30 +2551,30 @@ func TestDAGIngressRouteStatus(t *testing.T) { }, "ingressroute first, then identical httpproxy": { objs: []interface{}{ir1, proxy1, s4}, - want: map[Meta]Status{ - {name: ir1.Name, namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, - {name: proxy1.Name, namespace: proxy1.Namespace}: {Object: proxy1, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, + want: map[k8s.FullName]Status{ + {Name: ir1.Name, Namespace: ir1.Namespace}: {Object: ir1, Status: "valid", Description: "valid IngressRoute", Vhost: "example.com"}, + {Name: proxy1.Name, Namespace: proxy1.Namespace}: {Object: proxy1, Status: "valid", Description: "valid HTTPProxy", Vhost: "example.com"}, }, }, "valid HTTPProxy.TCPProxy": { objs: []interface{}{proxy48root, proxy48child, s1, sec1}, - want: map[Meta]Status{ - {name: proxy48root.Name, namespace: proxy48root.Namespace}: {Object: proxy48root, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, - {name: proxy48child.Name, namespace: proxy48child.Namespace}: {Object: proxy48child, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy48root.Name, Namespace: proxy48root.Namespace}: {Object: proxy48root, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, + {Name: proxy48child.Name, Namespace: proxy48child.Namespace}: {Object: proxy48child, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, }, }, "valid HTTPProxy.TCPProxy - plural": { objs: []interface{}{proxy48rootplural, proxy48child, s1, sec1}, - want: map[Meta]Status{ - {name: proxy48rootplural.Name, namespace: proxy48rootplural.Namespace}: {Object: proxy48rootplural, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, - {name: proxy48child.Name, namespace: proxy48child.Namespace}: {Object: proxy48child, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, + want: map[k8s.FullName]Status{ + {Name: proxy48rootplural.Name, Namespace: proxy48rootplural.Namespace}: {Object: proxy48rootplural, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, + {Name: proxy48child.Name, Namespace: proxy48child.Namespace}: {Object: proxy48child, Status: "valid", Description: "valid HTTPProxy", Vhost: "tcpproxy.example.com"}, }, }, // issue 2309, each route must have at least one service "invalid HTTPProxy due to empty route.service": { objs: []interface{}{proxy49, s1}, - want: map[Meta]Status{ - {name: proxy49.Name, namespace: proxy49.Namespace}: { + want: map[k8s.FullName]Status{ + {Name: proxy49.Name, Namespace: proxy49.Namespace}: { Object: proxy49, Status: "invalid", Description: "route.services must have at least one entry", diff --git a/internal/k8s/objectmeta.go b/internal/k8s/objectmeta.go new file mode 100644 index 00000000000..59e1f2fdd46 --- /dev/null +++ b/internal/k8s/objectmeta.go @@ -0,0 +1,37 @@ +// Copyright © 2020 VMware +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package k8s + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Object is any Kubernetes object that has an ObjectMeta. +type Object interface { + metav1.ObjectMetaAccessor +} + +// FullName holds the name and namespace of a Kubernetes object. +type FullName struct { + Name, Namespace string +} + +// ToFullName returns the FullName of any given Kubernetes object. +func ToFullName(obj Object) FullName { + m := obj.GetObjectMeta() + return FullName{ + Name: m.GetName(), + Namespace: m.GetNamespace(), + } +} diff --git a/internal/k8s/timeout.go b/internal/k8s/timeout.go new file mode 100644 index 00000000000..752085855db --- /dev/null +++ b/internal/k8s/timeout.go @@ -0,0 +1,43 @@ +// Copyright © 2020 VMware +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package k8s + +import "time" + +// ParseTimeout parses timeouts we pass in various places in a standard way. +func ParseTimeout(timeout string) time.Duration { + if timeout == "" { + // Blank is interpreted as no timeout specified, use envoy defaults + // By default envoy applies a 15 second timeout to all backend requests. + // The explicit value 0 turns off the timeout, implying "never time out" + // https://www.envoyproxy.io/docs/envoy/v1.5.0/api-v2/rds.proto#routeaction + return 0 + } + + // Interpret "infinity" explicitly as an infinite timeout, which envoy config + // expects as a timeout of 0. This could be specified with the duration string + // "0s" but want to give an explicit out for operators. + if timeout == "infinity" { + return -1 + } + + d, err := time.ParseDuration(timeout) + if err != nil { + // TODO(cmalonty) plumb a logger in here so we can log this error. + // Assuming infinite duration is going to surprise people less for + // a not-parseable duration than a implicit 15 second one. + return -1 + } + return d +}