Skip to content

Commit d03718d

Browse files
committed
feat: implicit container contributions
This commit introduces 3 changes to the container contribution mechanism: 1. The `controller.devfile.io/merge-contribution` component attribute is no longer required in order to define a target for container contributions. The first container component in a DevWorkspace (that is not imported by a plugin or parent DevWorkspace) is automatically selected as a merge target if the `controller.devfile.io/container-contribution` is used on at least one other DevWorkspace component. However, if the `controller.devfile.io/merge-contribution` attribute is set to `true` for a container component, it will be selected as a target for container contributions. 2. Users can opt out a container component from being implicitly selected as a container contribution merge target by setting the `controller.devfile.io/merge-contribution` attribute to false. 3. If a DevWorkspace has multiple container components with the `controller.devfile.io/merge-contribution` attribute set to true, the workspace fails. Fix #993 Signed-off-by: Andrew Obuchowicz <[email protected]>
1 parent 7e8aa72 commit d03718d

File tree

1 file changed

+73
-6
lines changed

1 file changed

+73
-6
lines changed

pkg/library/flatten/merge.go

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,17 @@ func mergeVolume(into, from *dw.VolumeComponent) error {
124124
}
125125

126126
// needsContainerContributionMerge returns whether merging container contributions is necessary for this workspace. Merging
127-
// is necessary if at least one component has the merge-contribution: true attribute and at least one component has the
128-
// container-contribution: true attribute. If either attribute is present but cannot be parsed as a bool, an error is returned.
127+
// is necessary if the following two conditions are met:
128+
//
129+
// - At least one component has the container-contribution: true attribute.
130+
//
131+
// - At least one component has the merge-contribution: true attribute OR there exists a container component that was not imported by a
132+
// plugin or parent devworkspace.
133+
//
134+
// If the container-contribution or merge-contribution attribute are present but cannot be parsed as a bool, an error is returned.
135+
// If multiple components have the merge-contribution: true attribute, an error is returned.
129136
func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec) (bool, error) {
130-
hasContribution, hasTarget := false, false
137+
hasContribution, hasTarget, explicitTarget := false, false, false
131138
var errHolder error
132139
for _, component := range flattenedSpec.Components {
133140
if component.Container == nil {
@@ -143,14 +150,23 @@ func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec)
143150
// Don't include error in message as it will be propagated to user and is not very clear (references Go unmarshalling)
144151
return false, fmt.Errorf("failed to parse %s attribute on component %s as true or false", constants.ContainerContributionAttribute, component.Name)
145152
}
146-
}
147-
if component.Attributes.Exists(constants.MergeContributionAttribute) {
153+
} else if component.Attributes.Exists(constants.MergeContributionAttribute) {
154+
// Explicit opt out case is handled here if the merge-contributions attribute is set to false
148155
if component.Attributes.GetBoolean(constants.MergeContributionAttribute, &errHolder) {
156+
if explicitTarget {
157+
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)
158+
}
159+
explicitTarget = true
149160
hasTarget = true
150161
}
151162
if errHolder != nil {
152163
return false, fmt.Errorf("failed to parse %s attribute on component %s as true or false", constants.MergeContributionAttribute, component.Name)
153164
}
165+
} else {
166+
if !component.Attributes.Exists(constants.PluginSourceAttribute) {
167+
// First, non-imported container component is implicitly selected as a contribution target
168+
hasTarget = true
169+
}
154170
}
155171
}
156172
return hasContribution && hasTarget, nil
@@ -164,6 +180,11 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err
164180
}
165181
}
166182

183+
targetComponentName, err := findMergeTarget(flattenedSpec)
184+
if err != nil {
185+
return err
186+
}
187+
167188
var newComponents []dw.Component
168189
mergeDone := false
169190
for _, component := range flattenedSpec.Components {
@@ -174,7 +195,7 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err
174195
if component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) {
175196
// drop contributions from updated list as they will be merged
176197
continue
177-
} else if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) && !mergeDone {
198+
} else if component.Name == targetComponentName && !mergeDone {
178199
mergedComponent, err := mergeContributionsInto(&component, contributions)
179200
if err != nil {
180201
return fmt.Errorf("failed to merge container contributions: %w", err)
@@ -193,6 +214,52 @@ func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) err
193214
return nil
194215
}
195216

217+
// Finds a component that is a suitable merge target for container contributions and returns its name.
218+
// The following rules are followed when finding a merge target:
219+
//
220+
// - A container component that has the merge-contribution: true attribute will automatically be selected as a merge target.
221+
//
222+
// - A container component that has the merge-contribution: false attribute will be never be selected as a merge target.
223+
//
224+
// - 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)
225+
// will be selected as a merge target.
226+
//
227+
// If no suitable merge target is found, an error is returned.
228+
func findMergeTarget(flattenedSpec *dw.DevWorkspaceTemplateSpec) (mergeTargetComponentName string, err error) {
229+
firstComponent := ""
230+
for _, component := range flattenedSpec.Components {
231+
if component.Container == nil {
232+
continue
233+
}
234+
235+
if component.Attributes.Exists(constants.MergeContributionAttribute) {
236+
// Check for explicit merge contributtion attribute
237+
if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) {
238+
return component.Name, nil
239+
}
240+
// Don't select components that opt out as a merge contribution target
241+
continue
242+
}
243+
244+
// The target must not have been imported by a plugin or parent.
245+
if component.Attributes.Exists(constants.PluginSourceAttribute) {
246+
continue
247+
}
248+
249+
// There might be other components that explicitly opt in as a merge target,
250+
// so don't return immediately
251+
if firstComponent == "" {
252+
firstComponent = component.Name
253+
}
254+
}
255+
256+
if firstComponent != "" {
257+
return firstComponent, nil
258+
}
259+
260+
return "", fmt.Errorf("couldn't find any merge contribution target component")
261+
}
262+
196263
func mergeContributionsInto(mergeInto *dw.Component, contributions []dw.Component) (*dw.Component, error) {
197264
if mergeInto == nil || mergeInto.Container == nil {
198265
return nil, fmt.Errorf("attempting to merge container contributions into a non-container component")

0 commit comments

Comments
 (0)