@@ -87,6 +87,9 @@ func RegisterSteps(sc *godog.ScenarioContext) {
8787 sc .Step (`^(?i)resource apply fails with error msg containing "([^"]+)"$` , ResourceApplyFails )
8888 sc .Step (`^(?i)resource "([^"]+)" is eventually restored$` , ResourceRestored )
8989 sc .Step (`^(?i)resource "([^"]+)" matches$` , ResourceMatches )
90+ sc .Step (`^(?i)user adds restart annotation to "([^"]+)"$` , UserAddsRestartAnnotation )
91+ sc .Step (`^(?i)resource "([^"]+)" has restart annotation$` , ResourceHasRestartAnnotation )
92+ sc .Step (`^(?i)ClusterExtension annotation "([^"]+)" is set to "([^"]+)"$` , ClusterExtensionAnnotationIsSet )
9093
9194 sc .Step (`^(?i)ServiceAccount "([^"]*)" with needed permissions is available in test namespace$` , ServiceAccountWithNeededPermissionsIsAvailableInNamespace )
9295 sc .Step (`^(?i)ServiceAccount "([^"]*)" with needed permissions is available in \${TEST_NAMESPACE}$` , ServiceAccountWithNeededPermissionsIsAvailableInNamespace )
@@ -1168,3 +1171,95 @@ func latestActiveRevisionForExtension(extName string) (*ocv1.ClusterExtensionRev
11681171
11691172 return latest , nil
11701173}
1174+
1175+ // UserAddsRestartAnnotation simulates a user running `kubectl rollout restart deployment/<name>`.
1176+ // This adds a restart annotation to the deployment's pod template to trigger a rolling restart.
1177+ // In OLMv0, this annotation would be reverted by the controller. In OLMv1 with Server-Side Apply,
1178+ // it should persist because the user (kubectl) manages this field, not the controller.
1179+ // See: https://github.com/operator-framework/operator-lifecycle-manager/issues/3392
1180+ func UserAddsRestartAnnotation (ctx context.Context , resourceName string ) error {
1181+ sc := scenarioCtx (ctx )
1182+
1183+ // Parse the resource name (format: "deployment/test-operator")
1184+ parts := strings .Split (resourceName , "/" )
1185+ if len (parts ) != 2 {
1186+ return fmt .Errorf ("invalid resource name format: %s (expected kind/name)" , resourceName )
1187+ }
1188+ kind := parts [0 ]
1189+
1190+ if kind != "deployment" {
1191+ return fmt .Errorf ("only deployment resources are supported for restart annotation, got: %s" , kind )
1192+ }
1193+
1194+ // Use kubectl rollout restart to add the restart annotation
1195+ // This is the actual command users would run, ensuring we test real-world behavior
1196+ _ , err := k8sClient ("rollout" , "restart" , resourceName , "-n" , sc .namespace )
1197+ if err != nil {
1198+ return fmt .Errorf ("failed to rollout restart %s: %w" , resourceName , err )
1199+ }
1200+
1201+ return nil
1202+ }
1203+
1204+ // ResourceHasRestartAnnotation verifies that a deployment has a restart annotation.
1205+ // This confirms that user-initiated changes persist after OLM reconciliation.
1206+ func ResourceHasRestartAnnotation (ctx context.Context , resourceName string ) error {
1207+ sc := scenarioCtx (ctx )
1208+
1209+ // Parse the resource name (format: "deployment/test-operator")
1210+ parts := strings .Split (resourceName , "/" )
1211+ if len (parts ) != 2 {
1212+ return fmt .Errorf ("invalid resource name format: %s (expected kind/name)" , resourceName )
1213+ }
1214+ kind := parts [0 ]
1215+ deploymentName := parts [1 ]
1216+
1217+ if kind != "deployment" {
1218+ return fmt .Errorf ("only deployment resources are supported for restart annotation check, got: %s" , kind )
1219+ }
1220+
1221+ // Check for the restart annotation added by kubectl rollout restart
1222+ restartAnnotationKey := "kubectl.kubernetes.io/restartedAt"
1223+
1224+ waitFor (ctx , func () bool {
1225+ // Get the restart annotation from the deployment's pod template
1226+ out , err := k8sClient ("get" , "deployment" , deploymentName , "-n" , sc .namespace ,
1227+ "-o" , fmt .Sprintf ("jsonpath={.spec.template.metadata.annotations['%s']}" , restartAnnotationKey ))
1228+ if err != nil {
1229+ return false
1230+ }
1231+
1232+ // If the annotation exists and has a value, it persisted
1233+ return out != ""
1234+ })
1235+
1236+ return nil
1237+ }
1238+
1239+ // ClusterExtensionAnnotationIsSet sets an annotation on the ClusterExtension to trigger reconciliation.
1240+ // This simulates changes that would cause the controller to re-apply managed resources.
1241+ func ClusterExtensionAnnotationIsSet (ctx context.Context , annotationKey , annotationValue string ) error {
1242+ sc := scenarioCtx (ctx )
1243+
1244+ // Build the patch to add the annotation
1245+ patch := map [string ]interface {}{
1246+ "metadata" : map [string ]interface {}{
1247+ "annotations" : map [string ]interface {}{
1248+ annotationKey : annotationValue ,
1249+ },
1250+ },
1251+ }
1252+
1253+ patchBytes , err := json .Marshal (patch )
1254+ if err != nil {
1255+ return fmt .Errorf ("failed to marshal annotation patch: %w" , err )
1256+ }
1257+
1258+ // Apply the patch to add the annotation
1259+ _ , err = k8sClient ("patch" , "clusterextension" , sc .clusterExtensionName , "--type" , "merge" , "-p" , string (patchBytes ))
1260+ if err != nil {
1261+ return fmt .Errorf ("failed to patch ClusterExtension with annotation: %w" , err )
1262+ }
1263+
1264+ return nil
1265+ }
0 commit comments