This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new d6ddcca717 issue #7656: load old lifecycle environments without 
marketplace env errors (#7657)
d6ddcca717 is described below

commit d6ddcca7176c714645bd16e619fc5495545306af
Author: Matt Casters <[email protected]>
AuthorDate: Tue Jul 28 11:37:11 2026 +0200

    issue #7656: load old lifecycle environments without marketplace env errors 
(#7657)
    
    * issue #7656: do not fail enabling environments without marketplace env
    
    Old lifecycle environments have no marketplace attributes. Runtime now
    treats missing onEnable as off, and missing envFile as a silent skip so
    project switches no longer error when hop-env.yaml is absent.
    
    * issue #7656: never block Hop GUI when enabling a lifecycle environment
    
    Marketplace onEnable checks only warn in the GUI (including enforce).
    Any failure is caught so project/environment open always succeeds;
    hard-fail remains for non-GUI runtimes only.
---
 .../hop/marketplace/env/MarketplaceAttributes.java |  29 ++-
 ...fecycleEnvironmentDialogTabsExtensionPoint.java |  10 +-
 ...ojectEnvironmentAfterEnabledExtensionPoint.java | 197 +++++++++++++++------
 .../gui/messages/messages_en_US.properties         |   2 +-
 .../marketplace/env/MarketplaceAttributesTest.java |  26 ++-
 5 files changed, 193 insertions(+), 71 deletions(-)

diff --git 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/env/MarketplaceAttributes.java
 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/env/MarketplaceAttributes.java
index faef714c38..c1cb856d93 100644
--- 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/env/MarketplaceAttributes.java
+++ 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/env/MarketplaceAttributes.java
@@ -51,22 +51,32 @@ public final class MarketplaceAttributes {
   private MarketplaceAttributes() {}
 
   /**
-   * Resolve on-enable policy from attributes, falling back to purpose-based 
defaults when unset.
+   * Resolve on-enable policy from attributes for <strong>runtime</strong> 
checks.
    *
-   * <ul>
-   *   <li>Production → enforce
-   *   <li>Testing / Acceptance → warn
-   *   <li>Development / CI / CB / other → off
-   * </ul>
+   * <p>When {@link #KEY_ON_ENABLE} is unset (typical of pre-marketplace 
lifecycle environments),
+   * returns {@link #ON_ENABLE_OFF} so enabling a project/environment never 
fails solely because the
+   * marketplace plugin is present. Purpose-based suggestions are only for the 
UI via {@link
+   * #defaultOnEnableForPurpose(String)}.
    */
   public static String resolveOnEnable(IAttributes attributes, String purpose) 
{
     String explicit = attributes != null ? attributes.getAttribute(GROUP, 
KEY_ON_ENABLE) : null;
     if (StringUtils.isNotBlank(explicit)) {
       return explicit.trim().toLowerCase();
     }
-    return defaultOnEnableForPurpose(purpose);
+    // Missing attribute → opt-out (issue #7656: old hop configs must load 
silently)
+    return ON_ENABLE_OFF;
   }
 
+  /**
+   * Suggested on-enable value when the user has not saved a marketplace 
choice yet (dialog defaults
+   * only — not used for runtime enforcement).
+   *
+   * <ul>
+   *   <li>Production → enforce
+   *   <li>Testing / Acceptance → warn
+   *   <li>Development / CI / CB / other → off
+   * </ul>
+   */
   public static String defaultOnEnableForPurpose(String purpose) {
     if (StringUtils.isBlank(purpose)) {
       return ON_ENABLE_OFF;
@@ -81,6 +91,11 @@ public final class MarketplaceAttributes {
     return ON_ENABLE_OFF;
   }
 
+  /** True when the lifecycle environment has an explicit marketplace envFile 
attribute. */
+  public static boolean hasExplicitEnvFile(IAttributes attributes) {
+    return StringUtils.isNotBlank(envFile(attributes));
+  }
+
   public static boolean isStrict(IAttributes attributes) {
     if (attributes == null) {
       return false;
diff --git 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/LifecycleEnvironmentDialogTabsExtensionPoint.java
 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/LifecycleEnvironmentDialogTabsExtensionPoint.java
index 95590eea6a..b5eb8e22d2 100644
--- 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/LifecycleEnvironmentDialogTabsExtensionPoint.java
+++ 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/LifecycleEnvironmentDialogTabsExtensionPoint.java
@@ -214,8 +214,14 @@ public class LifecycleEnvironmentDialogTabsExtensionPoint
       return;
     }
     wEnvFile.setText(Const.NVL(MarketplaceAttributes.envFile(context), ""));
-    // Explicit attribute, or purpose-based default when unset (not written 
until save).
-    wOnEnable.setText(MarketplaceAttributes.resolveOnEnable(context, 
context.getPurpose()));
+    // Runtime uses OFF when unset; dialog suggests a purpose-based default 
until the user saves.
+    String storedOnEnable =
+        context.getAttribute(MarketplaceAttributes.GROUP, 
MarketplaceAttributes.KEY_ON_ENABLE);
+    if (StringUtils.isNotBlank(storedOnEnable)) {
+      wOnEnable.setText(storedOnEnable.trim());
+    } else {
+      
wOnEnable.setText(MarketplaceAttributes.defaultOnEnableForPurpose(context.getPurpose()));
+    }
     wStrict.setSelection(MarketplaceAttributes.isStrict(context));
     wAutoApply.setSelection(MarketplaceAttributes.isAutoApply(context));
   }
diff --git 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/ProjectEnvironmentAfterEnabledExtensionPoint.java
 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/ProjectEnvironmentAfterEnabledExtensionPoint.java
index f12002b983..48754a9478 100644
--- 
a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/ProjectEnvironmentAfterEnabledExtensionPoint.java
+++ 
b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/xp/ProjectEnvironmentAfterEnabledExtensionPoint.java
@@ -44,8 +44,14 @@ import org.eclipse.swt.SWT;
 
 /**
  * When a project lifecycle environment is enabled, optionally validate (or 
apply) the marketplace
- * hop-env file against the local install. Settings come from {@link 
AttributesContext} group {@link
- * MarketplaceAttributes#GROUP} — no dependency on the Projects plugin classes.
+ * hop-env file against the local install.
+ *
+ * <p><strong>Hop GUI:</strong> this extension <em>never</em> throws. Missing 
config, missing
+ * hop-env, parse errors, and drift only log (and optionally show a warning 
dialog). Blocking enable
+ * would trap users out of their environment with no GUI recovery path (issue 
#7656).
+ *
+ * <p><strong>Non-GUI:</strong> {@code onEnable=enforce} may still throw so 
automation can hard-fail
+ * (prefer {@code hop marketplace validate} / hop-run checks for CI).
  */
 @ExtensionPoint(
     id = "MarketplaceProjectEnvironmentAfterEnabled",
@@ -58,16 +64,45 @@ public class ProjectEnvironmentAfterEnabledExtensionPoint
   @Override
   public void callExtensionPoint(ILogChannel log, IVariables variables, 
AttributesContext context)
       throws HopException {
+    // Outer guard: never let marketplace break environment enable in the GUI.
+    try {
+      runCheck(log, variables, context);
+    } catch (Exception e) {
+      if (isGuiRuntime()) {
+        if (log != null) {
+          log.logError(
+              "Marketplace environment check failed but environment enable 
continues: "
+                  + e.getMessage(),
+              e);
+        }
+        warnGui(
+            "Marketplace check failed",
+            "The marketplace could not validate hop-env for this 
environment.\n\n"
+                + Const.NVL(e.getMessage(), e.getClass().getSimpleName())
+                + "\n\nThe environment was still enabled.");
+        return;
+      }
+      if (e instanceof HopException he) {
+        throw he;
+      }
+      throw new HopException("Marketplace environment check failed", e);
+    }
+  }
+
+  private void runCheck(ILogChannel log, IVariables variables, 
AttributesContext context)
+      throws Exception {
     if (context == null) {
       return;
     }
 
     String onEnable = MarketplaceAttributes.resolveOnEnable(context, 
context.getPurpose());
     if (MarketplaceAttributes.ON_ENABLE_OFF.equals(onEnable)) {
-      log.logDetailed(
-          "Marketplace environment check skipped (onEnable=off) for 
environment '"
-              + Const.NVL(context.getEnvironmentName(), "")
-              + "'");
+      if (log != null) {
+        log.logDetailed(
+            "Marketplace environment check skipped (onEnable=off) for 
environment '"
+                + Const.NVL(context.getEnvironmentName(), "")
+                + "'");
+      }
       return;
     }
 
@@ -75,83 +110,133 @@ public class ProjectEnvironmentAfterEnabledExtensionPoint
     try {
       hopHome = HopHome.resolve();
     } catch (HopException e) {
-      log.logDetailed("Marketplace environment check skipped: Hop home not 
resolved");
+      if (log != null) {
+        log.logDetailed("Marketplace environment check skipped: Hop home not 
resolved");
+      }
       return;
     }
 
+    boolean explicitEnvFile = 
MarketplaceAttributes.hasExplicitEnvFile(context);
     Path envFile = resolveEnvFile(context, variables, hopHome);
     if (envFile == null) {
+      // No hop-env configured and none found: silent skip (issue #7656).
+      if (!explicitEnvFile) {
+        if (log != null) {
+          log.logDetailed(
+              "Marketplace environment check skipped (no envFile attribute and 
no hop-env.yaml) for '"
+                  + Const.NVL(context.getEnvironmentName(), "")
+                  + "'");
+        }
+        return;
+      }
       String msg =
           "Marketplace environment file not found for environment '"
               + Const.NVL(context.getEnvironmentName(), "")
-              + "'. Set marketplace attribute envFile or place hop-env.yaml 
under the project home.";
-      if (MarketplaceAttributes.ON_ENABLE_ENFORCE.equals(onEnable)) {
+              + "': "
+              + Const.NVL(MarketplaceAttributes.envFile(context), "")
+              + ". Fix marketplace attribute envFile or place hop-env.yaml 
under the project home.";
+      // GUI: never block open — warn only. Non-GUI enforce may throw.
+      if (MarketplaceAttributes.ON_ENABLE_ENFORCE.equals(onEnable) && 
!isGuiRuntime()) {
         throw new HopException(msg);
       }
-      log.logBasic(msg);
+      if (log != null) {
+        log.logBasic(msg);
+      }
+      if (isGuiRuntime()) {
+        warnGui("Marketplace environment file missing", msg);
+      }
       return;
     }
 
-    try {
-      MarketplaceConfig config = MarketplaceConfig.load();
-      HopEnvironmentSpec env = HopEnvironmentLoader.load(envFile);
-      EnvironmentApplier applier = new EnvironmentApplier(log, hopHome, 
config);
-      EnvironmentDrift drift = applier.validate(env);
+    MarketplaceConfig config = MarketplaceConfig.load();
+    HopEnvironmentSpec env = HopEnvironmentLoader.load(envFile);
+    EnvironmentApplier applier = new EnvironmentApplier(log, hopHome, config);
+    EnvironmentDrift drift = applier.validate(env);
 
-      if (MarketplaceAttributes.isStrict(context)) {
-        populateExtraPlugins(hopHome, env, drift);
-      }
+    if (MarketplaceAttributes.isStrict(context)) {
+      populateExtraPlugins(hopHome, env, drift);
+    }
 
-      boolean hard =
-          !drift.getMissingPlugins().isEmpty()
-              || !drift.getVersionMismatches().isEmpty()
-              || !drift.getMissingDependencies().isEmpty()
-              || (MarketplaceAttributes.isStrict(context)
-                  && !drift.getExtraMarketplacePlugins().isEmpty());
+    boolean hard =
+        !drift.getMissingPlugins().isEmpty()
+            || !drift.getVersionMismatches().isEmpty()
+            || !drift.getMissingDependencies().isEmpty()
+            || (MarketplaceAttributes.isStrict(context)
+                && !drift.getExtraMarketplacePlugins().isEmpty());
 
-      if (!hard) {
+    if (!hard) {
+      if (log != null) {
         log.logBasic("Marketplace environment file " + envFile + " matches 
local install.");
-        return;
       }
+      return;
+    }
 
-      String report =
-          "Environment drift for '"
-              + Const.NVL(context.getEnvironmentName(), "")
-              + "' against "
-              + envFile
-              + ":\n"
-              + drift.formatReport()
-              + "Run 'hop marketplace apply -f "
-              + envFile
-              + "' to fix your environment.";
-
-      if (MarketplaceAttributes.isAutoApply(context) && config.isEnabled()) {
+    String report =
+        "Environment drift for '"
+            + Const.NVL(context.getEnvironmentName(), "")
+            + "' against "
+            + envFile
+            + ":\n"
+            + drift.formatReport()
+            + "Run 'hop marketplace apply -f "
+            + envFile
+            + "' to fix your environment.";
+
+    if (MarketplaceAttributes.isAutoApply(context) && config.isEnabled()) {
+      if (log != null) {
         log.logBasic("Auto-applying marketplace environment file " + envFile);
+      }
+      try {
         applier.apply(env, false);
-        return;
+      } catch (Exception applyEx) {
+        // Auto-apply must not strand the GUI without an open environment.
+        if (isGuiRuntime()) {
+          if (log != null) {
+            log.logError("Marketplace auto-apply failed; environment remains 
enabled", applyEx);
+          }
+          warnGui(
+              "Marketplace auto-apply failed",
+              report
+                  + "\n\nAuto-apply error: "
+                  + Const.NVL(applyEx.getMessage(), 
applyEx.getClass().getSimpleName())
+                  + "\n\nThe environment was still enabled.");
+          return;
+        }
+        throw applyEx;
       }
+      return;
+    }
 
-      if (MarketplaceAttributes.ON_ENABLE_ENFORCE.equals(onEnable)) {
-        throw new HopException("FATAL: " + report);
-      }
+    if (MarketplaceAttributes.ON_ENABLE_ENFORCE.equals(onEnable) && 
!isGuiRuntime()) {
+      throw new HopException("FATAL: " + report);
+    }
 
-      // warn
+    // warn (and in GUI always warn-only, even if onEnable was enforce)
+    if (log != null) {
       log.logError(report);
-      if ("GUI".equalsIgnoreCase(Const.getHopPlatformRuntime())) {
-        try {
-          MessageBox box =
-              new MessageBox(HopGui.getInstance().getShell(), SWT.OK | 
SWT.ICON_WARNING);
-          box.setText("Marketplace environment drift");
-          box.setMessage(report);
-          box.open();
-        } catch (Exception e) {
-          // headless or shell unavailable
-        }
+      if (MarketplaceAttributes.ON_ENABLE_ENFORCE.equals(onEnable) && 
isGuiRuntime()) {
+        log.logBasic(
+            "Marketplace onEnable=enforce is treated as warn in Hop GUI so 
environments can always"
+                + " be opened. Use hop-run / marketplace validate for hard 
fail.");
       }
-    } catch (HopException e) {
-      throw e;
+    }
+    if (isGuiRuntime()) {
+      warnGui("Marketplace environment drift", report);
+    }
+  }
+
+  static boolean isGuiRuntime() {
+    return "GUI".equalsIgnoreCase(Const.getHopPlatformRuntime());
+  }
+
+  private static void warnGui(String title, String message) {
+    try {
+      MessageBox box = new MessageBox(HopGui.getInstance().getShell(), SWT.OK 
| SWT.ICON_WARNING);
+      box.setText(title);
+      box.setMessage(message);
+      box.open();
     } catch (Exception e) {
-      throw new HopException("Failed to validate marketplace environment file 
" + envFile, e);
+      // headless or shell unavailable
     }
   }
 
diff --git 
a/plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/gui/messages/messages_en_US.properties
 
b/plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/gui/messages/messages_en_US.properties
index fbfe263eb5..ed67006ff2 100644
--- 
a/plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/gui/messages/messages_en_US.properties
+++ 
b/plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/gui/messages/messages_en_US.properties
@@ -24,7 +24,7 @@ MarketplaceLifecycleEnv.EnvFile.Label=Environment file
 MarketplaceLifecycleEnv.Button.Browse=Browse…
 MarketplaceLifecycleEnv.Button.EditEnv=Edit…
 MarketplaceLifecycleEnv.OnEnable.Label=On enable
-MarketplaceLifecycleEnv.OnEnable.Tooltip=off: no check; warn: log/dialog on 
drift; enforce: fail enabling the environment on drift
+MarketplaceLifecycleEnv.OnEnable.Tooltip=off: no check; warn: log/dialog on 
drift; enforce: hard-fail only outside Hop GUI (hop-run / automation). In Hop 
GUI, enforce is treated as warn so environments always open.
 MarketplaceLifecycleEnv.Strict.Label=Strict (fail if extra marketplace plugins 
installed)
 MarketplaceLifecycleEnv.Strict.Tooltip=When validating, also treat marketplace 
plugins with receipts that are not listed in the env file as drift
 MarketplaceLifecycleEnv.AutoApply.Label=Auto-apply missing plugins on enable
diff --git 
a/plugins/misc/marketplace/src/test/java/org/apache/hop/marketplace/env/MarketplaceAttributesTest.java
 
b/plugins/misc/marketplace/src/test/java/org/apache/hop/marketplace/env/MarketplaceAttributesTest.java
index a7e28d0e56..0886f13121 100644
--- 
a/plugins/misc/marketplace/src/test/java/org/apache/hop/marketplace/env/MarketplaceAttributesTest.java
+++ 
b/plugins/misc/marketplace/src/test/java/org/apache/hop/marketplace/env/MarketplaceAttributesTest.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
 class MarketplaceAttributesTest {
 
   @Test
-  void purposeDefaults() {
+  void purposeDefaultsForUiOnly() {
     assertEquals(
         MarketplaceAttributes.ON_ENABLE_ENFORCE,
         MarketplaceAttributes.defaultOnEnableForPurpose("Production"));
@@ -45,15 +45,31 @@ class MarketplaceAttributesTest {
   }
 
   @Test
-  void explicitOverridesPurpose() {
+  void unsetOnEnableIsOffAtRuntimeEvenForProduction() {
+    // Old hop configs have no marketplace attributes — must not enforce 
(issue #7656)
+    AttributesContext ctx = new AttributesContext();
+    assertEquals(
+        MarketplaceAttributes.ON_ENABLE_OFF,
+        MarketplaceAttributes.resolveOnEnable(ctx, "Production"));
+    assertEquals(
+        MarketplaceAttributes.ON_ENABLE_OFF,
+        MarketplaceAttributes.resolveOnEnable(null, "Production"));
+    assertFalse(MarketplaceAttributes.hasExplicitEnvFile(ctx));
+  }
+
+  @Test
+  void explicitOverridesUnsetDefault() {
     AttributesContext ctx = new AttributesContext();
     ctx.setAttribute(
         MarketplaceAttributes.GROUP,
         MarketplaceAttributes.KEY_ON_ENABLE,
-        MarketplaceAttributes.ON_ENABLE_OFF);
+        MarketplaceAttributes.ON_ENABLE_ENFORCE);
     assertEquals(
-        MarketplaceAttributes.ON_ENABLE_OFF,
-        MarketplaceAttributes.resolveOnEnable(ctx, "Production"));
+        MarketplaceAttributes.ON_ENABLE_ENFORCE,
+        MarketplaceAttributes.resolveOnEnable(ctx, "Development"));
+    ctx.setAttribute(
+        MarketplaceAttributes.GROUP, MarketplaceAttributes.KEY_ENV_FILE, 
"/tmp/hop-env.yaml");
+    assertTrue(MarketplaceAttributes.hasExplicitEnvFile(ctx));
   }
 
   @Test

Reply via email to