Skip to content

Commit 48eade6

Browse files
authored
Merge pull request #971 from scramjetorg/feat/cli-seq-start-cfg
Add option to provide file with arguments to start/deploy sequence
2 parents a931201 + 62960c8 commit 48eade6

File tree

5 files changed

+138
-49
lines changed

5 files changed

+138
-49
lines changed

packages/cli/src/lib/commands/sequence.ts

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-len */
22
import { CommandDefinition } from "../../types";
3-
import { createWriteStream, lstatSync } from "fs";
3+
import { createWriteStream, lstatSync, readFileSync } from "fs";
44
import { displayEntity, displayError, displayMessage, displayObject } from "../output";
55
import { getHostClient } from "../common";
66
import { getSequenceId, profileManager, sessionConfig } from "../config";
@@ -10,9 +10,12 @@ import { PassThrough, Writable } from "stream";
1010
import { isDevelopment } from "../../utils/envs";
1111

1212
import { resolve } from "path";
13-
import { sequenceDelete, sequencePack, sequenceParseArgs, sequenceSendPackage, sequenceStart } from "../helpers/sequence";
13+
import { sequenceDelete, sequencePack, sequenceParseArgs, sequenceParseConfig, sequenceSendPackage, sequenceStart } from "../helpers/sequence";
1414
import { ClientError } from "@scramjet/client-utils";
1515
import { initPlatform } from "../platform";
16+
import { AppConfig, DeepPartial } from "@scramjet/types";
17+
import { isStartSequenceEndpointPayloadDTO, merge } from "@scramjet/utility";
18+
import { SequenceDeployArgs, SequenceStartCLIArgs } from "../../types/params";
1619

