Skip to content

Commit d5224a3

Browse files
committed
Track CustomContainerImages and prevent minor updates without image changes
Add tracking and validation to ensure CustomContainerImages are updated when changing targetVersion during minor updates: - Add TrackedCustomImages field to OpenStackVersionStatus to track CustomContainerImages used for each version - Implement validation webhook logic to prevent targetVersion updates when CustomContainerImages remain unchanged from previous version - Add helper functions for deep comparison of CustomContainerImages - Update controller to automatically track CustomContainerImages for each processed target version - Generate updated CRD manifests with new tracking field This prevents inconsistent deployments where users update the target version but forget to update associated custom container images, ensuring proper version tracking and validation during minor updates. Co-Authored-By: Claude <noreply@anthropic.com> Jira: OSPRH-19183
1 parent 35723e2 commit d5224a3

7 files changed

Lines changed: 882 additions & 0 deletions

File tree

apis/bases/core.openstack.org_openstackversions.yaml

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,203 @@ spec:
674674
glanceWsgi:
675675
type: string
676676
type: object
677+
trackedCustomImages:
678+
additionalProperties:
679+
properties:
680+
agentImage:
681+
type: string
682+
ansibleeeImage:
683+
type: string
684+
aodhAPIImage:
685+
type: string
686+
aodhEvaluatorImage:
687+
type: string
688+
aodhListenerImage:
689+
type: string
690+
aodhNotifierImage:
691+
type: string
692+
apacheImage:
693+
type: string
694+
barbicanAPIImage:
695+
type: string
696+
barbicanKeystoneListenerImage:
697+
type: string
698+
barbicanWorkerImage:
699+
type: string
700+
ceilometerCentralImage:
701+
type: string
702+
ceilometerComputeImage:
703+
type: string
704+
ceilometerIpmiImage:
705+
type: string
706+
ceilometerMysqldExporterImage:
707+
type: string
708+
ceilometerNotificationImage:
709+
type: string
710+
ceilometerSgcoreImage:
711+
type: string
712+
cinderAPIImage:
713+
type: string
714+
cinderBackupImage:
715+
type: string
716+
cinderSchedulerImage:
717+
type: string
718+
cinderVolumeImages:
719+
additionalProperties:
720+
type: string
721+
type: object
722+
designateAPIImage:
723+
type: string
724+
designateBackendbind9Image:
725+
type: string
726+
designateCentralImage:
727+
type: string
728+
designateMdnsImage:
729+
type: string
730+
designateProducerImage:
731+
type: string
732+
designateUnboundImage:
733+
type: string
734+
designateWorkerImage:
735+
type: string
736+
edpmFrrImage:
737+
type: string
738+
edpmIscsidImage:
739+
type: string
740+
edpmKeplerImage:
741+
type: string
742+
edpmLogrotateCrondImage:
743+
type: string
744+
edpmMultipathdImage:
745+
type: string
746+
edpmNeutronDhcpAgentImage:
747+
type: string
748+
edpmNeutronMetadataAgentImage:
749+
type: string
750+
edpmNeutronOvnAgentImage:
751+
type: string
752+
edpmNeutronSriovAgentImage:
753+
type: string
754+
edpmNodeExporterImage:
755+
type: string
756+
edpmOpenstackNetworkExporterImage:
757+
type: string
758+
edpmOvnBgpAgentImage:
759+
type: string
760+
edpmPodmanExporterImage:
761+
type: string
762+
glanceAPIImage:
763+
type: string
764+
heatAPIImage:
765+
type: string
766+
heatCfnapiImage:
767+
type: string
768+
heatEngineImage:
769+
type: string
770+
horizonImage:
771+
type: string
772+
infraDnsmasqImage:
773+
type: string
774+
infraMemcachedImage:
775+
type: string
776+
infraRedisImage:
777+
type: string
778+
ironicAPIImage:
779+
type: string
780+
ironicConductorImage:
781+
type: string
782+
ironicInspectorImage:
783+
type: string
784+
ironicNeutronAgentImage:
785+
type: string
786+
ironicPxeImage:
787+
type: string
788+
ironicPythonAgentImage:
789+
type: string
790+
keystoneAPIImage:
791+
type: string
792+
ksmImage:
793+
type: string
794+
manilaAPIImage:
795+
type: string
796+
manilaSchedulerImage:
797+
type: string
798+
manilaShareImages:
799+
additionalProperties:
800+
type: string
801+
type: object
802+
mariadbImage:
803+
type: string
804+
netUtilsImage:
805+
type: string
806+
neutronAPIImage:
807+
type: string
808+
novaAPIImage:
809+
type: string
810+
novaComputeImage:
811+
type: string
812+
novaConductorImage:
813+
type: string
814+
novaNovncImage:
815+
type: string
816+
novaSchedulerImage:
817+
type: string
818+
octaviaAPIImage:
819+
type: string
820+
octaviaHealthmanagerImage:
821+
type: string
822+
octaviaHousekeepingImage:
823+
type: string
824+
octaviaRsyslogImage:
825+
type: string
826+
octaviaWorkerImage:
827+
type: string
828+
openstackClientImage:
829+
type: string
830+
openstackNetworkExporterImage:
831+
type: string
832+
osContainerImage:
833+
type: string
834+
ovnControllerImage:
835+
type: string
836+
ovnControllerOvsImage:
837+
type: string
838+
ovnNbDbclusterImage:
839+
type: string
840+
ovnNorthdImage:
841+
type: string
842+
ovnSbDbclusterImage:
843+
type: string
844+
placementAPIImage:
845+
type: string
846+
rabbitmqImage:
847+
type: string
848+
swiftAccountImage:
849+
type: string
850+
swiftContainerImage:
851+
type: string
852+
swiftObjectImage:
853+
type: string
854+
swiftProxyImage:
855+
type: string
856+
telemetryNodeExporterImage:
857+
type: string
858+
testAnsibletestImage:
859+
type: string
860+
testHorizontestImage:
861+
type: string
862+
testTempestImage:
863+
type: string
864+
testTobikoImage:
865+
type: string
866+
watcherAPIImage:
867+
type: string
868+
watcherApplierImage:
869+
type: string
870+
watcherDecisionEngineImage:
871+
type: string
872+
type: object
873+
type: object
677874
type: object
678875
type: object
679876
served: true

