fix: ensure given docker-compose file(s) are valid and at least one is provided

Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
This commit is contained in:
Emilien Escalle 2024-04-02 16:46:49 +02:00 committed by Emilien Escalle
parent 8d12e916ef
commit 2bd57c29bd
7 changed files with 145 additions and 106 deletions

View File

@ -23,11 +23,11 @@ jobs:
- name: "Assert: only expected services are running"
run: |
docker-compose -f ./docker/docker-compose.yml ps
docker compose -f ./docker/docker-compose.yml ps
docker-compose -f ./docker/docker-compose.yml ps | grep docker-service-b-1 || (echo "Service service-b is not running" && exit 1)
docker-compose -f ./docker/docker-compose.yml ps | grep docker-service-c-1 || (echo "Service service-c is not running" && exit 1)
(docker-compose -f ./docker/docker-compose.yml ps | grep docker-service-a-1 && echo "Unexpected service service-a is running" && exit 1) || true
docker compose -f ./docker/docker-compose.yml ps | grep docker-service-b-1 || (echo "Service service-b is not running" && exit 1)
docker compose -f ./docker/docker-compose.yml ps | grep docker-service-c-1 || (echo "Service service-c is not running" && exit 1)
(docker compose -f ./docker/docker-compose.yml ps | grep docker-service-a-1 && echo "Unexpected service service-a is running" && exit 1) || true
test-action-with-down-flags:
runs-on: ubuntu-latest

19
dist/index.js generated vendored
View File

