Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/content-service/pkg/initializer/prebuild.go
2499 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 initializer
6
7
import (
8
"context"
9
"errors"
10
"fmt"
11
"os"
12
"path/filepath"
13
"strings"
14
"time"
15
16
"github.com/opentracing/opentracing-go"
17
tracelog "github.com/opentracing/opentracing-go/log"
18
"golang.org/x/xerrors"
19
20
"github.com/gitpod-io/gitpod/common-go/log"
21
"github.com/gitpod-io/gitpod/common-go/tracing"
22
csapi "github.com/gitpod-io/gitpod/content-service/api"
23
"github.com/gitpod-io/gitpod/content-service/pkg/archive"
24
"github.com/gitpod-io/gitpod/content-service/pkg/git"
25
)
26
27
// PrebuildInitializer first tries to restore the snapshot/prebuild and if that succeeds performs Git operations.
28
// If restoring the prebuild does not succeed we fall back to Git entriely.
29
type PrebuildInitializer struct {
30
Git []*GitInitializer
31
Prebuild *SnapshotInitializer
32
}
33
34
// Run runs the prebuild initializer
35
func (p *PrebuildInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, stats csapi.InitializerMetrics, err error) {
36
//nolint:ineffassign
37
span, ctx := opentracing.StartSpanFromContext(ctx, "PrebuildInitializer")
38
defer tracing.FinishSpan(span, &err)
39
startTime := time.Now()
40
initialSize, fsErr := getFsUsage()
41
if fsErr != nil {
42
log.WithError(fsErr).Error("could not get disk usage")
43
}
44
45
var spandata []tracelog.Field
46
if p.Prebuild == nil {
47
spandata = append(spandata, tracelog.Bool("hasSnapshot", false))
48
} else {
49
spandata = append(spandata,
50
tracelog.Bool("hasSnapshot", true),
51
tracelog.String("snapshot", p.Prebuild.Snapshot),
52
)
53
}
54
if len(p.Git) == 0 {
55
spandata = append(spandata, tracelog.Bool("hasGit", false))
56
} else {
57
spandata = append(spandata,
58
tracelog.Bool("hasGit", true),
59
)
60
}
61
span.LogFields(spandata...)
62
63
if p.Prebuild != nil {
64
var (
65
snapshot = p.Prebuild.Snapshot
66
location = p.Prebuild.Location
67
log = log.WithField("location", p.Prebuild.Location)
68
)
69
_, s, err := p.Prebuild.Run(ctx, mappings)
70
if err == nil {
71
stats = append(stats, s...)
72
}
73
74
if err != nil {
75
log.WithError(err).Warnf("prebuilt init was unable to restore snapshot %s. Resorting the regular Git init", snapshot)
76
77
if err := clearWorkspace(location); err != nil {
78
return csapi.WorkspaceInitFromOther, nil, xerrors.Errorf("prebuild initializer: %w", err)
79
}
80
81
for _, gi := range p.Git {
82
_, s, err := gi.Run(ctx, mappings)
83
if err != nil {
84
return csapi.WorkspaceInitFromOther, nil, xerrors.Errorf("prebuild initializer: Git fallback: %w", err)
85
}
86
stats = append(stats, s...)
87
}
88
}
89
}
90
91
// at this point we're actually a prebuild initialiser because we've been able to restore
92
// the prebuild.
93
94
src = csapi.WorkspaceInitFromPrebuild
95
96
// make sure we're on the correct branch
97
for _, gi := range p.Git {
98
99
commitChanged, err := runGitInit(ctx, gi)
100
if err != nil {
101
return src, nil, err
102
}
103
if commitChanged {
104
// head commit has changed, so it's an outdated prebuild, which we treat as other
105
src = csapi.WorkspaceInitFromOther
106
}
107
}
108
log.Debug("Initialized workspace with prebuilt snapshot")
109
110
if fsErr == nil {
111
currentSize, fsErr := getFsUsage()
112
if fsErr != nil {
113
log.WithError(fsErr).Error("could not get disk usage")
114
}
115
116
stats = append(stats, csapi.InitializerMetric{
117
Type: "prebuild",
118
Duration: time.Since(startTime),
119
Size: currentSize - initialSize,
120
})
121
}
122
123
return
124
}
125
126
func clearWorkspace(location string) error {
127
files, err := filepath.Glob(filepath.Join(location, "*"))
128
if err != nil {
129
return err
130
}
131
for _, file := range files {
132
err = os.RemoveAll(file)
133
if err != nil {
134
return xerrors.Errorf("prebuild initializer: %w", err)
135
}
136
}
137
return nil
138
}
139
140
func runGitInit(ctx context.Context, gInit *GitInitializer) (commitChanged bool, err error) {
141
span, ctx := opentracing.StartSpanFromContext(ctx, "runGitInit")
142
span.LogFields(
143
tracelog.String("IsWorkingCopy", fmt.Sprintf("%v", git.IsWorkingCopy(gInit.Location))),
144
tracelog.String("location", fmt.Sprintf("%v", gInit.Location)),
145
)
146
defer tracing.FinishSpan(span, &err)
147
if git.IsWorkingCopy(gInit.Location) {
148
out, err := gInit.GitWithOutput(ctx, nil, "stash", "push", "--no-include-untracked")
149
if err != nil {
150
var giterr git.OpFailedError
151
if errors.As(err, &giterr) && strings.Contains(giterr.Output, "You do not have the initial commit yet") {
152
// git stash push returns a non-zero exit code if the repository does not have a single commit.
153
// In this case that's not an error though, hence we don't want to fail here.
154
} else {
155
// git returned a non-zero exit code because of some reason we did not anticipate or an actual failure.
156
log.WithError(err).WithField("output", string(out)).Error("unexpected git stash error")
157
return commitChanged, xerrors.Errorf("prebuild initializer: %w", err)
158
}
159
}
160
didStash := !strings.Contains(string(out), "No local changes to save")
161
162
statusBefore, err := gInit.Status(ctx)
163
if err != nil {
164
log.WithError(err).Warn("couldn't run git status - continuing")
165
}
166
err = checkGitStatus(gInit.realizeCloneTarget(ctx))
167
if err != nil {
168
return commitChanged, xerrors.Errorf("prebuild initializer: %w", err)
169
}
170
statusAfter, err := gInit.Status(ctx)
171
if err != nil {
172
log.WithError(err).Warn("couldn't run git status - continuing")
173
}
174
if statusBefore != nil && statusAfter != nil {
175
commitChanged = statusBefore.LatestCommit != statusAfter.LatestCommit
176
}
177
178
err = gInit.UpdateSubmodules(ctx)
179
if err != nil {
180
log.WithError(err).Warn("error while updating submodules from prebuild initializer - continuing")
181
}
182
183
// If any of these cleanup operations fail that's no reason to fail ws initialization.
184
// It just results in a slightly degraded state.
185
if didStash {
186
err = gInit.Git(ctx, "stash", "pop")
187
if err != nil {
188
// If restoring the stashed changes produces merge conflicts on the new Git ref, simply
189
// throw them away (they'll remain in the stash, but are likely outdated anyway).
190
_ = gInit.Git(ctx, "reset", "--hard")
191
}
192
}
193
194
log.Debug("prebuild initializer Git operations complete")
195
}
196
197
return commitChanged, nil
198
}
199
200