Kaynağa Gözat

correctly support porter yaml private web field (#3462)

ianedwards 2 yıl önce
ebeveyn
işleme
9137d1fdfc

+ 2 - 0
cli/cmd/v2/apply.go

@@ -62,6 +62,8 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por
 	var commitSHA string
 	if os.Getenv("PORTER_COMMIT_SHA") != "" {
 		commitSHA = os.Getenv("PORTER_COMMIT_SHA")
+	} else if os.Getenv("GITHUB_SHA") != "" {
+		commitSHA = os.Getenv("GITHUB_SHA")
 	} else if commit, err := git.LastCommit(); err == nil && commit != nil {
 		commitSHA = commit.Sha
 	}

+ 1 - 0
dashboard/src/lib/hooks/usePorterYaml.ts

@@ -73,6 +73,7 @@ export const usePorterYaml = ({
         source?.type === "github" &&
         Boolean(source.git_repo_name) &&
         Boolean(source.git_branch),
+      retry: false,
     }
   );
 

+ 11 - 1
dashboard/src/lib/porter-apps/services.ts

@@ -38,9 +38,12 @@ export const serviceValidator = z.object({
     z.object({
       type: z.literal("web"),
       autoscaling: autoscalingValidator.optional(),
-      ingressEnabled: z.boolean().default(false).optional(),
       domains: domainsValidator,
       healthCheck: healthcheckValidator.optional(),
+      private: serviceBooleanValidator.default({
+        value: false,
+        readOnly: false,
+      }),
     }),
     z.object({
       type: z.literal("worker"),
@@ -76,6 +79,7 @@ export type SerializedService = {
         }[];
         autoscaling?: SerializedAutoscaling;
         healthCheck?: SerializedHealthcheck;
+        private: boolean;
       }
     | {
         type: "worker";
@@ -132,6 +136,7 @@ export function defaultSerialized({
         autoscaling: defaultAutoscaling,
         healthCheck: defaultHealthCheck,
         domains: [],
+        private: false,
       },
     }))
     .with("worker", () => ({
@@ -180,6 +185,7 @@ export function serializeService(service: ClientService): SerializedService {
           domains: config.domains.map((domain) => ({
             name: domain.name.value,
           })),
+          private: config.private.value,
         },
       })
     )
@@ -280,6 +286,10 @@ export function deserializeService({
               )?.name
             ),
           })),
+          private: ServiceField.boolean(
+            config.private,
+            overrideWebConfig?.private
+          ),
         },
       };
     })

+ 66 - 1
dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx

@@ -20,6 +20,11 @@ import { useQueryClient } from "@tanstack/react-query";
 import Settings from "./tabs/Settings";
 import BuildSettings from "./tabs/BuildSettings";
 import Environment from "./tabs/Environment";
+import AnimateHeight from "react-animate-height";
+import Banner from "components/porter/Banner";
+import Button from "components/porter/Button";
+import Icon from "components/porter/Icon";
+import save from "assets/save-01.svg";
 
 // commented out tabs are not yet implemented
 // will be included as support is available based on data from app revisions rather than helm releases
@@ -101,9 +106,40 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
   const {
     reset,
     handleSubmit,
-    formState: { dirtyFields },
+    formState: { isDirty, dirtyFields, isSubmitting },
   } = porterAppFormMethods;
 
+  // getAllDirtyFields recursively gets all dirty fields from the dirtyFields object
+  // all fields in the form are set to a boolean indicating if the current value is different from the default value
+  const getAllDirtyFields = (dirtyFields: object) => {
+    const dirty: string[] = [];
+
+    Object.entries(dirtyFields).forEach(([key, value]) => {
+      if (value) {
+        if (typeof value === "boolean" && value === true) {
+          dirty.push(key);
+        }
+
+        if (typeof value === "object") {
+          dirty.push(...getAllDirtyFields(value));
+        }
+      }
+    });
+
+    return dirty;
+  };
+
+  // onlyExpandedChanged is true if the only dirty fields are expanded and id
+  // expanded is a ui only value used to determine if a service is expanded or not
+  // id is set by useFieldArray and is also not relevant to the app proto
+  const onlyExpandedChanged = useMemo(() => {
+    if (!isDirty) return false;
+
+    // get all entries in entire dirtyFields object that are true
+    const dirty = getAllDirtyFields(dirtyFields);
+    return dirty.every((f) => f === "expanded" || f === "id");
+  }, [isDirty, JSON.stringify(dirtyFields)]);
+
   const onSubmit = handleSubmit(async (data) => {
     try {
       const validatedAppProto = await validateApp(data);
@@ -148,6 +184,11 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
         deploymentTargetId,
         porterApp.name,
       ]);
+
+      reset({
+        app: clientAppFromProto(latestProto, servicesFromYaml),
+        source: latestSource,
+      });
     } catch (err) {}
   });
 
