diff --git a/src/commands/build/handler.ts b/src/commands/build/handler.ts index 9c036595..137443cd 100644 --- a/src/commands/build/handler.ts +++ b/src/commands/build/handler.ts @@ -24,6 +24,7 @@ export async function buildHandler({ variants_dir_name: variantsDirName = defaultVariantsDirName, variants, skip_compose_validation: skipComposeValidation, + strip_compose_version: stripComposeVersion, arch, // Global options dir = defaultDir, @@ -65,9 +66,9 @@ export async function buildHandler({ skipUpload, composeFileName, requireGitData, - deleteOldPins, variantsDirPath, skipComposeValidation, + stripComposeVersion, packagesToBuildProps }; diff --git a/src/commands/build/index.ts b/src/commands/build/index.ts index c56f1018..c17bc639 100644 --- a/src/commands/build/index.ts +++ b/src/commands/build/index.ts @@ -59,6 +59,12 @@ export const build: CommandModule = { description: `Skip the Dappnode compose validation step`, type: "boolean" }, + strip_compose_version: { + alias: "strip-compose-version", + description: `For testing only: strip the obsolete top-level Compose version`, + type: "boolean", + default: false + }, arch: { description: `Specify the architecture to build for: "linux/amd64" or "linux/arm64"`, type: "string" diff --git a/src/commands/build/types.ts b/src/commands/build/types.ts index 1c234c92..587da620 100644 --- a/src/commands/build/types.ts +++ b/src/commands/build/types.ts @@ -13,6 +13,7 @@ export interface BuildCommandOptions extends CliGlobalOptions { variants?: string; all_variants?: boolean; skip_compose_validation?: boolean; + strip_compose_version?: boolean; arch?: string; } diff --git a/src/commands/publish/handler.ts b/src/commands/publish/handler.ts index 7534edd3..27bd37d4 100644 --- a/src/commands/publish/handler.ts +++ b/src/commands/publish/handler.ts @@ -28,6 +28,7 @@ export async function publishHandler({ require_git_data: requireGitData, delete_old_pins: deleteOldPins, skip_compose_validation: skipComposeValidation, + strip_compose_version: stripComposeVersion, // Global options dir = defaultDir, compose_file_name: composeFileName = defaultComposeFileName, @@ -76,12 +77,12 @@ export async function publishHandler({ uploadTo, userTimeout, requireGitData, - deleteOldPins, developerAddress, githubRelease, verbosityOptions, variantsDirPath, skipComposeValidation, + stripComposeVersion, packagesToBuildProps: getPackagesToBuildProps({ allVariants: Boolean(allVariants), commaSeparatedVariants: variants, diff --git a/src/commands/publish/index.ts b/src/commands/publish/index.ts index a5233a30..41fc3369 100644 --- a/src/commands/publish/index.ts +++ b/src/commands/publish/index.ts @@ -88,6 +88,12 @@ export const publish: CommandModule = { alias: "skip-compose-validation", description: `Skip the Dappnode compose validation step`, type: "boolean" + }, + strip_compose_version: { + alias: "strip-compose-version", + description: `For testing only: strip the obsolete top-level Compose version`, + type: "boolean", + default: false } }, diff --git a/src/commands/publish/types.ts b/src/commands/publish/types.ts index 8a3040d3..8faa4bb7 100644 --- a/src/commands/publish/types.ts +++ b/src/commands/publish/types.ts @@ -17,4 +17,5 @@ export interface PublishCommandOptions extends CliGlobalOptions { variants?: string; all_variants?: boolean; skip_compose_validation?: boolean; + strip_compose_version?: boolean; } diff --git a/src/files/compose/index.ts b/src/files/compose/index.ts index 1577217f..b25e2281 100644 --- a/src/files/compose/index.ts +++ b/src/files/compose/index.ts @@ -3,5 +3,6 @@ export { getComposePackageImages } from "./getComposePackageImages.js"; export { getComposePath } from "./getComposePath.js"; export { parseComposeUpstreamVersion } from "./parseComposeUpstreamVersion.js"; export { readCompose } from "./readCompose.js"; +export { stripComposeVersion } from "./stripComposeVersion.js"; export { updateComposeImageTags } from "./updateComposeImageTags.js"; export { writeCompose } from "./writeCompose.js"; diff --git a/src/files/compose/stripComposeVersion.ts b/src/files/compose/stripComposeVersion.ts new file mode 100644 index 00000000..6b1ae8f0 --- /dev/null +++ b/src/files/compose/stripComposeVersion.ts @@ -0,0 +1,11 @@ +import { Compose } from "@dappnode/types"; + +/** + * Remove the obsolete top-level version from a parsed Compose object. + * + * Compose still declares version as required in @dappnode/types, so use + * Reflect.deleteProperty until that shared type makes the property optional. + */ +export function stripComposeVersion(compose: Compose): void { + Reflect.deleteProperty(compose, "version"); +} diff --git a/src/tasks/buildAndUpload/getFileValidationTask.ts b/src/tasks/buildAndUpload/getFileValidationTask.ts index 2cbc77e4..e431c11b 100644 --- a/src/tasks/buildAndUpload/getFileValidationTask.ts +++ b/src/tasks/buildAndUpload/getFileValidationTask.ts @@ -7,40 +7,53 @@ import { validateDappnodeCompose, validateNotificationsSchema } from "@dappnode/schemas"; -import { getComposePath } from "../../files/index.js"; +import { + getComposePath, + stripComposeVersion as removeComposeVersion +} from "../../files/index.js"; import { CliError } from "../../params.js"; export function getFileValidationTask({ packagesToBuildProps, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }: { packagesToBuildProps: PackageToBuildProps[]; skipComposeValidation?: boolean; + stripComposeVersion?: boolean; }): ListrTask { return { title: `Validate files`, task: async () => await validatePackageFiles({ packagesToBuildProps, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }) }; } async function validatePackageFiles({ packagesToBuildProps, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }: { packagesToBuildProps: PackageToBuildProps[]; skipComposeValidation?: boolean; + stripComposeVersion?: boolean; }): Promise { for (const pkgProps of packagesToBuildProps) - await validateVariantFiles(pkgProps, skipComposeValidation); + await validateVariantFiles( + pkgProps, + skipComposeValidation, + stripComposeVersion + ); } async function validateVariantFiles( pkgProps: PackageToBuildProps, - skipComposeValidation?: boolean + skipComposeValidation?: boolean, + stripComposeVersion?: boolean ): Promise { const { manifest, @@ -75,8 +88,9 @@ async function validateVariantFiles( // Validate notifications schema if (notifications) validateNotificationsSchema(notifications); - // Validate setup wizard schema - if (setupWizard) validateSetupWizardSchema(setupWizard); + // Keep version available for the Dappnode validation above. Stripping is + // opt-in while support for versionless Compose files is being tested. + if (stripComposeVersion) removeComposeVersion(compose); } function validatePackageName(name: string): void { diff --git a/src/tasks/buildAndUpload/index.ts b/src/tasks/buildAndUpload/index.ts index afd059e4..877f91b0 100644 --- a/src/tasks/buildAndUpload/index.ts +++ b/src/tasks/buildAndUpload/index.ts @@ -28,11 +28,11 @@ export function buildAndUpload({ dir, variantsDirPath = defaultVariantsDirName, packagesToBuildProps, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }: BuildAndUploadOptions): ListrTask[] { const buildTimeout = parseTimeout(userTimeout); - // Release upload. Use function for return syntax const releaseUploaderProvider = cliArgsToReleaseUploaderProvider({ uploadTo, contentProvider @@ -40,7 +40,11 @@ export function buildAndUpload({ const releaseUploader = getReleaseUploader(releaseUploaderProvider); return [ - getFileValidationTask({ packagesToBuildProps, skipComposeValidation }), + getFileValidationTask({ + packagesToBuildProps, + skipComposeValidation, + stripComposeVersion + }), getVerifyConnectionTask({ releaseUploader, skipUpload }), getReleaseDirCreationTask({ packagesToBuildProps }), getFileCopyTask({ diff --git a/src/tasks/buildAndUpload/types.ts b/src/tasks/buildAndUpload/types.ts index 29238b08..6e02dfb0 100644 --- a/src/tasks/buildAndUpload/types.ts +++ b/src/tasks/buildAndUpload/types.ts @@ -14,4 +14,5 @@ export interface BuildAndUploadOptions { variantsDirPath?: string; packagesToBuildProps: PackageToBuildProps[]; skipComposeValidation?: boolean; + stripComposeVersion?: boolean; } diff --git a/src/tasks/publish/index.ts b/src/tasks/publish/index.ts index 67277903..9ae740de 100644 --- a/src/tasks/publish/index.ts +++ b/src/tasks/publish/index.ts @@ -24,7 +24,8 @@ export function publish({ variantsDirPath, packagesToBuildProps, isMultiVariant, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }: PublishOptions): ListrTask[] { return [ getVerifyEthConnectionTask({ ethProvider }), @@ -50,7 +51,8 @@ export function publish({ deleteOldPins, packagesToBuildProps, variantsDirPath, - skipComposeValidation + skipComposeValidation, + stripComposeVersion }, verbosityOptions }), diff --git a/src/tasks/publish/types.ts b/src/tasks/publish/types.ts index 10464b77..baadc520 100644 --- a/src/tasks/publish/types.ts +++ b/src/tasks/publish/types.ts @@ -19,4 +19,5 @@ export interface PublishOptions { packagesToBuildProps: PackageToBuildProps[]; isMultiVariant: boolean; skipComposeValidation?: boolean; + stripComposeVersion?: boolean; } diff --git a/test/commands/build.test.ts b/test/commands/build.test.ts index 220d8214..3508b48c 100644 --- a/test/commands/build.test.ts +++ b/test/commands/build.test.ts @@ -9,6 +9,7 @@ import { defaultVariantsEnvValues } from "../../src/params.js"; import { normalizeIpfsProvider } from "../../src/releaseUploader/ipfsNode/ipfsProvider.js"; +import { readCompose } from "../../src/files/index.js"; const contentProvider = normalizeIpfsProvider("remote"); @@ -51,6 +52,32 @@ describe("Init and build simple package", function () { // Check returned hash is correct expect(releaseHashes[0]).to.include("/ipfs/Qm"); }); + + it("Should only strip the Compose version when explicitly enabled", async () => { + const defaultBuild = await buildHandler({ + dir: testDir, + provider: contentProvider, + upload_to: "ipfs", + timeout: "5min", + skip_save: true, + skip_upload: true + }); + const [{ releaseDir }] = Object.values(defaultBuild); + + expect(readCompose([{ dir: releaseDir }]).version).to.equal("3.5"); + + await buildHandler({ + dir: testDir, + provider: contentProvider, + upload_to: "ipfs", + timeout: "5min", + skip_save: true, + skip_upload: true, + strip_compose_version: true + }); + + expect(readCompose([{ dir: releaseDir }]).version).to.be.undefined; + }); }); describe("Init and build package variants", function () { diff --git a/test/files/compose/stripComposeVersion.test.ts b/test/files/compose/stripComposeVersion.test.ts new file mode 100644 index 00000000..c3ecc5b1 --- /dev/null +++ b/test/files/compose/stripComposeVersion.test.ts @@ -0,0 +1,22 @@ +import { expect } from "chai"; +import { Compose } from "@dappnode/types"; +import { stripComposeVersion } from "../../../src/files/index.js"; + +describe("files / compose / stripComposeVersion", () => { + it("Should strip the obsolete top-level version", () => { + const compose: Compose = { + version: "3.5", + services: { + service: { image: "service:1.0.0" } + } + }; + + stripComposeVersion(compose); + + expect(compose).to.deep.equal({ + services: { + service: { image: "service:1.0.0" } + } + }); + }); +});