usePorterYaml.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { PorterApp } from "@porter-dev/api-contracts";
  2. import { useQuery } from "@tanstack/react-query";
  3. import { SourceOptions, serviceOverrides } from "lib/porter-apps";
  4. import { DetectedServices } from "lib/porter-apps/services";
  5. import { useCallback, useContext, useEffect, useState } from "react";
  6. import { Context } from "shared/Context";
  7. import api from "shared/api";
  8. import { z } from "zod";
  9. type PorterYamlStatus =
  10. | {
  11. loading: true;
  12. detectedName: null;
  13. detectedServices: null;
  14. porterYamlFound: false;
  15. }
  16. | {
  17. detectedServices: DetectedServices | null;
  18. detectedName: string | null;
  19. loading: false;
  20. porterYamlFound: boolean;
  21. };
  22. /*
  23. *
  24. * usePorterYaml is a hook that will fetch the porter.yaml file from the
  25. * specified source and parse it to determine the services that should be
  26. * added to an app by default with read-only values.
  27. *
  28. */
  29. export const usePorterYaml = ({
  30. source,
  31. appName = "",
  32. useDefaults = true,
  33. }: {
  34. source: (SourceOptions & { type: "github" }) | null;
  35. appName?: string;
  36. useDefaults?: boolean;
  37. }): PorterYamlStatus => {
  38. const { currentProject, currentCluster } = useContext(Context);
  39. const [
  40. detectedServices,
  41. setDetectedServices,
  42. ] = useState<DetectedServices | null>(null);
  43. const [detectedName, setDetectedName] = useState<string | null>(null);
  44. const [porterYamlFound, setPorterYamlFound] = useState(false);
  45. const { data, status } = useQuery(
  46. [
  47. "getPorterYamlContents",
  48. currentProject?.id,
  49. source?.git_branch,
  50. source?.git_repo_name,
  51. source?.porter_yaml_path,
  52. ],
  53. async () => {
  54. if (!currentProject || !source) {
  55. return;
  56. }
  57. const res = await api.getPorterYamlContents(
  58. "<token>",
  59. {
  60. path: source.porter_yaml_path,
  61. },
  62. {
  63. project_id: currentProject.id,
  64. git_repo_id: source.git_repo_id,
  65. kind: "github",
  66. owner: source.git_repo_name.split("/")[0],
  67. name: source.git_repo_name.split("/")[1],
  68. branch: source.git_branch,
  69. }
  70. );
  71. setPorterYamlFound(true);
  72. return z.string().parseAsync(res.data);
  73. },
  74. {
  75. enabled:
  76. source?.type === "github" &&
  77. Boolean(source.git_repo_name) &&
  78. Boolean(source.git_branch),
  79. onError: () => {
  80. setPorterYamlFound(false);
  81. },
  82. refetchOnWindowFocus: false,
  83. }
  84. );
  85. const detectServices = useCallback(
  86. async ({
  87. b64Yaml,
  88. appName,
  89. projectId,
  90. clusterId,
  91. }: {
  92. b64Yaml: string;
  93. appName: string;
  94. projectId: number;
  95. clusterId: number;
  96. }) => {
  97. try {
  98. const res = await api.parsePorterYaml(
  99. "<token>",
  100. { b64_yaml: b64Yaml, app_name: appName },
  101. {
  102. project_id: projectId,
  103. cluster_id: clusterId,
  104. }
  105. );
  106. const data = await z
  107. .object({
  108. b64_app_proto: z.string(),
  109. })
  110. .parseAsync(res.data);
  111. const proto = PorterApp.fromJsonString(atob(data.b64_app_proto));
  112. const { services, predeploy, build } = serviceOverrides({
  113. overrides: proto,
  114. useDefaults,
  115. });
  116. if (services.length || predeploy || build) {
  117. setDetectedServices({
  118. build,
  119. services,
  120. predeploy,
  121. });
  122. }
  123. if (proto.name) {
  124. setDetectedName(proto.name);
  125. }
  126. } catch (err) {
  127. // silent failure for now
  128. }
  129. },
  130. []
  131. );
  132. useEffect(() => {
  133. if (!currentProject || !currentCluster) {
  134. return;
  135. }
  136. if (data) {
  137. detectServices({
  138. b64Yaml: data,
  139. appName: appName,
  140. projectId: currentProject.id,
  141. clusterId: currentCluster.id,
  142. });
  143. }
  144. if (!data && detectedServices) {
  145. setDetectedServices(null);
  146. }
  147. }, [data]);
  148. if (source?.type !== "github") {
  149. return {
  150. loading: false,
  151. detectedName: null,
  152. detectedServices: null,
  153. porterYamlFound: false,
  154. };
  155. }
  156. if (status === "loading") {
  157. return {
  158. loading: true,
  159. detectedName: null,
  160. detectedServices: null,
  161. porterYamlFound: false,
  162. };
  163. }
  164. return {
  165. detectedServices,
  166. detectedName,
  167. loading: false,
  168. porterYamlFound,
  169. };
  170. };