2021-04-12 22:43:19 +05:30
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Copyright ( c ) Red Hat , Inc . All rights reserved .
* Licensed under the MIT License . See LICENSE file in the project root for license information .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2021-02-01 23:24:50 +05:30
import * as core from "@actions/core" ;
import * as io from "@actions/io" ;
import * as path from "path" ;
2021-02-08 21:54:18 +05:30
import { Inputs , Outputs } from "./generated/inputs-outputs" ;
2021-02-01 23:24:50 +05:30
import { BuildahCli , BuildahConfigSettings } from "./buildah" ;
2021-09-15 00:33:38 +05:30
import {
2021-10-14 23:09:15 +01:00
getArch , getPlatform , getContainerfiles , getInputList , splitByNewline ,
2021-11-17 15:09:25 +05:30
isFullImageName , getFullImageName , removeIllegalCharacters ,
2021-09-15 00:33:38 +05:30
} from "./utils" ;
2020-11-06 18:42:45 +01:00
export async function run ( ) : Promise < void > {
2021-02-01 23:24:50 +05:30
if ( process . env . RUNNER_OS !== "Linux" ) {
throw new Error ( "buildah, and therefore this action, only works on Linux. Please use a Linux runner." ) ;
2020-11-06 18:42:45 +01:00
}
2020-11-19 09:19:57 +01:00
2020-11-06 18:42:45 +01:00
// get buildah cli
2021-02-01 23:24:50 +05:30
const buildahPath = await io . which ( "buildah" , true ) ;
2020-11-19 09:19:57 +01:00
const cli : BuildahCli = new BuildahCli ( buildahPath ) ;
2021-03-23 19:45:56 +05:30
// print buildah version
2021-04-09 20:02:13 +05:30
await cli . execute ( [ "version" ] , { group : true } ) ;
// Check if fuse-overlayfs exists and find the storage driver
await cli . setStorageOptsEnv ( ) ;
2021-03-23 19:45:56 +05:30
2021-02-24 21:43:55 +05:30
const DEFAULT_TAG = "latest" ;
2021-02-01 23:24:50 +05:30
const workspace = process . env . GITHUB_WORKSPACE || process . cwd ( ) ;
2021-09-15 00:33:38 +05:30
const containerFiles = getContainerfiles ( ) ;
2021-10-12 10:21:52 -07:00
const image = core . getInput ( Inputs . IMAGE ) ;
2021-02-24 21:43:55 +05:30
const tags = core . getInput ( Inputs . TAGS ) ;
2021-10-12 10:21:52 -07:00
const tagsList : string [ ] = tags . trim ( ) . split ( /\s+/ ) ;
2021-10-19 11:41:41 -07:00
const labels = core . getInput ( Inputs . LABELS ) ;
const labelsList : string [ ] = labels ? splitByNewline ( labels ) : [ ] ;
2021-02-24 21:43:55 +05:30
2022-05-09 17:19:27 +05:30
const normalizedTagsList : string [ ] = [ ] ;
let isNormalized = false ;
for ( const tag of tagsList ) {
normalizedTagsList . push ( tag . toLowerCase ( ) ) ;
if ( tag . toLowerCase ( ) !== tag ) {
isNormalized = true ;
}
}
const normalizedImage = image . toLowerCase ( ) ;
if ( isNormalized || image !== normalizedImage ) {
core . warning ( ` Reference to image and/or tag must be lowercase. `
+ ` Reference has been converted to be compliant with standard. ` ) ;
}
2021-02-24 21:43:55 +05:30
// info message if user doesn't provides any tag
2021-07-09 11:12:48 -04:00
if ( tagsList . length === 0 ) {
2021-02-24 21:43:55 +05:30
core . info ( ` Input " ${ Inputs . TAGS } " is not provided, using default tag " ${ DEFAULT_TAG } " ` ) ;
tagsList . push ( DEFAULT_TAG ) ;
}
2021-10-12 10:21:52 -07:00
// check if all tags provided are in `image:tag` format
2022-05-09 17:19:27 +05:30
const isFullImageNameTag = isFullImageName ( normalizedTagsList [ 0 ] ) ;
if ( normalizedTagsList . some ( ( tag ) = > isFullImageName ( tag ) !== isFullImageNameTag ) ) {
2021-10-14 15:39:58 -07:00
throw new Error ( ` Input " ${ Inputs . TAGS } " cannot have a mix of full name and non full name tags. Refer to https://github.com/redhat-actions/buildah-build#image-tag-inputs ` ) ;
2021-10-12 10:21:52 -07:00
}
2022-05-09 17:19:27 +05:30
if ( ! isFullImageNameTag && ! normalizedImage ) {
2021-10-14 15:39:58 -07:00
throw new Error ( ` Input " ${ Inputs . IMAGE } " must be provided when not using full image name tags. Refer to https://github.com/redhat-actions/buildah-build#image-tag-inputs ` ) ;
2021-10-12 10:21:52 -07:00
}
2022-05-09 17:19:27 +05:30
const newImage = getFullImageName ( normalizedImage , normalizedTagsList [ 0 ] ) ;
2021-02-08 21:54:18 +05:30
const useOCI = core . getInput ( Inputs . OCI ) === "true" ;
2021-07-09 11:12:48 -04:00
2021-11-17 15:09:25 +05:30
const archs = getArch ( ) ;
const platforms = getPlatform ( ) ;
2021-10-14 23:09:15 +01:00
2021-11-17 15:09:25 +05:30
if ( ( archs . length > 0 ) && ( platforms . length > 0 ) ) {
2021-10-14 23:09:15 +01:00
throw new Error ( "The --platform option may not be used in combination with the --arch option." ) ;
}
2020-11-26 13:00:17 -05:00
2021-11-18 20:03:42 +05:30
const builtImage = [ ] ;
2021-09-15 00:33:38 +05:30
if ( containerFiles . length !== 0 ) {
2021-11-18 20:03:42 +05:30
builtImage . push ( . . . await doBuildUsingContainerFiles ( cli , newImage , workspace , containerFiles , useOCI ,
archs , platforms , labelsList ) ) ;
2021-02-01 23:24:50 +05:30
}
else {
2021-11-17 15:09:25 +05:30
if ( platforms . length > 0 ) {
2021-10-18 18:41:01 +01:00
throw new Error ( "The --platform option is not supported for builds without containerfiles." ) ;
}
2021-11-18 20:03:42 +05:30
builtImage . push ( . . . await doBuildFromScratch ( cli , newImage , useOCI , archs , labelsList ) ) ;
2020-11-19 13:46:44 -05:00
}
2020-11-23 17:08:21 -05:00
2021-11-24 10:38:34 +05:30
if ( ( archs . length > 1 ) || ( platforms . length > 1 ) ) {
2022-05-09 17:19:27 +05:30
core . info ( ` Creating manifest with tag ${ normalizedTagsList . length !== 1 ? "s" : "" } `
+ ` " ${ normalizedTagsList . join ( ", " ) } " ` ) ;
2021-11-17 15:09:25 +05:30
const builtManifest = [ ] ;
2022-05-09 17:19:27 +05:30
for ( const tag of normalizedTagsList ) {
const manifestName = getFullImageName ( normalizedImage , tag ) ;
2021-11-17 15:09:25 +05:30
await cli . manifestCreate ( manifestName ) ;
builtManifest . push ( manifestName ) ;
for ( const arch of archs ) {
const tagSuffix = removeIllegalCharacters ( arch ) ;
await cli . manifestAdd ( manifestName , ` ${ newImage } - ${ tagSuffix } ` ) ;
}
for ( const platform of platforms ) {
const tagSuffix = removeIllegalCharacters ( platform ) ;
await cli . manifestAdd ( manifestName , ` ${ newImage } - ${ tagSuffix } ` ) ;
}
}
core . info ( ` ✅ Successfully built image ${ builtImage . length !== 1 ? "s" : "" } " ${ builtImage . join ( ", " ) } " `
+ ` and manifest ${ builtManifest . length !== 1 ? "s" : "" } " ${ builtManifest . join ( ", " ) } " ` ) ;
}
2022-05-09 17:19:27 +05:30
else if ( normalizedTagsList . length > 1 ) {
await cli . tag ( normalizedImage , normalizedTagsList ) ;
2021-02-01 23:24:50 +05:30
}
2022-05-09 17:19:27 +05:30
else if ( normalizedTagsList . length === 1 ) {
core . info ( ` ✅ Successfully built image " ${ getFullImageName ( normalizedImage , normalizedTagsList [ 0 ] ) } " ` ) ;
2021-11-17 15:09:25 +05:30
}
2022-05-09 17:19:27 +05:30
core . setOutput ( Outputs . IMAGE , normalizedImage ) ;
2021-02-08 21:54:18 +05:30
core . setOutput ( Outputs . TAGS , tags ) ;
2021-10-12 10:21:52 -07:00
core . setOutput ( Outputs . IMAGE_WITH_TAG , newImage ) ;
2020-11-19 09:19:57 +01:00
}
2021-09-15 00:33:38 +05:30
async function doBuildUsingContainerFiles (
2021-11-17 15:09:25 +05:30
cli : BuildahCli , newImage : string , workspace : string , containerFiles : string [ ] , useOCI : boolean , archs : string [ ] ,
platforms : string [ ] , labels : string [ ] ,
2021-11-18 20:03:42 +05:30
) : Promise < string [ ] > {
2021-09-15 00:33:38 +05:30
if ( containerFiles . length === 1 ) {
core . info ( ` Performing build from Containerfile ` ) ;
2020-11-23 17:08:21 -05:00
}
else {
2021-09-15 00:33:38 +05:30
core . info ( ` Performing build from ${ containerFiles . length } Containerfiles ` ) ;
2020-11-23 17:08:21 -05:00
}
2021-02-08 21:54:18 +05:30
const context = path . join ( workspace , core . getInput ( Inputs . CONTEXT ) ) ;
const buildArgs = getInputList ( Inputs . BUILD_ARGS ) ;
2021-09-15 00:33:38 +05:30
const containerFileAbsPaths = containerFiles . map ( ( file ) = > path . join ( workspace , file ) ) ;
2021-03-30 18:20:32 +05:30
const layers = core . getInput ( Inputs . LAYERS ) ;
2021-04-12 22:43:19 +05:30
const inputExtraArgsStr = core . getInput ( Inputs . EXTRA_ARGS ) ;
let buildahBudExtraArgs : string [ ] = [ ] ;
if ( inputExtraArgsStr ) {
// transform the array of lines into an array of arguments
// by splitting over lines, then over spaces, then trimming.
const lines = splitByNewline ( inputExtraArgsStr ) ;
buildahBudExtraArgs = lines . flatMap ( ( line ) = > line . split ( " " ) ) . map ( ( arg ) = > arg . trim ( ) ) ;
}
2021-11-18 20:03:42 +05:30
const builtImage = [ ] ;
2021-11-17 15:09:25 +05:30
// since multi arch image can not have same tag
// therefore, appending arch/platform in the tag
if ( archs . length > 0 || platforms . length > 0 ) {
for ( const arch of archs ) {
2021-11-24 10:38:34 +05:30
// handling it seperately as, there is no need of
// tagSuffix if only one image has to be built
let tagSuffix = "" ;
if ( archs . length > 1 ) {
tagSuffix = ` - ${ removeIllegalCharacters ( arch ) } ` ;
}
2021-11-17 15:09:25 +05:30
await cli . buildUsingDocker (
2021-11-24 10:38:34 +05:30
` ${ newImage } ${ tagSuffix } ` , context , containerFileAbsPaths , buildArgs ,
2021-11-17 15:09:25 +05:30
useOCI , labels , layers , buildahBudExtraArgs , arch , undefined
) ;
2021-11-24 10:38:34 +05:30
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 15:09:25 +05:30
}
for ( const platform of platforms ) {
2021-11-24 10:38:34 +05:30
let tagSuffix = "" ;
if ( platforms . length > 1 ) {
tagSuffix = ` - ${ removeIllegalCharacters ( platform ) } ` ;
}
2021-11-17 15:09:25 +05:30
await cli . buildUsingDocker (
2021-11-24 10:38:34 +05:30
` ${ newImage } ${ tagSuffix } ` , context , containerFileAbsPaths , buildArgs ,
2021-11-17 15:09:25 +05:30
useOCI , labels , layers , buildahBudExtraArgs , undefined , platform
) ;
2021-11-24 10:38:34 +05:30
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 15:09:25 +05:30
}
}
2021-11-24 10:38:34 +05:30
else if ( archs . length === 1 || platforms . length === 1 ) {
await cli . buildUsingDocker (
newImage , context , containerFileAbsPaths , buildArgs ,
useOCI , labels , layers , buildahBudExtraArgs , archs [ 0 ] , platforms [ 0 ]
) ;
builtImage . push ( newImage ) ;
}
2021-11-17 15:09:25 +05:30
else {
await cli . buildUsingDocker (
newImage , context , containerFileAbsPaths , buildArgs ,
useOCI , labels , layers , buildahBudExtraArgs
) ;
2021-11-18 20:03:42 +05:30
builtImage . push ( newImage ) ;
2021-11-17 15:09:25 +05:30
}
2021-11-18 20:03:42 +05:30
return builtImage ;
2020-11-19 09:19:57 +01:00
}
2021-02-01 23:24:50 +05:30
async function doBuildFromScratch (
2021-11-17 15:09:25 +05:30
cli : BuildahCli , newImage : string , useOCI : boolean , archs : string [ ] , labels : string [ ] ,
2021-11-18 20:03:42 +05:30
) : Promise < string [ ] > {
2021-02-01 23:24:50 +05:30
core . info ( ` Performing build from scratch ` ) ;
2020-11-06 18:42:45 +01:00
2021-02-08 21:54:18 +05:30
const baseImage = core . getInput ( Inputs . BASE_IMAGE , { required : true } ) ;
const content = getInputList ( Inputs . CONTENT ) ;
const entrypoint = getInputList ( Inputs . ENTRYPOINT ) ;
const port = core . getInput ( Inputs . PORT ) ;
const workingDir = core . getInput ( Inputs . WORKDIR ) ;
const envs = getInputList ( Inputs . ENVS ) ;
2020-11-19 13:46:44 -05:00
2020-11-13 12:38:29 +01:00
const container = await cli . from ( baseImage ) ;
2021-02-01 23:24:50 +05:30
const containerId = container . output . replace ( "\n" , "" ) ;
2020-11-06 18:42:45 +01:00
2021-11-18 20:03:42 +05:30
const builtImage = [ ] ;
2021-11-17 15:09:25 +05:30
if ( archs . length > 0 ) {
for ( const arch of archs ) {
2021-11-24 10:38:34 +05:30
let tagSuffix = "" ;
if ( archs . length > 1 ) {
tagSuffix = ` - ${ removeIllegalCharacters ( arch ) } ` ;
}
2021-11-17 15:09:25 +05:30
const newImageConfig : BuildahConfigSettings = {
entrypoint ,
port ,
workingdir : workingDir ,
envs ,
arch ,
labels ,
} ;
await cli . config ( containerId , newImageConfig ) ;
await cli . copy ( containerId , content ) ;
2021-11-24 10:38:34 +05:30
await cli . commit ( containerId , ` ${ newImage } ${ tagSuffix } ` , useOCI ) ;
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 15:09:25 +05:30
}
}
else {
const newImageConfig : BuildahConfigSettings = {
entrypoint ,
port ,
workingdir : workingDir ,
envs ,
labels ,
} ;
await cli . config ( containerId , newImageConfig ) ;
await cli . copy ( containerId , content ) ;
await cli . commit ( containerId , newImage , useOCI ) ;
2021-11-18 20:03:42 +05:30
builtImage . push ( newImage ) ;
2021-11-17 15:09:25 +05:30
}
2021-11-18 20:03:42 +05:30
return builtImage ;
2020-11-06 18:42:45 +01:00
}
2020-11-19 13:46:44 -05:00
run ( ) . catch ( core . setFailed ) ;