Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 82 additions & 19 deletions cli/cmd/auth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,108 @@ package cmd

import (
"bufio"
"errors"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)

// TODO: What credentials are we taking?
// TODO: Update with real auth URL
const authUrl = "https://example.com/login"

type LoginCmd struct {
}

func verifyUser(username, password string) error {
// Placeholder for actual authentication logic
if username == "admin" && password == "password" {
return nil
func openUrl() error {

var cmd *exec.Cmd
url := authUrl
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", url)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
default: // Linux, BSD, etc.
cmd = exec.Command("xdg-open", url)
}

return cmd.Start()
}

func saveTokenToConfig(ctx *AppContext, token string) error {
configPath := defaultConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}

if ctx.Config == nil {
ctx.Config = &Config{}
}
ctx.Config.AccessToken = token

data, err := json.MarshalIndent(ctx.Config, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
return errors.New("invalid credentials")
if err := os.WriteFile(configPath, data, 0o600); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
fmt.Println("Token saved:", token)
return nil
}

// TODO: Figure out how to handle password input without exposing it in the terminal historyn (go get golang.org/x/term)
// TODO: Where are we storing auth token? Are we getting JWT?
func getLongLivedToken(shortLivedToken string) (string, error) {
// Placeholder for actual implementation to exchange short-lived token for long-lived token
// In a real scenario, this would involve making an HTTP request to the auth server
return shortLivedToken + "_long_lived", nil
}

func (l *LoginCmd) Run() error {
func (l *LoginCmd) Run(ctx *AppContext) error {
// mist auth login
if ctx.Config != nil && ctx.Config.AccessToken != "" {

fmt.Print("Username: ")
// Already logged in, ask if they want to re-login
fmt.Println("Already logged in with token:", ctx.Config.AccessToken)
fmt.Print("Re-enter token? (y/N): ")
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
answer = strings.TrimSpace(strings.ToLower(answer))
if answer != "y" && answer != "yes" {
fmt.Println("Aborting login.")
return nil
}
}

fmt.Println("Opening browser for authentication...")
fmt.Printf("If your browser didn't open, click here: \033]8;;%s\033\\%s\033]8;;\033\\\n", authUrl, authUrl)

err := openUrl()
if err != nil {
fmt.Println("Error opening browser:", err)
return err
}
fmt.Print("token: ")

reader := bufio.NewReader(os.Stdin)
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(strings.ToLower(username))
token, _ := reader.ReadString('\n')
token = strings.TrimSpace(token)

token, err = getLongLivedToken(token)
if err != nil {
fmt.Println("Error obtaining long-lived token:", err)
return err
}

fmt.Print("Password: ")
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(strings.ToLower(password))
err := verifyUser(username, password)
err = saveTokenToConfig(ctx, token)
if err != nil {
fmt.Println("Error during authentication:", err)
fmt.Println("Error during token saving")
return err
}

fmt.Println("Logging in with username:", username)
fmt.Println("Saved token to config")

return nil
}
20 changes: 9 additions & 11 deletions cli/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@ package cmd

import "fmt"


// Config flags
type ConfigCmd struct{
// Config flags
type ConfigCmd struct {
DefaultCluster string `help:"Set the default compute cluster." optional: ""`
Show bool `help:"Show current configuration."`

Show bool `help:"Show current configuration."`
}

func (h *ConfigCmd) Run() error {
// Some dummy config; Call API or something
func (h *ConfigCmd) Run(ctx *AppContext) error {
// Some dummy config; Call API or something
defaultConfig := map[string]string{
"defaultCluster": "AMD-cluster-1",
"region": "us-east",
"region": "us-east",
}

if h.Show && h.DefaultCluster!= "" {
if h.Show && h.DefaultCluster != "" {
fmt.Printf("Cannot use --show and --default-cluster together")
return nil
}

if h.DefaultCluster != "" {
// This is not actually set.
// This is not actually set.
fmt.Printf("Setting default cluster to: %s\n", h.DefaultCluster)
return nil
}
Expand All @@ -36,7 +34,7 @@ func (h *ConfigCmd) Run() error {
return nil
}

fmt.Println("No config action specified. Use --help for options.")
fmt.Println("No config action specified. Use --help for options.")

return nil
}
24 changes: 11 additions & 13 deletions cli/cmd/job_cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import (
"time"
)


type JobCancelCmd struct {
ID string `arg:"" help:"ID of job you want to cancel"`
ID string `arg:"" help:"ID of job you want to cancel"`
}

func (c *JobCancelCmd) Run() error {
// Same Mock data from job list.
func (c *JobCancelCmd) Run(ctx *AppContext) error {
// Same Mock data from job list.
jobs := []Job{
{
ID: "ID:1",
Expand All @@ -39,36 +38,35 @@ func (c *JobCancelCmd) Run() error {
},
}

// Check if job exists
// Check if job exists
if !jobExists(jobs, c.ID) {
fmt.Printf("%s does not exist in your jobs.\n", c.ID)
fmt.Printf("Use the command \"job list\" for your list of jobs.")
return nil
return nil
}


fmt.Printf("Are you sure you want to cancel %s? (y/n): \n", c.ID)

reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(strings.ToLower(input))

if input == "y" || input == "yes"{
if input == "y" || input == "yes" {
fmt.Println("Confirmed, proceeding job cancellation....")

// Confirmed job cancellation logic
// Confirmed job cancellation logic

fmt.Println("Cancelling job with ID:", c.ID)
fmt.Printf("Job cancelled successfully with ID: %s\n", c.ID)
return nil
} else if input == "n" || input == "no"{
} else if input == "n" || input == "no" {
fmt.Println("Cancelled.")
return nil
} else{
} else {
fmt.Println("Invalid response.")
return nil
}

return nil
return nil

}
}
2 changes: 1 addition & 1 deletion cli/cmd/job_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Job struct {
CreatedAt time.Time
}

func (l *ListCmd) Run() error {
func (l *ListCmd) Run(ctx *AppContext) error {
// Mock data - pull from API in real implementation
jobs := []Job{
{
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/job_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type JobStatusCmd struct {
ID string `arg:"" help:"The ID of the job to check the status for"`
}

func (j *JobStatusCmd) Run() error {
func (j *JobStatusCmd) Run(ctx *AppContext) error {
// Mock data - pull from API in real implementation
jobs := []Job{{
ID: "ID:1",
Expand Down
3 changes: 1 addition & 2 deletions cli/cmd/job_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ type JobSubmitCmd struct {
Compute string `help:"Type of compute required for the job: AMD|TT|CPU" default:"AMD"`
}


func (j *JobSubmitCmd) Run() error {
func (j *JobSubmitCmd) Run(ctx *AppContext) error {
// mist job submit <script> <compute_type>

// TODO: ADD AUTH CHECK
Expand Down
62 changes: 55 additions & 7 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,76 @@
package cmd

import (
"encoding/json"
"net/http"
"os"
"time"

"github.com/alecthomas/kong"
)

type Config struct {
AccessToken string `json:"access_token"`
// maybe APIBaseURL, etc.
}

type AppContext struct {
Config *Config
HTTPClient *http.Client
}

type Globals struct {
ConfigPath string `name:"config" help:"Path to config file" default:"${config_path}"`
}

type CLI struct {
// Define your CLI structure here: Top Level Commands
Globals

// Define your CLI structure here: Top Level Commands
Auth AuthCmd `cmd:"" help:"Authentication commands"`
Job JobCmd `cmd:"" help:"Job management commands"`
// Config ConfigCmd `cmd:"" help:"Configuration commands"`
Help HelpCmd `cmd:"" help:"Show help information"`
Config ConfigCmd `cmd:"" help: "Display Cluster Configuration"`
// Config ConfigCmd `cmd:"" help: "Display Cluster Configuration"`
}

func loadConfig(path string) (*Config, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var cfg Config
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, err
}

return &cfg, nil
}

func Main() {
var cli CLI
// Read command-line arguments
ctx := kong.Parse(&cli,
// Read command-line arguments

appCtx := &AppContext{
HTTPClient: &http.Client{Timeout: 10 * time.Second},
}

kctx := kong.Parse(&cli,
kong.Name("mist"),
kong.Description("MIST CLI - Manage your MIST jobs and configurations"),
kong.UsageOnError(),
kong.Vars{"config_path": defaultConfigPath()},
kong.Bind(appCtx),
)

err := ctx.Run()
ctx.FatalIfErrorf(err)
// fmt.Println("Command executed successfully")
if cfg, err := loadConfig(cli.ConfigPath); err == nil {
appCtx.Config = cfg
} else if !os.IsNotExist(err) {
// config file is present but broken
kctx.FatalIfErrorf(err)
}

err := kctx.Run()
kctx.FatalIfErrorf(err)
}
11 changes: 10 additions & 1 deletion cli/cmd/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
// "strings"
)

Expand All @@ -21,7 +22,6 @@ func MockInput(input string, f func()) {
os.Stdin = old
}


func CaptureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
Expand Down Expand Up @@ -59,5 +59,14 @@ func findJobByID(jobs []Job, id string) (Job, error) {
return Job{}, fmt.Errorf("job with ID %s not found", id)
}

func defaultConfigPath() string {
dir, err := os.UserConfigDir()
if err != nil {
home, _ := os.UserHomeDir()
return filepath.Join(home, ".config", "mist", "config.json")
}
return filepath.Join(dir, "mist", "config.json")
}

// Creating a helper function for capturing
// Stdout outputs