Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Podman option support #135

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@ inputs:
description: An explicit key for restoring and saving the cache
required: true
default: docker-layer-caching-${{ github.workflow }}-{hash}
container:
description: Docker or Podman
required: false
default: "Docker"
restore-keys:
description: An ordered list of keys to use for restoring the cache if no cache hit occurred for key
required: false
default: docker-layer-caching-${{ github.workflow }}-
concurrency:
description: The number of concurrency when restoring and saving layers
required: true
default: '4'
default: "4"
skip-save:
description: Skip saving layers in the post step
required: false
default: 'false'
default: "false"

runs:
using: node12
Expand Down
71 changes: 49 additions & 22 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
import * as core from '@actions/core'
import * as exec from 'actions-exec-listener'
import { LayerCache } from './src/LayerCache'
import { ImageDetector } from './src/ImageDetector'
import * as core from "@actions/core";
import { LayerCache } from "./src/LayerCache";
import { ImageDetector } from "./src/ImageDetector";

const main = async () => {
const primaryKey = core.getInput(`key`, { required: true })
const restoreKeys = core.getInput(`restore-keys`, { required: false }).split(`\n`).filter(key => key !== ``)
const primaryKey = core.getInput(`key`, { required: true });
const restoreKeys = core
.getInput(`restore-keys`, { required: false })
.split(`\n`)
.filter((key) => key !== ``);

const imageDetector = new ImageDetector()
const imageDetector = new ImageDetector();

const alreadyExistingImages = await imageDetector.getExistingImages()
core.saveState(`already-existing-images`, JSON.stringify(alreadyExistingImages))
const container = core.getInput(`container`).toLowerCase();
if (container !== "docker" && container !== "podman") {
throw new Error("Wrong container name: " + container);
}

const layerCache = new LayerCache([])
layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10)
const restoredKey = await layerCache.restore(primaryKey, restoreKeys)
await layerCache.cleanUp()
const alreadyExistingImages = await imageDetector.getExistingImages(
container
);
core.saveState(
`already-existing-images`,
JSON.stringify(alreadyExistingImages)
);

core.saveState(`restored-key`, JSON.stringify(restoredKey !== undefined ? restoredKey : ''))
core.saveState(`restored-images`, JSON.stringify(await imageDetector.getImagesShouldSave(alreadyExistingImages)))
}
const layerCache = new LayerCache([]);
layerCache.concurrency = parseInt(
core.getInput(`concurrency`, { required: true }),
10
);
const restoredKey = await layerCache.restore(
primaryKey,
container,
restoreKeys
);
await layerCache.cleanUp();

main().catch(e => {
console.error(e)
core.setFailed(e)
core.saveState(
`restored-key`,
JSON.stringify(restoredKey !== undefined ? restoredKey : "")
);
core.saveState(
`restored-images`,
JSON.stringify(
await imageDetector.getImagesShouldSave(alreadyExistingImages, container)
)
);
};

core.saveState(`restored-key`, JSON.stringify(``))
core.saveState(`restored-images`, JSON.stringify([]))
})
main().catch((e) => {
console.error(e);
core.setFailed(e);

core.saveState(`restored-key`, JSON.stringify(``));
core.saveState(`restored-images`, JSON.stringify([]));
});
80 changes: 49 additions & 31 deletions post.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,61 @@
import * as core from '@actions/core'
import * as core from "@actions/core";

import { LayerCache } from './src/LayerCache'
import { ImageDetector } from './src/ImageDetector'
import { assertType } from 'typescript-is'
import { LayerCache } from "./src/LayerCache";
import { ImageDetector } from "./src/ImageDetector";
import { assertType } from "typescript-is";

