From 0ef797ffe0c77c298d11ad0a293b723f1c85cfea Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Wed, 2 Mar 2022 10:46:36 +0000 Subject: [PATCH] fix #6508: Allow users to define Jetbrains plugins to be installed on a given project --- .../gitpod-protocol/data/gitpod-schema.json | 15 ++ .../gitpod-protocol/go/gitpod-config-types.go | 11 ++ components/ide/jetbrains/image/startup.sh | 12 +- .../ide/jetbrains/image/status/BUILD.yaml | 1 + components/ide/jetbrains/image/status/go.mod | 20 ++- components/ide/jetbrains/image/status/go.sum | 21 +++ components/ide/jetbrains/image/status/main.go | 163 +++++++++++++++--- 7 files changed, 203 insertions(+), 40 deletions(-) diff --git a/components/gitpod-protocol/data/gitpod-schema.json b/components/gitpod-protocol/data/gitpod-schema.json index b81233a1397ed4..c642d062147a20 100644 --- a/components/gitpod-protocol/data/gitpod-schema.json +++ b/components/gitpod-protocol/data/gitpod-schema.json @@ -222,6 +222,21 @@ } } }, + "jetbrains": { + "type": "object", + "description": "Configure JetBrains integration", + "deprecationMessage": "The 'jetbrains' property is experimental.", + "additionalProperties": false, + "properties": { + "plugins": { + "type": "array", + "description": "List of plugins which should be installed for users of this workspace. From the JetBrains Marketplace page, find a page of the required plugin, select 'Versions' tab, click any version to copy pluginId (short name such as org.rust.lang) of the plugin you want to install.", + "items": { + "type": "string" + } + } + } + }, "experimentalNetwork": { "type": "boolean", "description": "Experimental network configuration in workspaces (deprecated). Enabled by default" diff --git a/components/gitpod-protocol/go/gitpod-config-types.go b/components/gitpod-protocol/go/gitpod-config-types.go index 3a4a9fc3bc8801..9c5a0b775564c2 100644 --- a/components/gitpod-protocol/go/gitpod-config-types.go +++ b/components/gitpod-protocol/go/gitpod-config-types.go @@ -52,6 +52,9 @@ type GitpodConfig struct { // Configure VS Code integration Vscode *Vscode `yaml:"vscode,omitempty"` + // Configure JetBrains integration + JetBrains *JetBrains `yaml:"jetbrains,omitempty"` + // Path to where the IDE's workspace should be opened. WorkspaceLocation string `yaml:"workspaceLocation,omitempty"` } @@ -142,6 +145,14 @@ type Vscode struct { Extensions []string `yaml:"extensions,omitempty"` } +// Configure JetBrains integration +type JetBrains struct { + + // List of plugins which should be installed for users of this workspace. From the JetBrains Marketplace page, find a page of the required plugin, select 'Versions' tab, click any version to copy pluginId (short name such as org.rust.lang) of the plugin you want to install. + Plugins []string `yaml:"plugins,omitempty"` + +} + func (strct *Github) MarshalJSON() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) buf.WriteString("{") diff --git a/components/ide/jetbrains/image/startup.sh b/components/ide/jetbrains/image/startup.sh index 007cf2b7942adc..429a529224f7eb 100755 --- a/components/ide/jetbrains/image/startup.sh +++ b/components/ide/jetbrains/image/startup.sh @@ -8,14 +8,6 @@ set -euo pipefail # kill background jobs when the script exits trap "jobs -p | xargs -r kill" SIGINT SIGTERM EXIT -/ide-desktop/status "$1" "$2" & - -echo "Desktop IDE: Waiting for the content initializer ..." -until curl -sS "$SUPERVISOR_ADDR"/_supervisor/v1/status/content/wait/true | grep '"available":true' > /dev/null; do - sleep 1 -done -echo "Desktop IDE: Content available." - # instead put them into /ide-desktop/backend/bin/idea64.vmoptions # otherwise JB will complain to a user on each startup # by default remote dev already set -Xmx2048m, see /ide-desktop/backend/plugins/remote-dev-server/bin/launcher.sh @@ -33,6 +25,4 @@ export IJ_HOST_SYSTEM_BASE_DIR=/workspace/.cache/JetBrains # Enable host status endpoint export CWM_HOST_STATUS_OVER_HTTP_TOKEN=gitpod -/ide-desktop/backend/bin/remote-dev-server.sh run "$GITPOD_REPO_ROOT" - -echo "Desktop IDE startup script exited" +/ide-desktop/status "$1" "$2" \ No newline at end of file diff --git a/components/ide/jetbrains/image/status/BUILD.yaml b/components/ide/jetbrains/image/status/BUILD.yaml index d55c21cf29bca6..ccd914013f7dbd 100644 --- a/components/ide/jetbrains/image/status/BUILD.yaml +++ b/components/ide/jetbrains/image/status/BUILD.yaml @@ -12,3 +12,4 @@ packages: - components/supervisor-api/go:lib config: packaging: app + buildCommand: ["go", "build", "-trimpath", "-ldflags", "-buildid= -w -s -X 'github.com/gitpod-io/gitpod/jetbrains/status.Version=commit-${__git_commit}'"] diff --git a/components/ide/jetbrains/image/status/go.mod b/components/ide/jetbrains/image/status/go.mod index 5757150f3ff2a8..ff324e7156888a 100644 --- a/components/ide/jetbrains/image/status/go.mod +++ b/components/ide/jetbrains/image/status/go.mod @@ -2,18 +2,32 @@ module github.com/gitpod-io/gitpod/jetbrains/status go 1.17 +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 require github.com/gitpod-io/gitpod/supervisor/api v0.0.0-00010101000000-000000000000 require ( + github.com/golang/mock v1.6.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 // indirect +) + +require ( + github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000 + github.com/gitpod-io/gitpod/gitpod-protocol v0.0.0-00010101000000-000000000000 github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect - golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect google.golang.org/grpc v1.39.1 // indirect google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/components/ide/jetbrains/image/status/go.sum b/components/ide/jetbrains/image/status/go.sum index 96df188489ee32..7c04d26b2ef25f 100644 --- a/components/ide/jetbrains/image/status/go.sum +++ b/components/ide/jetbrains/image/status/go.sum @@ -43,6 +43,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -66,6 +67,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -107,6 +110,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE= @@ -123,7 +129,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= +github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -201,6 +212,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -226,6 +239,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -245,6 +259,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -253,6 +269,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -296,6 +314,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -392,6 +411,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/components/ide/jetbrains/image/status/main.go b/components/ide/jetbrains/image/status/main.go index 0f99933c2af2be..4c1d1096c9950d 100644 --- a/components/ide/jetbrains/image/status/main.go +++ b/components/ide/jetbrains/image/status/main.go @@ -7,26 +7,43 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "io/ioutil" - "log" "net/http" "net/url" "os" + "os/exec" + "path/filepath" "time" - supervisor "github.com/gitpod-io/gitpod/supervisor/api" "golang.org/x/xerrors" "google.golang.org/grpc" + yaml "gopkg.in/yaml.v2" + + "github.com/gitpod-io/gitpod/common-go/log" + gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" + supervisor "github.com/gitpod-io/gitpod/supervisor/api" ) const defaultBackendPort = "63342" -// proxy for the Code With Me status endpoints that transforms it into the supervisor status format. +var ( + // ServiceName is the name we use for tracing/logging. + ServiceName = "jetbrains-startup" + // Version of this service - set during build. + Version = "" +) + +const RemoteDevServer = "/ide-desktop/backend/bin/remote-dev-server.sh" + +// JB startup entrypoint func main() { + log.Init(ServiceName, Version, true, false) + startTime := time.Now() + if len(os.Args) < 3 { - fmt.Printf("Usage: %s []\n", os.Args[0]) - os.Exit(1) + log.Fatalf("Usage: %s []\n", os.Args[0]) } port := os.Args[1] kind := os.Args[2] @@ -35,7 +52,18 @@ func main() { label = os.Args[3] } - errlog := log.New(os.Stderr, "JetBrains IDE status: ", log.LstdFlags) + // wait until content ready + contentStatus, wsInfo, err := resolveWorkspaceInfo(context.Background()) + if err != nil || wsInfo == nil || contentStatus == nil || !contentStatus.Available { + log.WithError(err).WithField("wsInfo", wsInfo).WithField("cstate", contentStatus).Error("resolve workspace info failed") + return + } + log.WithField("cost", time.Now().Local().Sub(startTime).Milliseconds()).Info("content available") + + installPlugins(wsInfo) + log.WithField("cost", time.Now().Local().Sub(startTime).Milliseconds()).Info("plugins installation") + + go run(wsInfo, startTime) http.HandleFunc("/joinLink", func(w http.ResponseWriter, r *http.Request) { backendPort := r.URL.Query().Get("backendPort") @@ -44,7 +72,7 @@ func main() { } jsonLink, err := resolveJsonLink(backendPort) if err != nil { - errlog.Printf("cannot resolve join link: %v\n", err) + log.WithError(err).Error("cannot resolve join link") http.Error(w, err.Error(), http.StatusServiceUnavailable) return } @@ -55,9 +83,9 @@ func main() { if backendPort == "" { backendPort = defaultBackendPort } - jsonLink, err := resolveGatewayLink(backendPort) + jsonLink, err := resolveGatewayLink(backendPort, wsInfo) if err != nil { - errlog.Printf("cannot resolve gateway link: %v\n", err) + log.WithError(err).Error("cannot resolve gateway link") http.Error(w, err.Error(), http.StatusServiceUnavailable) return } @@ -68,9 +96,9 @@ func main() { if backendPort == "" { backendPort = defaultBackendPort } - gatewayLink, err := resolveGatewayLink(backendPort) + gatewayLink, err := resolveGatewayLink(backendPort, wsInfo) if err != nil { - errlog.Printf("cannot resolve gateway link: %v\n", err) + log.WithError(err).Error("cannot resolve gateway link") http.Error(w, err.Error(), http.StatusServiceUnavailable) return } @@ -96,11 +124,7 @@ type Response struct { Projects []Projects `json:"projects"` } -func resolveGatewayLink(backendPort string) (string, error) { - wsInfo, err := resolveWorkspaceInfo(context.Background()) - if err != nil { - return "", err - } +func resolveGatewayLink(backendPort string, wsInfo *supervisor.WorkspaceInfoResponse) (string, error) { gitpodUrl, err := url.Parse(wsInfo.GitpodHost) if err != nil { return "", err @@ -141,19 +165,106 @@ func resolveJsonLink(backendPort string) (string, error) { return jsonResp.Projects[0].JoinLink, nil } -func resolveWorkspaceInfo(ctx context.Context) (*supervisor.WorkspaceInfoResponse, error) { - supervisorAddr := os.Getenv("SUPERVISOR_ADDR") - if supervisorAddr == "" { - supervisorAddr = "localhost:22999" +func resolveWorkspaceInfo(ctx context.Context) (*supervisor.ContentStatusResponse, *supervisor.WorkspaceInfoResponse, error) { + resolve := func(ctx context.Context) (contentStatus *supervisor.ContentStatusResponse, wsInfo *supervisor.WorkspaceInfoResponse, err error) { + supervisorAddr := os.Getenv("SUPERVISOR_ADDR") + if supervisorAddr == "" { + supervisorAddr = "localhost:22999" + } + supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure()) + if err != nil { + err = errors.New("dial supervisor failed: " + err.Error()) + return + } + defer supervisorConn.Close() + if wsInfo, err = supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{}); err != nil { + err = errors.New("get workspace info failed: " + err.Error()) + return + } + contentStatus, err = supervisor.NewStatusServiceClient(supervisorConn).ContentStatus(ctx, &supervisor.ContentStatusRequest{Wait: true}) + if err != nil { + err = errors.New("get content available failed: " + err.Error()) + } + return + } + // try resolve workspace info 10 times + for attempt := 0; attempt < 10; attempt++ { + if contentStatus, wsInfo, err := resolve(ctx); err != nil { + log.WithError(err).Error("resolve workspace info failed") + time.Sleep(1 * time.Second) + } else { + return contentStatus, wsInfo, err + } + } + return nil, nil, errors.New("failed with attempt 10 times") +} + +/** +/ide-desktop/backend/bin/remote-dev-server.sh run "$GITPOD_REPO_ROOT" +*/ +func run(wsInfo *supervisor.WorkspaceInfoResponse) { + + args = append(args, os.Args[1:]...) + args = append(args, "--do-not-sync") + log.WithField("cost", time.Now().Local().Sub(startTime).Milliseconds()).Info("starting server") + cmd := exec.Command(Code, args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + log.WithError(err).Error("install ext and start code server failed") } - supervisorConn, err := grpc.Dial(supervisorAddr, grpc.WithInsecure()) + for _, plugin := range plugins { + extPathArgs = append(extPathArgs, os.Args[1:]...) + extPathArgs = append(extPathArgs, "--do-not-sync") + log.Info("installing extensions by path") + + } +} + +func installPlugins(wsInfo *supervisor.WorkspaceInfoResponse) { + plugins, err := getPlugins(wsInfo.GetCheckoutLocation()) if err != nil { - return nil, xerrors.Errorf("failed connecting to supervisor: %w", err) + log.WithError(err).Error("get plugins failed") + return + } + if len(plugins) <= 0 { + return + } + var args []string + args = append(args, "installPlugins") + args = append(args, wsInfo.GetCheckoutLocation()) + args = append(args, plugins...) + cmd := exec.Command(RemoteDevServer, args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + log.WithError(err).Error("installing plugins failed") + } +} + +func getPlugins(repoRoot string) (plugins []string, err error) { + if repoRoot == "" { + err = errors.New("repoRoot is empty") + return } - defer supervisorConn.Close() - wsinfo, err := supervisor.NewInfoServiceClient(supervisorConn).WorkspaceInfo(ctx, &supervisor.WorkspaceInfoRequest{}) + data, err := os.ReadFile(filepath.Join(repoRoot, ".gitpod.yml")) if err != nil { - return nil, xerrors.Errorf("failed getting workspace info from supervisor: %w", err) + // .gitpod.yml not exist is ok + if errors.Is(err, os.ErrNotExist) { + err = nil + return + } + err = errors.New("read .gitpod.yml file failed: " + err.Error()) + return + } + var config *gitpod.GitpodConfig + if err = yaml.Unmarshal(data, &config); err != nil { + err = errors.New("unmarshal .gitpod.yml file failed" + err.Error()) + return + } + if config == nil || config.JetBrains == nil { + err = errors.New("config.vscode field not exists") + return } - return wsinfo, nil + return config.JetBrains.Plugins, nil }