diff --git a/.gitea/actions/quickstack-oci/deploy.mjs b/.gitea/actions/quickstack-oci/deploy.mjs index b0b29e8..55f9924 100644 --- a/.gitea/actions/quickstack-oci/deploy.mjs +++ b/.gitea/actions/quickstack-oci/deploy.mjs @@ -23,7 +23,7 @@ const APP_FIELDS = [ "securityContextRunAsGroup", "securityContextFsGroup", "securityContextPrivileged", "gitUrl", "gitBranch", "gitUsername", "gitToken", "dockerfilePath", "replicas", "envVars", "memoryReservation", "memoryLimit", "cpuReservation", "cpuLimit", - "webhookId", "ingressNetworkPolicy", "egressNetworkPolicy", "useNetworkPolicy", + "webhookId", "useNetworkPolicy", "healthChechHttpGetPath", "healthCheckHttpScheme", "healthCheckHttpHeadersJson", "healthCheckHttpPort", "healthCheckPeriodSeconds", "healthCheckTimeoutSeconds", "healthCheckFailureThreshold", "healthCheckTcpPort", @@ -31,7 +31,6 @@ const APP_FIELDS = [ const COLLECTION_FIELDS = { appDomains: ["id", "hostname", "port", "useSsl", "redirectHttps"], - appPorts: ["id", "port"], appNodePorts: ["id", "port", "nodePort", "protocol"], appFileMounts: ["id", "containerMountPath", "content"], appVolumes: [ @@ -58,6 +57,11 @@ export function toSavePayload(app) { for (const [name, fields] of Object.entries(COLLECTION_FIELDS)) { payload[name] = Array.isArray(app[name]) ? app[name].map((item) => pick(item, fields)) : []; } + payload.appNetworkPolicy = app.appNetworkPolicy ? { + ...pick(app.appNetworkPolicy, ["id", "allowInternetAccess"]), + rules: app.appNetworkPolicy.rules.map((rule) => + pick(rule, ["id", "type", "targetAppId", "targetAgentId", "port", "protocol"])), + } : null; return payload; } diff --git a/.gitea/actions/quickstack-oci/deploy.test.mjs b/.gitea/actions/quickstack-oci/deploy.test.mjs index b2081b1..04b1ff3 100644 --- a/.gitea/actions/quickstack-oci/deploy.test.mjs +++ b/.gitea/actions/quickstack-oci/deploy.test.mjs @@ -297,6 +297,32 @@ test("preserves response-only fields and existing environment secrets safely", ( }); }); +test("uses the current QuickStack write contract and preserves network policy rules", () => { + const policy = { + id: "policy-one", appId: "app-one", allowInternetAccess: false, + rules: [ + { id: "rule-app", type: "APP", targetAppId: "database", targetAgentId: null, port: 5432, protocol: "TCP", targetApp: {} }, + { id: "rule-agent", type: "AGENT", targetAppId: null, targetAgentId: "agent-one", port: 9000, protocol: "TCP", policyId: "policy-one" }, + { type: "APP", targetAppId: "dns", port: 53, protocol: "UDP" }, + ], + }; + const payload = toSavePayload(app({ appNetworkPolicy: policy, networkPolicyMode: "legacy" })); + assert.deepEqual(payload.appNetworkPolicy, { + id: "policy-one", allowInternetAccess: false, + rules: [ + { id: "rule-app", type: "APP", targetAppId: "database", targetAgentId: null, port: 5432, protocol: "TCP" }, + { id: "rule-agent", type: "AGENT", targetAppId: null, targetAgentId: "agent-one", port: 9000, protocol: "TCP" }, + { type: "APP", targetAppId: "dns", port: 53, protocol: "UDP" }, + ], + }); + for (const field of ["appPorts", "ingressNetworkPolicy", "egressNetworkPolicy", "networkPolicyMode"]) { + assert.equal(Object.hasOwn(payload, field), false); + } + assert.equal(toSavePayload(app()).appNetworkPolicy, null); + assert.deepEqual(toSavePayload(app({ appNetworkPolicy: { allowInternetAccess: true, rules: [] } })).appNetworkPolicy, + { allowInternetAccess: true, rules: [] }); +}); + test("overrides only declared existing QuickStack volume mount paths", () => { const volumes = [ { id: "volume-local", sharedVolumeId: "shared-volume", containerMountPath: "/old" }, @@ -348,12 +374,19 @@ test("postflight waits for the exact expected identity", async () => { }); test("failed public verification restores and redeploys the previous source", async () => { - const original = app(); + const original = app({ appNetworkPolicy: { + id: "policy-one", allowInternetAccess: false, + rules: [{ id: "rule-one", type: "APP", targetAppId: "database", port: 5432, protocol: "TCP" }], + } }); let current = original; let deployments = 0; const client = { getApp: async () => current, - saveApp: async (payload) => { current = { ...current, ...payload }; }, + saveApp: async (payload) => { + assert.deepEqual(payload.appNetworkPolicy, original.appNetworkPolicy); + assert.equal(Object.hasOwn(payload, "appPorts"), false); + current = { ...current, ...payload }; + }, deployApp: async () => ({ deploymentId: `deployment-${++deployments}` }), getDeployment: async (_appId, deploymentId) => ({ deploymentId, status: "DEPLOYED" }), };