@@ -20,7 +20,10 @@ import path from 'path';
2020import * as core from '@actions/core' ;
2121import * as io from '@actions/io' ;
2222
23+ import { Context } from '../context' ;
24+ import { Cache } from '../cache' ;
2325import { Exec } from '../exec' ;
26+ import { Util } from '../util' ;
2427
2528import { ConfigFile } from '../types/docker' ;
2629
@@ -73,4 +76,89 @@ export class Docker {
7376 public static async printInfo ( ) : Promise < void > {
7477 await Exec . exec ( 'docker' , [ 'info' ] ) ;
7578 }
79+
80+ public static parseRepoTag ( image : string ) : { repository : string ; tag : string } {
81+ let sepPos : number ;
82+ const digestPos = image . indexOf ( '@' ) ;
83+ const colonPos = image . lastIndexOf ( ':' ) ;
84+ if ( digestPos >= 0 ) {
85+ // priority on digest
86+ sepPos = digestPos ;
87+ } else if ( colonPos >= 0 ) {
88+ sepPos = colonPos ;
89+ } else {
90+ return {
91+ repository : image ,
92+ tag : 'latest'
93+ } ;
94+ }
95+ const tag = image . slice ( sepPos + 1 ) ;
96+ if ( tag . indexOf ( '/' ) === - 1 ) {
97+ return {
98+ repository : image . slice ( 0 , sepPos ) ,
99+ tag : tag
100+ } ;
101+ }
102+ return {
103+ repository : image ,
104+ tag : 'latest'
105+ } ;
106+ }
107+
108+ public static async pull ( image : string , cache ?: boolean ) : Promise < void > {
109+ const parsedImage = Docker . parseRepoTag ( image ) ;
110+ const repoSanitized = parsedImage . repository . replace ( / [ ^ a - z A - Z 0 - 9 . ] + / g, '--' ) ;
111+ const tagSanitized = parsedImage . tag . replace ( / [ ^ a - z A - Z 0 - 9 . ] + / g, '--' ) ;
112+
113+ const imageCache = new Cache ( {
114+ htcName : repoSanitized ,
115+ htcVersion : tagSanitized ,
116+ baseCacheDir : path . join ( Docker . configDir , '.cache' , 'images' , repoSanitized ) ,
117+ cacheFile : 'image.tar'
118+ } ) ;
119+
120+ let cacheFoundPath : string | undefined ;
121+ if ( cache ) {
122+ cacheFoundPath = await imageCache . find ( ) ;
123+ if ( cacheFoundPath ) {
124+ core . info ( `Image found from cache in ${ cacheFoundPath } ` ) ;
125+ await Exec . getExecOutput ( `docker` , [ 'load' , '-i' , cacheFoundPath ] , {
126+ ignoreReturnCode : true
127+ } ) . then ( res => {
128+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
129+ core . warning ( `Failed to load image from cache: ${ res . stderr . match ( / ( .* ) \s * $ / ) ?. [ 0 ] ?. trim ( ) ?? 'unknown error' } ` ) ;
130+ }
131+ } ) ;
132+ }
133+ }
134+
135+ let pulled = true ;
136+ await Exec . getExecOutput ( `docker` , [ 'pull' , image ] , {
137+ ignoreReturnCode : true
138+ } ) . then ( res => {
139+ pulled = false ;
140+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
141+ const err = res . stderr . match ( / ( .* ) \s * $ / ) ?. [ 0 ] ?. trim ( ) ?? 'unknown error' ;
142+ if ( cacheFoundPath ) {
143+ core . warning ( `Failed to pull image, using one from cache: ${ err } ` ) ;
144+ } else {
145+ throw new Error ( err ) ;
146+ }
147+ }
148+ } ) ;
149+
150+ if ( cache && pulled ) {
151+ const imageTarPath = path . join ( Context . tmpDir ( ) , `${ Util . hash ( image ) } .tar` ) ;
152+ await Exec . getExecOutput ( `docker` , [ 'save' , '-o' , imageTarPath , image ] , {
153+ ignoreReturnCode : true
154+ } ) . then ( async res => {
155+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
156+ core . warning ( `Failed to save image: ${ res . stderr . match ( / ( .* ) \s * $ / ) ?. [ 0 ] ?. trim ( ) ?? 'unknown error' } ` ) ;
157+ } else {
158+ const cachePath = await imageCache . save ( imageTarPath ) ;
159+ core . info ( `Image cached to ${ cachePath } ` ) ;
160+ }
161+ } ) ;
162+ }
163+ }
76164}
0 commit comments