Skip to content
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
14 changes: 14 additions & 0 deletions docs/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@
"title": "Nodeenv image",
"type": "string",
"default": "hyperledger/fabric-nodeenv"
},
"committer": {
"$id": "#/properties/global/properties/fabricImages/properties/committer",
"title": "Fabric-X committer image",
"description": "Custom image repository or full image name for Fabric-X committer microservices",
"type": "string",
"default": "ghcr.io/hyperledger/fabric-x-committer"
},
"postgres": {
"$id": "#/properties/global/properties/fabricImages/properties/postgres",
"title": "Fabric-X committer database image",
"description": "Custom image repository or full image name for Fabric-X PostgreSQL database",
"type": "string",
"default": "docker.io/library/postgres"
}
}
},
Expand Down
14 changes: 14 additions & 0 deletions e2e/__snapshots__/schema.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ exports[`schema should match snapshot 1`] = `
"title": "CCENV image",
"type": "string",
},
"committer": {
"$id": "#/properties/global/properties/fabricImages/properties/committer",
"default": "ghcr.io/hyperledger/fabric-x-committer",
"description": "Custom image repository or full image name for Fabric-X committer microservices",
"title": "Fabric-X committer image",
"type": "string",
},
"javaenv": {
"$id": "#/properties/global/properties/fabricImages/properties/javaenv",
"default": "hyperledger/fabric-javaenv",
Expand All @@ -333,6 +340,13 @@ exports[`schema should match snapshot 1`] = `
"title": "Peer image",
"type": "string",
},
"postgres": {
"$id": "#/properties/global/properties/fabricImages/properties/postgres",
"default": "docker.io/library/postgres",
"description": "Custom image repository or full image name for Fabric-X PostgreSQL database",
"title": "Fabric-X committer database image",
"type": "string",
},
"tools": {
"$id": "#/properties/global/properties/fabricImages/properties/tools",
"default": "hyperledger/fabric-tools",
Expand Down
79 changes: 79 additions & 0 deletions e2e/fabloCommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,63 @@ describe("validate", () => {
expect(commandResult.output).toContain(" instance.orgs[0].organization.mspName : does not match pattern");
expect(commands.getFiles()).toEqual([]);
});

it("should warn when classic-only image is configured for Fabric-X", () => {
// Given
commands.fabloExec("init fabric-x --set global.fabricImages.peer=myorg/peer:2.5");

// When
const commandResult = commands.fabloExec("validate");

// Then
expect(commandResult).toEqual(TestCommands.success());
expect(commandResult.output).toContain("Validation warnings count: 1");
expect(commandResult.output).toContain(
"Setting 'global.fabricImages.peer' is not supported for provider 'fabric-x' and will be ignored.",
);
});

it("should warn when committer image is configured for classic Fabric", () => {
// Given
commands.fabloExec("init --set global.fabricImages.committer=myorg/committer:1.0.3");

// When
const commandResult = commands.fabloExec("validate");

// Then
expect(commandResult).toEqual(TestCommands.success());
expect(commandResult.output).toContain("Validation warnings count: 1");
expect(commandResult.output).toContain(
"Setting 'global.fabricImages.committer' is only supported when 'global.provider' is 'fabric-x' and will be ignored.",
);
});

it("should not produce warnings for shared orderer and tools settings", () => {
// Given
commands.fabloExec(
"init --set global.fabricImages.orderer=myorg/orderer:custom --set global.fabricImages.tools=myorg/tools:custom",
);

// When
const classicResult = commands.fabloExec("validate");

// Then
expect(classicResult).toEqual(TestCommands.success());
expect(classicResult.output).toContain("Validation warnings count: 0");

// Given
commands.cleanupWorkdir();
commands.fabloExec(
"init fabric-x --set global.fabricImages.orderer=myorg/orderer:custom --set global.fabricImages.tools=myorg/tools:custom",
);

// When
const fabricXResult = commands.fabloExec("validate");

// Then
expect(fabricXResult).toEqual(TestCommands.success());
expect(fabricXResult.output).toContain("Validation warnings count: 0");
});
});

