Skip to content
This repository was archived by the owner on Apr 13, 2020. It is now read-only.

Commit 603b9f0

Browse files
committed
Exit on invalid DNS user input
- `spk service create` and `spk ring create` now log an error and exit when any arguments are inputted which are not RFC1123 compliant. - The checks only occur when the input has been explicitly passed by the user (i.e. not `undefined` or `""`)
1 parent e7325df commit 603b9f0

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

src/commands/ring/create.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from "../../lib/bedrockYaml";
55
import * as fileUtils from "../../lib/fileutils";
66
import { createTempDir } from "../../lib/ioUtil";
7+
import * as dns from "../../lib/net/dns";
78
import { disableVerboseLogging, enableVerboseLogging } from "../../logger";
89
import { IBedrockFile } from "../../types";
910
import { checkDependencies, execute } from "./create";
@@ -60,9 +61,19 @@ describe("test execute function and logic", () => {
6061
expect(exitFn).toBeCalledTimes(1);
6162
expect(exitFn.mock.calls).toEqual([[1]]);
6263
});
64+
it("test execute function: invalid ring input", async () => {
65+
const exitFn = jest.fn();
66+
jest.spyOn(dns, "assertIsValid");
67+
await execute("-not!dns@compliant%", "someprojectpath", exitFn);
68+
expect(dns.assertIsValid).toHaveReturnedTimes(0); // should never return because it throws
69+
expect(exitFn).toBeCalledTimes(1);
70+
expect(exitFn.mock.calls).toEqual([[1]]);
71+
});
6372
it("test execute function: missing project path", async () => {
6473
const exitFn = jest.fn();
74+
jest.spyOn(dns, "assertIsValid");
6575
await execute("ring", "", exitFn);
76+
expect(dns.assertIsValid).toHaveReturnedTimes(1);
6677
expect(exitFn).toBeCalledTimes(1);
6778
expect(exitFn.mock.calls).toEqual([[1]]);
6879
});

