diff --git a/docs/user-guide/t2tc-commands.md b/docs/user-guide/t2tc-commands.md index b31abe55..740f02ad 100644 --- a/docs/user-guide/t2tc-commands.md +++ b/docs/user-guide/t2tc-commands.md @@ -93,6 +93,11 @@ content-cli t2tc package export -p --packageKeys key1 ... keyN The `--keysByVersion` option can be used to export packages by specific version. You can specify multiple packages with version separated by spaces, in the format of 'packageKey.version'. The `--withDependencies` option can be used to also export dependencies of the given packages. The `--unzip` option can be used to unzip the exported packages into the current working directory. +The `--staging` option exports the current staging state of the given `--packageKeys` instead of their latest deployed version. Branch package keys (`@`) are supported. Staging packages appear as `_staging.zip`, use `staging` as their version in `manifest.json` and `variables.json`, and their staging variable values are exported. With `--withDependencies`, dependencies are exported at the published versions the staging configuration references. `--staging` cannot be combined with `--keysByVersion`. + +```bash +content-cli t2tc package export -p --packageKeys projectKey@branchKey --staging --withDependencies +``` Depending on the `--unzip` option used, a zip file, or a directory containing the exported packages, will be created in the current working directory containing: diff --git a/src/commands/t2tc/api/t2tc-package-api.ts b/src/commands/t2tc/api/t2tc-package-api.ts index 27e4cf0b..15cea3e1 100644 --- a/src/commands/t2tc/api/t2tc-package-api.ts +++ b/src/commands/t2tc/api/t2tc-package-api.ts @@ -74,6 +74,16 @@ export class T2tcPackageApi { }); } + public async exportStagingPackages(packageKeys: string[], withDependencies: boolean): Promise { + const queryParams = new URLSearchParams(); + packageKeys.forEach(packageKey => queryParams.append("packageKeys", packageKey)); + queryParams.set("withDependencies", withDependencies.toString()); + + return this.httpClient().getFile(`/package-manager/api/core/staging/packages/export/batch?${queryParams.toString()}`).catch(e => { + throw new FatalError(`Problem exporting staging packages: ${e}`); + }); + } + public async exportPackagesByVersions(packageKeysWithVersion: string[], withDependencies: boolean): Promise { const queryParams = new URLSearchParams(); packageKeysWithVersion.forEach(packageKeyByVersion => queryParams.append("packageKeysWithVersion", packageKeyByVersion)); diff --git a/src/commands/t2tc/batch-export-import.constants.ts b/src/commands/t2tc/batch-export-import.constants.ts index 534543c6..92ca2144 100644 --- a/src/commands/t2tc/batch-export-import.constants.ts +++ b/src/commands/t2tc/batch-export-import.constants.ts @@ -7,5 +7,6 @@ export enum BatchExportImportConstants { ZIP_EXTENSION = ".zip", JSON_EXTENSION = ".json", NODES_FOLDER_NAME = "nodes/", - SCENARIO_NODE = "SCENARIO" + SCENARIO_NODE = "SCENARIO", + STAGING_VERSION = "staging" } diff --git a/src/commands/t2tc/module.ts b/src/commands/t2tc/module.ts index 7da3374d..f89aa688 100644 --- a/src/commands/t2tc/module.ts +++ b/src/commands/t2tc/module.ts @@ -33,6 +33,7 @@ class Module extends IModule { .option("--unzip", "Unzip the exported file", "") .option("--gitProfile ", "Git profile which you want to use for the Git operations") .option("--gitBranch ", "Git branch in which you want to push the exported file") + .option("--staging", "Export the staging state of the given --packageKeys (including branch packages) instead of the latest deployed version", false) .action(this.batchExportPackages); t2tcPackageCommand.command("import") @@ -81,8 +82,11 @@ class Module extends IModule { if (options.gitProfile && !options.gitBranch) { throw new Error("Please specify a branch using --gitBranch when using a Git profile."); } + if (options.staging && options.keysByVersion) { + throw new Error("Staging parameter is only supported with --packageKeys, not --keysByVersion."); + } options.withDependencies = options.withDependencies ?? false; - await new T2tcCommandService(context).batchExportPackages(options.packageKeys, options.keysByVersion, options.withDependencies, options.gitBranch, options.unzip); + await new T2tcCommandService(context).batchExportPackages(options.packageKeys, options.keysByVersion, options.withDependencies, options.gitBranch, options.unzip, options.staging); } private async batchImportPackages(context: Context, command: Command, options: OptionValues): Promise { diff --git a/src/commands/t2tc/t2tc-command.service.ts b/src/commands/t2tc/t2tc-command.service.ts index eb75e313..fd665c2d 100644 --- a/src/commands/t2tc/t2tc-command.service.ts +++ b/src/commands/t2tc/t2tc-command.service.ts @@ -39,8 +39,8 @@ export class T2tcCommandService { } } - public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise { - return this.t2tcPackageService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip); + public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean, staging: boolean = false): Promise { + return this.t2tcPackageService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip, staging); } public batchImportPackages(file: string, directory: string, overwrite: boolean, gitBranch: string, performValidation: boolean = false): Promise { diff --git a/src/commands/t2tc/t2tc-package.service.ts b/src/commands/t2tc/t2tc-package.service.ts index 35301ede..31fcc985 100644 --- a/src/commands/t2tc/t2tc-package.service.ts +++ b/src/commands/t2tc/t2tc-package.service.ts @@ -16,6 +16,7 @@ import { parse, stringify } from "../../core/utils/json"; import { PackageApi } from "../studio/api/package-api"; import { T2tcPackageApi } from "./api/t2tc-package-api"; import { VariableApi } from "../configuration-management/api/variable-api"; +import { StagingPackageVariablesApi } from "../configuration-management/api/staging-package-variables-api"; import { fixConnectionVariables } from "../configuration-management/connection-variable.helper"; import { StudioService } from "./studio.service"; import { GitService } from "../../core/git-profile/git/git.service"; @@ -26,6 +27,7 @@ export class T2tcPackageService { private t2tcPackageApi: T2tcPackageApi; private variableApi: VariableApi; + private stagingPackageVariablesApi: StagingPackageVariablesApi; private studioPackageApi: PackageApi; private studioService: StudioService; @@ -35,6 +37,7 @@ export class T2tcPackageService { constructor(context: Context) { this.t2tcPackageApi = new T2tcPackageApi(context); this.variableApi = new VariableApi(context); + this.stagingPackageVariablesApi = new StagingPackageVariablesApi(context); this.studioPackageApi = new PackageApi(context); this.studioService = new StudioService(context); @@ -72,9 +75,11 @@ export class T2tcPackageService { }); } - public async batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise { + public async batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean, staging: boolean = false): Promise { let exportedPackagesData: Buffer; - if (packageKeys) { + if (staging) { + exportedPackagesData = await this.t2tcPackageApi.exportStagingPackages(packageKeys, withDependencies); + } else if (packageKeys) { exportedPackagesData = await this.t2tcPackageApi.exportPackages(packageKeys, withDependencies); } else { exportedPackagesData = await this.t2tcPackageApi.exportPackagesByVersions(packageKeysByVersion, withDependencies); @@ -184,10 +189,15 @@ export class T2tcPackageService { return versionsByPackageKey; } - private getVersionedVariablesForPackagesWithKeys(versionsByPackageKey: Map): Promise { + private async getVersionedVariablesForPackagesWithKeys(versionsByPackageKey: Map): Promise { const variableExportRequest: PackageKeyAndVersionPair[] = []; + const stagingPackageKeys: string[] = []; versionsByPackageKey?.forEach((versions, key) => { versions?.forEach(version => { + if (version === BatchExportImportConstants.STAGING_VERSION) { + stagingPackageKeys.push(key); + return; + } variableExportRequest.push({ packageKey: key, version: version, @@ -195,7 +205,21 @@ export class T2tcPackageService { }) }); - return this.variableApi.findVariablesWithValuesByPackageKeysAndVersion(variableExportRequest) + const versionedVariables = await this.variableApi.findVariablesWithValuesByPackageKeysAndVersion(variableExportRequest); + const stagingVariables = stagingPackageKeys.length + ? await this.getStagingVariablesForPackagesWithKeys(stagingPackageKeys) + : []; + + return [...versionedVariables, ...stagingVariables]; + } + + private async getStagingVariablesForPackagesWithKeys(packageKeys: string[]): Promise { + const stagingVariables = await this.stagingPackageVariablesApi.findAllByPackageKeys(packageKeys); + return stagingVariables.map(stagingVariable => ({ + packageKey: stagingVariable.packageKey, + version: BatchExportImportConstants.STAGING_VERSION, + variables: stagingVariable.variables ?? [], + })); } private buildBodyForImport(configs: AdmZip, sourcePath: string, variablesManifests: VariableManifestTransport[]): FormData { diff --git a/tests/commands/t2tc/module.spec.ts b/tests/commands/t2tc/module.spec.ts index 270adfdb..bca5a738 100644 --- a/tests/commands/t2tc/module.spec.ts +++ b/tests/commands/t2tc/module.spec.ts @@ -154,6 +154,39 @@ describe("T2TC Module", () => { undefined, false, undefined, + true, + undefined + ); + }); + + it("throws when --staging is combined with --keysByVersion", async () => { + const options: OptionValues = { + keysByVersion: ["package1.1.0.0"], + staging: true, + }; + + await expect( + (module as any).batchExportPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is only supported with --packageKeys, not --keysByVersion."); + + expect(mockT2tcCommandService.batchExportPackages).not.toHaveBeenCalled(); + }); + + it("delegates to T2tcCommandService with staging enabled", async () => { + const options: OptionValues = { + packageKeys: ["project@my-branch"], + withDependencies: true, + staging: true, + }; + + await (module as any).batchExportPackages(testContext, mockCommand, options); + + expect(mockT2tcCommandService.batchExportPackages).toHaveBeenCalledWith( + ["project@my-branch"], + undefined, + true, + undefined, + undefined, true ); }); diff --git a/tests/commands/t2tc/t2tc-package-export.spec.ts b/tests/commands/t2tc/t2tc-package-export.spec.ts index 7cf81c0d..5c708ddf 100644 --- a/tests/commands/t2tc/t2tc-package-export.spec.ts +++ b/tests/commands/t2tc/t2tc-package-export.spec.ts @@ -1210,4 +1210,50 @@ describe("Config export", () => { expect(getDownloadedFileName()).not.toBeNull(); }) -}) \ No newline at end of file + it("Should export staging packages from the staging endpoint with staging variables", async () => { + const manifest: PackageManifestTransport[] = []; + manifest.push(ConfigUtils.buildManifestForKeyAndFlavorAndVersion("project@my-branch", "TEST", null, { + [BatchExportImportConstants.STAGING_VERSION]: [], + } as unknown as Map)); + manifest.push(ConfigUtils.buildManifestForKeyAndFlavorAndVersion("dependency-key", "TEST", "1.0.0", { + "1.0.0": [], + } as unknown as Map)); + const exportedPackagesZip = ConfigUtils.buildBatchExportZip(manifest, []); + + const stagingVariable = { + key: "staging-variable", + type: PackageManagerVariableType.PLAIN_TEXT, + value: "staging-value", + metadata: {}, + }; + const versionedVariables: VariableManifestTransport[] = [{ + packageKey: "dependency-key", + version: "1.0.0", + variables: [], + }]; + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/staging/packages/export/batch?packageKeys=project%40my-branch&withDependencies=true", exportedPackagesZip.toBuffer()); + mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/batch/variables-with-assignments", versionedVariables); + mockAxiosPost("https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys", [{ + packageKey: "project@my-branch", + variables: [stagingVariable], + }]); + + await new T2tcCommandService(testContext).batchExportPackages(["project@my-branch"], undefined, true, null, null, true); + + const versionedVariablesRequest = parse(mockedPostRequestBodyByUrl.get("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/batch/variables-with-assignments")); + expect(versionedVariablesRequest).toEqual([{packageKey: "dependency-key", version: "1.0.0"}]); + + const stagingVariablesRequest = parse(mockedPostRequestBodyByUrl.get("https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys")); + expect(stagingVariablesRequest).toEqual(["project@my-branch"]); + + const actualZip = new AdmZip(getDownloadedFileName()); + const exportedVariables: VariableManifestTransport[] = parse(actualZip.getEntry(BatchExportImportConstants.VARIABLES_FILE_NAME).getData().toString()); + expect(exportedVariables).toContainEqual({ + packageKey: "project@my-branch", + version: BatchExportImportConstants.STAGING_VERSION, + variables: [stagingVariable], + }); + expect(exportedVariables).toContainEqual(versionedVariables[0]); + }) +})