Skip to content
This repository has been archived by the owner on Jan 20, 2023. It is now read-only.

Add ability to configure several extensions in a single VS Code plugin #36

Merged
merged 4 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 50 additions & 27 deletions brokers/theia/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,47 @@ import (
"github.com/eclipse/che-plugin-broker/model"
)

var re = regexp.MustCompile(`[^a-zA-Z_0-9]+`)

// GenerateSidecarTooling generates Theia plugin runner sidecar and adds a single plugin to it
// Deprecated: use GenerateSidecar + AddExtension instead
func GenerateSidecarTooling(image string, pj model.PackageJSON, rand common.Random) *model.ToolingConf {
tooling := &model.ToolingConf{
Containers: []model.Container{*containerConfig(image, rand)},
}
addPortToTooling(tooling, pj, rand)
tooling := &model.ToolingConf{}
tooling.Containers = append(tooling.Containers, containerConfig(image, rand))
endpoint := generateTheiaSidecarEndpoint(rand)
setEndpoint(tooling, endpoint)
AddExtension(tooling, pj)

return tooling
}

func containerConfig(image string, rand common.Random) *model.Container {
c := model.Container{
// GenerateSidecar generates sidecar tooling configuration.
// Plugins can be added to the configuration using function AddExtension
func GenerateSidecar(image string, rand common.Random) *model.ToolingConf {
tooling := &model.ToolingConf{}
tooling.Containers = append(tooling.Containers, containerConfig(image, rand))
endpoint := generateTheiaSidecarEndpoint(rand)
setEndpoint(tooling, endpoint)

return tooling
}

// AddExtension adds to tooling an environment variable needed for extension to be consumed by Theia.
// Environment variable uses extension name and publisher specified in PackageJSON.
// Extension publisher and plugin name taken by retrieving info from package.json and replacing all
// chars matching [^a-z_0-9]+ with a dash character
func AddExtension(toolingConf *model.ToolingConf, pj model.PackageJSON) {
sidecarEndpoint := toolingConf.Endpoints[0]
prettyID := re.ReplaceAllString(pj.Publisher+"_"+pj.Name, `_`)
sidecarTheiaEnvVarName := "THEIA_PLUGIN_REMOTE_ENDPOINT_" + prettyID
sidecarTheiaEnvVarValue := "ws://" + sidecarEndpoint.Name + ":" + strconv.Itoa(sidecarEndpoint.TargetPort)

toolingConf.WorkspaceEnv = append(toolingConf.WorkspaceEnv, model.EnvVar{Name: sidecarTheiaEnvVarName, Value: sidecarTheiaEnvVarValue})
}

// Generates sidecar container config with needed image and volumes
func containerConfig(image string, rand common.Random) model.Container {
return model.Container{
Name: "pluginsidecar" + rand.String(6),
Image: image,
Volumes: []model.Volume{
Expand All @@ -44,31 +74,24 @@ func containerConfig(image string, rand common.Random) *model.Container {
},
},
}
return &c
}

// addPortToTooling adds to tooling everything needed to start Theia remote plugin:
// - Random port to the container (one and only)
// - Endpoint matching the port
// - Environment variable THEIA_PLUGIN_ENDPOINT_PORT to the container with the port as value
// - Environment variable that start from THEIA_PLUGIN_REMOTE_ENDPOINT_ and ends with
// plugin publisher and plugin name taken from packageJson and replacing all
// chars matching [^a-z_0-9]+ with a dash character
func addPortToTooling(toolingConf *model.ToolingConf, pj model.PackageJSON, rand common.Random) {
port := rand.IntFromRange(4000, 10000)
sPort := strconv.Itoa(port)
// Generates random non-publicly exposed endpoint for sidecar to allow Theia connecting to it
func generateTheiaSidecarEndpoint(rand common.Random) model.Endpoint {
endpointName := rand.String(10)
var re = regexp.MustCompile(`[^a-zA-Z_0-9]+`)
prettyID := re.ReplaceAllString(pj.Publisher+"_"+pj.Name, `_`)
sidecarTheiaEnvVarName := "THEIA_PLUGIN_REMOTE_ENDPOINT_" + prettyID
sidecarTheiaEnvVarValue := "ws://" + endpointName + ":" + sPort

toolingConf.Containers[0].Ports = append(toolingConf.Containers[0].Ports, model.ExposedPort{ExposedPort: port})
toolingConf.Endpoints = append(toolingConf.Endpoints, model.Endpoint{
port := rand.IntFromRange(4000, 10000)
return model.Endpoint{
Name: endpointName,
Public: false,
TargetPort: port,
})
toolingConf.Containers[0].Env = append(toolingConf.Containers[0].Env, model.EnvVar{Name: "THEIA_PLUGIN_ENDPOINT_PORT", Value: sPort})
toolingConf.WorkspaceEnv = append(toolingConf.WorkspaceEnv, model.EnvVar{Name: sidecarTheiaEnvVarName, Value: sidecarTheiaEnvVarValue})
}
}

// Sets sidecar endpoint into tooling and adds needed port exposure and environment variable to the sidecar container
// to run plugin runner Theia slave on a port specified in provided endpoint
func setEndpoint(toolingConf *model.ToolingConf, endpoint model.Endpoint) {
port := endpoint.TargetPort
toolingConf.Containers[0].Ports = append(toolingConf.Containers[0].Ports, model.ExposedPort{ExposedPort: port})
toolingConf.Endpoints = append(toolingConf.Endpoints, endpoint)
toolingConf.Containers[0].Env = append(toolingConf.Containers[0].Env, model.EnvVar{Name: "THEIA_PLUGIN_ENDPOINT_PORT", Value: strconv.Itoa(port)})
}
181 changes: 181 additions & 0 deletions brokers/theia/sidecar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//
// Copyright (c) 2019 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//

package theia

import (
"strconv"
"testing"

"github.com/eclipse/che-plugin-broker/common/mocks"
"github.com/eclipse/che-plugin-broker/model"
"github.com/stretchr/testify/assert"
)

func TestGenerateSidecar(t *testing.T) {
testImage := "test/test:latest"
random6 := "123456"
random10 := "1234567890"
randomInt := 8889
rand := &mocks.Random{}
rand.On("String", 6).Return(random6)
rand.On("String", 10).Return(random10)
rand.On("IntFromRange", 4000, 10000).Return(randomInt)

expected := generateTestToolingWithVars(testImage, random6, randomInt, random10)

actual := GenerateSidecar(testImage, rand)

assert.Equal(t, expected, actual)
}

func TestAddExtension(t *testing.T) {
type args struct {
toolingConf *model.ToolingConf
pj model.PackageJSON
}
tests := []struct {
name string
args args
want *model.ToolingConf
}{
{
name: "Test adding extension with package.json data with a-zA-Z0-9_ symbols",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("pluginName8", "publisherName1_0_"),
},
want: generateTestToolingWithExtension("pluginName8", "publisherName1_0_"),
},
{
name: "Test adding extension with package.json data with # symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("plugin#Name8", "publisherName1_0_"),
},
want: generateTestToolingWithExtension("plugin_Name8", "publisherName1_0_"),
},
{
name: "Test adding extension with package.json data with @ symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("plu@ginName8", "publisherName1_0_"),
},
want: generateTestToolingWithExtension("plu_ginName8", "publisherName1_0_"),
},
{
name: "Test adding extension with package.json data with : symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("pluginName8", "publisherName:1_0_"),
},
want: generateTestToolingWithExtension("pluginName8", "publisherName_1_0_"),
},
{
name: "Test adding extension with package.json data with ? symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("pluginName8", "publisherName?1_0_"),
},
want: generateTestToolingWithExtension("pluginName8", "publisherName_1_0_"),
},
{
name: "Test adding extension with package.json data with - symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("plugin-Name-8", "publisherName1_0_"),
},
want: generateTestToolingWithExtension("plugin_Name_8", "publisherName1_0_"),
},
{
name: "Test adding extension with package.json data with ! symbol",
args: args{
toolingConf: generateTestTooling(),
pj: generatePackageJSON("plugin!Name8", "publisherName1_0_!"),
},
want: generateTestToolingWithExtension("plugin_Name8", "publisherName1_0__"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AddExtension(tt.args.toolingConf, tt.args.pj)
})
assert.Equal(t, tt.args.toolingConf, tt.want)
}
}