1720
/**
1821
* Initializes `sequence` command.
@@ -108,6 +111,28 @@ export const sequence: CommandDefinition = (program) => {
108111
}
109112
);
110113

114+
function validateStartupConfig(config: DeepPartial<SequenceDeployArgs>) {
115+
return isStartSequenceEndpointPayloadDTO(config);
116+
}
117+
118+
function loadStartupConfig(filename: string): DeepPartial<SequenceDeployArgs> {
119+
if (!filename) return {};
120+
121+
let json = {};
122+
123+
try {
124+
const data = readFileSync(filename, "utf8");
125+
126+
json = JSON.parse(data);
127+
} catch (e) {
128+
// eslint-disable-next-line no-console
129+
console.error(e);
130+
process.exit(1);
131+
}
132+
133+
return json;
134+
}
135+
111136
sequenceCmd
112137
.command("start")
113138
.argument("<id>", "Sequence id to start or '-' for the last uploaded")
@@ -119,28 +144,40 @@ export const sequence: CommandDefinition = (program) => {
119144
.option("--output-topic <string>", "Topic to which the output stream should be routed")
120145
.option("--input-topic <string>", "Topic to which the input stream should be routed")
121146
.option("--args <json-string>", "Arguments to be passed to the first function in the Sequence")
147+
.option("--startup-config <path-to-config>", "Path to startup config", loadStartupConfig)
122148
.option("--limits <json-string>", "Instance limits")
123149
.description("Start the Sequence with or without given arguments")
124-
.action(async (id, { configFile, configString, outputTopic, inputTopic, args: argsStr, limits: limitsStr, instId }) => {
125-
let args;
126-
127-
if (argsStr) args = sequenceParseArgs(argsStr);
150+
.action(async (id, { startupConfig, configFile, configString, outputTopic, inputTopic, args: argsStr, limits: limitsStr, instId: instanceId }: SequenceStartCLIArgs) => {
151+
const args = argsStr ? sequenceParseArgs(argsStr) : undefined;
152+
const appConfig = await sequenceParseConfig(configFile, configString);
128153
const limits = limitsStr ? JSON.parse(limitsStr) : {};
129154

130-
const instanceClient = await sequenceStart(
131-
id, { configFile, configString, args, outputTopic, inputTopic, limits, instId });
155+
startupConfig ||= {};
156+
merge(startupConfig, {
157+
appConfig,
158+
args,
159+
instanceId,
160+
inputTopic,
161+
outputTopic,
162+
limits
163+
});
164+
165+
if (!validateStartupConfig(startupConfig)) {
166+
throw new Error("Invalid startup config",);
167+
}
168+
169+
const instanceClient = await sequenceStart(id, {
170+
appConfig: startupConfig.appConfig as AppConfig,
171+
args: startupConfig.args,
172+
limits: startupConfig.limits,
173+
instanceId: startupConfig.instanceId,
174+
outputTopic: startupConfig.outputTopic,
175+
inputTopic: startupConfig.inputTopic
176+
});
132177

133178
displayObject(instanceClient, profileManager.getProfileConfig().format);
134179
});
135180

136-
type DeployArgs = {
137-
output: string;
138-
configFile: any;
139-
configString: string;
140-
instId?: string;
141-
args?: string;
142-
};
143-
144181
sequenceCmd
145182
.command("deploy")
146183
.alias("run")
@@ -150,38 +187,64 @@ export const sequence: CommandDefinition = (program) => {
150187
.option("-s, --config-string <json-string>", "Configuration in JSON format to be passed to the Instance context")
151188
.option("--inst-id <string>", "Start Sequence with a custom Instance Id. Should consist of 36 characters")
152189
// TODO: check if output-topic and input-topic should be added after development
190+
.option("--output-topic <string>", "Topic to which the output stream should be routed")
191+
.option("--input-topic <string>", "Topic to which the input stream should be routed")
153192
.option("--args <json-string>", "Arguments to be passed to the first function in the Sequence")
193+
.option("--startup-config <path-to-config>", "Path to startup config", loadStartupConfig)
194+
.option("--limits <json-string>", "Instance limits")
154195
.description("Pack (if needed), send and start the Sequence")
155-
.action(async (path: string, { output: fileoutput, configFile, configString, args: argsStr, instId }: DeployArgs) => {
156-
let args;
196+
.action(async (path: string, { startupConfig, output, configFile, configString, outputTopic, inputTopic, args: argsStr, limits: limitsStr, instId }: SequenceStartCLIArgs) => {
197+
const args = argsStr ? sequenceParseArgs(argsStr) : undefined;
198+
const appConfig = await sequenceParseConfig(configFile, configString);
199+
const limits = limitsStr ? JSON.parse(limitsStr) : {};
157200

158-
if (argsStr) args = sequenceParseArgs(argsStr);
201+
startupConfig ||= {};
202+
merge(startupConfig, {
203+
output,
204+
appConfig,
205+
args,
206+
instanceId: instId,
207+
inputTopic,
208+
outputTopic,
209+
limits
210+
});
159211

160-
const output = new PassThrough();
212+
if (!validateStartupConfig(startupConfig)) {
213+
throw new Error("Invalid startup config",);
214+
}
161215

162-
if (fileoutput) {
163-
const outputPath = fileoutput ? resolve(fileoutput) : `${resolve(path)}.tar.gz`;
216+
const compressedPackageStream = new PassThrough();
164217

165-
output.pipe(createWriteStream(outputPath));
218+
if (startupConfig.output) {
219+
const outputPath = startupConfig.output ? resolve(startupConfig.output) : `${resolve(path)}.tar.gz`;
220+
221+
compressedPackageStream.pipe(createWriteStream(outputPath));
166222
sessionConfig.setLastPackagePath(outputPath);
167223
}
168224
const format = profileManager.getProfileConfig().format;
169225

170226
if (lstatSync(path).isDirectory()) {
171227
// eslint-disable-next-line @typescript-eslint/no-floating-promises
172-
const sendSeqPromise = getHostClient().sendSequence(output).then(seq => {
228+
const sendSeqPromise = getHostClient().sendSequence(compressedPackageStream).then(seq => {
173229
sessionConfig.setLastSequenceId(seq.id);
174230
});
175231

176-
await sequencePack(path, { output });
232+
await sequencePack(path, { output: compressedPackageStream });
177233
await sendSeqPromise;
178234
} else {
179235
const sequenceClient = await sequenceSendPackage(path, {}, false, { progress: sequenceCmd.parent?.getOptionValue("progress") });
180236

181237
displayObject(sequenceClient, profileManager.getProfileConfig().format);
182238
}
183239

184-
const instanceClient = await sequenceStart("-", { configFile, configString, args, instId });
240+
const instanceClient = await sequenceStart("-", {
241+
appConfig: startupConfig.appConfig as AppConfig,
242+
args: startupConfig.args,
243+
limits: startupConfig.limits,
244+
instanceId: startupConfig.instanceId,
245+
outputTopic: startupConfig.outputTopic,
246+
inputTopic: startupConfig.inputTopic
247+
});
185248

186249
displayObject(instanceClient, format);
187250
});

packages/cli/src/lib/helpers/sequence.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
import { GetSequenceResponse } from "@scramjet/types/src/rest-api-sth";
3-
import { InstanceLimits } from "@scramjet/types";
3+
import { AppConfig, InstanceLimits } from "@scramjet/types";
44
import { constants, createReadStream, createWriteStream, PathLike } from "fs";
55
import { readFile, readdir, access, lstat } from "fs/promises";
66
import { InstanceClient, SequenceClient } from "@scramjet/api-client";
@@ -148,35 +148,39 @@ export const sequenceSendPackage = async (
148148
}
149149
};
150150

151-
export const sequenceStart = async (
152-
id: string, { configFile, configString, args, outputTopic, inputTopic, limits, instId }:
153-
{
154-
configFile: any,
155-
configString: string,
156-
args?: any[],
157-
outputTopic?: string,
158-
inputTopic?: string,
159-
limits?: InstanceLimits,
160-
instId?: string
161-
}
162-
): Promise<InstanceClient> => {
151+
export const sequenceParseConfig = async (configFile: string = "", configString: string = ""): Promise<AppConfig> => {
163152
if (configFile && configString) {
164153
return Promise.reject(new Error("Provide one source of configuration"));
165154
}
166155

167-
let appConfig = {};
156+
let appConfig;
168157

169158
try {
170159
if (configString) appConfig = JSON.parse(configString);
171160
if (configFile) appConfig = JSON.parse(await readFile(configFile, "utf-8"));
172161
} catch (_) {
173162
return Promise.reject(new Error("Unable to read configuration"));
174163
}
164+
165+
return appConfig;
166+
};
167+
168+
export const sequenceStart = async (
169+
id: string, { appConfig, args, outputTopic, inputTopic, limits, instanceId }:
170+
{
171+
appConfig: AppConfig,
172+
args?: any[],
173+
outputTopic?: string,
174+
inputTopic?: string,
175+
limits?: InstanceLimits,
176+
instanceId?: string
177+
}
178+
): Promise<InstanceClient> => {
175179
const sequenceClient = SequenceClient.from(getSequenceId(id), getHostClient());
176180

177181
try {
178182
const instance = await sequenceClient.start({
179-
appConfig, args, outputTopic, inputTopic, limits, instanceId: instId
183+
appConfig, args: args?.length ? args : undefined, outputTopic, inputTopic, limits, instanceId
180184
});
181185

182186
sessionConfig.setLastInstanceId(instance.id);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Arguments types
3+
*/
4+
5+
export * from "./sequence";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DeepPartial, STHRestAPI } from "@scramjet/types";
2+
3+
export type SequenceDeployArgs = STHRestAPI.StartSequencePayload & {
4+
output?: string;
5+
};
6+
7+
export type SequenceStartCLIArgs = {
8+
args?: string;
9+
configFile?: string;
10+
configString?: string;
11+
instId?: string;
12+
inputTopic?: string;
13+
limits?: string;
14+
output?: string;
15+
outputTopic?: string;
16+
startupConfig: DeepPartial<SequenceDeployArgs>;
17+
};
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { AppConfig } from "../app-config";
22
import { InstanceLimits } from "../instance-limits";
33

4-
export type StartSequenceResponse = { id: string }
4+
export type StartSequenceResponse = { id: string };
55

66
export type StartSequencePayload = {
7-
appConfig: AppConfig,
8-
args?: any[],
9-
outputTopic?: string,
10-
inputTopic?: string,
11-
limits?: InstanceLimits,
12-
instanceId?: string
13-
}
7+
appConfig: AppConfig;
8+
args?: any[];
9+
outputTopic?: string;
10+
inputTopic?: string;
11+
limits?: InstanceLimits;
12+
instanceId?: string;
13+
};

0 commit comments

Comments
 (0)