Skip to content

Commit

Permalink
feat: add workspace uid
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianKramm committed Mar 28, 2023
1 parent 18d4bd9 commit 90307cc
Show file tree
Hide file tree
Showing 29 changed files with 1,319 additions and 75 deletions.
14 changes: 13 additions & 1 deletion cmd/agent/workspace/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"github.com/loft-sh/devpod/pkg/agent"
"github.com/loft-sh/devpod/pkg/devcontainer"
"github.com/loft-sh/devpod/pkg/log"
provider2 "github.com/loft-sh/devpod/pkg/provider"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"os"
"path/filepath"
)

// BuildCmd holds the cmd flags
Expand Down Expand Up @@ -44,7 +46,7 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
// Run runs the command logic
func (cmd *BuildCmd) Run(ctx context.Context) error {
// write workspace info
shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfo(cmd.WorkspaceInfo, log.Default.ErrorStreamOnly())
shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfoAndDeleteOld(cmd.WorkspaceInfo, deleteWorkspace, log.Default.ErrorStreamOnly())
if err != nil {
return err
} else if shouldExit {
Expand Down Expand Up @@ -76,3 +78,13 @@ func (cmd *BuildCmd) Run(ctx context.Context) error {
logger.Donef("Successfully build and pushed image %s", imageName)
return nil
}

func deleteWorkspace(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error {
err := removeContainer(workspaceInfo, log)
if err != nil {
return errors.Wrap(err, "remove container")
}

_ = os.RemoveAll(filepath.Join(workspaceInfo.Folder, ".."))
return nil
}
2 changes: 1 addition & 1 deletion cmd/agent/workspace/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func NewUpCmd(flags *flags.GlobalFlags) *cobra.Command {
// Run runs the command logic
func (cmd *UpCmd) Run(ctx context.Context) error {
// get workspace
shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfo(cmd.WorkspaceInfo, log.Default.ErrorStreamOnly())
shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfoAndDeleteOld(cmd.WorkspaceInfo, deleteWorkspace, log.Default.ErrorStreamOnly())
if err != nil {
return fmt.Errorf("error parsing workspace info: %v", err)
} else if shouldExit {
Expand Down
95 changes: 55 additions & 40 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,59 +97,74 @@ func (cmd *DeleteCmd) Run(ctx context.Context, devPodConfig *config.Config, args
duration = &gracePeriod
}

// delete if single machine provider
wasDeleted, err := cmd.deleteSingleMachine(ctx, client, devPodConfig, duration)
if err != nil {
return err
} else if wasDeleted {
return nil
}

// destroy environment
err = client.Delete(ctx, client2.DeleteOptions{
Force: cmd.Force,
GracePeriod: duration,
})
if err != nil {
return err
}

log.Default.Donef("Successfully deleted workspace '%s'", client.Workspace())
return nil
}

func (cmd *DeleteCmd) deleteSingleMachine(ctx context.Context, client client2.WorkspaceClient, devPodConfig *config.Config, duration *time.Duration) (bool, error) {
// check if single machine
singleMachineName := workspace2.SingleMachineName(client.Provider())
if devPodConfig.Current().IsSingleMachine(client.Provider()) && client.WorkspaceConfig().Machine.ID == singleMachineName {
workspaces, err := listWorkspaces(devPodConfig, log.Default)
if err != nil {
return errors.Wrap(err, "list workspaces")
}
if !devPodConfig.Current().IsSingleMachine(client.Provider()) || client.WorkspaceConfig().Machine.ID != singleMachineName {
return false, nil
}

// try to find other workspace with same machine
foundOther := false
for _, workspace := range workspaces {
if workspace.ID == client.Workspace() || workspace.Machine.ID != singleMachineName {
continue
}
// try to find other workspace with same machine
workspaces, err := listWorkspaces(devPodConfig, log.Default)
if err != nil {
return false, errors.Wrap(err, "list workspaces")
}

foundOther = true
break
// loop workspaces
foundOther := false
for _, workspace := range workspaces {
if workspace.ID == client.Workspace() || workspace.Machine.ID != singleMachineName {
continue
}

// if we haven't found another workspace on this machine, delete the whole machine
if !foundOther {
machineClient, err := workspace2.GetMachine(devPodConfig, []string{singleMachineName}, log.Default)
if err == nil {
err = machineClient.Delete(ctx, client2.DeleteOptions{
Force: cmd.Force,
GracePeriod: duration,
})
if err != nil {
return err
}

err = clientimplementation.DeleteWorkspaceFolder(client.Context(), client.Workspace())
if err != nil {
return err
}

log.Default.Donef("Successfully deleted workspace '%s'", client.Workspace())
return nil
} else {
log.Default.Errorf("Retrieving machine client: %v", err)
}
}
foundOther = true
break
}
if foundOther {
return false, nil
}

// destroy environment
err = client.Delete(ctx, client2.DeleteOptions{
// if we haven't found another workspace on this machine, delete the whole machine
machineClient, err := workspace2.GetMachine(devPodConfig, []string{singleMachineName}, log.Default)
if err != nil {
return false, errors.Wrap(err, "get machine")
}

// delete the machine
err = machineClient.Delete(ctx, client2.DeleteOptions{
Force: cmd.Force,
GracePeriod: duration,
})
if err != nil {
return err
return false, errors.Wrap(err, "delete machine")
}

err = clientimplementation.DeleteWorkspaceFolder(client.Context(), client.Workspace())
if err != nil {
return false, err
}

log.Default.Donef("Successfully deleted workspace '%s'", client.Workspace())
return nil
return true, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/go-enry/go-enry/v2 v2.8.4
github.com/gofrs/flock v0.8.1
github.com/google/go-containerregistry v0.13.0
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.5.1
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/loft-sh/utils v0.0.15
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
Expand Down
34 changes: 30 additions & 4 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ func readAgentWorkspaceInfo(agentFolder, context, id string) (*provider2.AgentWo
return nil, err
}

// parse agent workspace info
return parseAgentWorkspaceInfo(filepath.Join(workspaceDir, provider2.WorkspaceConfigFile))
}

func parseAgentWorkspaceInfo(workspaceConfigFile string) (*provider2.AgentWorkspaceInfo, error) {
// read workspace config
out, err := os.ReadFile(filepath.Join(workspaceDir, provider2.WorkspaceConfigFile))
out, err := os.ReadFile(workspaceConfigFile)
if err != nil {
return nil, err
}
Expand All @@ -67,7 +72,7 @@ func readAgentWorkspaceInfo(agentFolder, context, id string) (*provider2.AgentWo
return nil, errors.Wrap(err, "parse workspace info")
}

workspaceInfo.Folder = GetAgentWorkspaceContentDir(workspaceDir)
workspaceInfo.Folder = GetAgentWorkspaceContentDir(filepath.Dir(workspaceConfigFile))
return workspaceInfo, nil
}

Expand Down Expand Up @@ -136,6 +141,10 @@ func ReadAgentWorkspaceInfo(agentFolder, context, id string, log log.Logger) (bo
}

func WriteWorkspaceInfo(workspaceInfoEncoded string, log log.Logger) (bool, *provider2.AgentWorkspaceInfo, error) {
return WriteWorkspaceInfoAndDeleteOld(workspaceInfoEncoded, nil, log)
}

func WriteWorkspaceInfoAndDeleteOld(workspaceInfoEncoded string, deleteWorkspace func(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error, log log.Logger) (bool, *provider2.AgentWorkspaceInfo, error) {
workspaceInfo, decoded, err := DecodeWorkspaceInfo(workspaceInfoEncoded)
if err != nil {
return false, nil, err
Expand All @@ -155,11 +164,28 @@ func WriteWorkspaceInfo(workspaceInfoEncoded string, log log.Logger) (bool, *pro
return false, nil, err
}

// write workspace config
// check if workspace config already exists
workspaceConfig := filepath.Join(workspaceDir, provider2.WorkspaceConfigFile)
oldWorkspaceInfo, err := parseAgentWorkspaceInfo(workspaceConfig)
if oldWorkspaceInfo != nil && oldWorkspaceInfo.Workspace.UID != workspaceInfo.Workspace.UID {
// delete the old workspace
log.Infof("Delete old workspace '%s'", oldWorkspaceInfo.Workspace.ID)
err = deleteWorkspace(oldWorkspaceInfo, log)
if err != nil {
return false, nil, errors.Wrap(err, "delete old workspace")
}

// recreate workspace folder again
workspaceDir, err = CreateAgentWorkspaceDir(workspaceInfo.Agent.DataPath, workspaceInfo.Workspace.Context, workspaceInfo.Workspace.ID)
if err != nil {
return false, nil, err
}
}

// write workspace config
err = os.WriteFile(workspaceConfig, []byte(decoded), 0666)
if err != nil {
return false, nil, fmt.Errorf("write workspace config file")
return false, nil, fmt.Errorf("write workspace config file: %v", err)
}

workspaceInfo.Folder = GetAgentWorkspaceContentDir(workspaceDir)
Expand Down
87 changes: 58 additions & 29 deletions pkg/client/clientimplementation/workspace_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,39 +193,46 @@ func (s *workspaceClient) Delete(ctx context.Context, opt client.DeleteOptions)

// should just delete container?
if !s.isMachineProvider() || !s.workspace.Machine.AutoDelete {
writer := s.log.Writer(logrus.InfoLevel, false)
defer writer.Close()

s.log.Infof("Deleting container...")
agentConfig := options.ResolveAgentConfig(s.devPodConfig, s.config, s.workspace, s.machine)
command := fmt.Sprintf("%s agent workspace delete --id %s --context %s", agentConfig.Path, s.workspace.ID, s.workspace.Context)
if agentConfig.DataPath != "" {
command += fmt.Sprintf(" --agent-dir '%s'", agentConfig.DataPath)
}
err := RunCommandWithBinaries(
ctx,
"command",
s.config.Exec.Command,
s.workspace.Context,
s.workspace,
s.machine,
s.devPodConfig.ProviderOptions(s.config.Name),
s.config,
map[string]string{
provider.CommandEnv: command,
},
nil,
writer,
writer,
s.log.ErrorStreamOnly(),
)
isRunning, err := s.isMachineRunning(ctx)
if err != nil {
if !opt.Force {
return err
}

if err != context.DeadlineExceeded {
s.log.Errorf("Error deleting container: %v", err)
} else if isRunning {
writer := s.log.Writer(logrus.InfoLevel, false)
defer writer.Close()

s.log.Infof("Deleting container...")
agentConfig := options.ResolveAgentConfig(s.devPodConfig, s.config, s.workspace, s.machine)
command := fmt.Sprintf("%s agent workspace delete --id %s --context %s", agentConfig.Path, s.workspace.ID, s.workspace.Context)
if agentConfig.DataPath != "" {
command += fmt.Sprintf(" --agent-dir '%s'", agentConfig.DataPath)
}
err := RunCommandWithBinaries(
ctx,
"command",
s.config.Exec.Command,
s.workspace.Context,
s.workspace,
s.machine,
s.devPodConfig.ProviderOptions(s.config.Name),
s.config,
map[string]string{
provider.CommandEnv: command,
},
nil,
writer,
writer,
s.log.ErrorStreamOnly(),
)
if err != nil {
if !opt.Force {
return err
}

if err != context.DeadlineExceeded {
s.log.Errorf("Error deleting container: %v", err)
}
}
}
} else if s.machine != nil && s.workspace.Machine.ID != "" && len(s.config.Exec.Delete) > 0 {
Expand All @@ -246,6 +253,28 @@ func (s *workspaceClient) Delete(ctx context.Context, opt client.DeleteOptions)
return DeleteWorkspaceFolder(s.workspace.Context, s.workspace.ID)
}

func (s *workspaceClient) isMachineRunning(ctx context.Context) (bool, error) {
if !s.isMachineProvider() {
return true, nil
}

// delete machine if config was found
machineClient, err := NewMachineClient(s.devPodConfig, s.config, s.machine, s.log)
if err != nil {
return false, err
}

// retrieve status
status, err := machineClient.Status(ctx, client.StatusOptions{})
if err != nil {
return false, errors.Wrap(err, "retrieve machine status")
} else if status == client.StatusRunning {
return true, nil
}

return false, nil
}

func (s *workspaceClient) Start(ctx context.Context, options client.StartOptions) error {
s.m.Lock()
defer s.m.Unlock()
Expand Down
3 changes: 3 additions & 0 deletions pkg/provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type Workspace struct {
// ID is the workspace id to use
ID string `json:"id,omitempty"`

// UID is used to identify this specific workspace
UID string `json:"uid,omitempty"`

// Folder is the local folder where workspace related contents will be stored
Folder string `json:"folder,omitempty"`

Expand Down
5 changes: 5 additions & 0 deletions pkg/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package workspace
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/loft-sh/devpod/pkg/client"
"github.com/loft-sh/devpod/pkg/client/clientimplementation"
"github.com/loft-sh/devpod/pkg/command"
Expand Down Expand Up @@ -267,11 +268,13 @@ func createWorkspace(ctx context.Context, devPodConfig *config.Config, ide *prov

func resolve(defaultProvider *ProviderWithOptions, devPodConfig *config.Config, name, workspaceID, workspaceFolder string, isLocalPath bool) (*provider2.Workspace, error) {
now := types.Now()
uid := uuid.New().String()

// is local folder?
if isLocalPath {
return &provider2.Workspace{
ID: workspaceID,
UID: uid,
Folder: workspaceFolder,
Context: devPodConfig.DefaultContext,
Provider: provider2.WorkspaceProviderConfig{
Expand All @@ -290,6 +293,7 @@ func resolve(defaultProvider *ProviderWithOptions, devPodConfig *config.Config,
if strings.HasSuffix(name, ".git") || pingRepository(gitRepository) {
return &provider2.Workspace{
ID: workspaceID,
UID: uid,
Folder: workspaceFolder,
Context: devPodConfig.DefaultContext,
Provider: provider2.WorkspaceProviderConfig{
Expand All @@ -309,6 +313,7 @@ func resolve(defaultProvider *ProviderWithOptions, devPodConfig *config.Config,
if err == nil {
return &provider2.Workspace{
ID: workspaceID,
UID: uid,
Folder: workspaceFolder,
Context: devPodConfig.DefaultContext,
Provider: provider2.WorkspaceProviderConfig{
Expand Down
Loading

0 comments on commit 90307cc

Please sign in to comment.