import { Image as ImageInterface , ImageInspectInfo , ImageInfo , } from 'dockerode' ; import { clean , coerce , valid , rcompare , major , minor , patch , prerelease , } from 'semver' ; import { ImageClientInterface , ConfigInterface , RegistryInterface , PullAuthInterface , } from './interfaces' ; import { Registry } from './registry' ; import chalk from 'chalk' ; import { logger } from './logger' ; const LATEST_TAG = 'latest' ; export class Image implements ImageClientInterface { client : any ; constructor ( client ) { this . client = client ; } return { ( Promise < ImageInfo [ ] | undefined > ) } memberof async listAll ( ) { try { const imageList = await this . client . listImages ( ) ; return imageList ; } catch ( err ) { console . log ( chalk . red ( `List images error: ${ err . message }` ) ) ; logger . error ( `List images error: ${ err . message }` ) ; return ; } } param { string } name return { ( Promise < ImageInspectInfo | undefined > ) } memberof async inspect ( name ) { try { const image = await this . client . getImage ( name ) ; const imageInfo = await image . inspect ( ) ; return imageInfo ; } catch ( err ) { console . log ( chalk . red ( `Inspect images error: ${ err . message }` ) ) ; logger . error ( `Inspect images error: ${ err . message }` ) ; return ; } } param { ImageInspectInfo } image param { ( PullAuthInterface | undefined ) } authconfig return { ( Promise < ImageInspectInfo | undefined > ) } memberof async pullLatestImage ( imageName , imageTag , config ) { let pullAuthCredentials ; if ( config . repoUser && config . repoPass ) { pullAuthCredentials = { username : config . repoUser , password : config . repoPass , } ; } try { const latestName = `${ imageName }:${ imageTag }` ; if ( ! imageName || ! imageTag ) { throw new Error ( `Invalid image name or tag. Name: ${ imageName } Tag: ${ imageTag }` ) ; } const authCredentials = pullAuthCredentials ? { pullAuthCredentials } : { } ; return new Promise ( ( resolve , reject ) => { this . client . pull ( latestName , authCredentials , ( err , stream ) => { this . client . modem . followProgress ( stream , async ( err , output ) => { if ( err ) { reject ( err ) ; } try { const inspectObject = await this . inspect ( latestName ) ; resolve ( inspectObject ) ; } catch ( e ) { reject ( e ) ; } } ) ; } ) ; } ) ; } catch ( err ) { if ( err instanceof TypeError ) { console . log ( `Container already running for the latest image.\n` ) ; logger . debug ( `Container already running for the latest image: ${ err . message }` ) ; } else { console . log ( chalk . red ( `Pull latest image error: ${ err }` ) , `\n` ) ; logger . error ( `Pull latest images error: ${ err . message }` ) ; } return ; } } param { ImageInspectInfo } image return { Promise < void > } memberof async remove ( image ) { try { const imageName = image . RepoTags [ image . RepoTags . length - 1 ] ; const imageObject = await this . client . getImage ( imageName ) ; await imageObject . remove ( ) ; console . log ( chalk . cyan ( `Removed image ${ imageName }` ) ) ; logger . debug ( `Removed image ${ imageName }` ) ; } catch ( err ) { console . log ( chalk . red ( `Remove image error: ${ err }` ) ) ; logger . error ( `Remove image error: ${ err . message }` ) ; } } static param { string } oldSha param { string } newSha return { boolean } memberof static shouldUpdateCurrentImage ( oldSha , newSha ) { return ! ( newSha === oldSha ) ; } param { string } name return { string | undefined } getUntaggedImageName ( name ) { if ( ! name ) { return ; } return name . split ( ':' ) [ 0 ] ; } param { string } name return { string | undefined } getImageTag ( name ) { if ( ! name ) { return ; } return name . split ( ':' ) [ 1 ] ; } param { ImageInspectInfo } image param { string | undefined } currentTag param config return { Promise < string | undefined > } static async fetchUpdatedImageTag ( image , currentTag , config ) { if ( ! config . semverUpdate ) { return Promise . resolve ( LATEST_TAG ) ; } const repoName = Image . getRepositoryName ( image ) ; if ( ! repoName ) return ; const registryClient = new Registry ( config ) ; const repoTags = await registryClient . getRepositoryTags ( repoName ) ; if ( ! repoTags || ! repoTags . length ) return ; let comparator ; if ( config . allowPrereleaseUpdates ) { comparator = ( tag ) => valid ( clean ( tag , { loose : true } ) , { loose : true } ) ; } else { comparator = ( tag ) => valid ( coerce ( clean ( tag ) ) ) && ! prerelease ( clean ( tag ) ) ; } const validTags = repoTags . filter ( comparator ) ; console . log ( config . allowPrereleaseUpdates , validTags ) ; if ( ! validTags . length ) { console . log ( chalk . yellow ( `No valid semver tags found for ${ repoName }. Trying with 'latest' tag instead.` ) ) ; logger . info ( `No valid semver tags found for ${ repoName }. Trying with 'latest' tag instead.` ) ; return Promise . resolve ( LATEST_TAG ) ; } if ( config . allowMajorUpdates ) { return Image . getMajorUpdateTag ( currentTag , validTags , config . allowPrereleaseUpdates ) ; } if ( config . allowPatchOnly ) { return Image . getPatchUpdateTag ( currentTag , validTags ) ; } return Image . getMinorAndPatchUpdateTag ( currentTag , validTags , config . allowPrereleaseUpdates ) ; } private static getRepositoryName ( image ) { if ( ! image ) return undefined ; const repoDigest = image . RepoDigests [ 0 ] ; return repoDigest . split ( '@sha256:' ) [ 0 ] ; } private static getMajorUpdateTag ( currentTag , tags , preReleasesAllowed = false ) { if ( ! valid ( currentTag ) ) { return Image . findRecentUpdate ( tags ) ; } const tagsWithHigherMajorVersion = tags . filter ( ( tag ) => major ( tag ) > major ( currentTag ) ) ; if ( ! tagsWithHigherMajorVersion . length ) { return Image . getMinorAndPatchUpdateTag ( currentTag , tags , preReleasesAllowed ) ; } const comparator = function ( tag1 , tag2 ) : number { if ( major ( tag1 ) < major ( tag2 ) ) return 1 ; if ( major ( tag1 ) > major ( tag2 ) ) return - 1 ; if ( major ( tag1 ) === major ( tag1 ) ) { if ( minor ( tag1 ) < minor ( tag2 ) ) return 1 ; if ( minor ( tag1 ) > minor ( tag2 ) ) return - 1 ; } if ( major ( tag1 ) === major ( tag2 ) && minor ( tag1 ) === minor ( tag2 ) ) { if ( patch ( tag1 ) < patch ( tag2 ) ) return 1 ; if ( patch ( tag1 ) > patch ( tag2 ) ) return - 1 ; } if ( major ( tag1 ) === major ( tag2 ) && minor ( tag1 ) === minor ( tag2 ) && patch ( tag1 ) === patch ( tag2 ) ) { if ( ! preReleasesAllowed ) { return 0 ; } if ( prerelease ( tag1 ) < prerelease ( tag2 ) ) return 1 ; if ( prerelease ( tag1 ) > prerelease ( tag2 ) ) return - 1 ; } return 0 ; } ; tagsWithHigherMajorVersion . sort ( comparator ) ; return tagsWithHigherMajorVersion [ 0 ] ; } private static getPatchUpdateTag ( currentTag , tags ) { if ( ! valid ( currentTag ) ) { return Image . findRecentUpdate ( tags ) ; } const tagsWithSameMajorMinorVersion = tags . filter ( ( tag ) => major ( tag ) === major ( currentTag ) && minor ( tag ) === minor ( currentTag ) ) ; const comparator = function ( tag1 , tag2 ) : number { if ( patch ( tag1 ) < patch ( tag2 ) ) return 1 ; if ( patch ( tag1 ) > patch ( tag2 ) ) return - 1 ; return 0 ; } ; tagsWithSameMajorMinorVersion . sort ( comparator ) ; return tagsWithSameMajorMinorVersion [ 0 ] ; } private static getMinorAndPatchUpdateTag ( currentTag , tags , preReleasesAllowed = false ) { if ( ! valid ( currentTag ) ) { return Image . findRecentUpdate ( tags ) ; } const tagsWithSameMajorVersion = tags . filter ( ( tag ) => major ( tag ) === major ( currentTag ) ) ; const comparator = function ( tag1 , tag2 ) : number { if ( minor ( tag1 ) < minor ( tag2 ) || ( minor ( tag1 ) === minor ( tag2 ) && patch ( tag1 ) < patch ( tag2 ) ) ) { return 1 ; } if ( minor ( tag1 ) > minor ( tag2 ) || ( minor ( tag1 ) === minor ( tag2 ) && patch ( tag1 ) > patch ( tag2 ) ) ) { return - 1 ; } if ( preReleasesAllowed ) { if ( minor ( tag1 ) === minor ( tag2 ) && patch ( tag1 ) === patch ( tag2 ) ) { if ( prerelease ( tag1 ) < prerelease ( tag2 ) ) return 1 ; if ( prerelease ( tag1 ) > prerelease ( tag2 ) ) return - 1 ; } } return 0 ; } ; tagsWithSameMajorVersion . sort ( comparator ) ; return tagsWithSameMajorVersion [ 0 ] ; } private static findRecentUpdate ( tags ) { if ( ! tags . length ) return LATEST_TAG ; const sortedTags = tags . sort ( rcompare ) ; return sortedTags [ 0 ] ; } }