describe("extend config", () => {
Expand Down Expand Up @@ -390,4 +447,26 @@ describe("generate", () => {
expect(baseFunctions).toContain("User1@bank.fablo.com");
expect(baseFunctions).toContain("AND('BankMSP.member')");
});

it("should generate Fabric-X network files with custom fabricImages", () => {
// Given
commands.fabloExec(
"init fabric-x --set global.fabricImages.committer=myorg/committer:2.0.0 --set global.fabricImages.orderer=myorg/orderer:2.0.0 --set global.fabricImages.tools=myorg/tools:2.0.0 --set global.fabricImages.postgres=myorg/postgres:18.4",
);

// When
const commandResult = commands.fabloExec("generate");

// Then
expect(commandResult).toEqual(TestCommands.success());

const dockerCompose = commands.getFileContent("fablo-target/fabric-x/docker-compose.yaml");
expect(dockerCompose).toContain("image: myorg/committer:2.0.0");
expect(dockerCompose).toContain("image: myorg/orderer:2.0.0");
expect(dockerCompose).toContain("image: myorg/postgres:18.4");

const baseFunctions = commands.getFileContent("fablo-target/fabric-x/scripts/base-functions.sh");
expect(baseFunctions).toContain('TOOLS_IMAGE="${TOOLS_IMAGE:-myorg/tools:2.0.0}"');
expect(baseFunctions).toContain('ORDERER_IMAGE="${ORDERER_IMAGE:-myorg/orderer:2.0.0}"');
});
});
26 changes: 26 additions & 0 deletions src/commands/validate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as path from "path";
import {
ChaincodeJson,
ChannelJson,
FabricImagesJson,
FabloConfigJson,
GlobalJson,
OrdererJson,
Expand Down Expand Up @@ -126,6 +127,7 @@ export default class Validate extends Command {
this._validateFabricXSettings(networkConfig);
return;
}
this._validateClassicFabricImages(networkConfig.global);
networkConfig.chaincodes.forEach((chaincode) => this._validateCcaaTLS(networkConfig.global, chaincode));
this._validateOrgs(networkConfig.orgs);
this._validateEngineSpecificSettings(networkConfig);
Expand Down Expand Up @@ -585,6 +587,30 @@ export default class Validate extends Command {
message: `fabric-x requires exactly one channel found ${channels.length}.`,
});
}

const classicOnlyImages: (keyof FabricImagesJson)[] = ["peer", "ca", "ccenv", "baseos", "javaenv", "nodeenv"];

classicOnlyImages.forEach((key) => {
if (global.fabricImages?.[key]) {
this.emit(validationErrorType.WARN, {
category: validationCategories.GENERAL,
message: `Setting 'global.fabricImages.${key}' is not supported for provider 'fabric-x' and will be ignored.`,
});
}
});
}