apis/core/v1beta1/openstackversion_types.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ type OpenStackVersionStatus struct {
198198
// ServiceDefaults - struct that contains current defaults for OSP services
199199
ServiceDefaults ServiceDefaults `json:"serviceDefaults,omitempty"`
200200

201+
// TrackedCustomImages tracks CustomContainerImages used for each version to detect changes
202+
TrackedCustomImages map[string]CustomContainerImages `json:"trackedCustomImages,omitempty"`
203+
201204
//ObservedGeneration - the most recent generation observed for this object.
202205
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
203206
}
@@ -288,3 +291,146 @@ func GetOpenStackVersions(namespace string, k8sClient client.Client) (*OpenStack
288291

289292
return versionList, nil
290293
}
294+
295+
// customContainerImagesEqual compares two CustomContainerImages for equality
296+
func customContainerImagesEqual(a, b CustomContainerImages) bool {
297+
// Compare all ContainerTemplate fields
298+
if !containerTemplateEqual(a.ContainerTemplate, b.ContainerTemplate) {
299+
return false
300+
}
301+
302+
// Compare CinderVolumeImages maps
303+
if !stringMapEqual(a.CinderVolumeImages, b.CinderVolumeImages) {
304+
return false
305+
}
306+
307+
// Compare ManilaShareImages maps
308+
if !stringMapEqual(a.ManilaShareImages, b.ManilaShareImages) {
309+
return false
310+
}
311+
312+
return true
313+
}
314+
315+
// containerTemplateEqual compares two ContainerTemplate structs for equality
316+
func containerTemplateEqual(a, b ContainerTemplate) bool {
317+
return stringPtrEqual(a.AgentImage, b.AgentImage) &&
318+
stringPtrEqual(a.AnsibleeeImage, b.AnsibleeeImage) &&
319+
stringPtrEqual(a.AodhAPIImage, b.AodhAPIImage) &&
320+
stringPtrEqual(a.AodhEvaluatorImage, b.AodhEvaluatorImage) &&
321+
stringPtrEqual(a.AodhListenerImage, b.AodhListenerImage) &&
322+
stringPtrEqual(a.AodhNotifierImage, b.AodhNotifierImage) &&
323+
stringPtrEqual(a.ApacheImage, b.ApacheImage) &&
324+
stringPtrEqual(a.BarbicanAPIImage, b.BarbicanAPIImage) &&
325+
stringPtrEqual(a.BarbicanKeystoneListenerImage, b.BarbicanKeystoneListenerImage) &&
326+
stringPtrEqual(a.BarbicanWorkerImage, b.BarbicanWorkerImage) &&
327+
stringPtrEqual(a.CeilometerCentralImage, b.CeilometerCentralImage) &&
328+
stringPtrEqual(a.CeilometerComputeImage, b.CeilometerComputeImage) &&
329+
stringPtrEqual(a.CeilometerIpmiImage, b.CeilometerIpmiImage) &&
330+
stringPtrEqual(a.CeilometerNotificationImage, b.CeilometerNotificationImage) &&
331+
stringPtrEqual(a.CeilometerSgcoreImage, b.CeilometerSgcoreImage) &&
332+
stringPtrEqual(a.CeilometerMysqldExporterImage, b.CeilometerMysqldExporterImage) &&
333+
stringPtrEqual(a.CinderAPIImage, b.CinderAPIImage) &&
334+
stringPtrEqual(a.CinderBackupImage, b.CinderBackupImage) &&
335+
stringPtrEqual(a.CinderSchedulerImage, b.CinderSchedulerImage) &&
336+
stringPtrEqual(a.DesignateAPIImage, b.DesignateAPIImage) &&
337+
stringPtrEqual(a.DesignateBackendbind9Image, b.DesignateBackendbind9Image) &&
338+
stringPtrEqual(a.DesignateCentralImage, b.DesignateCentralImage) &&
339+
stringPtrEqual(a.DesignateMdnsImage, b.DesignateMdnsImage) &&
340+
stringPtrEqual(a.DesignateProducerImage, b.DesignateProducerImage) &&
341+
stringPtrEqual(a.DesignateUnboundImage, b.DesignateUnboundImage) &&
342+
stringPtrEqual(a.DesignateWorkerImage, b.DesignateWorkerImage) &&
343+
stringPtrEqual(a.EdpmFrrImage, b.EdpmFrrImage) &&
344+
stringPtrEqual(a.EdpmIscsidImage, b.EdpmIscsidImage) &&
345+
stringPtrEqual(a.EdpmLogrotateCrondImage, b.EdpmLogrotateCrondImage) &&
346+
stringPtrEqual(a.EdpmMultipathdImage, b.EdpmMultipathdImage) &&
347+
stringPtrEqual(a.EdpmNeutronDhcpAgentImage, b.EdpmNeutronDhcpAgentImage) &&
348+
stringPtrEqual(a.EdpmNeutronMetadataAgentImage, b.EdpmNeutronMetadataAgentImage) &&
349+
stringPtrEqual(a.EdpmNeutronOvnAgentImage, b.EdpmNeutronOvnAgentImage) &&
350+
stringPtrEqual(a.EdpmNeutronSriovAgentImage, b.EdpmNeutronSriovAgentImage) &&
351+
stringPtrEqual(a.EdpmOvnBgpAgentImage, b.EdpmOvnBgpAgentImage) &&
352+
stringPtrEqual(a.EdpmNodeExporterImage, b.EdpmNodeExporterImage) &&
353+
stringPtrEqual(a.EdpmKeplerImage, b.EdpmKeplerImage) &&
354+
stringPtrEqual(a.EdpmPodmanExporterImage, b.EdpmPodmanExporterImage) &&
355+
stringPtrEqual(a.EdpmOpenstackNetworkExporterImage, b.EdpmOpenstackNetworkExporterImage) &&
356+
stringPtrEqual(a.OpenstackNetworkExporterImage, b.OpenstackNetworkExporterImage) &&
357+
stringPtrEqual(a.GlanceAPIImage, b.GlanceAPIImage) &&
358+
stringPtrEqual(a.HeatAPIImage, b.HeatAPIImage) &&
359+
stringPtrEqual(a.HeatCfnapiImage, b.HeatCfnapiImage) &&
360+
stringPtrEqual(a.HeatEngineImage, b.HeatEngineImage) &&
361+
stringPtrEqual(a.HorizonImage, b.HorizonImage) &&
362+
stringPtrEqual(a.InfraDnsmasqImage, b.InfraDnsmasqImage) &&
363+
stringPtrEqual(a.InfraMemcachedImage, b.InfraMemcachedImage) &&
364+
stringPtrEqual(a.InfraRedisImage, b.InfraRedisImage) &&
365+
stringPtrEqual(a.IronicAPIImage, b.IronicAPIImage) &&
366+
stringPtrEqual(a.IronicConductorImage, b.IronicConductorImage) &&
367+
stringPtrEqual(a.IronicInspectorImage, b.IronicInspectorImage) &&
368+
stringPtrEqual(a.IronicNeutronAgentImage, b.IronicNeutronAgentImage) &&
369+
stringPtrEqual(a.IronicPxeImage, b.IronicPxeImage) &&
370+
stringPtrEqual(a.IronicPythonAgentImage, b.IronicPythonAgentImage) &&
371+
stringPtrEqual(a.KeystoneAPIImage, b.KeystoneAPIImage) &&
372+
stringPtrEqual(a.KsmImage, b.KsmImage) &&
373+
stringPtrEqual(a.ManilaAPIImage, b.ManilaAPIImage) &&
374+
stringPtrEqual(a.ManilaSchedulerImage, b.ManilaSchedulerImage) &&
375+
stringPtrEqual(a.MariadbImage, b.MariadbImage) &&
376+
stringPtrEqual(a.NetUtilsImage, b.NetUtilsImage) &&
377+
stringPtrEqual(a.NeutronAPIImage, b.NeutronAPIImage) &&
378+
stringPtrEqual(a.NovaAPIImage, b.NovaAPIImage) &&
379+
stringPtrEqual(a.NovaComputeImage, b.NovaComputeImage) &&
380+
stringPtrEqual(a.NovaConductorImage, b.NovaConductorImage) &&
381+
stringPtrEqual(a.NovaNovncImage, b.NovaNovncImage) &&
382+
stringPtrEqual(a.NovaSchedulerImage, b.NovaSchedulerImage) &&
383+
stringPtrEqual(a.OctaviaAPIImage, b.OctaviaAPIImage) &&
384+
stringPtrEqual(a.OctaviaHealthmanagerImage, b.OctaviaHealthmanagerImage) &&
385+
stringPtrEqual(a.OctaviaHousekeepingImage, b.OctaviaHousekeepingImage) &&
386+
stringPtrEqual(a.OctaviaWorkerImage, b.OctaviaWorkerImage) &&
387+
stringPtrEqual(a.OctaviaRsyslogImage, b.OctaviaRsyslogImage) &&
388+
stringPtrEqual(a.OpenstackClientImage, b.OpenstackClientImage) &&
389+
stringPtrEqual(a.OsContainerImage, b.OsContainerImage) &&
390+
stringPtrEqual(a.OvnControllerImage, b.OvnControllerImage) &&
391+
stringPtrEqual(a.OvnControllerOvsImage, b.OvnControllerOvsImage) &&
392+
stringPtrEqual(a.OvnNbDbclusterImage, b.OvnNbDbclusterImage) &&
393+
stringPtrEqual(a.OvnNorthdImage, b.OvnNorthdImage) &&
394+
stringPtrEqual(a.OvnSbDbclusterImage, b.OvnSbDbclusterImage) &&
395+
stringPtrEqual(a.PlacementAPIImage, b.PlacementAPIImage) &&
396+
stringPtrEqual(a.RabbitmqImage, b.RabbitmqImage) &&
397+
stringPtrEqual(a.SwiftAccountImage, b.SwiftAccountImage) &&
398+
stringPtrEqual(a.SwiftContainerImage, b.SwiftContainerImage) &&
399+
stringPtrEqual(a.SwiftObjectImage, b.SwiftObjectImage) &&
400+
stringPtrEqual(a.SwiftProxyImage, b.SwiftProxyImage) &&
401+
stringPtrEqual(a.TelemetryNodeExporterImage, b.TelemetryNodeExporterImage) &&
402+
stringPtrEqual(a.TestTempestImage, b.TestTempestImage) &&
403+
stringPtrEqual(a.TestTobikoImage, b.TestTobikoImage) &&
404+
stringPtrEqual(a.TestHorizontestImage, b.TestHorizontestImage) &&
405+
stringPtrEqual(a.TestAnsibletestImage, b.TestAnsibletestImage) &&
406+
stringPtrEqual(a.WatcherAPIImage, b.WatcherAPIImage) &&
407+
stringPtrEqual(a.WatcherApplierImage, b.WatcherApplierImage) &&
408+
stringPtrEqual(a.WatcherDecisionEngineImage, b.WatcherDecisionEngineImage)
409+
}
410+
411+
// stringPtrEqual compares two string pointers for equality
412+
func stringPtrEqual(a, b *string) bool {
413+
if a == nil && b == nil {
414+
return true
415+
}
416+
if a == nil || b == nil {
417+
return false
418+
}
419+
return *a == *b
420+
}
421+
422+
// stringMapEqual compares two string maps for equality
423+
func stringMapEqual(a, b map[string]*string) bool {
424+
if len(a) != len(b) {
425+
return false
426+
}
427+
428+
for key, valueA := range a {
429+
valueB, exists := b[key]
430+
if !exists || !stringPtrEqual(valueA, valueB) {
431+
return false
432+
}
433+
}
434+
435+
return true
436+
}

0 commit comments

Comments
 (0)