Skip to content

Commit 2f002fb

Browse files
nfs access point (#1865)
* nfs access point * removed replace * vpc ids added in default AP * review comments resolved
1 parent dd0b8b3 commit 2f002fb

13 files changed

Lines changed: 1175 additions & 25 deletions

File tree

commands/displayers/nfs.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,53 @@ func (na *NfsAction) KV() []map[string]any {
158158

159159
return out
160160
}
161+
162+
type NfsAccessPoint struct {
163+
NfsAccessPoints []do.NfsAccessPoint
164+
}
165+
166+
var _ Displayable = &NfsAccessPoint{}
167+
168+
func (n *NfsAccessPoint) JSON(out io.Writer) error {
169+
return writeJSON(n.NfsAccessPoints, out)
170+
}
171+
172+
func (n *NfsAccessPoint) Cols() []string {
173+
return []string{
174+
"ID", "Name", "ShareID", "Path", "Status", "IsDefault", "VpcIDs", "CreatedAt", "UpdatedAt",
175+
}
176+
}
177+
178+
func (n *NfsAccessPoint) ColMap() map[string]string {
179+
return map[string]string{
180+
"ID": "ID",
181+
"Name": "Name",
182+
"ShareID": "Share ID",
183+
"Path": "Path",
184+
"Status": "Status",
185+
"IsDefault": "Default",
186+
"VpcIDs": "VPC IDs",
187+
"CreatedAt": "Created At",
188+
"UpdatedAt": "Updated At",
189+
}
190+
}
191+
192+
func (n *NfsAccessPoint) KV() []map[string]any {
193+
out := make([]map[string]any, 0, len(n.NfsAccessPoints))
194+
for _, ap := range n.NfsAccessPoints {
195+
m := map[string]any{
196+
"ID": ap.ID,
197+
"Name": ap.Name,
198+
"ShareID": ap.ShareID,
199+
"Path": ap.Path,
200+
"Status": ap.Status,
201+
"IsDefault": ap.IsDefault,
202+
"VpcIDs": strings.Join(ap.VpcIDs, ", "),
203+
"CreatedAt": ap.CreatedAt,
204+
"UpdatedAt": ap.UpdatedAt,
205+
}
206+
out = append(out, m)
207+
}
208+
209+
return out
210+
}

commands/nfs.go

Lines changed: 225 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package commands
1717
import (
1818
"fmt"
1919
"strconv"
20+
"strings"
2021

2122
"github.com/digitalocean/doctl"
2223
"github.com/digitalocean/doctl/commands/displayers"
@@ -77,18 +78,18 @@ doctl nfs list --region 'atl1' --format ID,Name,Size,Status`
7778
cmdNfsAttach := CmdBuilder(cmd, nfsAttach, "attach [flags]", "Attach an NFS share to a VPC", "Attaches an NFS share to a VPC with the given ID and region.", Writer)
7879
AddStringFlag(cmdNfsAttach, "id", "", "", "the ID of the NFS share", requiredOpt())
7980
AddStringFlag(cmdNfsAttach, "region", "r", "", "the region where the NFS share resides", requiredOpt())
80-
AddStringFlag(cmdNfsAttach, "vpc_id", "", "", "the id of the VPC we want to attach NFS share to", requiredOpt())
81+
AddStringFlag(cmdNfsAttach, "vpc-id", "", "", "the id of the VPC we want to attach NFS share to", requiredOpt())
8182
AddBoolFlag(cmdNfsAttach, doctl.ArgCommandWait, "", false, "Wait for action to complete")
8283
cmdNfsAttach.Example =
83-
`doctl nfs attach --region 'atl1' --id b050990d-4337-4a9d-9c8d-9f759a83936a --vpc_id example-vpc-id`
84+
`doctl nfs attach --region 'atl1' --id b050990d-4337-4a9d-9c8d-9f759a83936a --vpc-id example-vpc-id`
8485

8586
cmdNfsDetach := CmdBuilder(cmd, nfsDetach, "detach [flags]", "Detach an NFS share from a VPC", "Detaches an NFS share from a VPC with the given ID and region.", Writer)
8687
AddStringFlag(cmdNfsDetach, "id", "", "", "the ID of the NFS share", requiredOpt())
8788
AddStringFlag(cmdNfsDetach, "region", "r", "", "the region where the NFS share resides", requiredOpt())
88-
AddStringFlag(cmdNfsDetach, "vpc_id", "", "", "the id of the VPC we want to detach NFS share from", requiredOpt())
89+
AddStringFlag(cmdNfsDetach, "vpc-id", "", "", "the id of the VPC we want to detach NFS share from", requiredOpt())
8990
AddBoolFlag(cmdNfsDetach, doctl.ArgCommandWait, "", false, "Wait for action to complete")
9091
cmdNfsDetach.Example =
91-
`doctl nfs detach --region 'atl1' --id b050990d-4337-4a9d-9c8d-9f759a83936a --vpc_id example-vpc-id`
92+
`doctl nfs detach --region 'atl1' --id b050990d-4337-4a9d-9c8d-9f759a83936a --vpc-id example-vpc-id`
9293

9394
cmdNfsReassign := CmdBuilder(cmd, nfsReassign, "reassign [flags]", "Reassign an NFS share between VPCs", "Reassigns an NFS share from one VPC to another with the given ID.", Writer)
9495
AddStringFlag(cmdNfsReassign, "id", "", "", "the ID of the NFS share", requiredOpt())
@@ -106,6 +107,40 @@ doctl nfs list --region 'atl1' --format ID,Name,Size,Status`
106107
`doctl nfs switch-performance-tier --id b050990d-4337-4a9d-9c8d-9f759a83936a --performance-tier high`
107108

108109
cmd.AddCommand(nfsSnapshots())
110+
cmd.AddCommand(nfsAccessPoints())
111+
112+
return cmd
113+
}
114+
115+
func nfsAccessPoints() *Command {
116+
cmd := &Command{
117+
Command: &cobra.Command{
118+
Use: "access-point",
119+
Short: "Display commands for NFS access points",
120+
Long: "The commands under `doctl nfs access-point` are for managing NFS share access points.",
121+
},
122+
}
123+
124+
create := CmdBuilder(cmd, nfsAccessPointCreate, "create [flags]", "Create an NFS access point", "Create an NFS access point for a share.", Writer, displayerType(&displayers.NfsAction{}), overrideCmdNS("nfs-access-point"))
125+
AddStringFlag(create, "share-id", "", "", "the ID of the NFS share", requiredOpt())
126+
AddStringFlag(create, "name", "n", "", "the access point name", requiredOpt())
127+
AddStringFlag(create, "path", "", "", "the export path for the access point, must start with /", requiredOpt())
128+
AddStringSliceFlag(create, "protocols", "", []string{"NFS4"}, "allowed protocols (NFS, NFS4)")
129+
AddStringFlag(create, "squash-config", "", "ROOT_SQUASH", "squash mode (NO_SQUASH, ROOT_SQUASH, ALL_SQUASH)")
130+
AddStringFlag(create, "anonuid", "", "65534", "anon uid for squashed users")
131+
AddStringFlag(create, "anongid", "", "65534", "anon gid for squashed users")
132+
AddBoolFlag(create, "identity-enforcement-enabled", "", false, "enable identity enforcement for the export")
133+
AddStringFlag(create, "vpc-id", "", "", "the VPC ID for this access point", requiredOpt())
134+
135+
get := CmdBuilder(cmd, nfsAccessPointGet, "get [flags]", "Get an NFS access point", "Get an NFS access point by ID.", Writer, displayerType(&displayers.NfsAccessPoint{}), overrideCmdNS("nfs-access-point"))
136+
AddStringFlag(get, "id", "", "", "the ID of the NFS access point", requiredOpt())
137+
138+
list := CmdBuilder(cmd, nfsAccessPointList, "list [flags]", "List NFS access points for a share", "List NFS access points for a share.", Writer, aliasOpt("ls"), displayerType(&displayers.NfsAccessPoint{}), overrideCmdNS("nfs-access-point"))
139+
AddStringFlag(list, "share-id", "", "", "the ID of the NFS share", requiredOpt())
140+
AddStringFlag(list, "status", "", "", "optional status filter (ACCESS_POINT_CREATING, ACCESS_POINT_ACTIVE, ACCESS_POINT_FAILED, ACCESS_POINT_DELETED)")
141+
142+
deleteCmd := CmdBuilder(cmd, nfsAccessPointDelete, "delete [flags]", "Delete an NFS access point", "Delete an NFS access point by ID.", Writer, aliasOpt("rm"), displayerType(&displayers.NfsAction{}), overrideCmdNS("nfs-access-point"))
143+
AddStringFlag(deleteCmd, "id", "", "", "the ID of the NFS access point", requiredOpt())
109144

110145
return cmd
111146
}
@@ -369,7 +404,7 @@ func nfsAttach(c *CmdConfig) error {
369404

370405
region, _ := c.Doit.GetString(c.NS, "region")
371406

372-
vpcIdStr, err := c.Doit.GetString(c.NS, "vpc_id")
407+
vpcIdStr, err := c.Doit.GetString(c.NS, "vpc-id")
373408
if err != nil {
374409
return err
375410
}
@@ -407,7 +442,7 @@ func nfsDetach(c *CmdConfig) error {
407442
}
408443

409444
region, _ := c.Doit.GetString(c.NS, "region")
410-
vpcIdStr, err := c.Doit.GetString(c.NS, "vpc_id")
445+
vpcIdStr, err := c.Doit.GetString(c.NS, "vpc-id")
411446
if err != nil {
412447
return err
413448
}
@@ -515,6 +550,185 @@ func nfsSwitchPerformanceTier(c *CmdConfig) error {
515550
return c.Display(item)
516551
}
517552

553+
func nfsAccessPointCreate(c *CmdConfig) error {
554+
shareID, err := c.Doit.GetString(c.NS, "share-id")
555+
if err != nil {
556+
return err
557+
}
558+
559+
name, err := c.Doit.GetString(c.NS, "name")
560+
if err != nil {
561+
return err
562+
}
563+
564+
path, err := c.Doit.GetString(c.NS, "path")
565+
if err != nil {
566+
return err
567+
}
568+
569+
protocols, err := c.Doit.GetStringSlice(c.NS, "protocols")
570+
if err != nil {
571+
return err
572+
}
573+
574+
squashConfig, err := c.Doit.GetString(c.NS, "squash-config")
575+
if err != nil {
576+
return err
577+
}
578+
579+
anonUIDStr, err := c.Doit.GetString(c.NS, "anonuid")
580+
if err != nil {
581+
return err
582+
}
583+
anonUID, err := strconv.ParseUint(anonUIDStr, 10, 64)
584+
if err != nil {
585+
return fmt.Errorf("invalid anonuid value: %v", err)
586+
}
587+
588+
anonGIDStr, err := c.Doit.GetString(c.NS, "anongid")
589+
if err != nil {
590+
return err
591+
}
592+
anonGID, err := strconv.ParseUint(anonGIDStr, 10, 64)
593+
if err != nil {
594+
return fmt.Errorf("invalid anongid value: %v", err)
595+
}
596+
597+
identityEnforcementEnabled, err := c.Doit.GetBool(c.NS, "identity-enforcement-enabled")
598+
if err != nil {
599+
return err
600+
}
601+
602+
vpcID, err := c.Doit.GetString(c.NS, "vpc-id")
603+
if err != nil {
604+
return err
605+
}
606+
607+
request := &do.NfsAccessPointCreateRequest{
608+
Name: name,
609+
Path: path,
610+
VpcID: vpcID,
611+
AccessPolicy: do.NfsAccessPointPolicy{
612+
Anonuid: anonUID,
613+
Anongid: anonGID,
614+
Protocols: normalizeProtocols(protocols),
615+
SquashConfig: strings.ToUpper(squashConfig),
616+
IdentityEnforcementEnabled: identityEnforcementEnabled,
617+
},
618+
}
619+
620+
resp, err := c.Nfs().CreateAccessPoint(shareID, request)
621+
if err != nil {
622+
return err
623+
}
624+
625+
item := &displayers.NfsAction{NfsActions: []do.NfsAction{{NfsAction: resp.Action}}}
626+
return c.Display(item)
627+
}
628+
629+
func nfsAccessPointGet(c *CmdConfig) error {
630+
id, err := c.Doit.GetString(c.NS, "id")
631+
if err != nil {
632+
return err
633+
}
634+
635+
ap, err := c.Nfs().GetAccessPoint(id)
636+
if err != nil {
637+
return err
638+
}
639+
640+
accessPoints, err := enrichNfsAccessPointsForShare(c, ap.ShareID, []do.NfsAccessPoint{*ap})
641+
if err != nil {
642+
return err
643+
}
644+
645+
return displayNfsAccessPoints(c, accessPoints...)
646+
}
647+
648+
func nfsAccessPointList(c *CmdConfig) error {
649+
shareID, err := c.Doit.GetString(c.NS, "share-id")
650+
if err != nil {
651+
return err
652+
}
653+
654+
status, err := c.Doit.GetString(c.NS, "status")
655+
if err != nil {
656+
return err
657+
}
658+
659+
accessPoints, err := c.Nfs().ListAccessPoints(shareID, strings.ToUpper(status))
660+
if err != nil {
661+
return err
662+
}
663+
664+
accessPoints, err = enrichNfsAccessPointsForShare(c, shareID, accessPoints)
665+
if err != nil {
666+
return err
667+
}
668+
669+
return displayNfsAccessPoints(c, accessPoints...)
670+
}
671+
672+
// enrichNfsAccessPointsForShare fills VPC IDs on the default access point from the
673+
// parent share. The access point API does not return VPCs for the default "/"
674+
// export, and the share can be looked up by ID without a region.
675+
func enrichNfsAccessPointsForShare(c *CmdConfig, shareID string, accessPoints []do.NfsAccessPoint) ([]do.NfsAccessPoint, error) {
676+
needsShareVPCs := false
677+
for _, ap := range accessPoints {
678+
if ap.IsDefault {
679+
needsShareVPCs = true
680+
break
681+
}
682+
}
683+
if !needsShareVPCs {
684+
return accessPoints, nil
685+
}
686+
687+
share, err := c.Nfs().Get(shareID, "")
688+
if err != nil {
689+
return nil, err
690+
}
691+
692+
out := make([]do.NfsAccessPoint, len(accessPoints))
693+
for i, ap := range accessPoints {
694+
out[i] = ap
695+
if ap.IsDefault {
696+
out[i].VpcIDs = share.VpcIDs
697+
}
698+
}
699+
700+
return out, nil
701+
}
702+
703+
func nfsAccessPointDelete(c *CmdConfig) error {
704+
id, err := c.Doit.GetString(c.NS, "id")
705+
if err != nil {
706+
return err
707+
}
708+
709+
resp, err := c.Nfs().DeleteAccessPoint(id)
710+
if err != nil {
711+
return err
712+
}
713+
714+
item := &displayers.NfsAction{NfsActions: []do.NfsAction{{NfsAction: resp.Action}}}
715+
return c.Display(item)
716+
}
717+
718+
func normalizeProtocols(protocols []string) []string {
719+
out := make([]string, 0, len(protocols))
720+
for _, p := range protocols {
721+
trimmed := strings.TrimSpace(p)
722+
if trimmed != "" {
723+
out = append(out, strings.ToUpper(trimmed))
724+
}
725+
}
726+
if len(out) == 0 {
727+
return []string{"NFS4"}
728+
}
729+
return out
730+
}
731+
518732
func displayNfs(c *CmdConfig, shares ...do.Nfs) error {
519733
item := &displayers.Nfs{NfsShares: shares}
520734
return c.Display(item)
@@ -524,3 +738,8 @@ func displayNfsSnapshots(c *CmdConfig, snapshots ...do.NfsSnapshot) error {
524738
item := &displayers.NfsSnapshot{NfsSnapshots: snapshots}
525739
return c.Display(item)
526740
}
741+
742+
func displayNfsAccessPoints(c *CmdConfig, accessPoints ...do.NfsAccessPoint) error {
743+
item := &displayers.NfsAccessPoint{NfsAccessPoints: accessPoints}
744+
return c.Display(item)
745+
}

0 commit comments

Comments
 (0)