-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
prebuild.go
156 lines (138 loc) · 4.9 KB
/
prebuild.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package initializer
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/opentracing/opentracing-go"
tracelog "github.com/opentracing/opentracing-go/log"
"golang.org/x/xerrors"
"github.com/gitpod-io/gitpod/common-go/log"
"github.com/gitpod-io/gitpod/common-go/tracing"
csapi "github.com/gitpod-io/gitpod/content-service/api"
"github.com/gitpod-io/gitpod/content-service/pkg/archive"
"github.com/gitpod-io/gitpod/content-service/pkg/git"
)
// PrebuildInitializer first tries to restore the snapshot/prebuild and if that succeeds performs Git operations.
// If restoring the prebuild does not succeed we fall back to Git entriely.
type PrebuildInitializer struct {
Git []*GitInitializer
Prebuild *SnapshotInitializer
}
// Run runs the prebuild initializer
func (p *PrebuildInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, err error) {
//nolint:ineffassign
span, ctx := opentracing.StartSpanFromContext(ctx, "PrebuildInitializer")
defer tracing.FinishSpan(span, &err)
var spandata []tracelog.Field
if p.Prebuild == nil {
spandata = append(spandata, tracelog.Bool("hasSnapshot", false))
} else {
spandata = append(spandata,
tracelog.Bool("hasSnapshot", true),
tracelog.String("snapshot", p.Prebuild.Snapshot),
)
}
if len(p.Git) == 0 {
spandata = append(spandata, tracelog.Bool("hasGit", false))
} else {
spandata = append(spandata,
tracelog.Bool("hasGit", true),
)
}
span.LogFields(spandata...)
if p.Prebuild != nil {
var (
snapshot = p.Prebuild.Snapshot
location = p.Prebuild.Location
log = log.WithField("location", p.Prebuild.Location)
)
_, err = p.Prebuild.Run(ctx, mappings)
if err != nil {
log.WithError(err).Warnf("prebuilt init was unable to restore snapshot %s. Resorting the regular Git init", snapshot)
if err := clearWorkspace(location); err != nil {
return csapi.WorkspaceInitFromOther, xerrors.Errorf("prebuild initializer: %w", err)
}
for _, gi := range p.Git {
_, err = gi.Run(ctx, mappings)
if err != nil {
return csapi.WorkspaceInitFromOther, xerrors.Errorf("prebuild initializer: Git fallback: %w", err)
}
}
}
}
// at this point we're actually a prebuild initialiser because we've been able to restore
// the prebuild.
src = csapi.WorkspaceInitFromPrebuild
// make sure we're on the correct branch
for _, gi := range p.Git {
err = runGitInit(ctx, gi)
if err != nil {
return src, err
}
}
log.Debug("Initialized workspace with prebuilt snapshot")
return
}
func clearWorkspace(location string) error {
files, err := filepath.Glob(filepath.Join(location, "*"))
if err != nil {
return err
}
for _, file := range files {
err = os.RemoveAll(file)
if err != nil {
return xerrors.Errorf("prebuild initializer: %w", err)
}
}
return nil
}
func runGitInit(ctx context.Context, gInit *GitInitializer) (err error) {
span, ctx := opentracing.StartSpanFromContext(ctx, "runGitInit")
span.LogFields(
tracelog.String("IsWorkingCopy", fmt.Sprintf("%v", git.IsWorkingCopy(gInit.Location))),
tracelog.String("location", fmt.Sprintf("%v", gInit.Location)),
)
defer tracing.FinishSpan(span, &err)
if git.IsWorkingCopy(gInit.Location) {
out, err := gInit.GitWithOutput(ctx, nil, "stash", "push", "--no-include-untracked")
if err != nil {
var giterr git.OpFailedError
if errors.As(err, &giterr) && strings.Contains(giterr.Output, "You do not have the initial commit yet") {
// git stash push returns a non-zero exit code if the repository does not have a single commit.
// In this case that's not an error though, hence we don't want to fail here.
} else {
// git returned a non-zero exit code because of some reason we did not anticipate or an actual failure.
log.WithError(err).WithField("output", string(out)).Error("unexpected git stash error")
return xerrors.Errorf("prebuild initializer: %w", err)
}
}
didStash := !strings.Contains(string(out), "No local changes to save")
err = gInit.Fetch(ctx)
err = checkGitStatus(err)
if err != nil {
return xerrors.Errorf("prebuild initializer: %w", err)
}
err = gInit.realizeCloneTarget(ctx)
if err != nil {
return xerrors.Errorf("prebuild initializer: %w", err)
}
// If any of these cleanup operations fail that's no reason to fail ws initialization.
// It just results in a slightly degraded state.
if didStash {
err = gInit.Git(ctx, "stash", "pop")
if err != nil {
// If restoring the stashed changes produces merge conflicts on the new Git ref, simply
// throw them away (they'll remain in the stash, but are likely outdated anyway).
_ = gInit.Git(ctx, "reset", "--hard")
}
}
log.Debug("prebuild initializer Git operations complete")
}
return nil
}