@@ -172,6 +213,30 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
           sourceType={latestSource.type}
         />
         <Spacer y={1} />
+        <AnimateHeight height={isDirty && !onlyExpandedChanged ? "auto" : 0}>
+          <Banner
+            type="warning"
+            suffix={
+              <>
+                <Button
+                  type="submit"
+                  loadingText={"Updating..."}
+                  height={"10px"}
+                  status={isSubmitting ? "loading" : ""}
+                  disabled={isSubmitting}
+                >
+                  <Icon src={save} height={"13px"} />
+                  <Spacer inline x={0.5} />
+                  Save as latest version
+                </Button>
+              </>
+            }
+          >
+            Changes you are currently previewing have not been saved.
+            <Spacer inline width="5px" />
+          </Banner>
+          <Spacer y={1} />
+        </AnimateHeight>
         <TabSelector
           noBuffer
           options={[

+ 1 - 1
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -98,10 +98,10 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
       app: {
         name: "",
         build: {
+          method: "pack",
           context: "./",
           builder: "",
           buildpacks: [],
-          dockerfile: "",
         },
       },
       source: {

+ 6 - 6
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Networking.tsx

@@ -28,7 +28,7 @@ const prefixSubdomain = (subdomain: string) => {
 const Networking: React.FC<NetworkingProps> = ({ index, service }) => {
   const { register, control, watch } = useFormContext<PorterAppFormData>();
 
-  const ingressEnabled = watch(`app.services.${index}.config.ingressEnabled`);
+  const privateService = watch(`app.services.${index}.config.private.value`);
 
   const getApplicationURLText = () => {
     if (service.config.domains.length !== 0) {
@@ -69,12 +69,12 @@ const Networking: React.FC<NetworkingProps> = ({ index, service }) => {
       />
       <Spacer y={1} />
       <Controller
-        name={`app.services.${index}.config.ingressEnabled`}
+        name={`app.services.${index}.config.private.value`}
         control={control}
         render={({ field: { value, onChange } }) => (
           <Checkbox
-            checked={Boolean(value)}
-            disabled={service.config.domains.some((d) => d.name.readOnly)}
+            checked={value}
+            disabled={service.config.private.readOnly}
             toggleChecked={() => {
               onChange(!value);
             }}
@@ -82,11 +82,11 @@ const Networking: React.FC<NetworkingProps> = ({ index, service }) => {
               "You may only edit this field in your porter.yaml."
             }
           >
-            <Text color="helper">Expose to external traffic</Text>
+            <Text color="helper">Private Service</Text>
           </Checkbox>
         )}
       />
-      {ingressEnabled && (
+      {!privateService && (
         <>
           <Spacer y={0.5} />
           {getApplicationURLText()}

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

@@ -112,6 +112,7 @@ type Service struct {
 	HealthCheck     *HealthCheck `yaml:"healthCheck,omitempty" validate:"excluded_unless=Type web"`
 	AllowConcurrent bool         `yaml:"allowConcurrent" validate:"excluded_unless=Type job"`
 	Cron            string       `yaml:"cron" validate:"excluded_unless=Type job"`
+	Private         bool         `yaml:"private" validate:"excluded_unless=Type web"`
 }
 
 // AutoScaling represents the autoscaling settings for web services
@@ -218,6 +219,7 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
 			})
 		}
 		webConfig.Domains = domains
+		webConfig.Private = service.Private
 
 		serviceProto.Config = &porterv1.Service_WebConfig{
 			WebConfig: webConfig,