Skip to content
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

fix/workspace entry after offline #1599

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
22 changes: 14 additions & 8 deletions desktop/src/lib/useStoreTroubleshoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,19 @@ export function useStoreTroubleshoot() {

const zip = new JSZip()

const logFilesData = await Promise.all(
unwrappedLogFiles.map(async ([src, target]) => {
const data = await client.readFile(src)

return { fileName: target, data }
})
)
const logFilesData = (
await Promise.all(
unwrappedLogFiles.map(async ([src, target]) => {
try {
const data = await client.readFile(src)
return { fileName: target, data }
} catch (err) {
// ignore missing log files and continue
return null
}
})
)
).filter((d): d is Exclude<typeof d, null> => d != null)

logFilesData.forEach((logFile) => {
zip.file(logFile.fileName, logFile.data)
Expand All @@ -67,7 +73,7 @@ export function useStoreTroubleshoot() {
},
onError(error) {
toast({
title: `Failed to save logs: ${error}`,
title: `Failed to save zip: ${error}`,
status: "error",
isClosable: true,
duration: 30_000, // 30 sec
Expand Down
70 changes: 49 additions & 21 deletions pkg/workspace/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"slices"
"strconv"
"strings"
"sync"
Expand All @@ -18,6 +18,7 @@ import (
providerpkg "github.com/loft-sh/devpod/pkg/provider"
"github.com/loft-sh/devpod/pkg/types"
"github.com/loft-sh/log"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

Expand All @@ -31,21 +32,23 @@ func List(ctx context.Context, devPodConfig *config.Config, skipPro bool, log lo
proWorkspaces := []*providerpkg.Workspace{}
if !skipPro {
// list remote workspaces
proWorkspaces, err = listProWorkspaces(ctx, devPodConfig, log)
proWorkspaceResults, err := listProWorkspaces(ctx, devPodConfig, log)
if err != nil {
return nil, err
}
proWorkspacesByUID := map[string]*providerpkg.Workspace{}
for _, w := range proWorkspaces {
proWorkspacesByUID[w.UID] = w
// extract pure workspace list first
for _, result := range proWorkspaceResults {
proWorkspaces = append(proWorkspaces, result.workspaces...)
}

// Check if every local file based workspace has a remote counterpart
// If not, mark `exists` as false and allow consumers of this function to take necessary measures
// If not, delete it
// However, we need to differentiate between workspaces that are legitimately not available anymore
// and the ones where we were temporarily not able to reach the host
cleanedLocalWorkspaces := []*providerpkg.Workspace{}
for _, localWorkspace := range localWorkspaces {
if localWorkspace.IsPro() {
if _, ok := proWorkspacesByUID[localWorkspace.UID]; !ok {
if shouldDeleteLocalWorkspace(localWorkspace, proWorkspaceResults) {
err = clientimplementation.DeleteWorkspaceFolder(devPodConfig.DefaultContext, localWorkspace.ID, "", log)
if err != nil {
log.Debugf("failed to delete local workspace %s: %v", localWorkspace.ID, err)
Expand Down Expand Up @@ -107,9 +110,16 @@ func ListLocalWorkspaces(contextName string, skipPro bool, log log.Logger) ([]*p
return retWorkspaces, nil
}

func listProWorkspaces(ctx context.Context, devPodConfig *config.Config, log log.Logger) ([]*providerpkg.Workspace, error) {
retWorkspaces := []*providerpkg.Workspace{}
// lock around `retWorkspaces`
var errListProWorkspaces = errors.New("list pro workspaces")

type listProWorkspacesResult struct {
workspaces []*providerpkg.Workspace
err error
}

func listProWorkspaces(ctx context.Context, devPodConfig *config.Config, log log.Logger) (map[string]listProWorkspacesResult, error) {
results := map[string]listProWorkspacesResult{}
// lock around `results`
var mu sync.Mutex
wg := sync.WaitGroup{}

Expand All @@ -132,24 +142,23 @@ func listProWorkspaces(ctx context.Context, devPodConfig *config.Config, log log
go func() {
defer wg.Done()
workspaces, err := listProWorkspacesForProvider(ctx, devPodConfig, provider, providerConfig, log)
if err != nil {
log.ErrorStreamOnly().Warn(err)
return
}
mu.Lock()
defer mu.Unlock()
retWorkspaces = append(retWorkspaces, workspaces...)
results[provider] = listProWorkspacesResult{
workspaces: workspaces,
err: err,
}
}()
}
wg.Wait()

return retWorkspaces, nil
return results, nil
}

func listProWorkspacesForProvider(ctx context.Context, devPodConfig *config.Config, provider string, providerConfig *providerpkg.ProviderConfig, log log.Logger) ([]*providerpkg.Workspace, error) {
opts := devPodConfig.ProviderOptions(provider)
opts[providerpkg.LOFT_FILTER_BY_OWNER] = config.OptionValue{Value: "true"}
var buf bytes.Buffer
var stdout bytes.Buffer
if err := clientimplementation.RunCommandWithBinaries(
ctx,
"listWorkspaces",
Expand All @@ -159,16 +168,16 @@ func listProWorkspacesForProvider(ctx context.Context, devPodConfig *config.Conf
nil,
opts,
providerConfig,
nil, nil, &buf, log.ErrorStreamOnly().Writer(logrus.ErrorLevel, false), log,
nil, nil, &stdout, log.ErrorStreamOnly().Writer(logrus.ErrorLevel, false), log,
); err != nil {
return nil, fmt.Errorf("list workspaces for provider \"%s\": %w", provider, err)
return nil, errListProWorkspaces
}
if buf.Len() == 0 {
if stdout.Len() == 0 {
return nil, nil
}

instances := []managementv1.DevPodWorkspaceInstance{}
if err := json.Unmarshal(buf.Bytes(), &instances); err != nil {
if err := json.Unmarshal(stdout.Bytes(), &instances); err != nil {
log.ErrorStreamOnly().Errorf("unmarshal devpod workspace instances: %w", err)
}

Expand Down Expand Up @@ -253,3 +262,22 @@ func listProWorkspacesForProvider(ctx context.Context, devPodConfig *config.Conf

return retWorkspaces, nil
}

func shouldDeleteLocalWorkspace(localWorkspace *providerpkg.Workspace, proWorkspaceResults map[string]listProWorkspacesResult) bool {
// get the correct result for this local workspace
res, ok := proWorkspaceResults[localWorkspace.Provider.Name]
if !ok {
return false
}
if isTransientError(res.err) {
return false
}
hasProCounterpart := slices.ContainsFunc(res.workspaces, func(w *providerpkg.Workspace) bool {
return localWorkspace.UID == w.UID
})
return !hasProCounterpart
}

func isTransientError(err error) bool {
return errors.Is(err, errListProWorkspaces)
}
Loading