|
16 | 16 | package flatten
|
17 | 17 |
|
18 | 18 | import (
|
| 19 | + "encoding/json" |
19 | 20 | "fmt"
|
20 | 21 |
|
21 | 22 | dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
22 | 23 | "github.com/devfile/api/v2/pkg/utils/overriding"
|
| 24 | + "github.com/devfile/devworkspace-operator/pkg/constants" |
23 | 25 | "k8s.io/apimachinery/pkg/api/resource"
|
24 | 26 | )
|
25 | 27 |
|
@@ -118,3 +120,118 @@ func mergeVolume(into, from *dw.VolumeComponent) error {
|
118 | 120 | }
|
119 | 121 | return nil
|
120 | 122 | }
|
| 123 | + |
| 124 | +func needsContainerContributionMerge(flattenedSpec *dw.DevWorkspaceTemplateSpec) bool { |
| 125 | + hasContribution, hasTarget := false, false |
| 126 | + for _, component := range flattenedSpec.Components { |
| 127 | + if component.Container == nil { |
| 128 | + // Ignore attribute on non-container components as it's not clear what this would mean |
| 129 | + continue |
| 130 | + } |
| 131 | + if component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) == true { |
| 132 | + hasContribution = true |
| 133 | + } |
| 134 | + if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) == true { |
| 135 | + hasTarget = true |
| 136 | + } |
| 137 | + } |
| 138 | + return hasContribution && hasTarget |
| 139 | +} |
| 140 | + |
| 141 | +func mergeContainerContributions(flattenedSpec *dw.DevWorkspaceTemplateSpec) error { |
| 142 | + var contributions []dw.Component |
| 143 | + for _, component := range flattenedSpec.Components { |
| 144 | + if component.Container != nil && component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) == true { |
| 145 | + contributions = append(contributions, component) |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + var newComponents []dw.Component |
| 150 | + mergeDone := false |
| 151 | + for _, component := range flattenedSpec.Components { |
| 152 | + if component.Container == nil { |
| 153 | + newComponents = append(newComponents, component) |
| 154 | + continue |
| 155 | + } |
| 156 | + if component.Attributes.GetBoolean(constants.ContainerContributionAttribute, nil) == true { |
| 157 | + // drop contributions from updated list as they will be merged |
| 158 | + continue |
| 159 | + } else if component.Attributes.GetBoolean(constants.MergeContributionAttribute, nil) == true && !mergeDone { |
| 160 | + mergedComponent, err := mergeContributionsInto(&component, contributions) |
| 161 | + if err != nil { |
| 162 | + return fmt.Errorf("failed to merge container contributions: %w", err) |
| 163 | + } |
| 164 | + delete(mergedComponent.Attributes, constants.ContainerContributionAttribute) |
| 165 | + newComponents = append(newComponents, *mergedComponent) |
| 166 | + mergeDone = true |
| 167 | + } else { |
| 168 | + newComponents = append(newComponents, component) |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + if mergeDone { |
| 173 | + flattenedSpec.Components = newComponents |
| 174 | + } |
| 175 | + |
| 176 | + return nil |
| 177 | +} |
| 178 | + |
| 179 | +func mergeContributionsInto(mergeInto *dw.Component, contributions []dw.Component) (*dw.Component, error) { |
| 180 | + if mergeInto == nil || mergeInto.Container == nil { |
| 181 | + return nil, fmt.Errorf("attempting to merge container contributions into a non-container component") |
| 182 | + } |
| 183 | + totalResources, err := parseResourcesFromComponent(mergeInto) |
| 184 | + if err != nil { |
| 185 | + return nil, err |
| 186 | + } |
| 187 | + |
| 188 | + // We don't want to reimplement the complexity of a strategic merge here, so we set up a fake plugin override |
| 189 | + // and use devfile/api overriding functionality. For specific fields that have to be handled specifically (memory |
| 190 | + // and cpu limits, we compute the value separately and set it at the end |
| 191 | + var toMerge []dw.ComponentPluginOverride |
| 192 | + for _, component := range contributions { |
| 193 | + if component.Container == nil { |
| 194 | + return nil, fmt.Errorf("attempting to merge container contribution from a non-container component") |
| 195 | + } |
| 196 | + // Set name to match target component so that devfile/api override functionality will apply it correctly |
| 197 | + component.Name = mergeInto.Name |
| 198 | + // Unset image to avoid overriding the default image |
| 199 | + component.Container.Image = "" |
| 200 | + if err := addResourceRequirements(totalResources, &component); err != nil { |
| 201 | + return nil, err |
| 202 | + } |
| 203 | + component.Container.MemoryLimit = "" |
| 204 | + component.Container.MemoryRequest = "" |
| 205 | + component.Container.CpuLimit = "" |
| 206 | + component.Container.CpuRequest = "" |
| 207 | + // Workaround to convert dw.Component into dw.ComponentPluginOverride: marshal to json, and unmarshal to a different type |
| 208 | + // This works since plugin overrides are generated from components, with the difference being that all fields are optional |
| 209 | + componentPluginOverride := dw.ComponentPluginOverride{} |
| 210 | + tempJSONBytes, err := json.Marshal(component) |
| 211 | + if err != nil { |
| 212 | + return nil, err |
| 213 | + } |
| 214 | + if err := json.Unmarshal(tempJSONBytes, &componentPluginOverride); err != nil { |
| 215 | + return nil, err |
| 216 | + } |
| 217 | + toMerge = append(toMerge, componentPluginOverride) |
| 218 | + } |
| 219 | + |
| 220 | + tempSpecContent := &dw.DevWorkspaceTemplateSpecContent{ |
| 221 | + Components: []dw.Component{ |
| 222 | + *mergeInto, |
| 223 | + }, |
| 224 | + } |
| 225 | + |
| 226 | + mergedSpecContent, err := overriding.OverrideDevWorkspaceTemplateSpec(tempSpecContent, dw.PluginOverrides{ |
| 227 | + Components: toMerge, |
| 228 | + }) |
| 229 | + if err != nil { |
| 230 | + return nil, err |
| 231 | + } |
| 232 | + |
| 233 | + mergedComponent := mergedSpecContent.Components[0] |
| 234 | + applyResourceRequirementsToComponent(mergedComponent.Container, totalResources) |
| 235 | + |
| 236 | + return &mergedComponent, nil |
| 237 | +} |
0 commit comments