src/commands/ring/create.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
PROJECT_INIT_DEPENDENCY_ERROR_MESSAGE
1111
} from "../../lib/constants";
1212
import { updateTriggerBranchesForServiceBuildAndUpdatePipeline } from "../../lib/fileutils";
13+
import * as dns from "../../lib/net/dns";
1314
import { hasValue } from "../../lib/validator";
1415
import { logger } from "../../logger";
1516
import { IBedrockFile, IBedrockFileInfo } from "../../types";
@@ -35,6 +36,7 @@ export const execute = async (
3536
try {
3637
logger.info(`Project path: ${projectPath}`);
3738

39+
dns.assertIsValid("<ring-name>", ringName);
3840
checkDependencies(projectPath, ringName);
3941

4042
// Add ring to bedrock.yaml

src/commands/service/create.test.ts

+68-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import fs from "fs";
2-
import path from "path";
1+
import * as fs from "fs";
2+
import * as path from "path";
33
import { promisify } from "util";
4-
import uuid from "uuid/v4";
4+
import uuid = require("uuid/v4");
55
import { Bedrock } from "../../config";
66
import * as config from "../../config";
77
import * as bedrockYaml from "../../lib/bedrockYaml";
88
import { DEFAULT_CONTENT as BedrockMockedContent } from "../../lib/bedrockYaml";
99
import { SERVICE_PIPELINE_FILENAME } from "../../lib/constants";
1010
import { checkoutCommitPushCreatePRLink } from "../../lib/gitutils";
1111
import { createTempDir, removeDir } from "../../lib/ioUtil";
12+
import * as dns from "../../lib/net/dns";
1213
import { deepClone } from "../../lib/util";
1314
import {
1415
disableVerboseLogging,
@@ -20,12 +21,14 @@ import {
2021
createTestMaintainersYaml
2122
} from "../../test/mockFactory";
2223
import {
24+
assertValidDnsInputs,
2325
createService,
2426
execute,
2527
fetchValues,
2628
ICommandValues,
2729
validateGitUrl
2830
} from "./create";
31+
2932
jest.mock("../../lib/gitutils");
3033

3134
beforeAll(() => {
@@ -130,7 +133,69 @@ describe("Test fetchValues function", () => {
130133
});
131134
});
132135

136+
describe("isValidDnsInputs", () => {
137+
test("valid inputs does not throw", () => {
138+
expect(() =>
139+
assertValidDnsInputs({
140+
displayName: "bar",
141+
k8sBackend: "my-service",
142+
pathPrefix: "service",
143+
pathPrefixMajorVersion: "v1"
144+
})
145+
).not.toThrow();
146+
});
147+
148+
test("invalid inputs throws", () => {
149+
expect(() =>
150+
assertValidDnsInputs({
151+
displayName: "-not_dns_compliant",
152+
k8sBackend: "",
153+
pathPrefix: "",
154+
pathPrefixMajorVersion: ""
155+
})
156+
).toThrow();
157+
158+
expect(() =>
159+
assertValidDnsInputs({
160+
displayName: "",
161+
k8sBackend: "-not_dns_compliant",
162+
pathPrefix: "",
163+
pathPrefixMajorVersion: ""
164+
})
165+
).toThrow();
166+
167+
expect(() =>
168+
assertValidDnsInputs({
169+
displayName: "",
170+
k8sBackend: "",
171+
pathPrefix: "-not_dns_compliant",
172+
pathPrefixMajorVersion: ""
173+
})
174+
).toThrow();
175+
176+
expect(() =>
177+
assertValidDnsInputs({
178+
displayName: "",
179+
k8sBackend: "",
180+
pathPrefix: "",
181+
pathPrefixMajorVersion: "-not_dns_compliant"
182+
})
183+
).toThrow();
184+
});
185+
});
186+
133187
describe("Test execute function", () => {
188+
it("Negative test: with non-dns compliant values", async () => {
189+
const exitFn = jest.fn();
190+
jest.spyOn(dns, "assertIsValid");
191+
await execute(
192+
"foo",
193+
{ ...getMockValues(), displayName: "-non_dns@compliant" },
194+
exitFn
195+
);
196+
expect(dns.assertIsValid).toHaveBeenCalledTimes(1);
197+
});
198+
134199
it("Negative test: without service name", async () => {
135200
const exitFn = jest.fn();
136201
await execute("", getMockValues(), exitFn);

src/commands/service/create.ts

+39-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
generateServiceBuildAndUpdatePipelineYaml
2222
} from "../../lib/fileutils";
2323
import { checkoutCommitPushCreatePRLink } from "../../lib/gitutils";
24+
import * as dns from "../../lib/net/dns";
2425
import { isPortNumberString } from "../../lib/validator";
2526
import { logger } from "../../logger";
2627
import { IBedrockFileInfo, IHelmConfig, IUser } from "../../types";
@@ -54,7 +55,7 @@ export interface ICommandValues extends ICommandOptions {
5455

5556
export const fetchValues = (opts: ICommandOptions) => {
5657
if (!isPortNumberString(opts.k8sBackendPort)) {
57-
throw new Error("value for --k8s-service-port is not a valid port number");
58+
throw Error("value for --k8s-service-port is not a valid port number");
5859
}
5960

6061
const bedrock = Bedrock();
@@ -97,9 +98,34 @@ export const fetchValues = (opts: ICommandOptions) => {
9798
export const checkDependencies = (projectPath: string) => {
9899
const fileInfo: IBedrockFileInfo = bedrockFileInfo(projectPath);
99100
if (fileInfo.exist === false) {
100-
throw new Error(PROJECT_INIT_CVG_DEPENDENCY_ERROR_MESSAGE);
101+
throw Error(PROJECT_INIT_CVG_DEPENDENCY_ERROR_MESSAGE);
101102
} else if (fileInfo.hasVariableGroups === false) {
102-
throw new Error(PROJECT_CVG_DEPENDENCY_ERROR_MESSAGE);
103+
throw Error(PROJECT_CVG_DEPENDENCY_ERROR_MESSAGE);
104+
}
105+
};
106+
107+
/**
108+
* Validate that the provided ICommandOpts contain RFC1123 compliant values if
109+
* they are were set by the user
110+
*
111+
* @throws {Error} when any of displayName, k8sBackend, pathPrefix,
112+
* pathPrefixMajorVersion are provided and are non-DNS compliant
113+
*/
114+
export const assertValidDnsInputs = (opts: Partial<ICommandOptions>): void => {
115+
if (opts.displayName) {
116+
dns.assertIsValid("--display-name", opts.displayName);
117+
}
118+
if (opts.k8sBackend) {
119+
dns.assertIsValid("--k8s-backend", opts.k8sBackend);
120+
}
121+
if (opts.pathPrefix) {
122+
dns.assertIsValid("--path-prefix", opts.pathPrefix);
123+
}
124+
if (opts.pathPrefixMajorVersion) {
125+
dns.assertIsValid(
126+
"--path-prefix-major-version",
127+
opts.pathPrefixMajorVersion
128+
);
103129
}
104130
};
105131

@@ -116,12 +142,20 @@ export const execute = async (
116142

117143
if (serviceName === "." && opts.displayName === "") {
118144
logger.error(
119-
`If specifying the current directory as service name, please incluce a display name using '-n'`
145+
`If specifying the current directory as service name, please include a display name using '-n'`
120146
);
121147
await exitFn(1);
122148
return;
123149
}
124150

151+
// validate user inputs are DNS compliant
152+
try {
153+
assertValidDnsInputs(opts);
154+
} catch (err) {
155+
logger.error(err);
156+
await exitFn(1);
157+
}
158+
125159
// Sanity checking the specified Helm URLs
126160
await validateGitUrl(opts.helmConfigGit, exitFn);
127161

@@ -242,7 +276,7 @@ export const createService = async (
242276
logger.error(
243277
`Cannot create service pipeline due to serviceName being '.'. Please include a displayName if you are trying to create a service in your project root directory.`
244278
);
245-
throw new Error(
279+
throw Error(
246280
"Cannot create service pipeline due to serviceName being '.'. Please include a displayName if you are trying to create a service in your project root directory."
247281
);
248282
}

src/lib/net/dns.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function assertIsValid(
5555
): asserts dns is string {
5656
if (!isValid(dns)) {
5757
throw Error(
58-
`Invalid ${fieldName} '${dns}' provided for Traefik IngressRoute. Must be RFC1123 compliant and match regex: ${validDnsRegex}`
58+
`Invalid ${fieldName} '${dns}' provided. Must be RFC1123 compliant and match regex: ${validDnsRegex}`
5959
);
6060
}
6161
}

0 commit comments

Comments
 (0)