@crossdelta/infrastructure 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,21 @@
1
- import type { ServiceConfig } from '../types';
1
+ import type { K8sServiceConfig } from '../runtimes/doks/types';
2
2
  /**
3
- * Auto-discovers all service configurations from a directory.
4
- * Each .ts file (except index.ts) should export a ServiceConfig as default.
3
+ * Auto-discovers all K8s service configurations from infra/services directory.
4
+ * Each .ts file (except index.ts) should export a K8sServiceConfig as default.
5
+ *
6
+ * If a config does not specify `image`, it will be auto-generated using
7
+ * the registry from root package.json's `pf.registry` config.
5
8
  *
6
9
  * @param servicesDir - Path to services directory (relative to cwd or absolute)
7
- * @throws Error if duplicate ports are detected
10
+ * @throws Error if duplicate ports are detected or pf.registry is missing
8
11
  *
9
12
  * @example
10
13
  * ```typescript
11
- * // Relative path (resolved from process.cwd())
12
- * const configs = discoverServices('infra/services')
13
- *
14
- * // Absolute path
15
- * const configs = discoverServices('/absolute/path/to/services')
14
+ * // Config without image - will use pf.registry automatically
15
+ * const config: K8sServiceConfig = {
16
+ * name: 'my-service',
17
+ * containerPort: 4001,
18
+ * }
16
19
  * ```
17
20
  */
