@@ -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,92 @@ 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+ resourceName = substituteScenarioVars (resourceName , sc )
1183+
1184+ kind , _ , ok := strings .Cut (resourceName , "/" )
1185+ if ! ok {
1186+ return fmt .Errorf ("invalid resource name format: %s (expected kind/name)" , resourceName )
1187+ }
1188+
1189+ if kind != "deployment" {
1190+ return fmt .Errorf ("only deployment resources are supported for restart annotation, got: %s" , kind )
1191+ }
1192+
1193+ // Use kubectl rollout restart to add the restart annotation
1194+ // This is the actual command users would run, ensuring we test real-world behavior
1195+ _ , err := k8sClient ("rollout" , "restart" , resourceName , "-n" , sc .namespace )
1196+ if err != nil {
1197+ return fmt .Errorf ("failed to rollout restart %s: %w" , resourceName , err )
1198+ }
1199+
1200+ return nil
1201+ }
1202+
1203+ // ResourceHasRestartAnnotation verifies that a deployment has a restart annotation.
1204+ // This confirms that user-initiated changes persist after OLM reconciliation.
1205+ func ResourceHasRestartAnnotation (ctx context.Context , resourceName string ) error {
1206+ sc := scenarioCtx (ctx )
1207+ resourceName = substituteScenarioVars (resourceName , sc )
1208+
1209+ kind , deploymentName , ok := strings .Cut (resourceName , "/" )
1210+ if ! ok {
1211+ return fmt .Errorf ("invalid resource name format: %s (expected kind/name)" , resourceName )
1212+ }
1213+
1214+ if kind != "deployment" {
1215+ return fmt .Errorf ("only deployment resources are supported for restart annotation check, got: %s" , kind )
1216+ }
1217+
1218+ // Check for the restart annotation added by kubectl rollout restart
1219+ restartAnnotationKey := "kubectl.kubernetes.io/restartedAt"
1220+
1221+ waitFor (ctx , func () bool {
1222+ // Get the restart annotation from the deployment's pod template
1223+ out , err := k8sClient ("get" , "deployment" , deploymentName , "-n" , sc .namespace ,
1224+ "-o" , fmt .Sprintf ("jsonpath={.spec.template.metadata.annotations['%s']}" , restartAnnotationKey ))
1225+ if err != nil {
1226+ return false
1227+ }
1228+
1229+ // If the annotation exists and has a value, it persisted
1230+ return out != ""
1231+ })
1232+
1233+ return nil
1234+ }
1235+
1236+ // ClusterExtensionAnnotationIsSet sets an annotation on the ClusterExtension to trigger reconciliation.
1237+ // This simulates changes that would cause the controller to re-apply managed resources.
1238+ func ClusterExtensionAnnotationIsSet (ctx context.Context , annotationKey , annotationValue string ) error {
1239+ sc := scenarioCtx (ctx )
1240+
1241+ // Build the patch to add the annotation
1242+ patch := map [string ]interface {}{
1243+ "metadata" : map [string ]interface {}{
1244+ "annotations" : map [string ]interface {}{
1245+ annotationKey : annotationValue ,
1246+ },
1247+ },
1248+ }
1249+
1250+ patchBytes , err := json .Marshal (patch )
1251+ if err != nil {
1252+ return fmt .Errorf ("failed to marshal annotation patch: %w" , err )
1253+ }
1254+
1255+ // Apply the patch to add the annotation
1256+ _ , err = k8sClient ("patch" , "clusterextension" , sc .clusterExtensionName , "--type" , "merge" , "-p" , string (patchBytes ))
1257+ if err != nil {
1258+ return fmt .Errorf ("failed to patch ClusterExtension with annotation: %w" , err )
1259+ }
1260+
1261+ return nil
1262+ }
0 commit comments