Skip to content

[Gitpod CLI] gp rebuild improvements #15740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 18, 2023
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
130 changes: 74 additions & 56 deletions components/gitpod-cli/cmd/rebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"

"github.com/gitpod-io/gitpod/gitpod-cli/pkg/supervisor"
Expand All @@ -19,8 +21,8 @@ import (
"github.com/spf13/cobra"
)

func TerminateExistingContainer() error {
cmd := exec.Command("docker", "ps", "-q", "-f", "label=gp-rebuild")
func TerminateExistingContainer(ctx context.Context) error {
cmd := exec.CommandContext(ctx, "docker", "ps", "-q", "-f", "label=gp-rebuild")
containerIds, err := cmd.Output()
if err != nil {
return err
Expand All @@ -31,13 +33,13 @@ func TerminateExistingContainer() error {
continue
}

cmd = exec.Command("docker", "stop", id)
cmd = exec.CommandContext(ctx, "docker", "stop", id)
err := cmd.Run()
if err != nil {
return err
}

cmd = exec.Command("docker", "rm", "-f", id)
cmd = exec.CommandContext(ctx, "docker", "rm", "-f", id)
err = cmd.Run()
if err != nil {
return err
Expand All @@ -47,17 +49,15 @@ func TerminateExistingContainer() error {
return nil
}

func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClient, event *utils.EventTracker) error {
func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClient, event *utils.EventTracker) (string, error) {
wsInfo, err := supervisorClient.Info.WorkspaceInfo(ctx, &api.WorkspaceInfoRequest{})
if err != nil {
event.Set("ErrorCode", utils.SystemErrorCode)
return err
return utils.Outcome_SystemErr, err
}

tmpDir, err := os.MkdirTemp("", "gp-rebuild-*")
if err != nil {
event.Set("ErrorCode", utils.SystemErrorCode)
return err
return utils.Outcome_SystemErr, err
}
defer os.RemoveAll(tmpDir)

Expand All @@ -68,7 +68,7 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie
fmt.Println("For help check out the reference page:")
fmt.Println("https://www.gitpod.io/docs/references/gitpod-yml#gitpodyml")
event.Set("ErrorCode", utils.RebuildErrorCode_MalformedGitpodYaml)
return err
return utils.Outcome_UserErr, err
}

if gitpodConfig == nil {
Expand All @@ -79,7 +79,7 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie
fmt.Println("Alternatively, check out the following docs for getting started configuring your project")
fmt.Println("https://www.gitpod.io/docs/configure#configure-gitpod")
event.Set("ErrorCode", utils.RebuildErrorCode_MissingGitpodYaml)
return err
return utils.Outcome_UserErr, nil
}

var baseimage string
Expand All @@ -93,13 +93,11 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie

if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) {
fmt.Println("Your .gitpod.yml points to a Dockerfile that doesn't exist: " + dockerfilePath)
event.Set("ErrorCode", utils.RebuildErrorCode_DockerfileNotFound).Send(ctx)
return err
return utils.Outcome_UserErr, err
}
dockerfile, err := os.ReadFile(dockerfilePath)
if err != nil {
event.Set("ErrorCode", utils.RebuildErrorCode_DockerfileCannotRead)
return err
return utils.Outcome_SystemErr, err
}
if string(dockerfile) == "" {
fmt.Println("Your Gitpod's Dockerfile is empty")
Expand All @@ -108,14 +106,13 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie
fmt.Println("https://www.gitpod.io/docs/configure/workspaces/workspace-image#use-a-custom-dockerfile")
fmt.Println("")
fmt.Println("Once you configure your Dockerfile, re-run this command to validate your changes")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerfileEmpty)
return err
return utils.Outcome_UserErr, nil
}
baseimage = "\n" + string(dockerfile) + "\n"
default:
fmt.Println("Check your .gitpod.yml and make sure the image property is configured correctly")
event.Set("ErrorCode", utils.RebuildErrorCode_MalformedGitpodYaml)
return err
return utils.Outcome_UserErr, nil
}

