Add ingress status for ClusterIP and NodePort Service Type

This commit is contained in:
mlec 2025-01-03 16:10:04 +01:00 committed by GitHub
parent 845d0b5ac7
commit 6d3a685d5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 505 additions and 58 deletions

View file

@ -398,11 +398,17 @@ providers:
_Optional, Default: ""_
The Kubernetes service to copy status from.
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the ingresses.
Format: `namespace/servicename`.
The Kubernetes service to copy status from,
depending on the service type:
- **ClusterIP:** The ExternalIPs of the service will be propagated to the ingress status.
- **NodePort:** The ExternalIP addresses of the nodes in the cluster will be propagated to the ingress status.
- **LoadBalancer:** The IPs from the service's `loadBalancer.status` field (which contains the endpoints provided by the load balancer) will be propagated to the ingress status.
When using third-party tools such as External-DNS, this option enables the copying of external service IPs to the ingress resources.
```yaml tab="File (YAML)"
providers:
kubernetesIngress:

View file

@ -39,25 +39,25 @@ which in turn creates the resulting routers, services, handlers, etc.
## Configuration Options
<!-- markdownlint-disable MD013 -->
| Field | Description | Default | Required |
|:------|:----------------------------------------------------------|:---------------------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesIngress.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | | No |
| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.<br />It alleviates the requirement of giving Traefik the rights to look IngressClasses up.<br />Ignore Ingresses with IngressClass.<br />Annotations are not affected by this option. | false | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.hostname` | Hostname used for Kubernetes Ingress endpoints. | "" | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.ip` | This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.publishedService` | The Kubernetes service to copy status from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the ingresses. | "" | No |
| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No |
| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No |
| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
| Field | Description | Default | Required |
|:-----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
| `providers.kubernetesIngress.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | | No |
| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.<br />It alleviates the requirement of giving Traefik the rights to look IngressClasses up.<br />Ignore Ingresses with IngressClass.<br />Annotations are not affected by this option. | false | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.hostname` | Hostname used for Kubernetes Ingress endpoints. | "" | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.ip` | This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
| `providers.kubernetesIngress.`<br />`ingressEndpoint.publishedService` | The Kubernetes service to copy status from.<br />More information [here](#ingressendpointpublishedservice). | "" | No |
| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No |
| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No |
| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
<!-- markdownlint-enable MD013 -->
@ -99,6 +99,38 @@ providers:
--providers.kubernetesingress.endpoint=http://localhost:8080
```
### `ingressEndpoint.publishedService`
Format: `namespace/servicename`.
The Kubernetes service to copy status from,
depending on the service type:
- **ClusterIP:** The ExternalIPs of the service will be propagated to the ingress status.
- **NodePort:** The ExternalIP addresses of the nodes in the cluster will be propagated to the ingress status.
- **LoadBalancer:** The IPs from the service's `loadBalancer.status` field (which contains the endpoints provided by the load balancer) will be propagated to the ingress status.
When using third-party tools such as External-DNS, this option enables the copying of external service IPs to the ingress resources.
```yaml tab="File (YAML)"
providers:
kubernetesIngress:
ingressEndpoint:
publishedService: "namespace/foo-service"
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesIngress.ingressEndpoint]
publishedService = "namespace/foo-service"
# ...
```
```bash tab="CLI"
--providers.kubernetesingress.ingressendpoint.publishedservice=namespace/foo-service
```
## Routing Configuration
See the dedicated section in [routing](../../../../routing/providers/kubernetes-ingress.md).

View file

@ -131,7 +131,7 @@ func Test_parseServiceConfig(t *testing.T) {
Secure: true,
HTTPOnly: true,
SameSite: "none",
Path: String("foobar"),
Path: pointer("foobar"),
},
},
ServersScheme: "protocol",
@ -150,7 +150,7 @@ func Test_parseServiceConfig(t *testing.T) {
Service: &ServiceIng{
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Path: String("/"),
Path: pointer("/"),
},
},
PassHostHeader: pointer(true),

View file

