Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/internal/session/workspace.go
2500 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package session
6
7
import (
8
"context"
9
"encoding/json"
10
"errors"
11
"fmt"
12
"io/fs"
13
"os"
14
"path/filepath"
15
"time"
16
17
"github.com/opentracing/opentracing-go"
18
"github.com/sirupsen/logrus"
19
"golang.org/x/xerrors"
20
21
"github.com/gitpod-io/gitpod/common-go/log"
22
"github.com/gitpod-io/gitpod/common-go/tracing"
23
csapi "github.com/gitpod-io/gitpod/content-service/api"
24
"github.com/gitpod-io/gitpod/content-service/pkg/git"
25
)
26
27
const (
28
// AttrRemoteStorage is the name of the remote storage associated with a workspace.
29
// Expect this to be an instance of storage.RemoteStorage
30
AttrRemoteStorage = "remote-storage"
31
32
// AttrWorkspaceServer is the name of the workspace server cancel func.
33
// Expect this to be an instance of context.CancelFunc
34
AttrWorkspaceServer = "workspace-server"
35
36
// AttrWaitForContent is the name of the wait-for-content probe cancel func.
37
// Expect this to be an instance of context.CancelFunc
38
AttrWaitForContent = "wait-for-content"
39
)
40
41
const (
42
// maxPendingChanges is the limit beyond which we no longer report pending changes.
43
// For example, if a workspace has then 150 untracked files, we'll report the first
44
// 100 followed by "... and 50 more".
45
//
46
// We do this to keep the load on our infrastructure light and because beyond this number
47
// the changes are irrelevant anyways.
48
maxPendingChanges = 100
49
)
50
51
// Workspace is a single workspace on-disk that we're managing.
52
type Workspace struct {
53
// Location is the absolute path in the local filesystem where to find this workspace
54
Location string `json:"location"`
55
// CheckoutLocation is the path relative to location where the main Git working copy of this
56
// workspace resides. If this workspace has no Git working copy, this field is an empty string.
57
CheckoutLocation string `json:"checkoutLocation"`
58
59
CreatedAt time.Time `json:"createdAt"`
60
DoBackup bool `json:"doBackup"`
61
// DoWipe is a mode that a) does not make backups and b) ensures leaving a clean slate on workspace stop
62
DoWipe bool `json:"doWipe"`
63
Owner string `json:"owner"`
64
WorkspaceID string `json:"metaID"`
65
InstanceID string `json:"workspaceID"`
66
LastGitStatus *csapi.GitStatus `json:"lastGitStatus"`
67
ContentManifest []byte `json:"contentManifest"`
68
69
ServiceLocNode string `json:"serviceLocNode"`
70
ServiceLocDaemon string `json:"serviceLocDaemon"`
71
72
RemoteStorageDisabled bool `json:"remoteStorageDisabled,omitempty"`
73
StorageQuota int `json:"storageQuota,omitempty"`
74
75
XFSProjectID int `json:"xfsProjectID"`
76
77
NonPersistentAttrs map[string]interface{} `json:"-"`
78
}
79
80
// OWI produces the owner, workspace, instance log metadata from the information
81
// of this workspace.
82
func (s *Workspace) OWI() logrus.Fields {
83
return log.OWI(s.Owner, s.WorkspaceID, s.InstanceID)
84
}
85
86
// WorkspaceState is the lifecycle state of a workspace
87
type WorkspaceState string
88
89
const (
90
// WorkspaceInitializing means the workspace content is currently being initialized
91
WorkspaceInitializing WorkspaceState = "initializing"
92
// WorkspaceReady means the workspace content is available on disk
93
WorkspaceReady WorkspaceState = "ready"
94
// WorkspaceDisposing means the workspace content is currently being backed up and removed from disk.
95
// No workspace content modifications must take place anymore.
96
WorkspaceDisposing WorkspaceState = "disposing"
97
// WorkspaceDisposed means the workspace content has been backed up and will be removed from disk soon.
98
WorkspaceDisposed WorkspaceState = "disposed"
99
)
100
101
// WorkspaceLivecycleHook can modify a workspace's non-persistent state.
102
// They're intended to start regular operations or initialize non-persistent objects.
103
type WorkspaceLivecycleHook func(ctx context.Context, ws *Workspace) error
104
105
// Dispose marks the workspace as disposed and clears it from disk
106
func (s *Workspace) Dispose(ctx context.Context, hooks []WorkspaceLivecycleHook) (err error) {
107
//nolint:ineffassign,staticcheck
108
span, ctx := opentracing.StartSpanFromContext(ctx, "workspace.Dispose")
109
defer tracing.FinishSpan(span, &err)
110
111
// we remove the workspace file first, so that should something go wrong while deleting the
112
// old workspace content we can garbage collect that content later.
113
err = os.Remove(s.persistentStateLocation())
114
if err != nil {
115
if errors.Is(err, fs.ErrNotExist) {
116
log.WithError(err).WithFields(s.OWI()).Warn("workspace persistent state location not exist")
117
err = nil
118
} else {
119
return xerrors.Errorf("cannot remove workspace persistent state location: %w", err)
120
}
121
}
122
123
for _, h := range hooks {
124
err := h(ctx, s)
125
if err != nil {
126
return err
127
}
128
}
129
130
err = os.RemoveAll(s.Location)
131
if err != nil {
132
return xerrors.Errorf("cannot remove workspace all: %w", err)
133
}
134
135
return nil
136
}
137
138
// UpdateGitStatus attempts to update the LastGitStatus from the workspace's local working copy.
139
func (s *Workspace) UpdateGitStatus(ctx context.Context) (res *csapi.GitStatus, err error) {
140
var loc string
141
142
loc = s.Location
143
if loc == "" {
144
log.WithField("loc", loc).WithFields(s.OWI()).Debug("not updating Git status of FWB workspace")
145
return
146
}
147
148
loc = filepath.Join(loc, s.CheckoutLocation)
149
if !git.IsWorkingCopy(loc) {
150
log.WithField("loc", loc).WithField("checkout location", s.CheckoutLocation).WithFields(s.OWI()).Debug("did not find a Git working copy - not updating Git status")
151
return nil, nil
152
}
153
154
c := git.Client{Location: loc}
155
156
stat, err := c.Status(ctx)
157
if err != nil {
158
return nil, err
159
}
160
161
s.LastGitStatus = toGitStatus(stat)
162
163
err = s.Persist()
164
if err != nil {
165
log.WithError(err).WithFields(s.OWI()).Warn("cannot persist latest Git status")
166
}
167
168
return s.LastGitStatus, nil
169
}
170
171
func toGitStatus(s *git.Status) *csapi.GitStatus {
172
limit := func(entries []string) []string {
173
if len(entries) > maxPendingChanges {
174
return append(entries[0:maxPendingChanges], fmt.Sprintf("... and %d more", len(entries)-maxPendingChanges))
175
}
176
177
return entries
178
}
179
180
return &csapi.GitStatus{
181
Branch: s.BranchHead,
182
LatestCommit: s.LatestCommit,
183
UncommitedFiles: limit(s.UncommitedFiles),
184
TotalUncommitedFiles: int64(len(s.UncommitedFiles)),
185
UntrackedFiles: limit(s.UntrackedFiles),
186
TotalUntrackedFiles: int64(len(s.UntrackedFiles)),
187
UnpushedCommits: limit(s.UnpushedCommits),
188
TotalUnpushedCommits: int64(len(s.UnpushedCommits)),
189
}
190
}
191
192
func (s *Workspace) persistentStateLocation() string {
193
return filepath.Join(filepath.Dir(s.Location), fmt.Sprintf("%s.workspace.json", s.InstanceID))
194
}
195
196
func (s *Workspace) Persist() error {
197
fc, err := json.Marshal(s)
198
if err != nil {
199
return xerrors.Errorf("cannot marshal workspace: %w", err)
200
}
201
202
err = os.WriteFile(s.persistentStateLocation(), fc, 0644)
203
if err != nil {
204
return xerrors.Errorf("cannot persist workspace: %w", err)
205
}
206
207
return nil
208
}
209
210
func LoadWorkspace(ctx context.Context, path string) (sess *Workspace, err error) {
211
span, _ := opentracing.StartSpanFromContext(ctx, "loadWorkspace")
212
defer tracing.FinishSpan(span, &err)
213
214
fc, err := os.ReadFile(path)
215
if err != nil {
216
return nil, fmt.Errorf("cannot load session file: %w", err)
217
}
218
219
var workspace Workspace
220
err = json.Unmarshal(fc, &workspace)
221
if err != nil {
222
return nil, fmt.Errorf("cannot unmarshal session file: %w", err)
223
}
224
225
workspace.NonPersistentAttrs = make(map[string]interface{})
226
return &workspace, nil
227
}
228
229