Skip to content
Merged
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
2 changes: 1 addition & 1 deletion trainings/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (h *Handlers) Clone(ctx context.Context, executionID string) error {
"err": err,
}).Debug("Received exercise from server")

if err := h.startTraining(ctx, resp.TrainingName); err != nil {
if _, err := h.startTraining(ctx, resp.TrainingName, false); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion trainings/config/training.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ func (c Config) FindTrainingRoot() (string, error) {
previousDir = dir
}

return "", TrainingRootNotFoundError
return "", errors.WithStack(TrainingRootNotFoundError)
}
99 changes: 70 additions & 29 deletions trainings/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strings"

"github.com/ThreeDotsLabs/cli/internal"
Expand All @@ -23,46 +24,81 @@ func (h *Handlers) Init(ctx context.Context, trainingName string) error {
"training_name": trainingName,
}).Debug("Starting training")

if err := h.startTraining(ctx, trainingName); errors.Is(err, ErrInterrupted) {
trainingRoot, err := h.startTraining(ctx, trainingName, true)
if errors.Is(err, ErrInterrupted) {
fmt.Println("Interrupted")
return nil
} else if err != nil {
return err
}

// todo - handle situation when training was started but something failed here and someone is starting excersise again (because he have no local files)
_, err := h.nextExercise(ctx, "")
_, err = h.nextExercise(ctx, "", trainingRoot)
if err != nil {
return err
}

fmt.Println("To see exercise content, please go back to " + color.CyanString(internal.WebsiteAddress))
if !isInTrainingRoot(trainingRoot) {
fmt.Println("\nNow run " + color.CyanString("cd "+trainingName+"/") + " to enter the training workspace")
}

return nil
}

func isInTrainingRoot(trainingRoot string) bool {
pwd, err := os.Getwd()
if err != nil {
logrus.WithError(err).Warn("Can't get current working directory")
return false
}

absPwd, err := filepath.Abs(pwd)
if err != nil {
logrus.WithError(err).Warn("Can't get absolute path of current working directory")
return false
}

absTrainingRoot, err := filepath.Abs(trainingRoot)
if err != nil {
logrus.WithError(err).Warn("Can't get absolute path of training root")
return false
}

return absPwd == absTrainingRoot
}

var ErrInterrupted = errors.New("interrupted")

