Path: blob/main/components/content-service/pkg/initializer/prebuild.go
2499 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package initializer56import (7"context"8"errors"9"fmt"10"os"11"path/filepath"12"strings"13"time"1415"github.com/opentracing/opentracing-go"16tracelog "github.com/opentracing/opentracing-go/log"17"golang.org/x/xerrors"1819"github.com/gitpod-io/gitpod/common-go/log"20"github.com/gitpod-io/gitpod/common-go/tracing"21csapi "github.com/gitpod-io/gitpod/content-service/api"22"github.com/gitpod-io/gitpod/content-service/pkg/archive"23"github.com/gitpod-io/gitpod/content-service/pkg/git"24)2526// PrebuildInitializer first tries to restore the snapshot/prebuild and if that succeeds performs Git operations.27// If restoring the prebuild does not succeed we fall back to Git entriely.28type PrebuildInitializer struct {29Git []*GitInitializer30Prebuild *SnapshotInitializer31}3233// Run runs the prebuild initializer34func (p *PrebuildInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, stats csapi.InitializerMetrics, err error) {35//nolint:ineffassign36span, ctx := opentracing.StartSpanFromContext(ctx, "PrebuildInitializer")37defer tracing.FinishSpan(span, &err)38startTime := time.Now()39initialSize, fsErr := getFsUsage()40if fsErr != nil {41log.WithError(fsErr).Error("could not get disk usage")42}4344var spandata []tracelog.Field45if p.Prebuild == nil {46spandata = append(spandata, tracelog.Bool("hasSnapshot", false))47} else {48spandata = append(spandata,49tracelog.Bool("hasSnapshot", true),50tracelog.String("snapshot", p.Prebuild.Snapshot),51)52}53if len(p.Git) == 0 {54spandata = append(spandata, tracelog.Bool("hasGit", false))55} else {56spandata = append(spandata,57tracelog.Bool("hasGit", true),58)59}60span.LogFields(spandata...)6162if p.Prebuild != nil {63var (64snapshot = p.Prebuild.Snapshot65location = p.Prebuild.Location66log = log.WithField("location", p.Prebuild.Location)67)68_, s, err := p.Prebuild.Run(ctx, mappings)69if err == nil {70stats = append(stats, s...)71}7273if err != nil {74log.WithError(err).Warnf("prebuilt init was unable to restore snapshot %s. Resorting the regular Git init", snapshot)7576if err := clearWorkspace(location); err != nil {77return csapi.WorkspaceInitFromOther, nil, xerrors.Errorf("prebuild initializer: %w", err)78}7980for _, gi := range p.Git {81_, s, err := gi.Run(ctx, mappings)82if err != nil {83return csapi.WorkspaceInitFromOther, nil, xerrors.Errorf("prebuild initializer: Git fallback: %w", err)84}85stats = append(stats, s...)86}87}88}8990// at this point we're actually a prebuild initialiser because we've been able to restore91// the prebuild.9293src = csapi.WorkspaceInitFromPrebuild9495// make sure we're on the correct branch96for _, gi := range p.Git {9798commitChanged, err := runGitInit(ctx, gi)99if err != nil {100return src, nil, err101}102if commitChanged {103// head commit has changed, so it's an outdated prebuild, which we treat as other104src = csapi.WorkspaceInitFromOther105}106}107log.Debug("Initialized workspace with prebuilt snapshot")108109if fsErr == nil {110currentSize, fsErr := getFsUsage()111if fsErr != nil {112log.WithError(fsErr).Error("could not get disk usage")113}114115stats = append(stats, csapi.InitializerMetric{116Type: "prebuild",117Duration: time.Since(startTime),118Size: currentSize - initialSize,119})120}121122return123}124125func clearWorkspace(location string) error {126files, err := filepath.Glob(filepath.Join(location, "*"))127if err != nil {128return err129}130for _, file := range files {131err = os.RemoveAll(file)132if err != nil {133return xerrors.Errorf("prebuild initializer: %w", err)134}135}136return nil137}138139func runGitInit(ctx context.Context, gInit *GitInitializer) (commitChanged bool, err error) {140span, ctx := opentracing.StartSpanFromContext(ctx, "runGitInit")141span.LogFields(142tracelog.String("IsWorkingCopy", fmt.Sprintf("%v", git.IsWorkingCopy(gInit.Location))),143tracelog.String("location", fmt.Sprintf("%v", gInit.Location)),144)145defer tracing.FinishSpan(span, &err)146if git.IsWorkingCopy(gInit.Location) {147out, err := gInit.GitWithOutput(ctx, nil, "stash", "push", "--no-include-untracked")148if err != nil {149var giterr git.OpFailedError150if errors.As(err, &giterr) && strings.Contains(giterr.Output, "You do not have the initial commit yet") {151// git stash push returns a non-zero exit code if the repository does not have a single commit.152// In this case that's not an error though, hence we don't want to fail here.153} else {154// git returned a non-zero exit code because of some reason we did not anticipate or an actual failure.155log.WithError(err).WithField("output", string(out)).Error("unexpected git stash error")156return commitChanged, xerrors.Errorf("prebuild initializer: %w", err)157}158}159didStash := !strings.Contains(string(out), "No local changes to save")160161statusBefore, err := gInit.Status(ctx)162if err != nil {163log.WithError(err).Warn("couldn't run git status - continuing")164}165err = checkGitStatus(gInit.realizeCloneTarget(ctx))166if err != nil {167return commitChanged, xerrors.Errorf("prebuild initializer: %w", err)168}169statusAfter, err := gInit.Status(ctx)170if err != nil {171log.WithError(err).Warn("couldn't run git status - continuing")172}173if statusBefore != nil && statusAfter != nil {174commitChanged = statusBefore.LatestCommit != statusAfter.LatestCommit175}176177err = gInit.UpdateSubmodules(ctx)178if err != nil {179log.WithError(err).Warn("error while updating submodules from prebuild initializer - continuing")180}181182// If any of these cleanup operations fail that's no reason to fail ws initialization.183// It just results in a slightly degraded state.184if didStash {185err = gInit.Git(ctx, "stash", "pop")186if err != nil {187// If restoring the stashed changes produces merge conflicts on the new Git ref, simply188// throw them away (they'll remain in the stash, but are likely outdated anyway).189_ = gInit.Git(ctx, "reset", "--hard")190}191}192193log.Debug("prebuild initializer Git operations complete")194}195196return commitChanged, nil197}198199200