-
Notifications
You must be signed in to change notification settings - Fork 1.5k
OCPBUGS-78420: preserve user-provided configs when multiple on-disk assets coexist #10390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,10 @@ type assetState struct { | |
| // presentOnDisk is true if the asset in on-disk. This is set whether the | ||
| // asset is sourced from on-disk or not. It is used in purging consumed assets. | ||
| presentOnDisk bool | ||
| // dirtyDueToUserConfigs is true if this asset is dirty only because of user-provided | ||
| // WritableAssets loaded from disk (not because of actual code changes or regeneration needs). | ||
| // This is used to preserve on-disk WritableAssets when multiple user configs coexist. | ||
| dirtyDueToUserConfigs bool | ||
| } | ||
|
|
||
| // storeImpl is the implementation of Store. | ||
|
|
@@ -243,13 +247,22 @@ func (s *storeImpl) load(a asset.Asset, indent string) (*assetState, error) { | |
|
|
||
| // Load dependencies from on-disk. | ||
| anyParentsDirty := false | ||
| dirtyDueToUserConfigs := true // Track if dirty only due to user-provided configs | ||
| for _, d := range a.Dependencies() { | ||
| state, err := s.load(d, increaseIndent(indent)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if state.anyParentsDirty || state.source == onDiskSource { | ||
| anyParentsDirty = true | ||
| // Check if this dependency's dirtiness is due to user configs: | ||
| // - If it's a WritableAsset loaded from disk, it's a user config | ||
| // - If it's dirty only because of user configs (propagated), preserve that status | ||
| _, isWritable := d.(asset.WritableAsset) | ||
| isDueToUserConfig := (isWritable && state.source == onDiskSource) || state.dirtyDueToUserConfigs | ||
| if !isDueToUserConfig { | ||
| dirtyDueToUserConfigs = false | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -273,10 +286,13 @@ func (s *storeImpl) load(a asset.Asset, indent string) (*assetState, error) { | |
| foundInStateFile bool | ||
| onDiskMatchesStateFile bool | ||
| ) | ||
| // Check if asset is in state file (needed for preserving user-provided configs). | ||
| // Normally we skip this check if anyParentsDirty, but we need it for the special case below. | ||
| foundInStateFile = s.isAssetInState(a) | ||
|
|
||
| // Do not need to bother with loading from state file if any of the parents | ||
| // are dirty because the asset must be re-generated in this case. | ||
| if !anyParentsDirty { | ||
| foundInStateFile = s.isAssetInState(a) | ||
| if foundInStateFile { | ||
| stateFileAsset = reflect.New(reflect.TypeOf(a).Elem()).Interface().(asset.Asset) | ||
| if err := s.loadAssetFromState(stateFileAsset); err != nil { | ||
|
|
@@ -301,6 +317,16 @@ func (s *storeImpl) load(a asset.Asset, indent string) (*assetState, error) { | |
| source assetSource | ||
| ) | ||
| switch { | ||
| // Special case: A parent is dirty, but the dirtiness is only due to user-provided configs from disk, | ||
| // AND this asset is also found on disk AND is not in the state file (newly user-provided). | ||
| // In this case, preserve the on-disk version instead of regenerating, since multiple user-provided | ||
| // configs should coexist (e.g., agent-config.yaml with rendezvousIP + nmstateconfig.yaml with | ||
| // network config in the OVE use case). If the asset IS in the state file, it was previously generated | ||
| // and should be regenerated when its inputs change. | ||
| case anyParentsDirty && dirtyDueToUserConfigs && foundOnDisk && !foundInStateFile: | ||
| logrus.Debugf("%sUsing %s loaded from target directory (preserving user-provided config despite on-disk dependencies)", indent, a.Name()) | ||
| assetToStore = onDiskAsset | ||
| source = onDiskSource | ||
|
Comment on lines
+320
to
+329
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This preservation disappears after the first save/load cycle. This branch treats 🤖 Prompt for AI Agents |
||
| // A parent is dirty. The asset must be re-generated. | ||
| case anyParentsDirty: | ||
| if foundOnDisk { | ||
|
|
@@ -324,10 +350,11 @@ func (s *storeImpl) load(a asset.Asset, indent string) (*assetState, error) { | |
| } | ||
|
|
||
| state := &assetState{ | ||
| asset: assetToStore, | ||
| source: source, | ||
| anyParentsDirty: anyParentsDirty, | ||
| presentOnDisk: foundOnDisk, | ||
| asset: assetToStore, | ||
| source: source, | ||
| anyParentsDirty: anyParentsDirty, | ||
| presentOnDisk: foundOnDisk, | ||
| dirtyDueToUserConfigs: dirtyDueToUserConfigs && anyParentsDirty, | ||
| } | ||
| s.assets[reflect.TypeOf(a)] = state | ||
| return state, nil | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The store type is a very fundamental type for the whole installer and I don't think it's a good idea to introduce such low level change with potentially a huge impact area across the repo. It looks like more an issue on how we shaped the specific assets dependencies, so I'd prefer to review that first
/hold
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree this is NOT the best way as it changes the core asset store functionality.