Jelajahi Sumber

support worker health command on frontend (#4339)

d-g-town 2 tahun lalu
induk
melakukan
e99eb1a50f

+ 7 - 7
dashboard/package-lock.json

@@ -95,7 +95,7 @@
         "@babel/preset-typescript": "^7.15.0",
         "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
         "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
-        "@porter-dev/api-contracts": "^0.2.103",
+        "@porter-dev/api-contracts": "^0.2.112",
         "@testing-library/jest-dom": "^4.2.4",
         "@testing-library/react": "^9.3.2",
         "@testing-library/user-event": "^7.1.2",
@@ -2754,9 +2754,9 @@
       }
     },
     "node_modules/@porter-dev/api-contracts": {
-      "version": "0.2.103",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.103.tgz",
-      "integrity": "sha512-D8Ky1jBWSi4oLsMIl1DCQSy6QRfxXsrkRuj6eT16YGkwQ1tTwsb9zP2l0mCvYHzZyDb/4l4l57E+IJzxd9uEhw==",
+      "version": "0.2.112",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.112.tgz",
+      "integrity": "sha512-O+BN4GNe3YQXzatCFOm3SNyZEg+iYrlrcLv9M2pwmur0bBg1lHszHYAa1idTMivcGVW3ynAO1kMGpjHRdOHgOA==",
       "dev": true,
       "dependencies": {
         "@bufbuild/protobuf": "^1.1.0"
@@ -20056,9 +20056,9 @@
       "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
     },
     "@porter-dev/api-contracts": {
-      "version": "0.2.103",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.103.tgz",
-      "integrity": "sha512-D8Ky1jBWSi4oLsMIl1DCQSy6QRfxXsrkRuj6eT16YGkwQ1tTwsb9zP2l0mCvYHzZyDb/4l4l57E+IJzxd9uEhw==",
+      "version": "0.2.112",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.112.tgz",
+      "integrity": "sha512-O+BN4GNe3YQXzatCFOm3SNyZEg+iYrlrcLv9M2pwmur0bBg1lHszHYAa1idTMivcGVW3ynAO1kMGpjHRdOHgOA==",
       "dev": true,
       "requires": {
         "@bufbuild/protobuf": "^1.1.0"

+ 1 - 1
dashboard/package.json

@@ -102,7 +102,7 @@
     "@babel/preset-typescript": "^7.15.0",
     "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
-    "@porter-dev/api-contracts": "^0.2.103",
+    "@porter-dev/api-contracts": "^0.2.112",
     "@testing-library/jest-dom": "^4.2.4",
     "@testing-library/react": "^9.3.2",
     "@testing-library/user-event": "^7.1.2",

+ 10 - 0
dashboard/src/lib/porter-apps/services.ts

@@ -72,6 +72,7 @@ export type ClientWebConfig = z.infer<typeof webConfigValidator>;
 const workerConfigValidator = z.object({
   type: z.literal("worker"),
   autoscaling: autoscalingValidator.optional(),
+  healthCheck: healthcheckValidator.optional(),
 });
 export type ClientWorkerConfig = z.infer<typeof workerConfigValidator>;
 
@@ -160,6 +161,7 @@ export type SerializedService = {
     | {
         type: "worker";
         autoscaling?: SerializedAutoscaling;
+        healthCheck?: SerializedHealthcheck;
       }
     | {
         type: "job";
@@ -261,6 +263,7 @@ export function defaultSerialized({
       config: {
         type: "worker" as const,
         autoscaling: defaultAutoscaling,
+        healthCheck: defaultHealthCheck,
       },
     }))
     .with("job", () => ({
@@ -328,6 +331,7 @@ export function serializeService(service: ClientService): SerializedService {
           autoscaling: serializeAutoscaling({
             autoscaling: config.autoscaling,
           }),
+          healthCheck: serializeHealth({ health: config.healthCheck }),
         })
       )
       .with({ type: "job" }, (config) =>
@@ -505,6 +509,11 @@ export function deserializeService({
             override: overrideWorkerConfig?.autoscaling,
             setDefaults,
           }),
+          healthCheck: deserializeHealthCheck({
+            health: config.healthCheck,
+            override: overrideWorkerConfig?.healthCheck,
+            setDefaults,
+          }),
         },
       };
     })
@@ -673,6 +682,7 @@ export function serializedServiceFromProto({
       config: {
         type: "worker" as const,
         autoscaling: value.autoscaling ? value.autoscaling : undefined,
+        healthCheck: value.healthCheck ? value.healthCheck : undefined,
         ...value,
       },
     }))

+ 7 - 0
dashboard/src/lib/porter-apps/values.ts

@@ -160,11 +160,13 @@ export function deserializeAutoscaling({
 export const healthcheckValidator = z.object({
   enabled: serviceBooleanValidator,
   httpPath: serviceStringValidator.optional(),
+  command: serviceStringValidator.optional(),
 });
 export type ClientHealthCheck = z.infer<typeof healthcheckValidator>;
 export type SerializedHealthcheck = {
   enabled: boolean;
   httpPath?: string;
+  command?: string;
 };
 
 export function serializeHealth({
@@ -176,6 +178,7 @@ export function serializeHealth({
     health && {
       enabled: health.enabled.value,
       httpPath: health.httpPath?.value,
+      command: health.command?.value,
     }
   );
 }
@@ -194,11 +197,15 @@ export function deserializeHealthCheck({
         httpPath: health.httpPath
           ? ServiceField.string(health.httpPath, override?.httpPath)
           : ServiceField.string("", undefined),
+        command: health.command
+          ? ServiceField.string(health.command, override?.command)
+          : ServiceField.string("", undefined),
       }
     : setDefaults
     ? {
         enabled: ServiceField.boolean(false, undefined),
         httpPath: ServiceField.string("", undefined),
+        command: ServiceField.string("", undefined),
       }
     : undefined;
 }

+ 30 - 15
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Health.tsx

@@ -10,9 +10,10 @@ import { type PorterAppFormData } from "lib/porter-apps";
 
 type HealthProps = {
   index: number;
+  command?: boolean;
 };
 
-const Health: React.FC<HealthProps> = ({ index }) => {
+const Health: React.FC<HealthProps> = ({ index, command = false }) => {
   const { register, control, watch } = useFormContext<PorterAppFormData>();
 
   const healthCheckEnabled = watch(
@@ -58,20 +59,34 @@ const Health: React.FC<HealthProps> = ({ index }) => {
           </Checkbox>
         )}
       />
-      {healthCheckEnabled.value && (
-        <>
-          <Spacer y={0.5} />
-          <Text>Health check endpoint</Text>
-          <Spacer y={0.5} />
-          <ControlledInput
-            type="text"
-            placeholder="ex: /healthz"
-            {...register(
-              `app.services.${index}.config.healthCheck.httpPath.value`
-            )}
-          />
-        </>
-      )}
+      {healthCheckEnabled.value &&
+        (command ? (
+          <>
+            <Spacer y={0.5} />
+            <Text>Health check command</Text>
+            <Spacer y={0.5} />
+            <ControlledInput
+              type="text"
+              placeholder="ex: ./healthz.sh"
+              {...register(
+                `app.services.${index}.config.healthCheck.command.value`
+              )}
+            />
+          </>
+        ) : (
+          <>
+            <Spacer y={0.5} />
+            <Text>Health check endpoint</Text>
+            <Spacer y={0.5} />
+            <ControlledInput
+              type="text"
+              placeholder="ex: /healthz"
+              {...register(
+                `app.services.${index}.config.healthCheck.httpPath.value`
+              )}
+            />
+          </>
+        ))}
     </>
   );
 };

+ 2 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/WorkerTabs.tsx

@@ -6,6 +6,7 @@ import TabSelector from "components/TabSelector";
 import { type ClientService } from "lib/porter-apps/services";
 
 import Advanced from "./Advanced";
+import Health from "./Health";
 import MainTab from "./Main";
 import Resources from "./Resources";
 
@@ -59,6 +60,7 @@ const WorkerTabs: React.FC<Props> = ({
         ))
         .with("advanced", () => (
           <>
+            <Health index={index} command={true} />
             <Spacer y={1} />
             <Advanced index={index} />
           </>

+ 1 - 1
go.mod

@@ -83,7 +83,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.111
+	github.com/porter-dev/api-contracts v0.2.112
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 2
go.sum

@@ -1523,8 +1523,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.2.111 h1:MMDIMumereUdKIK2yNZjhlCRgNz6jBh+uK+Kmf0qbTc=
-github.com/porter-dev/api-contracts v0.2.111/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.112 h1:ZywNLFVopYsZvV5WkjFIXRxoPI64wvJYsy4RMLCTgAY=
+github.com/porter-dev/api-contracts v0.2.112/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

+ 2 - 0
internal/porter_app/test/parse_test.go

@@ -79,6 +79,7 @@ var result_nobuild = &porterv1.PorterApp{
 					HealthCheck: &porterv1.HealthCheck{
 						Enabled:  true,
 						HttpPath: "/healthz",
+						Command:  "",
 					},
 				},
 			},
@@ -180,6 +181,7 @@ var v1_result_nobuild_no_image = &porterv1.PorterApp{
 					HealthCheck: &porterv1.HealthCheck{
 						Enabled:  true,
 						HttpPath: "/healthz",
+						Command:  "",
 					},
 					Private: pointer.Bool(false),
 				},

+ 1 - 0
internal/porter_app/testdata/v2_input_no_build_no_env.yaml

@@ -26,6 +26,7 @@ services:
     healthCheck:
       enabled: true
       httpPath: /healthz
+      command: ""
   - name: example-wkr
     type: worker
     run: echo 'work'

+ 23 - 0
internal/porter_app/v2/yaml.go

@@ -215,6 +215,7 @@ type Domains struct {
 type HealthCheck struct {
 	Enabled  bool   `yaml:"enabled"`
 	HttpPath string `yaml:"httpPath"`
+	Command  string `yaml:"command"`
 }
 
 // ProtoFromApp converts a PorterApp type to a base PorterApp proto type and returns env variables
@@ -417,6 +418,7 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
 			healthCheck = &porterv1.HealthCheck{
 				Enabled:  service.HealthCheck.Enabled,
 				HttpPath: service.HealthCheck.HttpPath,
+				Command:  service.HealthCheck.Command,
 			}
 		}
 		webConfig.HealthCheck = healthCheck
@@ -457,6 +459,16 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
 		}
 		workerConfig.Autoscaling = autoscaling
 
+		var healthCheck *porterv1.HealthCheck
+		if service.HealthCheck != nil {
+			healthCheck = &porterv1.HealthCheck{
+				Enabled:  service.HealthCheck.Enabled,
+				HttpPath: service.HealthCheck.HttpPath,
+				Command:  service.HealthCheck.Command,
+			}
+		}
+		workerConfig.HealthCheck = healthCheck
+
 		serviceProto.Config = &porterv1.Service_WorkerConfig{
 			WorkerConfig: workerConfig,
 		}
@@ -592,6 +604,7 @@ func appServiceFromProto(service *porterv1.Service) (Service, error) {
 			healthCheck = &HealthCheck{
 				Enabled:  webConfig.HealthCheck.Enabled,
 				HttpPath: webConfig.HealthCheck.HttpPath,
+				Command:  webConfig.HealthCheck.Command,
 			}
 		}
 		appService.HealthCheck = healthCheck
@@ -628,6 +641,16 @@ func appServiceFromProto(service *porterv1.Service) (Service, error) {
 			}
 		}
 		appService.Autoscaling = autoscaling
+
+		var healthCheck *HealthCheck
+		if workerConfig.HealthCheck != nil {
+			healthCheck = &HealthCheck{
+				Enabled:  workerConfig.HealthCheck.Enabled,
+				HttpPath: workerConfig.HealthCheck.HttpPath,
+				Command:  workerConfig.HealthCheck.Command,
+			}
+		}
+		appService.HealthCheck = healthCheck
 	case porterv1.ServiceType_SERVICE_TYPE_JOB:
 		jobConfig := service.GetJobConfig()
 		appService.Type = "job"