Merge pull request #91268 from cmluciano/cml/ingv1describe

ingress: Add v1 describers for Ingress and IngressClass
This commit is contained in:
Kubernetes Prow Robot 2020-06-19 18:22:40 -07:00 committed by GitHub
commit bcdb3c568e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 512 additions and 54 deletions

View File

@ -187,6 +187,8 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]ResourceDescr
{Group: extensionsv1beta1.GroupName, Kind: "Ingress"}: &IngressDescriber{c},
{Group: networkingv1beta1.GroupName, Kind: "Ingress"}: &IngressDescriber{c},
{Group: networkingv1beta1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c},
{Group: networkingv1.GroupName, Kind: "Ingress"}: &IngressDescriber{c},
{Group: networkingv1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c},
{Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c},
{Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
{Group: appsv1.GroupName, Kind: "StatefulSet"}: &StatefulSetDescriber{c},
@ -2343,24 +2345,36 @@ func describeSecret(secret *corev1.Secret) (string, error) {
}
type IngressDescriber struct {
clientset.Interface
client clientset.Interface
}
func (i *IngressDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
c := i.NetworkingV1beta1().Ingresses(namespace)
ing, err := c.Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return "", err
var events *corev1.EventList
// try ingress/v1 first (v1.19) and fallback to ingress/v1beta if an err occurs
netV1, err := i.client.NetworkingV1().Ingresses(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil {
if describerSettings.ShowEvents {
events, _ = i.client.CoreV1().Events(namespace).Search(scheme.Scheme, netV1)
}
return i.describeIngressV1(netV1, events)
}
return i.describeIngress(ing, describerSettings)
netV1beta1, err := i.client.NetworkingV1beta1().Ingresses(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil {
if describerSettings.ShowEvents {
events, _ = i.client.CoreV1().Events(namespace).Search(scheme.Scheme, netV1beta1)
}
return i.describeIngressV1beta1(netV1beta1, events)
}
return "", err
}
func (i *IngressDescriber) describeBackend(ns string, backend *networkingv1beta1.IngressBackend) string {
endpoints, err := i.CoreV1().Endpoints(ns).Get(context.TODO(), backend.ServiceName, metav1.GetOptions{})
func (i *IngressDescriber) describeBackendV1beta1(ns string, backend *networkingv1beta1.IngressBackend) string {
endpoints, err := i.client.CoreV1().Endpoints(ns).Get(context.TODO(), backend.ServiceName, metav1.GetOptions{})
if err != nil {
return fmt.Sprintf("<error: %v>", err)
}
service, err := i.CoreV1().Services(ns).Get(context.TODO(), backend.ServiceName, metav1.GetOptions{})
service, err := i.client.CoreV1().Services(ns).Get(context.TODO(), backend.ServiceName, metav1.GetOptions{})
if err != nil {
return fmt.Sprintf("<error: %v>", err)
}
@ -2381,7 +2395,93 @@ func (i *IngressDescriber) describeBackend(ns string, backend *networkingv1beta1
return formatEndpoints(endpoints, sets.NewString(spName))
}
func (i *IngressDescriber) describeIngress(ing *networkingv1beta1.Ingress, describerSettings DescriberSettings) (string, error) {
func (i *IngressDescriber) describeBackendV1(ns string, backend *networkingv1.IngressBackend) string {
if backend.Service != nil {
sb := serviceBackendStringer(backend.Service)
endpoints, err := i.client.CoreV1().Endpoints(ns).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{})
if err != nil {
return fmt.Sprintf("%v (<error: %v>)", sb, err)
}
service, err := i.client.CoreV1().Services(ns).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{})
if err != nil {
return fmt.Sprintf("%v(<error: %v>)", sb, err)
}
spName := ""
for i := range service.Spec.Ports {
sp := &service.Spec.Ports[i]
if backend.Service.Port.Number != 0 && backend.Service.Port.Number == sp.Port {
spName = sp.Name
} else if len(backend.Service.Port.Name) > 0 && backend.Service.Port.Name == sp.Name {
spName = sp.Name
}
}
ep := formatEndpoints(endpoints, sets.NewString(spName))
return fmt.Sprintf("%s\t %s)", sb, ep)
}
if backend.Resource != nil {
ic := backend.Resource
return fmt.Sprintf("APIGroup: %v, Kind: %v, Name: %v", *ic.APIGroup, ic.Kind, ic.Name)
}
return ""
}
func (i *IngressDescriber) describeIngressV1(ing *networkingv1.Ingress, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%v\n", ing.Name)
w.Write(LEVEL_0, "Namespace:\t%v\n", ing.Namespace)
w.Write(LEVEL_0, "Address:\t%v\n", loadBalancerStatusStringer(ing.Status.LoadBalancer, true))
def := ing.Spec.DefaultBackend
ns := ing.Namespace
if def == nil {
// Ingresses that don't specify a default backend inherit the
// default backend in the kube-system namespace.
def = &networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "default-http-backend",
Port: networkingv1.ServiceBackendPort{
Number: 80,
},
},
}
ns = metav1.NamespaceSystem
}
w.Write(LEVEL_0, "Default backend:\t%s\n", i.describeBackendV1(ns, def))
if len(ing.Spec.TLS) != 0 {
describeIngressTLSV1(w, ing.Spec.TLS)
}
w.Write(LEVEL_0, "Rules:\n Host\tPath\tBackends\n")
w.Write(LEVEL_1, "----\t----\t--------\n")
count := 0
for _, rules := range ing.Spec.Rules {
if rules.HTTP == nil {
continue
}
count++
host := rules.Host
if len(host) == 0 {
host = "*"
}
w.Write(LEVEL_1, "%s\t\n", host)
for _, path := range rules.HTTP.Paths {
w.Write(LEVEL_2, "\t%s \t%s\n", path.Path, i.describeBackendV1(ing.Namespace, &path.Backend))
}
}
if count == 0 {
w.Write(LEVEL_1, "\t%s %s\n", "*", "*", i.describeBackendV1(ns, def))
}
printAnnotationsMultiline(w, "Annotations", ing.Annotations)
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
func (i *IngressDescriber) describeIngressV1beta1(ing *networkingv1beta1.Ingress, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%v\n", ing.Name)
@ -2398,14 +2498,15 @@ func (i *IngressDescriber) describeIngress(ing *networkingv1beta1.Ingress, descr
}
ns = metav1.NamespaceSystem
}
w.Write(LEVEL_0, "Default backend:\t%s (%s)\n", backendStringer(def), i.describeBackend(ns, def))
w.Write(LEVEL_0, "Default backend:\t%s (%s)\n", backendStringer(def), i.describeBackendV1beta1(ns, def))
if len(ing.Spec.TLS) != 0 {
describeIngressTLS(w, ing.Spec.TLS)
describeIngressTLSV1beta1(w, ing.Spec.TLS)
}
w.Write(LEVEL_0, "Rules:\n Host\tPath\tBackends\n")
w.Write(LEVEL_1, "----\t----\t--------\n")
count := 0
for _, rules := range ing.Spec.Rules {
if rules.HTTP == nil {
continue
}
@ -2416,25 +2517,33 @@ func (i *IngressDescriber) describeIngress(ing *networkingv1beta1.Ingress, descr
}
w.Write(LEVEL_1, "%s\t\n", host)
for _, path := range rules.HTTP.Paths {
w.Write(LEVEL_2, "\t%s \t%s (%s)\n", path.Path, backendStringer(&path.Backend), i.describeBackend(ing.Namespace, &path.Backend))
w.Write(LEVEL_2, "\t%s \t%s (%s)\n", path.Path, backendStringer(&path.Backend), i.describeBackendV1beta1(ing.Namespace, &path.Backend))
}
}
if count == 0 {
w.Write(LEVEL_1, "%s\t%s \t%s (%s)\n", "*", "*", backendStringer(def), i.describeBackend(ns, def))
w.Write(LEVEL_1, "%s\t%s \t%s (%s)\n", "*", "*", backendStringer(def), i.describeBackendV1beta1(ns, def))
}
printAnnotationsMultiline(w, "Annotations", ing.Annotations)
if describerSettings.ShowEvents {
events, _ := i.CoreV1().Events(ing.Namespace).Search(scheme.Scheme, ing)
if events != nil {
DescribeEvents(events, w)
}
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
func describeIngressTLS(w PrefixWriter, ingTLS []networkingv1beta1.IngressTLS) {
func describeIngressTLSV1beta1(w PrefixWriter, ingTLS []networkingv1beta1.IngressTLS) {
w.Write(LEVEL_0, "TLS:\n")
for _, t := range ingTLS {
if t.SecretName == "" {
w.Write(LEVEL_1, "SNI routes %v\n", strings.Join(t.Hosts, ","))
} else {
w.Write(LEVEL_1, "%v terminates %v\n", t.SecretName, strings.Join(t.Hosts, ","))
}
}
}
func describeIngressTLSV1(w PrefixWriter, ingTLS []networkingv1.IngressTLS) {
w.Write(LEVEL_0, "TLS:\n")
for _, t := range ingTLS {
if t.SecretName == "" {
@ -2446,19 +2555,30 @@ func describeIngressTLS(w PrefixWriter, ingTLS []networkingv1beta1.IngressTLS) {
}
type IngressClassDescriber struct {
clientset.Interface
client clientset.Interface
}
func (i *IngressClassDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
c := i.NetworkingV1beta1().IngressClasses()
ic, err := c.Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return "", err
var events *corev1.EventList
// try IngressClass/v1 first (v1.19) and fallback to IngressClass/v1beta if an err occurs
netV1, err := i.client.NetworkingV1().IngressClasses().Get(context.TODO(), name, metav1.GetOptions{})
if err == nil {
if describerSettings.ShowEvents {
events, _ = i.client.CoreV1().Events(namespace).Search(scheme.Scheme, netV1)
}
return i.describeIngressClassV1(netV1, events)
}
return i.describeIngressClass(ic, describerSettings)
netV1beta1, err := i.client.NetworkingV1beta1().IngressClasses().Get(context.TODO(), name, metav1.GetOptions{})
if err == nil {
if describerSettings.ShowEvents {
events, _ = i.client.CoreV1().Events(namespace).Search(scheme.Scheme, netV1beta1)
}
return i.describeIngressClassV1beta1(netV1beta1, events)
}
return "", err
}
func (i *IngressClassDescriber) describeIngressClass(ic *networkingv1beta1.IngressClass, describerSettings DescriberSettings) (string, error) {
func (i *IngressClassDescriber) describeIngressClassV1beta1(ic *networkingv1beta1.IngressClass, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", ic.Name)
@ -2474,7 +2594,32 @@ func (i *IngressClassDescriber) describeIngressClass(ic *networkingv1beta1.Ingre
w.Write(LEVEL_1, "Kind:\t%v\n", ic.Spec.Parameters.Kind)
w.Write(LEVEL_1, "Name:\t%v\n", ic.Spec.Parameters.Name)
}
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
func (i *IngressClassDescriber) describeIngressClassV1(ic *networkingv1.IngressClass, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", ic.Name)
printLabelsMultiline(w, "Labels", ic.Labels)
printAnnotationsMultiline(w, "Annotations", ic.Annotations)
w.Write(LEVEL_0, "Controller:\t%v\n", ic.Spec.Controller)
if ic.Spec.Parameters != nil {
w.Write(LEVEL_0, "Parameters:\n")
if ic.Spec.Parameters.APIGroup != nil {
w.Write(LEVEL_1, "APIGroup:\t%v\n", *ic.Spec.Parameters.APIGroup)
}
w.Write(LEVEL_1, "Kind:\t%v\n", ic.Spec.Parameters.Kind)
w.Write(LEVEL_1, "Name:\t%v\n", ic.Spec.Parameters.Name)
}
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
@ -4896,6 +5041,21 @@ func extractCSRStatus(conditions []string, certificateBytes []byte) string {
return status
}
// backendStringer behaves just like a string interface and converts the given backend to a string.
func serviceBackendStringer(backend *networkingv1.IngressServiceBackend) string {
if backend == nil {
return ""
}
var bPort string
if backend.Port.Number != 0 {
sNum := int64(backend.Port.Number)
bPort = strconv.FormatInt(sNum, 10)
} else {
bPort = backend.Port.Name
}
return fmt.Sprintf("%v:%v", backend.Name, bPort)
}
// backendStringer behaves just like a string interface and converts the given backend to a string.
func backendStringer(backend *networkingv1beta1.IngressBackend) string {
if backend == nil {

View File

@ -1677,12 +1677,11 @@ func TestDescribeDeployment(t *testing.T) {
}
func TestDescribeIngress(t *testing.T) {
defaultBackend := networkingv1beta1.IngressBackend{
backendV1beta1 := networkingv1beta1.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
fakeClient := fake.NewSimpleClientset(&networkingv1beta1.Ingress{
v1beta1 := fake.NewSimpleClientset(&networkingv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
@ -1694,6 +1693,278 @@ func TestDescribeIngress(t *testing.T) {
IngressRuleValue: networkingv1beta1.IngressRuleValue{
HTTP: &networkingv1beta1.HTTPIngressRuleValue{
Paths: []networkingv1beta1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendV1beta1,
},
},
},
},
},
},
},
})
backendV1 := networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "default-backend",
Port: networkingv1.ServiceBackendPort{
Number: 80,
},
},
}
netv1 := fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendV1,
},
},
},
},
},
},
},
})
backendResource := networkingv1.IngressBackend{
Resource: &corev1.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("example.com"),
Kind: "foo",
Name: "bar",
},
}
tests := map[string]struct {
input *fake.Clientset
output string
}{
"IngressRule.HTTP.Paths.Backend.Service v1beta1": {
input: v1beta1,
output: `Name: bar
Namespace: foo
Address:
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>)
Annotations: <none>
Events: <none>` + "\n",
},
"IngressRule.HTTP.Paths.Backend.Service v1": {
input: netv1,
output: `Name: bar
Namespace: foo
Address:
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>)
Annotations: <none>
Events: <none>` + "\n",
},
"IngressRule.HTTP.Paths.Backend.Resource v1": {
input: fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendResource,
},
},
},
},
},
},
},
}),
output: `Name: bar
Namespace: foo
Address:
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo APIGroup: example.com, Kind: foo, Name: bar
Annotations: <none>
Events: <none>` + "\n",
},
"Spec.DefaultBackend.Service & IngressRule.HTTP.Paths.Backend.Service v1": {
input: fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
DefaultBackend: &backendV1,
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendV1,
},
},
},
},
},
},
},
}),
output: `Name: bar
Namespace: foo
Address:
Default backend: default-backend:80 (<error: endpoints "default-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>)
Annotations: <none>
Events: <none>` + "\n",
},
"Spec.DefaultBackend.Resource & IngressRule.HTTP.Paths.Backend.Resource v1": {
input: fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
DefaultBackend: &backendResource,
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendResource,
},
},
},
},
},
},
},
}),
output: `Name: bar
Namespace: foo
Address:
Default backend: APIGroup: example.com, Kind: foo, Name: bar
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo APIGroup: example.com, Kind: foo, Name: bar
Annotations: <none>
Events: <none>` + "\n",
},
"Spec.DefaultBackend.Resource & IngressRule.HTTP.Paths.Backend.Service v1": {
input: fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
DefaultBackend: &backendResource,
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: backendV1,
},
},
},
},
},
},
},
}),
output: `Name: bar
Namespace: foo
Address:
Default backend: APIGroup: example.com, Kind: foo, Name: bar
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>)
Annotations: <none>
Events: <none>` + "\n",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
c := &describeClient{T: t, Namespace: "foo", Interface: test.input}
i := IngressDescriber{c}
out, err := i.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if out != test.output {
t.Logf(out)
t.Logf(test.output)
t.Errorf("expected: \n%q\n but got output: \n%q\n", test.output, out)
}
})
}
}
func TestDescribeIngressV1(t *testing.T) {
defaultBackend := networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "default-backend",
Port: networkingv1.ServiceBackendPort{
Number: 80,
},
},
}
fakeClient := fake.NewSimpleClientset(&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -3097,27 +3368,6 @@ func TestDescribeResourceQuota(t *testing.T) {
}
func TestDescribeIngressClass(t *testing.T) {
fake := fake.NewSimpleClientset(&networkingv1beta1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: "example-class",
},
Spec: networkingv1beta1.IngressClassSpec{
Controller: "example.com/controller",
Parameters: &corev1.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("v1"),
Kind: "ConfigMap",
Name: "example-parameters",
},
},
})
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := IngressClassDescriber{c}
out, err := d.Describe("", "example-class", DescriberSettings{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedOut := `Name: example-class
Labels: <none>
Annotations: <none>
@ -3127,9 +3377,57 @@ Parameters:
Kind: ConfigMap
Name: example-parameters` + "\n"
if out != expectedOut {
t.Logf(out)
t.Errorf("expected : %q\n but got output:\n %q", expectedOut, out)
tests := map[string]struct {
input *fake.Clientset
output string
}{
"basic IngressClass (v1beta1)": {
input: fake.NewSimpleClientset(&networkingv1beta1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: "example-class",
},
Spec: networkingv1beta1.IngressClassSpec{
Controller: "example.com/controller",
Parameters: &corev1.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("v1"),
Kind: "ConfigMap",
Name: "example-parameters",
},
},
}),
output: expectedOut,
},
"basic IngressClass (v1)": {
input: fake.NewSimpleClientset(&networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: "example-class",
},
Spec: networkingv1.IngressClassSpec{
Controller: "example.com/controller",
Parameters: &corev1.TypedLocalObjectReference{
APIGroup: utilpointer.StringPtr("v1"),
Kind: "ConfigMap",
Name: "example-parameters",
},
},
}),
output: expectedOut,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
c := &describeClient{T: t, Namespace: "foo", Interface: test.input}
d := IngressClassDescriber{c}
out, err := d.Describe("", "example-class", DescriberSettings{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if out != expectedOut {
t.Logf(out)
t.Errorf("expected : %q\n but got output:\n %q", test.output, out)
}
})
}
}