diff --git a/components/gitpod-cli/cmd/rebuild.go b/components/gitpod-cli/cmd/rebuild.go index ee575297607453..c4a059f7b572dd 100644 --- a/components/gitpod-cli/cmd/rebuild.go +++ b/components/gitpod-cli/cmd/rebuild.go @@ -238,6 +238,9 @@ func runRebuild(ctx context.Context, supervisorClient *supervisor.SupervisorClie for _, env := range debugEnvs.Envs { envs += env + "\n" } + for _, env := range rebuildOpts.GitpodEnvs { + envs += env + "\n" + } for _, env := range workspaceEnvs { envs += fmt.Sprintf("%s=%s\n", env.Name, env.Value) } @@ -432,6 +435,9 @@ var rebuildOpts struct { LogLevel string From string Prebuild bool + + // internal + GitpodEnvs []string } var rebuildCmd = &cobra.Command{ @@ -467,4 +473,8 @@ func init() { rebuildCmd.PersistentFlags().StringVarP(&rebuildOpts.LogLevel, "log", "", "error", "Log level to use. Allowed values are 'error', 'warn', 'info', 'debug', 'trace'.") rebuildCmd.PersistentFlags().StringVarP(&rebuildOpts.From, "from", "", "", "Starts from 'prebuild' or 'snapshot'.") rebuildCmd.PersistentFlags().BoolVarP(&rebuildOpts.Prebuild, "prebuild", "", false, "starts as a prebuild workspace (--from is ignored).") + + // internal + rebuildCmd.PersistentFlags().StringArrayVarP(&rebuildOpts.GitpodEnvs, "gitpod-env", "", nil, "") + rebuildCmd.PersistentFlags().MarkHidden("gitpod-env") } diff --git a/components/gitpod-cli/rebuild.sh b/components/gitpod-cli/rebuild.sh new file mode 100755 index 00000000000000..56d6d0dc99d964 --- /dev/null +++ b/components/gitpod-cli/rebuild.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Copyright (c) 2023 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. + +set -Eeuo pipefail + +DIR="$(dirname "$(realpath "$0")")" +COMPONENT="$(basename "$DIR")" +cd "$DIR" + +# build +go build . +echo "$COMPONENT built" + +sudo rm -rf "/.supervisor/$COMPONENT" && true +sudo mv ./"$COMPONENT" /.supervisor +echo "$COMPONENT in /.supervisor replaced" diff --git a/components/supervisor/.vscode/launch.json b/components/supervisor/.vscode/launch.json new file mode 100644 index 00000000000000..b3662ad4dbbd31 --- /dev/null +++ b/components/supervisor/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "asRoot": true, + "console": "integratedTerminal", + "processId": "supervisor" + } + ] +} diff --git a/components/supervisor/pkg/config/gitpod-config.go b/components/supervisor/pkg/config/gitpod-config.go index 6c73f5cc64e39f..447eefd2288961 100644 --- a/components/supervisor/pkg/config/gitpod-config.go +++ b/components/supervisor/pkg/config/gitpod-config.go @@ -7,13 +7,14 @@ package config import ( "context" "os" + "path/filepath" "sync" "time" "github.com/fsnotify/fsnotify" - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" + "github.com/gitpod-io/gitpod/common-go/log" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" ) @@ -23,19 +24,111 @@ type ConfigInterface interface { Watch(ctx context.Context) // Observe provides channels triggered whenever the config is changed Observe(ctx context.Context) <-chan *gitpod.GitpodConfig + // Observe provides channels triggered whenever the image file is changed + ObserveImageFile(ctx context.Context) <-chan *struct{} } // ConfigService provides access to the gitpod config file. type ConfigService struct { - location string locationReady <-chan struct{} - cond *sync.Cond - config *gitpod.GitpodConfig + configLocation string + configWatcher *fileWatcher[gitpod.GitpodConfig] - pollTimer *time.Timer + imageWatcher *fileWatcher[struct{}] +} - log *logrus.Entry +// NewConfigService creates a new instance of ConfigService. +func NewConfigService(configLocation string, locationReady <-chan struct{}) *ConfigService { + return &ConfigService{ + locationReady: locationReady, + configLocation: configLocation, + configWatcher: newFileWatcher(func(data []byte) (*gitpod.GitpodConfig, error) { + var config *gitpod.GitpodConfig + err := yaml.Unmarshal(data, &config) + return config, err + }), + imageWatcher: newFileWatcher(func(data []byte) (*struct{}, error) { + return &struct{}{}, nil + }), + } +} + +// Observe provides channels triggered whenever the config is changed. +func (service *ConfigService) Observe(ctx context.Context) <-chan *gitpod.GitpodConfig { + return service.configWatcher.observe(ctx) +} + +// Observe provides channels triggered whenever the image file is changed +func (service *ConfigService) ObserveImageFile(ctx context.Context) <-chan *struct{} { + return service.imageWatcher.observe(ctx) +} + +// Watch starts the config watching. +func (service *ConfigService) Watch(ctx context.Context) { + select { + case <-service.locationReady: + case <-ctx.Done(): + return + } + go service.watchImageFile(ctx) + service.configWatcher.watch(ctx, service.configLocation) +} + +func (service *ConfigService) watchImageFile(ctx context.Context) { + var ( + imageLocation string + cancelWatch func() + ) + defer func() { + if cancelWatch != nil { + cancelWatch() + } + }() + cfgs := service.configWatcher.observe(ctx) + for { + select { + case cfg, ok := <-cfgs: + if !ok { + return + } + var currentImageLocation string + if cfg != nil { + switch img := cfg.Image.(type) { + case map[string]interface{}: + if file, ok := img["file"].(string); ok { + currentImageLocation = filepath.Join(filepath.Dir(service.configLocation), file) + } + } + } + if imageLocation == currentImageLocation { + continue + } + if cancelWatch != nil { + cancelWatch() + cancelWatch = nil + service.imageWatcher.reset() + } + imageLocation = currentImageLocation + if imageLocation == "" { + continue + } + watchCtx, cancel := context.WithCancel(ctx) + cancelWatch = cancel + go service.imageWatcher.watch(watchCtx, imageLocation) + case <-ctx.Done(): + return + } + } +} + +type fileWatcher[T any] struct { + unmarshal func(data []byte) (*T, error) + + cond *sync.Cond + data *T + + pollTimer *time.Timer ready chan struct{} readyOnce sync.Once @@ -43,30 +136,26 @@ type ConfigService struct { debounceDuration time.Duration } -// NewConfigService creates a new instance of ConfigService. -func NewConfigService(configLocation string, locationReady <-chan struct{}, log *logrus.Entry) *ConfigService { - return &ConfigService{ - location: configLocation, - locationReady: locationReady, +func newFileWatcher[T any](unmarshal func(data []byte) (*T, error)) *fileWatcher[T] { + return &fileWatcher[T]{ + unmarshal: unmarshal, cond: sync.NewCond(&sync.Mutex{}), - log: log.WithField("location", configLocation), ready: make(chan struct{}), debounceDuration: 100 * time.Millisecond, } } -// Observe provides channels triggered whenever the config is changed. -func (service *ConfigService) Observe(ctx context.Context) <-chan *gitpod.GitpodConfig { - configs := make(chan *gitpod.GitpodConfig) +func (service *fileWatcher[T]) observe(ctx context.Context) <-chan *T { + results := make(chan *T) go func() { - defer close(configs) + defer close(results) <-service.ready service.cond.L.Lock() defer service.cond.L.Unlock() for { - configs <- service.config + results <- service.data service.cond.Wait() if ctx.Err() != nil { @@ -74,58 +163,62 @@ func (service *ConfigService) Observe(ctx context.Context) <-chan *gitpod.Gitpod } } }() - return configs + return results } -// Watch starts the config watching. -func (service *ConfigService) Watch(ctx context.Context) { - service.log.Info("gitpod config watcher: starting...") +func (service *fileWatcher[T]) markReady() { + service.readyOnce.Do(func() { + close(service.ready) + }) +} - select { - case <-service.locationReady: - case <-ctx.Done(): - return - } +func (service *fileWatcher[T]) reset() { + service.cond.L.Lock() + defer service.cond.L.Unlock() - _, err := os.Stat(service.location) - if os.IsNotExist(err) { - service.poll(ctx) + if service.data != nil { + service.data = nil + service.cond.Broadcast() } - service.watch(ctx) } -func (service *ConfigService) markReady() { - service.readyOnce.Do(func() { - close(service.ready) - }) +func (service *fileWatcher[T]) watch(ctx context.Context, location string) { + log.WithField("location", location).Info("file watcher: starting...") + + _, err := os.Stat(location) + if os.IsNotExist(err) { + service.poll(ctx, location) + } else { + service.doWatch(ctx, location) + } } -func (service *ConfigService) watch(ctx context.Context) { +func (service *fileWatcher[T]) doWatch(ctx context.Context, location string) { watcher, err := fsnotify.NewWatcher() defer func() { if err != nil { - service.log.WithError(err).Error("gitpod config watcher: failed to start") + log.WithField("location", location).WithError(err).Error("file watcher: failed to start") return } - service.log.Info("gitpod config watcher: started") + log.WithField("location", location).Info("file watcher: started") }() if err != nil { return } - err = watcher.Add(service.location) + err = watcher.Add(location) if err != nil { watcher.Close() return } go func() { - defer service.log.Info("gitpod config watcher: stopped") + defer log.WithField("location", location).Info("file watcher: stopped") defer watcher.Close() polling := make(chan struct{}, 1) - service.scheduleUpdateConfig(ctx, polling) + service.scheduleUpdate(ctx, polling, location) for { select { case <-polling: @@ -133,32 +226,32 @@ func (service *ConfigService) watch(ctx context.Context) { case <-ctx.Done(): return case err := <-watcher.Errors: - service.log.WithError(err).Error("gitpod config watcher: failed to watch") + log.WithField("location", location).WithError(err).Error("file watcher: failed to watch") case <-watcher.Events: - service.scheduleUpdateConfig(ctx, polling) + service.scheduleUpdate(ctx, polling, location) } } }() } -func (service *ConfigService) scheduleUpdateConfig(ctx context.Context, polling chan<- struct{}) { +func (service *fileWatcher[T]) scheduleUpdate(ctx context.Context, polling chan<- struct{}, location string) { service.cond.L.Lock() defer service.cond.L.Unlock() if service.pollTimer != nil { service.pollTimer.Stop() } service.pollTimer = time.AfterFunc(service.debounceDuration, func() { - err := service.updateConfig() + err := service.update(location) if os.IsNotExist(err) { polling <- struct{}{} - go service.poll(ctx) + go service.poll(ctx, location) } else if err != nil { - service.log.WithError(err).Error("gitpod config watcher: failed to parse") + log.WithField("location", location).WithError(err).Error("file watcher: failed to parse") } }) } -func (service *ConfigService) poll(ctx context.Context) { +func (service *fileWatcher[T]) poll(ctx context.Context, location string) { service.markReady() timer := time.NewTicker(2 * time.Second) @@ -171,35 +264,33 @@ func (service *ConfigService) poll(ctx context.Context) { case <-timer.C: } - if _, err := os.Stat(service.location); !os.IsNotExist(err) { - service.watch(ctx) + if _, err := os.Stat(location); !os.IsNotExist(err) { + service.doWatch(ctx, location) return } } } -func (service *ConfigService) updateConfig() error { +func (service *fileWatcher[T]) update(location string) error { service.cond.L.Lock() defer service.cond.L.Unlock() - config, err := service.parse() + data, err := service.parse(location) if err == nil || os.IsNotExist(err) { - service.config = config + service.data = data service.markReady() service.cond.Broadcast() - service.log.WithField("config", service.config).Debug("gitpod config watcher: updated") + log.WithField("location", location).WithField("data", service.data).Debug("file watcher: updated") } return err } -func (service *ConfigService) parse() (*gitpod.GitpodConfig, error) { - data, err := os.ReadFile(service.location) +func (service *fileWatcher[T]) parse(location string) (*T, error) { + data, err := os.ReadFile(location) if err != nil { return nil, err } - var config *gitpod.GitpodConfig - err = yaml.Unmarshal(data, &config) - return config, err + return service.unmarshal(data) } diff --git a/components/supervisor/pkg/config/gitpod-config_analytics_test.go b/components/supervisor/pkg/config/gitpod-config_analytics_test.go index bf7f5b3934dad8..c7f31679b85720 100644 --- a/components/supervisor/pkg/config/gitpod-config_analytics_test.go +++ b/components/supervisor/pkg/config/gitpod-config_analytics_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" + "github.com/gitpod-io/gitpod/common-go/log" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" ) @@ -65,7 +66,7 @@ func TestAnalyzeGitpodConfig(t *testing.T) { for _, test := range tests { t.Run(test.Desc, func(t *testing.T) { var fields []string - analyzer := NewConfigAnalyzer(log, 100*time.Millisecond, func(field string) { + analyzer := NewConfigAnalyzer(log.Log, 100*time.Millisecond, func(field string) { fields = append(fields, field) }, test.Prev) <-analyzer.Analyse(test.Current) diff --git a/components/supervisor/pkg/config/gitpod-config_test.go b/components/supervisor/pkg/config/gitpod-config_test.go index 60b8b9b9e35a40..06ae768a78f7f2 100644 --- a/components/supervisor/pkg/config/gitpod-config_test.go +++ b/components/supervisor/pkg/config/gitpod-config_test.go @@ -12,14 +12,11 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" ) -var log = logrus.NewEntry(logrus.StandardLogger()) - func TestGitpodConfig(t *testing.T) { tests := []struct { Desc string @@ -89,7 +86,7 @@ vscode: defer os.RemoveAll(tempDir) locationReady := make(chan struct{}) - configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady, log) + configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady) ctx, cancel := context.WithCancel(context.Background()) defer cancel() close(locationReady) @@ -118,7 +115,7 @@ vscode: t.Fatal(err) } - err = os.WriteFile(configService.location, []byte(test.Content), 0o600) + err = os.WriteFile(configService.configLocation, []byte(test.Content), 0o600) if err != nil { t.Fatal(err) } @@ -139,7 +136,7 @@ vscode: t.Fatal(err) } - err = os.Remove(configService.location) + err = os.Remove(configService.configLocation) if err != nil { t.Fatal(err) } @@ -156,7 +153,7 @@ func TestInvalidGitpodConfig(t *testing.T) { defer os.RemoveAll(tempDir) locationReady := make(chan struct{}) - configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady, log) + configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady) ctx, cancel := context.WithCancel(context.Background()) defer cancel() close(locationReady) @@ -170,7 +167,7 @@ func TestInvalidGitpodConfig(t *testing.T) { t.Errorf("unexpected output (-want +got):\n%s", diff) } - err = os.WriteFile(configService.location, []byte(` + err = os.WriteFile(configService.configLocation, []byte(` ports: - port: 8080 @@ -194,7 +191,7 @@ vscode: t.Errorf("unexpected output (-want +got):\n%s", diff) } - err = os.WriteFile(configService.location, []byte(` + err = os.WriteFile(configService.configLocation, []byte(` ports: - port: visibility: private @@ -212,9 +209,9 @@ vscode: t.Fatal(err) } - time.Sleep(configService.debounceDuration * 10) + time.Sleep(configService.configWatcher.debounceDuration * 10) - err = os.WriteFile(configService.location, []byte(` + err = os.WriteFile(configService.configLocation, []byte(` ports: - port: 8081 `), 0o600) @@ -229,3 +226,84 @@ ports: t.Errorf("unexpected output (-want +got):\n%s", diff) } } + +func TestWatchImageFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test-gitpod-config-*") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + locationReady := make(chan struct{}) + configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + close(locationReady) + + go configService.Watch(ctx) + + listener := configService.ObserveImageFile(ctx) + + err = os.WriteFile(configService.configLocation, []byte(` +image: + file: Dockerfile +`), 0o600) + if err != nil { + t.Fatal(err) + } + + changes := <-listener + if diff := cmp.Diff((*struct{})(nil), changes); diff != "" { + t.Errorf("unexpected output (-want +got):\n%s", diff) + } + + imageLocation := tempDir + "/Dockerfile" + err = os.WriteFile(imageLocation, []byte(` +FROM ubuntu +`), 0o600) + if err != nil { + t.Fatal(err) + } + + changes = <-listener + if diff := cmp.Diff(&struct{}{}, changes); diff != "" { + t.Errorf("unexpected output (-want +got):\n%s", diff) + } + + err = os.WriteFile(configService.configLocation, []byte(` +image: ubuntu +`), 0o600) + if err != nil { + t.Fatal(err) + } + + changes = <-listener + if diff := cmp.Diff((*struct{})(nil), changes); diff != "" { + t.Errorf("unexpected output (-want +got):\n%s", diff) + } + + err = os.WriteFile(configService.configLocation, []byte(` +image: + file: Dockerfile +`), 0o600) + if err != nil { + t.Fatal(err) + } + + changes = <-listener + if diff := cmp.Diff(&struct{}{}, changes); diff != "" { + t.Errorf("unexpected output (-want +got):\n%s", diff) + } + + err = os.WriteFile(imageLocation, []byte(` +FROM node +`), 0o600) + if err != nil { + t.Fatal(err) + } + + changes = <-listener + if diff := cmp.Diff(&struct{}{}, changes); diff != "" { + t.Errorf("unexpected output (-want +got):\n%s", diff) + } +} diff --git a/components/supervisor/pkg/ports/ports-config_test.go b/components/supervisor/pkg/ports/ports-config_test.go index 43fe8f34ec721c..d8437f74cbabac 100644 --- a/components/supervisor/pkg/ports/ports-config_test.go +++ b/components/supervisor/pkg/ports/ports-config_test.go @@ -83,8 +83,10 @@ func TestPortsConfig(t *testing.T) { t.Run(test.Desc, func(t *testing.T) { configService := &testGitpodConfigService{ configs: make(chan *gitpod.GitpodConfig), + changes: make(chan *struct{}), } defer close(configService.configs) + defer close(configService.changes) context, cancel := context.WithCancel(context.Background()) defer cancel() @@ -128,6 +130,7 @@ type PortConfigTestExpectations struct { type testGitpodConfigService struct { configs chan *gitpod.GitpodConfig + changes chan *struct{} } func (service *testGitpodConfigService) Watch(ctx context.Context) { @@ -136,3 +139,7 @@ func (service *testGitpodConfigService) Watch(ctx context.Context) { func (service *testGitpodConfigService) Observe(ctx context.Context) <-chan *gitpod.GitpodConfig { return service.configs } + +func (service *testGitpodConfigService) ObserveImageFile(ctx context.Context) <-chan *struct{} { + return service.changes +} diff --git a/components/supervisor/pkg/supervisor/supervisor.go b/components/supervisor/pkg/supervisor/supervisor.go index f15ea619876784..a2287e29653317 100644 --- a/components/supervisor/pkg/supervisor/supervisor.go +++ b/components/supervisor/pkg/supervisor/supervisor.go @@ -273,7 +273,7 @@ func Run(options ...RunOption) { } tokenService.provider[KindGit] = []tokenProvider{NewGitTokenProvider(gitpodService, cfg.WorkspaceConfig, notificationService)} - gitpodConfigService := config.NewConfigService(cfg.RepoRoot+"/.gitpod.yml", cstate.ContentReady(), log.Log) + gitpodConfigService := config.NewConfigService(cfg.RepoRoot+"/.gitpod.yml", cstate.ContentReady()) go gitpodConfigService.Watch(ctx) var exposedPorts ports.ExposedPortsInterface @@ -297,7 +297,7 @@ func Run(options ...RunOption) { topService.Observe(ctx) } - if !cfg.isHeadless() && !opts.RunGP && !cfg.isDebugWorkspace() { + if !cfg.isHeadless() && !opts.RunGP { go analyseConfigChanges(ctx, cfg, telemetry, gitpodConfigService) go analysePerfChanges(ctx, cfg, telemetry, topService) } @@ -1592,17 +1592,20 @@ func analysePerfChanges(ctx context.Context, wscfg *Config, w analytics.Writer, if !analyzer.analyze(used) { return } - log.WithField("buckets", analyzer.buckets).WithField("used", used).WithField("label", analyzer.label).Debug("gitpod perf analytics: changed") - w.Track(analytics.TrackMessage{ - Identity: analytics.Identity{UserID: wscfg.OwnerId}, - Event: "gitpod_" + analyzer.label + "_changed", - Properties: map[string]interface{}{ - "used": used, - "buckets": analyzer.buckets, - "instanceId": wscfg.WorkspaceInstanceID, - "workspaceId": wscfg.WorkspaceID, - }, - }) + if wscfg.isDebugWorkspace() { + log.WithField("buckets", analyzer.buckets).WithField("used", used).WithField("label", analyzer.label).Info("gitpod perf analytics: changed") + } else { + w.Track(analytics.TrackMessage{ + Identity: analytics.Identity{UserID: wscfg.OwnerId}, + Event: "gitpod_" + analyzer.label + "_changed", + Properties: map[string]interface{}{ + "used": used, + "buckets": analyzer.buckets, + "instanceId": wscfg.WorkspaceInstanceID, + "workspaceId": wscfg.WorkspaceID, + }, + }) + } } cpuAnalyzer := &PerfAnalyzer{label: "cpu", defs: []int{1, 2, 3, 4, 5, 6, 7, 8}} @@ -1623,10 +1626,64 @@ func analysePerfChanges(ctx context.Context, wscfg *Config, w analytics.Writer, } } +func analyzeImageFileChanges(ctx context.Context, wscfg *Config, w analytics.Writer, cfgobs config.ConfigInterface, debounceDuration time.Duration) { + var ( + timer *time.Timer + mu sync.Mutex + ) + analyze := func(change *struct{}) { + mu.Lock() + defer mu.Unlock() + if timer != nil && !timer.Stop() { + <-timer.C + } + timer = time.AfterFunc(debounceDuration, func() { + mu.Lock() + defer mu.Unlock() + timer = nil + msg := analytics.TrackMessage{ + Identity: analytics.Identity{UserID: wscfg.OwnerId}, + Event: "gitpod_image_file_changed", + Properties: map[string]interface{}{ + "instanceId": wscfg.WorkspaceInstanceID, + "workspaceId": wscfg.WorkspaceID, + "exists": change != nil, + }, + } + if !wscfg.isDebugWorkspace() { + w.Track(msg) + } else { + log.WithField("msg", msg).Info("gitpod config analytics: image file changed") + } + }) + } + changes := cfgobs.ObserveImageFile(ctx) + initial := true + for { + select { + case change, ok := <-changes: + if !ok { + return + } + if initial { + // only report changes, not initial state + initial = false + } else { + analyze(change) + } + case <-ctx.Done(): + return + } + } +} + func analyseConfigChanges(ctx context.Context, wscfg *Config, w analytics.Writer, cfgobs config.ConfigInterface) { var analyzer *config.ConfigAnalyzer log.Debug("gitpod config analytics: watching...") + debounceDuration := 5 * time.Second + go analyzeImageFileChanges(ctx, wscfg, w, cfgobs, debounceDuration) + cfgs := cfgobs.Observe(ctx) for { select { @@ -1637,8 +1694,8 @@ func analyseConfigChanges(ctx context.Context, wscfg *Config, w analytics.Writer if analyzer != nil { analyzer.Analyse(cfg) } else { - analyzer = config.NewConfigAnalyzer(log.Log, 5*time.Second, func(field string) { - w.Track(analytics.TrackMessage{ + analyzer = config.NewConfigAnalyzer(log.Log, debounceDuration, func(field string) { + msg := analytics.TrackMessage{ Identity: analytics.Identity{UserID: wscfg.OwnerId}, Event: "gitpod_config_changed", Properties: map[string]interface{}{ @@ -1646,7 +1703,12 @@ func analyseConfigChanges(ctx context.Context, wscfg *Config, w analytics.Writer "instanceId": wscfg.WorkspaceInstanceID, "workspaceId": wscfg.WorkspaceID, }, - }) + } + if !wscfg.isDebugWorkspace() { + w.Track(msg) + } else { + log.WithField("msg", msg).Info("gitpod config analytics: config changed") + } }, cfg) } diff --git a/components/supervisor/validate.sh b/components/supervisor/validate.sh new file mode 100755 index 00000000000000..fe3652d121e61c --- /dev/null +++ b/components/supervisor/validate.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Copyright (c) 2023 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. + +set -Eeuo pipefail + +ROOT_DIR="$(realpath "$(dirname "$0")/../..")" +bash "$ROOT_DIR/components/gitpod-cli/rebuild.sh" + +DIR="$(dirname "$(realpath "$0")")" +COMPONENT="$(basename "$DIR")" +cd "$DIR" + +# build +go build -gcflags=all="-N -l" . +echo "$COMPONENT built" + +sudo rm -rf "/.supervisor/$COMPONENT" && true +sudo mv ./"$COMPONENT" /.supervisor +echo "$COMPONENT in /.supervisor replaced" + +gp rebuild --workspace-folder="$ROOT_DIR/dev/ide/example/workspace" --gitpod-env "GITPOD_ANALYTICS_SEGMENT_KEY=YErmvd89wPsrCuGcVnF2XAl846W9WIGl" "$@" diff --git a/dev/ide/example/workspace/.gitpod.yml b/dev/ide/example/workspace/.gitpod.yml new file mode 100644 index 00000000000000..5ce220c1c14804 --- /dev/null +++ b/dev/ide/example/workspace/.gitpod.yml @@ -0,0 +1,2 @@ +image: + file: Dockerfile diff --git a/dev/ide/example/workspace/Dockerfile b/dev/ide/example/workspace/Dockerfile new file mode 100644 index 00000000000000..c79638293fbd98 --- /dev/null +++ b/dev/ide/example/workspace/Dockerfile @@ -0,0 +1,3 @@ +FROM ubuntu + +RUN apt-get update -y && apt-get install -y git sudo curl