_validateClassicFabricImages(global: GlobalJson): void {
const fabricXOnlyImages: (keyof FabricImagesJson)[] = ["committer", "postgres"];

fabricXOnlyImages.forEach((key) => {
if (global.fabricImages?.[key]) {
this.emit(validationErrorType.WARN, {
category: validationCategories.GENERAL,
message: `Setting 'global.fabricImages.${key}' is only supported when 'global.provider' is 'fabric-x' and will be ignored.`,
});
}
});
}
_validateExplorer(global: GlobalJson, orgs: OrgJson[]): void {
if (global.tools?.explorer === true) {
Expand Down
188 changes: 188 additions & 0 deletions src/extend-config/extendGlobal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,192 @@ describe("extendGlobal fabricImages", () => {
expect(global.peerImage).toBe("fablo.io/peer/fabric-peer:dev");
expect(global.toolsImage).toBe("fablo.io/tools/fabric-tools@sha256:deadbeef");
});

describe("Fabric-X image resolution", () => {
it("should use pinned default Fabric-X images when provider is fabric-x", () => {
// Given
const globalJson = {
fabricVersion: "3.1.0",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
};

// When
const global = extendGlobal(globalJson);

// Then
expect(global.ordererImage).toBe("ghcr.io/hyperledger/fabric-x-orderer:1.0.0");
expect(global.committerImage).toBe("ghcr.io/hyperledger/fabric-x-committer:1.0.3");
expect(global.toolsImage).toBe("ghcr.io/hyperledger/fabric-x-tools:1.0.0");
expect(global.postgresImage).toBe("docker.io/library/postgres:18.3-alpine3.23");
expect("peerImage" in global).toBe(false);
expect("caImage" in global).toBe(false);
expect("ccenvImage" in global).toBe(false);
expect("baseosImage" in global).toBe(false);
expect("javaenvImage" in global).toBe(false);
expect("nodeenvImage" in global).toBe(false);
expect("fabricCaVersion" in global).toBe(false);
expect("fabricCcenvVersion" in global).toBe(false);
expect("fabricBaseosVersion" in global).toBe(false);
expect("fabricJavaenvVersion" in global).toBe(false);
expect("fabricNodeenvVersion" in global).toBe(false);
expect(global.ordererVersion).toBe("1.0.0");
expect(global.committerVersion).toBe("1.0.3");
expect(global.toolsVersion).toBe("1.0.0");
expect(global.postgresVersion).toBe("18.3-alpine3.23");
});

it("should keep pinned Fabric-X default tags regardless of fabricVersion", () => {
// Given
const globalJson = {
fabricVersion: "2.5.12",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
};

// When
const global = extendGlobal(globalJson);

// Then
expect(global.ordererImage).toBe("ghcr.io/hyperledger/fabric-x-orderer:1.0.0");
expect(global.committerImage).toBe("ghcr.io/hyperledger/fabric-x-committer:1.0.3");
expect(global.toolsImage).toBe("ghcr.io/hyperledger/fabric-x-tools:1.0.0");
expect(global.postgresImage).toBe("docker.io/library/postgres:18.3-alpine3.23");
});

it("should align Fabric-X orderer and tools image versions when fabricVersion matches a Fabric-X release", () => {
// Given
const globalJson = {
fabricVersion: "1.0.2",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
};

// When
const global = extendGlobal(globalJson);

// Then
expect(global.ordererVersion).toBe("1.0.2");
expect(global.toolsVersion).toBe("1.0.2");
expect(global.committerVersion).toBe("1.0.3");
expect(global.postgresVersion).toBe("18.3-alpine3.23");
expect(global.ordererImage).toBe("ghcr.io/hyperledger/fabric-x-orderer:1.0.2");
expect(global.toolsImage).toBe("ghcr.io/hyperledger/fabric-x-tools:1.0.2");
expect(global.committerImage).toBe("ghcr.io/hyperledger/fabric-x-committer:1.0.3");
expect(global.postgresImage).toBe("docker.io/library/postgres:18.3-alpine3.23");
});

it("should append pinned default tags when overriding Fabric-X image repositories without tags", () => {
// Given
const globalJson = {
fabricVersion: "3.1.0",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
fabricImages: {
committer: "mirror.local/committer",
postgres: "mirror.local/postgres",
orderer: "mirror.local/orderer",
tools: "mirror.local/tools",
},
};

// When
const global = extendGlobal(globalJson);

// Then
expect(global.committerImage).toBe("mirror.local/committer:1.0.3");
expect(global.postgresImage).toBe("mirror.local/postgres:18.3-alpine3.23");
expect(global.ordererImage).toBe("mirror.local/orderer:1.0.0");
expect(global.toolsImage).toBe("mirror.local/tools:1.0.0");
});

it("should keep custom tags or digests for Fabric-X images as is", () => {
// Given
const globalJson = {
fabricVersion: "3.1.0",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
fabricImages: {
committer: "myorg/committer:2.0.0-rc1",
postgres: "myorg/postgres@sha256:deadbeefcafe",
orderer: "myorg/orderer:custom-tag",
tools: "myorg/tools@sha256:1234567890",
},
};

// When
const global = extendGlobal(globalJson);

// Then
expect(global.committerImage).toBe("myorg/committer:2.0.0-rc1");
expect(global.postgresImage).toBe("myorg/postgres@sha256:deadbeefcafe");
expect(global.ordererImage).toBe("myorg/orderer:custom-tag");
expect(global.toolsImage).toBe("myorg/tools@sha256:1234567890");
});

it("should ignore classic-only images configured for Fabric-X", () => {
// Given
const globalJson = {
fabricVersion: "3.1.0",
tls: true,
peerDevMode: false,
provider: "fabric-x" as const,
fabricImages: {
peer: "myorg/peer:custom",
ca: "myorg/ca:custom",
ccenv: "myorg/ccenv:custom",
baseos: "myorg/baseos:custom",
javaenv: "myorg/javaenv:custom",
nodeenv: "myorg/nodeenv:custom",
},
};

// When
const global = extendGlobal(globalJson);

// Then
expect("peerImage" in global).toBe(false);
expect("caImage" in global).toBe(false);
expect("ccenvImage" in global).toBe(false);
expect("baseosImage" in global).toBe(false);
expect("javaenvImage" in global).toBe(false);
expect("nodeenvImage" in global).toBe(false);
expect("fabricCaVersion" in global).toBe(false);
expect("fabricCcenvVersion" in global).toBe(false);
expect("fabricBaseosVersion" in global).toBe(false);
expect("fabricJavaenvVersion" in global).toBe(false);
expect("fabricNodeenvVersion" in global).toBe(false);
expect(global.ordererImage).toBe("ghcr.io/hyperledger/fabric-x-orderer:1.0.0");
});
});

it("should ignore Fabric-X only images configured for classic Fabric", () => {
// Given
const globalJson = {
fabricVersion: "2.5.12",
tls: true,
peerDevMode: false,
fabricImages: {
committer: "myorg/committer:custom",
postgres: "myorg/postgres:custom",
},
};

// When
const global = extendGlobal(globalJson);

// Then
expect("committerImage" in global).toBe(false);
expect("postgresImage" in global).toBe(false);
expect("ordererVersion" in global).toBe(false);
expect("committerVersion" in global).toBe(false);
expect("toolsVersion" in global).toBe(false);
expect("postgresVersion" in global).toBe(false);
expect(global.peerImage).toBe("hyperledger/fabric-peer:2.5.12");
});
});
Loading
Loading