Path: blob/main/components/ide/code/codehelper/main.go
2500 views
// Copyright (c) 2022 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 main56import (7"context"8"errors"9"fmt"10"net/url"11"os"12"path/filepath"13"regexp"14"strings"15"syscall"16"time"1718"google.golang.org/grpc"19"google.golang.org/grpc/credentials/insecure"20yaml "gopkg.in/yaml.v2"2122"github.com/gitpod-io/gitpod/common-go/log"23"github.com/gitpod-io/gitpod/common-go/util"24gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"25supervisor "github.com/gitpod-io/gitpod/supervisor/api"26"github.com/sirupsen/logrus"27)2829var (30// ServiceName is the name we use for tracing/logging.31ServiceName = "codehelper"32// Version of this service - set during build.33Version = ""34)3536const (37Code = "/ide/bin/gitpod-code"38ProductJsonLocation = "/ide/product.json"39WebWorkbenchMainLocation = "/ide/out/vs/workbench/workbench.web.main.js"40ServerMainLocation = "/ide/out/vs/server/node/server.main.js"41)4243func main() {44enableDebug := os.Getenv("SUPERVISOR_DEBUG_ENABLE") == "true"4546log.Init(ServiceName, Version, true, enableDebug)47log.Info("codehelper started")48startTime := time.Now()4950phaseDone := phaseLogging("ResolveWsInfo")51cstate, wsInfo, err := resolveWorkspaceInfo(context.Background())52if err != nil || wsInfo == nil {53log.WithError(err).WithField("wsInfo", wsInfo).Error("resolve workspace info failed")54return55}56phaseDone()5758url, err := url.Parse(wsInfo.GitpodHost)59if err != nil {60log.WithError(err).Errorf("failed to parse GitpodHost %s", wsInfo.GitpodHost)61return62}63domain := url.Hostname()6465// code server args install extension with id66args := []string{}6768if enableDebug {69args = append(args, "--inspect", "--log=trace")70} else {71switch log.Log.Logger.GetLevel() {72case logrus.PanicLevel:73args = append(args, "--log=critical")74case logrus.FatalLevel:75args = append(args, "--log=critical")76case logrus.ErrorLevel:77args = append(args, "--log=error")78case logrus.WarnLevel:79args = append(args, "--log=warn")80case logrus.InfoLevel:81args = append(args, "--log=info")82case logrus.DebugLevel:83args = append(args, "--log=debug")84case logrus.TraceLevel:85args = append(args, "--log=trace")86}87}8889args = append(args, "--install-builtin-extension", "gitpod.gitpod-theme")9091wsContextUrl := wsInfo.GetWorkspaceContextUrl()92if ctxUrl, err := url.Parse(wsContextUrl); err == nil {93if ctxUrl.Host == "github.com" {94log.Info("ws context url is from github.com, install builtin extension github.vscode-pull-request-github")95args = append(args, "--install-builtin-extension", "github.vscode-pull-request-github")96}97} else {98log.WithError(err).WithField("wsContextUrl", wsContextUrl).Error("parse ws context url failed")99}100101phaseDone = phaseLogging("GetExtensions")102uniqMap := map[string]struct{}{}103extensions, err := getExtensions(wsInfo.GetCheckoutLocation())104if err != nil {105log.WithError(err).Error("get extensions failed")106}107log.WithField("ext", extensions).Info("get extensions")108for _, ext := range extensions {109if _, ok := uniqMap[ext.Location]; ok {110continue111}112uniqMap[ext.Location] = struct{}{}113// don't install url extension for backup, see https://github.com/microsoft/vscode/issues/143617#issuecomment-1047881213114if cstate.Source != supervisor.ContentSource_from_backup || !ext.IsUrl {115args = append(args, "--install-extension", ext.Location)116}117}118phaseDone()119120// install extensions and run code server with exec121args = append(args, os.Args[1:]...)122args = append(args, "--do-not-sync")123args = append(args, "--start-server")124cmdEnv := append(os.Environ(), fmt.Sprintf("GITPOD_CODE_HOST=%s", domain))125log.WithField("cost", time.Now().Local().Sub(startTime).Milliseconds()).Info("starting server")126if err := syscall.Exec(Code, append([]string{"gitpod-code"}, args...), cmdEnv); err != nil {127log.WithError(err).Error("install ext and start code server failed")128}129}130131func resolveWorkspaceInfo(ctx context.Context) (*supervisor.ContentStatusResponse, *supervisor.WorkspaceInfoResponse, error) {132resolve := func(ctx context.Context) (cstate *supervisor.ContentStatusResponse, wsInfo *supervisor.WorkspaceInfoResponse, err error) {133supervisorConn, err := grpc.Dial(util.GetSupervisorAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))134if err != nil {135err = errors.New("dial supervisor failed: " + err.Error())136return137}138defer supervisorConn.Close()139if cstate, err = supervisor.NewStatusServiceClient(supervisorConn).ContentStatus(ctx, &supervisor.ContentStatusRequest{}); err != nil {140err = errors.New("get content state failed: " + err.Error())141return142}143if wsInfo, err = supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{}); err != nil {144err = errors.New("get workspace info failed: " + err.Error())145return146}147return148}149// try resolve workspace info 10 times150for attempt := 0; attempt < 10; attempt++ {151if cstate, wsInfo, err := resolve(ctx); err != nil {152log.WithError(err).Error("resolve workspace info failed")153time.Sleep(1 * time.Second)154} else {155return cstate, wsInfo, err156}157}158return nil, nil, errors.New("failed with attempt 10 times")159}160161type Extension struct {162IsUrl bool `json:"is_url"`163Location string `json:"location"`164}165166func getExtensions(repoRoot string) (extensions []Extension, err error) {167if repoRoot == "" {168err = errors.New("repoRoot is empty")169return170}171data, err := os.ReadFile(filepath.Join(repoRoot, ".gitpod.yml"))172if err != nil {173// .gitpod.yml not exist is ok174if errors.Is(err, os.ErrNotExist) {175err = nil176return177}178err = errors.New("read .gitpod.yml file failed: " + err.Error())179return180}181var config *gitpod.GitpodConfig182if err = yaml.Unmarshal(data, &config); err != nil {183err = errors.New("unmarshal .gitpod.yml file failed" + err.Error())184return185}186if config == nil || config.Vscode == nil {187return188}189for _, ext := range config.Vscode.Extensions {190lowerCaseExtension := strings.ToLower(ext)191if isUrl(lowerCaseExtension) {192extensions = append(extensions, Extension{193IsUrl: true,194Location: ext,195})196} else {197extensions = append(extensions, Extension{198IsUrl: false,199Location: lowerCaseExtension,200})201}202}203return204}205206func isUrl(lowerCaseIdOrUrl string) bool {207isUrl, _ := regexp.MatchString(`http[s]?://`, lowerCaseIdOrUrl)208return isUrl209}210211func phaseLogging(phase string) context.CancelFunc {212ctx, cancel := context.WithCancel(context.Background())213start := time.Now()214log.WithField("phase", phase).Info("phase start")215go func() {216<-ctx.Done()217duration := time.Now().Local().Sub(start).Seconds()218log.WithField("phase", phase).WithField("duration", duration).Info("phase end")219}()220return cancel221}222223224