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

Implement remote plugin runtime injection #71

Closed
wants to merge 1 commit into from
Closed
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
16 changes: 9 additions & 7 deletions brokers/unified/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,14 @@ func resolveRelativeExtensionPaths(metas []model.PluginMeta, defaultRegistry str
// to be passed back to Che.
func ConvertMetaToPlugin(meta model.PluginMeta) model.ChePlugin {
return model.ChePlugin{
ID: meta.ID,
Name: meta.Name,
Publisher: meta.Publisher,
Version: meta.Version,
Containers: meta.Spec.Containers,
Endpoints: meta.Spec.Endpoints,
WorkspaceEnv: meta.Spec.WorkspaceEnv,
ID: meta.ID,
Name: meta.Name,
Type: meta.Type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I assume adding the type here is only a way to forward the type (defined in the meta.yaml) to the Che server in order to use it in the Che server Kubernetes infrastructure ?

Publisher: meta.Publisher,
Version: meta.Version,
Containers: meta.Spec.Containers,
Endpoints: meta.Spec.Endpoints,
WorkspaceEnv: meta.Spec.WorkspaceEnv,
PluginPatcher: meta.Spec.PluginPatcher,
}
}
28 changes: 23 additions & 5 deletions brokers/unified/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func TestBroker_processPlugins(t *testing.T) {
metas: []model.PluginMeta{createChePluginMeta("id1"), createCheEditorMeta("id2")},
},
want: want{
commonPlugins: []model.ChePlugin{createChePlugin("id1"), createChePlugin("id2")},
commonPlugins: []model.ChePlugin{createChePlugin("id1"), createCheEditorPlugin("id2")},
},
},
{
Expand Down Expand Up @@ -368,6 +368,7 @@ func TestBroker_processPlugins(t *testing.T) {
Name: "name1",
Version: "v0.13",
ID: "id1",
Type: ChePluginType,
Endpoints: []model.Endpoint{
{
Name: "end1",
Expand Down Expand Up @@ -399,6 +400,7 @@ func TestBroker_processPlugins(t *testing.T) {
Name: "name2",
Version: "v0",
ID: "id2",
Type: EditorPluginType,
Endpoints: []model.Endpoint{
{
Name: "end2",
Expand Down Expand Up @@ -585,23 +587,26 @@ func TestBroker_processPlugins(t *testing.T) {
},
commonPlugins: []model.ChePlugin{
{
ID: "id11",
Type: "che plugin",
ID: "id11",
Containers: []model.Container{
{
Image: defaultImage,
},
},
},
{
ID: "id12",
Type: "Che Plugin",
ID: "id12",
Containers: []model.Container{
{
Image: defaultImage,
},
},
},
{
ID: "id13",
Type: "cHE plugIN",
ID: "id13",
Containers: []model.Container{
{
Image: defaultImage,
Expand Down Expand Up @@ -1443,7 +1448,20 @@ func createMetaWithExtension(ID string, extensions ...string) model.PluginMeta {

func createChePlugin(ID string) model.ChePlugin {
return model.ChePlugin{
ID: ID,
ID: ID,
Type: TestChePluginType,
Containers: []model.Container{
{
Image: defaultImage,
},
},
}
}

func createCheEditorPlugin(ID string) model.ChePlugin {
return model.ChePlugin{
ID: ID,
Type: TestEditorPluginType,
Containers: []model.Container{
{
Image: defaultImage,
Expand Down
1 change: 1 addition & 0 deletions brokers/unified/vscode/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func convertMetaToPlugin(meta model.PluginMeta) model.ChePlugin {
return model.ChePlugin{
ID: meta.ID,
Name: meta.Name,
Type: meta.Type,
Publisher: meta.Publisher,
Version: meta.Version,
Containers: meta.Spec.Containers,
Expand Down
23 changes: 12 additions & 11 deletions brokers/unified/vscode/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: false,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
false),
false, vscodePluginType),
},
{
name: "Successful brokering of remote plugin when extension points to .theia archive, using localhost as the host name",
Expand All @@ -256,7 +256,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: true,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
true),
true, vscodePluginType),
},
{
name: "Successful brokering of local plugin with extensions field with several extensions",
Expand Down Expand Up @@ -333,7 +333,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: false,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
false),
false, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with several extensions, using localhost as the host name",
Expand All @@ -356,7 +356,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: true,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
true),
true, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with several extensions, using a generated the host name",
Expand All @@ -381,7 +381,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: false,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
false),
false, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with several extensions, using localhost as the host name",
Expand All @@ -406,7 +406,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: true,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
true),
true, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with mixed extensions and archives URLs, using a generated host name",
Expand All @@ -431,7 +431,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: false,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
false),
false, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with mixed extensions and archives URLs, using localhost as the host name",
Expand All @@ -456,7 +456,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: true,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
true),
true, vscodePluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with mixed extensions and archives URLs when plugin type is Theia, using a generated host name",
Expand All @@ -481,7 +481,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: false,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
false),
false, theiaPluginType),
},
{
name: "Successful brokering of remote plugin with extensions field with mixed extensions and archives URLs when plugin type is Theia, using localhost as the host name",
Expand All @@ -506,7 +506,7 @@ func TestBroker_processPlugin(t *testing.T) {
},
useLocalhost: true,
want: expectedPluginsWithSingleRemotePluginWithSeveralExtensions(
true),
true, theiaPluginType),
},
}
for _, tt := range cases {
Expand Down Expand Up @@ -538,9 +538,10 @@ func TestBroker_processPlugin(t *testing.T) {
}
}

func expectedPluginsWithSingleRemotePluginWithSeveralExtensions(usedLocalhost bool) []model.ChePlugin {
func expectedPluginsWithSingleRemotePluginWithSeveralExtensions(usedLocalhost bool, pluginType string) []model.ChePlugin {
expectedPlugin := model.ChePlugin{
ID: pluginID,
Type: pluginType,
Version: pluginVersion,
Publisher: pluginPublisher,
Name: pluginName,
Expand Down
43 changes: 32 additions & 11 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ type PluginMeta struct {
}

type PluginMetaSpec struct {
Endpoints []Endpoint `json:"endpoints" yaml:"endpoints"`
Containers []Container `json:"containers" yaml:"containers"`
WorkspaceEnv []EnvVar `json:"workspaceEnv" yaml:"workspaceEnv"`
Extensions []string `json:"extensions" yaml:"extensions"`
Endpoints []Endpoint `json:"endpoints" yaml:"endpoints"`
Containers []Container `json:"containers" yaml:"containers"`
WorkspaceEnv []EnvVar `json:"workspaceEnv" yaml:"workspaceEnv"`
Extensions []string `json:"extensions" yaml:"extensions"`
PluginPatcher PluginPatcher `json:"pluginPatcher" yaml:"pluginPatcher"`
}

type PluginFQN struct {
Expand Down Expand Up @@ -98,14 +99,34 @@ type Container struct {
Ports []ExposedPort `json:"ports" yaml:"ports"`
MemoryLimit string `json:"memoryLimit,omitempty" yaml:"memoryLimit,omitempty"`
MountSources bool `json:"mountSources" yaml:"mountSources"`

// Base root command inside container
Command []string `json:"command" yaml:"command"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the goal of this addition and how is it managed by the plugin broker ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command and args defines root process entrypoint container command. We could declare here command, for example, to copy remote binary from container to volume. Currently plugin broker doesn't manage it, this logic was implemented in the che-server...

// Arguments of the base root command inside container
Args []string `json:"args" yaml:"args"`
}

type ChePlugin struct {
ID string `json:"id" yaml:"id"`
Version string `json:"version" yaml:"version"`
Name string `json:"name" yaml:"name"`
Publisher string `json:"publisher" yaml:"publisher"`
Endpoints []Endpoint `json:"endpoints" yaml:"endpoints"`
Containers []Container `json:"containers" yaml:"containers"`
WorkspaceEnv []EnvVar `json:"workspaceEnv" yaml:"workspaceEnv"`
ID string `json:"id" yaml:"id"`
Version string `json:"version" yaml:"version"`
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Publisher string `json:"publisher" yaml:"publisher"`
Endpoints []Endpoint `json:"endpoints" yaml:"endpoints"`
Containers []Container `json:"containers" yaml:"containers"`
WorkspaceEnv []EnvVar `json:"workspaceEnv" yaml:"workspaceEnv"`
PluginPatcher PluginPatcher `json:"pluginPatcher" yaml:"pluginPatcher"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't the plugin patcher managed by the plugin broker directly ?
In such a case, only the Container object would be modified (possibly to add a isInitContainer boolean flag), no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly to add a isInitContainer boolean flag

I believe we try to be as close to K8s API as possible. So, accordingly to K8s API it would be better to introduce new field initContainers instead of just adding a boolean flag.

Why isn't the plugin patcher managed by the plugin broker directly ?

I believe that the current implementation brings a consistent picture(I'm not talking about fields naming but about idea in general). Let me explain my thought:
In the current implementation one place - theia.meta.yaml has declarative configuration for plugin patching:

  pluginPatcher:
    pluginTypeMatcher: ["vs code extension", "theia plugin"]
    pluginContainerCommand: ["sh", "-c"]
    pluginContainerArgs: ["/plugins/remote-launcher/entrypoint.sh"]
    initContainers:
      - name: theia-remote-plugin-laucher
        image: aandrienko/che-theia-endpoint-runtime:next
        initContainer: true
        command: ['cp']
        args: ['-rf', '/remote-plugin-launcher', '/plugins/remote-launcher']
        volumes:
          - mountPath: "/plugins"
            name: plugins

More see https://github.com/eclipse/che-plugin-registry/pull/210/files

In case of adding only just initContainers to meta.yaml - we would have such configuration in different places:
like theia.meta.yaml

    - name: theia-remote-plugin-laucher
      image: aandrienko/che-theia-endpoint-runtime:next
      initContainer: true
      volumes:
      - mountPath: "/plugins"
        name: plugins

Then Dockerfile of che-theia-endpoint-runtime:next would have cp rf /remote-plugin-launcher /plugins/remote-launcher.
And plugin-broker should (via configuration or hard-coded value) would have

    pluginTypeMatcher: ["vs code extension", "theia plugin"]
    pluginContainerCommand: ["sh", "-c"]
    pluginContainerArgs: ["/plugins/remote-launcher/entrypoint.sh"]

And we can easily imagine a situation when vscode and remote plugin need different contianers entrypoints, or even more difficult situation - different entrypoint according to che-theia-endpoint-runtime container version, like che-theia-endpoint-runtime: next -> /plugins/remote-launcher/entrypoint.sh and che-theia-endpoint-runtime: 7.0.0 -> /plugins/remote-launcher/launch ${THEIA_PLUGIN_FILE_PATH}

Another case, what if a workspace does not have any vscode or remote plugin, in the current solution init container won't be propagated because of a matcher.
In case of moving this logic to plugin broker - it should delete this information from theia.meta.yaml what is like hidden logic, or run initContainer always even if it is redundant.

The current solution allows solving these issue quite nicely.

P.S. Hope I explained my thought clearly and feel free to reask if something is unclear or missing
P.S.S. I'm quite OK with introducing only initContainers in meta.yaml but it does not look as clear solution and it is needed to understand why we accept this solution: like keep meta.yaml as simple as possible (but it would not make things clearer in general), or because we don't have much time to implement something else...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 To have pluginPatcher configuration in one place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried with this approach, since it changes the schema of the plugin meta.yaml defintion to manage a very specific case (provide the ability for a given plugin to patch other plugins). It seems to me that we push logic that is implementation-dependent into a format that should be declarative.

Adding the concept of initConainers in the definition of a plugin is a very good thing IMO because it's a very general, and kubernetes standard, and easy to understand to a user.

BUT

The concept of some logic that should be triggered by the presence of 1 plugin for other plugins seems not straightforward to grasp for the meta.yaml reader IMO, and makes the schema (== spec ?) of the meta.yaml very implementation-dependent.

Let's not forget that plugin definitions are in fact a part of a Devfile (definition of the workspace structure). IMO we should keep the Devfile format (as well as the format of the plugin defintions it references) as much declarative and simple as possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the concept of initConainers in the definition of a plugin is a very good thing IMO because it's a very general, and kubernetes standard, and easy to understand to a user.

Totally agree with that. And here

I believe we try to be as close to K8s API as possible. So, accordingly to K8s API it would be better to introduce new field initContainers instead of just adding a boolean flag.

I tried to explain that I prefer initContainer field instead of adding initContainer flag into a container entity model.

I think it makes sense to separate and merge changes we all are agreed:

  1. Add an ability to add init containers in meta.yaml
  2. Add an ability to override command and args
  3. Add an ability to forcibly mark a volume as ephemeral (emptyDir, persistVolume:false).

So, I propose to do the following changes independently from pluginMatcher discussions: change meta.yaml or put all logic into che-plugin-broker.

apiVersion: v2
publisher: eclipse
...
spec:
  endpoints:
   -  name: "theia"
      ...
  initContainers:
    - name: theia-remote-plugin-launcher
      image: eclipse/che-theia-endpoint-runtime:next
      command: ['cp']
      args: ['-rf', '/remote-plugin-launcher', '/plugins/remote-launcher']
      volumes:
        - mountPath: "/plugin-laucher"
          name: plugin-laucher
          attributes:
            persistVolume: false
  containers:
   - name: theia-ide
     image: "docker.io/eclipse/che-theia:next"
     env:
       - name: THEIA_PLUGINS
         value: local-dir:///plugins
     ...
     volumes:
       - mountPath: "/plugins"
         name: plugins
     mountSources: true
     ...

@davidfestal @amisevsk @l0rd WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sleshchenko I think it makes sense to separate and merge changes we all are agreed:
Add an ability to add init containers in meta.yaml
Add an ability to override command and args
Add an ability to forcibly mark a volume as ephemeral (emptyDir, persistVolume:false).

Cool idea. +1 To split work part which was agreed like positive changes and move incremental forward. Che server then will be ready for any our solution with plugin-broker. And then rework plugin-broker.

@sleshchenko I prefer initContainer field instead of adding initContainer flag into a container entity model.

+1 for initContainer field

}

type PluginPatcher struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the whole PluginPatcher structure really required to manage only the Che Theia use-case ?
Wasn't it simpler to just implement the change on the plugin-broker side for the Theia/Vscode plugins (since the broken already makes the difference between the plugin types) ?

Is the PluginPatcher type (that now leaks into the plugin registry meta.yaml json schema) sufficiently general to be really useful for other type of Che plugins ? I'm especially thinking of the matcher: maybe matching on the type only is a bit limited if you want the plugin patcher to be a general and generic mechanism...

// List init containers
InitContainers []Container `json:"initContainers" yaml:"initContainers"`

// List plugin types matched to PluginPatcher
PluginTypeMatcher []string `json:"pluginTypeMatcher" yaml:"pluginTypeMatcher"`

// Command to patch original plugin container command
PluginContainerCommand []string `json:"pluginContainerCommand" yaml:"pluginContainerCommand"`
// Argument to patch original plugin container arguments
PluginContainerArgs []string `json:"pluginContainerArgs" yaml:"pluginContainerArgs"`
}