@ -25937,14 +25937,10 @@ const docker_compose_service_1 = __nccwpck_require__(2911);
async function run(callback) {
try {
const loggerService = new logger_service_1.LoggerService();
const inputService = new input_service_1.InputService(loggerService);
const inputService = new input_service_1.InputService();
const dockerComposeService = new docker_compose_service_1.DockerComposeService();
const inputs = inputService.getInputs();
loggerService.debug(`inputs: ${JSON.stringify(inputs)}`);
if (!inputs.composeFiles.length) {
loggerService.warn("no compose files found");
return;
}
await callback(inputs, loggerService, dockerComposeService);
}
catch (error) {
@ -26032,10 +26028,6 @@ var InputNames;
InputNames["Cwd"] = "cwd";
})(InputNames || (exports.InputNames = InputNames = {}));
class InputService {
logger;
constructor(logger) {
this.logger = logger;
}
getInputs() {
return {
composeFiles: this.getComposeFiles(),
@ -26048,16 +26040,19 @@ class InputService {
}
getComposeFiles() {
const cwd = this.getCwd();
return (0, core_1.getMultilineInput)(InputNames.ComposeFile).filter((composeFile) => {
const composeFiles = (0, core_1.getMultilineInput)(InputNames.ComposeFile).filter((composeFile) => {
if (!composeFile.length) {
return false;
}
if (!(0, fs_1.existsSync)((0, path_1.join)(cwd, composeFile))) {
this.logger.warn(`${composeFile} does not exist in ${cwd}`);
return false;
throw new Error(`${composeFile} does not exist in ${cwd}`);
}
return true;
});
if (!composeFiles.length) {
throw new Error("No compose files found");
}
return composeFiles;
}
getServices() {
return (0, core_1.getMultilineInput)(InputNames.Services, { required: false });

19
dist/post.js generated vendored
View File

@ -25937,14 +25937,10 @@ const docker_compose_service_1 = __nccwpck_require__(2911);
async function run(callback) {
try {
const loggerService = new logger_service_1.LoggerService();
const inputService = new input_service_1.InputService(loggerService);
const inputService = new input_service_1.InputService();
const dockerComposeService = new docker_compose_service_1.DockerComposeService();
const inputs = inputService.getInputs();
loggerService.debug(`inputs: ${JSON.stringify(inputs)}`);
if (!inputs.composeFiles.length) {
loggerService.warn("no compose files found");
return;
}
await callback(inputs, loggerService, dockerComposeService);
}
catch (error) {
@ -26032,10 +26028,6 @@ var InputNames;
InputNames["Cwd"] = "cwd";
})(InputNames || (exports.InputNames = InputNames = {}));
class InputService {
logger;
constructor(logger) {
this.logger = logger;
}
getInputs() {
return {
composeFiles: this.getComposeFiles(),
@ -26048,16 +26040,19 @@ class InputService {
}
getComposeFiles() {
const cwd = this.getCwd();
return (0, core_1.getMultilineInput)(InputNames.ComposeFile).filter((composeFile) => {
const composeFiles = (0, core_1.getMultilineInput)(InputNames.ComposeFile).filter((composeFile) => {
if (!composeFile.length) {
return false;
}
if (!(0, fs_1.existsSync)((0, path_1.join)(cwd, composeFile))) {
this.logger.warn(`${composeFile} does not exist in ${cwd}`);
return false;
throw new Error(`${composeFile} does not exist in ${cwd}`);
}
return true;
});
if (!composeFiles.length) {
throw new Error("No compose files found");
}
return composeFiles;
}
getServices() {
return (0, core_1.getMultilineInput)(InputNames.Services, { required: false });

View File

@ -17,7 +17,6 @@ const runMock = jest.spyOn(runner, "run");
// Mock the external libraries and services used by the action
let debugMock: jest.SpiedFunction<typeof LoggerService.prototype.debug>;
let warnMock: jest.SpiedFunction<typeof LoggerService.prototype.warn>;
let setFailedMock: jest.SpiedFunction<typeof core.setFailed>;
let getInputsMock: jest.SpiedFunction<typeof InputService.prototype.getInputs>;
describe("run", () => {
@ -25,58 +24,11 @@ describe("run", () => {
jest.clearAllMocks();
debugMock = jest.spyOn(LoggerService.prototype, "debug").mockImplementation();
warnMock = jest.spyOn(LoggerService.prototype, "warn").mockImplementation();
setFailedMock = jest.spyOn(core, "setFailed").mockImplementation();
getInputsMock = jest.spyOn(InputService.prototype, "getInputs");
});
it("should return and warns when composeFile is empty", async () => {
getInputsMock.mockImplementation(() => ({
composeFiles: [],
services: [],
composeFlags: [],
upFlags: [],
downFlags: [],
cwd: "/current/working/dir",
}));
const callbackMock = jest.fn();
await runner.run(callbackMock);
expect(runMock).toHaveReturned();
// Verify that all of the functions were called correctly
expect(debugMock).toHaveBeenNthCalledWith(
1,
'inputs: {"composeFiles":[],"services":[],"composeFlags":[],"upFlags":[],"downFlags":[],"cwd":"/current/working/dir"}'
);
expect(warnMock).toHaveBeenNthCalledWith(1, "no compose files found");
expect(callbackMock).not.toHaveBeenCalled();
expect(setFailedMock).not.toHaveBeenCalled();
});
it("sets a failed status", async () => {
getInputsMock.mockImplementation(() => ({
composeFiles: ["docker-compose.yml"],
services: [],
composeFlags: [],
upFlags: [],
downFlags: [],
cwd: "/current/working/dir",
}));
const callbackMock = jest.fn();
callbackMock.mockRejectedValueOnce(new Error("unkown error"));
await runner.run(callbackMock);
expect(runMock).toHaveReturned();
// Verify that all of the functions were called correctly
expect(callbackMock).toHaveBeenCalled();
expect(setFailedMock).toHaveBeenNthCalledWith(1, "Error: unkown error");
});
it("should call callback when some compose files are provided", async () => {
it("should call given callback when inputs are valid", async () => {
getInputsMock.mockImplementation(() => ({
composeFiles: ["docker-compose.yml"],
services: [],
@ -113,4 +65,25 @@ describe("run", () => {
expect(setFailedMock).not.toHaveBeenCalled();
});
it("sets a failed status", async () => {
getInputsMock.mockImplementation(() => ({
composeFiles: ["docker-compose.yml"],
services: [],
composeFlags: [],
upFlags: [],
downFlags: [],
cwd: "/current/working/dir",
}));
const callbackMock = jest.fn();
callbackMock.mockRejectedValueOnce(new Error("unkown error"));
await runner.run(callbackMock);
expect(runMock).toHaveReturned();
// Verify that all of the functions were called correctly
expect(callbackMock).toHaveBeenCalled();
expect(setFailedMock).toHaveBeenNthCalledWith(1, "Error: unkown error");
});
});

View File

@ -16,17 +16,12 @@ export type RunCallback = (
export async function run(callback: RunCallback): Promise<void> {
try {
const loggerService = new LoggerService();
const inputService = new InputService(loggerService);
const inputService = new InputService();
const dockerComposeService = new DockerComposeService();
const inputs = inputService.getInputs();
loggerService.debug(`inputs: ${JSON.stringify(inputs)}`);
if (!inputs.composeFiles.length) {
loggerService.warn("no compose files found");
return;
}
await callback(inputs, loggerService, dockerComposeService);
} catch (error) {
setFailed(`${error instanceof Error ? error : JSON.stringify(error)}`);

View File

@ -1,5 +1,4 @@
import { InputService, InputNames } from "./input.service";
import { LoggerService } from "./logger.service";
import * as core from "@actions/core";
import * as fs from "fs";
@ -13,14 +12,12 @@ jest.mock("@actions/core");
describe("InputService", () => {
let service: InputService;
let logger: LoggerService;
let getInputMock: jest.SpiedFunction<typeof core.getInput>;
let getMultilineInputMock: jest.SpiedFunction<typeof core.getMultilineInput>;
let existsSyncMock: jest.SpiedFunction<typeof fs.existsSync>;
beforeEach(() => {
logger = new LoggerService();
service = new InputService(logger);
service = new InputService();
existsSyncMock = jest.spyOn(fs, "existsSync").mockImplementation();
getInputMock = jest.spyOn(core, "getInput").mockImplementation();
@ -52,7 +49,7 @@ describe("InputService", () => {
expect(inputs.composeFiles).toEqual(["file1", "file2"]);
});
it("should return only existing composeFiles input", () => {
it("should throws an error when a compose file does not exist", () => {
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
@ -62,13 +59,28 @@ describe("InputService", () => {
}
});
getInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.Cwd:
return "/current/working/directory";
default:
return "";
}
});
existsSyncMock.mockImplementation((file) => file === "/current/working/directory/file1");
expect(() => service.getInputs()).toThrow(
"file2 does not exist in /current/working/directory"
);
});
it("should throws an error when no composeFiles input", () => {
getMultilineInputMock.mockReturnValue([]);
getInputMock.mockReturnValue("");
existsSyncMock.mockImplementation((file) => file === "file1");
const inputs = service.getInputs();
expect(inputs.composeFiles).toEqual(["file1"]);
expect(() => service.getInputs()).toThrow("No compose files found");
});
});
@ -78,12 +90,15 @@ describe("InputService", () => {
switch (inputName) {
case InputNames.Services:
return ["service1", "service2"];
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockReturnValue("");
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
@ -93,7 +108,14 @@ describe("InputService", () => {
describe("compose-flags", () => {
it("should return given compose-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockImplementation((inputName) => {
switch (inputName) {
@ -104,15 +126,27 @@ describe("InputService", () => {
}
});
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.composeFlags).toEqual(["compose-flag1", "compose-flag2"]);
});
it("should return empty array when no compose-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockReturnValue("");
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.composeFlags).toEqual([]);
@ -121,7 +155,14 @@ describe("InputService", () => {
describe("up-flags", () => {
it("should return given up-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockImplementation((inputName) => {
switch (inputName) {
@ -132,15 +173,27 @@ describe("InputService", () => {
}
});
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.upFlags).toEqual(["up-flag1", "up-flag2"]);
});
it("should return empty array when no up-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockReturnValue("");
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.upFlags).toEqual([]);
@ -149,7 +202,14 @@ describe("InputService", () => {
describe("down-flags", () => {
it("should return given down-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockImplementation((inputName) => {
switch (inputName) {
@ -160,15 +220,26 @@ describe("InputService", () => {
}
});
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.downFlags).toEqual(["down-flag1", "down-flag2"]);
});
it("should return empty array when no down-flags input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockReturnValue("");
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();
expect(inputs.downFlags).toEqual([]);
@ -177,7 +248,14 @@ describe("InputService", () => {
describe("cwd", () => {
it("should return given cwd input", () => {
getMultilineInputMock.mockReturnValue([]);
getMultilineInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.ComposeFile:
return ["file1"];
default:
return [];
}
});
getInputMock.mockImplementation((inputName) => {
switch (inputName) {
case InputNames.Cwd:
@ -186,6 +264,7 @@ describe("InputService", () => {
return "";
}
});
existsSyncMock.mockReturnValue(true);
const inputs = service.getInputs();

View File

@ -1,5 +1,4 @@
import { getInput, getMultilineInput } from "@actions/core";
import { LoggerService } from "./logger.service";
import { existsSync } from "fs";
import { join } from "path";
@ -22,8 +21,6 @@ export enum InputNames {
}
export class InputService {
constructor(private readonly logger: LoggerService) {}
getInputs(): Inputs {
return {
composeFiles: this.getComposeFiles(),
@ -37,18 +34,23 @@ export class InputService {
private getComposeFiles(): string[] {
const cwd = this.getCwd();
return getMultilineInput(InputNames.ComposeFile).filter((composeFile) => {
const composeFiles = getMultilineInput(InputNames.ComposeFile).filter((composeFile) => {
if (!composeFile.length) {
return false;
}
if (!existsSync(join(cwd, composeFile))) {
this.logger.warn(`${composeFile} does not exist in ${cwd}`);
return false;
throw new Error(`${composeFile} does not exist in ${cwd}`);
}
return true;
});
if (!composeFiles.length) {
throw new Error("No compose files found");
}
return composeFiles;
}
private getServices(): string[] {