func (h *Handlers) startTraining(ctx context.Context, trainingName string) error {
func (h *Handlers) startTraining(
ctx context.Context,
trainingName string,
addTrainingNameToDir bool,
) (string, error) {
var trainingRoot string

alreadyExistingTrainingRoot, err := h.config.FindTrainingRoot()
if err == nil {
fmt.Println(color.BlueString("Training was already initialised. Training root:" + alreadyExistingTrainingRoot))
trainingRoot = alreadyExistingTrainingRoot
} else if !errors.Is(err, config.TrainingRootNotFoundError) {
return errors.Wrap(err, "can't check if training root exists")
return "", errors.Wrap(err, "can't check if training root exists")
} else {
if err := h.showTrainingStartPrompt(); err != nil {
return err
}

wd, err := os.Getwd()
if err != nil {
return errors.WithStack(err)
return "", errors.WithStack(err)
}

if addTrainingNameToDir {
trainingRoot = path.Join(wd, trainingName)
} else {
trainingRoot = wd
}

if err := h.showTrainingStartPrompt(trainingRoot); err != nil {
return "", err
}

// we will create training root in current working directory
trainingRoot = wd
logrus.Debug("No training root yet")
}

Expand All @@ -71,13 +107,18 @@ func (h *Handlers) startTraining(ctx context.Context, trainingName string) error
if alreadyExistingTrainingRoot != "" {
cfg := h.config.TrainingConfig(trainingRootFs)
if cfg.TrainingName != trainingName {
return fmt.Errorf(
return "", fmt.Errorf(
"training %s was already started in this directory, please go to other directory and run `tdl training init`",
cfg.TrainingName,
)
}
} else {
err = createGoWorkspace(trainingRoot)
err := os.MkdirAll(trainingRoot, 0755)
if err != nil {
return "", errors.Wrap(err, "can't create training root dir")
}

err = createGoWorkspace(trainingRoot, trainingName)
if err != nil {
logrus.WithError(err).Warn("Could not create go workspace")
}
Expand All @@ -91,18 +132,18 @@ func (h *Handlers) startTraining(ctx context.Context, trainingName string) error
},
)
if err != nil {
return errors.Wrap(err, "start training gRPC call failed")
return "", errors.Wrap(err, "start training gRPC call failed")
}

if err := h.config.WriteTrainingConfig(config.TrainingConfig{TrainingName: trainingName}, trainingRootFs); err != nil {
return errors.Wrap(err, "can't write training config")
return "", errors.Wrap(err, "can't write training config")
}

if err := writeGitignore(trainingRootFs); err != nil {
return err
return "", err
}

return nil
return trainingRoot, nil
}

var gitignore = strings.Join(
Expand All @@ -129,14 +170,19 @@ func writeGitignore(trainingRootFs *afero.BasePathFs) error {
return nil
}

func createGoWorkspace(trainingRoot string) error {
func createGoWorkspace(trainingRoot, trainingName string) error {
cmd := exec.Command("go", "work", "init")
cmd.Dir = trainingRoot

printlnCommand(".", "go work init")
printlnCommand(trainingRoot, "go work init")

if err := cmd.Run(); err != nil {
return errors.Wrap(err, "can't run go work init")
out, err := cmd.CombinedOutput()
if strings.Contains(string(out), "already exists") {
logrus.Debug("go.work already exists")
return nil
}
if err != nil {
return errors.Wrapf(err, "can't run go work init: %s", string(out))
}

return nil
Expand All @@ -155,7 +201,7 @@ func addModuleToWorkspace(trainingRoot string, modulePath string) error {
cmd := exec.Command("go", "work", "use", modulePath)
cmd.Dir = trainingRoot

printlnCommand(".", fmt.Sprintf("go work use %v", modulePath))
printlnCommand(trainingRoot, fmt.Sprintf("go work use %v", modulePath))

if err := cmd.Run(); err != nil {
return errors.Wrap(err, "can't run go work use")
Expand All @@ -164,15 +210,10 @@ func addModuleToWorkspace(trainingRoot string, modulePath string) error {
return nil
}

func (h *Handlers) showTrainingStartPrompt() error {
pwd, err := os.Getwd()
if err != nil {
return errors.Wrap(err, "can't get wd")
}

func (h *Handlers) showTrainingStartPrompt(trainingDir string) error {
fmt.Printf(
"This command will clone training source code to %s directory.\n",
pwd,
trainingDir,
)

if !internal.ConfirmPromptDefaultYes("continue") {
Expand Down
7 changes: 1 addition & 6 deletions trainings/next.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import (
"github.com/spf13/afero"
)

func (h *Handlers) nextExercise(ctx context.Context, currentExerciseID string) (finished bool, err error) {
func (h *Handlers) nextExercise(ctx context.Context, currentExerciseID string, trainingRoot string) (finished bool, err error) {
h.solutionHintDisplayed = false

trainingRoot, err := h.config.FindTrainingRoot()
if err != nil {
return false, err
}

// We should never trust the remote server.
// Writing files based on external name is a vector for Path Traversal attack.
// For more info please check: https://owasp.org/www-community/attacks/Path_Traversal
Expand Down
7 changes: 6 additions & 1 deletion trainings/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import (
)

func (h *Handlers) Reset(ctx context.Context) error {
_, err := h.nextExercise(ctx, "")
trainingRoot, err := h.config.FindTrainingRoot()
if err != nil {
return err
}

_, err = h.nextExercise(ctx, "", trainingRoot)
if err != nil {
return err
}
Expand Down
39 changes: 35 additions & 4 deletions trainings/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ func (h *Handlers) detachedRun(ctx context.Context, trainingRootFs *afero.BasePa
os.Exit(0)
}

finished, err := h.nextExercise(ctx, h.config.ExerciseConfig(trainingRootFs).ExerciseID)
trainingRoot, err := h.config.FindTrainingRoot()
if err != nil {
return err
}

finished, err := h.nextExercise(ctx, h.config.ExerciseConfig(trainingRootFs).ExerciseID, trainingRoot)
if err != nil {
return err
}
Expand Down Expand Up @@ -122,7 +127,12 @@ func (h *Handlers) interactiveRun(ctx context.Context, trainingRootFs *afero.Bas
continue
}

finished, err := h.nextExercise(ctx, h.config.ExerciseConfig(trainingRootFs).ExerciseID)
trainingRoot, err := h.config.FindTrainingRoot()
if err != nil {
return err
}

finished, err := h.nextExercise(ctx, h.config.ExerciseConfig(trainingRootFs).ExerciseID, trainingRoot)
if err != nil {
return err
}
Expand Down Expand Up @@ -154,7 +164,12 @@ func (h *Handlers) run(ctx context.Context, trainingRootFs *afero.BasePathFs) (b
os.Exit(0)
}

_, err = h.nextExercise(ctx, "")
trainingRoot, err := h.config.FindTrainingRoot()
if err != nil {
return false, err
}

_, err = h.nextExercise(ctx, "", trainingRoot)
return true, err
}

Expand Down Expand Up @@ -271,7 +286,23 @@ func printCommand(root string, command string) {
}

func printlnCommand(root string, command string) {
printCommand(root, command+"\n")
pwd, err := os.Getwd()
if err != nil {
fmt.Println("Error getting current directory:", err)
return
}

relPath, err := filepath.Rel(pwd, root)
if err != nil {
fmt.Println("Error getting relative path:", err)
return
}

if relPath != "." && relPath != "" {
relPath = "./" + relPath
}

printCommand(relPath, command+"\n")
}

func (h *Handlers) generateRunTerminalPath(trainingRootFs *afero.BasePathFs) string {
Expand Down