Skip to content

Commit 0123140

Browse files
WarrickTolliekm
andauthored
CLI Skeleton Preview (#68)
* set up kong and got initial file structure and "mist help" working * added job status command * Added some logic and files for login and auth. * Added initial logic for submitting job * added user input to auth login * Added config and cancel jobs * Added config unit tests * Some unit testing done * Added job_status unit test + Made invalid response consistent with job_cancel * Added job status test and job submit test (Not completely done) * Adding gitignore for bin * Remove bin directory from tracking --------- Co-authored-by: Oliver Kwun-Morfitt <oliverkwunmorfitt@gmail.com>
1 parent 086a308 commit 0123140

25 files changed

Lines changed: 737 additions & 0 deletions

.DS_Store

6 KB
Binary file not shown.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin/

cli/cmd/auth.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cmd
2+
3+
type AuthCmd struct {
4+
Login LoginCmd `cmd:"" help:"Log in to your account"`
5+
// Logout LogoutCmd `cmd:"" help:"Log out of your account"`
6+
// Status AuthStatusCmd `cmd:"" help:"Check your authentication status" default:1`
7+
}
8+
9+
func (a *AuthCmd) Run() error {
10+
// Possible fallback if no subcommand is provided
11+
// fmt.Println("(auth root) – try 'mist auth login|logout|status' or mist help")
12+
return nil
13+
}

cli/cmd/auth_login.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cmd
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"strings"
9+
)
10+
11+
// TODO: What credentials are we taking?
12+
type LoginCmd struct {
13+
}
14+
15+
func verifyUser(username, password string) error {
16+
// Placeholder for actual authentication logic
17+
if username == "admin" && password == "password" {
18+
return nil
19+
}
20+
return errors.New("invalid credentials")
21+
}
22+
23+
// TODO: Figure out how to handle password input without exposing it in the terminal historyn (go get golang.org/x/term)
24+
// TODO: Where are we storing auth token? Are we getting JWT?
25+
26+
func (l *LoginCmd) Run() error {
27+
// mist auth login
28+
29+
fmt.Print("Username: ")
30+
31+
reader := bufio.NewReader(os.Stdin)
32+
username, _ := reader.ReadString('\n')
33+
username = strings.TrimSpace(strings.ToLower(username))
34+
35+
fmt.Print("Password: ")
36+
password, _ := reader.ReadString('\n')
37+
password = strings.TrimSpace(strings.ToLower(password))
38+
err := verifyUser(username, password)
39+
if err != nil {
40+
fmt.Println("Error during authentication:", err)
41+
}
42+
43+
fmt.Println("Logging in with username:", username)
44+
45+
return nil
46+
}

cli/cmd/config.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import "fmt"
4+
5+
6+
// Config flags
7+
type ConfigCmd struct{
8+
DefaultCluster string `help:"Set the default compute cluster." optional: ""`
9+
Show bool `help:"Show current configuration."`
10+
11+
}
12+
13+
func (h *ConfigCmd) Run() error {
14+
// Some dummy config; Call API or something
15+
defaultConfig := map[string]string{
16+
"defaultCluster": "AMD-cluster-1",
17+
"region": "us-east",
18+
}
19+
20+
if h.Show && h.DefaultCluster!= "" {
21+
fmt.Printf("Cannot use --show and --default-cluster together")
22+
return nil
23+
}
24+
25+
if h.DefaultCluster != "" {
26+
// This is not actually set.
27+
fmt.Printf("Setting default cluster to: %s\n", h.DefaultCluster)
28+
return nil
29+
}
30+
31+
if h.Show {
32+
fmt.Println("Current configuration: ")
33+
for key, value := range defaultConfig {
34+
fmt.Printf(" %s: %s \n", key, value)
35+
}
36+
return nil
37+
}
38+
39+
fmt.Println("No config action specified. Use --help for options.")
40+
41+
return nil
42+
}

cli/cmd/config_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
// "fmt"
6+
// "bytes"
7+
)
8+
9+
// No Flag config
10+
func TestConfigNoFlags(t *testing.T){
11+
cmd := &ConfigCmd{}
12+
output := CaptureOutput(func(){
13+
_ = cmd.Run()
14+
})
15+
if want := "No config action specified. Use --help for options."; !contains(output, want){
16+
t.Errorf("expected output to contain %q, got %q", want, output)
17+
}
18+
}
19+
20+
// Set Default Cluster to tt-gpu-cluster-1
21+
func TestConfigDefaultCluster(t *testing.T){
22+
cmd := &ConfigCmd{DefaultCluster: "tt-gpu-cluster-1"} // Create config object
23+
24+
output := CaptureOutput(func(){
25+
_ = cmd.Run()
26+
})
27+
28+
// fmt.Printf("Captured the output: %s\n", output)
29+
30+
if want := "Setting default cluster to: tt-gpu-cluster-1"; !contains(output, want) {
31+
t.Errorf("expected output to contain %q, got %q", want, output)
32+
}
33+
}
34+
35+
// Show Config
36+
func TestConfigCmd_Show(t *testing.T){
37+
cmd := &ConfigCmd{Show: true}
38+
output := CaptureOutput(func(){
39+
_ = cmd.Run()
40+
})
41+
42+
if want := "Current configuration:"; !contains(output, want){
43+
t.Errorf("expected output to contain %q, got %q", want, output)
44+
}
45+
}
46+
47+
// Show Error message if both flags are sent
48+
func TestConfigBothFlagError(t *testing.T){
49+
cmd := &ConfigCmd{DefaultCluster: "tt-gpu-cluster-1", Show: true}
50+
output := CaptureOutput(func(){
51+
_ = cmd.Run()
52+
})
53+
54+
if want := "Cannot use --show and --default-cluster together"; !contains(output, want){
55+
t.Errorf("Expected the error message of \"%s\", got %q", want, output)
56+
}
57+
58+
}

