Skip to content

Commit 3e0eadb

Browse files
committed
fix: improved bash resiliency
1 parent d0f1bfa commit 3e0eadb

File tree

5 files changed

+128
-68
lines changed

5 files changed

+128
-68
lines changed

cmd/config/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var (
1818

1919
var ConfigCmd = &cobra.Command{
2020
Use: "config",
21-
Short: "CLI Config",
21+
Short: "Manage CLI configuration",
2222
Long: "RunPod CLI Config Settings",
2323
Run: func(c *cobra.Command, args []string) {
2424
if err := viper.WriteConfig(); err != nil {

cmd/project.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88

99
var projectCmd = &cobra.Command{
1010
Use: "project [command]",
11-
Short: "Manage RunPod projects",
12-
Long: "Develop and deploy projects entirely on RunPod's infrastructure",
11+
Short: "(NEW) Manage RunPod projects",
12+
Long: "Develop and deploy projects entirely on RunPod's infrastructure.",
1313
}
1414

1515
func init() {

cmd/project/project.go

+108-56
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,31 @@ import (
55
"errors"
66
"fmt"
77
"os"
8-
"strings"
98

109
"github.com/manifoldco/promptui"
1110
"github.com/spf13/cobra"
1211
"github.com/spf13/viper"
1312
)
1413

15-
var projectName string
16-
var modelType string
17-
var modelName string
18-
var initCurrentDir bool
19-
var setDefaultNetworkVolume bool
20-
var includeEnvInDockerfile bool
21-
var showPrefixInPodLogs bool
14+
var (
15+
projectName string
16+
modelType string
17+
modelName string
18+
initCurrentDir bool
19+
setDefaultNetworkVolume bool
20+
includeEnvInDockerfile bool
21+
showPrefixInPodLogs bool
22+
)
2223

2324
const inputPromptPrefix string = " > "
2425

2526
func prompt(message string) string {
26-
var s string = ""
27-
for s == "" {
28-
fmt.Print(inputPromptPrefix + message + ": ")
29-
fmt.Scanln(&s)
27+
var selection string = ""
28+
for selection == "" {
29+
fmt.Print(inputPromptPrefix + message)
30+
fmt.Scanln(&selection)
3031
}
31-
return s
32+
return selection
3233
}
3334

3435
func contains(input string, choices []string) bool {
@@ -41,36 +42,51 @@ func contains(input string, choices []string) bool {
4142
}
4243

4344
func promptChoice(message string, choices []string, defaultChoice string) string {
44-
var s string = ""
45-
for !contains(s, choices) {
46-
s = ""
47-
fmt.Print(inputPromptPrefix + message + " (" + strings.Join(choices, ", ") + ") " + "[" + defaultChoice + "]" + ": ")
48-
fmt.Scanln(&s)
49-
if s == "" {
45+
var selection string = ""
46+
for !contains(selection, choices) {
47+
selection = ""
48+
fmt.Println(message)
49+
fmt.Print(" Available options: ")
50+
for _, choice := range choices {
51+
fmt.Printf("%s", choice)
52+
if choice == defaultChoice {
53+
fmt.Print(" (default)")
54+
}
55+
if choice != choices[len(choices)-1] {
56+
fmt.Print(", ")
57+
}
58+
59+
}
60+
61+
fmt.Print("\n > ")
62+
63+
fmt.Scanln(&selection)
64+
65+
if selection == "" {
5066
return defaultChoice
5167
}
5268
}
53-
return s
69+
return selection
5470
}
5571

5672
func selectNetworkVolume() (networkVolumeId string, err error) {
5773
networkVolumes, err := api.GetNetworkVolumes()
5874
if err != nil {
59-
fmt.Println("Something went wrong trying to fetch network volumes")
60-
fmt.Println(err)
75+
fmt.Println("Error fetching network volumes:", err)
6176
return "", err
6277
}
6378
if len(networkVolumes) == 0 {
64-
fmt.Println("You do not have any network volumes.")
65-
fmt.Println("Please create a network volume (https://runpod.io/console/user/storage) and try again.")
66-
return "", errors.New("account has no network volumes")
79+
fmt.Println("No network volumes found. Please create one and try again. (https://runpod.io/console/user/storage)")
80+
return "", errors.New("no network volumes found")
6781
}
82+
6883
promptTemplates := &promptui.SelectTemplates{
6984
Label: inputPromptPrefix + "{{ . }}",
7085
Active: ` {{ "●" | cyan }} {{ .Name | cyan }}`,
7186
Inactive: ` {{ .Name | white }}`,
7287
Selected: ` {{ .Name | white }}`,
7388
}
89+
7490
options := []NetVolOption{}
7591
for _, networkVolume := range networkVolumes {
7692
options = append(options, NetVolOption{Name: fmt.Sprintf("%s: %s (%d GB, %s)", networkVolume.Id, networkVolume.Name, networkVolume.Size, networkVolume.DataCenterId), Value: networkVolume.Id})
@@ -111,7 +127,7 @@ func selectStarterTemplate() (template string, err error) {
111127
options = append(options, StarterTemplateOption{Name: template.Name(), Value: template.Name()})
112128
}
113129
getStarterTemplate := promptui.Select{
114-
Label: "Select a Starter Project:",
130+
Label: "Select a Starter Example:",
115131
Items: options,
116132
Templates: promptTemplates,
117133
}
@@ -131,20 +147,26 @@ type NetVolOption struct {
131147
}
132148

133149
var NewProjectCmd = &cobra.Command{
134-
Use: "create",
135-
Args: cobra.ExactArgs(0),
136-
Short: "creates a new project",
137-
Long: "creates a new RunPod project folder on your local machine",
150+
Use: "create",
151+
Aliases: []string{"new"},
152+
Args: cobra.ExactArgs(0),
153+
Short: "Creates a new project",
154+
Long: "Creates a new RunPod project folder on your local machine.",
138155
Run: func(cmd *cobra.Command, args []string) {
139-
fmt.Println("Creating a new project...")
156+
fmt.Print("Welcome to the RunPod Project Creator!\n--------------------------------------\n\n")
140157

141158
// Project Name
142159
if projectName == "" {
143-
projectName = prompt("Enter the project name")
160+
fmt.Print("1. Project Name:\n")
161+
fmt.Print(" Please enter the name of your project.\n")
162+
projectName = prompt("")
144163
}
145-
fmt.Println("Project name: " + projectName)
164+
fmt.Print("\n Project name set to '" + projectName + "'.\n\n")
165+
166+
// Project Examples
167+
fmt.Print("2. Starter Example:\n")
168+
fmt.Print(" Choose a starter example to begin with.\n")
146169

147-
// Starter Example
148170
if modelType == "" {
149171
starterExample, err := selectStarterTemplate()
150172
modelType = starterExample
@@ -153,22 +175,42 @@ var NewProjectCmd = &cobra.Command{
153175
}
154176
}
155177

178+
fmt.Println("")
179+
180+
// Model Name
181+
if modelType != "Hello World" {
182+
fmt.Print(" Model Name:\n")
183+
fmt.Print(" Please enter the name of the Hugging Face model you would like to use.\n")
184+
fmt.Print(" Leave blank to use the default model for the selected example.\n > ")
185+
fmt.Scanln(&modelName)
186+
fmt.Println("")
187+
}
188+
189+
// Project Configuration
190+
fmt.Print("3. Configuration:\n")
191+
fmt.Print(" Let's configure the project environment.\n\n")
192+
156193
// CUDA Version
157-
cudaVersion := promptChoice("Select CUDA version [default: 11.8.0]: ",
158-
[]string{"11.1.1", "11.8.0", "12.1.0"}, "11.8.0")
194+
fmt.Println(" CUDA Version:")
195+
cudaVersion := promptChoice(" Choose a CUDA version for your project.",
196+
[]string{"11.8.0", "12.1.0", "12.2.0"}, "11.8.0")
197+
198+
fmt.Println("\n Using CUDA version: " + cudaVersion)
159199

160200
// Python Version
161-
pythonVersion := promptChoice("Select Python version [default: 3.10]: ",
201+
fmt.Println("\n Python Version:")
202+
pythonVersion := promptChoice(" Choose a Python version for your project.",
162203
[]string{"3.8", "3.9", "3.10", "3.11"}, "3.10")
163204

205+
fmt.Println("\n Using Python version: " + pythonVersion)
206+
164207
// Project Summary
165208
fmt.Println("\nProject Summary:")
166-
fmt.Println("------------------------------------------------")
167-
fmt.Printf("Project name : %s\n", projectName)
168-
fmt.Printf("Starter project : %s\n", modelType)
169-
fmt.Printf("CUDA version : %s\n", cudaVersion)
170-
fmt.Printf("Python version : %s\n", pythonVersion)
171-
fmt.Println("------------------------------------------------")
209+
fmt.Println("----------------")
210+
fmt.Printf("- Project Name : %s\n", projectName)
211+
fmt.Printf("- Starter Example : %s\n", modelType)
212+
fmt.Printf("- CUDA version : %s\n", cudaVersion)
213+
fmt.Printf("- Python version : %s\n", pythonVersion)
172214

173215
// Confirm
174216
currentDir, err := os.Getwd()
@@ -177,27 +219,36 @@ var NewProjectCmd = &cobra.Command{
177219
return
178220
}
179221

180-
fmt.Printf("\nThe project will be created in the current directory: %s\n", currentDir)
181-
confirm := promptChoice("Proceed with creation? [yes/no, default: yes]: ", []string{"yes", "no"}, "yes")
222+
fmt.Printf("\nThe project will be created in the current directory: \n%s\n\n", currentDir)
223+
confirm := promptChoice("Proceed with creation?", []string{"yes", "no"}, "yes")
182224
if confirm != "yes" {
183225
fmt.Println("Project creation cancelled.")
184226
return
185227
}
186228

229+
fmt.Println("\nCreating project...")
230+
187231
// Create Project
188232
createNewProject(projectName, cudaVersion, pythonVersion, modelType, modelName, initCurrentDir)
189-
fmt.Printf("\nProject %s created successfully! Run `cd %s` to change directory to your project.\n", projectName, projectName)
190-
fmt.Println("From your project root run `runpodctl project dev` to start a development session.")
233+
fmt.Printf("\nProject %s created successfully! \nNavigate to your project directory with `cd %s`\n\n", projectName, projectName)
234+
fmt.Println("Tip: Run `runpodctl project dev` to start a development session for your project.")
191235
},
192236
}
193237

194238
var StartProjectCmd = &cobra.Command{
195239
Use: "dev",
196240
Aliases: []string{"start"},
197241
Args: cobra.ExactArgs(0),
198-
Short: "starts a development session for the current project",
199-
Long: "connects your local environment and the project environment on your Pod. Changes propagate to the project environment in real time.",
242+
Short: "Start a development session for the current project",
243+
Long: "This command establishes a connection between your local development environment and your RunPod project environment, allowing for real-time synchronization of changes.",
200244
Run: func(cmd *cobra.Command, args []string) {
245+
// Check for the existence of 'runpod.toml' in the current directory
246+
if _, err := os.Stat("runpod.toml"); os.IsNotExist(err) {
247+
fmt.Println("No 'runpod.toml' found in the current directory.")
248+
fmt.Println("Please navigate to your project directory and try again.")
249+
return
250+
}
251+
201252
config := loadProjectConfig()
202253
projectId := config.GetPath([]string{"project", "uuid"}).(string)
203254
networkVolumeId := viper.GetString(fmt.Sprintf("project_volumes.%s", projectId))
@@ -278,13 +329,14 @@ var BuildProjectCmd = &cobra.Command{
278329
}
279330

280331
func init() {
281-
NewProjectCmd.Flags().StringVarP(&projectName, "name", "n", "", "project name")
282-
// NewProjectCmd.Flags().StringVarP(&modelName, "model", "m", "", "model name")
283-
// NewProjectCmd.Flags().StringVarP(&modelType, "type", "t", "", "model type")
284-
NewProjectCmd.Flags().BoolVarP(&initCurrentDir, "init", "i", false, "use the current directory as the project directory")
332+
// Set up flags for the project commands
333+
NewProjectCmd.Flags().StringVarP(&projectName, "name", "n", "", "Set the project name, a directory with this name will be created in the current path.")
334+
NewProjectCmd.Flags().BoolVarP(&initCurrentDir, "init", "i", false, "Initialize the project in the current directory instead of creating a new one.")
335+
StartProjectCmd.Flags().BoolVar(&setDefaultNetworkVolume, "select-volume", false, "Choose a new default network volume for the project.")
285336

286-
StartProjectCmd.Flags().BoolVar(&setDefaultNetworkVolume, "select-volume", false, "select a new default network volume for current project")
287-
StartProjectCmd.Flags().BoolVar(&showPrefixInPodLogs, "prefix-pod-logs", true, "prefix logs from project Pod with Pod ID")
288-
BuildProjectCmd.Flags().BoolVar(&includeEnvInDockerfile, "include-env", false, "include environment variables from runpod.toml in generated Dockerfile")
337+
NewProjectCmd.Flags().StringVarP(&modelName, "model", "m", "", "Specify the Hugging Face model name for the project.")
338+
NewProjectCmd.Flags().StringVarP(&modelType, "type", "t", "", "Specify the model type for the project.")
289339

340+
StartProjectCmd.Flags().BoolVar(&showPrefixInPodLogs, "prefix-pod-logs", true, "Include the Pod ID as a prefix in log messages from the project Pod.")
341+
BuildProjectCmd.Flags().BoolVar(&includeEnvInDockerfile, "include-env", false, "Incorporate environment variables defined in runpod.toml into the generated Dockerfile.")
290342
}

cmd/project/ssh.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -70,28 +70,33 @@ func (sshConn *SSHConnection) getSshOptions() []string {
7070

7171
func (sshConn *SSHConnection) Rsync(localDir string, remoteDir string, quiet bool) error {
7272
rsyncCmdArgs := []string{"-avz", "--no-owner", "--no-group"}
73+
74+
// Retrieve and apply ignore patterns
7375
patterns, err := GetIgnoreList()
7476
if err != nil {
75-
return err
77+
return fmt.Errorf("getting ignore list: %w", err)
7678
}
77-
7879
for _, pat := range patterns {
7980
rsyncCmdArgs = append(rsyncCmdArgs, "--exclude", pat)
8081
}
82+
83+
// Add quiet flag if requested
8184
if quiet {
8285
rsyncCmdArgs = append(rsyncCmdArgs, "--quiet")
8386
}
8487

85-
sshOptions := strings.Join(sshConn.getSshOptions(), " ")
86-
rsyncCmdArgs = append(rsyncCmdArgs, "-e", fmt.Sprintf("ssh %s", sshOptions))
87-
rsyncCmdArgs = append(rsyncCmdArgs, localDir, fmt.Sprintf("root@%s:%s", sshConn.podIp, remoteDir))
88+
// Prepare SSH options for rsync
89+
sshOptions := fmt.Sprintf("ssh %s", strings.Join(sshConn.getSshOptions(), " "))
90+
rsyncCmdArgs = append(rsyncCmdArgs, "-e", sshOptions, localDir, fmt.Sprintf("root@%s:%s", sshConn.podIp, remoteDir))
8891

8992
cmd := exec.Command("rsync", rsyncCmdArgs...)
9093
cmd.Stdout = os.Stdout
9194
cmd.Stderr = os.Stderr
95+
9296
if err := cmd.Run(); err != nil {
9397
return fmt.Errorf("executing rsync command: %w", err)
9498
}
99+
95100
return nil
96101
}
97102

@@ -129,10 +134,14 @@ func hasChanges(localDir string, lastSyncTime time.Time) (bool, string) {
129134
func (sshConn *SSHConnection) SyncDir(localDir string, remoteDir string) {
130135
syncFiles := func() {
131136
fmt.Println("Syncing files...")
132-
sshConn.Rsync(localDir, remoteDir, true)
137+
err := sshConn.Rsync(localDir, remoteDir, true)
138+
if err != nil {
139+
fmt.Printf(" error: %v\n", err)
140+
return
141+
}
133142
}
134143

135-
// Start listening for events.
144+
// Start listening for events in a separate goroutine.
136145
go func() {
137146
lastSyncTime := time.Now()
138147
for {
@@ -146,7 +155,6 @@ func (sshConn *SSHConnection) SyncDir(localDir string, remoteDir string) {
146155
}
147156
}()
148157

149-
// Block main goroutine forever.
150158
<-make(chan struct{})
151159
}
152160

cmd/root.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var rootCmd = &cobra.Command{
1919
Use: "runpodctl",
2020
Aliases: []string{"runpod"},
2121
Short: "CLI for runpod.io",
22-
Long: "CLI tool to manage your pods for runpod.io",
22+
Long: "The RunPod CLI tool to manage resources on runpod.io and develop serverless applications.",
2323
}
2424

2525
func GetRootCmd() *cobra.Command {

0 commit comments

Comments
 (0)