2021-04-12 17:13:19 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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 17:54:50 +00:00
import * as core from "@actions/core" ;
import * as io from "@actions/io" ;
import * as path from "path" ;
2021-02-08 16:24:18 +00:00
import { Inputs , Outputs } from "./generated/inputs-outputs" ;
2021-02-01 17:54:50 +00:00
import { BuildahCli , BuildahConfigSettings } from "./buildah" ;
2021-09-14 19:03:38 +00:00
import {
2021-10-14 22:09:15 +00:00
getArch , getPlatform , getContainerfiles , getInputList , splitByNewline ,
2021-11-17 09:39:25 +00:00
isFullImageName , getFullImageName , removeIllegalCharacters ,
2021-09-14 19:03:38 +00:00
} from "./utils" ;
2020-11-06 17:42:45 +00:00
export async function run ( ) : Promise < void > {
2021-02-01 17:54:50 +00:00
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 17:42:45 +00:00
}
2020-11-19 08:19:57 +00:00
2020-11-06 17:42:45 +00:00
// get buildah cli
2021-02-01 17:54:50 +00:00
const buildahPath = await io . which ( "buildah" , true ) ;
2020-11-19 08:19:57 +00:00
const cli : BuildahCli = new BuildahCli ( buildahPath ) ;
2021-03-23 14:15:56 +00:00
// print buildah version
2021-04-09 14:32:13 +00:00
await cli . execute ( [ "version" ] , { group : true } ) ;
// Check if fuse-overlayfs exists and find the storage driver
await cli . setStorageOptsEnv ( ) ;
2021-03-23 14:15:56 +00:00
2021-02-24 16:13:55 +00:00
const DEFAULT_TAG = "latest" ;
2021-02-01 17:54:50 +00:00
const workspace = process . env . GITHUB_WORKSPACE || process . cwd ( ) ;
2021-09-14 19:03:38 +00:00
const containerFiles = getContainerfiles ( ) ;
2021-10-12 17:21:52 +00:00
const image = core . getInput ( Inputs . IMAGE ) ;
2021-02-24 16:13:55 +00:00
const tags = core . getInput ( Inputs . TAGS ) ;
2021-10-12 17:21:52 +00:00
const tagsList : string [ ] = tags . trim ( ) . split ( /\s+/ ) ;
2021-10-19 18:41:41 +00:00
const labels = core . getInput ( Inputs . LABELS ) ;
const labelsList : string [ ] = labels ? splitByNewline ( labels ) : [ ] ;
2021-02-24 16:13:55 +00:00
// info message if user doesn't provides any tag
2021-07-09 15:12:48 +00:00
if ( tagsList . length === 0 ) {
2021-02-24 16:13:55 +00:00
core . info ( ` Input " ${ Inputs . TAGS } " is not provided, using default tag " ${ DEFAULT_TAG } " ` ) ;
tagsList . push ( DEFAULT_TAG ) ;
}
2021-10-12 17:21:52 +00:00
// check if all tags provided are in `image:tag` format
const isFullImageNameTag = isFullImageName ( tagsList [ 0 ] ) ;
if ( tagsList . some ( ( tag ) = > isFullImageName ( tag ) !== isFullImageNameTag ) ) {
2021-10-14 22:39:58 +00: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 17:21:52 +00:00
}
if ( ! isFullImageNameTag && ! image ) {
2021-10-14 22:39:58 +00: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 17:21:52 +00:00
}
const newImage = getFullImageName ( image , tagsList [ 0 ] ) ;
2021-02-08 16:24:18 +00:00
const useOCI = core . getInput ( Inputs . OCI ) === "true" ;
2021-07-09 15:12:48 +00:00
2021-11-17 09:39:25 +00:00
const archs = getArch ( ) ;
const platforms = getPlatform ( ) ;
2021-10-14 22:09:15 +00:00
2021-11-17 09:39:25 +00:00
if ( ( archs . length > 0 ) && ( platforms . length > 0 ) ) {
2021-10-14 22:09:15 +00:00
throw new Error ( "The --platform option may not be used in combination with the --arch option." ) ;
}
2020-11-26 18:00:17 +00:00
2021-11-18 14:33:42 +00:00
const builtImage = [ ] ;
2021-09-14 19:03:38 +00:00
if ( containerFiles . length !== 0 ) {
2021-11-18 14:33:42 +00:00
builtImage . push ( . . . await doBuildUsingContainerFiles ( cli , newImage , workspace , containerFiles , useOCI ,
archs , platforms , labelsList ) ) ;
2021-02-01 17:54:50 +00:00
}
else {
2021-11-17 09:39:25 +00:00
if ( platforms . length > 0 ) {
2021-10-18 17:41:01 +00:00
throw new Error ( "The --platform option is not supported for builds without containerfiles." ) ;
}
2021-11-18 14:33:42 +00:00
builtImage . push ( . . . await doBuildFromScratch ( cli , newImage , useOCI , archs , labelsList ) ) ;
2020-11-19 18:46:44 +00:00
}
2020-11-23 22:08:21 +00:00
2021-11-24 05:08:34 +00:00
if ( ( archs . length > 1 ) || ( platforms . length > 1 ) ) {
2021-11-17 09:39:25 +00:00
core . info ( ` Creating manifest with tag ${ tagsList . length !== 1 ? "s" : "" } " ${ tagsList . join ( ", " ) } " ` ) ;
const builtManifest = [ ] ;
for ( const tag of tagsList ) {
const manifestName = getFullImageName ( image , tag ) ;
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 ( ", " ) } " ` ) ;
}
else if ( tagsList . length > 1 ) {
2021-02-01 17:54:50 +00:00
await cli . tag ( image , tagsList ) ;
}
2021-11-17 09:39:25 +00:00
else if ( tagsList . length === 1 ) {
core . info ( ` ✅ Successfully built image " ${ getFullImageName ( image , tagsList [ 0 ] ) } " ` ) ;
}
2021-02-08 16:24:18 +00:00
core . setOutput ( Outputs . IMAGE , image ) ;
core . setOutput ( Outputs . TAGS , tags ) ;
2021-10-12 17:21:52 +00:00
core . setOutput ( Outputs . IMAGE_WITH_TAG , newImage ) ;
2020-11-19 08:19:57 +00:00
}
2021-09-14 19:03:38 +00:00
async function doBuildUsingContainerFiles (
2021-11-17 09:39:25 +00:00
cli : BuildahCli , newImage : string , workspace : string , containerFiles : string [ ] , useOCI : boolean , archs : string [ ] ,
platforms : string [ ] , labels : string [ ] ,
2021-11-18 14:33:42 +00:00
) : Promise < string [ ] > {
2021-09-14 19:03:38 +00:00
if ( containerFiles . length === 1 ) {
core . info ( ` Performing build from Containerfile ` ) ;
2020-11-23 22:08:21 +00:00
}
else {
2021-09-14 19:03:38 +00:00
core . info ( ` Performing build from ${ containerFiles . length } Containerfiles ` ) ;
2020-11-23 22:08:21 +00:00
}
2021-02-08 16:24:18 +00:00
const context = path . join ( workspace , core . getInput ( Inputs . CONTEXT ) ) ;
const buildArgs = getInputList ( Inputs . BUILD_ARGS ) ;
2021-09-14 19:03:38 +00:00
const containerFileAbsPaths = containerFiles . map ( ( file ) = > path . join ( workspace , file ) ) ;
2021-03-30 12:50:32 +00:00
const layers = core . getInput ( Inputs . LAYERS ) ;
2021-04-12 17:13:19 +00:00
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 14:33:42 +00:00
const builtImage = [ ] ;
2021-11-17 09:39:25 +00:00
// 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 05:08:34 +00:00
// 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 09:39:25 +00:00
await cli . buildUsingDocker (
2021-11-24 05:08:34 +00:00
` ${ newImage } ${ tagSuffix } ` , context , containerFileAbsPaths , buildArgs ,
2021-11-17 09:39:25 +00:00
useOCI , labels , layers , buildahBudExtraArgs , arch , undefined
) ;
2021-11-24 05:08:34 +00:00
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 09:39:25 +00:00
}
for ( const platform of platforms ) {
2021-11-24 05:08:34 +00:00
let tagSuffix = "" ;
if ( platforms . length > 1 ) {
tagSuffix = ` - ${ removeIllegalCharacters ( platform ) } ` ;
}
2021-11-17 09:39:25 +00:00
await cli . buildUsingDocker (
2021-11-24 05:08:34 +00:00
` ${ newImage } ${ tagSuffix } ` , context , containerFileAbsPaths , buildArgs ,
2021-11-17 09:39:25 +00:00
useOCI , labels , layers , buildahBudExtraArgs , undefined , platform
) ;
2021-11-24 05:08:34 +00:00
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 09:39:25 +00:00
}
}
2021-11-24 05:08:34 +00:00
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 09:39:25 +00:00
else {
await cli . buildUsingDocker (
newImage , context , containerFileAbsPaths , buildArgs ,
useOCI , labels , layers , buildahBudExtraArgs
) ;
2021-11-18 14:33:42 +00:00
builtImage . push ( newImage ) ;
2021-11-17 09:39:25 +00:00
}
2021-11-18 14:33:42 +00:00
return builtImage ;
2020-11-19 08:19:57 +00:00
}
2021-02-01 17:54:50 +00:00
async function doBuildFromScratch (
2021-11-17 09:39:25 +00:00
cli : BuildahCli , newImage : string , useOCI : boolean , archs : string [ ] , labels : string [ ] ,
2021-11-18 14:33:42 +00:00
) : Promise < string [ ] > {
2021-02-01 17:54:50 +00:00
core . info ( ` Performing build from scratch ` ) ;
2020-11-06 17:42:45 +00:00
2021-02-08 16:24:18 +00:00
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 18:46:44 +00:00
2020-11-13 11:38:29 +00:00
const container = await cli . from ( baseImage ) ;
2021-02-01 17:54:50 +00:00
const containerId = container . output . replace ( "\n" , "" ) ;
2020-11-06 17:42:45 +00:00
2021-11-18 14:33:42 +00:00
const builtImage = [ ] ;
2021-11-17 09:39:25 +00:00
if ( archs . length > 0 ) {
for ( const arch of archs ) {
2021-11-24 05:08:34 +00:00
let tagSuffix = "" ;
if ( archs . length > 1 ) {
tagSuffix = ` - ${ removeIllegalCharacters ( arch ) } ` ;
}
2021-11-17 09:39:25 +00:00
const newImageConfig : BuildahConfigSettings = {
entrypoint ,
port ,
workingdir : workingDir ,
envs ,
arch ,
labels ,
} ;
await cli . config ( containerId , newImageConfig ) ;
await cli . copy ( containerId , content ) ;
2021-11-24 05:08:34 +00:00
await cli . commit ( containerId , ` ${ newImage } ${ tagSuffix } ` , useOCI ) ;
builtImage . push ( ` ${ newImage } ${ tagSuffix } ` ) ;
2021-11-17 09:39:25 +00:00
}
}
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 14:33:42 +00:00
builtImage . push ( newImage ) ;
2021-11-17 09:39:25 +00:00
}
2021-11-18 14:33:42 +00:00
return builtImage ;
2020-11-06 17:42:45 +00:00
}
2020-11-19 18:46:44 +00:00
run ( ) . catch ( core . setFailed ) ;