From ff8f91ed605732ebc95f7696335dc30bdd17c406 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Wed, 24 Mar 2021 10:27:25 +0000 Subject: [PATCH 1/2] fix #3162: decouple gp env from theia --- components/gitpod-cli/cmd/env.go | 276 ++++++++++--- components/gitpod-cli/go.mod | 1 + components/gitpod-protocol/src/protocol.ts | 46 ++- components/image-builder-api/go/go.mod | 8 +- .../server/src/auth/resource-access.spec.ts | 367 +++++++++++------- components/server/src/auth/resource-access.ts | 138 +++++-- .../src/workspace/gitpod-server-impl.ts | 53 ++- .../server/src/workspace/workspace-starter.ts | 19 +- components/supervisor-api/go/control.pb.go | 7 +- components/supervisor-api/go/info.pb.go | 159 ++++++-- .../supervisor-api/go/notification.pb.go | 7 +- components/supervisor-api/go/status.pb.go | 7 +- components/supervisor-api/go/terminal.pb.go | 7 +- components/supervisor-api/go/token.pb.go | 7 +- components/supervisor-api/info.proto | 9 + .../supervisor/pkg/supervisor/config.go | 16 + .../supervisor/pkg/supervisor/services.go | 10 + components/ws-manager/pkg/manager/create.go | 2 +- 18 files changed, 807 insertions(+), 332 deletions(-) diff --git a/components/gitpod-cli/cmd/env.go b/components/gitpod-cli/cmd/env.go index 30fd51a844de0e..50fab6d6c779cc 100644 --- a/components/gitpod-cli/cmd/env.go +++ b/components/gitpod-cli/cmd/env.go @@ -5,13 +5,20 @@ package cmd import ( + "context" "fmt" "os" "strings" + "sync" + "time" "github.com/spf13/cobra" + "golang.org/x/xerrors" + "google.golang.org/grpc" "github.com/gitpod-io/gitpod/gitpod-cli/pkg/theialib" + serverapi "github.com/gitpod-io/gitpod/gitpod-protocol" + supervisor "github.com/gitpod-io/gitpod/supervisor/api" ) var exportEnvs = false @@ -42,82 +49,243 @@ delete environment variables with a repository pattern of */foo, foo/* or */*. `, Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { - fail := func(msg string) { - fmt.Fprintln(os.Stderr, msg) - os.Exit(-1) + if len(args) > 0 { + if unsetEnvs { + deleteEnvs(args) + return + } + + setEnvs(args) + } else { + getEnvs() } + }, +} + +type connectToServerResult struct { + repositoryPattern string + client *serverapi.APIoverJSONRPC +} - service, err := theialib.NewServiceFromEnv() +func connectToServer(ctx context.Context) (*connectToServerResult, error) { + supervisorAddr := os.Getenv("SUPERVISOR_ADDR") + if supervisorAddr == "" { + supervisorAddr = "localhost:22999" + } + supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure()) + if err != nil { + return nil, xerrors.Errorf("failed connecting to supervisor: %w", err) + } + wsinfo, err := supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{}) + if err != nil { + return nil, xerrors.Errorf("failed getting workspace info from supervisor: %w", err) + } + if wsinfo.Repository == nil { + return nil, xerrors.New("workspace info is missing repository") + } + if wsinfo.Repository.Owner == "" { + return nil, xerrors.New("repository info is missing owner") + } + if wsinfo.Repository.Name == "" { + return nil, xerrors.New("repository info is missing name") + } + repositoryPattern := wsinfo.Repository.Owner + "/" + wsinfo.Repository.Name + clientToken, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{ + Host: wsinfo.GitpodApi.Host, + Kind: "gitpod", + Scope: []string{ + "function:getEnvVars", + "function:setEnvVar", + "function:deleteEnvVar", + "resource:envVar::" + repositoryPattern + "::create/get/update/delete", + }, + }) + if err != nil { + return nil, xerrors.Errorf("failed getting token from supervisor: %w", err) + } + client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{Token: clientToken.Token, Context: ctx}) + if err != nil { + return nil, xerrors.Errorf("failed connecting to server: %w", err) + } + return &connectToServerResult{repositoryPattern, client}, nil +} + +func getEnvs() { + if !isTheiaIDE() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + result, err := connectToServer(ctx) if err != nil { fail(err.Error()) } - setEnvs := func() { - vars := make([]theialib.EnvironmentVariable, len(args)) - for i, arg := range args { - kv := strings.Split(arg, "=") - if len(kv) != 2 { - fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg)) - } + vars, err := result.client.GetEnvVars(ctx) + if err != nil { + fail("failed to fetch env vars from server: " + err.Error()) + } - key := strings.TrimSpace(kv[0]) - if key == "" { - fail(fmt.Sprintf("variable must have a name")) - } - // Do not trim value - the user might want whitespace here - // Also do not check if the value is empty, as an empty value means we want to delete the variable - val := kv[1] - if val == "" { - fail(fmt.Sprintf("variable must have a value; use -u to unset a variable")) - } + for _, v := range vars { + printVar(v, exportEnvs) + } + return + } - vars[i] = theialib.EnvironmentVariable{Name: key, Value: val} - } + service, err := theialib.NewServiceFromEnv() + if err != nil { + fail(err.Error()) + } - _, err = service.SetEnvVar(theialib.SetEnvvarRequest{Variables: vars}) - if err != nil { - fail(fmt.Sprintf("cannot set environment variables: %v", err)) - } + vars, err := service.GetEnvVars(theialib.GetEnvvarsRequest{}) + if err != nil { + fail(fmt.Sprintf("cannot get environment variables: %v", err)) + } - for _, v := range vars { - printVar(v, exportEnvs) - } + for _, v := range vars.Variables { + printVarFromTheia(v, exportEnvs) + } +} + +func setEnvs(args []string) { + if !isTheiaIDE() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + result, err := connectToServer(ctx) + if err != nil { + fail(err.Error()) } - getEnvs := func() { - vars, err := service.GetEnvVars(theialib.GetEnvvarsRequest{}) - if err != nil { - fail(fmt.Sprintf("cannot get environment variables: %v", err)) + + vars := make([]*serverapi.UserEnvVarValue, len(args)) + for i, arg := range args { + kv := strings.Split(arg, "=") + if len(kv) != 2 { + fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg)) } - for _, v := range vars.Variables { - printVar(v, exportEnvs) + key := strings.TrimSpace(kv[0]) + if key == "" { + fail(fmt.Sprintf("variable must have a name")) } - } - doUnsetEnvs := func() { - resp, err := service.DeleteEnvVar(theialib.DeleteEnvvarRequest{Variables: args}) - if err != nil { - fail(fmt.Sprintf("cannot unset environment variables: %v", err)) + // Do not trim value - the user might want whitespace here + // Also do not check if the value is empty, as an empty value means we want to delete the variable + val := kv[1] + if val == "" { + fail(fmt.Sprintf("variable must have a value; use -u to unset a variable")) } - if len(resp.NotDeleted) != 0 { - fail(fmt.Sprintf("cannot unset environment variables: %s", strings.Join(resp.NotDeleted, ", "))) - } + vars[i] = &serverapi.UserEnvVarValue{Name: key, Value: val, RepositoryPattern: result.repositoryPattern} } - if len(args) > 0 { - if unsetEnvs { - doUnsetEnvs() - return - } + var exitCode int + var wg sync.WaitGroup + wg.Add(len(vars)) + for _, v := range vars { + go func(v *serverapi.UserEnvVarValue) { + err = result.client.SetEnvVar(ctx, v) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("cannot set %s: %v", v.Name, err)) + exitCode = -1 + } else { + printVar(v, exportEnvs) + } + wg.Done() + }(v) + } + wg.Wait() + os.Exit(exitCode) + } - setEnvs() - } else { - getEnvs() + service, err := theialib.NewServiceFromEnv() + if err != nil { + fail(err.Error()) + } + + vars := make([]theialib.EnvironmentVariable, len(args)) + for i, arg := range args { + kv := strings.Split(arg, "=") + if len(kv) != 2 { + fail(fmt.Sprintf("%s has no value (correct format is %s=some_value)", arg, arg)) } - }, + + key := strings.TrimSpace(kv[0]) + if key == "" { + fail(fmt.Sprintf("variable must have a name")) + } + // Do not trim value - the user might want whitespace here + // Also do not check if the value is empty, as an empty value means we want to delete the variable + val := kv[1] + if val == "" { + fail(fmt.Sprintf("variable must have a value; use -u to unset a variable")) + } + + vars[i] = theialib.EnvironmentVariable{Name: key, Value: val} + } + + _, err = service.SetEnvVar(theialib.SetEnvvarRequest{Variables: vars}) + if err != nil { + fail(fmt.Sprintf("cannot set environment variables: %v", err)) + } + + for _, v := range vars { + printVarFromTheia(v, exportEnvs) + } +} + +func deleteEnvs(args []string) { + if !isTheiaIDE() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + result, err := connectToServer(ctx) + if err != nil { + fail(err.Error()) + } + + var exitCode int + var wg sync.WaitGroup + wg.Add(len(args)) + for _, name := range args { + go func(name string) { + err = result.client.DeleteEnvVar(ctx, &serverapi.UserEnvVarValue{Name: name, RepositoryPattern: result.repositoryPattern}) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("cannot unset %s: %v", name, err)) + exitCode = -1 + } + wg.Done() + }(name) + } + wg.Wait() + os.Exit(exitCode) + } + + service, err := theialib.NewServiceFromEnv() + if err != nil { + fail(err.Error()) + } + + resp, err := service.DeleteEnvVar(theialib.DeleteEnvvarRequest{Variables: args}) + if err != nil { + fail(fmt.Sprintf("cannot unset environment variables: %v", err)) + } + + if len(resp.NotDeleted) != 0 { + fail(fmt.Sprintf("cannot unset environment variables: %s", strings.Join(resp.NotDeleted, ", "))) + } +} + +func fail(msg string) { + fmt.Fprintln(os.Stderr, msg) + os.Exit(-1) +} + +func printVar(v *serverapi.UserEnvVarValue, export bool) { + val := strings.Replace(v.Value, "\"", "\\\"", -1) + if export { + fmt.Printf("export %s=\"%s\"\n", v.Name, val) + } else { + fmt.Printf("%s=%s\n", v.Name, val) + } } -func printVar(v theialib.EnvironmentVariable, export bool) { +func printVarFromTheia(v theialib.EnvironmentVariable, export bool) { val := strings.Replace(v.Value, "\"", "\\\"", -1) if export { fmt.Printf("export %s=\"%s\"\n", v.Name, val) diff --git a/components/gitpod-cli/go.mod b/components/gitpod-cli/go.mod index c52608ea2e95b7..7670e2ee61ede3 100644 --- a/components/gitpod-cli/go.mod +++ b/components/gitpod-cli/go.mod @@ -16,6 +16,7 @@ require ( github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v0.0.5 golang.org/x/sys v0.0.0-20201112073958-5cba982894dd + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/grpc v1.36.0 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect gopkg.in/yaml.v2 v2.4.0 diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index eb47688f5125e2..1dfb3d9c978022 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -167,6 +167,28 @@ export namespace UserEnvVar { return pattern.toLocaleLowerCase(); } + export function score(value: UserEnvVarValue): number { + // We use a score to enforce precedence: + // value/value = 0 + // value/* = 1 + // */value = 2 + // */* = 3 + // #/# = 4 (used for env vars passed through the URL) + // the lower the score, the higher the precedence. + const [ownerPattern, repoPattern] = splitRepositoryPattern(value.repositoryPattern); + let score = 0; + if (repoPattern == "*") { + score += 1; + } + if (ownerPattern == '*') { + score += 2; + } + if (ownerPattern == "#" || repoPattern == "#") { + score = 4; + } + return score; + } + export function filter(vars: T[], owner: string, repo: string): T[] { let result = vars.filter(e => { const [ownerPattern, repoPattern] = splitRepositoryPattern(e.repositoryPattern); @@ -202,25 +224,7 @@ export namespace UserEnvVar { let minscore = 10; let bestCandidate: T | undefined; for (const e of candidates) { - // We use a score to enforce precedence: - // value/value = 0 - // value/* = 1 - // */value = 2 - // */* = 3 - // #/# = 4 (used for env vars passed through the URL) - // the lower the score, the higher the precedence. - const [ownerPattern, repoPattern] = splitRepositoryPattern(e.repositoryPattern); - let score = 0; - if (repoPattern == "*") { - score += 1; - } - if (ownerPattern == '*') { - score += 2; - } - if (ownerPattern == "#" || repoPattern == "#") { - score = 4; - } - + const score = UserEnvVar.score(e); if (!bestCandidate || score < minscore) { minscore = score; bestCandidate = e; @@ -232,7 +236,7 @@ export namespace UserEnvVar { return result; } - function splitRepositoryPattern(repositoryPattern: string): string[] { + export function splitRepositoryPattern(repositoryPattern: string): string[] { const patterns = repositoryPattern.split('/'); const repoPattern = patterns.pop() || ""; const ownerPattern = patterns.join('/'); @@ -632,7 +636,7 @@ export interface PrebuiltWorkspace { export namespace PrebuiltWorkspace { export function isDone(pws: PrebuiltWorkspace) { - return pws.state === "available" || pws.state === "timeout" || pws.state === 'aborted'; + return pws.state === "available" || pws.state === "timeout" || pws.state === 'aborted'; } export function isAvailable(pws: PrebuiltWorkspace) { diff --git a/components/image-builder-api/go/go.mod b/components/image-builder-api/go/go.mod index 490741b912cc38..b5c8f0fe0ce809 100644 --- a/components/image-builder-api/go/go.mod +++ b/components/image-builder-api/go/go.mod @@ -2,10 +2,6 @@ module github.com/gitpod-io/gitpod/image-builder/api go 1.16 -replace github.com/gitpod-io/gitpod/common-go => ../../common-go // leeway - -replace github.com/gitpod-io/gitpod/content-service/api => ../../content-service-api/go // leeway - require ( github.com/gitpod-io/gitpod/content-service/api v0.0.0-00010101000000-000000000000 github.com/golang/mock v1.5.0 @@ -14,6 +10,10 @@ require ( google.golang.org/protobuf v1.25.0 ) +replace github.com/gitpod-io/gitpod/common-go => ../../common-go // leeway + +replace github.com/gitpod-io/gitpod/content-service/api => ../../content-service-api/go // leeway + replace k8s.io/api => k8s.io/api v0.20.4 // leeway indirect from components/common-go:lib replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.4 // leeway indirect from components/common-go:lib diff --git a/components/server/src/auth/resource-access.spec.ts b/components/server/src/auth/resource-access.spec.ts index eb37a0ac4185bd..58fb08d714b649 100644 --- a/components/server/src/auth/resource-access.spec.ts +++ b/components/server/src/auth/resource-access.spec.ts @@ -7,7 +7,8 @@ import { suite, test } from "mocha-typescript"; import * as chai from 'chai'; const expect = chai.expect; -import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAccessOp } from "./resource-access"; +import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAccessOp, GuardEnvVar, WorkspaceEnvVarAccessGuard } from "./resource-access"; +import { UserEnvVar } from "@gitpod/gitpod-protocol/lib/protocol"; @suite class TestResourceAccess { @@ -18,17 +19,17 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces lower: string[] isSubset: boolean }[] = [ - {name: "empty scopes", upper: [], lower: [], isSubset: true}, - {name: "empty upper, function lower", upper: [], lower: ["function:foo"], isSubset: false}, - {name: "empty upper, resource lower", upper: [], lower: ["resource:workspace::foobar::get"], isSubset: false}, - {name: "resource default upper, resource lower", upper: ["resource:default"], lower: ["resource:workspace::foobar::get"], isSubset: false}, - {name: "resource upper, empty lower", upper: ["resource:workspace::foobar::get"], lower: [], isSubset: true}, - {name: "resource upper, one op less lower", upper: ["resource:workspace::foobar::get/create"], lower: ["resource:workspace::foobar::get"], isSubset: true}, - {name: "resource upper, different resource lower", upper: ["resource:workspace::foobar::get/create"], lower: ["resource:workspace::blabla::get"], isSubset: false}, - {name: "function upper, empty lower", upper: ["function:foo"], lower: [], isSubset: true}, - {name: "function upper, function lower", upper: ["function:foo"], lower: ["function:foo"], isSubset: true}, - {name: "function upper, one function lower", upper: ["function:foo", "function:bar"], lower: ["function:foo"], isSubset: true}, - ]; + { name: "empty scopes", upper: [], lower: [], isSubset: true }, + { name: "empty upper, function lower", upper: [], lower: ["function:foo"], isSubset: false }, + { name: "empty upper, resource lower", upper: [], lower: ["resource:workspace::foobar::get"], isSubset: false }, + { name: "resource default upper, resource lower", upper: ["resource:default"], lower: ["resource:workspace::foobar::get"], isSubset: false }, + { name: "resource upper, empty lower", upper: ["resource:workspace::foobar::get"], lower: [], isSubset: true }, + { name: "resource upper, one op less lower", upper: ["resource:workspace::foobar::get/create"], lower: ["resource:workspace::foobar::get"], isSubset: true }, + { name: "resource upper, different resource lower", upper: ["resource:workspace::foobar::get/create"], lower: ["resource:workspace::blabla::get"], isSubset: false }, + { name: "function upper, empty lower", upper: ["function:foo"], lower: [], isSubset: true }, + { name: "function upper, function lower", upper: ["function:foo"], lower: ["function:foo"], isSubset: true }, + { name: "function upper, one function lower", upper: ["function:foo", "function:bar"], lower: ["function:foo"], isSubset: true }, + ]; tests.forEach(t => { const res = TokenResourceGuard.areScopesSubsetOf(t.upper, t.lower); @@ -43,13 +44,13 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces child: ScopedResourceGuard.ResourceScope isAllowed: boolean }[] = [ - {name: "different kind", isAllowed: false, parent: {kind: "workspace", subjectID: "foo", operations: ["get"]}, child: {kind: "workspaceInstance", subjectID: "foo", operations: ["get"]}}, - {name: "different subject", isAllowed: false, parent: {kind: "workspace", subjectID: "foo", operations: ["get"]}, child: {kind: "workspace", subjectID: "somethingElse", operations: ["get"]}}, - {name: "new op", isAllowed: false, parent: {kind: "workspace", subjectID: "foo", operations: ["get"]}, child: {kind: "workspace", subjectID: "foo", operations: ["get", "create"]}}, - {name: "fewer ops", isAllowed: true, parent: {kind: "workspace", subjectID: "foo", operations: ["get", "create"]}, child: {kind: "workspace", subjectID: "foo", operations: ["get"]}}, - {name: "exact match", isAllowed: true, parent: {kind: "workspace", subjectID: "foo", operations: ["get"]}, child: {kind: "workspace", subjectID: "foo", operations: ["get"]}}, - {name: "no ops", isAllowed: true, parent: {kind: "workspace", subjectID: "foo", operations: []}, child: {kind: "workspace", subjectID: "foo", operations: []}}, - ]; + { name: "different kind", isAllowed: false, parent: { kind: "workspace", subjectID: "foo", operations: ["get"] }, child: { kind: "workspaceInstance", subjectID: "foo", operations: ["get"] } }, + { name: "different subject", isAllowed: false, parent: { kind: "workspace", subjectID: "foo", operations: ["get"] }, child: { kind: "workspace", subjectID: "somethingElse", operations: ["get"] } }, + { name: "new op", isAllowed: false, parent: { kind: "workspace", subjectID: "foo", operations: ["get"] }, child: { kind: "workspace", subjectID: "foo", operations: ["get", "create"] } }, + { name: "fewer ops", isAllowed: true, parent: { kind: "workspace", subjectID: "foo", operations: ["get", "create"] }, child: { kind: "workspace", subjectID: "foo", operations: ["get"] } }, + { name: "exact match", isAllowed: true, parent: { kind: "workspace", subjectID: "foo", operations: ["get"] }, child: { kind: "workspace", subjectID: "foo", operations: ["get"] } }, + { name: "no ops", isAllowed: true, parent: { kind: "workspace", subjectID: "foo", operations: [] }, child: { kind: "workspace", subjectID: "foo", operations: [] } }, + ]; tests.forEach(t => { const res = ScopedResourceGuard.isAllowedUnder(t.parent, t.child); @@ -58,7 +59,7 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces } @test public async tokenResourceGuardCanAccess() { - const workspaceResource: GuardedResource = {kind: "workspace", subject: {id:"wsid", ownerId: "foo"} as any}; + const workspaceResource: GuardedResource = { kind: "workspace", subject: { id: "wsid", ownerId: "foo" } as any }; const tests: { name: string guard: TokenResourceGuard @@ -66,71 +67,71 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces operation?: ResourceAccessOp, expectation: boolean }[] = [ - { - name: "no scopes", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, []), - expectation: false, - }, - { - name: "default scope positive", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [TokenResourceGuard.DefaultResourceScope]), - expectation: true, - }, - { - name: "default scope negative", - guard: new TokenResourceGuard("someoneElse", [TokenResourceGuard.DefaultResourceScope]), - expectation: false, - }, - { - name: "explicit scope", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:"+ScopedResourceGuard.marshalResourceScopeFromResource(workspaceResource, ["get"]), - ]), - expectation: true, - }, - { - name: "default and explicit scope", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:default", - "resource:"+ScopedResourceGuard.marshalResourceScopeFromResource(workspaceResource, ["create"]), - ]), - expectation: true, - }, - { - name: "delegate scopes delegate to owner resource guard", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "workspace", subjectID: "*", operations: ["get"]}), - ]), - expectation: true, - }, - { - name: "snaphshot create", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "snapshot", subjectID: ScopedResourceGuard.SNAPSHOT_WORKSPACE_SUBJECT_ID_PREFIX + workspaceResource.subject.id, operations: ["create"]}), - ]), - resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: workspaceResource.subject.ownerId}, - operation: "create", - expectation: true, - }, - { - name: "snaphshot create missing prefix fails", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "snapshot", subjectID: workspaceResource.subject.id, operations: ["create"]}), - ]), - resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: workspaceResource.subject.ownerId}, - operation: "create", - expectation: false, - }, - { - name: "snaphshot create other user fails", - guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "snapshot", subjectID: workspaceResource.subject.id, operations: ["create"]}), - ]), - resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: "other_owner"}, - operation: "create", - expectation: false, - }, - ] + { + name: "no scopes", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, []), + expectation: false, + }, + { + name: "default scope positive", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [TokenResourceGuard.DefaultResourceScope]), + expectation: true, + }, + { + name: "default scope negative", + guard: new TokenResourceGuard("someoneElse", [TokenResourceGuard.DefaultResourceScope]), + expectation: false, + }, + { + name: "explicit scope", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:" + ScopedResourceGuard.marshalResourceScopeFromResource(workspaceResource, ["get"]), + ]), + expectation: true, + }, + { + name: "default and explicit scope", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:default", + "resource:" + ScopedResourceGuard.marshalResourceScopeFromResource(workspaceResource, ["create"]), + ]), + expectation: true, + }, + { + name: "delegate scopes delegate to owner resource guard", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspace", subjectID: "*", operations: ["get"] }), + ]), + expectation: true, + }, + { + name: "snaphshot create", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "snapshot", subjectID: ScopedResourceGuard.SNAPSHOT_WORKSPACE_SUBJECT_ID_PREFIX + workspaceResource.subject.id, operations: ["create"] }), + ]), + resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: workspaceResource.subject.ownerId }, + operation: "create", + expectation: true, + }, + { + name: "snaphshot create missing prefix fails", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "snapshot", subjectID: workspaceResource.subject.id, operations: ["create"] }), + ]), + resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: workspaceResource.subject.ownerId }, + operation: "create", + expectation: false, + }, + { + name: "snaphshot create other user fails", + guard: new TokenResourceGuard(workspaceResource.subject.ownerId, [ + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "snapshot", subjectID: workspaceResource.subject.id, operations: ["create"] }), + ]), + resource: { kind: "snapshot", subject: undefined, workspaceID: workspaceResource.subject.id, workspaceOwnerID: "other_owner" }, + operation: "create", + expectation: false, + }, + ] await Promise.all(tests.map(async t => { const res = await t.guard.canAccess(t.resource || workspaceResource, t.operation || "get") @@ -139,62 +140,62 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces } @test public async scopedResourceGuardCanAccess() { - const workspaceResource: GuardedResource = {kind: "workspace", subject: {id:"wsid", ownerId: "foo"} as any}; + const workspaceResource: GuardedResource = { kind: "workspace", subject: { id: "wsid", ownerId: "foo" } as any }; const tests: { name: string guard: ScopedResourceGuard expectation: boolean }[] = [ - { - name: "no scopes", - guard: new ScopedResourceGuard([]), - expectation: false - }, - { - name: "explicit scope", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"]} - ]), - expectation: true - }, - { - name: "explicit scope with different op", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["create"]} - ]), - expectation: false - }, - { - name: "delegate scope", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: "*", operations: ["get"]} - ], { canAccess: async () => true }), - expectation: true - }, - { - name: "delegate scope has precedence", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"]}, - {kind: workspaceResource.kind, subjectID: "*", operations: ["get"]}, - ], { canAccess: async () => "actually comes from delegate" as any }), - expectation: "actually comes from delegate" as any - }, - { - name: "delegate scope matches ops", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"]}, - {kind: workspaceResource.kind, subjectID: "*", operations: ["create"]}, - ], { canAccess: async () => "actually comes from delegate" as any }), - expectation: true - }, - { - name: "delegate scope not configured", - guard: new ScopedResourceGuard([ - {kind: workspaceResource.kind, subjectID: "*", operations: ["get"]}, - ]), - expectation: false - } - ] + { + name: "no scopes", + guard: new ScopedResourceGuard([]), + expectation: false + }, + { + name: "explicit scope", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"] } + ]), + expectation: true + }, + { + name: "explicit scope with different op", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["create"] } + ]), + expectation: false + }, + { + name: "delegate scope", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: "*", operations: ["get"] } + ], { canAccess: async () => true }), + expectation: true + }, + { + name: "delegate scope has precedence", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"] }, + { kind: workspaceResource.kind, subjectID: "*", operations: ["get"] }, + ], { canAccess: async () => "actually comes from delegate" as any }), + expectation: "actually comes from delegate" as any + }, + { + name: "delegate scope matches ops", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: workspaceResource.subject.id, operations: ["get"] }, + { kind: workspaceResource.kind, subjectID: "*", operations: ["create"] }, + ], { canAccess: async () => "actually comes from delegate" as any }), + expectation: true + }, + { + name: "delegate scope not configured", + guard: new ScopedResourceGuard([ + { kind: workspaceResource.kind, subjectID: "*", operations: ["get"] }, + ]), + expectation: false + } + ] await Promise.all(tests.map(async t => { const res = await t.guard.canAccess(workspaceResource, "get") @@ -202,6 +203,100 @@ import { TokenResourceGuard, ScopedResourceGuard, GuardedResource, ResourceAcces })) } + @test public async workspaceEnvVarAccessGuardCanAccess() { + const getEnvVarResourceScope: ScopedResourceGuard.ResourceScope<'envVar'> = { kind: 'envVar', subjectID: 'foo/x', operations: ['get'] }; + const tests: { + name: string + guard: ScopedResourceGuard + guardEnvVar: GuardEnvVar + operation: ResourceAccessOp + expectation: boolean + }[] = [ + { + name: "no scopes with any owner and repo", + guard: new WorkspaceEnvVarAccessGuard([]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: '*/*' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + { + name: "explicit scope with any owner and repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: '*/*' } as UserEnvVar }, + operation: 'get', + expectation: true + }, + { + name: "explicit scope with any owner and exact same repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: '*/x' } as UserEnvVar }, + operation: 'get', + expectation: true + }, + { + name: "explicit scope with any owner and exact different repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: '*/y' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + { + name: "explicit scope with exact same owner and any repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'foo/*' } as UserEnvVar }, + operation: 'get', + expectation: true + }, + { + name: "explicit scope with exact different owner and any repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'bar/*' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + { + name: "explicit scope with exact same owner and repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'foo/x' } as UserEnvVar }, + operation: 'get', + expectation: true + }, + { + name: "explicit scope with exact same owner and exact different repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'foo/y' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + { + name: "explicit scope with exact same owner and repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'foo/x' } as UserEnvVar }, + operation: 'get', + expectation: true + }, + { + name: "explicit scope with exact different owner and exact same repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'bar/x' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + { + name: "explicit scope with exact different owner and repo", + guard: new WorkspaceEnvVarAccessGuard([getEnvVarResourceScope]), + guardEnvVar: { kind: 'envVar', subject: { repositoryPattern: 'bar/y' } as UserEnvVar }, + operation: 'get', + expectation: false + }, + ] + + await Promise.all(tests.map(async t => { + const res = await t.guard.canAccess(t.guardEnvVar, 'get') + expect(res).to.be.eq(t.expectation, `"${t.name}" expected canAccess(...) === ${t.expectation}, but was ${res}`); + })) + } + } module.exports = new TestResourceAccess(); \ No newline at end of file diff --git a/components/server/src/auth/resource-access.ts b/components/server/src/auth/resource-access.ts index 45cd729fd63ac5..bcb22d10c3c98d 100644 --- a/components/server/src/auth/resource-access.ts +++ b/components/server/src/auth/resource-access.ts @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -import { Workspace, WorkspaceInstance, User, Snapshot, GitpodToken, Token } from "@gitpod/gitpod-protocol"; +import { GitpodToken, Snapshot, Token, User, UserEnvVar, Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol"; declare var resourceInstance: GuardedResource; export type GuardedResourceKind = typeof resourceInstance.kind; @@ -17,8 +17,24 @@ export type GuardedResource = GuardedGitpodToken | GuardedToken | GuardedUserStorage | - GuardedContentBlob -; + GuardedContentBlob | + GuardEnvVar + ; + +const ALL_GUARDED_RESOURCE_KINDS = new Set([ + 'workspace', + 'workspaceInstance', + 'user', + 'snapshot', + 'gitpodToken', + 'token', + 'userStorage', + 'contentBlob', + 'envVar' +]); +export function isGuardedResourceKind(kind: any): kind is GuardedResourceKind { + return typeof kind === 'string' && ALL_GUARDED_RESOURCE_KINDS.has(kind as GuardedResourceKind); +} export interface GuardedWorkspace { kind: "workspace"; @@ -56,6 +72,11 @@ export interface GuardedContentBlob { name: string; } +export interface GuardEnvVar { + kind: "envVar"; + subject: UserEnvVar; +} + export interface GuardedGitpodToken { kind: "gitpodToken"; subject: GitpodToken; @@ -70,9 +91,9 @@ export interface GuardedToken { export type ResourceAccessOp = "create" | "update" | - "get" | + "get" | "delete" -; + ; export const ResourceAccessGuard = Symbol("ResourceAccessGuard"); @@ -89,8 +110,8 @@ export interface WithResourceAccessGuard { * CompositeResourceAccessGuard grants access to resources if at least one of its children does. */ export class CompositeResourceAccessGuard implements ResourceAccessGuard { - - constructor(protected readonly children: ResourceAccessGuard[]) {} + + constructor(protected readonly children: ResourceAccessGuard[]) { } async canAccess(resource: GuardedResource, operation: ResourceAccessOp): Promise { // if a single guard permitts access, we're good to go @@ -105,7 +126,7 @@ export class CompositeResourceAccessGuard implements ResourceAccessGuard { */ export class OwnerResourceGuard implements ResourceAccessGuard { - constructor(readonly userId: string) {} + constructor(readonly userId: string) { } async canAccess(resource: GuardedResource, operation: ResourceAccessOp): Promise { switch (resource.kind) { @@ -125,6 +146,8 @@ export class OwnerResourceGuard implements ResourceAccessGuard { return resource.subject.ownerId === this.userId; case "workspaceInstance": return resource.workspaceOwnerID === this.userId; + case "envVar": + return resource.subject.userId === this.userId; } } @@ -144,11 +167,13 @@ export class SharedWorkspaceAccessGuard implements ResourceAccessGuard { } } -export class ScopedResourceGuard implements ResourceAccessGuard { - protected readonly scopes: { [index: string]: ScopedResourceGuard.ResourceScope } = {}; +export class ScopedResourceGuard implements ResourceAccessGuard { + private readonly scopes = new Map>(); - constructor(scopes: ScopedResourceGuard.ResourceScope[], protected readonly delegate?: ResourceAccessGuard) { - scopes.forEach(s => this.scopes[`${s.kind}::${s.subjectID}`] = s); + constructor(scopes: ScopedResourceGuard.ResourceScope[], protected readonly delegate?: ResourceAccessGuard) { + for (const scope of scopes) { + this.pushScope(scope); + } } async canAccess(resource: GuardedResource, operation: ResourceAccessOp): Promise { @@ -157,17 +182,49 @@ export class ScopedResourceGuard implements ResourceAccessGuard { return false; } - const defaultScope = this.scopes[`${resource.kind}::*`]; - if (!!this.delegate && !!defaultScope && defaultScope.operations.some(op => op === operation)) { - return await this.delegate.canAccess(resource, operation); + if (this.delegate && this.hasScope(`${resource.kind}::*`, operation)) { + return this.delegate.canAccess(resource, operation); } - const scope = this.scopes[`${resource.kind}::${subjectID}`]; - if (!scope) { + return this.hasScope(`${resource.kind}::${subjectID}`, operation); + } + + private hasScope(scope: string, operation: ResourceAccessOp): boolean { + return !!this.scopes.get(scope)?.has(operation); + } + + protected pushScope(scope: ScopedResourceGuard.ResourceScope): void { + this.scopes.set(`${scope.kind}::${scope.subjectID}`, new Set(scope.operations)); + } + +} + +export class WorkspaceEnvVarAccessGuard extends ScopedResourceGuard<'envVar'> { + + private readAccessWildcardPatterns: Set | undefined; + + async canAccess(resource: GuardedResource, operation: ResourceAccessOp): Promise { + if (resource.kind !== 'envVar') { return false; } + // allow read access based on wildcard repo patterns matching + if (operation === 'get' && this.readAccessWildcardPatterns?.has(resource.subject.repositoryPattern)) { + return true; + } + // but mutations only based on exact matching + return super.canAccess(resource, operation); + } - return scope.operations.some(op => op === operation); + protected pushScope(scope: ScopedResourceGuard.ResourceScope<'envVar'>): void { + super.pushScope(scope); + if (!scope.operations.includes('get')) { + return; + } + const [owner, repo] = UserEnvVar.splitRepositoryPattern(scope.subjectID); + this.readAccessWildcardPatterns = this.readAccessWildcardPatterns || new Set(); + this.readAccessWildcardPatterns.add('*/*'); + this.readAccessWildcardPatterns.add(`${owner}/*`); + this.readAccessWildcardPatterns.add(`*/${repo}`); } } @@ -176,11 +233,14 @@ export namespace ScopedResourceGuard { export const SNAPSHOT_WORKSPACE_SUBJECT_ID_PREFIX = 'ws-' - export interface ResourceScope { - kind: GuardedResourceKind; + export interface ResourceScope { + kind: K; subjectID: string; operations: ResourceAccessOp[]; } + export function ofKind(scope: ResourceScope, kind: K): scope is ResourceScope { + return scope.kind === kind; + } export function isAllowedUnder(parent: ResourceScope, child: ResourceScope): boolean { if (child.kind !== parent.kind) { @@ -199,12 +259,19 @@ export namespace ScopedResourceGuard { export function unmarshalResourceScope(scope: string): ResourceScope { const segs = scope.split("::"); if (segs.length != 3) { - throw new Error("invalid scope") + throw new Error("invalid scope"); + } + const kind = segs[0]; + if (!isGuardedResourceKind(kind)) { + throw new Error("invalid resource kind"); + } + let subjectID = segs[1]; + if (kind === 'envVar') { + subjectID = UserEnvVar.normalizeRepoPattern(subjectID); } - return { - kind: segs[0] as GuardedResourceKind, - subjectID: segs[1], + kind, + subjectID, operations: segs[2].split("/").map(o => o.trim()) as ResourceAccessOp[], }; } @@ -250,6 +317,8 @@ export namespace ScopedResourceGuard { return resource.subject.id; case "workspaceInstance": return resource.subject ? resource.subject.id : undefined; + case "envVar": + return resource.subject.repositoryPattern; } } } @@ -265,7 +334,22 @@ export class TokenResourceGuard implements ResourceAccessGuard { this.delegate = ownerResourceGuard; } else { const resourceScopes = TokenResourceGuard.getResourceScopes(allTokenScopes); - this.delegate = new ScopedResourceGuard(resourceScopes, ownerResourceGuard); + const envVarScopes: ScopedResourceGuard.ResourceScope<'envVar'>[] = []; + const otherScopes: ScopedResourceGuard.ResourceScope[] = []; + for (const scope of resourceScopes) { + if (ScopedResourceGuard.ofKind(scope, 'envVar')) { + envVarScopes.push(scope); + } else { + otherScopes.push(scope); + } + } + this.delegate = new ScopedResourceGuard(otherScopes, ownerResourceGuard) + if (envVarScopes.length) { + this.delegate = new CompositeResourceAccessGuard([ + new WorkspaceEnvVarAccessGuard(envVarScopes, ownerResourceGuard), + this.delegate + ]); + } } } @@ -280,12 +364,12 @@ export class TokenResourceGuard implements ResourceAccessGuard { } export namespace TokenResourceGuard { - + export const DefaultResourceScope = "resource:default"; export function getResourceScopes(s: string[]): ScopedResourceGuard.ResourceScope[] { return s.filter(s => s.startsWith("resource:") && s !== DefaultResourceScope) - .map(s => ScopedResourceGuard.unmarshalResourceScope(s.substring("resource:".length))); + .map(s => ScopedResourceGuard.unmarshalResourceScope(s.substring("resource:".length))); } export function areScopesSubsetOf(upperScopes: string[], lowerScopes: string[]) { diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index b6bf0171c97675..3109593c0f591d 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -368,9 +368,9 @@ export class GitpodServerImpl { - // Note: this operation is per-user only, hence needs no resource guard - const user = this.checkUser("getEnvVars"); - return (await this.userDB.getEnvVars(user.id)).map(v => { - return { - id: v.id, - name: v.name, - value: v.value, - repositoryPattern: v.repositoryPattern, + const result = new Map(); + for (const value of await this.userDB.getEnvVars(user.id)) { + if (!await this.resourceAccessGuard.canAccess({ kind: 'envVar', subject: value }, 'get')) { + continue; } - }); + const score = UserEnvVar.score(value); + const current = result.get(value.name); + if (!current || score < current.score) { + result.set(value.name, { value, score }); + } + } + return [...result.values()] + .map(({ value: { id, name, value, repositoryPattern } }) => ({ id, name, value, repositoryPattern })); } async setEnvVar(variable: UserEnvVarValue): Promise { @@ -1278,6 +1281,13 @@ export class GitpodServerImpl !v.deleted); + + const existingVar = existingVars.find(v => v.name == variable.name && v.repositoryPattern == variable.repositoryPattern); + if (!!existingVar) { + // overwrite existing variable rather than introduce a duplicate + variable.id = existingVar.id; + } + if (!variable.id) { // this is a new variable - make sure the user does not have too many (don't DOS our database using gp env) const varCount = existingVars.length; @@ -1286,17 +1296,13 @@ export class GitpodServerImpl v.name == variable.name && v.repositoryPattern == variable.repositoryPattern); - if (!!existingVar) { - // overwrite existing variable rather than introduce a duplicate - variable.id = existingVar.id; - } - const envvar: UserEnvVar = { ...variable, id: variable.id || uuidv4(), userId: user.id, }; + await this.guardAccess({ kind: 'envVar', subject: envvar }, typeof variable.id === 'string' ? 'update' : 'create'); + await this.userDB.setEnvVar(envvar); } @@ -1304,6 +1310,13 @@ export class GitpodServerImpl !v.deleted); + const existingVar = existingVars.find(v => v.name == variable.name && v.repositoryPattern == variable.repositoryPattern); + variable.id = existingVar?.id; + } + if (!variable.id) { throw new ResponseError(ErrorCodes.NOT_FOUND, "Missing ID field") } @@ -1313,6 +1326,8 @@ export class GitpodServerImpl { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } - async tsAssignSlot(teamSubscriptionId: string, teamSubscriptionSlotId: string, identityStr: string|undefined): Promise { + async tsAssignSlot(teamSubscriptionId: string, teamSubscriptionSlotId: string, identityStr: string | undefined): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } async tsReassignSlot(teamSubscriptionId: string, teamSubscriptionSlotId: string, newIdentityStr: string): Promise { diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index a7ed9f1eae500c..a8168e01031f13 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -499,6 +499,11 @@ export class WorkspaceStarter { contextUrlEnv.setValue(workspace.contextURL); envvars.push(contextUrlEnv); + const contextEnv = new EnvironmentVariable(); + contextEnv.setName('GITPOD_WORKSPACE_CONTEXT'); + contextEnv.setValue(JSON.stringify(workspace.context)); + envvars.push(contextEnv); + log.debug("Workspace config", workspace.config) if (!!workspace.config.tasks) { // The task config is interpreted by Theia only, there's little point in transforming it into something @@ -619,7 +624,7 @@ export class WorkspaceStarter { } protected createDefaultGitpodAPITokenScopes(workspace: Workspace, instance: WorkspaceInstance): string[] { - return [ + const scopes = [ "function:getWorkspace", "function:getLoggedInUser", "function:getPortAuthenticationToken", @@ -643,6 +648,9 @@ export class WorkspaceStarter { "function:getContentBlobDownloadUrl", "function:accessCodeSyncStorage", "function:guessGitTokenScopes", + "function:getEnvVars", + "function:setEnvVar", + "function:deleteEnvVar", "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "workspace", subjectID: workspace.id, operations: ["get", "update"]}), "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "workspaceInstance", subjectID: instance.id, operations: ["get", "update", "delete"]}), @@ -651,7 +659,14 @@ export class WorkspaceStarter { "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "userStorage", subjectID: "*", operations: ["create", "get", "update"]}), "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "token", subjectID: "*", operations: ["get"]}), "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "contentBlob", subjectID: "*", operations: ["create", "get"]}), - ] + ]; + if (CommitContext.is(workspace.context)) { + const subjectID = workspace.context.repository.owner + '/' + workspace.context.repository.name; + scopes.push( + "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "envVar", subjectID, operations: ["create", "get", "update", "delete"]}), + ); + } + return scopes; } protected createGitSpec(workspace: Workspace, user: User): GitSpec { diff --git a/components/supervisor-api/go/control.pb.go b/components/supervisor-api/go/control.pb.go index a63bd67ae3c4ec..5cf3eccb5f096f 100644 --- a/components/supervisor-api/go/control.pb.go +++ b/components/supervisor-api/go/control.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: control.proto package api import ( - proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -25,10 +24,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type ExposePortRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/components/supervisor-api/go/info.pb.go b/components/supervisor-api/go/info.pb.go index da97fa680a0202..acc2da837397bc 100644 --- a/components/supervisor-api/go/info.pb.go +++ b/components/supervisor-api/go/info.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: info.proto package api import ( - proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -26,10 +25,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type WorkspaceInfoRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -93,6 +88,8 @@ type WorkspaceInfoResponse struct { GitpodHost string `protobuf:"bytes,8,opt,name=gitpod_host,json=gitpodHost,proto3" json:"gitpod_host,omitempty"` // workspace_context_url is an URL for which the workspace was created. WorkspaceContextUrl string `protobuf:"bytes,9,opt,name=workspace_context_url,json=workspaceContextUrl,proto3" json:"workspace_context_url,omitempty"` + // repository is a repository from which this workspace was created + Repository *WorkspaceInfoResponse_Repository `protobuf:"bytes,10,opt,name=repository,proto3" json:"repository,omitempty"` } func (x *WorkspaceInfoResponse) Reset() { @@ -197,6 +194,13 @@ func (x *WorkspaceInfoResponse) GetWorkspaceContextUrl() string { return "" } +func (x *WorkspaceInfoResponse) GetRepository() *WorkspaceInfoResponse_Repository { + if x != nil { + return x.Repository + } + return nil +} + type isWorkspaceInfoResponse_WorkspaceLocation interface { isWorkspaceInfoResponse_WorkspaceLocation() } @@ -272,6 +276,63 @@ func (x *WorkspaceInfoResponse_GitpodAPI) GetHost() string { return "" } +type WorkspaceInfoResponse_Repository struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // owner is the repository owner + Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + // name is the repository name + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *WorkspaceInfoResponse_Repository) Reset() { + *x = WorkspaceInfoResponse_Repository{} + if protoimpl.UnsafeEnabled { + mi := &file_info_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkspaceInfoResponse_Repository) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceInfoResponse_Repository) ProtoMessage() {} + +func (x *WorkspaceInfoResponse_Repository) ProtoReflect() protoreflect.Message { + mi := &file_info_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkspaceInfoResponse_Repository.ProtoReflect.Descriptor instead. +func (*WorkspaceInfoResponse_Repository) Descriptor() ([]byte, []int) { + return file_info_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *WorkspaceInfoResponse_Repository) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *WorkspaceInfoResponse_Repository) GetName() string { + if x != nil { + return x.Name + } + return "" +} + var File_info_proto protoreflect.FileDescriptor var file_info_proto_rawDesc = []byte{ @@ -279,8 +340,8 @@ var file_info_proto_rawDesc = []byte{ 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x91, - 0x04, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x97, + 0x05, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, @@ -307,24 +368,32 @@ var file_info_proto_rawDesc = []byte{ 0x0a, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x72, 0x6c, 0x1a, - 0x3b, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x41, 0x50, 0x49, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x42, 0x14, 0x0a, 0x12, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x32, 0x7f, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x70, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x20, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, - 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, - 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, - 0x6f, 0x64, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x55, 0x72, 0x6c, 0x12, + 0x4c, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, + 0x79, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x3b, 0x0a, + 0x09, 0x47, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x41, 0x50, 0x49, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x1a, 0x36, 0x0a, 0x0a, 0x52, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x42, 0x14, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x7f, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x70, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, + 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x2f, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, + 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, + 0x73, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -339,21 +408,23 @@ func file_info_proto_rawDescGZIP() []byte { return file_info_proto_rawDescData } -var file_info_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_info_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_info_proto_goTypes = []interface{}{ - (*WorkspaceInfoRequest)(nil), // 0: supervisor.WorkspaceInfoRequest - (*WorkspaceInfoResponse)(nil), // 1: supervisor.WorkspaceInfoResponse - (*WorkspaceInfoResponse_GitpodAPI)(nil), // 2: supervisor.WorkspaceInfoResponse.GitpodAPI + (*WorkspaceInfoRequest)(nil), // 0: supervisor.WorkspaceInfoRequest + (*WorkspaceInfoResponse)(nil), // 1: supervisor.WorkspaceInfoResponse + (*WorkspaceInfoResponse_GitpodAPI)(nil), // 2: supervisor.WorkspaceInfoResponse.GitpodAPI + (*WorkspaceInfoResponse_Repository)(nil), // 3: supervisor.WorkspaceInfoResponse.Repository } var file_info_proto_depIdxs = []int32{ 2, // 0: supervisor.WorkspaceInfoResponse.gitpod_api:type_name -> supervisor.WorkspaceInfoResponse.GitpodAPI - 0, // 1: supervisor.InfoService.WorkspaceInfo:input_type -> supervisor.WorkspaceInfoRequest - 1, // 2: supervisor.InfoService.WorkspaceInfo:output_type -> supervisor.WorkspaceInfoResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 3, // 1: supervisor.WorkspaceInfoResponse.repository:type_name -> supervisor.WorkspaceInfoResponse.Repository + 0, // 2: supervisor.InfoService.WorkspaceInfo:input_type -> supervisor.WorkspaceInfoRequest + 1, // 3: supervisor.InfoService.WorkspaceInfo:output_type -> supervisor.WorkspaceInfoResponse + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_info_proto_init() } @@ -398,6 +469,18 @@ func file_info_proto_init() { return nil } } + file_info_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceInfoResponse_Repository); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_info_proto_msgTypes[1].OneofWrappers = []interface{}{ (*WorkspaceInfoResponse_WorkspaceLocationFile)(nil), @@ -409,7 +492,7 @@ func file_info_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_info_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/components/supervisor-api/go/notification.pb.go b/components/supervisor-api/go/notification.pb.go index 30598dc57d072f..b1845ee9f0fb65 100644 --- a/components/supervisor-api/go/notification.pb.go +++ b/components/supervisor-api/go/notification.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: notification.proto package api import ( - proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -26,10 +25,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type NotifyRequest_Level int32 const ( diff --git a/components/supervisor-api/go/status.pb.go b/components/supervisor-api/go/status.pb.go index e1e1bd5929cca8..890b520436200e 100644 --- a/components/supervisor-api/go/status.pb.go +++ b/components/supervisor-api/go/status.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: status.proto package api import ( - proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -26,10 +25,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type ContentSource int32 const ( diff --git a/components/supervisor-api/go/terminal.pb.go b/components/supervisor-api/go/terminal.pb.go index 53aef0686fb95a..9a0757caf774a7 100644 --- a/components/supervisor-api/go/terminal.pb.go +++ b/components/supervisor-api/go/terminal.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: terminal.proto package api import ( - proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -26,10 +25,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type TerminalSize struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/components/supervisor-api/go/token.pb.go b/components/supervisor-api/go/token.pb.go index a9dbc23e7c0f3d..4345b92aae0283 100644 --- a/components/supervisor-api/go/token.pb.go +++ b/components/supervisor-api/go/token.pb.go @@ -4,14 +4,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.15.5 // source: token.proto package api import ( - proto "github.com/golang/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -27,10 +26,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type TokenReuse int32 const ( diff --git a/components/supervisor-api/info.proto b/components/supervisor-api/info.proto index fc1c3e9548e955..4c62668299666b 100644 --- a/components/supervisor-api/info.proto +++ b/components/supervisor-api/info.proto @@ -56,4 +56,13 @@ message WorkspaceInfoResponse { // workspace_context_url is an URL for which the workspace was created. string workspace_context_url = 9; + + message Repository { + // owner is the repository owner + string owner = 1; + // name is the repository name + string name = 2; + } + // repository is a repository from which this workspace was created + Repository repository = 10; } diff --git a/components/supervisor/pkg/supervisor/config.go b/components/supervisor/pkg/supervisor/config.go index 689f5558facab5..1410c05d4637e9 100644 --- a/components/supervisor/pkg/supervisor/config.go +++ b/components/supervisor/pkg/supervisor/config.go @@ -18,6 +18,7 @@ import ( env "github.com/Netflix/go-env" "golang.org/x/xerrors" + gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" "github.com/gitpod-io/gitpod/supervisor/api" ) @@ -195,6 +196,9 @@ type WorkspaceConfig struct { // DebugEnabled controls whether the supervisor debugging facilities (pprof, grpc tracing) shoudl be enabled DebugEnable bool `env:"SUPERVISOR_DEBUG_ENABLE"` + + // WorkspaceContext is a context for this workspace + WorkspaceContext string `env:"GITPOD_WORKSPACE_CONTEXT"` } // WorkspaceGitpodToken is a list of tokens that should be added to supervisor's token service @@ -311,6 +315,18 @@ func (c WorkspaceConfig) getGitpodTasks() (tasks *[]TaskConfig, err error) { return } +// getCommit returns a commit from which this workspace was created +func (c WorkspaceConfig) getCommit() (commit *gitpod.Commit, err error) { + if c.WorkspaceContext == "" { + return + } + err = json.Unmarshal([]byte(c.WorkspaceContext), &commit) + if err != nil { + return nil, fmt.Errorf("cannot parse workspace context as a commit: %w", err) + } + return +} + // GetConfig loads the supervisor configuration func GetConfig() (*Config, error) { static, err := loadStaticConfigFromFile() diff --git a/components/supervisor/pkg/supervisor/services.go b/components/supervisor/pkg/supervisor/services.go index caa3ae51d056f5..ae68c50ab48ae9 100644 --- a/components/supervisor/pkg/supervisor/services.go +++ b/components/supervisor/pkg/supervisor/services.go @@ -581,6 +581,16 @@ func (is *InfoService) WorkspaceInfo(context.Context, *api.WorkspaceInfoRequest) WorkspaceContextUrl: is.cfg.WorkspaceContextURL, } + commit, err := is.cfg.getCommit() + if err != nil { + log.WithError(err).Error() + } else if commit != nil && commit.Repository != nil { + resp.Repository = &api.WorkspaceInfoResponse_Repository{ + Owner: commit.Repository.Owner, + Name: commit.Repository.Name, + } + } + stat, err := os.Stat(is.cfg.WorkspaceRoot) if err != nil { return nil, status.Error(codes.Internal, err.Error()) diff --git a/components/ws-manager/pkg/manager/create.go b/components/ws-manager/pkg/manager/create.go index f5337245ddc853..1d836d1507188d 100644 --- a/components/ws-manager/pkg/manager/create.go +++ b/components/ws-manager/pkg/manager/create.go @@ -537,7 +537,7 @@ func (m *Manager) createWorkspaceEnvironment(startContext *startWorkspaceContext // User-defined env vars (i.e. those coming from the request) if spec.Envvars != nil { for _, e := range spec.Envvars { - if e.Name == "GITPOD_WORKSPACE_CONTEXT_URL" || e.Name == "GITPOD_TASKS" || e.Name == "GITPOD_RESOLVED_EXTENSIONS" || e.Name == "GITPOD_EXTERNAL_EXTENSIONS" { + if e.Name == "GITPOD_WORKSPACE_CONTEXT" || e.Name == "GITPOD_WORKSPACE_CONTEXT_URL" || e.Name == "GITPOD_TASKS" || e.Name == "GITPOD_RESOLVED_EXTENSIONS" || e.Name == "GITPOD_EXTERNAL_EXTENSIONS" { result = append(result, corev1.EnvVar{Name: e.Name, Value: e.Value}) continue } else if strings.HasPrefix(e.Name, "GITPOD_") { From 2adaa06b6621e1ce0077c748791a629004e56f60 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 25 Mar 2021 19:27:39 +0000 Subject: [PATCH 2/2] [gitpod-cli/protocol] remove dependency to common-go --- components/gitpod-cli/BUILD.yaml | 1 - components/gitpod-cli/cmd/env.go | 15 +++++- .../gitpod-cli/cmd/git-token-validator.go | 14 ++++- components/gitpod-cli/go.mod | 50 ------------------ components/gitpod-cli/go.sum | 2 + components/gitpod-protocol/go/BUILD.yaml | 2 - .../gitpod-protocol/go/gitpod-config.go | 16 +++--- .../gitpod-protocol/go/gitpod-config_test.go | 5 +- .../gitpod-protocol/go/gitpod-service.go | 11 ++-- components/gitpod-protocol/go/go.mod | 52 +------------------ components/gitpod-protocol/go/go.sum | 1 + .../gitpod-protocol/go/reconnecting-ws.go | 16 +++--- .../src/workspace/gitpod-server-impl.ts | 2 +- components/supervisor/cmd/call-server.go | 1 + .../supervisor/pkg/supervisor/supervisor.go | 3 +- test/pkg/integration/apis.go | 2 + 16 files changed, 66 insertions(+), 127 deletions(-) diff --git a/components/gitpod-cli/BUILD.yaml b/components/gitpod-cli/BUILD.yaml index 14c987464aa8d7..453ba337078343 100644 --- a/components/gitpod-cli/BUILD.yaml +++ b/components/gitpod-cli/BUILD.yaml @@ -9,7 +9,6 @@ packages: - CGO_ENABLED=0 - GOOS=linux deps: - - components/common-go:lib - components/supervisor-api/go:lib - components/gitpod-protocol/go:lib config: diff --git a/components/gitpod-cli/cmd/env.go b/components/gitpod-cli/cmd/env.go index 50fab6d6c779cc..410d75d4bd72a9 100644 --- a/components/gitpod-cli/cmd/env.go +++ b/components/gitpod-cli/cmd/env.go @@ -7,11 +7,13 @@ package cmd import ( "context" "fmt" + "io" "os" "strings" "sync" "time" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/xerrors" "google.golang.org/grpc" @@ -49,6 +51,13 @@ delete environment variables with a repository pattern of */foo, foo/* or */*. `, Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(io.Discard) + f, err := os.OpenFile(os.TempDir()+"/gp-env.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err == nil { + defer f.Close() + log.SetOutput(f) + } + if len(args) > 0 { if unsetEnvs { deleteEnvs(args) @@ -103,7 +112,11 @@ func connectToServer(ctx context.Context) (*connectToServerResult, error) { if err != nil { return nil, xerrors.Errorf("failed getting token from supervisor: %w", err) } - client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{Token: clientToken.Token, Context: ctx}) + client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{ + Token: clientToken.Token, + Context: ctx, + Log: log.NewEntry(log.StandardLogger()), + }) if err != nil { return nil, xerrors.Errorf("failed connecting to server: %w", err) } diff --git a/components/gitpod-cli/cmd/git-token-validator.go b/components/gitpod-cli/cmd/git-token-validator.go index d3afa5d6007511..b9e9333ef75c73 100644 --- a/components/gitpod-cli/cmd/git-token-validator.go +++ b/components/gitpod-cli/cmd/git-token-validator.go @@ -7,6 +7,7 @@ package cmd import ( "context" "fmt" + "io" "os" "strings" "time" @@ -35,6 +36,13 @@ var gitTokenValidator = &cobra.Command{ Args: cobra.ExactArgs(0), Hidden: true, Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(io.Discard) + f, err := os.OpenFile(os.TempDir()+"/gitpod-git-credential-helper.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err == nil { + defer f.Close() + log.SetOutput(f) + } + log.Infof("gp git-token-validator") ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) @@ -61,7 +69,11 @@ var gitTokenValidator = &cobra.Command{ if err != nil { log.WithError(err).Fatal("error getting token from supervisor") } - client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{Token: clientToken.Token, Context: ctx}) + client, err := serverapi.ConnectToServer(wsinfo.GitpodApi.Endpoint, serverapi.ConnectToServerOpts{ + Token: clientToken.Token, + Context: ctx, + Log: log.NewEntry(log.StandardLogger()), + }) if err != nil { log.WithError(err).Fatal("error connecting to server") } diff --git a/components/gitpod-cli/go.mod b/components/gitpod-cli/go.mod index 7670e2ee61ede3..93233c615fe5b3 100644 --- a/components/gitpod-cli/go.mod +++ b/components/gitpod-cli/go.mod @@ -22,56 +22,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -replace github.com/gitpod-io/gitpod/common-go => ../common-go // leeway - replace github.com/gitpod-io/gitpod/gitpod-protocol => ../gitpod-protocol/go // leeway replace github.com/gitpod-io/gitpod/supervisor/api => ../supervisor-api/go // leeway - -replace k8s.io/api => k8s.io/api v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apimachinery => k8s.io/apimachinery v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apiserver => k8s.io/apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/client-go => k8s.io/client-go v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/code-generator => k8s.io/code-generator v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/component-base => k8s.io/component-base v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cri-api => k8s.io/cri-api v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kubelet => k8s.io/kubelet v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/metrics => k8s.io/metrics v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/component-helpers => k8s.io/component-helpers v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/controller-manager => k8s.io/controller-manager v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kubectl => k8s.io/kubectl v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/mount-utils => k8s.io/mount-utils v0.20.4 // leeway indirect from components/common-go:lib diff --git a/components/gitpod-cli/go.sum b/components/gitpod-cli/go.sum index 46951ac2b1286e..cc0eb641132436 100644 --- a/components/gitpod-cli/go.sum +++ b/components/gitpod-cli/go.sum @@ -378,6 +378,7 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -437,6 +438,7 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/components/gitpod-protocol/go/BUILD.yaml b/components/gitpod-protocol/go/BUILD.yaml index 76718ebe93d402..3edc9805815635 100644 --- a/components/gitpod-protocol/go/BUILD.yaml +++ b/components/gitpod-protocol/go/BUILD.yaml @@ -5,8 +5,6 @@ packages: - "**/*.go" - "go.mod" - "go.sum" - deps: - - components/common-go:lib env: - CGO_ENABLED=0 - GOOS=linux diff --git a/components/gitpod-protocol/go/gitpod-config.go b/components/gitpod-protocol/go/gitpod-config.go index 250c25b203502e..5eb3abe6965ed4 100644 --- a/components/gitpod-protocol/go/gitpod-config.go +++ b/components/gitpod-protocol/go/gitpod-config.go @@ -11,9 +11,8 @@ import ( "time" "github.com/fsnotify/fsnotify" + "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" - - "github.com/gitpod-io/gitpod/common-go/log" ) // ConfigInterface provides access to the gitpod config file. @@ -32,6 +31,8 @@ type ConfigService struct { stop context.CancelFunc mu sync.Mutex pollTimer *time.Timer + + log *logrus.Entry } type configListener struct { @@ -40,11 +41,12 @@ type configListener struct { } // NewConfigService creates a new instance of ConfigService -func NewConfigService(configLocation string, locationReady <-chan struct{}) *ConfigService { +func NewConfigService(configLocation string, locationReady <-chan struct{}, log *logrus.Entry) *ConfigService { return &ConfigService{ location: configLocation, locationReady: locationReady, listeners: make(map[configListener]struct{}), + log: log, } } @@ -98,7 +100,7 @@ func (service *ConfigService) start() error { return nil } - log.WithField("location", service.location).Info("Starting watching...") + service.log.WithField("location", service.location).Info("Starting watching...") context, stop := context.WithCancel(context.Background()) service.stop = stop service.mu.Unlock() @@ -119,11 +121,11 @@ func (service *ConfigService) watch(ctx context.Context) (err error) { watcher, err := fsnotify.NewWatcher() defer func() { if err != nil { - log.WithField("location", service.location).WithError(err).Error("Failed to start watching...") + service.log.WithField("location", service.location).WithError(err).Error("Failed to start watching...") return } - log.WithField("location", service.location).Info("Started watching") + service.log.WithField("location", service.location).Info("Started watching") }() if err != nil { return err @@ -136,7 +138,7 @@ func (service *ConfigService) watch(ctx context.Context) (err error) { } go func() { - defer log.WithField("location", service.location).Info("Stopped watching") + defer service.log.WithField("location", service.location).Info("Stopped watching") defer watcher.Close() polling := make(chan struct{}, 1) diff --git a/components/gitpod-protocol/go/gitpod-config_test.go b/components/gitpod-protocol/go/gitpod-config_test.go index 6a44a86b6ca8c2..d77b56c6d10089 100644 --- a/components/gitpod-protocol/go/gitpod-config_test.go +++ b/components/gitpod-protocol/go/gitpod-config_test.go @@ -10,8 +10,11 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/sirupsen/logrus" ) +var log = logrus.NewEntry(logrus.StandardLogger()) + func TestGitpodConfig(t *testing.T) { tests := []struct { Desc string @@ -81,7 +84,7 @@ vscode: defer os.RemoveAll(tempDir) locationReady := make(chan struct{}) - configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady) + configService := NewConfigService(tempDir+"/.gitpod.yml", locationReady, log) context, cancel := context.WithCancel(context.Background()) defer cancel() close(locationReady) diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index 178782fc09c2d7..639feedfcd29a4 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -19,7 +19,7 @@ import ( "github.com/sourcegraph/jsonrpc2" "golang.org/x/xerrors" - "github.com/gitpod-io/gitpod/common-go/log" + "github.com/sirupsen/logrus" ) // APIInterface wraps the @@ -220,6 +220,7 @@ var errNotConnected = errors.New("not connected to Gitpod server") type ConnectToServerOpts struct { Context context.Context Token string + Log *logrus.Entry } // ConnectToServer establishes a new websocket connection to the server @@ -246,17 +247,19 @@ func ConnectToServer(endpoint string, opts ConnectToServerOpts) (*APIoverJSONRPC if opts.Token != "" { reqHeader.Set("Authorization", "Bearer "+opts.Token) } - ws := NewReconnectingWebsocket(endpoint, reqHeader) + ws := NewReconnectingWebsocket(endpoint, reqHeader, opts.Log) go ws.Dial() var res APIoverJSONRPC + res.log = opts.Log res.C = jsonrpc2.NewConn(opts.Context, ws, jsonrpc2.HandlerWithError(res.handler)) return &res, nil } // APIoverJSONRPC makes JSON RPC calls to the Gitpod server is the APIoverJSONRPC message type type APIoverJSONRPC struct { - C jsonrpc2.JSONRPC2 + C jsonrpc2.JSONRPC2 + log *logrus.Entry mu sync.RWMutex subs map[string]map[chan *WorkspaceInstance]struct{} @@ -318,7 +321,7 @@ func (gp *APIoverJSONRPC) handler(ctx context.Context, conn *jsonrpc2.Conn, req var instance WorkspaceInstance err = json.Unmarshal(*req.Params, &instance) if err != nil { - log.WithError(err).WithField("raw", string(*req.Params)).Error("cannot unmarshal instance update") + gp.log.WithError(err).WithField("raw", string(*req.Params)).Error("cannot unmarshal instance update") return } diff --git a/components/gitpod-protocol/go/go.mod b/components/gitpod-protocol/go/go.mod index b88700d5d7fe7c..b1c28c834749ca 100644 --- a/components/gitpod-protocol/go/go.mod +++ b/components/gitpod-protocol/go/go.mod @@ -4,61 +4,11 @@ go 1.16 require ( github.com/fsnotify/fsnotify v1.4.9 - github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000 github.com/golang/mock v1.4.4 github.com/google/go-cmp v0.5.2 github.com/gorilla/websocket v1.4.2 + github.com/sirupsen/logrus v1.7.0 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/yaml.v2 v2.4.0 ) - -replace github.com/gitpod-io/gitpod/common-go => ../../common-go // leeway - -replace k8s.io/api => k8s.io/api v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apimachinery => k8s.io/apimachinery v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/apiserver => k8s.io/apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/client-go => k8s.io/client-go v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/code-generator => k8s.io/code-generator v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/component-base => k8s.io/component-base v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/cri-api => k8s.io/cri-api v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kubelet => k8s.io/kubelet v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/metrics => k8s.io/metrics v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/component-helpers => k8s.io/component-helpers v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/controller-manager => k8s.io/controller-manager v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/kubectl => k8s.io/kubectl v0.20.4 // leeway indirect from components/common-go:lib - -replace k8s.io/mount-utils => k8s.io/mount-utils v0.20.4 // leeway indirect from components/common-go:lib diff --git a/components/gitpod-protocol/go/go.sum b/components/gitpod-protocol/go/go.sum index 0cd5d42404dcde..80f376a36926ef 100644 --- a/components/gitpod-protocol/go/go.sum +++ b/components/gitpod-protocol/go/go.sum @@ -178,6 +178,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/components/gitpod-protocol/go/reconnecting-ws.go b/components/gitpod-protocol/go/reconnecting-ws.go index 6b81eb871aafe5..52efce64c457c5 100644 --- a/components/gitpod-protocol/go/reconnecting-ws.go +++ b/components/gitpod-protocol/go/reconnecting-ws.go @@ -10,8 +10,7 @@ import ( "time" "github.com/gorilla/websocket" - - "github.com/gitpod-io/gitpod/common-go/log" + "github.com/sirupsen/logrus" ) // The ReconnectingWebsocket represents a Reconnecting WebSocket connection. @@ -27,10 +26,12 @@ type ReconnectingWebsocket struct { closedCh chan struct{} connCh chan chan *websocket.Conn errCh chan error + + log *logrus.Entry } // NewReconnectingWebsocket creates a new instance of ReconnectingWebsocket -func NewReconnectingWebsocket(url string, reqHeader http.Header) *ReconnectingWebsocket { +func NewReconnectingWebsocket(url string, reqHeader http.Header, log *logrus.Entry) *ReconnectingWebsocket { return &ReconnectingWebsocket{ url: url, reqHeader: reqHeader, @@ -41,6 +42,7 @@ func NewReconnectingWebsocket(url string, reqHeader http.Header) *ReconnectingWe connCh: make(chan chan *websocket.Conn), closedCh: make(chan struct{}), errCh: make(chan error), + log: log, } } @@ -109,7 +111,7 @@ func (rc *ReconnectingWebsocket) Dial() { if conn == nil { return } - log.WithField("url", rc.url).Warn("connection is permanently closed") + rc.log.WithField("url", rc.url).Warn("connection is permanently closed") conn.Close() }() @@ -122,7 +124,7 @@ func (rc *ReconnectingWebsocket) Dial() { case connCh := <-rc.connCh: connCh <- conn case err := <-rc.errCh: - log.WithError(err).WithField("url", rc.url).Warn("connection has been closed, reconnecting...") + rc.log.WithError(err).WithField("url", rc.url).Warn("connection has been closed, reconnecting...") conn.Close() time.Sleep(1 * time.Second) @@ -137,12 +139,12 @@ func (rc *ReconnectingWebsocket) connect() *websocket.Conn { dialer := websocket.Dialer{HandshakeTimeout: rc.handshakeTimeout} conn, _, err := dialer.Dial(rc.url, rc.reqHeader) if err == nil { - log.WithField("url", rc.url).Info("connection was successfully established") + rc.log.WithField("url", rc.url).Info("connection was successfully established") return conn } - log.WithError(err).WithField("url", rc.url).Errorf("failed to connect, trying again in %d seconds...", uint32(delay.Seconds())) + rc.log.WithError(err).WithField("url", rc.url).Errorf("failed to connect, trying again in %d seconds...", uint32(delay.Seconds())) select { case <-rc.closedCh: return nil diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 3109593c0f591d..dde4ef46a217b4 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -1318,7 +1318,7 @@ export class GitpodServerImpl