diff --git a/pkg/constants/attributes.go b/pkg/constants/attributes.go index 59f161d15..d3378fd68 100644 --- a/pkg/constants/attributes.go +++ b/pkg/constants/attributes.go @@ -89,4 +89,18 @@ const ( // EndpointURLAttribute is an attribute added to endpoints to denote the endpoint on the cluster that // was created to route to this endpoint EndpointURLAttribute = "controller.devfile.io/endpoint-url" + + // ContainerContributionAttribute defines a container component as a container contribution that should be merged + // into an existing container in the devfile if possible. If no suitable container exists, this component + // is treated as a regular container component + ContainerContributionAttribute = "controller.devfile.io/container-contribution" + + // MergeContributionAttribute defines a container component as a target for merging a container contribution. If + // present on a container component, any container contributions will be merged into that container. If multiple + // container components have the merge-contribution attribute, the first one will be used and all others ignored. + MergeContributionAttribute = "controller.devfile.io/merge-contribution" + + // MergedContributionsAttribute is applied as an attribute onto a component to list the components from the unflattened + // DevWorkspace that have been merged into the current component. The contributions are listed in a comma-separated list. + MergedContributionsAttribute = "controller.devfile.io/merged-contributions" ) diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index ed1a7d44f..34c7f13fa 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -64,6 +64,14 @@ func ResolveDevWorkspace(workspace *dw.DevWorkspaceTemplateSpec, tooling Resolve return resolvedDW, &warnings, nil } + if needsMerge, err := needsContainerContributionMerge(resolvedDW); needsMerge { + if err := mergeContainerContributions(resolvedDW); err != nil { + return nil, nil, err + } + } else if err != nil { + return nil, nil, err + } + return resolvedDW, nil, nil } diff --git a/pkg/library/flatten/flatten_test.go b/pkg/library/flatten/flatten_test.go index ce2e3b771..28a6a2b82 100644 --- a/pkg/library/flatten/flatten_test.go +++ b/pkg/library/flatten/flatten_test.go @@ -211,6 +211,29 @@ func TestMergesDuplicateVolumeComponents(t *testing.T) { } } +func TestMergeContainerContributions(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/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, 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/helper.go b/pkg/library/flatten/helper.go index 9f175a558..479db8987 100644 --- a/pkg/library/flatten/helper.go +++ b/pkg/library/flatten/helper.go @@ -19,6 +19,9 @@ import ( "fmt" "reflect" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ) @@ -67,3 +70,100 @@ func formatImportCycle(end *resolutionContextTree) string { } return cycle } + +func parseResourcesFromComponent(component *dw.Component) (*corev1.ResourceRequirements, error) { + if component.Container == nil { + return nil, fmt.Errorf("attemped to parse resource requirements from a non-container component") + } + memLimitStr := component.Container.MemoryLimit + if memLimitStr == "" { + memLimitStr = "0Mi" + } + memRequestStr := component.Container.MemoryRequest + if memRequestStr == "" { + memRequestStr = "0Mi" + } + cpuLimitStr := component.Container.CpuLimit + if cpuLimitStr == "" { + cpuLimitStr = "0m" + } + cpuRequestStr := component.Container.CpuRequest + if cpuRequestStr == "" { + cpuRequestStr = "0m" + } + + memoryLimit, err := resource.ParseQuantity(memLimitStr) + if err != nil { + return nil, fmt.Errorf("failed to parse memory limit for container component %s: %w", component.Name, err) + } + memoryRequest, err := resource.ParseQuantity(memRequestStr) + if err != nil { + return nil, fmt.Errorf("failed to parse memory request for container component %s: %w", component.Name, err) + } + cpuLimit, err := resource.ParseQuantity(cpuLimitStr) + if err != nil { + return nil, fmt.Errorf("failed to parse CPU limit for container component %s: %w", component.Name, err) + } + cpuRequest, err := resource.ParseQuantity(cpuRequestStr) + if err != nil { + return nil, fmt.Errorf("failed to parse CPU request for container component %s: %w", component.Name, err) + } + + return &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: memoryLimit, + corev1.ResourceCPU: cpuLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: memoryRequest, + corev1.ResourceCPU: cpuRequest, + }, + }, nil +} + +func addResourceRequirements(resources *corev1.ResourceRequirements, toAdd *dw.Component) error { + componentResources, err := parseResourcesFromComponent(toAdd) + if err != nil { + return err + } + + memoryLimit := resources.Limits[corev1.ResourceMemory] + memoryLimit.Add(componentResources.Limits[corev1.ResourceMemory]) + resources.Limits[corev1.ResourceMemory] = memoryLimit + + cpuLimit := resources.Limits[corev1.ResourceCPU] + cpuLimit.Add(componentResources.Limits[corev1.ResourceCPU]) + resources.Limits[corev1.ResourceCPU] = cpuLimit + + memoryRequest := resources.Requests[corev1.ResourceMemory] + memoryRequest.Add(componentResources.Requests[corev1.ResourceMemory]) + resources.Requests[corev1.ResourceMemory] = memoryRequest + + cpuRequest := resources.Requests[corev1.ResourceCPU] + cpuRequest.Add(componentResources.Requests[corev1.ResourceCPU]) + resources.Requests[corev1.ResourceCPU] = cpuRequest + + return nil +} + +func applyResourceRequirementsToComponent(container *dw.ContainerComponent, resources *corev1.ResourceRequirements) { + memLimit := resources.Limits[corev1.ResourceMemory] + if !memLimit.IsZero() { + container.MemoryLimit = memLimit.String() + } + + cpuLimit := resources.Limits[corev1.ResourceCPU] + if !cpuLimit.IsZero() { + container.CpuLimit = cpuLimit.String() + } + + memRequest := resources.Requests[corev1.ResourceMemory] + if !memRequest.IsZero() { + container.MemoryRequest = memRequest.String() + } + + cpuRequest := resources.Requests[corev1.ResourceCPU] + if !cpuRequest.IsZero() { + container.CpuRequest = cpuRequest.String() + } +} diff --git a/pkg/library/flatten/internal/testutil/common.go b/pkg/library/flatten/internal/testutil/common.go index 593ae1981..b014bbccb 100644 --- a/pkg/library/flatten/internal/testutil/common.go +++ b/pkg/library/flatten/internal/testutil/common.go @@ -37,6 +37,12 @@ var WorkspaceTemplateDiffOpts = cmp.Options{ cmpopts.SortSlices(func(a, b dw.EnvVar) bool { return strings.Compare(a.Name, b.Name) > 0 }), + cmpopts.SortSlices(func(a, b dw.Endpoint) bool { + return strings.Compare(a.Name, b.Name) > 0 + }), + cmpopts.SortSlices(func(a, b dw.VolumeMount) bool { + return strings.Compare(a.Name, b.Name) > 0 + }), // TODO: Devworkspace overriding results in empty []string instead of nil cmpopts.IgnoreFields(dw.DevWorkspaceEvents{}, "PostStart", "PreStop", "PostStop"), } diff --git a/pkg/library/flatten/merge.go b/pkg/library/flatten/merge.go index f33cc59ff..c1feabf26 100644 --- a/pkg/library/flatten/merge.go +++ b/pkg/library/flatten/merge.go @@ -16,10 +16,14 @@ package flatten import ( + "encoding/json" "fmt" + "strings" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/api/v2/pkg/attributes" "github.com/devfile/api/v2/pkg/utils/overriding" + "github.com/devfile/devworkspace-operator/pkg/constants" "k8s.io/apimachinery/pkg/api/resource" ) @@ -118,3 +122,147 @@ func mergeVolume(into, from *dw.VolumeComponent) error { } return nil } + +// 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. +func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec) (bool, error) { + hasContribution, hasTarget := false, false + var errHolder error + for _, component := range flattenedSpec.Components { + if component.Container == nil { + // Ignore attribute on non-container components as it's not clear what this would mean + continue + } + // Need to check existence before value to avoid potential KeyNotFoundError + if component.Attributes.Exists(constants.ContainerContributionAttribute) { + if component.Attributes.GetBoolean(constants.ContainerContributionAttribute, &errHolder) { + hasContribution = true + } + if errHolder != nil { + // 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) { + if component.Attributes.GetBoolean(constants.MergeContributionAttribute, &errHolder) { + 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) + } + } + } + return hasContribution && hasTarget, nil +} + +func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) error { + var contributions []dw.Component + for _, component := range flattenedSpec.Components { + if component.Container != nil && component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) { + contributions = append(contributions, component) + } + } + + var newComponents []dw.Component + mergeDone := false + for _, component := range flattenedSpec.Components { + if component.Container == nil { + newComponents = append(newComponents, component) + continue + } + 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 { + mergedComponent, err := mergeContributionsInto(&component, contributions) + if err != nil { + return fmt.Errorf("failed to merge container contributions: %w", err) + } + newComponents = append(newComponents, *mergedComponent) + mergeDone = true + } else { + newComponents = append(newComponents, component) + } + } + + if mergeDone { + flattenedSpec.Components = newComponents + } + + return nil +} + +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") + } + totalResources, err := parseResourcesFromComponent(mergeInto) + if err != nil { + return nil, err + } + + // We don't want to reimplement the complexity of a strategic merge here, so we set up a fake plugin override + // and use devfile/api overriding functionality. For specific fields that have to be handled specially (memory + // and cpu limits, we compute the value separately and set it at the end + var toMerge []dw.ComponentPluginOverride + // Store names of original plugins to allow us to generate the merged-contributions attribute + var mergedComponentNames []string + for _, component := range contributions { + if component.Container == nil { + return nil, fmt.Errorf("attempting to merge container contribution from a non-container component") + } + // Set name to match target component so that devfile/api override functionality will apply it correctly + component.Name = mergeInto.Name + // Unset image to avoid overriding the default image + component.Container.Image = "" + // Store original source attribute's value and remove from component + if component.Attributes.Exists(constants.PluginSourceAttribute) { + mergedComponentNames = append(mergedComponentNames, component.Attributes.GetString(constants.PluginSourceAttribute, nil)) + delete(component.Attributes, constants.PluginSourceAttribute) + } + if err := addResourceRequirements(totalResources, &component); err != nil { + return nil, err + } + component.Container.MemoryLimit = "" + component.Container.MemoryRequest = "" + component.Container.CpuLimit = "" + component.Container.CpuRequest = "" + // Workaround to convert dw.Component into dw.ComponentPluginOverride: marshal to json, and unmarshal to a different type + // This works since plugin overrides are generated from components, with the difference being that all fields are optional + componentPluginOverride := dw.ComponentPluginOverride{} + tempJSONBytes, err := json.Marshal(component) + if err != nil { + return nil, err + } + if err := json.Unmarshal(tempJSONBytes, &componentPluginOverride); err != nil { + return nil, err + } + toMerge = append(toMerge, componentPluginOverride) + } + + tempSpecContent := &dw.DevWorkspaceTemplateSpecContent{ + Components: []dw.Component{ + *mergeInto, + }, + } + + mergedSpecContent, err := overriding.OverrideDevWorkspaceTemplateSpec(tempSpecContent, dw.PluginOverrides{ + Components: toMerge, + }) + if err != nil { + return nil, err + } + + mergedComponent := mergedSpecContent.Components[0] + applyResourceRequirementsToComponent(mergedComponent.Container, totalResources) + + if mergedComponent.Attributes == nil { + mergedComponent.Attributes = attributes.Attributes{} + } + mergedComponent.Attributes.PutString(constants.MergedContributionsAttribute, strings.Join(mergedComponentNames, ",")) + delete(mergedComponent.Attributes, constants.MergeContributionAttribute) + delete(mergedComponent.Attributes, constants.ContainerContributionAttribute) + + return &mergedComponent, nil +} diff --git a/pkg/library/flatten/testdata/container-contributions/adds-resources.yaml b/pkg/library/flatten/testdata/container-contributions/adds-resources.yaml new file mode 100644 index 000000000..d7760e780 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/adds-resources.yaml @@ -0,0 +1,46 @@ +name: "Adds attributes from contribution" + +input: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merge-contribution: true + 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/container-contributions/adds-unmerged-elements.yaml b/pkg/library/flatten/testdata/container-contributions/adds-unmerged-elements.yaml new file mode 100644 index 000000000..80cde4996 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/adds-unmerged-elements.yaml @@ -0,0 +1,76 @@ +name: "Adds unmerged elements" + +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-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/container-contributions/che-code-usecase.yaml b/pkg/library/flatten/testdata/container-contributions/che-code-usecase.yaml new file mode 100644 index 000000000..ed59ee804 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/che-code-usecase.yaml @@ -0,0 +1,188 @@ +name: "Merges Che Code IDE contribution" + +input: + devworkspace: + components: + - name: tools + attributes: + controller.devfile.io/merge-contribution: true + 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/container-contributions/merges-list-elements.yaml b/pkg/library/flatten/testdata/container-contributions/merges-list-elements.yaml new file mode 100644 index 000000000..1e3f9037e --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/merges-list-elements.yaml @@ -0,0 +1,89 @@ +name: "Merges list elements in contribution" + +input: + devworkspace: + components: + - name: test-component + attributes: + controller.devfile.io/merge-contribution: true + 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/container-contributions/no-op-if-no-contribution.yaml b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml new file mode 100644 index 000000000..72ae5e8bd --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-contribution.yaml @@ -0,0 +1,82 @@ +name: "Adds unmerged elements" + +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-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/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/container-contributions/no-op-if-no-target.yaml b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-target.yaml new file mode 100644 index 000000000..394196f57 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/no-op-if-no-target.yaml @@ -0,0 +1,81 @@ +name: "Adds unmerged elements" + +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-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/container-contribution: true + 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/container-contributions/theia-merge-usecase.yaml b/pkg/library/flatten/testdata/container-contributions/theia-merge-usecase.yaml new file mode 100644 index 000000000..e64282c52 --- /dev/null +++ b/pkg/library/flatten/testdata/container-contributions/theia-merge-usecase.yaml @@ -0,0 +1,386 @@ +name: "Merges Theia IDE contribution" + +input: + devworkspace: + components: + - name: tools + attributes: + controller.devfile.io/merge-contribution: true + 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