func generatePackageJSON(name string, publisher string) model.PackageJSON {
return model.PackageJSON{
Name: name,
Publisher: publisher,
}
}

func generateTestToolingWithExtension(extName string, extPublisher string) *model.ToolingConf {
testImage := "test/test:latest"
random6 := "123456"
random10 := "1234567890"
randomInt := 8889
tooling := generateTestToolingWithVars(testImage, random6, randomInt, random10)
tooling.WorkspaceEnv = append(tooling.WorkspaceEnv, model.EnvVar{
Name: "THEIA_PLUGIN_REMOTE_ENDPOINT_" + extPublisher + "_" + extName,
Value: "ws://" + random10 + ":" + strconv.Itoa(randomInt),
})
return tooling
}

func generateTestTooling() *model.ToolingConf {
testImage := "test/test:latest"
random6 := "123456"
random10 := "1234567890"
randomInt := 8889
return generateTestToolingWithVars(testImage, random6, randomInt, random10)
}

func generateTestToolingWithVars(testImage string, nameSuffix string, port int, endpointName string) *model.ToolingConf {
return &model.ToolingConf{
Containers: []model.Container{
{
Name: "pluginsidecar" + nameSuffix,
Image: testImage,
Volumes: []model.Volume{
{
Name: "projects",
MountPath: "/projects",
},
{
Name: "plugins",
MountPath: "/plugins",
},
},
Ports: []model.ExposedPort{
{
ExposedPort: port,
},
},
Env: []model.EnvVar{
{
Name: "THEIA_PLUGIN_ENDPOINT_PORT",
Value: strconv.Itoa(port),
},
},
},
},
Endpoints: []model.Endpoint{
{
Name: endpointName,
Public: false,
TargetPort: port,
},
},
}
}
Loading