Skip to content

Commit 8d120f9

Browse files
Merge pull request #62 from latitudesh/feat/block-storage-actions
feat: add block storage actions
2 parents 6fbba32 + 2525f9d commit 8d120f9

8 files changed

Lines changed: 936 additions & 4 deletions

File tree

README.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,50 @@ lsh plans list --gpu true
100100

101101
```
102102

103-
You can see more examples [here](https://docs.latitude.sh/docs/cli/examples). Reach out if you want to see other use cases on `lsh`.
104-
103+
```bash
104+
105+
lsh block list --project <PROJECT_ID>
106+
107+
```
108+
109+
Mount block storage to a server (auto-generates/detects NQN and executes mount automatically)
110+
111+
```bash
112+
113+
# Run directly on the server with sudo - NQN will be auto-detected or generated
114+
# and mount will be executed automatically
115+
sudo lsh block mount --id <BLOCK_ID> --subsystem-nqn <CONNECTOR_ID>
116+
117+
# Example with actual values
118+
# Simple! Just provide the block ID
119+
sudo lsh block mount --id blk_abc123
120+
121+
# Or override subsystem NQN if needed
122+
sudo lsh block mount \
123+
--id blk_abc123 \
124+
--subsystem-nqn nqn.2001-07.com.ceph:YOUR-CONNECTOR-ID
125+
126+
# This will automatically:
127+
# 1. Fetch block storage details and connector_id from API
128+
# 2. Install nvme-cli if not present (apt/yum/dnf)
129+
# 3. Auto-detect NQN from /etc/nvme/hostnqn (or generate if missing)
130+
# 4. Send client NQN to API to authorize access to the storage
131+
# 5. Load required NVMe modules (nvme_tcp)
132+
# 6. Connect to the NVMe-oF target using connector_id
133+
# 7. Verify the connection and show available devices
134+
# 8. Provide instructions for formatting and mounting
135+
136+
# Required parameters:
137+
# --id: Block storage ID
138+
139+
# How it works:
140+
# - Block ID: Used to auto-fetch connector_id (subsystem NQN)
141+
# - Client NQN: Auto-detected and sent to API to authorize this client
142+
# - Subsystem NQN: Auto-fetched from block storage's connector_id
143+
# - Gateway: Defaults to 67.213.118.147:4420
144+
145+
```
146+
105147

106148
## Troubleshooting
107149
If you encounter any problems when installing the CLI with the installation script, you can use the command below to uninstall the CLI.

cli/block_get_operation.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
latitudeshgosdk "github.com/latitudesh/latitudesh-go-sdk"
9+
"github.com/latitudesh/lsh/internal/cmdflag"
10+
"github.com/latitudesh/lsh/internal/utils"
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
)
14+
15+
func makeOperationBlockGetCmd() (*cobra.Command, error) {
16+
operation := BlockGetOperation{}
17+
18+
cmd, err := operation.Register()
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
return cmd, nil
24+
}
25+
26+
type BlockGetOperation struct {
27+
PathParamFlags cmdflag.Flags
28+
}
29+
30+
func (o *BlockGetOperation) Register() (*cobra.Command, error) {
31+
cmd := &cobra.Command{
32+
Use: "get",
33+
Short: "Get block storage details",
34+
Long: "Get detailed information about a specific block storage including connector details needed for mounting",
35+
RunE: o.run,
36+
PreRun: o.preRun,
37+
}
38+
39+
o.registerFlags(cmd)
40+
41+
return cmd, nil
42+
}
43+
44+
func (o *BlockGetOperation) registerFlags(cmd *cobra.Command) {
45+
o.PathParamFlags = cmdflag.Flags{FlagSet: cmd.Flags()}
46+
47+
pathParamsSchema := &cmdflag.FlagsSchema{
48+
&cmdflag.String{
49+
Name: "id",
50+
Label: "Block Storage ID",
51+
Description: "The ID of the block storage to retrieve",
52+
Required: true,
53+
},
54+
}
55+
56+
o.PathParamFlags.Register(pathParamsSchema)
57+
}
58+
59+
func (o *BlockGetOperation) preRun(cmd *cobra.Command, args []string) {
60+
o.PathParamFlags.PreRun(cmd, args)
61+
}
62+
63+
func (o *BlockGetOperation) run(cmd *cobra.Command, args []string) error {
64+
// Get the block ID from flags
65+
blockID, err := cmd.Flags().GetString("id")
66+
if err != nil {
67+
return fmt.Errorf("error getting block ID: %w", err)
68+
}
69+
70+
if dryRun {
71+
logDebugf("dry-run flag specified. Skip sending request.")
72+
return nil
73+
}
74+
75+
// Initialize the SDK client
76+
apiKey := viper.GetString("Authorization")
77+
if apiKey == "" {
78+
return fmt.Errorf("API key not found. Please run 'lsh login' first")
79+
}
80+
81+
ctx := context.Background()
82+
client := latitudeshgosdk.New(
83+
latitudeshgosdk.WithSecurity(apiKey),
84+
)
85+
86+
// NOTE: The SDK doesn't seem to have a GetStorageBlock (singular) method yet
87+
// We'll need to use GetStorageBlocks and filter, or wait for the API to add this endpoint
88+
fmt.Fprintf(os.Stdout, "Fetching block storage details for: %s\n", blockID)
89+
90+
// For now, use list and filter
91+
response, err := client.Storage.GetStorageBlocks(ctx, nil)
92+
if err != nil {
93+
utils.PrintError(err)
94+
return nil
95+
}
96+
97+
if !debug {
98+
if response != nil && response.HTTPMeta.Response != nil {
99+
fmt.Fprintf(os.Stdout, "Block storage details retrieved (Status: %s)\n", response.HTTPMeta.Response.Status)
100+
fmt.Fprintf(os.Stdout, "\nNote: This command will show connector_id, gateway IP, and port once the API returns them.\n")
101+
fmt.Fprintf(os.Stdout, "Look for:\n")
102+
fmt.Fprintf(os.Stdout, " - connector_id: The NVMe subsystem NQN (nqn.2001-07.com.ceph:...)\n")
103+
fmt.Fprintf(os.Stdout, " - gateway_ip: The IP address of the storage gateway\n")
104+
fmt.Fprintf(os.Stdout, " - gateway_port: The port (typically 4420)\n")
105+
}
106+
}
107+
108+
return nil
109+
}

cli/block_list_operation.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
latitudeshgosdk "github.com/latitudesh/latitudesh-go-sdk"
9+
"github.com/latitudesh/lsh/internal/cmdflag"
10+
"github.com/latitudesh/lsh/internal/utils"
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
)
14+
15+
func makeOperationBlockListCmd() (*cobra.Command, error) {
16+
operation := BlockListOperation{}
17+
18+
cmd, err := operation.Register()
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
return cmd, nil
24+
}
25+
26+
type BlockListOperation struct {
27+
QueryParamFlags cmdflag.Flags
28+
}
29+
30+
func (o *BlockListOperation) Register() (*cobra.Command, error) {
31+
cmd := &cobra.Command{
32+
Use: "list",
33+
Short: "List all block storages",
34+
Long: "List all block storages for your team, optionally filtered by project",
35+
RunE: o.run,
36+
PreRun: o.preRun,
37+
}
38+
39+
o.registerFlags(cmd)
40+
41+
return cmd, nil
42+
}
43+
44+
func (o *BlockListOperation) registerFlags(cmd *cobra.Command) {
45+
o.QueryParamFlags = cmdflag.Flags{FlagSet: cmd.Flags()}
46+
47+
queryParamsSchema := &cmdflag.FlagsSchema{
48+
&cmdflag.String{
49+
Name: "project",
50+
Label: "Project ID or Slug",
51+
Description: "Filter block storages by project ID or slug",
52+
Required: false,
53+
},
54+
}
55+
56+
o.QueryParamFlags.Register(queryParamsSchema)
57+
}
58+
59+
func (o *BlockListOperation) preRun(cmd *cobra.Command, args []string) {
60+
o.QueryParamFlags.PreRun(cmd, args)
61+
}
62+
63+
func (o *BlockListOperation) run(cmd *cobra.Command, args []string) error {
64+
// Get optional project filter
65+
project, _ := cmd.Flags().GetString("project")
66+
67+
if dryRun {
68+
logDebugf("dry-run flag specified. Skip sending request.")
69+
return nil
70+
}
71+
72+
// Initialize the SDK client
73+
apiKey := viper.GetString("Authorization")
74+
if apiKey == "" {
75+
return fmt.Errorf("API key not found. Please run 'lsh login' first")
76+
}
77+
78+
ctx := context.Background()
79+
client := latitudeshgosdk.New(
80+
latitudeshgosdk.WithSecurity(apiKey),
81+
)
82+
83+
// Create filter pointer if project is specified
84+
var filterProject *string
85+
if project != "" {
86+
filterProject = &project
87+
}
88+
89+
// Call the API
90+
response, err := client.Storage.GetStorageBlocks(ctx, filterProject)
91+
if err != nil {
92+
utils.PrintError(err)
93+
return nil
94+
}
95+
96+
if !debug {
97+
if response != nil && response.HTTPMeta.Response != nil {
98+
fmt.Fprintf(os.Stdout, "Block storages listed successfully (Status: %s)\n", response.HTTPMeta.Response.Status)
99+
fmt.Fprintf(os.Stdout, "\nNote: Use 'lsh block get --id <BLOCK_ID>' to see full details including connector information\n")
100+
}
101+
}
102+
103+
return nil
104+
}

0 commit comments

Comments
 (0)