@ -10,7 +10,6 @@ import (
"slices"
"time"
"github.com/hashicorp/go-version"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
"github.com/traefik/traefik/v3/pkg/types"
@ -58,7 +57,6 @@ type clientWrapper struct {
disableIngressClassInformer bool // Deprecated.
disableClusterScopeInformer bool
watchedNamespaces []string
serverVersion *version.Version
}
// newInClusterClient returns a new Provider client that is expected to run
@ -141,19 +139,6 @@ func newClientImpl(clientset kclientset.Interface) *clientWrapper {
// WatchAll starts namespace-specific controllers for all relevant kinds.
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
// Get and store the serverVersion for future use.
serverVersionInfo, err := c.clientset.Discovery().ServerVersion()
if err != nil {
return nil, fmt.Errorf("could not retrieve server version: %w", err)
}
serverVersion, err := version.NewVersion(serverVersionInfo.GitVersion)
if err != nil {
return nil, fmt.Errorf("could not parse server version: %w", err)
}
c.serverVersion = serverVersion
eventCh := make(chan interface{}, 1)
eventHandler := &k8s.ResourceEventHandler{Ev: eventCh}
@ -175,7 +160,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
for _, ns := range namespaces {
factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(matchesLabelSelector))
_, err = factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
_, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
@ -230,7 +215,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if !c.disableIngressClassInformer || !c.disableClusterScopeInformer {
c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod)
_, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
_, err := c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}

View file

@ -0,0 +1,72 @@
---
kind: Service
apiVersion: v1
metadata:
name: published-service
namespace: default
spec:
type: ClusterIP
externalIPs:
- 1.2.3.4
- 5.6.7.8
ports:
- port: 9090
protocol: TCP
name: first
- port: 9091
protocol: TCP
name: second
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: foo
namespace: default
spec:
rules:
- host: "*.foo.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service1
port:
number: 80
---
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: default
spec:
ports:
- port: 80
clusterIP: 10.0.0.1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1-abc
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- 10.10.0.1
conditions:
ready: true

View file

@ -0,0 +1,84 @@
---
kind: Service
apiVersion: v1
metadata:
name: published-service
namespace: default
spec:
type: LoadBalancer
ports:
- port: 9090
protocol: TCP
name: first
- port: 9091
protocol: TCP
name: second
status:
loadBalancer:
ingress:
- ip: 1.2.3.4
ports:
- port: 9090
protocol: TCP
- port: 9091
protocol: TCP
- ip: 5.6.7.8
ports:
- port: 9090
protocol: TCP
- port: 9091
protocol: TCP
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: foo
namespace: default
spec:
rules:
- host: "*.foo.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service1
port:
number: 80
---
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: default
spec:
ports:
- port: 80
clusterIP: 10.0.0.1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1-abc
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- 10.10.0.1
conditions:
ready: true

View file

@ -0,0 +1,92 @@
---
kind: Node
apiVersion: v1
metadata:
name: node1
status:
addresses:
- type: ExternalIP
address: 1.2.3.4
---
kind: Node
apiVersion: v1
metadata:
name: node2
status:
addresses:
- type: ExternalIP
address: 5.6.7.8
---
kind: Service
apiVersion: v1
metadata:
name: published-service
namespace: default
spec:
type: NodePort
ports:
- port: 9190
protocol: TCP
name: first
nodePort: 9090
- port: 9191
protocol: TCP
name: second
nodePort: 9091
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: foo
namespace: default
spec:
rules:
- host: "*.foo.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service1
port:
number: 80
---
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: default
spec:
ports:
- port: 80
clusterIP: 10.0.0.1
---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
name: service1-abc
labels:
kubernetes.io/service-name: service1
addressType: IPv4
ports:
- port: 8080
name: ""
endpoints:
- addresses:
- 10.10.0.1
conditions:
ready: true

View file

@ -247,6 +247,10 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
continue
}
if err := p.updateIngressStatus(ingress, client); err != nil {
logger.Error().Err(err).Msg("Error while updating ingress status")
}
rtConfig, err := parseRouterConfig(ingress.Annotations)
if err != nil {
logger.Error().Err(err).Msg("Failed to parse annotations")
@ -305,10 +309,6 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
routers := map[string][]*dynamic.Router{}
for _, rule := range ingress.Spec.Rules {
if err := p.updateIngressStatus(ingress, client); err != nil {
logger.Error().Err(err).Msg("Error while updating ingress status")
}
if rule.HTTP == nil {
continue
}
@ -407,22 +407,75 @@ func (p *Provider) updateIngressStatus(ing *netv1.Ingress, k8sClient Client) err
return fmt.Errorf("cannot get service %s, received error: %w", p.IngressEndpoint.PublishedService, err)
}
if exists && service.Status.LoadBalancer.Ingress == nil {
// service exists, but has no Load Balancer status
log.Debug().Msgf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService)
return nil
}
if !exists {
return fmt.Errorf("missing service: %s", p.IngressEndpoint.PublishedService)
}
ingresses, err := convertSlice[netv1.IngressLoadBalancerIngress](service.Status.LoadBalancer.Ingress)
if err != nil {
return err
var ingressStatus []netv1.IngressLoadBalancerIngress
switch service.Spec.Type {
case corev1.ServiceTypeLoadBalancer:
if service.Status.LoadBalancer.Ingress == nil {
// service exists, but has no Load Balancer status
log.Debug().Msgf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService)
return nil
}
ingressStatus, err = convertSlice[netv1.IngressLoadBalancerIngress](service.Status.LoadBalancer.Ingress)
if err != nil {
return fmt.Errorf("converting ingress loadbalancer status: %w", err)
}
case corev1.ServiceTypeClusterIP:
var ports []netv1.IngressPortStatus
for _, port := range service.Spec.Ports {
ports = append(ports, netv1.IngressPortStatus{
Port: port.Port,
Protocol: port.Protocol,
})
}
for _, ip := range service.Spec.ExternalIPs {
ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{
IP: ip,
Ports: ports,
})
}
case corev1.ServiceTypeNodePort:
if p.DisableClusterScopeResources {
return errors.New("node port service type is not supported when cluster scope resources lookup is disabled")
}
nodes, _, err := k8sClient.GetNodes()
if err != nil {
return fmt.Errorf("getting nodes: %w", err)
}
var ports []netv1.IngressPortStatus
for _, port := range service.Spec.Ports {
ports = append(ports, netv1.IngressPortStatus{
Port: port.NodePort,
Protocol: port.Protocol,
})
}
for _, node := range nodes {
for _, address := range node.Status.Addresses {
if address.Type == corev1.NodeExternalIP {
ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{
IP: address.Address,
Ports: ports,
})
}
}
}
default:
return fmt.Errorf("unsupported service type: %s", service.Spec.Type)
}
return k8sClient.UpdateIngressStatus(ing, ingresses)
return k8sClient.UpdateIngressStatus(ing, ingressStatus)
}
func (p *Provider) shouldProcessIngress(ingress *netv1.Ingress, ingressClasses []*netv1.IngressClass) bool {

View file

@ -4,28 +4,31 @@ import (
"context"
"errors"
"math"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
"github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
corev1 "k8s.io/api/core/v1"
netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
)
var _ provider.Provider = (*Provider)(nil)
func pointer[T any](v T) *T { return &v }
func String(v string) *string { return &v }
func TestLoadConfigurationFromIngresses(t *testing.T) {
testCases := []struct {
desc string
@ -134,7 +137,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
Name: "foobar",
Secure: true,
HTTPOnly: true,
Path: String("/"),
Path: pointer("/"),
},
},
Servers: []dynamic.Server{
@ -2082,3 +2085,123 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
})
}
}
func TestIngressEndpointPublishedService(t *testing.T) {
testCases := []struct {
desc string
disableClusterScopeResources bool
expected []netv1.IngressLoadBalancerIngress
}{
{
desc: "Published Service ClusterIP",
expected: []netv1.IngressLoadBalancerIngress{
{
IP: "1.2.3.4",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
{
IP: "5.6.7.8",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
},
},
{
desc: "Published Service LoadBalancer",
expected: []netv1.IngressLoadBalancerIngress{
{
IP: "1.2.3.4",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
{
IP: "5.6.7.8",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
},
},
{
desc: "Published Service NodePort",
disableClusterScopeResources: true,
},
{
desc: "Published Service NodePort",
expected: []netv1.IngressLoadBalancerIngress{
{
IP: "1.2.3.4",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
{
IP: "5.6.7.8",
Ports: []netv1.IngressPortStatus{
{Port: 9090, Protocol: "TCP"},
{Port: 9091, Protocol: "TCP"},
},
},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
k8sObjects := readResources(t, []string{generateTestFilename(test.desc)})
kubeClient := kubefake.NewClientset(k8sObjects...)
client := newClientImpl(kubeClient)
stopCh := make(chan struct{})
eventCh, err := client.WatchAll(nil, stopCh)
require.NoError(t, err)
if k8sObjects != nil {
// just wait for the first event
<-eventCh
}
p := Provider{
DisableClusterScopeResources: test.disableClusterScopeResources,
IngressEndpoint: &EndpointIngress{
PublishedService: "default/published-service",
},
}
p.loadConfigurationFromIngresses(context.Background(), client)
ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(context.Background(), "foo", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, test.expected, ingress.Status.LoadBalancer.Ingress)
})
}
}
func readResources(t *testing.T, paths []string) []runtime.Object {
t.Helper()
var k8sObjects []runtime.Object
for _, path := range paths {
yamlContent, err := os.ReadFile(filepath.FromSlash(path))
if err != nil {
panic(err)
}
objects := k8s.MustParseYaml(yamlContent)
k8sObjects = append(k8sObjects, objects...)
}
return k8sObjects
}