1
- import * as fs from 'fs' ;
2
- import * as path from 'path' ;
3
- import AdmZip from 'adm-zip' ;
4
- import extract from 'extract-zip' ;
5
- import { execSync } from 'child_process' ;
6
- import { Readable } from 'stream' ;
7
- import { S3Client , GetObjectCommand , PutObjectCommand } from '@aws-sdk/client-s3' ;
1
+ import { BatchGetBuildsCommand , CodeBuildClient , StartBuildCommand } from '@aws-sdk/client-codebuild' ;
8
2
import type { ResourceProperties } from '../../src/types' ;
9
3
10
- const s3 = new S3Client ( { } ) ;
4
+ const cb = new CodeBuildClient ( { } ) ;
11
5
12
6
type Event = {
13
7
RequestType : 'Create' | 'Update' | 'Delete' ;
@@ -20,53 +14,98 @@ type Event = {
20
14
} ;
21
15
22
16
export const handler = async ( event : Event , context : any ) => {
23
- let rootDir = '' ;
24
17
console . log ( JSON . stringify ( event ) ) ;
25
18
26
19
try {
27
20
if ( event . RequestType == 'Create' || event . RequestType == 'Update' ) {
28
21
const props = event . ResourceProperties ;
29
- rootDir = fs . mkdtempSync ( '/tmp/extract' ) ;
30
22
31
- // set npm cache directory under /tmp since other directories are read-only in Lambda env
32
- process . env . NPM_CONFIG_CACHE = '/tmp/.npm' ;
23
+ // start code build project
24
+ const build = await cb . send (
25
+ new StartBuildCommand ( {
26
+ projectName : props . codeBuildProjectName ,
27
+ environmentVariablesOverride : [
28
+ {
29
+ name : 'input' ,
30
+ value : JSON . stringify (
31
+ props . sources . map ( ( source ) => ( {
32
+ assetUrl : `s3://${ source . sourceBucketName } /${ source . sourceObjectKey } ` ,
33
+ extractPath : source . extractPath ,
34
+ commands : ( source . commands ?? [ ] ) . join ( ' && ' ) ,
35
+ } ) )
36
+ ) ,
37
+ } ,
38
+ {
39
+ name : 'buildCommands' ,
40
+ value : props . buildCommands . join ( ' && ' ) ,
41
+ } ,
42
+ {
43
+ name : 'destinationBucketName' ,
44
+ value : props . destinationBucketName ,
45
+ } ,
46
+ {
47
+ name : 'destinationObjectKey' ,
48
+ value : props . destinationObjectKey ,
49
+ } ,
50
+ {
51
+ name : 'workingDirectory' ,
52
+ value : props . workingDirectory ,
53
+ } ,
54
+ {
55
+ name : 'outputSourceDirectory' ,
56
+ value : props . outputSourceDirectory ,
57
+ } ,
58
+ {
59
+ name : 'projectName' ,
60
+ value : props . codeBuildProjectName ,
61
+ } ,
62
+ {
63
+ name : 'responseURL' ,
64
+ value : event . ResponseURL ,
65
+ } ,
66
+ {
67
+ name : 'stackId' ,
68
+ value : event . StackId ,
69
+ } ,
70
+ {
71
+ name : 'requestId' ,
72
+ value : event . RequestId ,
73
+ } ,
74
+ {
75
+ name : 'logicalResourceId' ,
76
+ value : event . LogicalResourceId ,
77
+ } ,
78
+ ...Object . entries ( props . environment ?? { } ) . map ( ( [ name , value ] ) => ( {
79
+ name,
80
+ value,
81
+ } ) ) ,
82
+ ] ,
83
+ } )
84
+ ) ;
33
85
34
- // inject environment variables from resource properties
35
- Object . entries ( props . environment ?? { } ) . forEach ( ( [ key , value ] ) => ( process . env [ key ] = value ) ) ;
86
+ // Sometimes CodeBuild build fails before running buildspec, without calling the CFn callback.
87
+ // We can poll the status of a build for a few minutes and sendStatus if such errors are detected.
88
+ // if (build.build?.id == null) {
89
+ // throw new Error('build id is null');
90
+ // }
36
91
37
- // Download and extract each asset, and execute commands if any specified.
38
- const promises = props . sources . map ( async ( p ) => {
39
- const dir = await extractZip ( rootDir , p . directoryName , p . sourceObjectKey , p . sourceBucketName ) ;
40
- if ( p . commands != null ) {
41
- execSync ( p . commands . join ( ' && ' ) , { cwd : dir , stdio : 'inherit' } ) ;
42
- }
43
- } ) ;
92
+ // for (let i=0; i< 20; i++) {
93
+ // const res = await cb.send(new BatchGetBuildsCommand({ ids: [build.build.id] }));
94
+ // const status = res.builds?.[0].buildStatus;
95
+ // if (status == null) {
96
+ // throw new Error('build status is null');
97
+ // }
44
98
45
- await Promise . all ( promises ) ;
46
-
47
- console . log ( JSON . stringify ( process . env ) ) ;
48
- const wd = path . join ( rootDir , props . workingDirectory ) ;
49
- execSync ( props . buildCommands . join ( ' && ' ) , {
50
- cwd : wd ,
51
- stdio : 'inherit' ,
52
- } ) ;
53
-
54
- // zip the artifact directory and upload it to a S3 bucket.
55
- const srcPath = path . join ( rootDir , props . outputSourceDirectory ) ;
56
- await uploadDistDirectory ( srcPath , props . destinationBucketName , props . destinationObjectKey ) ;
99
+ // await new Promise((resolve) => setTimeout(resolve, 5000));
100
+ // }
57
101
} else {
58
- // how do we process 'Delete' event?
102
+ // Do nothing on a DELETE event.
103
+ await sendStatus ( 'SUCCESS' , event , context ) ;
59
104
}
60
- await sendStatus ( 'SUCCESS' , event , context ) ;
61
105
} catch ( e ) {
62
106
console . log ( e ) ;
63
107
const err = e as Error ;
64
108
await sendStatus ( 'FAILED' , event , context , err . message ) ;
65
- } finally {
66
- if ( rootDir != '' ) {
67
- // remove the working directory to prevent storage leakage
68
- fs . rmSync ( rootDir , { recursive : true , force : true } ) ;
69
- }
70
109
}
71
110
} ;
72
111
@@ -91,37 +130,3 @@ const sendStatus = async (status: 'SUCCESS' | 'FAILED', event: Event, context: a
91
130
} ,
92
131
} ) ;
93
132
} ;
94
-
95
- const uploadDistDirectory = async ( srcPath : string , dstBucket : string , dstKey : string ) => {
96
- const zip = new AdmZip ( ) ;
97
- zip . addLocalFolder ( srcPath ) ;
98
- await s3 . send (
99
- new PutObjectCommand ( {
100
- Body : zip . toBuffer ( ) ,
101
- Bucket : dstBucket ,
102
- Key : dstKey ,
103
- } )
104
- ) ;
105
- } ;
106
-
107
- const extractZip = async ( rootDir : string , dirName : string , key : string , bucket : string ) => {
108
- const dir = path . join ( rootDir , dirName ) ;
109
- fs . mkdirSync ( dir , { recursive : true } ) ;
110
- const zipPath = path . join ( dir , 'temp.zip' ) ;
111
- await downloadS3File ( key , bucket , zipPath ) ;
112
- await extract ( zipPath , { dir } ) ;
113
- console . log ( `extracted to ${ dir } ` ) ;
114
- return dir ;
115
- } ;
116
-
117
- const downloadS3File = async ( key : string , bucket : string , dst : string ) => {
118
- const data = await s3 . send ( new GetObjectCommand ( { Bucket : bucket , Key : key } ) ) ;
119
- await new Promise ( ( resolve , reject ) => {
120
- if ( data . Body instanceof Readable ) {
121
- data . Body
122
- . pipe ( fs . createWriteStream ( dst ) )
123
- . on ( 'error' , ( err ) => reject ( err ) )
124
- . on ( 'close' , ( ) => resolve ( 1 ) ) ;
125
- }
126
- } ) ;
127
- } ;
0 commit comments