diff --git a/dashboard/src/lib/hooks/usePorterYaml.ts b/dashboard/src/lib/hooks/usePorterYaml.ts index 4c65202470..e0e55b2a61 100644 --- a/dashboard/src/lib/hooks/usePorterYaml.ts +++ b/dashboard/src/lib/hooks/usePorterYaml.ts @@ -121,13 +121,14 @@ export const usePorterYaml = ({ .parseAsync(res.data); const proto = PorterApp.fromJsonString(atob(data.b64_app_proto)); - const { services, predeploy } = serviceOverrides({ + const { services, predeploy, build } = serviceOverrides({ overrides: proto, useDefaults, }); - if (services.length || predeploy) { + if (services.length || predeploy || build) { setDetectedServices({ + build, services, predeploy, }); diff --git a/dashboard/src/lib/porter-apps/build.ts b/dashboard/src/lib/porter-apps/build.ts new file mode 100644 index 0000000000..9fcb061392 --- /dev/null +++ b/dashboard/src/lib/porter-apps/build.ts @@ -0,0 +1,18 @@ +import { buildpackSchema } from "main/home/app-dashboard/types/buildpack"; +import { z } from "zod"; + +// buildValidator is used to validate inputs for build setting fields +export const buildValidator = z.discriminatedUnion("method", [ + z.object({ + method: z.literal("pack"), + context: z.string().min(1).default("./").catch("./"), + buildpacks: z.array(buildpackSchema).default([]), + builder: z.string(), + }), + z.object({ + method: z.literal("docker"), + context: z.string().min(1).default("./").catch("./"), + dockerfile: z.string().min(1).default("./Dockerfile").catch("./Dockerfile"), + }), +]); +export type BuildOptions = z.infer; diff --git a/dashboard/src/lib/porter-apps/index.ts b/dashboard/src/lib/porter-apps/index.ts index efca8438fe..2524c624fb 100644 --- a/dashboard/src/lib/porter-apps/index.ts +++ b/dashboard/src/lib/porter-apps/index.ts @@ -1,13 +1,9 @@ -import { - BUILDPACK_TO_NAME, - buildpackSchema, -} from "main/home/app-dashboard/types/buildpack"; +import { BUILDPACK_TO_NAME } from "main/home/app-dashboard/types/buildpack"; import { z } from "zod"; import { DetectedServices, defaultSerialized, deserializeService, - isPredeployService, serializeService, serializedServiceFromProto, serviceProto, @@ -16,22 +12,7 @@ import { import { Build, PorterApp, Service } from "@porter-dev/api-contracts"; import { match } from "ts-pattern"; import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray"; - -// buildValidator is used to validate inputs for build setting fields -export const buildValidator = z.discriminatedUnion("method", [ - z.object({ - method: z.literal("pack"), - context: z.string().default("./"), - buildpacks: z.array(buildpackSchema).default([]), - builder: z.string(), - }), - z.object({ - method: z.literal("docker"), - context: z.string().default("./"), - dockerfile: z.string().default("./Dockerfile"), - }), -]); -export type BuildOptions = z.infer; +import { BuildOptions, buildValidator } from "./build"; // sourceValidator is used to validate inputs for source setting fields export const sourceValidator = z.discriminatedUnion("type", [ @@ -117,7 +98,10 @@ export function serviceOverrides({ .map((svc) => { if (useDefaults) { return deserializeService({ - service: defaultSerialized({ name: svc.name, type: svc.config.type }), + service: defaultSerialized({ + name: svc.name, + type: svc.config.type, + }), override: svc, expanded: true, }); @@ -126,6 +110,15 @@ export function serviceOverrides({ return deserializeService({ service: svc }); }); + const validatedBuild = buildValidator + .default({ + method: "pack", + context: "./", + buildpacks: [], + builder: "", + }) + .parse(overrides.build); + if (!overrides.predeploy) { return { services, @@ -134,6 +127,7 @@ export function serviceOverrides({ if (useDefaults) { return { + build: validatedBuild, services, predeploy: deserializeService({ service: defaultSerialized({ @@ -151,6 +145,7 @@ export function serviceOverrides({ } return { + build: validatedBuild, services, predeploy: deserializeService({ service: serializedServiceFromProto({ diff --git a/dashboard/src/lib/porter-apps/services.ts b/dashboard/src/lib/porter-apps/services.ts index 8b7ffbb8ee..845e19fb2e 100644 --- a/dashboard/src/lib/porter-apps/services.ts +++ b/dashboard/src/lib/porter-apps/services.ts @@ -16,10 +16,12 @@ import { ServiceField, } from "./values"; import { Service, ServiceType } from "@porter-dev/api-contracts"; +import { BuildOptions } from "./build"; export type DetectedServices = { services: ClientService[]; predeploy?: ClientService; + build?: BuildOptions; }; type ClientServiceType = "web" | "worker" | "job" | "predeploy"; diff --git a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx index cb88238d8d..d80d27aa26 100644 --- a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx +++ b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx @@ -185,7 +185,7 @@ const CreateApp: React.FC = ({ history }) => { loading: isLoadingPorterYaml, } = usePorterYaml({ source: source?.type === "github" ? source : null, - appName: name.value, + appName: "", // only want to know if porter.yaml has name set, otherwise use name from input }); const deploymentTarget = useDefaultDeploymentTarget(); const { updateAppStep } = useAppAnalytics(name.value); @@ -426,9 +426,13 @@ const CreateApp: React.FC = ({ history }) => { useEffect(() => { if (servicesFromYaml && !detectedServices.detected) { - const { services, predeploy } = servicesFromYaml; + const { services, predeploy, build: detectedBuild } = servicesFromYaml; setValue("app.services", services); setValue("app.predeploy", [predeploy].filter(valueExists)); + + if (detectedBuild) { + setValue("app.build", detectedBuild); + } setDetectedServices({ detected: true, count: services.length, diff --git a/dashboard/src/main/home/app-dashboard/create-app/RepoSettings.tsx b/dashboard/src/main/home/app-dashboard/create-app/RepoSettings.tsx index 970ab6861a..94ff61baa3 100644 --- a/dashboard/src/main/home/app-dashboard/create-app/RepoSettings.tsx +++ b/dashboard/src/main/home/app-dashboard/create-app/RepoSettings.tsx @@ -10,15 +10,12 @@ import { ControlledInput } from "components/porter/ControlledInput"; import Select from "components/porter/Select"; import AnimateHeight from "react-animate-height"; import { z } from "zod"; -import { - BuildOptions, - PorterAppFormData, - SourceOptions, -} from "lib/porter-apps"; +import { PorterAppFormData, SourceOptions } from "lib/porter-apps"; import RepositorySelector from "../build-settings/RepositorySelector"; import BranchSelector from "../build-settings/BranchSelector"; import BuildpackSettings from "../validate-apply/build-settings/buildpacks/BuildpackSettings"; import { match } from "ts-pattern"; +import { BuildOptions } from "lib/porter-apps/build"; type Props = { projectId: number; @@ -85,6 +82,9 @@ const RepoSettings: React.FC = ({ item.path.includes("Dockerfile") ); setValue("app.build.method", hasDockerfile ? "docker" : "pack"); + if (!hasDockerfile) { + setValue("app.build.buildpacks", []); + } }, [branchContents]); return ( @@ -235,7 +235,7 @@ const RepoSettings: React.FC = ({ ]} setValue={(option: string) => { if (option == "docker") { - setValue("app.build.method", "docker");4 + setValue("app.build.method", "docker"); } else if (option == "pack") { // if toggling from docker to pack, initialize buildpacks to empty array setValue("app.build.method", "pack"); diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackConfigurationModal.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackConfigurationModal.tsx index f542bfb920..2e26f80717 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackConfigurationModal.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackConfigurationModal.tsx @@ -11,7 +11,8 @@ import Select from "components/porter/Select"; import stars from "assets/stars-white.svg"; import { Buildpack } from "main/home/app-dashboard/types/buildpack"; import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { BuildOptions, PorterAppFormData } from "lib/porter-apps"; +import { PorterAppFormData } from "lib/porter-apps"; +import { BuildOptions } from "lib/porter-apps/build"; type Props = { build: BuildOptions & { diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackList.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackList.tsx index 6ddcf88557..5fd0c1f66b 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackList.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackList.tsx @@ -7,7 +7,8 @@ import Error from "components/porter/Error"; import { Droppable, DragDropContext } from "react-beautiful-dnd"; import { Buildpack } from "main/home/app-dashboard/types/buildpack"; import { useFieldArray, useFormContext } from "react-hook-form"; -import { BuildOptions, PorterAppFormData } from "lib/porter-apps"; +import { PorterAppFormData } from "lib/porter-apps"; +import { BuildOptions } from "lib/porter-apps/build"; interface Props { build: BuildOptions & { diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackSettings.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackSettings.tsx index 556a1e6e28..cfacce6f73 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackSettings.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/build-settings/buildpacks/BuildpackSettings.tsx @@ -18,11 +18,8 @@ import Button from "components/porter/Button"; import BuildpackList from "./BuildpackList"; import BuildpackConfigurationModal from "./BuildpackConfigurationModal"; import { useFieldArray, useFormContext } from "react-hook-form"; -import { - BuildOptions, - PorterAppFormData, - SourceOptions, -} from "lib/porter-apps"; +import { PorterAppFormData, SourceOptions } from "lib/porter-apps"; +import { BuildOptions } from "lib/porter-apps/build"; type Props = { projectId: number; diff --git a/internal/porter_app/v1/yaml.go b/internal/porter_app/v1/yaml.go index 3a2c304ef8..ecbfcdf4ee 100644 --- a/internal/porter_app/v1/yaml.go +++ b/internal/porter_app/v1/yaml.go @@ -19,9 +19,6 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte, appName strin ctx, span := telemetry.NewSpan(ctx, "v1-app-proto-from-yaml") defer span.End() - if appName == "" { - return nil, nil, telemetry.Error(ctx, span, nil, "app name is empty") - } telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-name", Value: appName}) if porterYamlBytes == nil { diff --git a/internal/porter_app/v2/yaml.go b/internal/porter_app/v2/yaml.go index d93bc65e2b..e333ad21da 100644 --- a/internal/porter_app/v2/yaml.go +++ b/internal/porter_app/v2/yaml.go @@ -39,9 +39,6 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte, appName strin Name: porterYaml.Name, } - if appProto.Name == "" { - return nil, nil, telemetry.Error(ctx, span, nil, "app name is empty") - } if porterYaml.Build != nil { appProto.Build = &porterv1.Build{