diff --git a/pkg/library/flatten/flatten_test.go b/pkg/library/flatten/flatten_test.go index d23d371d2..0c993ad23 100644 --- a/pkg/library/flatten/flatten_test.go +++ b/pkg/library/flatten/flatten_test.go @@ -234,6 +234,29 @@ func TestMergeContainerContributions(t *testing.T) { } } +func TestMergeImplicitContainerContributions(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/implicit-container-contributions") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.DevWorkspace.Components) > 0, "Test case defines workspace with no components") + testResolverTools := getTestingTools(tt.Input, "test-ignored") + + outputWorkspace, _, err := ResolveDevWorkspace(tt.Input.DevWorkspace, nil, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.DevWorkspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "DevWorkspace should match expected output:\n%s", + cmp.Diff(tt.Output.DevWorkspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + func TestMergeSpecContributions(t *testing.T) { tests := testutil.LoadAllTestsOrPanic(t, "testdata/spec-contributions") for _, tt := range tests { @@ -255,6 +278,27 @@ func TestMergeSpecContributions(t *testing.T) { } } +func TestMergeImplicitSpecContributions(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/spec-contributions") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + testResolverTools := getTestingTools(tt.Input, "test-namespace") + + outputWorkspace, _, err := ResolveDevWorkspace(tt.Input.DevWorkspace, tt.Input.Contributions, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.DevWorkspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "DevWorkspace should match expected output:\n%s", + cmp.Diff(tt.Output.DevWorkspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + func getTestingTools(input testutil.TestInput, testNamespace string) ResolverTools { testHttpGetter := &testutil.FakeHTTPGetter{ DevfileResources: input.DevfileResources, diff --git a/pkg/library/flatten/merge.go b/pkg/library/flatten/merge.go index 56183a99f..dcdb2a46b 100644 --- a/pkg/library/flatten/merge.go +++ b/pkg/library/flatten/merge.go @@ -124,10 +124,17 @@ func mergeVolume(into, from *dw.VolumeComponent) error { } // needsContainerContributionMerge returns whether merging container contributions is necessary for this workspace. Merging -// is necessary if at least one component has the merge-contribution: true attribute and at least one component has the -// container-contribution: true attribute. If either attribute is present but cannot be parsed as a bool, an error is returned. +// is necessary if the following two conditions are met: +// +// - At least one component has the container-contribution: true attribute. +// +// - At least one component has the merge-contribution: true attribute OR there exists a container component that was not imported by a +// plugin or parent devworkspace. +// +// If the container-contribution or merge-contribution attribute are present but cannot be parsed as a bool, an error is returned. +// If multiple components have the merge-contribution: true attribute, an error is returned. func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec) (bool, error) { - hasContribution, hasTarget := false, false + hasContribution, hasTarget, explicitTarget := false, false, false var errHolder error for _, component := range flattenedSpec.Components { if component.Container == nil { @@ -143,14 +150,23 @@ func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec) // Don't include error in message as it will be propagated to user and is not very clear (references Go unmarshalling) return false, fmt.Errorf("failed to parse %s attribute on component %s as true or false", constants.ContainerContributionAttribute, component.Name) } - } - if component.Attributes.Exists(constants.MergeContributionAttribute) { + } else if component.Attributes.Exists(constants.MergeContributionAttribute) { + // Explicit opt out case is handled here if the merge-contributions attribute is set to false if component.Attributes.GetBoolean(constants.MergeContributionAttribute, &errHolder) { + if explicitTarget { + return false, fmt.Errorf("multiple components have the %s attribute set to true. Only a single component may have the %s attribute set to true", constants.MergeContributionAttribute, constants.MergeContributionAttribute) + } + explicitTarget = true hasTarget = true } if errHolder != nil { return false, fmt.Errorf("failed to parse %s attribute on component %s as true or false", constants.MergeContributionAttribute, component.Name) } + } else { + if !component.Attributes.Exists(constants.PluginSourceAttribute) { + // First, non-imported container component is implicitly selected as a contribution target + hasTarget = true + } } } return hasContribution && hasTarget, nil @@ -164,6 +180,11 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err } } + targetComponentName, err := findMergeTarget(flattenedSpec) + if err != nil { + return err + } + var newComponents []dw.Component mergeDone := false for _, component := range flattenedSpec.Components { @@ -174,7 +195,7 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err if component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) { // drop contributions from updated list as they will be merged continue - } else if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) && !mergeDone { + } else if component.Name == targetComponentName && !mergeDone { mergedComponent, err := mergeContributionsInto(&component, contributions) if err != nil { return fmt.Errorf("failed to merge container contributions: %w", err) @@ -193,6 +214,52 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err return nil } +// Finds a component that is a suitable merge target for container contributions and returns its name. +// The following rules are followed when finding a merge target: +// +// - A container component that has the merge-contribution: true attribute will automatically be selected as a merge target. +// +// - A container component that has the merge-contribution: false attribute will be never be selected as a merge target. +// +// - Otherwise, the first container component found that was not imported by a plugin or parent devworkspace (i.e. the controller.devfile.io/imported-by attribute is not present) +// will be selected as a merge target. +// +// If no suitable merge target is found, an error is returned. +func findMergeTarget(flattenedSpec *dw.DevWorkspaceTemplateSpec) (mergeTargetComponentName string, err error) { + firstComponent := "" + for _, component := range flattenedSpec.Components { + if component.Container == nil { + continue + } + + if component.Attributes.Exists(constants.MergeContributionAttribute) { + // Check for explicit merge contributtion attribute + if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) { + return component.Name, nil + } + // Don't select components that opt out as a merge contribution target + continue + } + + // The target must not have been imported by a plugin or parent. + if component.Attributes.Exists(constants.PluginSourceAttribute) { + continue + } + + // There might be other components that explicitly opt in as a merge target, + // so don't return immediately + if firstComponent == "" { + firstComponent = component.Name + } + } + + if firstComponent != "" { + return firstComponent, nil + } + + return "", fmt.Errorf("couldn't find any merge contribution target component") +} + func mergeContributionsInto(mergeInto *dw.Component, contributions []dw.Component) (*dw.Component, error) { if mergeInto == nil || mergeInto.Container == nil { return nil, fmt.Errorf("attempting to merge container contributions into a non-container component") diff --git a/pkg/library/flatten/testdata/container-contributions/error_multiple-contribution-targets.yaml b/pkg/library/flatten/testdata/container-contributions/error_multiple-contribution-targets.yaml new file mode 100644 index 000000000..bc637efc4 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/error_multiple-contribution-targets.yaml @@ -0,0 +1,55 @@ +name: "Return error if multiple components have the merge-contribution attribute set to true" + +input: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-2 + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + errRegexp: "multiple components have the controller.devfile.io/merge-contribution attribute set to true. Only a single component may have the controller.devfile.io/merge-contribution attribute set to true" diff --git a/pkg/library/flatten/testdata/container-contributions/no-op-if-no-target.yaml b/pkg/library/flatten/testdata/container-contributions/no-op-if-explicit-opt-out.yaml similarity index 91% rename from pkg/library/flatten/testdata/container-contributions/no-op-if-no-target.yaml rename to pkg/library/flatten/testdata/container-contributions/no-op-if-explicit-opt-out.yaml index 394196f57..6b3a1600e 100644 --- a/pkg/library/flatten/testdata/container-contributions/no-op-if-no-target.yaml +++ b/pkg/library/flatten/testdata/container-contributions/no-op-if-explicit-opt-out.yaml @@ -1,11 +1,11 @@ -name: "Adds unmerged elements" +name: "Opt out of container contribution" input: devworkspace: components: - name: test-component - # attributes: - # controller.devfile.io/merge-contribution: true + attributes: + controller.devfile.io/merge-contribution: false container: image: test-image env: @@ -47,6 +47,8 @@ output: devworkspace: components: - name: test-component + attributes: + controller.devfile.io/merge-contribution: false container: image: test-image env: diff --git a/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml index 72ae5e8bd..efcabd35a 100644 --- a/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml +++ b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml @@ -1,4 +1,4 @@ -name: "Adds unmerged elements" +name: "No op if no contribution" input: devworkspace: diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/adds-resources.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/adds-resources.yaml new file mode 100644 index 000000000..867e90538 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/adds-resources.yaml @@ -0,0 +1,44 @@ +name: "Adds attributes from contribution" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + memoryLimit: 1Gi + memoryRequest: 1000Mi + cpuLimit: 1500m + cpuRequest: "1" + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + memoryLimit: 512Mi + memoryRequest: 1.5G + cpuLimit: "0.5" + cpuRequest: 500m + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + memoryLimit: 1536Mi + memoryRequest: "2548576000" # 1.5G + 1000Mi = 1.5*1000^3 + 1000*1024^2 + cpuLimit: "2" + cpuRequest: 1500m diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/adds-unmerged-elements.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/adds-unmerged-elements.yaml new file mode 100644 index 000000000..d31423f9a --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/adds-unmerged-elements.yaml @@ -0,0 +1,74 @@ +name: "Adds unmerged elements" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/che-code-usecase.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/che-code-usecase.yaml new file mode 100644 index 000000000..e98210b55 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/che-code-usecase.yaml @@ -0,0 +1,186 @@ +name: "Merges Che Code IDE contribution" + +input: + devworkspace: + components: + - name: tools + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + memoryLimit: 2Gi + mountSources: true + - name: che-code + plugin: + uri: che-code.yaml + + devfileResources: + che-code.yaml: + schemaVersion: 2.1.0 + metadata: + name: che-code + commands: + - id: init-container-command + apply: + component: che-code-injector + events: + preStart: + - init-container-command + components: + - name: che-code-runtime-description + attributes: + app.kubernetes.io/component: che-code-runtime + app.kubernetes.io/part-of: che-code.eclipse.org + controller.devfile.io/container-contribution: true + container: + image: quay.io/devfile/universal-developer-image:ubi8-0e189d9 + command: + - /checode/entrypoint-volume.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 1024Mi + memoryRequest: 256Mi + cpuLimit: 500m + cpuRequest: 30m + endpoints: + - name: che-code + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + path: '?tkn=eclipse-che' + secure: false + protocol: https + - name: code-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: code-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: code-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: checode + volume: {} + - name: che-code-injector + container: + image: quay.io/che-incubator/che-code:insiders + command: + - /entrypoint-init-container.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + +output: + devworkspace: + components: + - name: tools + attributes: + app.kubernetes.io/component: che-code-runtime + app.kubernetes.io/part-of: che-code.eclipse.org + controller.devfile.io/merged-contributions: "che-code" + container: + image: quay.io/devfile/universal-developer-image:latest + command: + - /checode/entrypoint-volume.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 3Gi + memoryRequest: 256Mi + cpuLimit: 500m + cpuRequest: 30m + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + - name: che-code + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + path: '?tkn=eclipse-che' + secure: false + protocol: https + - name: code-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: code-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: code-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + mountSources: true + - name: checode + attributes: + controller.devfile.io/imported-by: che-code + volume: {} + - name: che-code-injector + attributes: + controller.devfile.io/imported-by: che-code + container: + image: quay.io/che-incubator/che-code:insiders + command: + - /entrypoint-init-container.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + commands: + - id: init-container-command + attributes: + controller.devfile.io/imported-by: che-code + apply: + component: che-code-injector + events: + preStart: + - init-container-command diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/merges-list-elements.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/merges-list-elements.yaml new file mode 100644 index 000000000..918c31bfb --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/merges-list-elements.yaml @@ -0,0 +1,87 @@ +name: "Merges list elements in contribution" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + volumeMounts: + - name: test-volume + path: test-volume-path + endpoints: + - name: test-endpoint-1 + targetPort: 8888 + exposure: internal + protocol: https + - name: test-endpoint-2 + targetPort: 8889 + exposure: internal + protocol: https + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + volumeMounts: + - name: contrib-volume + path: contrib-volume-path + endpoints: + - name: contrib-endpoint-1 + targetPort: 9999 + exposure: public + protocol: https + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: CONTRIB_ENVVAR_2 + value: CONTRIB_VALUE_2 + + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + volumeMounts: + - name: test-volume + path: test-volume-path + - name: contrib-volume + path: contrib-volume-path + endpoints: + - name: test-endpoint-1 + targetPort: 8888 + exposure: internal + protocol: https + - name: test-endpoint-2 + targetPort: 8889 + exposure: internal + protocol: https + - name: contrib-endpoint-1 + targetPort: 9999 + exposure: public + protocol: https + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: CONTRIB_ENVVAR_2 + value: CONTRIB_VALUE_2 diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/no-op-if-no-contribution.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/no-op-if-no-contribution.yaml new file mode 100644 index 000000000..d717c680b --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/no-op-if-no-contribution.yaml @@ -0,0 +1,78 @@ +name: "No op if no contribution" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + # attributes: + # controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-contribution + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/opt-in-non-first-component.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/opt-in-non-first-component.yaml new file mode 100644 index 000000000..6e87aac3d --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/opt-in-non-first-component.yaml @@ -0,0 +1,88 @@ +name: "Opt in for second component" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-opt-in + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-opt-in + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/opt-out-but-merge-other-component.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/opt-out-but-merge-other-component.yaml new file mode 100644 index 000000000..2a05276c4 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/opt-out-but-merge-other-component.yaml @@ -0,0 +1,90 @@ +name: "Opt out for first component, implcit opt in for other component" + +input: + devworkspace: + components: + - name: test-component-opt-out + attributes: + controller.devfile.io/merge-contribution: false + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-implicit-opt-in + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component-opt-out + attributes: + controller.devfile.io/merge-contribution: false + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-implicit-opt-in + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-container-contributions/theia-merge-usecase.yaml b/pkg/library/flatten/testdata/implicit-container-contributions/theia-merge-usecase.yaml new file mode 100644 index 000000000..15aa880bb --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-container-contributions/theia-merge-usecase.yaml @@ -0,0 +1,384 @@ +name: "Merges Theia IDE contribution" + +input: + devworkspace: + components: + - name: tools + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + memoryLimit: 2Gi + mountSources: true + - name: theia-ide + plugin: + uri: theia-ide.yaml + + devfileResources: + theia-ide.yaml: + schemaVersion: 2.1.0 + metadata: + name: theia-ide + commands: + - id: init-container-command + apply: + component: remote-runtime-injector + events: + preStart: + - init-container-command + components: + - name: theia-ide-contributions + attributes: + controller.devfile.io/container-contribution: true + container: + args: + - sh + - '-c' + - '${PLUGIN_REMOTE_ENDPOINT_EXECUTABLE}' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/tools + memoryLimit: 512Mi + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + image: quay.io/devfile/universal-developer-image@sha256:53cec58dd190dd1e06100478ae879d7c28abd8fc883d5fdf5be3eb6e943fe5e7 + - name: theia-ide + container: + image: quay.io/eclipse/che-theia:next + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: '3130' + - name: THEIA_HOST + value: 127.0.0.1 + volumeMounts: + - name: plugins + path: /plugins + - name: theia-local + path: /home/theia/.theia + mountSources: true + memoryLimit: 512M + cpuLimit: 1500m + cpuRequest: 100m + endpoints: + - name: theia + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: webviews + attributes: + type: webview + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: mini-browser + attributes: + type: mini-browser + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: theia-dev + attributes: + type: ide-dev + discoverable: false + urlRewriteSupported: true + targetPort: 3130 + exposure: public + protocol: http + - name: theia-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: theia-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: theia-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: terminal + attributes: + type: collocated-terminal + discoverable: false + cookiesAuthEnabled: true + urlRewriteSupported: true + targetPort: 3333 + exposure: public + secure: false + protocol: wss + attributes: + app.kubernetes.io/component: che-theia + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: plugins + volume: {} + - name: theia-local + volume: {} + - name: che-machine-exec + container: + image: quay.io/eclipse/che-machine-exec:next + command: + - /go/bin/che-machine-exec + - '--url' + - 127.0.0.1:3333 + - '--idle-timeout' + - 15m + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + attributes: + app.kubernetes.io/component: machine-exec + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: remote-runtime-injector + container: + image: quay.io/eclipse/che-theia-endpoint-runtime-binary:next + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + attributes: + app.kubernetes.io/component: remote-runtime-injector + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: remote-endpoint + volume: + ephemeral: true + + +output: + devworkspace: + components: + - name: tools + attributes: + controller.devfile.io/merged-contributions: "theia-ide" + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/tools + args: + - sh + - '-c' + - '${PLUGIN_REMOTE_ENDPOINT_EXECUTABLE}' + endpoints: + - name: 8080-tcp + targetPort: 8080 + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 2560Mi # 2Gi = 2048Mi + 512Mi + mountSources: true + - name: theia-ide + attributes: + app.kubernetes.io/component: che-theia + app.kubernetes.io/part-of: che-theia.eclipse.org + controller.devfile.io/imported-by: theia-ide + container: + image: quay.io/eclipse/che-theia:next + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: '3130' + - name: THEIA_HOST + value: 127.0.0.1 + volumeMounts: + - name: plugins + path: /plugins + - name: theia-local + path: /home/theia/.theia + mountSources: true + memoryLimit: 512M + cpuLimit: 1500m + cpuRequest: 100m + endpoints: + - name: theia + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: webviews + attributes: + type: webview + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: mini-browser + attributes: + type: mini-browser + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: theia-dev + attributes: + type: ide-dev + discoverable: false + urlRewriteSupported: true + targetPort: 3130 + exposure: public + protocol: http + - name: theia-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: theia-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: theia-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: terminal + attributes: + type: collocated-terminal + discoverable: false + cookiesAuthEnabled: true + urlRewriteSupported: true + targetPort: 3333 + exposure: public + secure: false + protocol: wss + - name: plugins + attributes: + controller.devfile.io/imported-by: theia-ide + volume: {} + - name: theia-local + attributes: + controller.devfile.io/imported-by: theia-ide + volume: {} + - name: che-machine-exec + attributes: + app.kubernetes.io/component: machine-exec + app.kubernetes.io/part-of: che-theia.eclipse.org + controller.devfile.io/imported-by: theia-ide + container: + image: quay.io/eclipse/che-machine-exec:next + command: + - /go/bin/che-machine-exec + - '--url' + - 127.0.0.1:3333 + - '--idle-timeout' + - 15m + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + - name: remote-runtime-injector + attributes: + controller.devfile.io/imported-by: theia-ide + app.kubernetes.io/component: remote-runtime-injector + app.kubernetes.io/part-of: che-theia.eclipse.org + container: + image: quay.io/eclipse/che-theia-endpoint-runtime-binary:next + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + - name: remote-endpoint + attributes: + controller.devfile.io/imported-by: theia-ide + volume: + ephemeral: true + commands: + - id: init-container-command + attributes: + controller.devfile.io/imported-by: theia-ide + apply: + component: remote-runtime-injector + events: + preStart: + - init-container-command diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/adds-resources.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/adds-resources.yaml new file mode 100644 index 000000000..143a1ea57 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/adds-resources.yaml @@ -0,0 +1,44 @@ +name: "Adds attributes from contribution" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + memoryLimit: 1Gi + memoryRequest: 1000Mi + cpuLimit: 1500m + cpuRequest: "1" + contributions: + - name: test-contribution + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + memoryLimit: 512Mi + memoryRequest: 1.5G + cpuLimit: "0.5" + cpuRequest: 500m + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + memoryLimit: 1536Mi + memoryRequest: "2548576000" # 1.5G + 1000Mi = 1.5*1000^3 + 1000*1024^2 + cpuLimit: "2" + cpuRequest: 1500m diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/adds-unmerged-elements.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/adds-unmerged-elements.yaml new file mode 100644 index 000000000..4e6811656 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/adds-unmerged-elements.yaml @@ -0,0 +1,73 @@ +name: "Adds unmerged elements" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + contributions: + - name: test-contribution + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/che-code-usecase.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/che-code-usecase.yaml new file mode 100644 index 000000000..9373d441f --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/che-code-usecase.yaml @@ -0,0 +1,186 @@ +name: "Merges Che Code IDE contribution" + +input: + devworkspace: + components: + - name: tools + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + memoryLimit: 2Gi + mountSources: true + contributions: + - name: che-code + uri: che-code.yaml + + devfileResources: + che-code.yaml: + schemaVersion: 2.1.0 + metadata: + name: che-code + commands: + - id: init-container-command + apply: + component: che-code-injector + events: + preStart: + - init-container-command + components: + - name: che-code-runtime-description + attributes: + app.kubernetes.io/component: che-code-runtime + app.kubernetes.io/part-of: che-code.eclipse.org + controller.devfile.io/container-contribution: true + container: + image: quay.io/devfile/universal-developer-image:ubi8-0e189d9 + command: + - /checode/entrypoint-volume.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 1024Mi + memoryRequest: 256Mi + cpuLimit: 500m + cpuRequest: 30m + endpoints: + - name: che-code + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + path: '?tkn=eclipse-che' + secure: false + protocol: https + - name: code-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: code-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: code-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: checode + volume: {} + - name: che-code-injector + container: + image: quay.io/che-incubator/che-code:insiders + command: + - /entrypoint-init-container.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + +output: + devworkspace: + components: + - name: tools + attributes: + app.kubernetes.io/component: che-code-runtime + app.kubernetes.io/part-of: che-code.eclipse.org + controller.devfile.io/merged-contributions: "che-code" + container: + image: quay.io/devfile/universal-developer-image:latest + command: + - /checode/entrypoint-volume.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 3Gi + memoryRequest: 256Mi + cpuLimit: 500m + cpuRequest: 30m + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + - name: che-code + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + path: '?tkn=eclipse-che' + secure: false + protocol: https + - name: code-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: code-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: code-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + mountSources: true + - name: checode + attributes: + controller.devfile.io/imported-by: che-code + volume: {} + - name: che-code-injector + attributes: + controller.devfile.io/imported-by: che-code + container: + image: quay.io/che-incubator/che-code:insiders + command: + - /entrypoint-init-container.sh + volumeMounts: + - name: checode + path: /checode + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + commands: + - id: init-container-command + attributes: + controller.devfile.io/imported-by: che-code + apply: + component: che-code-injector + events: + preStart: + - init-container-command diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/no-op-if-no-contribution.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/no-op-if-no-contribution.yaml new file mode 100644 index 000000000..9f56fd96e --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/no-op-if-no-contribution.yaml @@ -0,0 +1,79 @@ +name: "No op if no contributions" + +input: + devworkspace: + components: + - name: test-component + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + contributions: + - name: test-contribution + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + # attributes: + # controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-contribution + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/opt-out-but-merge-other-component.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/opt-out-but-merge-other-component.yaml new file mode 100644 index 000000000..509542b08 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/opt-out-but-merge-other-component.yaml @@ -0,0 +1,90 @@ +name: "Opt out for first component, implcit opt in for other component" + +input: + devworkspace: + components: + - name: test-component-opt-out + attributes: + controller.devfile.io/merge-contribution: false + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-implicit-opt-in + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + contributions: + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + devworkspace: + components: + - name: test-component-opt-out + attributes: + controller.devfile.io/merge-contribution: false + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-implicit-opt-in + attributes: + controller.devfile.io/merged-contributions: "test-contribution" + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + attributes: + controller.devfile.io/imported-by: test-contribution + container: + image: unmerged-container + - name: unmerged-volume + attributes: + controller.devfile.io/imported-by: test-contribution + volume: {} + commands: + - name: plugin-command + attributes: + controller.devfile.io/imported-by: test-contribution + apply: + component: unmerged-container + events: + prestart: + - plugin-command diff --git a/pkg/library/flatten/testdata/implicit-spec-contributions/theia-merge-usecase.yaml b/pkg/library/flatten/testdata/implicit-spec-contributions/theia-merge-usecase.yaml new file mode 100644 index 000000000..008cbb5f8 --- /dev/null +++ b/pkg/library/flatten/testdata/implicit-spec-contributions/theia-merge-usecase.yaml @@ -0,0 +1,384 @@ +name: "Merges Theia IDE contribution" + +input: + devworkspace: + components: + - name: tools + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + endpoints: + - name: 8080-tcp + targetPort: 8080 + memoryLimit: 2Gi + mountSources: true + contributions: + - name: theia-ide + uri: theia-ide.yaml + + devfileResources: + theia-ide.yaml: + schemaVersion: 2.1.0 + metadata: + name: theia-ide + commands: + - id: init-container-command + apply: + component: remote-runtime-injector + events: + preStart: + - init-container-command + components: + - name: theia-ide-contributions + attributes: + controller.devfile.io/container-contribution: true + container: + args: + - sh + - '-c' + - '${PLUGIN_REMOTE_ENDPOINT_EXECUTABLE}' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/tools + memoryLimit: 512Mi + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + image: quay.io/devfile/universal-developer-image@sha256:53cec58dd190dd1e06100478ae879d7c28abd8fc883d5fdf5be3eb6e943fe5e7 + - name: theia-ide + container: + image: quay.io/eclipse/che-theia:next + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: '3130' + - name: THEIA_HOST + value: 127.0.0.1 + volumeMounts: + - name: plugins + path: /plugins + - name: theia-local + path: /home/theia/.theia + mountSources: true + memoryLimit: 512M + cpuLimit: 1500m + cpuRequest: 100m + endpoints: + - name: theia + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: webviews + attributes: + type: webview + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: mini-browser + attributes: + type: mini-browser + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: theia-dev + attributes: + type: ide-dev + discoverable: false + urlRewriteSupported: true + targetPort: 3130 + exposure: public + protocol: http + - name: theia-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: theia-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: theia-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: terminal + attributes: + type: collocated-terminal + discoverable: false + cookiesAuthEnabled: true + urlRewriteSupported: true + targetPort: 3333 + exposure: public + secure: false + protocol: wss + attributes: + app.kubernetes.io/component: che-theia + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: plugins + volume: {} + - name: theia-local + volume: {} + - name: che-machine-exec + container: + image: quay.io/eclipse/che-machine-exec:next + command: + - /go/bin/che-machine-exec + - '--url' + - 127.0.0.1:3333 + - '--idle-timeout' + - 15m + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + attributes: + app.kubernetes.io/component: machine-exec + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: remote-runtime-injector + container: + image: quay.io/eclipse/che-theia-endpoint-runtime-binary:next + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + attributes: + app.kubernetes.io/component: remote-runtime-injector + app.kubernetes.io/part-of: che-theia.eclipse.org + - name: remote-endpoint + volume: + ephemeral: true + + +output: + devworkspace: + components: + - name: tools + attributes: + controller.devfile.io/merged-contributions: "theia-ide" + container: + image: quay.io/devfile/universal-developer-image:latest + env: + - name: GOPATH + value: /projects:/home/user/go + - name: GOCACHE + value: /tmp/.cache + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/tools + args: + - sh + - '-c' + - '${PLUGIN_REMOTE_ENDPOINT_EXECUTABLE}' + endpoints: + - name: 8080-tcp + targetPort: 8080 + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 2560Mi # 2Gi = 2048Mi + 512Mi + mountSources: true + - name: theia-ide + attributes: + app.kubernetes.io/component: che-theia + app.kubernetes.io/part-of: che-theia.eclipse.org + controller.devfile.io/imported-by: theia-ide + container: + image: quay.io/eclipse/che-theia:next + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: '3130' + - name: THEIA_HOST + value: 127.0.0.1 + volumeMounts: + - name: plugins + path: /plugins + - name: theia-local + path: /home/theia/.theia + mountSources: true + memoryLimit: 512M + cpuLimit: 1500m + cpuRequest: 100m + endpoints: + - name: theia + attributes: + type: main + cookiesAuthEnabled: true + discoverable: false + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: webviews + attributes: + type: webview + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: mini-browser + attributes: + type: mini-browser + cookiesAuthEnabled: true + discoverable: false + unique: true + urlRewriteSupported: true + targetPort: 3100 + exposure: public + secure: false + protocol: https + - name: theia-dev + attributes: + type: ide-dev + discoverable: false + urlRewriteSupported: true + targetPort: 3130 + exposure: public + protocol: http + - name: theia-redirect-1 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13131 + exposure: public + protocol: http + - name: theia-redirect-2 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13132 + exposure: public + protocol: http + - name: theia-redirect-3 + attributes: + discoverable: false + urlRewriteSupported: true + targetPort: 13133 + exposure: public + protocol: http + - name: terminal + attributes: + type: collocated-terminal + discoverable: false + cookiesAuthEnabled: true + urlRewriteSupported: true + targetPort: 3333 + exposure: public + secure: false + protocol: wss + - name: plugins + attributes: + controller.devfile.io/imported-by: theia-ide + volume: {} + - name: theia-local + attributes: + controller.devfile.io/imported-by: theia-ide + volume: {} + - name: che-machine-exec + attributes: + app.kubernetes.io/component: machine-exec + app.kubernetes.io/part-of: che-theia.eclipse.org + controller.devfile.io/imported-by: theia-ide + container: + image: quay.io/eclipse/che-machine-exec:next + command: + - /go/bin/che-machine-exec + - '--url' + - 127.0.0.1:3333 + - '--idle-timeout' + - 15m + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + - name: remote-runtime-injector + attributes: + controller.devfile.io/imported-by: theia-ide + app.kubernetes.io/component: remote-runtime-injector + app.kubernetes.io/part-of: che-theia.eclipse.org + container: + image: quay.io/eclipse/che-theia-endpoint-runtime-binary:next + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + volumeMounts: + - name: plugins + path: /plugins + - name: remote-endpoint + path: /remote-endpoint + memoryLimit: 128Mi + memoryRequest: 32Mi + cpuLimit: 500m + cpuRequest: 30m + - name: remote-endpoint + attributes: + controller.devfile.io/imported-by: theia-ide + volume: + ephemeral: true + commands: + - id: init-container-command + attributes: + controller.devfile.io/imported-by: theia-ide + apply: + component: remote-runtime-injector + events: + preStart: + - init-container-command diff --git a/pkg/library/flatten/testdata/spec-contributions/error_multiple-contribution-targets.yaml b/pkg/library/flatten/testdata/spec-contributions/error_multiple-contribution-targets.yaml new file mode 100644 index 000000000..bc23ec86b --- /dev/null +++ b/pkg/library/flatten/testdata/spec-contributions/error_multiple-contribution-targets.yaml @@ -0,0 +1,55 @@ +name: "Return error if multiple components have the merge-contribution attribute set to true" + +input: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + - name: test-component-2 + attributes: + controller.devfile.io/merge-contribution: true + container: + image: test-image + env: + - name: TEST_ENVVAR + value: TEST_VALUE + contributions: + - name: test-contribution + plugin: + uri: test-contribution.yaml + + devfileResources: + test-contribution.yaml: + schemaVersion: 2.1.0 + metadata: + name: test-contribution + components: + - name: test-contribution + attributes: + controller.devfile.io/container-contribution: true + container: + image: contribution-image + env: + - name: CONTRIB_ENVVAR + value: CONTRIB_VALUE + - name: unmerged-container + container: + image: unmerged-container + - name: unmerged-volume + volume: {} + commands: + - name: plugin-command + apply: + component: unmerged-container + events: + prestart: + - plugin-command + +output: + errRegexp: "multiple components have the controller.devfile.io/merge-contribution attribute set to true. Only a single component may have the controller.devfile.io/merge-contribution attribute set to true" diff --git a/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-target.yaml b/pkg/library/flatten/testdata/spec-contributions/no-op-if-explicit-opt-out.yaml similarity index 91% rename from pkg/library/flatten/testdata/spec-contributions/no-op-if-no-target.yaml rename to pkg/library/flatten/testdata/spec-contributions/no-op-if-explicit-opt-out.yaml index af40ea46a..af05769c0 100644 --- a/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-target.yaml +++ b/pkg/library/flatten/testdata/spec-contributions/no-op-if-explicit-opt-out.yaml @@ -1,11 +1,11 @@ -name: "Adds unmerged elements" +name: "Opt out of container contribution" input: devworkspace: components: - name: test-component - # attributes: - # controller.devfile.io/merge-contribution: true + attributes: + controller.devfile.io/merge-contribution: false container: image: test-image env: @@ -46,6 +46,8 @@ output: devworkspace: components: - name: test-component + attributes: + controller.devfile.io/merge-contribution: false container: image: test-image env: diff --git a/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-contribution.yaml b/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-contribution.yaml index a5dd00731..f1e11a8f0 100644 --- a/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-contribution.yaml +++ b/pkg/library/flatten/testdata/spec-contributions/no-op-if-no-contribution.yaml @@ -1,4 +1,4 @@ -name: "Adds unmerged elements" +name: "No op if no contributions" input: devworkspace: diff --git a/webhook/workspace/handler/attributes.go b/webhook/workspace/handler/attributes.go new file mode 100644 index 000000000..42034d6fd --- /dev/null +++ b/webhook/workspace/handler/attributes.go @@ -0,0 +1,56 @@ +// +// Copyright (c) 2019-2023 Red Hat, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package handler + +import ( + "fmt" + "strings" + + "github.com/devfile/devworkspace-operator/pkg/constants" + + dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// Checks whether the given DevWorkspace Template Spec has multiple container components with the +// controller.devfile.io/merge-contribution attribute set to true. +// If only a single container component has the controller.devfile.io/merge-contribution attribute set to true, nil is returned. +// If multiple container component have the controller.devfile.io/merge-contribution attribute set to true, or an error occurs +// while parsing the attribute, an error is returned. +func checkMultipleContainerContributionTargets(devWorkspaceSpec dwv2.DevWorkspaceTemplateSpec) error { + var componentNames []string + for _, component := range devWorkspaceSpec.Components { + if component.Container == nil { + // Ignore attribute on non-container components as it's not clear what this would mean + continue + } + if component.Attributes.Exists(constants.MergeContributionAttribute) { + var errHolder error + if component.Attributes.GetBoolean(constants.MergeContributionAttribute, &errHolder) { + componentNames = append(componentNames, component.Name) + } + + if errHolder != nil { + return fmt.Errorf("failed to parse %s attribute on component %s as true or false", constants.MergeContributionAttribute, component.Name) + } + } + } + + if len(componentNames) > 1 { + return fmt.Errorf("only a single component may have the %s attribute set to true. The following %d components have the %s attribute set to true: %s", constants.MergeContributionAttribute, len(componentNames), constants.MergeContributionAttribute, strings.Join(componentNames, ", ")) + } + + return nil +} diff --git a/webhook/workspace/handler/workspace.go b/webhook/workspace/handler/workspace.go index 8773278d0..f954e0e37 100644 --- a/webhook/workspace/handler/workspace.go +++ b/webhook/workspace/handler/workspace.go @@ -62,6 +62,10 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha2OnCreate(ctx context.Context, re return admission.Denied(err.Error()) } + if err := checkMultipleContainerContributionTargets(wksp.Spec.Template); err != nil { + return admission.Denied(err.Error()) + } + if warnings := checkUnsupportedFeatures(wksp.Spec.Template); unsupportedWarningsPresent(warnings) { return h.returnPatched(req, wksp).WithWarnings(formatUnsupportedFeaturesWarning(warnings)) } @@ -170,6 +174,10 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha2OnUpdate(ctx context.Context, re return admission.Denied(err.Error()) } + if err := checkMultipleContainerContributionTargets(newWksp.Spec.Template); err != nil { + return admission.Denied(err.Error()) + } + oldCreator, found := oldWksp.Labels[constants.DevWorkspaceCreatorLabel] if !found { return admission.Denied(fmt.Sprintf("label '%s' is missing. Please recreate devworkspace to get it initialized", constants.DevWorkspaceCreatorLabel))