import { UISref } from '@uirouter/react'; import { head, sortBy } from 'lodash'; import React from 'react'; import { Dropdown, MenuItem } from 'react-bootstrap'; import type { Application, IApplicationSecurityGroup, ILoadBalancer, ILoadBalancerActionsProps, ILoadBalancerDetailsSectionProps, IUseDetailsHookProps, SecurityGroupReader, UseDetailsResult, } from '@spinnaker/core'; import { AccountTag, CollapsibleSection, CopyToClipboard, FirewallLabels, HealthCounts, ManagedResourceDetailsIndicator, REST, SubnetReader, timestamp, useDataSource, useDeckRuntimeServices, } from '@spinnaker/core'; import { LoadBalancerActions } from './LoadBalancerActions'; import type { IAmazonApplicationLoadBalancer, IAmazonLoadBalancer, IAmazonLoadBalancerSourceData, IApplicationLoadBalancerSourceData, IClassicLoadBalancerSourceData, IListenerAction, ITargetGroup, } from '../../domain'; import { VpcTag } from '../../vpc/VpcTag'; type ILoadBalancerStateParams = IUseDetailsHookProps['loadBalancerParams']; interface ILoadAmazonLoadBalancerDetailsProps { app: Application; autoClose: () => void; loadBalancerParams: ILoadBalancerStateParams; loadBalancers?: ILoadBalancer[]; securityGroupReader?: SecurityGroupReader; } interface IActionDetails extends IListenerAction { targetGroup?: ITargetGroup; } function ipAddressTypeDescription(ipAddressType: string): string { if (ipAddressType === 'dualstack') { return 'IPv4 and IPv6'; } if (ipAddressType === 'ipv4') { return 'IPv4'; } return ''; } function getElbProtocol(loadBalancer: IAmazonLoadBalancer, details: IAmazonLoadBalancerSourceData): string { const classic = details as IClassicLoadBalancerSourceData; if (classic.listenerDescriptions?.some((listener: any) => listener.listener.protocol === 'HTTPS')) { return 'https:'; } const applicationLoadBalancer = loadBalancer as IAmazonApplicationLoadBalancer; if (applicationLoadBalancer.listeners?.some((listener) => listener.protocol === 'HTTPS')) { return 'https:'; } return 'http:'; } function buildListenerRows( loadBalancer: IAmazonApplicationLoadBalancer, ): Array<{ in: string; actions: IActionDetails[] }> { const listeners = loadBalancer.listeners || []; const rows: Array<{ in: string; actions: IActionDetails[] }> = []; listeners.forEach((listener) => { (listener.rules || []).forEach((rule) => { let inMatch = [ listener.protocol, (rule.conditions.find((condition) => condition.field === 'host-header') || { values: [''] }).values[0], listener.port, ] .filter((value) => value) .join(':'); const path = (rule.conditions.find((condition) => condition.field === 'path-pattern') || { values: [] }) .values[0]; if (path) { inMatch = `${inMatch}${path}`; } const actions = (rule.actions || []).map((action) => { const actionDetails = { ...action } as IActionDetails; if (actionDetails.type === 'forward') { actionDetails.targetGroup = (loadBalancer.targetGroups || []).find( (targetGroup) => targetGroup.name === actionDetails.targetGroupName, ); } return actionDetails; }); rows.push({ in: inMatch, actions }); }); }); return rows; } function resolveSecurityGroups( app: Application, loadBalancer: IAmazonLoadBalancer, params: ILoadBalancerStateParams, securityGroupReader?: SecurityGroupReader, ): IApplicationSecurityGroup[] { const securityGroupIds = loadBalancer.elb?.securityGroups || loadBalancer.securityGroups || []; return securityGroupIds .map((securityGroupId: string) => { if (securityGroupReader) { return securityGroupReader.getApplicationSecurityGroup(app, params.accountId, params.region, securityGroupId); } return app['securityGroupsIndex']?.[params.accountId]?.[params.region]?.[securityGroupId]; }) .filter(Boolean) .sort((a: any, b: any) => a.name.localeCompare(b.name)); } async function loadSubnetDetails(loadBalancer: IAmazonLoadBalancer): Promise { if (!loadBalancer.subnets?.length) { loadBalancer.subnetDetails = loadBalancer.subnetDetails || []; return; } loadBalancer.subnetDetails = await Promise.all( loadBalancer.subnets.map((subnetId) => SubnetReader.getSubnetByIdAndProvider(subnetId, loadBalancer.provider)), ); } export async function loadAmazonLoadBalancerDetails({ app, autoClose, loadBalancerParams, loadBalancers, securityGroupReader, }: ILoadAmazonLoadBalancerDetailsProps): Promise { const { accountId, name, region } = loadBalancerParams; const loadBalancer = ((loadBalancers || app.loadBalancers.data).find( (candidate: ILoadBalancer) => candidate.name === name && candidate.region === region && candidate.account === accountId, ) as IAmazonLoadBalancer) || undefined; if (!loadBalancer) { autoClose(); return undefined; } const details = (await REST('/loadBalancers') .path(accountId, region, name) .query({ provider: 'aws' }) .get()) as IAmazonLoadBalancerSourceData[]; if (details.length) { loadBalancer.elb = details[0]; loadBalancer.elb.vpcId = loadBalancer.elb.vpcId || loadBalancer.elb.vpcid; loadBalancer.account = accountId; loadBalancer.securityGroups = resolveSecurityGroups( app, loadBalancer, loadBalancerParams, securityGroupReader, ) as any; loadBalancer.elbProtocol = getElbProtocol(loadBalancer, details[0]); loadBalancer.ipAddressTypeDescription = ipAddressTypeDescription( (details[0] as IApplicationLoadBalancerSourceData).ipAddressType, ); loadBalancer.listenerRows = loadBalancer.loadBalancerType === 'classic' ? [] : buildListenerRows(loadBalancer as IAmazonApplicationLoadBalancer); await loadSubnetDetails(loadBalancer); } return loadBalancer; } export function useAmazonLoadBalancerDetails({ app, loadBalancerParams, autoClose, }: IUseDetailsHookProps): UseDetailsResult { const { securityGroupReader } = useDeckRuntimeServices(); const dataSource = app.getDataSource('loadBalancers'); const { data: loadBalancers, loaded, refresh, error } = useDataSource(dataSource); const [data, setData] = React.useState(); const [loadingDetails, setLoadingDetails] = React.useState(false); const [detailsError, setDetailsError] = React.useState(null); const autoCloseRef = React.useRef(autoClose); const isMountedRef = React.useRef(true); const { accountId, name, provider, region, vpcId } = loadBalancerParams; React.useEffect(() => { return () => { isMountedRef.current = false; }; }, []); React.useEffect(() => { autoCloseRef.current = autoClose; }, [autoClose]); const loadDetails = React.useCallback(async () => { if (!loaded || error) { return; } setLoadingDetails(true); setDetailsError(null); try { const loadBalancer = await loadAmazonLoadBalancerDetails({ app, autoClose: () => autoCloseRef.current(), loadBalancerParams: { accountId, name, provider, region, vpcId }, loadBalancers, securityGroupReader, }); if (isMountedRef.current) { setData(loadBalancer); } } catch (e) { if (isMountedRef.current) { setDetailsError(e?.message || String(e)); } } finally { if (isMountedRef.current) { setLoadingDetails(false); } } }, [accountId, app, error, loadBalancers, loaded, name, provider, region, securityGroupReader, vpcId]); React.useEffect(() => { loadDetails(); }, [loadDetails]); return { data, loading: !loaded || loadingDetails, error: error || detailsError, refetch: async () => refresh(), }; } function sortServerGroups(a: any, b: any): number { if (a.isDisabled !== b.isDisabled) { return a.isDisabled ? 1 : -1; } return b.name.localeCompare(a.name); } function sortTargetGroups(a: any, b: any): number { if (a.isDisabled !== b.isDisabled) { return a.isDisabled ? 1 : -1; } return b.name.localeCompare(a.name); } export function AmazonLoadBalancerActions({ app, loadBalancer }: ILoadBalancerActionsProps): JSX.Element { const amazonLoadBalancer = loadBalancer as IAmazonLoadBalancer; const insightActions = (amazonLoadBalancer.elb as any)?.insightActions || []; return ( <> {insightActions.length > 0 && ( Insight {insightActions.map((action: any) => ( {action.label} ))} )} ); } export function AmazonLoadBalancerDetailsSection({ app, loadBalancer }: ILoadBalancerDetailsSectionProps): JSX.Element { const amazonLoadBalancer = loadBalancer as IAmazonLoadBalancer; const elb = amazonLoadBalancer.elb || ({} as IAmazonLoadBalancerSourceData); const dnsName = elb.dnsname || (elb as any).dnsName; const subnetPurpose = head((amazonLoadBalancer.subnetDetails || []).map((subnet) => subnet.purpose)) || ''; return ( <> {amazonLoadBalancer.isManaged && ( )}
Created
{elb.createdTime ? timestamp(elb.createdTime) : '-'}
In
{amazonLoadBalancer.region}
VPC
Subnet
{subnetPurpose}
Scheme
{amazonLoadBalancer.scheme || '-'}
{amazonLoadBalancer.loadBalancerType &&
Type
} {amazonLoadBalancer.loadBalancerType &&
{amazonLoadBalancer.loadBalancerType}
} {amazonLoadBalancer.ipAddressTypeDescription &&
IP Type
} {amazonLoadBalancer.ipAddressTypeDescription &&
{amazonLoadBalancer.ipAddressTypeDescription}
}
{!!elb.availabilityZones?.length && (
Availability Zones
    {elb.availabilityZones.map((availabilityZone) => (
  • {availabilityZone}
  • ))}
)} {!!amazonLoadBalancer.serverGroups?.length && (
Server Groups
    {amazonLoadBalancer.serverGroups .slice() .sort(sortServerGroups) .map((serverGroup: any) => (
  • {serverGroup.name}
  • ))}
)} {!!(amazonLoadBalancer as IAmazonApplicationLoadBalancer).targetGroups?.length && (
Target Groups
    {(amazonLoadBalancer as IAmazonApplicationLoadBalancer).targetGroups .slice() .sort(sortTargetGroups) .map((targetGroup) => (
  • {targetGroup.name}
  • ))}
)} {dnsName && (
DNS Name
{dnsName} {' '}
)}
); } export function AmazonLoadBalancerStatusSection({ loadBalancer }: ILoadBalancerDetailsSectionProps): JSX.Element { return ( {loadBalancer.loadBalancerType === 'classic' ? ( ) : ( Select a target group to check the instance health status from the view of the target group. )} ); } function renderListenerAction(action: IActionDetails, loadBalancer: IAmazonLoadBalancer): JSX.Element { if (action.type === 'redirect') { const redirectConfig = action.redirectConfig; return ( {redirectConfig.protocol !== '#{protocol}' && `${redirectConfig.protocol}:`} {redirectConfig.host !== '#{host}' && ` ${redirectConfig.host} `} {redirectConfig.port !== '#{port}' && ` ${redirectConfig.port} `} {redirectConfig.path !== '/#{path}' && ` ${redirectConfig.path} `} {redirectConfig.query !== '#{query}' && ` ?${redirectConfig.query} `}({redirectConfig.statusCode}) ); } if (action.type === 'authenticate-oidc') { const clientId = action.authenticateOidcConfig?.clientId; return ( {clientId} ); } if (action.targetGroupName && action.targetGroup) { return (