Skip to content

Commit e97685d

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. Users can skip all validations related to this commit by setting the core.openstack.org/skip-custom-images-validation metadata annotations. Co-Authored-By: Claude <[email protected]> Jira: OSPRH-19183
1 parent 9ad9305 commit e97685d

File tree

10 files changed

+1916
-0
lines changed

10 files changed

+1916
-0
lines changed

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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1beta1
1818

1919
import (
2020
"context"
21+
"reflect"
2122
"regexp"
2223

2324
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
@@ -198,6 +199,9 @@ type OpenStackVersionStatus struct {
198199
// ServiceDefaults - struct that contains current defaults for OSP services
199200
ServiceDefaults ServiceDefaults `json:"serviceDefaults,omitempty"`
200201

202+
// TrackedCustomImages tracks CustomContainerImages used for each version to detect changes
203+
TrackedCustomImages map[string]CustomContainerImages `json:"trackedCustomImages,omitempty"`
204+
201205
//ObservedGeneration - the most recent generation observed for this object.
202206
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
203207
}
@@ -288,3 +292,91 @@ func GetOpenStackVersions(namespace string, k8sClient client.Client) (*OpenStack
288292

289293
return versionList, nil
290294
}
295+
296+
// isContainerTemplateEmpty checks if all fields in a ContainerTemplate are nil
297+
func isContainerTemplateEmpty(ct ContainerTemplate) bool {
298+
v := reflect.ValueOf(ct)
299+
numFields := v.NumField()
300+
for i := 0; i < numFields; i++ {
301+
field := v.Field(i)
302+
// Check if field is a pointer and not nil
303+
if field.Kind() == reflect.Ptr && !field.IsNil() {
304+
return false
305+
}
306+
}
307+
return true
308+
}
309+
310+
// customContainerImagesModified compares two CustomContainerImages and returns true if they are different
311+
func customContainerImagesAllModified(a, b CustomContainerImages) bool {
312+
if !containerTemplateEqual(a.ContainerTemplate, b.ContainerTemplate) {
313+
return true
314+
}
315+
316+
if !stringMapEqual(a.CinderVolumeImages, b.CinderVolumeImages) {
317+
return true
318+
}
319+
320+
if !stringMapEqual(a.ManilaShareImages, b.ManilaShareImages) {
321+
return true
322+
}
323+
324+
// If all fields are equal, return false (not modified)
325+
return false
326+
}
327+
328+
// containerTemplateEqual compares two ContainerTemplate structs for equality using reflection
329+
func containerTemplateEqual(a, b ContainerTemplate) bool {
330+
va := reflect.ValueOf(a)
331+
vb := reflect.ValueOf(b)
332+
333+
numFields := va.NumField()
334+
for i := 0; i < numFields; i++ {
335+
fieldA := va.Field(i)
336+
fieldB := vb.Field(i)
337+
338+
// Both fields should be *string type
339+
if fieldA.Kind() != reflect.Ptr || fieldB.Kind() != reflect.Ptr {
340+
continue
341+
}
342+
343+
if fieldA.IsNil() && fieldB.IsNil() {
344+
continue
345+
}
346+
if fieldA.IsNil() || fieldB.IsNil() {
347+
return false
348+
}
349+
if fieldA.Elem().String() != fieldB.Elem().String() {
350+
return false
351+
}
352+
}
353+
354+
return true
355+
}
356+
357+
// stringPtrEqual compares two string pointers for equality
358+
func stringPtrEqual(a, b *string) bool {
359+
if a == nil && b == nil {
360+
return true
361+
}
362+
if a == nil || b == nil {
363+
return false
364+
}
365+
return *a == *b
366+
}
367+
368+
// stringMapEqual compares two string maps for equality
369+
func stringMapEqual(a, b map[string]*string) bool {
370+
if len(a) != len(b) {
371+
return false
372+
}
373+
374+
for key, valueA := range a {
375+
valueB, exists := b[key]
376+
if !exists || !stringPtrEqual(valueA, valueB) {
377+
return false
378+
}
379+
}
380+
381+
return true
382+
}

0 commit comments

Comments
 (0)