cli/cmd/help.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmd
2+
3+
import "fmt"
4+
5+
type HelpCmd struct{}
6+
7+
func (h *HelpCmd) Run() error {
8+
fmt.Println("MIST CLI Help")
9+
fmt.Println("Usage: mist [command] [options]")
10+
fmt.Println("Commands:")
11+
fmt.Println(" auth Authentication commands")
12+
fmt.Println(" job Job management commands")
13+
fmt.Println(" config Configuration commands")
14+
fmt.Println(" help Show help information")
15+
return nil
16+
}

cli/cmd/job.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmd
2+
3+
type JobCmd struct {
4+
Submit JobSubmitCmd `cmd:"" help:"Submit a new job"`
5+
Cancel JobCancelCmd `cmd:"" help:"Cancel an existing job"`
6+
// Delete JobDeleteCmd `cmd: "" help: "Delete an existing job"`
7+
Status JobStatusCmd `cmd:"" help:"Check the status of a job"`
8+
// Cancel CancelCmd `cmd:"" help:"Cancel a running job"`
9+
List ListCmd `cmd:"" help:"List all jobs" default:1`
10+
}
11+
12+
func (j *JobCmd) Run() error {
13+
// Possible fallback if no subcommand is providFAre yed
14+
// fmt.Println("(job root) – try 'mist job submit|status|list|cancel' or mist help")
15+
return nil
16+
}

cli/cmd/job_cancel.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cmd
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"time"
9+
)
10+
11+
12+
type JobCancelCmd struct {
13+
ID string `arg:"" help:"ID of job you want to cancel"`
14+
}
15+
16+
func (c *JobCancelCmd) Run() error {
17+
// Same Mock data from job list.
18+
jobs := []Job{
19+
{
20+
ID: "ID:1",
21+
Name: "docker_container_name_1",
22+
Status: "Running",
23+
GPUType: "AMD",
24+
CreatedAt: time.Now(),
25+
},
26+
{
27+
ID: "ID:2",
28+
Name: "docker_container_name_2",
29+
Status: "Enqueued",
30+
GPUType: "TT",
31+
CreatedAt: time.Now().Add(-time.Hour * 24),
32+
},
33+
{
34+
ID: "ID:3",
35+
Name: "docker_container_name_3",
36+
Status: "Running",
37+
GPUType: "TT",
38+
CreatedAt: time.Now().Add(-time.Hour * 24),
39+
},
40+
}
41+
42+
// Check if job exists
43+
if !jobExists(jobs, c.ID) {
44+
fmt.Printf("%s does not exist in your jobs.\n", c.ID)
45+
fmt.Printf("Use the command \"job list\" for your list of jobs.")
46+
return nil
47+
}
48+
49+
50+
fmt.Printf("Are you sure you want to cancel %s? (y/n): \n", c.ID)
51+
52+
reader := bufio.NewReader(os.Stdin)
53+
input, _ := reader.ReadString('\n')
54+
input = strings.TrimSpace(strings.ToLower(input))
55+
56+
if input == "y" || input == "yes"{
57+
fmt.Println("Confirmed, proceeding job cancellation....")
58+
59+
// Confirmed job cancellation logic
60+
61+
fmt.Println("Cancelling job with ID:", c.ID)
62+
fmt.Printf("Job cancelled successfully with ID: %s\n", c.ID)
63+
return nil
64+
} else if input == "n" || input == "no"{
65+
fmt.Println("Cancelled.")
66+
return nil
67+
} else{
68+
fmt.Println("Invalid response.")
69+
return nil
70+
}
71+
72+
return nil
73+
74+
}

cli/cmd/job_cancel_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
"fmt"
6+
)
7+
8+
9+
// Added job, with no compute type added
10+
func TestJobCancelJobDoesNotExist(t *testing.T){
11+
// This job should not exist in the dummy
12+
cmd := &JobCancelCmd{ID: "job_12345"}
13+
output := CaptureOutput(func(){
14+
_ = cmd.Run()
15+
})
16+
if want := "job_12345 does not exist in your jobs.\nUse the command \"job list\" for your list of jobs."; !contains(output, want){
17+
t.Errorf("expected output to contain %q, got %q", want, output)
18+
}
19+
}
20+
21+
// Added job, with compute type
22+
func TestJobCancelValid(t *testing.T){
23+
// This job should not exist in the dummy
24+
cmd := &JobCancelCmd{ID: "ID:1"}
25+
output := CaptureOutput(func(){
26+
_ = cmd.Run()
27+
})
28+
if want := "Are you sure you want to cancel ID:1? (y/n):"; !contains(output, want){
29+
t.Errorf("expected output to contain %q, got %q", want, output)
30+
}
31+
}
32+
33+
34+
func TestJobCancelProceed(t *testing.T){
35+
cmd := &JobCancelCmd{ID: "ID:1"}
36+
// Lowkey, we should refactor this into a
37+
output := CaptureOutput(func(){
38+
MockInput("y\n", func() {
39+
_ = cmd.Run()
40+
})
41+
})
42+
if !contains(output, "Confirmed, proceeding job cancellation...."){
43+
t.Errorf("expected 'Confirmed, proceeding job cancellation....' but got:\n%s", output)
44+
}
45+
// fmt.Printf("Got the output %s\n", output)
46+
}

0 commit comments

Comments
 (0)