2021-01-19 20:54:49 +05:30
|
|
|
import * as core from "@actions/core";
|
|
|
|
import * as exec from "@actions/exec";
|
|
|
|
import * as io from "@actions/io";
|
2021-01-07 18:49:38 -05:00
|
|
|
import * as fs from "fs";
|
2020-11-26 14:01:32 -05:00
|
|
|
import * as path from "path";
|
2020-11-07 13:04:04 +01:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
interface ExecResult {
|
|
|
|
exitCode: number;
|
|
|
|
stdout: string;
|
|
|
|
stderr: string;
|
|
|
|
}
|
2020-11-07 13:04:04 +01:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
let podmanPath: string | undefined;
|
2020-11-13 15:12:47 +01:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
// boolean value to check if pushed image is from Docker image storage
|
|
|
|
let isImageFromDocker = false;
|
|
|
|
let imageToPush: string;
|
|
|
|
|
|
|
|
async function getPodmanPath(): Promise<string> {
|
|
|
|
if (podmanPath == null) {
|
|
|
|
podmanPath = await io.which("podman", true);
|
2020-11-23 18:11:35 -05:00
|
|
|
}
|
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
return podmanPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
// base URL that gets appended if image is pulled from the Docker imaege storage
|
|
|
|
const dockerBaseUrl = "docker.io/library";
|
|
|
|
|
|
|
|
async function run(): Promise<void> {
|
|
|
|
const imageInput = core.getInput("image", { required: true });
|
|
|
|
const tag = core.getInput("tag") || "latest";
|
|
|
|
const registry = core.getInput("registry", { required: true });
|
|
|
|
const username = core.getInput("username", { required: true });
|
|
|
|
const password = core.getInput("password", { required: true });
|
|
|
|
const tlsVerify = core.getInput("tls-verify");
|
|
|
|
const digestFileInput = core.getInput("digestfile");
|
2020-11-26 14:01:32 -05:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
imageToPush = `${imageInput}:${tag}`;
|
2020-11-26 14:01:32 -05:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
// check if image exist in Podman image storage
|
|
|
|
const isPresentInPodman: boolean = await checkImageInPodman();
|
2020-11-26 14:01:32 -05:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
// check if image exist in Docker image storage and if exist pull the image to Podman
|
|
|
|
const isPresentInDocker: boolean = await pullImageFromDocker();
|
|
|
|
|
|
|
|
// failing if image is not found in Docker as well as Podman
|
|
|
|
if (!isPresentInDocker && !isPresentInPodman) {
|
|
|
|
throw new Error(`${imageToPush} not found in Podman local storage, or Docker local storage.`);
|
2020-11-13 15:12:47 +01:00
|
|
|
}
|
2020-11-07 13:04:04 +01:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
if (isPresentInPodman && isPresentInDocker) {
|
|
|
|
const isPodmanImageLatest = await isPodmanLocalImageLatest();
|
|
|
|
if (!isPodmanImageLatest) {
|
|
|
|
core.warning(`The version of ${imageToPush} in the Docker image storage is more recent `
|
|
|
|
+ `than the version in the Podman image storage. The image from the Docker image storage `
|
|
|
|
+ `will be pushed.`);
|
|
|
|
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
|
|
|
|
isImageFromDocker = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
core.warning(`The version of ${imageToPush} in the Podman image storage is more recent `
|
|
|
|
+ `than the version in the Docker image storage. The image from the Podman image `
|
|
|
|
+ `storage will be pushed.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (isPresentInDocker) {
|
|
|
|
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
|
|
|
|
core.info(`${imageToPush} was found in the Docker image storage, but not in the Podman `
|
|
|
|
+ `image storage. The image will be pulled into Podman image storage, pushed, and then `
|
|
|
|
+ `removed from the Podman image storage.`);
|
|
|
|
isImageFromDocker = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
|
|
|
|
if (username) {
|
|
|
|
pushMsg += ` as ${username}`;
|
|
|
|
}
|
|
|
|
core.info(pushMsg);
|
|
|
|
|
|
|
|
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
|
|
|
|
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
|
2020-11-23 18:11:35 -05:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
const creds = `${username}:${password}`;
|
2020-11-26 14:01:32 -05:00
|
|
|
|
2021-01-08 09:57:51 -05:00
|
|
|
let digestFile = digestFileInput;
|
|
|
|
if (!digestFile) {
|
2021-01-19 20:54:49 +05:30
|
|
|
digestFile = `${imageToPush.replace(
|
|
|
|
/[/\\/?%*:|"<>]/g,
|
|
|
|
"-",
|
|
|
|
)}_digest.txt`;
|
2021-01-08 09:57:51 -05:00
|
|
|
}
|
2021-01-07 18:49:38 -05:00
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
// push the image
|
|
|
|
const args = [
|
|
|
|
"push",
|
|
|
|
"--quiet",
|
|
|
|
"--digestfile",
|
|
|
|
digestFile,
|
|
|
|
"--creds",
|
|
|
|
creds,
|
2021-01-07 18:49:38 -05:00
|
|
|
imageToPush,
|
2021-01-19 20:54:49 +05:30
|
|
|
registryPath,
|
2021-01-07 18:49:38 -05:00
|
|
|
];
|
2020-11-27 11:44:15 +05:30
|
|
|
|
|
|
|
// check if tls-verify is not set to null
|
|
|
|
if (tlsVerify) {
|
|
|
|
args.push(`--tls-verify=${tlsVerify}`);
|
|
|
|
}
|
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
await execute(await getPodmanPath(), args);
|
2020-11-26 14:01:32 -05:00
|
|
|
|
2020-11-23 18:11:35 -05:00
|
|
|
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
|
2021-01-19 20:54:49 +05:30
|
|
|
core.setOutput("registry-path", registryPath);
|
2021-01-07 18:49:38 -05:00
|
|
|
|
|
|
|
try {
|
|
|
|
const digest = (await fs.promises.readFile(digestFile)).toString();
|
|
|
|
core.info(digest);
|
2021-01-19 20:54:49 +05:30
|
|
|
core.setOutput("digest", digest);
|
2021-01-07 18:49:38 -05:00
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
|
|
|
|
}
|
2020-11-07 13:04:04 +01:00
|
|
|
}
|
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
async function pullImageFromDocker(): Promise<boolean> {
|
|
|
|
try {
|
|
|
|
await execute(await getPodmanPath(), [ "pull", `docker-daemon:${imageToPush}` ]);
|
|
|
|
core.info(`${imageToPush} found in Docker image storage`);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.info(`${imageToPush} not found in Docker image storage`);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function checkImageInPodman(): Promise<boolean> {
|
|
|
|
// check if images exist in Podman's storage
|
|
|
|
core.info(`Checking if ${imageToPush} is in Podman image storage`);
|
|
|
|
try {
|
|
|
|
await execute(await getPodmanPath(), [ "image", "exists", imageToPush ]);
|
|
|
|
core.info(`${imageToPush} found in Podman image storage`);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.info(`${imageToPush} not found in Podman image storage`);
|
|
|
|
core.debug(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function isPodmanLocalImageLatest(): Promise<boolean> {
|
|
|
|
// get creation time of the image present in the Podman image storage
|
|
|
|
const podmanLocalImageTimeStamp = await execute(await getPodmanPath(), [
|
|
|
|
"image",
|
|
|
|
"inspect",
|
|
|
|
imageToPush,
|
|
|
|
"--format",
|
|
|
|
"{{.Created}}",
|
|
|
|
]);
|
|
|
|
|
|
|
|
// get creation time of the image pulled from the Docker image storage
|
|
|
|
// appending 'docker.io/library' infront of image name as pulled image name
|
|
|
|
// from Docker image storage starts with the 'docker.io/library'
|
|
|
|
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
|
|
|
|
"image",
|
|
|
|
"inspect",
|
|
|
|
`${dockerBaseUrl}/${imageToPush}`,
|
|
|
|
"--format",
|
|
|
|
"{{.Created}}",
|
|
|
|
]);
|
|
|
|
|
|
|
|
const podmanImageTime = new Date(podmanLocalImageTimeStamp.stdout).getTime();
|
|
|
|
|
|
|
|
const dockerImageTime = new Date(pulledImageCreationTimeStamp.stdout).getTime();
|
|
|
|
|
|
|
|
return podmanImageTime > dockerImageTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the pulled image from the Podman image storage
|
|
|
|
async function removeDockerImage(): Promise<void> {
|
|
|
|
if (imageToPush) {
|
|
|
|
core.info(`Removing ${imageToPush} from the Podman image storage`);
|
|
|
|
await execute(await getPodmanPath(), [ "rmi", imageToPush ]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function execute(
|
|
|
|
executable: string,
|
|
|
|
args: string[],
|
|
|
|
execOptions: exec.ExecOptions = {},
|
|
|
|
): Promise<ExecResult> {
|
2020-11-26 14:01:32 -05:00
|
|
|
let stdout = "";
|
|
|
|
let stderr = "";
|
2020-11-17 14:19:05 -05:00
|
|
|
|
2020-11-26 14:01:32 -05:00
|
|
|
const finalExecOptions = { ...execOptions };
|
2021-01-19 20:54:49 +05:30
|
|
|
finalExecOptions.ignoreReturnCode = true; // the return code is processed below
|
2020-11-26 14:01:32 -05:00
|
|
|
|
|
|
|
finalExecOptions.listeners = {
|
2021-01-19 20:54:49 +05:30
|
|
|
stdline: (line): void => {
|
|
|
|
stdout += `${line}\n`;
|
2020-11-26 14:01:32 -05:00
|
|
|
},
|
2021-01-19 20:54:49 +05:30
|
|
|
errline: (line): void => {
|
|
|
|
stderr += `${line}\n`;
|
2020-11-13 15:12:47 +01:00
|
|
|
},
|
2021-01-19 20:54:49 +05:30
|
|
|
};
|
2020-11-26 14:01:32 -05:00
|
|
|
|
|
|
|
const exitCode = await exec.exec(executable, args, finalExecOptions);
|
|
|
|
|
|
|
|
if (execOptions.ignoreReturnCode !== true && exitCode !== 0) {
|
2021-01-19 20:54:49 +05:30
|
|
|
// Throwing the stderr as part of the Error makes the stderr show up in the action outline,
|
|
|
|
// which saves some clicking when debugging.
|
2020-11-26 14:01:32 -05:00
|
|
|
let error = `${path.basename(executable)} exited with code ${exitCode}`;
|
|
|
|
if (stderr) {
|
|
|
|
error += `\n${stderr}`;
|
2020-11-07 13:04:04 +01:00
|
|
|
}
|
2020-11-26 14:01:32 -05:00
|
|
|
throw new Error(error);
|
2020-11-17 14:19:05 -05:00
|
|
|
}
|
2020-11-26 14:01:32 -05:00
|
|
|
|
|
|
|
return {
|
2021-01-19 20:54:49 +05:30
|
|
|
exitCode,
|
|
|
|
stdout,
|
|
|
|
stderr,
|
2020-11-26 14:01:32 -05:00
|
|
|
};
|
2020-11-07 13:04:04 +01:00
|
|
|
}
|
|
|
|
|
2021-01-19 20:54:49 +05:30
|
|
|
run()
|
|
|
|
.catch(core.setFailed)
|
|
|
|
.finally(() => {
|
|
|
|
if (isImageFromDocker) {
|
|
|
|
removeDockerImage();
|
|
|
|
}
|
|
|
|
});
|