const main = async () => {
if (JSON.parse(core.getInput('skip-save', { required: true }))) {
core.info('Skipping save.')
return
if (JSON.parse(core.getInput("skip-save", { required: true }))) {
core.info("Skipping save.");
return;
}

const primaryKey = core.getInput('key', { required: true })
const primaryKey = core.getInput("key", { required: true });

const restoredKey = JSON.parse(core.getState(`restored-key`))
const alreadyExistingImages = JSON.parse(core.getState(`already-existing-images`))
const restoredImages = JSON.parse(core.getState(`restored-images`))
const restoredKey = JSON.parse(core.getState(`restored-key`));
const alreadyExistingImages = JSON.parse(
core.getState(`already-existing-images`)
);
const restoredImages = JSON.parse(core.getState(`restored-images`));

assertType<string>(restoredKey)
assertType<string[]>(alreadyExistingImages)
assertType<string[]>(restoredImages)
assertType<string>(restoredKey);
assertType<string[]>(alreadyExistingImages);
assertType<string[]>(restoredImages);

const imageDetector = new ImageDetector()
const imageDetector = new ImageDetector();

const existingAndRestoredImages = alreadyExistingImages.concat(restoredImages)
const newImages = await imageDetector.getImagesShouldSave(existingAndRestoredImages)
if (newImages.length < 1) {
core.info(`There is no image to save.`)
return
const container = core.getInput(`container`).toLowerCase();
if (container !== "docker" && container !== "podman") {
throw new Error("Wrong container name: " + container);
}

const imagesToSave = await imageDetector.getImagesShouldSave(alreadyExistingImages)
const layerCache = new LayerCache(imagesToSave)
layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10)

await layerCache.store(primaryKey)
await layerCache.cleanUp()
}
const existingAndRestoredImages = alreadyExistingImages.concat(
restoredImages
);
const newImages = await imageDetector.getImagesShouldSave(
existingAndRestoredImages,
container
);
if (newImages.length < 1) {
core.info(`There is no image to save.`);
return;
}

main().catch(e => {
console.error(e)
core.setFailed(e)
})
const imagesToSave = await imageDetector.getImagesShouldSave(
alreadyExistingImages,
container
);
const layerCache = new LayerCache(imagesToSave);
layerCache.concurrency = parseInt(
core.getInput(`concurrency`, { required: true }),
10
);

await layerCache.store(primaryKey, container);
await layerCache.cleanUp();
};

main().catch((e) => {
console.error(e);
core.setFailed(e);
});
46 changes: 33 additions & 13 deletions src/ImageDetector.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import * as exec from 'actions-exec-listener'
import * as core from '@actions/core'
import * as exec from "actions-exec-listener";
import * as core from "@actions/core";

export class ImageDetector {
async getExistingImages(): Promise<string[]> {
const existingSet = new Set<string>([])
const ids = (await exec.exec(`docker image ls -q`, [], { silent: true, listeners: { stderr: console.warn }})).stdoutStr.split(`\n`).filter(id => id !== ``)
const repotags = (await exec.exec(`docker`, `image ls --format {{.Repository}}:{{.Tag}} --filter dangling=false`.split(' '), { silent: true, listeners: { stderr: console.warn }})).stdoutStr.split(`\n`).filter(id => id !== ``);
async getExistingImages(container: "docker" | "podman"): Promise<string[]> {
const existingSet = new Set<string>([]);
const ids = (
await exec.exec(`${container} image ls -q`, [], {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pin

Copy link
Author

@dabreadman dabreadman Apr 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we see is that we have replaced docker image ls -q with ${container} image ls -q, in which we can specify ${container} to be either docker or podman.

silent: true,
listeners: { stderr: console.warn },
})
).stdoutStr
.split(`\n`)
.filter((id) => id !== ``);
const repotags = (
await exec.exec(
`${container}`,
`image ls --format {{.Repository}}:{{.Tag}} --filter dangling=false`.split(
" "
),
{ silent: true, listeners: { stderr: console.warn } }
)
).stdoutStr
.split(`\n`)
.filter((id) => id !== ``);
core.debug(JSON.stringify({ log: "getExistingImages", ids, repotags }));
([...ids, ...repotags]).forEach(image => existingSet.add(image))
core.debug(JSON.stringify({ existingSet }))
return Array.from(existingSet)
[...ids, ...repotags].forEach((image) => existingSet.add(image));
core.debug(JSON.stringify({ existingSet }));
return Array.from(existingSet);
}

async getImagesShouldSave(alreadRegisteredImages: string[]): Promise<string[]> {
const resultSet = new Set(await this.getExistingImages())
alreadRegisteredImages.forEach(image => resultSet.delete(image))
return Array.from(resultSet)
async getImagesShouldSave(
alreadRegisteredImages: string[],
container: "docker" | "podman"
): Promise<string[]> {
const resultSet = new Set(await this.getExistingImages(container));
alreadRegisteredImages.forEach((image) => resultSet.delete(image));
return Array.from(resultSet);
}
}
Loading