if baseimage == "" {
Expand All @@ -124,94 +121,107 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie
fmt.Println("")
fmt.Println("https://www.gitpod.io/docs/configure/workspaces/workspace-image#use-a-public-docker-image")
event.Set("ErrorCode", utils.RebuildErrorCode_NoCustomImage)
return err
return utils.Outcome_UserErr, nil
}

err = os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(baseimage), 0644)
tmpDockerfile := filepath.Join(tmpDir, "Dockerfile")

err = os.WriteFile(tmpDockerfile, []byte(baseimage), 0644)
if err != nil {
fmt.Println("Could not write the temporary Dockerfile")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerfileCannotWirte)
return err
return utils.Outcome_SystemErr, err
}

dockerPath, err := exec.LookPath("docker")
if err != nil {
fmt.Println("Docker is not installed in your workspace")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerNotFound)
return err
return utils.Outcome_SystemErr, err
}

tag := "gp-rebuild-temp-build"

dockerCmd := exec.Command(dockerPath, "build", "-t", tag, "--progress=tty", ".")
dockerCmd.Dir = tmpDir
dockerCmd := exec.CommandContext(ctx, dockerPath, "build", "-f", tmpDockerfile, "-t", tag, wsInfo.CheckoutLocation)
dockerCmd.Stdout = os.Stdout
dockerCmd.Stderr = os.Stderr

imageBuildStartTime := time.Now()
err = dockerCmd.Run()
if _, ok := err.(*exec.ExitError); ok {
fmt.Println("Image Build Failed")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerBuildFailed)
return err
event.Set("ErrorCode", utils.RebuildErrorCode_ImageBuildFailed)
return utils.Outcome_UserErr, nil
} else if err != nil {
fmt.Println("Docker error")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerErr)
return err
return utils.Outcome_SystemErr, err
}
ImageBuildDuration := time.Since(imageBuildStartTime).Milliseconds()
event.Set("ImageBuildDuration", ImageBuildDuration)

err = TerminateExistingContainer()
err = TerminateExistingContainer(ctx)
if err != nil {
event.Set("ErrorCode", utils.SystemErrorCode)
return err
}

messages := []string{
"\n\nYou are now connected to the container",
"You can inspect the container and make sure the necessary tools & libraries are installed.",
"When you are done, just type exit to return to your Gitpod workspace\n",
return utils.Outcome_SystemErr, err
}

welcomeMessage := strings.Join(messages, "\n")
welcomeMessage := strings.Join([]string{
"\n\nYou are now connected to the container.",
"Check if all tools and libraries you need are properly installed.",
"When you are done, type \"exit\" to return to your Gitpod workspace.\n",
}, "\n")

dockerRunCmd := exec.Command(
dockerRunCmd := exec.CommandContext(ctx,
dockerPath,
"run",
"--rm",
"-v", "/workspace:/workspace",
"--label", "gp-rebuild=true",
"-it",
tag,
"bash",
"-it", tag,
"sh",
"-c",
fmt.Sprintf("echo '%s'; bash", welcomeMessage),
fmt.Sprintf(`
echo "%s";
cd "%s";
if [ -x "$(command -v $SHELL)" ]; then
$SHELL;
else
if [ -x "$(command -v bash)" ]; then
bash;
else
sh;
fi;
fi;
`, welcomeMessage, wsInfo.CheckoutLocation),
)

dockerRunCmd.Stdout = os.Stdout
dockerRunCmd.Stderr = os.Stderr
dockerRunCmd.Stdin = os.Stdin

err = dockerRunCmd.Run()
if _, ok := err.(*exec.ExitError); ok {
fmt.Println("Docker Run Command Failed")
err = dockerRunCmd.Start()
if err != nil {
fmt.Println("Failed to run docker container")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerRunFailed)
return err
} else if err != nil {
fmt.Println("Docker error")
event.Set("ErrorCode", utils.RebuildErrorCode_DockerErr)
return err
return utils.Outcome_UserErr, err
}

return nil
_ = dockerRunCmd.Wait()

return utils.Outcome_Success, nil
}

