@fjall/deploy-core 2.32.0 → 2.33.0
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.
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
218 files minified at 2026-07-
|
|
1
|
+
218 files minified at 2026-07-17T12:49:59.029Z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAbsolute as H,join as K,dirname as z,resolve as _}from"node:path";import{existsSync as F}from"node:fs";import{success as N,failure as h}from"@fjall/generator";import{deriveContentHashTag as J,getErrorMessage as M,maskSensitiveOutput as b,toKebab as x}from"@fjall/util";import{evaluateBakeGuard as X,evaluateResolvedBuildArgValues as Y,acknowledgedBuildArgKeys as q,createBakeWarningDeduper as Q,buildCacheRepositoryName as U,BAKE_GUARD_EXPOSURE_CLAUSE as Z}from"@fjall/util/docker";import{runWithBoundedConcurrency as ee,resolveBuildConcurrency as re}from"@fjall/util/buildConcurrency";import{logger as T}from"@fjall/util/logger";import{parseDockerServicesFromManifest as te,parseLambdaDockerServicesFromManifest as ne}from"@fjall/util/manifest";import{STEP_IDS as A}from"../../types/stepDefinitions.js";import{isAborted as G}from"../../aws/organisations/types.js";import{resolveBuildSecrets as oe,resolveSecretRefValue as ie,resolveBuildSecretSessionProvider as se,sourcedRefsFromBuildArgs as ae}from"./buildSecretResolver.js";import{partitionForRegion as de}from"./buildSecretSession.js";import{resolveBuildArgs as ce}from"./buildArgResolver.js";import{validateBuildGroup as ue}from"./buildGroupValidator.js";import{ensureBuildCacheRepository as le}from"./ecrCacheRepository.js";const L="dockerBuildHelper",C="Building and pushing Docker image";function fe(e,n,t){const r=K(n,e);if(F(r))return{path:r,base:n};if(t!==""&&t!==n){const o=K(t,e);if(F(o))return{path:o,base:t}}return{path:r,base:n}}function ge(e,n,t){const r=e.docker.path,o=e.docker.context;let s,a;if(H(r))s=r,a=n;else{const m=fe(r,n,t);s=m.path,a=m.base}return{buildContext:o?H(o)?_(o):_(a,o):_(z(s)),dockerfilePath:s,target:e.docker.target,resolvedViaFallback:a!==n}}function me(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function pe(e,n){return`Dockerfile not found for service '${e}' at ${n}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function he(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function V(e,n){return`${e}.dkr.ecr.${n}.${he(n)}`}function be(e,n,t){return`${V(e,n)}/${x(t)}`}function ye(e,n,t){return`${V(e,n)}/${U(x(t))}`}function ve(e,n,t){const r=new Map;for(const o of e){const s=ge(o,n,t),a=me(s),g=r.get(a);g?g.members.push(o):r.set(a,{identity:s,members:[o]})}return Array.from(r.values())}async function Se(e,n,t,r,o){const s=await e.buildAndPush({appName:n.appName,appPath:n.path,region:t,accountId:r},(a,g,m,S)=>{o.onDockerProgress?.(b(a),g,m,S)});return s.success?N({imageUri:s.data.imageUri,imageTag:s.data.imageTag}):h(s.error)}function j(){return{contentHashTagsByService:{},imageDigestsByService:{}}}async function we(e,n,t,r,o,s,a,g,m,S,E,f,k,w){const l=e.members[0];if(!F(e.identity.dockerfilePath))return h(new Error(pe(l.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&g.onLog?.(`Building service '${l.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const y=[...l.docker.buildSecrets??[],...k??[]],P=ue({serviceName:l.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...l.docker.buildArgs!==void 0&&{buildArgs:l.docker.buildArgs},...y.length>0&&{buildSecrets:y},members:e.members.map(i=>({serviceName:i.name,...i.docker.buildArgs!==void 0&&{buildArgs:i.docker.buildArgs},...i.docker.buildSecrets!==void 0&&{buildSecrets:i.docker.buildSecrets}}))});if(!P.success)return h(P.error);const d=X(l.docker.buildArgs,x(t.appName));for(const i of d.warnings){const v=b(i.message);w(v)&&g.onLog?.(v,"warn")}if(d.findings.length>0)return h(new Error(b(d.findings.map(i=>i.message).join(" "))));const c=y,D=ae(l.docker.buildArgs),I={appName:t.appName,region:r,accountId:o,partition:de(r)},$=await se(m,[...c,...D],I);if(!$.success)return h($.error);const O=$.data,R=await ce({manifestBuildArgs:l.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:t.path,...E!==void 0&&{stage:E},shellEnv:process.env,...f!==void 0&&{flagBuildArgs:f},resolveSourcedValue:(i,v)=>ie(O,{id:i,...v},S)});if(!R.success)return h(R.error);const B=R.data.buildArgs;for(const i of R.data.inferredDotenvKeys){const v=b(`buildArg "${i}" was auto-declared from a .env file and will be baked into the image \u2014 ${Z}.`);w(v)&&g.onLog?.(v,"warn")}const u=Y(B,x(t.appName),q(l.docker.buildArgs));if(u.length>0)return h(new Error(b(u.map(i=>i.message).join(" "))));let p;if(c.length>0){const i=await oe(O,c,S);if(!i.success)return h(i.error);p=i.data}try{if(G(S))return h(new Error(`Build of service '${l.name}' was aborted before the docker build started`));const i={appName:t.appName,appPath:e.identity.buildContext,region:r,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:l.name,...a!==void 0&&{cacheRepoUri:a},...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(B).length>0&&{buildArgs:B},...p!==void 0&&{buildSecrets:p.resolved}};return await Ee(e,i,n,s,g)}finally{p!==void 0&&await p.cleanup().catch(()=>g.onLog?.("Build-secret cleanup failed.","warn"))}}async function Ee(e,n,t,r,o){const s=await t.buildAndPush(n,(f,k,w,l)=>{o.onDockerProgress?.(b(f),k,w,l)});if(!s.success)return h(s.error);const a=s.data.imageDigest;if(typeof a!="string"||!a.startsWith("sha256:"))return h(new Error(`Buildx push succeeded but returned an invalid digest: ${b(String(a))}`));const g={},m={},S=[];for(const f of e.members){const k=f.docker.target,w=J(a,f.name,k);if(!w.success)return h(w.error);const l=w.data,y=f.name;g[y]=l,m[y]=a,S.push(`${r}:${l}`)}const E=await t.tagByDigest({sourceImage:r,digest:a,tags:S});return E.success?N({contentHashTagsByService:g,imageDigestsByService:m}):h(E.error)}function ke(e,n){const t=new Map(e.map(r=>[r.name,r]));for(const r of n){const o=t.get(r.name);if(o!==void 0&&JSON.stringify(o.docker)!==JSON.stringify(r.docker))throw new Error(`ECS service "${o.name}" and Lambda image key "${r.name}" share one build key but declare different docker configs \u2014 the second build would silently overwrite the first and both would deploy the same image. Rename one of them, or make the docker configs identical to deliberately share the build.`)}}async function Fe(e,n,t,r){const o=e.dockerProvider;if(!o)return N(j());const s=n.awsProvider.getAccountId();if(!s)return r.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),N(j());const a=n.awsProvider.getRegion(),g=K(t.path,"cdk.out");let m;try{const d=te(g),c=ne(g);ke(d,c),m=[...d,...c]}catch(d){const c=new Error(b(M(d)));return r.onError?.(c),h(c)}const S=be(s,a,t.appName);if(T.debug(L,"runDockerBuild starting",{appName:t.appName,appPath:t.path,accountId:s,region:a,cdkOutPath:g,manifestServiceCount:m.length,manifestServices:m.map(d=>({name:d.name,path:d.docker.path,context:d.docker.context,target:d.docker.target})),stepId:A.DOCKER_OPERATIONS,stepName:C}),r.onStepStart?.(A.DOCKER_OPERATIONS,C),m.length===0){r.onLog?.("Initialising ECR repository\u2026","info");const d=await o.initialiseECR({appName:t.appName,region:a,accountId:s});d.success||r.onLog?.(`ECR initialisation failed: ${b(d.error.message)}`,"warn"),r.onLog?.("Building and pushing Docker image\u2026","info");const c=await Se(o,t,a,s,r);if(!c.success){T.debug(L,"Docker buildAndPush (legacy) failed",{appName:t.appName,error:b(c.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const D=new Error(b(c.error.message));return r.onError?.(D),h(D)}return r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker image pushed: ${c.data.imageUri}`,"info"),N(j())}const E=await o.preflight?.();if(E!==void 0&&!E.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const d=new Error(b(E.error.message));return r.onError?.(d),h(d)}const f=ve(m,t.path,e.workingDirectory);let k=ye(s,a,t.appName);const w=await le(n.awsProvider,U(x(t.appName)),e.abortSignal);w.success||(k=void 0,r.onLog?.(`Building without registry cache \u2014 ${w.error.message}`,"warn")),r.onLog?.(`Building and pushing ${m.length} service image(s) in ${f.length} group(s)\u2026`,"info");let l=!1;if(o.beginEcrSession!==void 0){const d=await o.beginEcrSession({accountId:s,region:a});if(!d.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const c=new Error(b(d.error.message));return r.onError?.(c),h(c)}l=!0}const y=new AbortController,P=()=>y.abort();e.abortSignal!==void 0&&(e.abortSignal.aborted?y.abort():e.abortSignal.addEventListener("abort",P,{once:!0}));try{const d=()=>G(e.abortSignal)?"the deployment was cancelled":"a sibling group failed";let c;const D=re(f.length),I=Q(),$=f.map((u,p)=>async()=>{const i=u.members.map(W=>W.name).join(", ");if(y.signal.aborted)throw new Error(`Build of group ${p+1}/${f.length} (${i}) was not started \u2014 ${d()}`);r.onLog?.(`Building group ${p+1}/${f.length} (${i})\u2026`,"info");const v=await we(u,o,t,a,s,S,k,r,n.awsProvider,y.signal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets,I);if(!v.success)throw c===void 0&&(c={error:v.error,leader:u.members[0]?.name,members:i}),y.abort(),v.error;return v.data}),O=await ee($,D,{abortOnFirstFailure:!0,abortSignal:y.signal});if(c===void 0)for(let u=0;u<f.length;u++){const p=O[u];if(!(p!==void 0&&p.success)){c={error:p!==void 0&&!p.success?p.error:new Error(`Build of group ${u+1}/${f.length} never ran \u2014 ${d()}`),leader:f[u].members[0]?.name,members:f[u].members.map(i=>i.name).join(", ")};break}}if(c!==void 0){T.debug(L,"Docker buildAndPush failed",{appName:t.appName,leader:c.leader,members:c.members,error:b(c.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const u=new Error(b(c.error.message));return r.onError?.(u),h(u)}const R={},B={};for(const u of O)if(!(u===void 0||!u.success)){for(const[p,i]of Object.entries(u.data.contentHashTagsByService))R[p]=i;for(const[p,i]of Object.entries(u.data.imageDigestsByService))B[p]=i}return T.debug(L,"Docker buildAndPush succeeded",{appName:t.appName,services:m.map(u=>u.name),groupCount:f.length,contentHashTags:R}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker images pushed for ${m.length} service(s)`,"info"),N({contentHashTagsByService:R,imageDigestsByService:B,repoUri:S})}finally{e.abortSignal?.removeEventListener("abort",P),l&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(d=>{T.debug(L,"endEcrSession cleanup failed",{error:b(M(d))})})}}export{C as DOCKER_BUILD_STEP_NAME,be as ecrRepoUri,j as emptyBuildProducts,Fe as runDockerBuild};
|
|
1
|
+
import{isAbsolute as M,join as _,dirname as z,resolve as F}from"node:path";import{existsSync as j}from"node:fs";import{success as N,failure as h}from"@fjall/generator";import{deriveContentHashTag as J,getErrorMessage as U,maskSensitiveOutput as b,toKebab as x}from"@fjall/util";import{evaluateBakeGuard as X,evaluateResolvedBuildArgValues as Y,acknowledgedBuildArgKeys as q,createBakeWarningDeduper as Q,buildCacheRepositoryName as G,BAKE_GUARD_EXPOSURE_CLAUSE as Z}from"@fjall/util/docker";import{runWithBoundedConcurrency as ee,resolveBuildConcurrency as re}from"@fjall/util/buildConcurrency";import{logger as L}from"@fjall/util/logger";import{parseDockerServicesFromManifest as te,parseLambdaDockerServicesFromManifest as ne}from"@fjall/util/manifest";import{STEP_IDS as A}from"../../types/stepDefinitions.js";import{isAborted as V}from"../../aws/organisations/types.js";import{resolveBuildSecrets as oe,resolveSecretRefValue as ie,resolveBuildSecretSessionProvider as se,sourcedRefsFromBuildArgs as ae}from"./buildSecretResolver.js";import{partitionForRegion as de}from"./buildSecretSession.js";import{resolveBuildArgs as ce}from"./buildArgResolver.js";import{validateBuildGroup as ue}from"./buildGroupValidator.js";import{ensureBuildCacheRepository as le}from"./ecrCacheRepository.js";const T="dockerBuildHelper",C="Building and pushing Docker image";function fe(e,n,t){const r=_(n,e);if(j(r))return{path:r,base:n};if(t!==""&&t!==n){const o=_(t,e);if(j(o))return{path:o,base:t}}return{path:r,base:n}}function me(e,n,t){const r=e.docker.path,o=e.docker.context;let s,a;if(M(r))s=r,a=n;else{const g=fe(r,n,t);s=g.path,a=g.base}return{buildContext:o?M(o)?F(o):F(a,o):F(z(s)),dockerfilePath:s,target:e.docker.target,resolvedViaFallback:a!==n}}function ge(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function pe(e,n){return`Dockerfile not found for service '${e}' at ${n}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function he(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function W(e,n){return`${e}.dkr.ecr.${n}.${he(n)}`}function be(e,n,t){return`${W(e,n)}/${x(t)}`}function ye(e,n,t){return`${W(e,n)}/${G(x(t))}`}function ve(e,n,t){const r=new Map;for(const o of e){const s=me(o,n,t),a=ge(s),m=r.get(a);m?m.members.push(o):r.set(a,{identity:s,members:[o]})}return Array.from(r.values())}async function Se(e,n,t,r,o){const s=await e.buildAndPush({appName:n.appName,appPath:n.path,region:t,accountId:r},(a,m,g,S)=>{o.onDockerProgress?.(b(a),m,g,S)});return s.success?N({imageUri:s.data.imageUri,imageTag:s.data.imageTag}):h(s.error)}function H(){return{contentHashTagsByService:{},imageDigestsByService:{}}}async function we(e,n,t,r,o,s,a,m,g,S,E,f,k,w){const l=e.members[0];if(!j(e.identity.dockerfilePath))return h(new Error(pe(l.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&m.onLog?.(`Building service '${l.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const v=[...l.docker.buildSecrets??[],...k??[]],P=ue({serviceName:l.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...l.docker.buildArgs!==void 0&&{buildArgs:l.docker.buildArgs},...v.length>0&&{buildSecrets:v},members:e.members.map(i=>({serviceName:i.name,...i.docker.buildArgs!==void 0&&{buildArgs:i.docker.buildArgs},...i.docker.buildSecrets!==void 0&&{buildSecrets:i.docker.buildSecrets}}))});if(!P.success)return h(P.error);const d=X(l.docker.buildArgs,x(t.appName));for(const i of d.warnings){const y=b(i.message);w(y)&&m.onLog?.(y,"warn")}if(d.findings.length>0)return h(new Error(b(d.findings.map(i=>i.message).join(" "))));const c=v,D=ae(l.docker.buildArgs),I={appName:t.appName,region:r,accountId:o,partition:de(r)},$=await se(g,[...c,...D],I);if(!$.success)return h($.error);const O=$.data,R=await ce({manifestBuildArgs:l.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:t.path,...E!==void 0&&{stage:E},shellEnv:process.env,...f!==void 0&&{flagBuildArgs:f},resolveSourcedValue:(i,y)=>ie(O,{id:i,...y},S)});if(!R.success)return h(R.error);const B=R.data.buildArgs;for(const i of R.data.inferredDotenvKeys){const y=b(`buildArg "${i}" was auto-declared from a .env file and will be baked into the image \u2014 ${Z}.`);w(y)&&m.onLog?.(y,"warn")}const u=Y(B,x(t.appName),q(l.docker.buildArgs));if(u.length>0)return h(new Error(b(u.map(i=>i.message).join(" "))));let p;if(c.length>0){const i=await oe(O,c,S);if(!i.success)return h(i.error);p=i.data}try{if(V(S))return h(new Error(`Build of service '${l.name}' was aborted before the docker build started`));const i=e.members.some(K=>K.isLambda===!0),y={appName:t.appName,appPath:e.identity.buildContext,region:r,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:l.name,targetsLambda:i,...a!==void 0&&{cacheRepoUri:a},...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(B).length>0&&{buildArgs:B},...p!==void 0&&{buildSecrets:p.resolved}};return await Ee(e,y,n,s,m)}finally{p!==void 0&&await p.cleanup().catch(()=>m.onLog?.("Build-secret cleanup failed.","warn"))}}async function Ee(e,n,t,r,o){const s=await t.buildAndPush(n,(f,k,w,l)=>{o.onDockerProgress?.(b(f),k,w,l)});if(!s.success)return h(s.error);const a=s.data.imageDigest;if(typeof a!="string"||!a.startsWith("sha256:"))return h(new Error(`Buildx push succeeded but returned an invalid digest: ${b(String(a))}`));const m={},g={},S=[];for(const f of e.members){const k=f.docker.target,w=J(a,f.name,k);if(!w.success)return h(w.error);const l=w.data,v=f.name;m[v]=l,g[v]=a,S.push(`${r}:${l}`)}const E=await t.tagByDigest({sourceImage:r,digest:a,tags:S});return E.success?N({contentHashTagsByService:m,imageDigestsByService:g}):h(E.error)}function ke(e,n){const t=new Map(e.map(r=>[r.name,r]));for(const r of n){const o=t.get(r.name);if(o!==void 0&&JSON.stringify(o.docker)!==JSON.stringify(r.docker))throw new Error(`ECS service "${o.name}" and Lambda image key "${r.name}" share one build key but declare different docker configs \u2014 the second build would silently overwrite the first and both would deploy the same image. Rename one of them, or make the docker configs identical to deliberately share the build.`)}}async function Fe(e,n,t,r){const o=e.dockerProvider;if(!o)return N(H());const s=n.awsProvider.getAccountId();if(!s)return r.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),N(H());const a=n.awsProvider.getRegion(),m=_(t.path,"cdk.out");let g;try{const d=te(m),c=ne(m);ke(d,c),g=[...d,...c]}catch(d){const c=new Error(b(U(d)));return r.onError?.(c),h(c)}const S=be(s,a,t.appName);if(L.debug(T,"runDockerBuild starting",{appName:t.appName,appPath:t.path,accountId:s,region:a,cdkOutPath:m,manifestServiceCount:g.length,manifestServices:g.map(d=>({name:d.name,path:d.docker.path,context:d.docker.context,target:d.docker.target})),stepId:A.DOCKER_OPERATIONS,stepName:C}),r.onStepStart?.(A.DOCKER_OPERATIONS,C),g.length===0){r.onLog?.("Initialising ECR repository\u2026","info");const d=await o.initialiseECR({appName:t.appName,region:a,accountId:s});d.success||r.onLog?.(`ECR initialisation failed: ${b(d.error.message)}`,"warn"),r.onLog?.("Building and pushing Docker image\u2026","info");const c=await Se(o,t,a,s,r);if(!c.success){L.debug(T,"Docker buildAndPush (legacy) failed",{appName:t.appName,error:b(c.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const D=new Error(b(c.error.message));return r.onError?.(D),h(D)}return r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker image pushed: ${c.data.imageUri}`,"info"),N(H())}const E=await o.preflight?.();if(E!==void 0&&!E.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const d=new Error(b(E.error.message));return r.onError?.(d),h(d)}const f=ve(g,t.path,e.workingDirectory);let k=ye(s,a,t.appName);const w=await le(n.awsProvider,G(x(t.appName)),e.abortSignal);w.success||(k=void 0,r.onLog?.(`Building without registry cache \u2014 ${w.error.message}`,"warn")),r.onLog?.(`Building and pushing ${g.length} service image(s) in ${f.length} group(s)\u2026`,"info");let l=!1;if(o.beginEcrSession!==void 0){const d=await o.beginEcrSession({accountId:s,region:a});if(!d.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const c=new Error(b(d.error.message));return r.onError?.(c),h(c)}l=!0}const v=new AbortController,P=()=>v.abort();e.abortSignal!==void 0&&(e.abortSignal.aborted?v.abort():e.abortSignal.addEventListener("abort",P,{once:!0}));try{const d=()=>V(e.abortSignal)?"the deployment was cancelled":"a sibling group failed";let c;const D=re(f.length),I=Q(),$=f.map((u,p)=>async()=>{const i=u.members.map(K=>K.name).join(", ");if(v.signal.aborted)throw new Error(`Build of group ${p+1}/${f.length} (${i}) was not started \u2014 ${d()}`);r.onLog?.(`Building group ${p+1}/${f.length} (${i})\u2026`,"info");const y=await we(u,o,t,a,s,S,k,r,n.awsProvider,v.signal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets,I);if(!y.success)throw c===void 0&&(c={error:y.error,leader:u.members[0]?.name,members:i}),v.abort(),y.error;return y.data}),O=await ee($,D,{abortOnFirstFailure:!0,abortSignal:v.signal});if(c===void 0)for(let u=0;u<f.length;u++){const p=O[u];if(!(p!==void 0&&p.success)){c={error:p!==void 0&&!p.success?p.error:new Error(`Build of group ${u+1}/${f.length} never ran \u2014 ${d()}`),leader:f[u].members[0]?.name,members:f[u].members.map(i=>i.name).join(", ")};break}}if(c!==void 0){L.debug(T,"Docker buildAndPush failed",{appName:t.appName,leader:c.leader,members:c.members,error:b(c.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const u=new Error(b(c.error.message));return r.onError?.(u),h(u)}const R={},B={};for(const u of O)if(!(u===void 0||!u.success)){for(const[p,i]of Object.entries(u.data.contentHashTagsByService))R[p]=i;for(const[p,i]of Object.entries(u.data.imageDigestsByService))B[p]=i}return L.debug(T,"Docker buildAndPush succeeded",{appName:t.appName,services:g.map(u=>u.name),groupCount:f.length,contentHashTags:R}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker images pushed for ${g.length} service(s)`,"info"),N({contentHashTagsByService:R,imageDigestsByService:B,repoUri:S})}finally{e.abortSignal?.removeEventListener("abort",P),l&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(d=>{L.debug(T,"endEcrSession cleanup failed",{error:b(U(d))})})}}export{C as DOCKER_BUILD_STEP_NAME,be as ecrRepoUri,H as emptyBuildProducts,Fe as runDockerBuild};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={appName:!0,appPath:!0,region:!0,accountId:!0,imageTag:!0,services:!0,platform:!0,cacheRepoUri:!0,serviceName:!0,target:!0,dockerfilePath:!0,buildContext:!0,imageTags:!0,pushByDigest:!0,buildArgs:!0,buildSecrets:!0},t=Object.keys(e);export{t as ALL_DOCKER_BUILD_PARAM_KEYS};
|
|
1
|
+
const e={appName:!0,appPath:!0,region:!0,accountId:!0,imageTag:!0,services:!0,platform:!0,cacheRepoUri:!0,serviceName:!0,target:!0,dockerfilePath:!0,buildContext:!0,imageTags:!0,pushByDigest:!0,buildArgs:!0,buildSecrets:!0,targetsLambda:!0},t=Object.keys(e);export{t as ALL_DOCKER_BUILD_PARAM_KEYS};
|
|
@@ -33,8 +33,12 @@ export interface DockerBuildParams {
|
|
|
33
33
|
/**
|
|
34
34
|
* When provided, the provider constructs registry-mode buildx cache args
|
|
35
35
|
* (`--cache-from type=registry,ref=<cacheRepoUri>:<service>` and
|
|
36
|
-
* `--cache-to type=registry,ref=<cacheRepoUri>:<service>,mode
|
|
36
|
+
* `--cache-to type=registry,ref=<cacheRepoUri>:<service>,mode=<mode>`) so
|
|
37
37
|
* incremental builds reuse layers across CI runs and developer machines.
|
|
38
|
+
* The cache-to mode defaults to `max`; providers may resolve it per
|
|
39
|
+
* environment (the CLI provider honours `FJALL_BUILD_CACHE_MODE` via
|
|
40
|
+
* `resolveBuildCacheMode` at `@fjall/util`, omitting `--cache-to` entirely
|
|
41
|
+
* for `off` while keeping `--cache-from` — reading a cache is free).
|
|
38
42
|
* The orchestrator is responsible for ensuring the `<app>-cache` repo exists
|
|
39
43
|
* before invoking the provider; missing repos surface as a buildx push error.
|
|
40
44
|
*/
|
|
@@ -77,6 +81,16 @@ export interface DockerBuildParams {
|
|
|
77
81
|
* provider projects these 1:1 onto `BuildxBuildArgs.secrets`.
|
|
78
82
|
*/
|
|
79
83
|
buildSecrets?: readonly ResolvedBuildSecret[];
|
|
84
|
+
/**
|
|
85
|
+
* True when this build group contains at least one container-Lambda image.
|
|
86
|
+
* Lambda's `CreateFunction`/`UpdateFunctionCode` rejects an OCI image index
|
|
87
|
+
* with attestation manifests — the shape buildx produces by default when
|
|
88
|
+
* pushing (`--provenance=true`/`--sbom=true`) — so the provider must
|
|
89
|
+
* disable both for a Lambda-targeted build. ECS tolerates either shape, so
|
|
90
|
+
* a mixed ECS+Lambda group (sharing one Dockerfile/context/target) disables
|
|
91
|
+
* both for the whole group rather than trying to push two variants.
|
|
92
|
+
*/
|
|
93
|
+
targetsLambda?: boolean;
|
|
80
94
|
}
|
|
81
95
|
export interface DockerBuildResult {
|
|
82
96
|
imageUri: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"@aws-sdk/client-ssm": "^3.1038.0",
|
|
87
87
|
"@aws-sdk/client-sso-admin": "^3.1038.0",
|
|
88
88
|
"@aws-sdk/client-sts": "^3.1038.0",
|
|
89
|
-
"@fjall/generator": "^2.
|
|
90
|
-
"@fjall/util": "^2.
|
|
89
|
+
"@fjall/generator": "^2.33.0",
|
|
90
|
+
"@fjall/util": "^2.33.0",
|
|
91
91
|
"@smithy/node-http-handler": "^4.6.1",
|
|
92
92
|
"aws-cdk": "^2.1128.1",
|
|
93
93
|
"tsx": "^4.21.0",
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"typescript": "^6.0.3",
|
|
99
99
|
"vitest": "^4.1.5"
|
|
100
100
|
},
|
|
101
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "61c0d1917597c6d02e8e78b03420afdd194eaafc"
|
|
102
102
|
}
|