@@ -18,38 +18,71 @@ import { PleaseHold } from './util/please-hold';
18
18
*
19
19
* As a workaround, we calculate our own digest over parts of the manifest that
20
20
* are unlikely to change, and tag based on that.
21
+ *
22
+ * When running in CI, we pull the latest image first and use it as cache for
23
+ * the build. Generally pulling will be faster than building, especially for
24
+ * Dockerfiles with lots of OS/code packages installation or changes only in
25
+ * the bottom layers. When running locally chances are that we already have
26
+ * layers cache available.
27
+ *
28
+ * CI is detected by the presence of the `CI` environment variable or
29
+ * the `--ci` command line option.
21
30
*/
22
- export async function prepareContainerAsset ( asset : ContainerImageAssetMetadataEntry , toolkitInfo : ToolkitInfo ) : Promise < CloudFormation . Parameter [ ] > {
31
+ export async function prepareContainerAsset ( asset : ContainerImageAssetMetadataEntry ,
32
+ toolkitInfo : ToolkitInfo ,
33
+ ci ?: boolean ) : Promise < CloudFormation . Parameter [ ] > {
23
34
debug ( ' 👑 Preparing Docker image asset:' , asset . path ) ;
24
35
25
36
const buildHold = new PleaseHold ( ` ⌛ Building Docker image for ${ asset . path } ; this may take a while.` ) ;
26
37
try {
38
+ const ecr = await toolkitInfo . prepareEcrRepository ( asset . id ) ;
39
+ const latest = `${ ecr . repositoryUri } :latest` ;
40
+
41
+ let loggedIn = false ;
42
+
43
+ // In CI we try to pull latest first
44
+ if ( ci ) {
45
+ await dockerLogin ( toolkitInfo ) ;
46
+ loggedIn = true ;
47
+
48
+ try {
49
+ await shell ( [ 'docker' , 'pull' , latest ] ) ;
50
+ } catch ( e ) {
51
+ debug ( 'Failed to pull latest image from ECR repository' ) ;
52
+ }
53
+ }
54
+
27
55
buildHold . start ( ) ;
28
56
29
- const command = [ 'docker' ,
57
+ const baseCommand = [ 'docker' ,
30
58
'build' ,
31
59
'--quiet' ,
32
60
asset . path ] ;
61
+ const command = ci
62
+ ? [ ...baseCommand , '--cache-from' , latest ] // This does not fail if latest is not available
63
+ : baseCommand ;
33
64
const imageId = ( await shell ( command , { quiet : true } ) ) . trim ( ) ;
65
+
34
66
buildHold . stop ( ) ;
35
67
36
68
const tag = await calculateImageFingerprint ( imageId ) ;
37
69
38
- debug ( ` ⌛ Image has tag ${ tag } , preparing ECR repository` ) ;
39
- const ecr = await toolkitInfo . prepareEcrRepository ( asset . id , tag ) ;
70
+ debug ( ` ⌛ Image has tag ${ tag } , checking ECR repository` ) ;
71
+ const imageExists = await toolkitInfo . checkEcrImage ( ecr . repositoryName , tag ) ;
40
72
41
- if ( ecr . alreadyExists ) {
73
+ if ( imageExists ) {
42
74
debug ( ' 👑 Image already uploaded.' ) ;
43
75
} else {
44
76
// Login and push
45
77
debug ( ` ⌛ Image needs to be uploaded first.` ) ;
46
78
47
- await shell ( [ 'docker' , 'login' ,
48
- '--username' , ecr . username ,
49
- '--password' , ecr . password ,
50
- ecr . endpoint ] ) ;
79
+ if ( ! loggedIn ) { // We could be already logged in if in CI
80
+ await dockerLogin ( toolkitInfo ) ;
81
+ loggedIn = true ;
82
+ }
51
83
52
84
const qualifiedImageName = `${ ecr . repositoryUri } :${ tag } ` ;
85
+
53
86
await shell ( [ 'docker' , 'tag' , imageId , qualifiedImageName ] ) ;
54
87
55
88
// There's no way to make this quiet, so we can't use a PleaseHold. Print a header message.
@@ -58,6 +91,14 @@ export async function prepareContainerAsset(asset: ContainerImageAssetMetadataEn
58
91
debug ( ` 👑 Docker image for ${ asset . path } pushed.` ) ;
59
92
}
60
93
94
+ if ( ! loggedIn ) { // We could be already logged in if in CI or if image did not exist
95
+ await dockerLogin ( toolkitInfo ) ;
96
+ }
97
+
98
+ // Always tag and push latest
99
+ await shell ( [ 'docker' , 'tag' , imageId , latest ] ) ;
100
+ await shell ( [ 'docker' , 'push' , latest ] ) ;
101
+
61
102
return [
62
103
{ ParameterKey : asset . imageNameParameter , ParameterValue : `${ ecr . repositoryName } :${ tag } ` } ,
63
104
] ;
@@ -72,6 +113,17 @@ export async function prepareContainerAsset(asset: ContainerImageAssetMetadataEn
72
113
}
73
114
}
74
115
116
+ /**
117
+ * Get credentials from ECR and run docker login
118
+ */
119
+ async function dockerLogin ( toolkitInfo : ToolkitInfo ) {
120
+ const credentials = await toolkitInfo . getEcrCredentials ( ) ;
121
+ await shell ( [ 'docker' , 'login' ,
122
+ '--username' , credentials . username ,
123
+ '--password' , credentials . password ,
124
+ credentials . endpoint ] ) ;
125
+ }
126
+
75
127
/**
76
128
* Calculate image fingerprint.
77
129
*
0 commit comments