2021-01-11 10:04:37 +00:00
|
|
|
import * as core from "@actions/core";
|
|
|
|
import * as exec from "@actions/exec";
|
|
|
|
import * as io from "@actions/io";
|
2021-01-07 23:49:38 +00:00
|
|
|
import * as fs from "fs";
|
2020-11-26 19:01:32 +00:00
|
|
|
import * as path from "path";
|
2020-11-07 12:04:04 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
interface Response {
|
|
|
|
exitCode: number;
|
|
|
|
stdout: string;
|
|
|
|
stderr: string;
|
|
|
|
}
|
2020-11-07 12:04:04 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
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-13 14:12:47 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
// get Podman cli
|
|
|
|
const podman = await io.which("podman", true);
|
2020-11-23 23:11:35 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
let imageToPush = `${imageInput}:${tag}`;
|
2020-11-26 19:01:32 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
// check if image exist in Podman local registry
|
|
|
|
const isPresentInPodman: boolean = await checkImageInPodman(
|
|
|
|
imageToPush,
|
|
|
|
podman,
|
|
|
|
);
|
|
|
|
|
|
|
|
// check if image exist in Docker local registry and if exist pull the image to Podman
|
|
|
|
const isPresentInDocker: boolean = await pullImageFromDocker(
|
|
|
|
imageToPush,
|
|
|
|
podman,
|
|
|
|
);
|
2020-11-26 19:01:32 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
// boolean value to check if pushed image is from Docker local registry
|
|
|
|
let isPushingDockerImage = false;
|
2020-11-26 19:01:32 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
if (isPresentInPodman && isPresentInDocker) {
|
|
|
|
const warningMsg = "Image found in Podman as well as in Docker local registry.";
|
|
|
|
|
|
|
|
let isPodmanImageLatest = false;
|
|
|
|
try {
|
|
|
|
isPodmanImageLatest = await isPodmanLocalImageLatest(
|
|
|
|
imageToPush,
|
|
|
|
podman,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.setFailed(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isPodmanImageLatest) {
|
|
|
|
core.warning(`${warningMsg} Using Docker local registry's image as that is built latest`);
|
|
|
|
imageToPush = `docker.io/library/${imageToPush}`;
|
|
|
|
isPushingDockerImage = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
core.warning(`${warningMsg} Using Podman local registry's image as that is built latest`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (isPresentInDocker) {
|
|
|
|
imageToPush = `docker.io/library/${imageToPush}`;
|
|
|
|
isPushingDockerImage = true;
|
2020-11-13 14:12:47 +00:00
|
|
|
}
|
2020-11-07 12:04:04 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
|
|
|
|
if (username) {
|
|
|
|
pushMsg += ` as ${username}`;
|
|
|
|
}
|
|
|
|
core.info(pushMsg);
|
|
|
|
|
|
|
|
const registryPath = `${registry.replace(/\/$/, "")}/${imageInput}:${tag}`;
|
2020-11-23 23:11:35 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
const creds = `${username}:${password}`;
|
2020-11-26 19:01:32 +00:00
|
|
|
|
2021-01-08 14:57:51 +00:00
|
|
|
let digestFile = digestFileInput;
|
|
|
|
if (!digestFile) {
|
2021-01-11 10:04:37 +00:00
|
|
|
digestFile = `${imageToPush.replace(
|
|
|
|
/[/\\/?%*:|"<>]/g,
|
|
|
|
"-",
|
|
|
|
)}_digest.txt`;
|
2021-01-08 14:57:51 +00:00
|
|
|
}
|
2021-01-07 23:49:38 +00:00
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
// push image
|
|
|
|
const args = [
|
|
|
|
"push",
|
|
|
|
"--quiet",
|
|
|
|
"--digestfile",
|
|
|
|
digestFile,
|
|
|
|
"--creds",
|
|
|
|
creds,
|
2021-01-07 23:49:38 +00:00
|
|
|
imageToPush,
|
2021-01-11 10:04:37 +00:00
|
|
|
registryPath,
|
2021-01-07 23:49:38 +00:00
|
|
|
];
|
2020-11-27 06:14:15 +00:00
|
|
|
|
|
|
|
// check if tls-verify is not set to null
|
|
|
|
if (tlsVerify) {
|
|
|
|
args.push(`--tls-verify=${tlsVerify}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
await execute(podman, args);
|
2020-11-26 19:01:32 +00:00
|
|
|
|
2020-11-23 23:11:35 +00:00
|
|
|
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
|
2021-01-11 10:04:37 +00:00
|
|
|
core.setOutput("registry-path", registryPath);
|
|
|
|
|
|
|
|
// remove the pulled image from the Podman local registry
|
|
|
|
if (isPushingDockerImage) {
|
|
|
|
core.info(`Removing ${imageToPush} from the Podman local registry`);
|
|
|
|
await execute(podman, [ "rmi", imageToPush ]);
|
|
|
|
}
|
2021-01-07 23:49:38 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
const digest = (await fs.promises.readFile(digestFile)).toString();
|
|
|
|
core.info(digest);
|
2021-01-11 10:04:37 +00:00
|
|
|
core.setOutput("digest", digest);
|
2021-01-07 23:49:38 +00:00
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
|
|
|
|
}
|
2020-11-07 12:04:04 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 10:04:37 +00:00
|
|
|
async function pullImageFromDocker(
|
|
|
|
imageName: string,
|
|
|
|
podman: string,
|
|
|
|
): Promise<boolean> {
|
|
|
|
try {
|
|
|
|
await execute(podman, [ "pull", `docker-daemon:${imageName}` ]);
|
|
|
|
core.info("Image found and sucessfully pulled from Docker local registry");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.info("Image not found in Docker local registry");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function checkImageInPodman(
|
|
|
|
imageName: string,
|
|
|
|
podman: string,
|
|
|
|
): Promise<boolean> {
|
|
|
|
// check if images exist in Podman's local registry
|
|
|
|
core.info("Checking image in Podman local registry");
|
|
|
|
try {
|
|
|
|
await execute(podman, [ "image", "exists", imageName ]);
|
|
|
|
core.info("Image found in Podman local registry");
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
core.info("Image not found in Podman local registry");
|
|
|
|
core.debug(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function isPodmanLocalImageLatest(
|
|
|
|
imageName: string,
|
|
|
|
podman: string,
|
|
|
|
): Promise<boolean> {
|
|
|
|
// get creation time of the image present in the Podman local registry
|
|
|
|
const podmanLocalImageTimeStamp = await execute(podman, [
|
|
|
|
"image",
|
|
|
|
"inspect",
|
|
|
|
imageName,
|
|
|
|
"--format",
|
|
|
|
"{{.Created}}",
|
|
|
|
]);
|
|
|
|
|
|
|
|
// get creation time of the image pulled from the Docker local registry
|
|
|
|
// appending 'docker.io/library' infront of image name as pulled image name
|
|
|
|
// from Docker local registry starts with the 'docker.io/library'
|
|
|
|
const pulledImageCreationTimeStamp = await execute(podman, [
|
|
|
|
"image",
|
|
|
|
"inspect",
|
|
|
|
`docker.io/library/${imageName}`,
|
|
|
|
"--format",
|
|
|
|
"{{.Created}}",
|
|
|
|
]);
|
|
|
|
|
|
|
|
const podmanImageTime = new Date(podmanLocalImageTimeStamp.stdout).getTime();
|
|
|
|
|
|
|
|
const dockerImageTime = new Date(pulledImageCreationTimeStamp.stdout).getTime();
|
|
|
|
|
|
|
|
return podmanImageTime > dockerImageTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function execute(
|
|
|
|
executable: string,
|
|
|
|
args: string[],
|
|
|
|
execOptions: exec.ExecOptions = {},
|
|
|
|
): Promise<Response> {
|
2020-11-26 19:01:32 +00:00
|
|
|
let stdout = "";
|
|
|
|
let stderr = "";
|
2020-11-17 19:19:05 +00:00
|
|
|
|
2020-11-26 19:01:32 +00:00
|
|
|
const finalExecOptions = { ...execOptions };
|
2021-01-11 10:04:37 +00:00
|
|
|
finalExecOptions.ignoreReturnCode = true; // the return code is processed below
|
2020-11-26 19:01:32 +00:00
|
|
|
|
|
|
|
finalExecOptions.listeners = {
|
|
|
|
stdline: (line) => {
|
2021-01-11 10:04:37 +00:00
|
|
|
stdout += `${line}\n`;
|
2020-11-26 19:01:32 +00:00
|
|
|
},
|
|
|
|
errline: (line) => {
|
2021-01-11 10:04:37 +00:00
|
|
|
stderr += `${line}\n`;
|
2020-11-13 14:12:47 +00:00
|
|
|
},
|
2021-01-11 10:04:37 +00:00
|
|
|
};
|
2020-11-26 19:01:32 +00:00
|
|
|
|
|
|
|
const exitCode = await exec.exec(executable, args, finalExecOptions);
|
|
|
|
|
|
|
|
if (execOptions.ignoreReturnCode !== true && exitCode !== 0) {
|
2021-01-11 10:04:37 +00:00
|
|
|
// 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 19:01:32 +00:00
|
|
|
let error = `${path.basename(executable)} exited with code ${exitCode}`;
|
|
|
|
if (stderr) {
|
|
|
|
error += `\n${stderr}`;
|
2020-11-07 12:04:04 +00:00
|
|
|
}
|
2020-11-26 19:01:32 +00:00
|
|
|
throw new Error(error);
|
2020-11-17 19:19:05 +00:00
|
|
|
}
|
2020-11-26 19:01:32 +00:00
|
|
|
|
|
|
|
return {
|
2021-01-11 10:04:37 +00:00
|
|
|
exitCode,
|
|
|
|
stdout,
|
|
|
|
stderr,
|
2020-11-26 19:01:32 +00:00
|
|
|
};
|
2020-11-07 12:04:04 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 19:19:05 +00:00
|
|
|
run().catch(core.setFailed);
|