Skip to content

Commit

Permalink
Show URL for ingress objects (#707)
Browse files Browse the repository at this point in the history
* Show URL for ingress objects

* Rename helper files

* Print Ingress first

* Update test example
  • Loading branch information
andresmgot authored Oct 9, 2018
1 parent c6bccdf commit 475b0ff
Show file tree
Hide file tree
Showing 14 changed files with 636 additions and 257 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { IHTTPIngressPath, IIngressRule, IIngressSpec, IResource } from "shared/types";
import { GetURLItemFromIngress } from "./AccessURLIngressHelper";

describe("GetURLItemFromIngress", () => {
interface Itest {
description: string;
hosts: string[];
paths: IHTTPIngressPath[];
tlsHosts: string[];
expectedURLs: string[];
}
const tests: Itest[] = [
{
description: "it should show the host without port",
hosts: ["foo.bar"],
paths: [],
tlsHosts: [],
expectedURLs: ["http://foo.bar"],
},
{
description: "it should show several hosts without port",
hosts: ["foo.bar", "not-foo.bar"],
paths: [],
tlsHosts: [],
expectedURLs: ["http://foo.bar", "http://not-foo.bar"],
},
{
description: "it should show the host with the different paths",
hosts: ["foo.bar"],
paths: [{ path: "/one" }, { path: "/two" }],
tlsHosts: [],
expectedURLs: ["http://foo.bar/one", "http://foo.bar/two"],
},
{
description: "it should show TLS hosts with https",
hosts: ["foo.bar", "not-foo.bar"],
paths: [],
tlsHosts: ["foo.bar"],
expectedURLs: ["https://foo.bar", "http://not-foo.bar"],
},
];
tests.forEach(test => {
it(test.description, () => {
const ingress = {
metadata: {
name: "foo",
},
spec: {
rules: [],
} as IIngressSpec,
} as IResource;
test.hosts.forEach(h => {
const rule = {
host: h,
http: {
paths: [],
},
} as IIngressRule;
if (test.paths.length > 0) {
rule.http.paths = test.paths;
}
ingress.spec.rules.push(rule);
});
if (test.tlsHosts.length > 0) {
ingress.spec.tls = [
{
hosts: test.tlsHosts,
},
];
}
const ingressItem = GetURLItemFromIngress(ingress);
expect(ingressItem.isLink).toBe(true);
expect(ingressItem.URLs).toEqual(test.expectedURLs);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IIngressSpec, IIngressTLS, IResource } from "shared/types";
import { IURLItem } from "./IURLItem";

// URLs returns the list of URLs obtained from the service status
function URLs(ingress: IResource): string[] {
const spec = ingress.spec as IIngressSpec;
const res: string[] = [];
spec.rules.forEach(r => {
if (r.http.paths.length > 0) {
r.http.paths.forEach(p => {
res.push(getURL(r.host, spec.tls, p.path));
});
} else {
res.push(getURL(r.host, spec.tls));
}
});
return res;
}

// getURL returns a full URL based on a hostname, a TLS configuration and a optional path
function getURL(hostname: string, tls?: IIngressTLS[], path?: string) {
// If the hostname is configured within the TLS hosts it will use HTTPS
const protocol =
tls &&
tls.some(tlsRule => {
return tlsRule.hosts.indexOf(hostname) > -1;
})
? "https"
: "http";
return `${protocol}://${hostname}${path || ""}`;
}

export function GetURLItemFromIngress(ingress: IResource) {
return {
name: ingress.metadata.name,
type: "Ingress",
isLink: true,
URLs: URLs(ingress),
} as IURLItem;
}
Original file line number Diff line number Diff line change
@@ -1,111 +1,28 @@
import { shallow } from "enzyme";
import context from "jest-plugin-context";
import * as React from "react";

import { IResource, IServiceSpec, IServiceStatus } from "shared/types";
import AccessURLItem from "./AccessURLItem";
import { IURLItem } from "./IURLItem";

context("when the status is empty", () => {
const service = {
metadata: {
name: "foo",
},
spec: {
type: "LoadBalancer",
ports: [{ port: 8080 }],
} as IServiceSpec,
status: {
loadBalancer: {},
} as IServiceStatus,
} as IResource;

it("should show a Pending text", () => {
const wrapper = shallow(<AccessURLItem loadBalancerService={service} />);
expect(wrapper.text()).toContain("Pending");
expect(wrapper).toMatchSnapshot();
});

it("should not include a link", () => {
const wrapper = shallow(<AccessURLItem loadBalancerService={service} />);
expect(wrapper.find(".ServiceItem")).toExist();
const link = wrapper.find(".ServiceItem").find("a");
expect(link).not.toExist();
});
it("should show only a message (without a link) if the item is not a link", () => {
const item = { name: "foo", isLink: false, URLs: ["Pending"] } as IURLItem;
const wrapper = shallow(<AccessURLItem URLItem={item} />);
expect(wrapper.text()).toContain("Pending");
expect(wrapper).toMatchSnapshot();
const link = wrapper.find(".ServiceItem").find("a");
expect(link).not.toExist();
});

context("when the status is populated", () => {
interface Itest {
description: string;
ports: any[];
ingress: any[];
expectedURLs: string[];
}
const tests: Itest[] = [
{
description: "it should show the IP and port if it's not known",
ports: [{ port: 8080 }],
ingress: [{ ip: "1.2.3.4" }],
expectedURLs: ["http://1.2.3.4:8080"],
},
{
description: "it should show the hostname and port if it's not known",
ports: [{ port: 8080 }],
ingress: [{ hostname: "1.2.3.4" }],
expectedURLs: ["http://1.2.3.4:8080"],
},
{
description: "it should show the IP and skip the port if it's known",
ports: [{ port: 80 }],
ingress: [{ ip: "1.2.3.4" }],
expectedURLs: ["http://1.2.3.4"],
},
{
description: "it should show the https URL if the port is 443",
ports: [{ port: 443 }],
ingress: [{ ip: "1.2.3.4" }],
expectedURLs: ["https://1.2.3.4"],
},
{
description: "it should show several URLs if there are multipe ports",
ports: [{ port: 8080 }, { port: 8081 }],
ingress: [{ ip: "1.2.3.4" }],
expectedURLs: ["http://1.2.3.4:8080", "http://1.2.3.4:8081"],
},
{
description: "it should show several URLs if there are ingress ports",
ports: [{ port: 8080 }, { port: 8081 }],
ingress: [{ ip: "1.2.3.4" }, { hostname: "foo.bar" }],
expectedURLs: [
"http://1.2.3.4:8080",
"http://1.2.3.4:8081",
"http://foo.bar:8080",
"http://foo.bar:8081",
],
},
];
tests.forEach(test => {
it(test.description, () => {
const service = {
metadata: {
name: "foo",
},
spec: {
type: "LoadBalancer",
ports: test.ports,
},
status: {
loadBalancer: {
ingress: test.ingress,
},
} as IServiceStatus,
} as IResource;
const wrapper = shallow(<AccessURLItem loadBalancerService={service} />);
test.expectedURLs.forEach(url => {
expect(wrapper.find(".ServiceItem")).toExist();
const link = wrapper.find(".ServiceItem").find("a");
expect(link).toExist();
expect(wrapper.text()).toContain(url);
});
});
});
it("should show only an URL with a link", () => {
const item = {
name: "foo",
isLink: true,
URLs: ["http://1.2.3.4:8080", "https://foo.bar"],
} as IURLItem;
const wrapper = shallow(<AccessURLItem URLItem={item} />);
expect(wrapper.text()).toContain("http://1.2.3.4:8080");
expect(wrapper.text()).toContain("https://foo.bar");
expect(wrapper).toMatchSnapshot();
const link = wrapper.find(".ServiceItem").find("a");
expect(link).toExist();
});
Original file line number Diff line number Diff line change
@@ -1,82 +1,38 @@
import * as React from "react";

import { IResource, IServiceSpec, IServiceStatus } from "../../../../shared/types";
import "./AccessURLItem.css";
import { IURLItem } from "./IURLItem";

interface IAccessURLItem {
loadBalancerService: IResource;
URLItem: IURLItem;
}

class AccessURLItem extends React.Component<IAccessURLItem> {
public render() {
const { loadBalancerService } = this.props;
const isLink = this.isLink();
return (
<tr>
<td>{loadBalancerService.metadata.name}</td>
<td>{loadBalancerService.spec.type}</td>
<td>
{this.URLs().map(l => (
<span
key={l}
className={`ServiceItem ${
isLink ? "ServiceItem--with-link" : ""
} type-small margin-r-small padding-tiny padding-h-normal`}
>
{isLink ? (
<a className="padding-tiny padding-h-normal" href={l} target="_blank">
{l}
</a>
) : (
l
)}
</span>
))}
</td>
</tr>
);
}

// isLink returns true if there are any link in the Item
private isLink(): boolean {
if (
this.props.loadBalancerService.status.loadBalancer.ingress &&
this.props.loadBalancerService.status.loadBalancer.ingress.length
) {
return true;
}
return false;
}

// URLs returns the list of URLs obtained from the service status
private URLs(): string[] {
const URLs: string[] = [];
const { loadBalancerService } = this.props;
const status: IServiceStatus = loadBalancerService.status;
if (status.loadBalancer.ingress && status.loadBalancer.ingress.length) {
status.loadBalancer.ingress.forEach(i => {
(loadBalancerService.spec as IServiceSpec).ports.forEach(port => {
if (i.hostname) {
URLs.push(this.getURL(i.hostname, port.port));
}
if (i.ip) {
URLs.push(this.getURL(i.ip, port.port));
}
});
});
} else {
URLs.push("Pending");
}
return URLs;
}

// getURL returns a full URL adding the protocol and the port if needed
private getURL(base: string, port: number) {
const protocol = port === 443 ? "https" : "http";
// Only show the port in the URL if it's not a standard HTTP/HTTPS port
const portSuffix = port === 443 || port === 80 ? "" : `:${port}`;
return `${protocol}://${base}${portSuffix}`;
}
}
const AccessURLItem: React.SFC<IAccessURLItem> = props => {
const { URLItem } = props;
return (
<tr>
<td>{URLItem.name}</td>
<td>{URLItem.type}</td>
<td>
{URLItem.URLs.map(l => (
<span
key={l}
className={`ServiceItem ${
URLItem.isLink ? "ServiceItem--with-link" : ""
} type-small margin-r-small padding-tiny padding-h-normal`}
>
{URLItem.isLink ? (
<a className="padding-tiny padding-h-normal" href={l} target="_blank">
{l}
</a>
) : (
l
)}
</span>
))}
</td>
</tr>
);
};

export default AccessURLItem;
Loading

0 comments on commit 475b0ff

Please sign in to comment.