Skip to content
Draft
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
16 changes: 13 additions & 3 deletions docs/user-guide/config-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,17 @@ info: Config import report file: 9560f81f-f746-4117-83ee-dd1f614ad624.json

### Listing Package Variables

Package variables (with assignments) can be listed with the following command:
Variables can be read for **published package versions** (each package identified with a version) or for the **unpublished** configuration of packages (identified by package key only). Use either the published flow (`--keysByVersion` / `--keysByVersionFile`) or the unpublished flow (`--packageKeys`); combining them is not supported and the command will fail.

**Output (console and `--json`).** For both flows, each package is represented the same way: `packageKey`, `variables` (definitions and values), and—**only for published versions**—`version`. Without `--json`, each package is printed as one JSON object per line. With `--json`, the result is written to a file as a **JSON array** of those objects. For unpublished packages, `version` is simply omitted.

**Published versions** — `config variables list` with `--keysByVersion` or `--keysByVersionFile`:

```bash
content-cli config variables list -p <sourceProfile> --keysByVersion key1:version1 ... keyN:versionN
```

The --keysByVersion option should specify a list of key :(colon) version pairs. Alternatively, a json file path containing a list of key and version pairs can be used. The PackageKeyAndVersionPair for the file should have the following form:
The `--keysByVersion` option should specify a list of `key:version` pairs. Alternatively, pass a JSON file path with `--keysByVersionFile`. Each entry in the file should match:

```typescript
export interface PackageKeyAndVersionPair {
Expand All @@ -162,7 +166,13 @@ export interface PackageKeyAndVersionPair {
}
```

Similar to the other listing commands, the --json option can be used for exporting (saving) the result as a json file.
**Unpublished configuration** — `config variables list` with `--packageKeys`:

```bash
content-cli config variables list -p <profile> --packageKeys <packageKey> [<packageKey> ...]
```

Use `--json` with either flow to export the array to a file (see **Output** above).

### Listing Assignments

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Context } from "../../../core/command/cli-context";
import { FatalError } from "../../../core/utils/logger";
import { HttpClient } from "../../../core/http/http-client";
import { StagingVariableManifestTransport } from "../interfaces/package-export.interfaces";

export class StagingPackageVariablesApi {
private httpClient: () => HttpClient;

Check warning on line 7 in src/commands/configuration-management/api/staging-package-variables-api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'httpClient' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ0fJbAUTTMrv3kBlzDr&open=AZ0fJbAUTTMrv3kBlzDr&pullRequest=325

constructor(context: Context) {
this.httpClient = () => context.httpClient;
}

public async findAllByPackageKeys(packageKeys: string[]): Promise<StagingVariableManifestTransport[]> {
const path = `/pacman/api/core/staging/packages/variables/by-package-keys`;
return await this.httpClient()
.post(path, packageKeys)
.catch(e => {
throw new FatalError(`Problem listing staging variables for packages: ${e}`);
});
}
}
15 changes: 13 additions & 2 deletions src/commands/configuration-management/config-command.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,19 @@ export class ConfigCommandService {
}
}

public async listVariables(jsonResponse: boolean, keysByVersion: string[], keysByVersionFile: string): Promise<void> {
if (jsonResponse) {
public async listVariables(
jsonResponse: boolean,
keysByVersion: string[],
keysByVersionFile: string,
packageKeys: string[]
): Promise<void> {
if (packageKeys.length > 0) {
if (jsonResponse) {
await this.variableService.exportStagingVariables(packageKeys);
} else {
await this.variableService.listStagingVariables(packageKeys);
}
} else if (jsonResponse) {
await this.variableService.exportVariables(keysByVersion, keysByVersionFile);
} else {
await this.variableService.listVariables(keysByVersion, keysByVersionFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export interface VariableManifestTransport {
variables?: VariableExportTransport[];
}

export interface StagingVariableManifestTransport {
packageKey: string;
variables?: VariableExportTransport[];
}

export interface PackageKeyAndVersionPair {
packageKey: string;
version: string;
Expand Down
27 changes: 24 additions & 3 deletions src/commands/configuration-management/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ class Module extends IModule {
.description("Commands related to variable configs");

variablesCommand.command("list")
.description("Command to list versioned variables of packages")
.description("List package variables: use --packageKeys for staging (unpublished), or --keysByVersion / --keysByVersionFile for versioned packages")
.option("--json", "Return response as json type", "")
.option("--keysByVersion <keysByVersion...>", "Mapping of package keys and versions", "")
.option("--packageKeys <packageKeys...>", "Package keys (staging variables only; mutually exclusive with versioned options)", [])
.option("--keysByVersion <keysByVersion...>", "Mapping of package keys and versions", [])
.option("--keysByVersionFile <keysByVersionFile>", "Package keys by version mappings file path.", "")
.action(this.listVariables);

Expand Down Expand Up @@ -203,7 +204,27 @@ class Module extends IModule {
}

private async listVariables(context: Context, command: Command, options: OptionValues): Promise<void> {
await new ConfigCommandService(context).listVariables(options.json, options.keysByVersion, options.keysByVersionFile);
const hasStagingKeys = options.packageKeys.length > 0;
const hasVersioned =
options.keysByVersion.length > 0 || options.keysByVersionFile !== "";

if (hasStagingKeys && hasVersioned) {
throw new Error(
"Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both."
);
}
if (!hasStagingKeys && !hasVersioned) {
throw new Error(
"Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned packages."
);
}

await new ConfigCommandService(context).listVariables(
options.json,
options.keysByVersion,
options.keysByVersionFile,
options.packageKeys
);
}

private async listAssignments(context: Context, command: Command, options: OptionValues): Promise<void> {
Expand Down
27 changes: 26 additions & 1 deletion src/commands/configuration-management/variable.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
import { FatalError, logger } from "../../core/utils/logger";
import { StudioService } from "./studio.service";
import { FileService, fileService } from "../../core/utils/file-service";
import { PackageKeyAndVersionPair, VariableManifestTransport } from "./interfaces/package-export.interfaces";
import { PackageKeyAndVersionPair, StagingVariableManifestTransport, VariableManifestTransport } from "./interfaces/package-export.interfaces";
import { BatchImportExportApi } from "./api/batch-import-export-api";
import { URLSearchParams } from "url";
import { VariableAssignmentCandidatesApi } from "./api/variable-assignment-candidates-api";
import { StagingPackageVariablesApi } from "./api/staging-package-variables-api";

export class VariableService {

private batchImportExportApi: BatchImportExportApi;
private variableAssignmentCandidatesApi: VariableAssignmentCandidatesApi;
private stagingPackageVariablesApi: StagingPackageVariablesApi;

Check warning on line 16 in src/commands/configuration-management/variable.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'stagingPackageVariablesApi' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ0fJbDITTMrv3kBlzDs&open=AZ0fJbDITTMrv3kBlzDs&pullRequest=325
private studioService: StudioService;

constructor(context: Context) {
this.batchImportExportApi = new BatchImportExportApi(context);
this.variableAssignmentCandidatesApi = new VariableAssignmentCandidatesApi(context);
this.stagingPackageVariablesApi = new StagingPackageVariablesApi(context);
this.studioService = new StudioService(context);
}

Expand Down Expand Up @@ -50,6 +53,28 @@
this.exportToJson(variableManifests);
}

public async listStagingVariables(packageKeys: string[]): Promise<void> {
const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys);
byPackage.forEach(entry => {
logger.info(JSON.stringify(entry));
});
}

public async exportStagingVariables(packageKeys: string[]): Promise<void> {
const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys);
this.exportToJson(byPackage);
}

private async fetchStagingVariablesByPackageKeys(
packageKeys: string[]
): Promise<StagingVariableManifestTransport[]> {
if (packageKeys.length === 0) {
throw new FatalError("Please provide at least one package key!");
}

return await this.stagingPackageVariablesApi.findAllByPackageKeys(packageKeys);
}

private async getVersionedVariablesByKeyVersionPairs(keysByVersion: string[], keysByVersionFile: string): Promise<VariableManifestTransport[]> {
const variablesExportRequest: PackageKeyAndVersionPair[] = await this.buildKeyVersionPairs(keysByVersion, keysByVersionFile);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as fs from "fs";
import { parse } from "../../../src/core/utils/json";
import {
PackageKeyAndVersionPair,
StagingVariableManifestTransport,
VariableExportTransport,
VariableManifestTransport,
} from "../../../src/commands/configuration-management/interfaces/package-export.interfaces";
import { PackageManagerVariableType } from "../../../src/commands/studio/interfaces/package-manager.interfaces";
Expand Down Expand Up @@ -112,7 +114,7 @@ describe("Config listVariables", () => {
})

it("Should list fixed variables for non-json response", async () => {
await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], null);
await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", []);

expect(loggingTestTransport.logMessages.length).toBe(3);
expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0]));
Expand All @@ -124,7 +126,7 @@ describe("Config listVariables", () => {
})

it("Should export fixed variables for json response", async () => {
await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], null);
await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", []);

expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage);
Expand All @@ -140,7 +142,7 @@ describe("Config listVariables", () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs));

await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json");
await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json", []);

expect(loggingTestTransport.logMessages.length).toBe(3);
expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0]));
Expand All @@ -155,7 +157,7 @@ describe("Config listVariables", () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs));

await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json");
await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json", []);

expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage);
Expand All @@ -169,9 +171,66 @@ describe("Config listVariables", () => {

it("Should throw error if no mapping and no file path is provided", async () => {
try {
await new ConfigCommandService(testContext).listVariables(true, [], "");
await new ConfigCommandService(testContext).listVariables(true, [], "", []);
} catch (e) {
expect(e.message).toEqual("Please provide keysByVersion mappings or file path!");
}
})
})

describe("staging variables via --packageKeys", () => {
const stagingVariablesByPackageKeysBaseUrl =
`${testContext.profile.team.replace(/\/$/, "")}/pacman/api/core/staging/packages/variables/by-package-keys`;

const stagingVarsPkgA: VariableExportTransport[] = [
{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1", metadata: {} },
{ key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" }, metadata: {} },
];
const stagingVarsPkgB: VariableExportTransport[] = [
{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2", metadata: {} },
];

const batchResponse: StagingVariableManifestTransport[] = [
{ packageKey: "pkg-a", variables: stagingVarsPkgA },
{ packageKey: "pkg-b", variables: stagingVarsPkgB },
];

const expectedPackageKeys = ["pkg-a", "pkg-b"];

it("Should list staging variables for non-json response", async () => {
const url = stagingVariablesByPackageKeysBaseUrl;
mockAxiosPost(url, batchResponse);

await new ConfigCommandService(testContext).listVariables(false, [], "", expectedPackageKeys);

expect(loggingTestTransport.logMessages.length).toBe(2);
expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0]));
expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1]));

const postBody = parse<string[]>(mockedPostRequestBodyByUrl.get(url));
expect(postBody).toEqual(expectedPackageKeys);
});

it("Should export staging variables for json response", async () => {
const pkgAOnlyResponse: StagingVariableManifestTransport[] = [
{ packageKey: "pkg-a", variables: stagingVarsPkgA },
];
const url = stagingVariablesByPackageKeysBaseUrl;
mockAxiosPost(url, pkgAOnlyResponse);

await new ConfigCommandService(testContext).listVariables(true, [], "", ["pkg-a"]);

expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage);

const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1];
expect(mockWriteFileSync).toHaveBeenCalledWith(
path.resolve(process.cwd(), expectedFileName),
JSON.stringify(pkgAOnlyResponse),
{encoding: "utf-8"}
);

const postBody = parse<string[]>(mockedPostRequestBodyByUrl.get(url));
expect(postBody).toEqual(["pkg-a"]);
});
});
});
Loading
Loading