var buildCmd = &cobra.Command{
Use: "rebuild",
Short: "Re-builds the workspace image (useful to debug a workspace custom image)",
Hidden: false,
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
go func() {
<-sigChan
cancel()
}()
supervisorClient, err := supervisor.New(ctx)
if err != nil {
utils.LogError(ctx, err, "Could not get workspace info required to build", supervisorClient)
Expand All @@ -223,10 +233,18 @@ var buildCmd = &cobra.Command{
Command: cmd.Name(),
})

err = runRebuild(ctx, supervisorClient, event)
if err != nil && event.Data.ErrorCode == "" {
event.Set("ErrorCode", utils.SystemErrorCode)
outcome, err := runRebuild(ctx, supervisorClient, event)
event.Set("Outcome", outcome)

if outcome != utils.Outcome_Success && event.Data.ErrorCode == "" {
switch outcome {
case utils.Outcome_UserErr:
event.Set("ErrorCode", utils.UserErrorCode)
case utils.Outcome_SystemErr:
event.Set("ErrorCode", utils.SystemErrorCode)
}
}

event.Send(ctx)

if err != nil {
Expand Down
28 changes: 17 additions & 11 deletions components/gitpod-cli/pkg/utils/trackEvent.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,25 @@ import (
log "github.com/sirupsen/logrus"
)

const (
Outcome_Success = "success"
Outcome_UserErr = "user_error"
Outcome_SystemErr = "system_error"
)

const (
// System
SystemErrorCode = "system_error"
UserErrorCode = "user_error"

// Rebuild
RebuildErrorCode_DockerBuildFailed = "rebuild_docker_build_failed"
RebuildErrorCode_DockerErr = "rebuild_docker_err"
RebuildErrorCode_DockerfileCannotRead = "rebuild_dockerfile_cannot_read"
RebuildErrorCode_DockerfileCannotWirte = "rebuild_dockerfile_cannot_write"
RebuildErrorCode_DockerfileEmpty = "rebuild_dockerfile_empty"
RebuildErrorCode_DockerfileNotFound = "rebuild_dockerfile_not_found"
RebuildErrorCode_DockerNotFound = "rebuild_docker_not_found"
RebuildErrorCode_DockerRunFailed = "rebuild_docker_run_failed"
RebuildErrorCode_MalformedGitpodYaml = "rebuild_malformed_gitpod_yaml"
RebuildErrorCode_MissingGitpodYaml = "rebuild_missing_gitpod_yaml"
RebuildErrorCode_NoCustomImage = "rebuild_no_custom_image"
RebuildErrorCode_ImageBuildFailed = "rebuild_image_build_failed"
RebuildErrorCode_DockerErr = "rebuild_docker_err"
RebuildErrorCode_DockerNotFound = "rebuild_docker_not_found"
RebuildErrorCode_DockerRunFailed = "rebuild_docker_run_failed"
RebuildErrorCode_MalformedGitpodYaml = "rebuild_malformed_gitpod_yaml"
RebuildErrorCode_MissingGitpodYaml = "rebuild_missing_gitpod_yaml"
RebuildErrorCode_NoCustomImage = "rebuild_no_custom_image"
)

type TrackCommandUsageParams struct {
Expand All @@ -41,6 +44,7 @@ type TrackCommandUsageParams struct {
InstanceId string `json:"instanceId,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
ImageBuildDuration int64 `json:"imageBuildDuration,omitempty"`
Outcome string `json:"outcome,omitempty"`
}

type EventTracker struct {
Expand Down Expand Up @@ -96,6 +100,8 @@ func (t *EventTracker) Set(key string, value interface{}) *EventTracker {
t.Data.InstanceId = value.(string)
case "ImageBuildDuration":
t.Data.ImageBuildDuration = value.(int64)
case "Outcome":
t.Data.Outcome = value.(string)
}
return t
}
Expand Down