18
- export declare function discoverServices(servicesDir: string): ServiceConfig[];
21
+ export declare function discoverServiceConfigs(servicesDir?: string): K8sServiceConfig[];
package/dist/index.cjs CHANGED
@@ -289067,7 +289067,7 @@ var require_lib38 = __commonJS((exports2, module2) => {
289067
289067
  var require_lib39 = __commonJS((exports2, module2) => {
289068
289068
  var fs = require("fs");
289069
289069
  var { promisify } = require("util");
289070
- var { readFileSync } = fs;
289070
+ var { readFileSync: readFileSync2 } = fs;
289071
289071
  var readFile = promisify(fs.readFile);
289072
289072
  var extractPath = (path, cmdshimContents) => {
289073
289073
  if (/[.]cmd$/.test(path)) {
@@ -289119,7 +289119,7 @@ var require_lib39 = __commonJS((exports2, module2) => {
289119
289119
  });
289120
289120
  };
289121
289121
  var readCmdShimSync = (path) => {
289122
- const contents = readFileSync(path);
289122
+ const contents = readFileSync2(path);
289123
289123
  const destination = extractPath(path, contents.toString());
289124
289124
  if (!destination) {
289125
289125
  throw notaShim(path);
@@ -308309,7 +308309,7 @@ var require_getImage = __commonJS((exports2) => {
308309
308309
  exports2.getImageOutput = exports2.getImage = undefined;
308310
308310
  var pulumi = require_pulumi();
308311
308311
  var utilities = require_utilities();
308312
- function getImage(args, opts) {
308312
+ function getImage2(args, opts) {
308313
308313
  args = args || {};
308314
308314
  opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
308315
308315
  return pulumi.runtime.invoke("digitalocean:index/getImage:getImage", {
@@ -308319,7 +308319,7 @@ var require_getImage = __commonJS((exports2) => {
308319
308319
  source: args.source
308320
308320
  }, opts);
308321
308321
  }
308322
- exports2.getImage = getImage;
308322
+ exports2.getImage = getImage2;
308323
308323
  function getImageOutput(args, opts) {
308324
308324
  args = args || {};
308325
308325
  opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
@@ -337015,12 +337015,12 @@ var exports_lib = {};
337015
337015
  __export(exports_lib, {
337016
337016
  ghcrImage: () => ghcrImage,
337017
337017
  getServiceUrl: () => getServiceUrl,
337018
- getServicePort: () => getServicePort2,
337018
+ getServicePort: () => getServicePort,
337019
337019
  getImage: () => getImage,
337020
337020
  filterByPlatform: () => filterByPlatform,
337021
337021
  ensureDot: () => ensureDot,
337022
337022
  dockerHubImage: () => dockerHubImage,
337023
- discoverServices: () => discoverServices,
337023
+ discoverServiceConfigs: () => discoverServiceConfigs,
337024
337024
  deployNginxIngress: () => deployNginxIngress,
337025
337025
  deployNats: () => deployNats,
337026
337026
  deployK8sServices: () => deployK8sServices,
@@ -337081,21 +337081,67 @@ var defaultHealthCheck = {
337081
337081
  // lib/helpers/discover-services.ts
337082
337082
  var import_node_fs = require("node:fs");
337083
337083
  var import_node_path = require("node:path");
337084
- function getServicePort(config) {
337085
- if (config.httpPort)
337086
- return config.httpPort;
337087
- const internalPorts = config.internalPorts;
337088
- if (internalPorts?.[0])
337089
- return internalPorts[0];
337090
- return 8080;
337084
+
337085
+ // lib/helpers/image.ts
337086
+ var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
337087
+ var scopeImageTags = (() => {
337088
+ if (!scopeImageTagsRaw.trim()) {
337089
+ return {};
337090
+ }
337091
+ try {
337092
+ const parsed = JSON.parse(scopeImageTagsRaw);
337093
+ const tags = {};
337094
+ for (const [key, value] of Object.entries(parsed ?? {})) {
337095
+ if (typeof value === "string" && value.trim().length > 0) {
337096
+ tags[key] = value.trim();
337097
+ }
337098
+ }
337099
+ return tags;
337100
+ } catch (error) {
337101
+ console.warn("Unable to parse scope image tags from environment:", error);
337102
+ return {};
337103
+ }
337104
+ })();
337105
+ var resolveImageTag = (scopeName) => {
337106
+ const tag = scopeImageTags[scopeName];
337107
+ if (!tag) {
337108
+ return "latest";
337109
+ }
337110
+ return tag;
337111
+ };
337112
+ var ghcrImage = (registry, serviceName) => {
337113
+ const tag = resolveImageTag(serviceName);
337114
+ return `ghcr.io/${registry}/${serviceName}:${tag}`;
337115
+ };
337116
+ var getImage = (registry, repository, registryCredentials) => {
337117
+ const scopeName = repository.split("/").pop();
337118
+ if (!scopeName) {
337119
+ throw new Error(`Invalid repository name: ${repository}`);
337120
+ }
337121
+ return {
337122
+ registryType: "GHCR",
337123
+ registry,
337124
+ repository,
337125
+ registryCredentials,
337126
+ tag: resolveImageTag(scopeName)
337127
+ };
337128
+ };
337129
+
337130
+ // lib/helpers/discover-services.ts
337131
+ function getRegistryFromPackageJson(workspaceRoot) {
337132
+ const pkgPath = import_node_path.join(workspaceRoot, "package.json");
337133
+ const pkg = JSON.parse(import_node_fs.readFileSync(pkgPath, "utf-8"));
337134
+ if (!pkg.pf?.registry) {
337135
+ throw new Error(`Missing "pf.registry" in ${pkgPath}. Add it to your root package.json, e.g.: "pf": { "registry": "orgname/reponame" }`);
337136
+ }
337137
+ return pkg.pf.registry;
337091
337138
  }
337092
337139
  function validateNoDuplicatePorts(configs) {
337093
337140
  const portMap = new Map;
337094
337141
  for (const config of configs) {
337095
- const port = getServicePort(config);
337096
- const existing = portMap.get(port) || [];
337142
+ const existing = portMap.get(config.containerPort) || [];
337097
337143
  existing.push(config.name);
337098
- portMap.set(port, existing);
337144
+ portMap.set(config.containerPort, existing);
337099
337145
  }
337100
337146
  const conflicts = [...portMap.entries()].filter(([, services]) => services.length > 1).map(([port, services]) => `Port ${port}: ${services.join(", ")}`);
337101
337147
  if (conflicts.length > 0) {
@@ -337104,13 +337150,22 @@ ${conflicts.join(`
337104
337150
  `)}`);
337105
337151
  }
337106
337152
  }
337107
- function discoverServices(servicesDir) {
337153
+ function discoverServiceConfigs(servicesDir = "infra/services") {
337108
337154
  const resolvedDir = import_node_path.isAbsolute(servicesDir) ? servicesDir : import_node_path.resolve(process.cwd(), servicesDir);
337155
+ const workspaceRoot = import_node_path.resolve(resolvedDir, "..", "..");
337156
+ const registry = getRegistryFromPackageJson(workspaceRoot);
337109
337157
  const files = import_node_fs.readdirSync(resolvedDir).filter((file) => file.endsWith(".ts") && file !== "index.ts");
337110
337158
  const configs = files.map((file) => {
337111
337159
  const filePath = import_node_path.join(resolvedDir, file);
337112
337160
  const module2 = require(filePath);
337113
- return module2.default ?? module2;
337161
+ const config = module2.default ?? module2;
337162
+ if (!config.image) {
337163
+ return {
337164
+ ...config,
337165
+ image: ghcrImage(registry, config.name)
337166
+ };
337167
+ }
337168
+ return config;
337114
337169
  });
337115
337170
  validateNoDuplicatePorts(configs);
337116
337171
  return configs;
@@ -337338,50 +337393,6 @@ function buildDropletServices(options) {
337338
337393
  function filterByPlatform(configs, platform) {
337339
337394
  return configs.filter((c) => (c.platform || "app-platform") === platform);
337340
337395
  }
337341
- // lib/helpers/image.ts
337342
- var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
337343
- var scopeImageTags = (() => {
337344
- if (!scopeImageTagsRaw.trim()) {
337345
- return {};
337346
- }
337347
- try {
337348
- const parsed = JSON.parse(scopeImageTagsRaw);
337349
- const tags = {};
337350
- for (const [key, value] of Object.entries(parsed ?? {})) {
337351
- if (typeof value === "string" && value.trim().length > 0) {
337352
- tags[key] = value.trim();
337353
- }
337354
- }
337355
- return tags;
337356
- } catch (error) {
337357
- console.warn("Unable to parse scope image tags from environment:", error);
337358
- return {};
337359
- }
337360
- })();
337361
- var resolveImageTag = (scopeName) => {
337362
- const tag = scopeImageTags[scopeName];
337363
- if (!tag) {
337364
- return "latest";
337365
- }
337366
- return tag;
337367
- };
337368
- var ghcrImage = (registry, serviceName) => {
337369
- const tag = resolveImageTag(serviceName);
337370
- return `ghcr.io/${registry}/${serviceName}:${tag}`;
337371
- };
337372
- var getImage = (registry, repository, registryCredentials) => {
337373
- const scopeName = repository.split("/").pop();
337374
- if (!scopeName) {
337375
- throw new Error(`Invalid repository name: ${repository}`);
337376
- }
337377
- return {
337378
- registryType: "GHCR",
337379
- registry,
337380
- repository,
337381
- registryCredentials,
337382
- tag: resolveImageTag(scopeName)
337383
- };
337384
- };
337385
337396
  // lib/helpers/service-builder.ts
337386
337397
  function buildServices(options) {
337387
337398
  const { serviceConfigs, registryCredentials, logtailToken, registry } = options;
@@ -337426,7 +337437,7 @@ function buildIngressRules(serviceConfigs) {
337426
337437
  }
337427
337438
  // lib/helpers/service-runtime.ts
337428
337439
  var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
337429
- function getServicePort2(serviceName, defaultPort = 8080) {
337440
+ function getServicePort(serviceName, defaultPort = 8080) {
337430
337441
  const envKey = `${toEnvKey(serviceName)}_PORT`;
337431
337442
  const envValue = process.env[envKey];
337432
337443
  if (envValue) {
@@ -337442,7 +337453,7 @@ function getServiceUrl(serviceName) {
337442
337453
  return process.env[envKey];
337443
337454
  }
337444
337455
  // lib/helpers/service-urls.ts
337445
- function getServicePort3(config) {
337456
+ function getServicePort2(config) {
337446
337457
  if (config.httpPort)
337447
337458
  return config.httpPort;
337448
337459
  const internalPorts = config.internalPorts;
@@ -337452,7 +337463,7 @@ function getServicePort3(config) {
337452
337463
  }
337453
337464
  function buildInternalUrls(serviceConfigs) {
337454
337465
  return Object.fromEntries(serviceConfigs.map((config) => {
337455
- const url = config.internalUrl ?? `http://${config.name}:${getServicePort3(config)}`;
337466
+ const url = config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`;
337456
337467
  return [config.name, url];
337457
337468
  }));
337458
337469
  }
@@ -337464,7 +337475,7 @@ function buildExternalUrls(serviceConfigs, baseUrl) {
337464
337475
  }
337465
337476
  function buildLocalUrls(serviceConfigs) {
337466
337477
  return Object.fromEntries(serviceConfigs.map((config) => {
337467
- const port = getServicePort3(config);
337478
+ const port = getServicePort2(config);
337468
337479
  return [config.name, `http://localhost:${port}`];
337469
337480
  }));
337470
337481
  }
@@ -337472,14 +337483,14 @@ function buildServiceUrlEnvs(serviceConfigs) {
337472
337483
  return serviceConfigs.map((config) => ({
337473
337484
  key: `${config.name.toUpperCase().replace(/-/g, "_")}_URL`,
337474
337485
  scope: "RUN_TIME",
337475
- value: config.internalUrl ?? `http://${config.name}:${getServicePort3(config)}`
337486
+ value: config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`
337476
337487
  }));
337477
337488
  }
337478
337489
  function buildServicePortEnvs(serviceConfigs) {
337479
337490
  return serviceConfigs.map((config) => ({
337480
337491
  key: `${config.name.toUpperCase().replace(/-/g, "_")}_PORT`,
337481
337492
  scope: "RUN_TIME",
337482
- value: String(getServicePort3(config))
337493
+ value: String(getServicePort2(config))
337483
337494
  }));
337484
337495
  }
337485
337496
  // lib/runtimes/doks/cert-manager.ts
@@ -337844,10 +337855,7 @@ function getImagePullPolicy(image2, explicit) {
337844
337855
  return explicit;
337845
337856
  return image2.endsWith(":latest") ? "Always" : "IfNotPresent";
337846
337857
  }
337847
- function deployK8sService(provider, namespace, config2) {
337848
- const labels = buildLabels(config2.name, config2.labels);
337849
- const replicas = config2.replicas ?? DEFAULTS.replicas;
337850
- const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
337858
+ function buildEnvVars(config2) {
337851
337859
  const envVars = [];
337852
337860
  envVars.push({ name: "PORT", value: String(config2.containerPort) });
337853
337861
  if (config2.env) {
@@ -337855,17 +337863,7 @@ function deployK8sService(provider, namespace, config2) {
337855
337863
  envVars.push({ name: key, value: pulumi6.output(value) });
337856
337864
  }
337857
337865
  }
337858
- let secret;
337859
- if (config2.secrets && Object.keys(config2.secrets).length > 0) {
337860
- secret = new k8s5.core.v1.Secret(`${config2.name}-secret`, {
337861
- metadata: {
337862
- name: `${config2.name}-secret`,
337863
- namespace,
337864
- labels
337865
- },
337866
- type: "Opaque",
337867
- stringData: config2.secrets
337868
- }, { provider });
337866
+ if (config2.secrets) {
337869
337867
  for (const key of Object.keys(config2.secrets)) {
337870
337868
  envVars.push({
337871
337869
  name: key,
@@ -337878,80 +337876,205 @@ function deployK8sService(provider, namespace, config2) {
337878
337876
  });
337879
337877
  }
337880
337878
  }
337879
+ return envVars;
337880
+ }
337881
+ function createServiceSecret(provider, namespace, config2, labels) {
337882
+ if (!config2.secrets || Object.keys(config2.secrets).length === 0) {
337883
+ return;
337884
+ }
337885
+ return new k8s5.core.v1.Secret(`${config2.name}-secret`, {
337886
+ metadata: {
337887
+ name: `${config2.name}-secret`,
337888
+ namespace,
337889
+ labels
337890
+ },
337891
+ type: "Opaque",
337892
+ stringData: config2.secrets
337893
+ }, { provider });
337894
+ }
337895
+ function createServiceVolumes(provider, namespace, config2, labels) {
337881
337896
  const pvcs = [];
337882
337897
  const volumeMounts = [];
337883
337898
  const volumes = [];
337884
- if (config2.volumes) {
337885
- for (const vol of config2.volumes) {
337886
- const pvc = new k8s5.core.v1.PersistentVolumeClaim(`${config2.name}-${vol.name}`, {
337887
- metadata: {
337888
- name: `${config2.name}-${vol.name}`,
337889
- namespace,
337890
- labels
337891
- },
337892
- spec: {
337893
- accessModes: [vol.accessMode ?? "ReadWriteOnce"],
337894
- storageClassName: vol.storageClass ?? "do-block-storage",
337895
- resources: {
337896
- requests: {
337897
- storage: vol.size ?? "10Gi"
337898
- }
337899
- }
337900
- }
337901
- }, { provider });
337902
- pvcs.push(pvc);
337903
- volumeMounts.push({
337904
- name: vol.name,
337905
- mountPath: vol.mountPath,
337906
- readOnly: vol.readOnly
337907
- });
337908
- volumes.push({
337909
- name: vol.name,
337910
- persistentVolumeClaim: {
337911
- claimName: `${config2.name}-${vol.name}`
337899
+ if (!config2.volumes) {
337900
+ return { pvcs, volumeMounts, volumes };
337901
+ }
337902
+ for (const vol of config2.volumes) {
337903
+ const pvc = new k8s5.core.v1.PersistentVolumeClaim(`${config2.name}-${vol.name}`, {
337904
+ metadata: {
337905
+ name: `${config2.name}-${vol.name}`,
337906
+ namespace,
337907
+ labels
337908
+ },
337909
+ spec: {
337910
+ accessModes: [vol.accessMode ?? "ReadWriteOnce"],
337911
+ storageClassName: vol.storageClass ?? "do-block-storage",
337912
+ resources: {
337913
+ requests: { storage: vol.size ?? "10Gi" }
337912
337914
  }
337913
- });
337914
- }
337915
+ }
337916
+ }, { provider });
337917
+ pvcs.push(pvc);
337918
+ volumeMounts.push({
337919
+ name: vol.name,
337920
+ mountPath: vol.mountPath,
337921
+ readOnly: vol.readOnly
337922
+ });
337923
+ volumes.push({
337924
+ name: vol.name,
337925
+ persistentVolumeClaim: {
337926
+ claimName: `${config2.name}-${vol.name}`
337927
+ }
337928
+ });
337915
337929
  }
337930
+ return { pvcs, volumeMounts, volumes };
337931
+ }
337932
+ function buildHealthProbes(config2) {
337916
337933
  const healthCheck = config2.healthCheck;
337917
- let livenessProbe;
337918
- let readinessProbe;
337919
- if (healthCheck) {
337920
- const probeConfig = {
337921
- initialDelaySeconds: healthCheck.initialDelaySeconds ?? DEFAULTS.healthCheck.initialDelaySeconds,
337922
- periodSeconds: healthCheck.periodSeconds ?? DEFAULTS.healthCheck.periodSeconds,
337923
- failureThreshold: healthCheck.failureThreshold ?? DEFAULTS.healthCheck.failureThreshold,
337924
- successThreshold: healthCheck.successThreshold ?? DEFAULTS.healthCheck.successThreshold,
337925
- timeoutSeconds: healthCheck.timeoutSeconds ?? DEFAULTS.healthCheck.timeoutSeconds
337926
- };
337927
- if (healthCheck.httpPath) {
337928
- const httpGet = {
337929
- path: healthCheck.httpPath,
337930
- port: healthCheck.port ?? config2.containerPort
337931
- };
337932
- livenessProbe = { ...probeConfig, httpGet };
337933
- readinessProbe = { ...probeConfig, httpGet };
337934
- } else {
337935
- const tcpSocket = {
337936
- port: healthCheck.port ?? config2.containerPort
337937
- };
337938
- livenessProbe = { ...probeConfig, tcpSocket };
337939
- readinessProbe = { ...probeConfig, tcpSocket };
337940
- }
337934
+ if (!healthCheck) {
337935
+ return {};
337941
337936
  }
337942
- const resources = config2.resources ?? DEFAULTS.resources;
337943
- const containerPorts = [
337937
+ const probeConfig = {
337938
+ initialDelaySeconds: healthCheck.initialDelaySeconds ?? DEFAULTS.healthCheck.initialDelaySeconds,
337939
+ periodSeconds: healthCheck.periodSeconds ?? DEFAULTS.healthCheck.periodSeconds,
337940
+ failureThreshold: healthCheck.failureThreshold ?? DEFAULTS.healthCheck.failureThreshold,
337941
+ successThreshold: healthCheck.successThreshold ?? DEFAULTS.healthCheck.successThreshold,
337942
+ timeoutSeconds: healthCheck.timeoutSeconds ?? DEFAULTS.healthCheck.timeoutSeconds
337943
+ };
337944
+ if (healthCheck.httpPath) {
337945
+ const httpGet = {
337946
+ path: healthCheck.httpPath,
337947
+ port: healthCheck.port ?? config2.containerPort
337948
+ };
337949
+ return {
337950
+ livenessProbe: { ...probeConfig, httpGet },
337951
+ readinessProbe: { ...probeConfig, httpGet }
337952
+ };
337953
+ }
337954
+ const tcpSocket = { port: healthCheck.port ?? config2.containerPort };
337955
+ return {
337956
+ livenessProbe: { ...probeConfig, tcpSocket },
337957
+ readinessProbe: { ...probeConfig, tcpSocket }
337958
+ };
337959
+ }
337960
+ function buildContainerPorts(config2) {
337961
+ const ports = [
337944
337962
  { containerPort: config2.containerPort, name: "http" }
337945
337963
  ];
337946
337964
  if (config2.additionalPorts) {
337947
337965
  for (const port of config2.additionalPorts) {
337948
- containerPorts.push({
337966
+ ports.push({
337949
337967
  containerPort: port.targetPort ?? port.port,
337950
337968
  name: port.name,
337951
337969
  protocol: port.protocol ?? "TCP"
337952
337970
  });
337953
337971
  }
337954
337972
  }
337973
+ return ports;
337974
+ }
337975
+ function buildServicePorts(config2) {
337976
+ const ports = [
337977
+ {
337978
+ name: "http",
337979
+ port: config2.containerPort,
337980
+ targetPort: config2.containerPort,
337981
+ protocol: "TCP"
337982
+ }
337983
+ ];
337984
+ if (config2.additionalPorts) {
337985
+ for (const port of config2.additionalPorts) {
337986
+ ports.push({
337987
+ name: port.name,
337988
+ port: port.port,
337989
+ targetPort: port.targetPort ?? port.port,
337990
+ protocol: port.protocol ?? "TCP"
337991
+ });
337992
+ }
337993
+ }
337994
+ return ports;
337995
+ }
337996
+ function createServiceIngress(provider, namespace, config2, labels, service) {
337997
+ if (!config2.ingress) {
337998
+ return;
337999
+ }
338000
+ const ingressAnnotations = {
338001
+ "kubernetes.io/ingress.class": "nginx",
338002
+ ...config2.ingress.annotations
338003
+ };
338004
+ if (config2.ingress.tls?.enabled) {
338005
+ ingressAnnotations["cert-manager.io/cluster-issuer"] = config2.ingress.tls.issuerName ?? "letsencrypt-production";
338006
+ }
338007
+ let ingressPath = config2.ingress.path;
338008
+ let ingressPathType = config2.ingress.pathType ?? "Prefix";
338009
+ const shouldStripPrefix = config2.ingress.path !== "/" && !config2.ingress.keepPrefix;
338010
+ if (shouldStripPrefix) {
338011
+ ingressPath = `${config2.ingress.path}(/|$)(.*)`;
338012
+ ingressPathType = "ImplementationSpecific";
338013
+ ingressAnnotations["nginx.ingress.kubernetes.io/use-regex"] = "true";
338014
+ ingressAnnotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2";
338015
+ }
338016
+ const createRule = (host) => ({
338017
+ ...host && { host },
338018
+ http: {
338019
+ paths: [
338020
+ {
338021
+ path: ingressPath,
338022
+ pathType: ingressPathType,
338023
+ backend: {
338024
+ service: {
338025
+ name: config2.name,
338026
+ port: { number: config2.containerPort }
338027
+ }
338028
+ }
338029
+ }
338030
+ ]
338031
+ }
338032
+ });
338033
+ const ingressRules = [];
338034
+ if (config2.ingress.host) {
338035
+ ingressRules.push(createRule(config2.ingress.host));
338036
+ } else {
338037
+ ingressRules.push(createRule());
338038
+ }
338039
+ if (config2.ingress.additionalHosts) {
338040
+ for (const additionalHost of config2.ingress.additionalHosts) {
338041
+ ingressRules.push(createRule(additionalHost));
338042
+ }
338043
+ }
338044
+ const allHosts = [
338045
+ ...config2.ingress.host ? [config2.ingress.host] : [],
338046
+ ...config2.ingress.additionalHosts ?? []
338047
+ ];
338048
+ const tlsSecretName = config2.ingress.tls?.secretName ?? `${config2.name}-tls`;
338049
+ return new k8s5.networking.v1.Ingress(`${config2.name}-ingress`, {
338050
+ metadata: {
338051
+ name: config2.name,
338052
+ namespace,
338053
+ labels,
338054
+ annotations: ingressAnnotations
338055
+ },
338056
+ spec: {
338057
+ ...config2.ingress.tls?.enabled && allHosts.length > 0 && {
338058
+ tls: [{ hosts: allHosts, secretName: tlsSecretName }]
338059
+ },
338060
+ rules: ingressRules
338061
+ }
338062
+ }, { provider, dependsOn: [service] });
338063
+ }
338064
+ function deployK8sService(provider, namespace, config2) {
338065
+ if (!config2.image) {
338066
+ throw new Error(`Missing "image" for service "${config2.name}". Either specify image explicitly or use discoverServiceConfigs() which auto-generates it from pf.registry.`);
338067
+ }
338068
+ const labels = buildLabels(config2.name, config2.labels);
338069
+ const replicas = config2.replicas ?? DEFAULTS.replicas;
338070
+ const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
338071
+ const resources = config2.resources ?? DEFAULTS.resources;
338072
+ const secret = createServiceSecret(provider, namespace, config2, labels);
338073
+ const envVars = buildEnvVars(config2);
338074
+ const { pvcs, volumeMounts, volumes } = createServiceVolumes(provider, namespace, config2, labels);
338075
+ const { livenessProbe, readinessProbe } = buildHealthProbes(config2);
338076
+ const containerPorts = buildContainerPorts(config2);
338077
+ const servicePorts = buildServicePorts(config2);
337955
338078
  const deployment = new k8s5.apps.v1.Deployment(`${config2.name}-deployment`, {
337956
338079
  metadata: {
337957
338080
  name: config2.name,
@@ -337993,24 +338116,6 @@ function deployK8sService(provider, namespace, config2) {
337993
338116
  }
337994
338117
  }
337995
338118
  }, { provider, dependsOn: pvcs.length > 0 ? pvcs : undefined });
337996
- const servicePorts = [
337997
- {
337998
- name: "http",
337999
- port: config2.containerPort,
338000
- targetPort: config2.containerPort,
338001
- protocol: "TCP"
338002
- }
338003
- ];
338004
- if (config2.additionalPorts) {
338005
- for (const port of config2.additionalPorts) {
338006
- servicePorts.push({
338007
- name: port.name,
338008
- port: port.port,
338009
- targetPort: port.targetPort ?? port.port,
338010
- protocol: port.protocol ?? "TCP"
338011
- });
338012
- }
338013
- }
338014
338119
  const service = new k8s5.core.v1.Service(`${config2.name}-service`, {
338015
338120
  metadata: {
338016
338121
  name: config2.name,
@@ -338023,77 +338128,7 @@ function deployK8sService(provider, namespace, config2) {
338023
338128
  ports: servicePorts
338024
338129
  }
338025
338130
  }, { provider, dependsOn: [deployment] });
338026
- let ingress;
338027
- if (config2.ingress) {
338028
- const ingressAnnotations = {
338029
- "kubernetes.io/ingress.class": "nginx",
338030
- ...config2.ingress.annotations
338031
- };
338032
- if (config2.ingress.tls?.enabled) {
338033
- ingressAnnotations["cert-manager.io/cluster-issuer"] = config2.ingress.tls.issuerName ?? "letsencrypt-production";
338034
- }
338035
- let ingressPath = config2.ingress.path;
338036
- let ingressPathType = config2.ingress.pathType ?? "Prefix";
338037
- const shouldStripPrefix = config2.ingress.path !== "/" && !config2.ingress.keepPrefix;
338038
- if (shouldStripPrefix) {
338039
- ingressPath = `${config2.ingress.path}(/|$)(.*)`;
338040
- ingressPathType = "ImplementationSpecific";
338041
- ingressAnnotations["nginx.ingress.kubernetes.io/use-regex"] = "true";
338042
- ingressAnnotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2";
338043
- }
338044
- const tlsSecretName = config2.ingress.tls?.secretName ?? `${config2.name}-tls`;
338045
- const ingressRules = [];
338046
- const createRule = (host) => ({
338047
- ...host && { host },
338048
- http: {
338049
- paths: [
338050
- {
338051
- path: ingressPath,
338052
- pathType: ingressPathType,
338053
- backend: {
338054
- service: {
338055
- name: config2.name,
338056
- port: { number: config2.containerPort }
338057
- }
338058
- }
338059
- }
338060
- ]
338061
- }
338062
- });
338063
- if (config2.ingress.host) {
338064
- ingressRules.push(createRule(config2.ingress.host));
338065
- } else {
338066
- ingressRules.push(createRule());
338067
- }
338068
- if (config2.ingress.additionalHosts) {
338069
- for (const additionalHost of config2.ingress.additionalHosts) {
338070
- ingressRules.push(createRule(additionalHost));
338071
- }
338072
- }
338073
- const allHosts = [
338074
- ...config2.ingress.host ? [config2.ingress.host] : [],
338075
- ...config2.ingress.additionalHosts ?? []
338076
- ];
338077
- ingress = new k8s5.networking.v1.Ingress(`${config2.name}-ingress`, {
338078
- metadata: {
338079
- name: config2.name,
338080
- namespace,
338081
- labels,
338082
- annotations: ingressAnnotations
338083
- },
338084
- spec: {
338085
- ...config2.ingress.tls?.enabled && allHosts.length > 0 && {
338086
- tls: [
338087
- {
338088
- hosts: allHosts,
338089
- secretName: tlsSecretName
338090
- }
338091
- ]
338092
- },
338093
- rules: ingressRules
338094
- }
338095
- }, { provider, dependsOn: [service] });
338096
- }
338131
+ const ingress = createServiceIngress(provider, namespace, config2, labels, service);
338097
338132
  const serviceDns = `${config2.name}.${namespace}.svc.cluster.local`;
338098
338133
  const internalUrl = pulumi6.output(`http://${serviceDns}:${config2.containerPort}`);
338099
338134
  return {
package/dist/index.js CHANGED
@@ -289030,7 +289030,7 @@ var require_lib38 = __commonJS((exports, module) => {
289030
289030
  var require_lib39 = __commonJS((exports, module) => {
289031
289031
  var fs = __require("fs");
289032
289032
  var { promisify } = __require("util");
289033
- var { readFileSync } = fs;
289033
+ var { readFileSync: readFileSync2 } = fs;
289034
289034
  var readFile = promisify(fs.readFile);
289035
289035
  var extractPath = (path, cmdshimContents) => {
289036
289036
  if (/[.]cmd$/.test(path)) {
@@ -289082,7 +289082,7 @@ var require_lib39 = __commonJS((exports, module) => {
289082
289082
  });
289083
289083
  };
289084
289084
  var readCmdShimSync = (path) => {
289085
- const contents = readFileSync(path);
289085
+ const contents = readFileSync2(path);
289086
289086
  const destination = extractPath(path, contents.toString());
289087
289087
  if (!destination) {
289088
289088
  throw notaShim(path);
@@ -308272,7 +308272,7 @@ var require_getImage = __commonJS((exports) => {
308272
308272
  exports.getImageOutput = exports.getImage = undefined;
308273
308273
  var pulumi = require_pulumi();
308274
308274
  var utilities = require_utilities();
308275
- function getImage(args, opts) {
308275
+ function getImage2(args, opts) {
308276
308276
  args = args || {};
308277
308277
  opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
308278
308278
  return pulumi.runtime.invoke("digitalocean:index/getImage:getImage", {
@@ -308282,7 +308282,7 @@ var require_getImage = __commonJS((exports) => {
308282
308282
  source: args.source
308283
308283
  }, opts);
308284
308284
  }
308285
- exports.getImage = getImage;
308285
+ exports.getImage = getImage2;
308286
308286
  function getImageOutput(args, opts) {
308287
308287
  args = args || {};
308288
308288
  opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
@@ -337003,23 +337003,69 @@ var defaultHealthCheck = {
337003
337003
  httpPath: "/health"
337004
337004
  };
337005
337005
  // lib/helpers/discover-services.ts
337006
- import { readdirSync } from "node:fs";
337006
+ import { readdirSync, readFileSync } from "node:fs";
337007
337007
  import { isAbsolute, join, resolve } from "node:path";
337008
- function getServicePort(config) {
337009
- if (config.httpPort)
337010
- return config.httpPort;
337011
- const internalPorts = config.internalPorts;
337012
- if (internalPorts?.[0])
337013
- return internalPorts[0];
337014
- return 8080;
337008
+
337009
+ // lib/helpers/image.ts
337010
+ var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
337011
+ var scopeImageTags = (() => {
337012
+ if (!scopeImageTagsRaw.trim()) {
337013
+ return {};
337014
+ }
337015
+ try {
337016
+ const parsed = JSON.parse(scopeImageTagsRaw);
337017
+ const tags = {};
337018
+ for (const [key, value] of Object.entries(parsed ?? {})) {
337019
+ if (typeof value === "string" && value.trim().length > 0) {
337020
+ tags[key] = value.trim();
337021
+ }
337022
+ }
337023
+ return tags;
337024
+ } catch (error) {
337025
+ console.warn("Unable to parse scope image tags from environment:", error);
337026
+ return {};
337027
+ }
337028
+ })();
337029
+ var resolveImageTag = (scopeName) => {
337030
+ const tag = scopeImageTags[scopeName];
337031
+ if (!tag) {
337032
+ return "latest";
337033
+ }
337034
+ return tag;
337035
+ };
337036
+ var ghcrImage = (registry, serviceName) => {
337037
+ const tag = resolveImageTag(serviceName);
337038
+ return `ghcr.io/${registry}/${serviceName}:${tag}`;
337039
+ };
337040
+ var getImage = (registry, repository, registryCredentials) => {
337041
+ const scopeName = repository.split("/").pop();
337042
+ if (!scopeName) {
337043
+ throw new Error(`Invalid repository name: ${repository}`);
337044
+ }
337045
+ return {
337046
+ registryType: "GHCR",
337047
+ registry,
337048
+ repository,
337049
+ registryCredentials,
337050
+ tag: resolveImageTag(scopeName)
337051
+ };
337052
+ };
337053
+
337054
+ // lib/helpers/discover-services.ts
337055
+ function getRegistryFromPackageJson(workspaceRoot) {
337056
+ const pkgPath = join(workspaceRoot, "package.json");
337057
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
337058
+ if (!pkg.pf?.registry) {
337059
+ throw new Error(`Missing "pf.registry" in ${pkgPath}. Add it to your root package.json, e.g.: "pf": { "registry": "orgname/reponame" }`);
337060
+ }
337061
+ return pkg.pf.registry;
337015
337062
  }
337016
337063
  function validateNoDuplicatePorts(configs) {
337017
337064
  const portMap = new Map;
337018
337065
  for (const config of configs) {
337019
- const port = getServicePort(config);
337020
- const existing = portMap.get(port) || [];
337066
+ const existing = portMap.get(config.containerPort) || [];
337021
337067
  existing.push(config.name);
337022
- portMap.set(port, existing);
337068
+ portMap.set(config.containerPort, existing);
337023
337069
  }
337024
337070
  const conflicts = [...portMap.entries()].filter(([, services]) => services.length > 1).map(([port, services]) => `Port ${port}: ${services.join(", ")}`);
337025
337071
  if (conflicts.length > 0) {
@@ -337028,13 +337074,22 @@ ${conflicts.join(`
337028
337074
  `)}`);
337029
337075
  }
337030
337076
  }
337031
- function discoverServices(servicesDir) {
337077
+ function discoverServiceConfigs(servicesDir = "infra/services") {
337032
337078
  const resolvedDir = isAbsolute(servicesDir) ? servicesDir : resolve(process.cwd(), servicesDir);
337079
+ const workspaceRoot = resolve(resolvedDir, "..", "..");
337080
+ const registry = getRegistryFromPackageJson(workspaceRoot);
337033
337081
  const files = readdirSync(resolvedDir).filter((file) => file.endsWith(".ts") && file !== "index.ts");
337034
337082
  const configs = files.map((file) => {
337035
337083
  const filePath = join(resolvedDir, file);
337036
337084
  const module = __require(filePath);
337037
- return module.default ?? module;
337085
+ const config = module.default ?? module;
337086
+ if (!config.image) {
337087
+ return {
337088
+ ...config,
337089
+ image: ghcrImage(registry, config.name)
337090
+ };
337091
+ }
337092
+ return config;
337038
337093
  });
337039
337094
  validateNoDuplicatePorts(configs);
337040
337095
  return configs;
@@ -337262,50 +337317,6 @@ function buildDropletServices(options) {
337262
337317
  function filterByPlatform(configs, platform) {
337263
337318
  return configs.filter((c) => (c.platform || "app-platform") === platform);
337264
337319
  }
337265
- // lib/helpers/image.ts
337266
- var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
337267
- var scopeImageTags = (() => {
337268
- if (!scopeImageTagsRaw.trim()) {
337269
- return {};
337270
- }
337271
- try {
337272
- const parsed = JSON.parse(scopeImageTagsRaw);
337273
- const tags = {};
337274
- for (const [key, value] of Object.entries(parsed ?? {})) {
337275
- if (typeof value === "string" && value.trim().length > 0) {
337276
- tags[key] = value.trim();
337277
- }
337278
- }
337279
- return tags;
337280
- } catch (error) {
337281
- console.warn("Unable to parse scope image tags from environment:", error);
337282
- return {};
337283
- }
337284
- })();
337285
- var resolveImageTag = (scopeName) => {
337286
- const tag = scopeImageTags[scopeName];
337287
- if (!tag) {
337288
- return "latest";
337289
- }
337290
- return tag;
337291
- };
337292
- var ghcrImage = (registry, serviceName) => {
337293
- const tag = resolveImageTag(serviceName);
337294
- return `ghcr.io/${registry}/${serviceName}:${tag}`;
337295
- };
337296
- var getImage = (registry, repository, registryCredentials) => {
337297
- const scopeName = repository.split("/").pop();
337298
- if (!scopeName) {
337299
- throw new Error(`Invalid repository name: ${repository}`);
337300
- }
337301
- return {
337302
- registryType: "GHCR",
337303
- registry,
337304
- repository,
337305
- registryCredentials,
337306
- tag: resolveImageTag(scopeName)
337307
- };
337308
- };
337309
337320
  // lib/helpers/service-builder.ts
337310
337321
  function buildServices(options) {
337311
337322
  const { serviceConfigs, registryCredentials, logtailToken, registry } = options;
@@ -337350,7 +337361,7 @@ function buildIngressRules(serviceConfigs) {
337350
337361
  }
337351
337362
  // lib/helpers/service-runtime.ts
337352
337363
  var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
337353
- function getServicePort2(serviceName, defaultPort = 8080) {
337364
+ function getServicePort(serviceName, defaultPort = 8080) {
337354
337365
  const envKey = `${toEnvKey(serviceName)}_PORT`;
337355
337366
  const envValue = process.env[envKey];
337356
337367
  if (envValue) {
@@ -337366,7 +337377,7 @@ function getServiceUrl(serviceName) {
337366
337377
  return process.env[envKey];
337367
337378
  }
337368
337379
  // lib/helpers/service-urls.ts
337369
- function getServicePort3(config) {
337380
+ function getServicePort2(config) {
337370
337381
  if (config.httpPort)
337371
337382
  return config.httpPort;
337372
337383
  const internalPorts = config.internalPorts;
@@ -337376,7 +337387,7 @@ function getServicePort3(config) {
337376
337387
  }
337377
337388
  function buildInternalUrls(serviceConfigs) {
337378
337389
  return Object.fromEntries(serviceConfigs.map((config) => {
337379
- const url = config.internalUrl ?? `http://${config.name}:${getServicePort3(config)}`;
337390
+ const url = config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`;
337380
337391
  return [config.name, url];
337381
337392
  }));
337382
337393
  }
@@ -337388,7 +337399,7 @@ function buildExternalUrls(serviceConfigs, baseUrl) {
337388
337399
  }
337389
337400
  function buildLocalUrls(serviceConfigs) {
337390
337401
  return Object.fromEntries(serviceConfigs.map((config) => {
337391
- const port = getServicePort3(config);
337402
+ const port = getServicePort2(config);
337392
337403
  return [config.name, `http://localhost:${port}`];
337393
337404
  }));
337394
337405
  }
@@ -337396,14 +337407,14 @@ function buildServiceUrlEnvs(serviceConfigs) {
337396
337407
  return serviceConfigs.map((config) => ({
337397
337408
  key: `${config.name.toUpperCase().replace(/-/g, "_")}_URL`,
337398
337409
  scope: "RUN_TIME",
337399
- value: config.internalUrl ?? `http://${config.name}:${getServicePort3(config)}`
337410
+ value: config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`
337400
337411
  }));
337401
337412
  }
337402
337413
  function buildServicePortEnvs(serviceConfigs) {
337403
337414
  return serviceConfigs.map((config) => ({
337404
337415
  key: `${config.name.toUpperCase().replace(/-/g, "_")}_PORT`,
337405
337416
  scope: "RUN_TIME",
337406
- value: String(getServicePort3(config))
337417
+ value: String(getServicePort2(config))
337407
337418
  }));
337408
337419
  }
337409
337420
  // lib/runtimes/doks/cert-manager.ts
@@ -337768,10 +337779,7 @@ function getImagePullPolicy(image2, explicit) {
337768
337779
  return explicit;
337769
337780
  return image2.endsWith(":latest") ? "Always" : "IfNotPresent";
337770
337781
  }
337771
- function deployK8sService(provider, namespace, config2) {
337772
- const labels = buildLabels(config2.name, config2.labels);
337773
- const replicas = config2.replicas ?? DEFAULTS.replicas;
337774
- const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
337782
+ function buildEnvVars(config2) {
337775
337783
  const envVars = [];
337776
337784
  envVars.push({ name: "PORT", value: String(config2.containerPort) });
337777
337785
  if (config2.env) {
@@ -337779,17 +337787,7 @@ function deployK8sService(provider, namespace, config2) {
337779
337787
  envVars.push({ name: key, value: pulumi6.output(value) });
337780
337788
  }
337781
337789
  }
337782
- let secret;
337783
- if (config2.secrets && Object.keys(config2.secrets).length > 0) {
337784
- secret = new k8s5.core.v1.Secret(`${config2.name}-secret`, {
337785
- metadata: {
337786
- name: `${config2.name}-secret`,
337787
- namespace,
337788
- labels
337789
- },
337790
- type: "Opaque",
337791
- stringData: config2.secrets
337792
- }, { provider });
337790
+ if (config2.secrets) {
337793
337791
  for (const key of Object.keys(config2.secrets)) {
337794
337792
  envVars.push({
337795
337793
  name: key,
@@ -337802,80 +337800,205 @@ function deployK8sService(provider, namespace, config2) {
337802
337800
  });
337803
337801
  }
337804
337802
  }
337803
+ return envVars;
337804
+ }
337805
+ function createServiceSecret(provider, namespace, config2, labels) {
337806
+ if (!config2.secrets || Object.keys(config2.secrets).length === 0) {
337807
+ return;
337808
+ }
337809
+ return new k8s5.core.v1.Secret(`${config2.name}-secret`, {
337810
+ metadata: {
337811
+ name: `${config2.name}-secret`,
337812
+ namespace,
337813
+ labels
337814
+ },
337815
+ type: "Opaque",
337816
+ stringData: config2.secrets
337817
+ }, { provider });
337818
+ }
337819
+ function createServiceVolumes(provider, namespace, config2, labels) {
337805
337820
  const pvcs = [];
337806
337821
  const volumeMounts = [];
337807
337822
  const volumes = [];
337808
- if (config2.volumes) {
337809
- for (const vol of config2.volumes) {
337810
- const pvc = new k8s5.core.v1.PersistentVolumeClaim(`${config2.name}-${vol.name}`, {
337811
- metadata: {
337812
- name: `${config2.name}-${vol.name}`,
337813
- namespace,
337814
- labels
337815
- },
337816
- spec: {
337817
- accessModes: [vol.accessMode ?? "ReadWriteOnce"],
337818
- storageClassName: vol.storageClass ?? "do-block-storage",
337819
- resources: {
337820
- requests: {
337821
- storage: vol.size ?? "10Gi"
337822
- }
337823
- }
337824
- }
337825
- }, { provider });
337826
- pvcs.push(pvc);
337827
- volumeMounts.push({
337828
- name: vol.name,
337829
- mountPath: vol.mountPath,
337830
- readOnly: vol.readOnly
337831
- });
337832
- volumes.push({
337833
- name: vol.name,
337834
- persistentVolumeClaim: {
337835
- claimName: `${config2.name}-${vol.name}`
337823
+ if (!config2.volumes) {
337824
+ return { pvcs, volumeMounts, volumes };
337825
+ }
337826
+ for (const vol of config2.volumes) {
337827
+ const pvc = new k8s5.core.v1.PersistentVolumeClaim(`${config2.name}-${vol.name}`, {
337828
+ metadata: {
337829
+ name: `${config2.name}-${vol.name}`,
337830
+ namespace,
337831
+ labels
337832
+ },
337833
+ spec: {
337834
+ accessModes: [vol.accessMode ?? "ReadWriteOnce"],
337835
+ storageClassName: vol.storageClass ?? "do-block-storage",
337836
+ resources: {
337837
+ requests: { storage: vol.size ?? "10Gi" }
337836
337838
  }
337837
- });
337838
- }
337839
+ }
337840
+ }, { provider });
337841
+ pvcs.push(pvc);
337842
+ volumeMounts.push({
337843
+ name: vol.name,
337844
+ mountPath: vol.mountPath,
337845
+ readOnly: vol.readOnly
337846
+ });
337847
+ volumes.push({
337848
+ name: vol.name,
337849
+ persistentVolumeClaim: {
337850
+ claimName: `${config2.name}-${vol.name}`
337851
+ }
337852
+ });
337839
337853
  }
337854
+ return { pvcs, volumeMounts, volumes };
337855
+ }
337856
+ function buildHealthProbes(config2) {
337840
337857
  const healthCheck = config2.healthCheck;
337841
- let livenessProbe;
337842
- let readinessProbe;
337843
- if (healthCheck) {
337844
- const probeConfig = {
337845
- initialDelaySeconds: healthCheck.initialDelaySeconds ?? DEFAULTS.healthCheck.initialDelaySeconds,
337846
- periodSeconds: healthCheck.periodSeconds ?? DEFAULTS.healthCheck.periodSeconds,
337847
- failureThreshold: healthCheck.failureThreshold ?? DEFAULTS.healthCheck.failureThreshold,
337848
- successThreshold: healthCheck.successThreshold ?? DEFAULTS.healthCheck.successThreshold,
337849
- timeoutSeconds: healthCheck.timeoutSeconds ?? DEFAULTS.healthCheck.timeoutSeconds
337850
- };
337851
- if (healthCheck.httpPath) {
337852
- const httpGet = {
337853
- path: healthCheck.httpPath,
337854
- port: healthCheck.port ?? config2.containerPort
337855
- };
337856
- livenessProbe = { ...probeConfig, httpGet };
337857
- readinessProbe = { ...probeConfig, httpGet };
337858
- } else {
337859
- const tcpSocket = {
337860
- port: healthCheck.port ?? config2.containerPort
337861
- };
337862
- livenessProbe = { ...probeConfig, tcpSocket };
337863
- readinessProbe = { ...probeConfig, tcpSocket };
337864
- }
337858
+ if (!healthCheck) {
337859
+ return {};
337865
337860
  }
337866
- const resources = config2.resources ?? DEFAULTS.resources;
337867
- const containerPorts = [
337861
+ const probeConfig = {
337862
+ initialDelaySeconds: healthCheck.initialDelaySeconds ?? DEFAULTS.healthCheck.initialDelaySeconds,
337863
+ periodSeconds: healthCheck.periodSeconds ?? DEFAULTS.healthCheck.periodSeconds,
337864
+ failureThreshold: healthCheck.failureThreshold ?? DEFAULTS.healthCheck.failureThreshold,
337865
+ successThreshold: healthCheck.successThreshold ?? DEFAULTS.healthCheck.successThreshold,
337866
+ timeoutSeconds: healthCheck.timeoutSeconds ?? DEFAULTS.healthCheck.timeoutSeconds
337867
+ };
337868
+ if (healthCheck.httpPath) {
337869
+ const httpGet = {
337870
+ path: healthCheck.httpPath,
337871
+ port: healthCheck.port ?? config2.containerPort
337872
+ };
337873
+ return {
337874
+ livenessProbe: { ...probeConfig, httpGet },
337875
+ readinessProbe: { ...probeConfig, httpGet }
337876
+ };
337877
+ }
337878
+ const tcpSocket = { port: healthCheck.port ?? config2.containerPort };
337879
+ return {
337880
+ livenessProbe: { ...probeConfig, tcpSocket },
337881
+ readinessProbe: { ...probeConfig, tcpSocket }
337882
+ };
337883
+ }
337884
+ function buildContainerPorts(config2) {
337885
+ const ports = [
337868
337886
  { containerPort: config2.containerPort, name: "http" }
337869
337887
  ];
337870
337888
  if (config2.additionalPorts) {
337871
337889
  for (const port of config2.additionalPorts) {
337872
- containerPorts.push({
337890
+ ports.push({
337873
337891
  containerPort: port.targetPort ?? port.port,
337874
337892
  name: port.name,
337875
337893
  protocol: port.protocol ?? "TCP"
337876
337894
  });
337877
337895
  }
337878
337896
  }
337897
+ return ports;
337898
+ }
337899
+ function buildServicePorts(config2) {
337900
+ const ports = [
337901
+ {
337902
+ name: "http",
337903
+ port: config2.containerPort,
337904
+ targetPort: config2.containerPort,
337905
+ protocol: "TCP"
337906
+ }
337907
+ ];
337908
+ if (config2.additionalPorts) {
337909
+ for (const port of config2.additionalPorts) {
337910
+ ports.push({
337911
+ name: port.name,
337912
+ port: port.port,
337913
+ targetPort: port.targetPort ?? port.port,
337914
+ protocol: port.protocol ?? "TCP"
337915
+ });
337916
+ }
337917
+ }
337918
+ return ports;
337919
+ }
337920
+ function createServiceIngress(provider, namespace, config2, labels, service) {
337921
+ if (!config2.ingress) {
337922
+ return;
337923
+ }
337924
+ const ingressAnnotations = {
337925
+ "kubernetes.io/ingress.class": "nginx",
337926
+ ...config2.ingress.annotations
337927
+ };
337928
+ if (config2.ingress.tls?.enabled) {
337929
+ ingressAnnotations["cert-manager.io/cluster-issuer"] = config2.ingress.tls.issuerName ?? "letsencrypt-production";
337930
+ }
337931
+ let ingressPath = config2.ingress.path;
337932
+ let ingressPathType = config2.ingress.pathType ?? "Prefix";
337933
+ const shouldStripPrefix = config2.ingress.path !== "/" && !config2.ingress.keepPrefix;
337934
+ if (shouldStripPrefix) {
337935
+ ingressPath = `${config2.ingress.path}(/|$)(.*)`;
337936
+ ingressPathType = "ImplementationSpecific";
337937
+ ingressAnnotations["nginx.ingress.kubernetes.io/use-regex"] = "true";
337938
+ ingressAnnotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2";
337939
+ }
337940
+ const createRule = (host) => ({
337941
+ ...host && { host },
337942
+ http: {
337943
+ paths: [
337944
+ {
337945
+ path: ingressPath,
337946
+ pathType: ingressPathType,
337947
+ backend: {
337948
+ service: {
337949
+ name: config2.name,
337950
+ port: { number: config2.containerPort }
337951
+ }
337952
+ }
337953
+ }
337954
+ ]
337955
+ }
337956
+ });
337957
+ const ingressRules = [];
337958
+ if (config2.ingress.host) {
337959
+ ingressRules.push(createRule(config2.ingress.host));
337960
+ } else {
337961
+ ingressRules.push(createRule());
337962
+ }
337963
+ if (config2.ingress.additionalHosts) {
337964
+ for (const additionalHost of config2.ingress.additionalHosts) {
337965
+ ingressRules.push(createRule(additionalHost));
337966
+ }
337967
+ }
337968
+ const allHosts = [
337969
+ ...config2.ingress.host ? [config2.ingress.host] : [],
337970
+ ...config2.ingress.additionalHosts ?? []
337971
+ ];
337972
+ const tlsSecretName = config2.ingress.tls?.secretName ?? `${config2.name}-tls`;
337973
+ return new k8s5.networking.v1.Ingress(`${config2.name}-ingress`, {
337974
+ metadata: {
337975
+ name: config2.name,
337976
+ namespace,
337977
+ labels,
337978
+ annotations: ingressAnnotations
337979
+ },
337980
+ spec: {
337981
+ ...config2.ingress.tls?.enabled && allHosts.length > 0 && {
337982
+ tls: [{ hosts: allHosts, secretName: tlsSecretName }]
337983
+ },
337984
+ rules: ingressRules
337985
+ }
337986
+ }, { provider, dependsOn: [service] });
337987
+ }
337988
+ function deployK8sService(provider, namespace, config2) {
337989
+ if (!config2.image) {
337990
+ throw new Error(`Missing "image" for service "${config2.name}". Either specify image explicitly or use discoverServiceConfigs() which auto-generates it from pf.registry.`);
337991
+ }
337992
+ const labels = buildLabels(config2.name, config2.labels);
337993
+ const replicas = config2.replicas ?? DEFAULTS.replicas;
337994
+ const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
337995
+ const resources = config2.resources ?? DEFAULTS.resources;
337996
+ const secret = createServiceSecret(provider, namespace, config2, labels);
337997
+ const envVars = buildEnvVars(config2);
337998
+ const { pvcs, volumeMounts, volumes } = createServiceVolumes(provider, namespace, config2, labels);
337999
+ const { livenessProbe, readinessProbe } = buildHealthProbes(config2);
338000
+ const containerPorts = buildContainerPorts(config2);
338001
+ const servicePorts = buildServicePorts(config2);
337879
338002
  const deployment = new k8s5.apps.v1.Deployment(`${config2.name}-deployment`, {
337880
338003
  metadata: {
337881
338004
  name: config2.name,
@@ -337917,24 +338040,6 @@ function deployK8sService(provider, namespace, config2) {
337917
338040
  }
337918
338041
  }
337919
338042
  }, { provider, dependsOn: pvcs.length > 0 ? pvcs : undefined });
337920
- const servicePorts = [
337921
- {
337922
- name: "http",
337923
- port: config2.containerPort,
337924
- targetPort: config2.containerPort,
337925
- protocol: "TCP"
337926
- }
337927
- ];
337928
- if (config2.additionalPorts) {
337929
- for (const port of config2.additionalPorts) {
337930
- servicePorts.push({
337931
- name: port.name,
337932
- port: port.port,
337933
- targetPort: port.targetPort ?? port.port,
337934
- protocol: port.protocol ?? "TCP"
337935
- });
337936
- }
337937
- }
337938
338043
  const service = new k8s5.core.v1.Service(`${config2.name}-service`, {
337939
338044
  metadata: {
337940
338045
  name: config2.name,
@@ -337947,77 +338052,7 @@ function deployK8sService(provider, namespace, config2) {
337947
338052
  ports: servicePorts
337948
338053
  }
337949
338054
  }, { provider, dependsOn: [deployment] });
337950
- let ingress;
337951
- if (config2.ingress) {
337952
- const ingressAnnotations = {
337953
- "kubernetes.io/ingress.class": "nginx",
337954
- ...config2.ingress.annotations
337955
- };
337956
- if (config2.ingress.tls?.enabled) {
337957
- ingressAnnotations["cert-manager.io/cluster-issuer"] = config2.ingress.tls.issuerName ?? "letsencrypt-production";
337958
- }
337959
- let ingressPath = config2.ingress.path;
337960
- let ingressPathType = config2.ingress.pathType ?? "Prefix";
337961
- const shouldStripPrefix = config2.ingress.path !== "/" && !config2.ingress.keepPrefix;
337962
- if (shouldStripPrefix) {
337963
- ingressPath = `${config2.ingress.path}(/|$)(.*)`;
337964
- ingressPathType = "ImplementationSpecific";
337965
- ingressAnnotations["nginx.ingress.kubernetes.io/use-regex"] = "true";
337966
- ingressAnnotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2";
337967
- }
337968
- const tlsSecretName = config2.ingress.tls?.secretName ?? `${config2.name}-tls`;
337969
- const ingressRules = [];
337970
- const createRule = (host) => ({
337971
- ...host && { host },
337972
- http: {
337973
- paths: [
337974
- {
337975
- path: ingressPath,
337976
- pathType: ingressPathType,
337977
- backend: {
337978
- service: {
337979
- name: config2.name,
337980
- port: { number: config2.containerPort }
337981
- }
337982
- }
337983
- }
337984
- ]
337985
- }
337986
- });
337987
- if (config2.ingress.host) {
337988
- ingressRules.push(createRule(config2.ingress.host));
337989
- } else {
337990
- ingressRules.push(createRule());
337991
- }
337992
- if (config2.ingress.additionalHosts) {
337993
- for (const additionalHost of config2.ingress.additionalHosts) {
337994
- ingressRules.push(createRule(additionalHost));
337995
- }
337996
- }
337997
- const allHosts = [
337998
- ...config2.ingress.host ? [config2.ingress.host] : [],
337999
- ...config2.ingress.additionalHosts ?? []
338000
- ];
338001
- ingress = new k8s5.networking.v1.Ingress(`${config2.name}-ingress`, {
338002
- metadata: {
338003
- name: config2.name,
338004
- namespace,
338005
- labels,
338006
- annotations: ingressAnnotations
338007
- },
338008
- spec: {
338009
- ...config2.ingress.tls?.enabled && allHosts.length > 0 && {
338010
- tls: [
338011
- {
338012
- hosts: allHosts,
338013
- secretName: tlsSecretName
338014
- }
338015
- ]
338016
- },
338017
- rules: ingressRules
338018
- }
338019
- }, { provider, dependsOn: [service] });
338020
- }
338055
+ const ingress = createServiceIngress(provider, namespace, config2, labels, service);
338021
338056
  const serviceDns = `${config2.name}.${namespace}.svc.cluster.local`;
338022
338057
  const internalUrl = pulumi6.output(`http://${serviceDns}:${config2.containerPort}`);
338023
338058
  return {
@@ -338060,12 +338095,12 @@ function buildInternalUrl(serviceName, namespace, port) {
338060
338095
  export {
338061
338096
  ghcrImage,
338062
338097
  getServiceUrl,
338063
- getServicePort2 as getServicePort,
338098
+ getServicePort,
338064
338099
  getImage,
338065
338100
  filterByPlatform,
338066
338101
  ensureDot,
338067
338102
  dockerHubImage,
338068
- discoverServices,
338103
+ discoverServiceConfigs,
338069
338104
  deployNginxIngress,
338070
338105
  deployNats,
338071
338106
  deployK8sServices,
@@ -197,8 +197,11 @@ export interface K8sVolumeMount {
197
197
  export interface K8sServiceConfig {
198
198
  /** Unique name of the service (used for deployment, service, and labels) */
199
199
  name: string;
200
- /** Container image (e.g., 'ghcr.io/orderboss/platform/storefront:latest') */
201
- image: string;
200
+ /**
201
+ * Container image (e.g., 'ghcr.io/orderboss/platform/storefront:latest').
202
+ * If not specified, auto-generated from pf.registry config + service name.
203
+ */
204
+ image?: string;
202
205
  /** Port the container listens on */
203
206
  containerPort: number;
204
207
  /** Number of replicas (defaults to 1) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/infrastructure",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {