diff --git a/docs/commands/data.json b/docs/commands/data.json index b69f2336e..d85b22f7a 100644 --- a/docs/commands/data.json +++ b/docs/commands/data.json @@ -639,6 +639,19 @@ ], "markdown": "## Description\n\nAdd a new service into this initialized spk project repository.\n\n## Example\n\n```bash\nspk service create my-service . \\\n --display-name $app_name \\\n --helm-config-path $path_to_chart_in_repo \\\n --helm-config-git $helm_repo_url \\ # Needs to start with https and not contain user name\n --helm-config-branch master \\\n --helm-chart-access-token-variable $ENV_VAR_NAME\n```\n\n## Note\n\n- `--helm-chart-*` and `--helm-config-*` settings are mutually-exclusive. **You\n may only use one.**\n - If the git repository referenced in `--helm-config-git` is a private\n repository, you can specify an environment variable in your\n HLD-to-Materialized pipeline containing your a PAT to authenticate with via\n the `--helm-chart-access-token-variable` option.\n- `--middlewares`, `--k8s-backend-port`, `--path-prefix`,\n `--path-prefix-major-version`, and `--k8s-backend` are all used to configure\n the generated Traefik2 IngressRoutes. i.e.\n\n ```sh\n spk service create my-example-documents-service path/to/my/service \\\n --middlewares middleware \\\n --k8s-backend-port 3001 \\\n --k8s-backend docs-service \\\n --path-prefix documents \\\n --path-prefix-major-version v2\n ```\n\n will result in an IngressRoute that looks like:\n\n ```yaml\n apiVersion: traefik.containo.us/v1alpha1\n kind: IngressRoute\n metadata:\n name: my-example-documents-service-master\n spec:\n routes:\n - kind: Rule\n match: \"PathPrefix(`/v2/documents`) && Headers(`Ring`, `master`)\"\n middlewares:\n - name: my-example-documents-service-master\n - name: middlewareA\n services:\n - name: docs-service\n port: 3001\n ```\n" }, + "service get-display-name": { + "command": "get-display-name", + "alias": "gdn", + "description": "Gets display name for a service", + "options": [ + { + "arg": "-p, --path ", + "description": "Path to the service folder for which display name is to be extracted", + "required": true + } + ], + "markdown": "## Description\n\nGets display name of a service based on the provided path by extracting this\ninformation from bedrock.yaml. This command tries to locate bedrock.yaml in the\ncurrent directory.\n\nIf bedrock.yaml is not found in current directory, the command will fail to\nextract display name.\n\nIf the specified path is not found in any services listed in bedrock.yaml, the\ndisplay name will not be extracted. Make sure that specified path matches the\npath in bedrock.yaml exactly.\n" + }, "service install-build-pipeline": { "command": "install-build-pipeline ", "alias": "p", diff --git a/src/commands/service/get-display-name.decorator.json b/src/commands/service/get-display-name.decorator.json new file mode 100644 index 000000000..cfa735585 --- /dev/null +++ b/src/commands/service/get-display-name.decorator.json @@ -0,0 +1,12 @@ +{ + "command": "get-display-name", + "alias": "gdn", + "description": "Gets display name for a service", + "options": [ + { + "arg": "-p, --path ", + "description": "Path to the service folder for which display name is to be extracted", + "required": true + } + ] +} diff --git a/src/commands/service/get-display-name.md b/src/commands/service/get-display-name.md new file mode 100644 index 000000000..eeaeae0fc --- /dev/null +++ b/src/commands/service/get-display-name.md @@ -0,0 +1,12 @@ +## Description + +Gets display name of a service based on the provided path by extracting this +information from bedrock.yaml. This command tries to locate bedrock.yaml in the +current directory. + +If bedrock.yaml is not found in current directory, the command will fail to +extract display name. + +If the specified path is not found in any services listed in bedrock.yaml, the +display name will not be extracted. Make sure that specified path matches the +path in bedrock.yaml exactly. diff --git a/src/commands/service/get-display-name.test.ts b/src/commands/service/get-display-name.test.ts new file mode 100644 index 000000000..8bb471a59 --- /dev/null +++ b/src/commands/service/get-display-name.test.ts @@ -0,0 +1,47 @@ +import { disableVerboseLogging, enableVerboseLogging } from "../../logger"; +import { execute } from "./get-display-name"; +import * as fs from "fs"; +import { createTestBedrockYaml } from "../../test/mockFactory"; +import { BedrockFile } from "../../types"; +import * as bedrockYaml from "../../lib/bedrockYaml"; + +beforeAll(() => { + enableVerboseLogging(); +}); + +afterAll(() => { + disableVerboseLogging(); +}); + +describe("get display name", () => { + it("positive test", async () => { + const exitFn = jest.fn(); + const defaultBedrockFileObject = createTestBedrockYaml( + false + ) as BedrockFile; + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(bedrockYaml, "read").mockReturnValue(defaultBedrockFileObject); + jest.spyOn(process, "cwd").mockReturnValue("bedrock.yaml/"); + const consoleSpy = jest.spyOn(console, "log"); + execute({ path: "./packages/service1" }, exitFn); + expect(consoleSpy).toHaveBeenCalledWith("service1"); + expect(exitFn).toBeCalledTimes(1); + expect(exitFn).toBeCalledWith(0); + }); + it("negative test", async () => { + const exitFn = jest.fn(); + execute({ path: "" }, exitFn); + expect(exitFn).toBeCalledTimes(1); + execute({ path: undefined }, exitFn); + expect(exitFn).toBeCalledWith(1); + const defaultBedrockFileObject = createTestBedrockYaml( + false + ) as BedrockFile; + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(bedrockYaml, "read").mockReturnValue(defaultBedrockFileObject); + jest.spyOn(process, "cwd").mockReturnValue("bedrock.yaml/"); + execute({ path: "./packages/service" }, exitFn); // should not exist + expect(exitFn).toBeCalledTimes(3); + expect(exitFn).toBeCalledWith(1); + }); +}); diff --git a/src/commands/service/get-display-name.ts b/src/commands/service/get-display-name.ts new file mode 100644 index 000000000..3ae8645bb --- /dev/null +++ b/src/commands/service/get-display-name.ts @@ -0,0 +1,75 @@ +import commander from "commander"; +import { read as readBedrockYaml } from "../../lib/bedrockYaml"; +import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder"; +import { logger } from "../../logger"; +import decorator from "./get-display-name.decorator.json"; +import { build as buildError, log as logError } from "../../lib/errorBuilder"; +import { errorStatusCode } from "../../lib/errorStatusCode"; + +export interface CommandOptions { + path: string | undefined; +} + +/** + * Executes the command, can all exit function with 0 or 1 + * when command completed successfully or failed respectively. + * + * @param opts validated option values + * @param exitFn exit function + */ +export const execute = async ( + opts: CommandOptions, + exitFn: (status: number) => Promise +): Promise => { + // The assumption is that this command should be ran from the directory where bedrock.yaml exists + try { + if (!opts.path) { + throw buildError( + errorStatusCode.VALIDATION_ERR, + "service-get-display-name-path-missing-param-err" + ); + } + const bedrockFile = readBedrockYaml(process.cwd()); + if (!bedrockFile) { + throw buildError( + errorStatusCode.FILE_IO_ERR, + "service-get-display-name-bedrock-yaml-missing-err" + ); + } + + const serviceIndex = Object.keys(bedrockFile.services).find( + (index) => opts.path === bedrockFile.services[+index].path + ); + + if (serviceIndex) { + console.log(bedrockFile.services[+serviceIndex].displayName); + await exitFn(0); + } + + throw buildError(errorStatusCode.ENV_SETTING_ERR, { + errorKey: "service-get-display-name-err", + values: [opts.path], + }); + } catch (err) { + logError( + buildError( + errorStatusCode.VALIDATION_ERR, + "service-get-display-name-generic-err", + err + ) + ); + await exitFn(1); + } +}; + +/** + * Adds the get-display-name command to the commander command object + * @param command Commander command object to decorate + */ +export const commandDecorator = (command: commander.Command): void => { + buildCmd(command, decorator).action(async (opts: CommandOptions) => { + await execute(opts, async (status: number) => { + await exitCmd(logger, process.exit, status); + }); + }); +}; diff --git a/src/commands/service/index.ts b/src/commands/service/index.ts index 226b44536..3d3de297d 100644 --- a/src/commands/service/index.ts +++ b/src/commands/service/index.ts @@ -1,6 +1,11 @@ import { Command } from "../command"; -const subfolders = ["create", "create-revision", "pipeline"]; +const subfolders = [ + "create", + "create-revision", + "pipeline", + "get-display-name", +]; export const commandDecorator = Command( "service", diff --git a/src/lib/i18n.json b/src/lib/i18n.json index 8df238751..98928ba61 100644 --- a/src/lib/i18n.json +++ b/src/lib/i18n.json @@ -159,6 +159,10 @@ "introspect-dashboard-cmd-launch-pre-req-err": "Requirements to launch dashboard were not met.", "introspect-dashboard-cmd-launch-err": "Could not launch dashboard docker container.", + "service-get-display-name-path-missing-param-err": "Value for path parameter was missing. This is required for running get-display-command. Provide it.", + "service-get-display-name-bedrock-yaml-missing-err": "Could not find bedrock.yaml in current directory. Make sure to run this from a directory that contains bedrock.yaml.", + "service-get-display-name-err": "Could not find a service for path {0}. Make sure that the specified path is valid.", + "service-get-display-name-generic-err": "Error occurred while getting display name.", "az-cli-login-err": "Could not login through azure cli.", "az-cli-create-sp-err": "Could not create service principal with azure cli", diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 6469a4602..aa0a883ab 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -291,16 +291,19 @@ export const createTestBedrockYaml = ( path: "./", helm: service1HelmConfig, k8sBackendPort: 80, + displayName: "root", }, { path: "./packages/service1", helm: service2HelmConfig, k8sBackendPort: 80, + displayName: "service1", }, { path: "./zookeeper", helm: zookeeperHelmConfig, k8sBackendPort: 80, + displayName: "zookeeper", }, ], variableGroups: [],