Merge pull request 'feat(frontend): show how to mount an EFS file system on an EC2 instance' (#31) from feat/efs-mount-instructions into main
CI / checks (push) Successful in 27m54s
CI / checks (push) Successful in 27m54s
Reviewed-on: #31
This commit was merged in pull request #31.
This commit is contained in:
@@ -102,3 +102,22 @@ test('cannot attach until the file system has a mount target', async () => {
|
||||
expect(await screen.findByText(/No mount targets yet/)).toBeInTheDocument()
|
||||
expect(within(screen.getByRole('form', {name: 'Attach to a function'})).getByRole('button', {name: 'Attach'})).toBeDisabled()
|
||||
})
|
||||
|
||||
test('shows how to mount the file system on an EC2 instance once a mount target is available', async () => {
|
||||
renderPanel(fileSystem())
|
||||
|
||||
const commands = within(await screen.findByLabelText('Mount on an EC2 instance'))
|
||||
expect(commands.getByText('fs-1.efs.us-east-1.amazonaws.com')).toBeInTheDocument()
|
||||
expect(commands.getByText('sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-1.efs.us-east-1.amazonaws.com:/ efs')).toBeInTheDocument()
|
||||
expect(commands.getByText('sudo mount -t efs fs-1:/ efs')).toBeInTheDocument()
|
||||
expect(commands.getByText('Mount by IP address in us-east-1a')).toBeInTheDocument()
|
||||
expect(commands.getByText(/10\.0\.0\.10:\/ efs$/)).toBeInTheDocument()
|
||||
expect(commands.getByRole('button', {name: 'Copy mount with the nfs client command'})).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('holds the mount commands back while the mount target is still creating', async () => {
|
||||
renderPanel(fileSystem({mountTargets: [{id: 'fsmt-1', subnetId: 'subnet-a', availabilityZone: 'us-east-1a', ipAddress: '10.0.0.10', state: 'creating'}]}))
|
||||
|
||||
expect(await screen.findByText('The mount commands appear once a mount target is available.')).toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Mount on an EC2 instance')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import {RefreshCw} from 'lucide-react'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {useEffect, useState} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {CopyValueButton} from '@/components/CopyValueButton'
|
||||
import {
|
||||
attachFileSystemToFunction,
|
||||
createAccessPoint,
|
||||
@@ -26,6 +27,7 @@ type AccessPoint = {id: string; arn: string | null; name: string | null; path: s
|
||||
type MountedFunction = {name: string; accessPointId: string | null; mountPath: string | null}
|
||||
|
||||
const EMPTY_ACCESS_POINT = {name: '', path: '/', uid: '1000', gid: '1000', permissions: '755'}
|
||||
const NFS_OPTIONS = 'nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport'
|
||||
|
||||
export function FileSystemPanel({resource, runtimeReachable}: Props) {
|
||||
const accountId = useAccountId()
|
||||
@@ -40,6 +42,11 @@ export function FileSystemPanel({resource, runtimeReachable}: Props) {
|
||||
queryKey: detailKey,
|
||||
queryFn: ({signal}) => getCloudResource('aws', 'filesystems', resource?.id ?? '', signal),
|
||||
enabled: Boolean(resource) && runtimeReachable,
|
||||
// A new mount target is creating until its NFS server answers, a few seconds.
|
||||
refetchInterval: (query) => {
|
||||
const targets = (query.state.data?.metadata.mountTargets ?? []) as MountTarget[]
|
||||
return targets.some((mountTarget) => mountTarget.state === 'creating') ? 2_000 : false
|
||||
},
|
||||
})
|
||||
const functionsQuery = useQuery({
|
||||
queryKey: cloudQueryKeys.resources('aws', 'serverless'),
|
||||
@@ -185,7 +192,36 @@ export function FileSystemPanel({resource, runtimeReachable}: Props) {
|
||||
</form>}
|
||||
{accessPoints.length > 0 && <p className="muted compact-text efs-note">Lambda needs a VPC to mount a file system. A function not yet in one is put in this file system's mount target subnets with the default security group.</p>}
|
||||
|
||||
<Ec2MountInstructions fileSystemId={String(metadata.fileSystemId ?? fileSystem.id)} region={fileSystem.region ?? 'us-east-1'} mountTargets={mountTargets}/>
|
||||
|
||||
{notice && <p className="cognito-outcome cognito-outcome--ok" role="status">{notice}</p>}
|
||||
{error instanceof Error && <p className="error-text compact-text" role="alert">{error.message}</p>}
|
||||
</section>
|
||||
}
|
||||
|
||||
function Ec2MountInstructions({fileSystemId, region, mountTargets}: {fileSystemId: string; region: string; mountTargets: MountTarget[]}) {
|
||||
const available = mountTargets.filter((mountTarget) => mountTarget.state === 'available' && mountTarget.ipAddress)
|
||||
const dnsName = `${fileSystemId}.efs.${region}.amazonaws.com`
|
||||
const command = (label: string, value: string) => <div className="efs-command" key={label}>
|
||||
<span className="muted">{label}</span>
|
||||
<code>{value}</code>
|
||||
<CopyValueButton value={value} label={`${label.toLowerCase()} command`}/>
|
||||
</div>
|
||||
|
||||
return <>
|
||||
<div className="input-row resource-table-bar"><div><p className="eyebrow">EC2 instances</p><span className="muted">An instance in this file system's VPC mounts it over NFS through a mount target, as on AWS.</span></div></div>
|
||||
{available.length === 0
|
||||
? <p className="muted compact-text efs-note">{mountTargets.length === 0 ? 'Add a mount target to mount the file system on an instance.' : 'The mount commands appear once a mount target is available.'}</p>
|
||||
: <div className="efs-commands" aria-label="Mount on an EC2 instance">
|
||||
<p className="muted compact-text">DNS name <code>{dnsName}</code></p>
|
||||
{command('Install the NFS client on Amazon Linux', 'sudo dnf install -y nfs-utils')}
|
||||
{command('Install the NFS client on Debian or Ubuntu', 'sudo apt-get install -y nfs-common')}
|
||||
{command('Make a mount point', 'sudo mkdir -p efs')}
|
||||
{command('Mount with the NFS client', `sudo mount -t nfs4 -o ${NFS_OPTIONS} ${dnsName}:/ efs`)}
|
||||
{command('Mount with the EFS mount helper', `sudo mount -t efs ${fileSystemId}:/ efs`)}
|
||||
{available.map((mountTarget) => command(`Mount by IP address in ${mountTarget.availabilityZone ?? mountTarget.id}`,
|
||||
`sudo mount -t nfs4 -o ${NFS_OPTIONS} ${mountTarget.ipAddress}:/ efs`))}
|
||||
<p className="muted compact-text">The EFS mount helper is the <code>amazon-efs-utils</code> package. TheCloud serves plain NFS, so its <code>tls</code> and <code>accesspoint</code> options are not supported. A function's files are in its access point's root directory, such as <code>efs/my-function</code>.</p>
|
||||
</div>}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -6691,3 +6691,8 @@ tr[aria-current="true"] td { background: var(--hover-md); }
|
||||
.graphql-span { grid-column: span 2; }
|
||||
.graphql-target { overflow-wrap: anywhere; }
|
||||
@media (max-width: 640px) { .graphql-span { grid-column: auto; } .graphql-endpoints { grid-template-columns: minmax(0, 1fr); } }
|
||||
.efs-commands { display: grid; gap: 8px; padding: 0 16px 12px; }
|
||||
.efs-commands p { margin: 0; }
|
||||
.efs-command { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; row-gap: 2px; align-items: center; }
|
||||
.efs-command > span { grid-column: 1 / -1; font-size: 12px; }
|
||||
.efs-command > code { overflow-x: auto; white-space: nowrap; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--raised); font-size: 12px; }
|
||||
|
||||
Reference in New Issue
Block a user