diff --git a/libs/k8s-client/src/Model/Io/Keboola/Apps/V1/AppRunSpec.php b/libs/k8s-client/src/Model/Io/Keboola/Apps/V1/AppRunSpec.php index 768bc387f..dd9d2928a 100644 --- a/libs/k8s-client/src/Model/Io/Keboola/Apps/V1/AppRunSpec.php +++ b/libs/k8s-client/src/Model/Io/Keboola/Apps/V1/AppRunSpec.php @@ -88,4 +88,13 @@ class AppRunSpec extends AbstractModel * @var string|null */ public $imageVersion = null; + + /** + * Mode records how the parent App was running when this run was created. + * "prod" (default) for normal runs; "dev" for dev-mode runs. Treat absent + * or unknown values as "prod" for compatibility with legacy AppRun CRDs. + * + * @var string|null + */ + public $mode = null; } diff --git a/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppDevSpec.php b/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppDevSpec.php new file mode 100644 index 000000000..b7e5bfc0f --- /dev/null +++ b/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppDevSpec.php @@ -0,0 +1,31 @@ +. ISO-8601 duration string (e.g. "1s", "30s", "5m"). + * Default 1s, min 1s, max 5m (enforced at admission via CRD CEL). + * + * @var string|null + */ + public $gitPollInterval = null; + + /** + * AutoRunSetupOnDepChange controls whether the in-pod git-watcher runs + * setup-dev.sh and restarts the app program when a tracked dependency + * file changes during a poll cycle. Default true. + * + * @var bool|null + */ + public $autoRunSetupOnDepChange = null; +} diff --git a/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppSpec.php b/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppSpec.php index 5aa5a196e..0272d55a3 100644 --- a/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppSpec.php +++ b/libs/k8s-client/src/Model/Io/Keboola/Apps/V2/AppSpec.php @@ -83,4 +83,20 @@ class AppSpec extends AbstractModel * @var AppFeatures|null */ public $features = null; + + /** + * Mode selects how the app is run: "prod" (default) runs the app as-is; + * "dev" enables hot-reload via an in-pod git-watcher and the dev + * supervisord profile. Both Deployment and E2B backends honour Mode. + * + * @var string|null + */ + public $mode = null; + + /** + * Dev carries dev-mode-only knobs. Ignored when mode != "dev". + * + * @var AppDevSpec|null + */ + public $dev = null; } diff --git a/libs/k8s-client/tests/Model/V1/AppRunModelTest.php b/libs/k8s-client/tests/Model/V1/AppRunModelTest.php index 6a1d7d031..5abf0a73e 100644 --- a/libs/k8s-client/tests/Model/V1/AppRunModelTest.php +++ b/libs/k8s-client/tests/Model/V1/AppRunModelTest.php @@ -42,6 +42,7 @@ private static function getAppRunTestData(): array 'startupLogs' => "foo\nbar\n", 'runtimeSize' => 'small', 'configVersion' => '1', + 'mode' => 'dev', ], 'status' => [ 'syncedAt' => '2025-01-15T13:05:00Z', @@ -77,6 +78,7 @@ public function testAppRunModelHydration(): void self::assertSame("foo\nbar\n", $appRun->spec->startupLogs); self::assertSame('small', $appRun->spec->runtimeSize); self::assertSame('1', $appRun->spec->configVersion); + self::assertSame('dev', $appRun->spec->mode); // PodRef self::assertNotNull($appRun->spec->podRef); @@ -118,5 +120,6 @@ public function testAppRunModelSerialization(): void self::assertSame('Finished', $serialized['spec']['state']); self::assertSame('small', $serialized['spec']['runtimeSize']); self::assertSame('1', $serialized['spec']['configVersion']); + self::assertSame('dev', $serialized['spec']['mode']); } } diff --git a/libs/k8s-client/tests/Model/V2/AppModelTest.php b/libs/k8s-client/tests/Model/V2/AppModelTest.php index e28ca28c3..265233715 100644 --- a/libs/k8s-client/tests/Model/V2/AppModelTest.php +++ b/libs/k8s-client/tests/Model/V2/AppModelTest.php @@ -5,6 +5,7 @@ namespace Keboola\K8sClient\Tests\Model\V2; use Keboola\K8sClient\Model\Io\Keboola\Apps\V2\App; +use Keboola\K8sClient\Model\Io\Keboola\Apps\V2\AppDevSpec; use Keboola\K8sClient\Model\Io\Keboola\Apps\V2\AppFeatures; use Keboola\K8sClient\Model\Io\Keboola\Apps\V2\AppRuntime; use Keboola\K8sClient\Model\Io\Keboola\Apps\V2\AppSpec; @@ -63,6 +64,11 @@ private static function getAppTestData(): array 'type' => 'e2bSandbox', ], ], + 'mode' => 'dev', + 'dev' => [ + 'gitPollInterval' => '5s', + 'autoRunSetupOnDepChange' => false, + ], 'features' => [ 'storageToken' => [ 'description' => '[_internal][app] App 12345', @@ -251,6 +257,13 @@ public function testStreamlitAppModelHydration(): void self::assertInstanceOf(Backend::class, $app->spec->runtime->backend); self::assertSame('e2bSandbox', $app->spec->runtime->backend->type); + // Mode + Dev + self::assertSame('dev', $app->spec->mode); + self::assertNotNull($app->spec->dev); + self::assertInstanceOf(AppDevSpec::class, $app->spec->dev); + self::assertSame('5s', $app->spec->dev->gitPollInterval); + self::assertFalse($app->spec->dev->autoRunSetupOnDepChange); + // Features self::assertNotNull($app->spec->features); self::assertInstanceOf(AppFeatures::class, $app->spec->features); @@ -475,6 +488,13 @@ public function testAppModelSerialization(): void self::assertSame('e2bSandbox', $serialized['spec']['runtime']['backend']['type']); self::assertSame('small', $serialized['spec']['runtimeSize']); + // Verify mode + dev survive round-trip + self::assertArrayHasKey('mode', $serialized['spec']); + self::assertSame('dev', $serialized['spec']['mode']); + self::assertArrayHasKey('dev', $serialized['spec']); + self::assertSame('5s', $serialized['spec']['dev']['gitPollInterval']); + self::assertFalse($serialized['spec']['dev']['autoRunSetupOnDepChange']); + // Verify containerSpec is present and podSpec is not self::assertArrayHasKey('containerSpec', $serialized['spec']); self::assertArrayNotHasKey('podSpec', $serialized['spec']);