@axinom/mosaic-cli 0.20.0-rc.9 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/commands/hosting/index.js +2 -0
  2. package/dist/commands/hosting/index.js.map +1 -1
  3. package/dist/commands/hosting/manifest/upload-manifest.js +4 -0
  4. package/dist/commands/hosting/manifest/upload-manifest.js.map +1 -1
  5. package/dist/commands/hosting/pilet/pilet-commands.js +8 -13
  6. package/dist/commands/hosting/pilet/pilet-commands.js.map +1 -1
  7. package/dist/commands/hosting/pilet/register-pilet-options.d.ts +0 -2
  8. package/dist/commands/hosting/pilet/register-pilet.d.ts +1 -1
  9. package/dist/commands/hosting/pilet/register-pilet.js +62 -51
  10. package/dist/commands/hosting/pilet/register-pilet.js.map +1 -1
  11. package/dist/commands/hosting/service/deploy/service-deploy-command.d.ts +3 -0
  12. package/dist/commands/hosting/service/deploy/service-deploy-command.js +72 -0
  13. package/dist/commands/hosting/service/deploy/service-deploy-command.js.map +1 -0
  14. package/dist/commands/hosting/service/deploy/service-deploy-options.d.ts +14 -0
  15. package/dist/commands/hosting/service/deploy/service-deploy-options.js +3 -0
  16. package/dist/commands/hosting/service/deploy/service-deploy-options.js.map +1 -0
  17. package/dist/commands/hosting/service/deploy/service-deploy.d.ts +9 -0
  18. package/dist/commands/hosting/service/deploy/service-deploy.js +244 -0
  19. package/dist/commands/hosting/service/deploy/service-deploy.js.map +1 -0
  20. package/dist/commands/hosting/service/service-commands.d.ts +2 -0
  21. package/dist/commands/hosting/service/service-commands.js +16 -0
  22. package/dist/commands/hosting/service/service-commands.js.map +1 -0
  23. package/dist/commands/hosting/service/undeploy/service-undeploy-command.d.ts +3 -0
  24. package/dist/commands/hosting/service/undeploy/service-undeploy-command.js +53 -0
  25. package/dist/commands/hosting/service/undeploy/service-undeploy-command.js.map +1 -0
  26. package/dist/commands/hosting/service/undeploy/service-undeploy-options.d.ts +11 -0
  27. package/dist/commands/hosting/service/undeploy/service-undeploy-options.js +3 -0
  28. package/dist/commands/hosting/service/undeploy/service-undeploy-options.js.map +1 -0
  29. package/dist/commands/hosting/service/undeploy/service-undeploy.d.ts +9 -0
  30. package/dist/commands/hosting/service/undeploy/service-undeploy.js +140 -0
  31. package/dist/commands/hosting/service/undeploy/service-undeploy.js.map +1 -0
  32. package/package.json +4 -4
  33. package/src/commands/hosting/index.ts +2 -0
  34. package/src/commands/hosting/manifest/upload-manifest.ts +7 -0
  35. package/src/commands/hosting/pilet/pilet-commands.ts +9 -13
  36. package/src/commands/hosting/pilet/register-pilet-options.ts +0 -2
  37. package/src/commands/hosting/pilet/register-pilet.ts +89 -57
  38. package/src/commands/hosting/service/deploy/service-deploy-command.ts +72 -0
  39. package/src/commands/hosting/service/deploy/service-deploy-options.ts +14 -0
  40. package/src/commands/hosting/service/deploy/service-deploy.ts +347 -0
  41. package/src/commands/hosting/service/service-commands.ts +23 -0
  42. package/src/commands/hosting/service/undeploy/service-undeploy-command.ts +53 -0
  43. package/src/commands/hosting/service/undeploy/service-undeploy-options.ts +11 -0
  44. package/src/commands/hosting/service/undeploy/service-undeploy.ts +188 -0
@@ -1,4 +1,4 @@
1
- import { getServiceAccountToken } from '@axinom/mosaic-id-link-be';
1
+ import { TokenResult, getServiceAccountToken } from '@axinom/mosaic-id-link-be';
2
2
  import { isNullOrWhitespace } from '@axinom/mosaic-service-common';
3
3
  import axios, { AxiosError } from 'axios';
4
4
  import * as FormData from 'form-data';
@@ -9,11 +9,30 @@ import { GetRegisterPiletOptions } from './register-pilet-options';
9
9
  export const registerPilet = async (
10
10
  args: Required<GetRegisterPiletOptions>,
11
11
  ): Promise<void> => {
12
- const serviceAccountToken = await getServiceAccountToken(
13
- args.idServiceAuthBaseURL,
14
- args.mosaicHostingClientId,
15
- args.mosaicHostingClientSecret,
16
- );
12
+ let serviceAccountToken: TokenResult | undefined;
13
+
14
+ try {
15
+ serviceAccountToken = await getServiceAccountToken(
16
+ args.idServiceAuthBaseURL,
17
+ args.mosaicHostingClientId,
18
+ args.mosaicHostingClientSecret,
19
+ );
20
+ } catch (error) {
21
+ console.log(
22
+ `\nFailed to get the service account token. ${
23
+ (error as Error).message
24
+ }\n`,
25
+ );
26
+ return;
27
+ }
28
+
29
+ // Extract the tenant/environment IDs from the access token
30
+ const { tenantId, environmentId } = JSON.parse(
31
+ Buffer.from(
32
+ serviceAccountToken.accessToken.split('.')[1],
33
+ 'base64',
34
+ ).toString(),
35
+ ) as { tenantId: string; environmentId: string };
17
36
 
18
37
  const data = new FormData();
19
38
 
@@ -27,20 +46,21 @@ export const registerPilet = async (
27
46
 
28
47
  try {
29
48
  const artifactToPublish = findArtifactToPublish(args.piletArtifactPath);
30
- if (artifactToPublish) {
31
- data.append('', createReadStream(artifactToPublish));
32
- } else {
33
- throw new Error(`No Pilet Artifact found at ${args.piletArtifactPath}.`);
34
- }
49
+ data.append('', createReadStream(artifactToPublish));
35
50
  } catch (error) {
36
- throw new Error(
37
- `Error occurred while trying to read the Pilet Artifact at ${args.piletArtifactPath}.`,
38
- );
51
+ console.log(`\n${(error as Error).message}`);
52
+ return;
39
53
  }
40
54
 
41
55
  try {
56
+ console.log(`\nRegistering pilet...`);
57
+ console.log(`path:\t\t${args.piletArtifactPath}`);
58
+ console.log(`tenantId:\t${tenantId}`);
59
+ console.log(`environmentId:\t${environmentId}`);
60
+ console.log(`serviceId:\t${args.serviceId}\n`);
61
+
42
62
  const result = await axios.post(
43
- `${args.microFrontendServiceBaseURL}/v1/pilets/${args.tenantId}/${args.environmentId}/${args.serviceId}/register?override=${args.overrideRegistration}`,
63
+ `${args.microFrontendServiceBaseURL}/v1/pilets/${tenantId}/${environmentId}/${args.serviceId}/register?override=${args.overrideRegistration}`,
44
64
  data,
45
65
  config,
46
66
  );
@@ -52,6 +72,7 @@ export const registerPilet = async (
52
72
  'Registration of Pilet failed.',
53
73
  (error as AxiosError).response?.data ?? (error as Error).message,
54
74
  );
75
+ return;
55
76
  }
56
77
  };
57
78
 
@@ -64,82 +85,93 @@ export const registerPilet = async (
64
85
  */
65
86
  export const validateArgs = (
66
87
  args: GetRegisterPiletOptions,
67
- ): Required<GetRegisterPiletOptions> => {
88
+ ): [Required<GetRegisterPiletOptions>, string[]] => {
89
+ const errorMessages: string[] = [];
90
+
68
91
  const idServiceAuthBaseURL =
69
- args.idServiceAuthBaseURL ?? process.env.ID_SERVICE_AUTH_BASE_URL;
92
+ args.idServiceAuthBaseURL ?? process.env.ID_SERVICE_AUTH_BASE_URL ?? '';
70
93
  const mosaicHostingClientId =
71
- args.mosaicHostingClientId ?? process.env.MOSAIC_HOSTING_CLIENT_ID;
94
+ args.mosaicHostingClientId ?? process.env.MOSAIC_HOSTING_CLIENT_ID ?? '';
72
95
  const mosaicHostingClientSecret =
73
- args.mosaicHostingClientSecret ?? process.env.MOSAIC_HOSTING_CLIENT_SECRET;
74
- const tenantId = args.tenantId ?? process.env.TENANT_ID;
75
- const environmentId = args.environmentId ?? process.env.ENVIRONMENT_ID;
76
- const serviceId = args.serviceId ?? process.env.SERVICE_ID;
77
- const piletArtifactPath = args.piletArtifactPath ?? process.env.PILET_PATH;
96
+ args.mosaicHostingClientSecret ??
97
+ process.env.MOSAIC_HOSTING_CLIENT_SECRET ??
98
+ '';
99
+ const serviceId = args.serviceId ?? process.env.SERVICE_ID ?? '';
100
+ const piletArtifactPath =
101
+ args.piletArtifactPath ?? process.env.PILET_PATH ?? '';
78
102
  const microFrontendServiceBaseURL =
79
103
  args.microFrontendServiceBaseURL ??
80
- process.env.MICRO_FRONTEND_SERVICE_BASE_URL;
104
+ process.env.MICRO_FRONTEND_SERVICE_BASE_URL ??
105
+ '';
81
106
 
82
107
  if (isNullOrWhitespace(idServiceAuthBaseURL)) {
83
- throw new Error('idServiceAuthBaseURL is required.');
108
+ errorMessages.push('[idServiceAuthBaseURL] is required.');
84
109
  }
85
110
  if (isNullOrWhitespace(mosaicHostingClientId)) {
86
- throw new Error('clientId is required.');
111
+ errorMessages.push('[clientId] is required.');
87
112
  }
88
113
  if (isNullOrWhitespace(mosaicHostingClientSecret)) {
89
- throw new Error('clientSecret is required.');
90
- }
91
- if (isNullOrWhitespace(tenantId)) {
92
- throw new Error('tenantId is required.');
93
- }
94
- if (isNullOrWhitespace(environmentId)) {
95
- throw new Error('environmentId is required,');
114
+ errorMessages.push('[clientSecret] is required.');
96
115
  }
97
116
  if (isNullOrWhitespace(serviceId)) {
98
- throw new Error('serviceId is required.');
117
+ errorMessages.push('[serviceId] is required.');
99
118
  }
100
119
  if (isNullOrWhitespace(piletArtifactPath)) {
101
- throw new Error('piletArtifactPath is required,');
120
+ errorMessages.push('[piletArtifactPath] is required,');
102
121
  }
103
122
  if (isNullOrWhitespace(microFrontendServiceBaseURL)) {
104
- throw new Error('microFrontendServiceBaseURL is required.');
123
+ errorMessages.push('[microFrontendServiceBaseURL] is required.');
105
124
  }
106
125
 
107
- return {
108
- idServiceAuthBaseURL,
109
- mosaicHostingClientId,
110
- mosaicHostingClientSecret,
111
- tenantId,
112
- environmentId,
113
- serviceId,
114
- piletArtifactPath,
115
- microFrontendServiceBaseURL,
116
- overrideRegistration: args.overrideRegistration ?? false,
117
- };
126
+ return [
127
+ {
128
+ idServiceAuthBaseURL,
129
+ mosaicHostingClientId,
130
+ mosaicHostingClientSecret,
131
+ serviceId,
132
+ piletArtifactPath,
133
+ microFrontendServiceBaseURL,
134
+ overrideRegistration: args.overrideRegistration ?? false,
135
+ },
136
+ errorMessages,
137
+ ];
118
138
  };
119
139
 
120
- const findArtifactToPublish = (piletPath: string): string | undefined => {
140
+ const findArtifactToPublish = (piletPath: string): string => {
121
141
  if (!existsSync(piletPath)) {
122
- throw new Error(`No directory found at ${piletPath}`);
142
+ throw new Error(
143
+ `The specified pilet artifact path [${piletPath}] does not exist.`,
144
+ );
123
145
  }
124
146
 
125
- const files = readdirSync(piletPath);
126
147
  const tgzFiles: string[] = [];
127
148
 
128
- for (let i = 0; i < files.length; i++) {
129
- const filename = path.join(piletPath, files[i]);
130
- const stat = lstatSync(filename);
131
- if (!stat.isDirectory() && filename.endsWith('.tgz')) {
132
- tgzFiles.push(filename);
149
+ // Is the path a directory?
150
+ if (lstatSync(piletPath).isDirectory()) {
151
+ const files = readdirSync(piletPath);
152
+
153
+ for (let i = 0; i < files.length; i++) {
154
+ const filename = path.join(piletPath, files[i]);
155
+ const stat = lstatSync(filename);
156
+ if (!stat.isDirectory() && filename.endsWith('.tgz')) {
157
+ tgzFiles.push(filename);
158
+ }
133
159
  }
160
+ } // Path is a file
161
+ else {
162
+ tgzFiles.push(piletPath);
134
163
  }
135
164
 
136
165
  if (tgzFiles.length > 1) {
137
166
  throw new Error(
138
- `Found more than one .tgz files in the folder ${piletPath}. Please make sure only one .tgz file exists`,
167
+ `Found more than one .tgz files in the folder [${piletPath}]. Please make sure only one .tgz file exists or specify the path to the .tgz file.`,
139
168
  );
140
169
  }
170
+
141
171
  if (tgzFiles.length === 0) {
142
- throw new Error(`'No .tgz files found at ${piletPath}.`);
172
+ throw new Error(
173
+ `No .tgz files found at pilet artifact path [${piletPath}].`,
174
+ );
143
175
  }
144
176
 
145
177
  return tgzFiles[0];
@@ -0,0 +1,72 @@
1
+ import { red, yellow } from 'chalk';
2
+ import { CommandModule } from 'yargs';
3
+ import { deployService, validateDeploymentArgs } from './service-deploy';
4
+ import { GetServiceDeployOptions } from './service-deploy-options';
5
+
6
+ export const serviceDeploy: CommandModule<unknown, GetServiceDeployOptions> = {
7
+ builder: (yargs) => {
8
+ return yargs
9
+ .option('name', {
10
+ describe: 'Name of the Deployment',
11
+ alias: 'n',
12
+ string: true,
13
+ demandOption: true,
14
+ })
15
+ .option('serviceId', {
16
+ describe:
17
+ 'Service ID the deployment must be done for. (If a deployment manifest is found in the current directory, the serviceId will be derived from it.)',
18
+ alias: 'i',
19
+ string: true,
20
+ })
21
+ .option('containerImageTag', {
22
+ describe:
23
+ 'Tag of the container image to be used in the deployment. i.e: It should be the <tag> from the container image format <username>/<repository_name>:<tag>.',
24
+ alias: 't',
25
+ string: true,
26
+ demandOption: true,
27
+ })
28
+ .option('manifestName', {
29
+ describe: 'Name of the deployment manifest',
30
+ alias: 'm',
31
+ string: true,
32
+ demandOption: true,
33
+ })
34
+ .option('pilets', {
35
+ describe:
36
+ 'Pilet(s) to be used in the deployment. The pilet name should match the format package-name@version => media-workflows@1.5.7',
37
+ alias: 'p',
38
+ array: true,
39
+ string: true,
40
+ })
41
+ .option('mosaicHostingClientId', {
42
+ describe: 'Service Account Client ID to authenticate',
43
+ alias: 'c',
44
+ string: true,
45
+ })
46
+ .option('mosaicHostingClientSecret', {
47
+ describe: 'Service Account Client Secret to authenticate',
48
+ alias: 's',
49
+ string: true,
50
+ })
51
+ .option('idServiceAuthBaseURL', {
52
+ describe: 'ID Service Authentication Endpoint Base URL',
53
+ alias: 'a',
54
+ string: true,
55
+ })
56
+ .option('hostingServiceBaseURL', {
57
+ describe: 'Hosting Service Base URL',
58
+ alias: 'h',
59
+ string: true,
60
+ });
61
+ },
62
+ handler: async (args) => {
63
+ const [validatedArgs, errorMessages] = validateDeploymentArgs(args);
64
+ if (errorMessages.length > 0) {
65
+ console.log(red('Some required arguments are missing.'));
66
+ console.log(yellow(errorMessages));
67
+ console.log();
68
+ } else {
69
+ await deployService(validatedArgs);
70
+ }
71
+ },
72
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Options for Deploying a Service
3
+ */
4
+ export interface GetServiceDeployOptions {
5
+ name: string;
6
+ serviceId?: string;
7
+ containerImageTag: string;
8
+ manifestName: string;
9
+ pilets?: string[];
10
+ mosaicHostingClientId?: string;
11
+ mosaicHostingClientSecret?: string;
12
+ idServiceAuthBaseURL?: string;
13
+ hostingServiceBaseURL?: string;
14
+ }
@@ -0,0 +1,347 @@
1
+ import { getServiceAccountToken } from '@axinom/mosaic-id-link-be';
2
+ import { isNullOrWhitespace } from '@axinom/mosaic-service-common';
3
+ import axios, { AxiosError, AxiosInstance } from 'axios';
4
+ import { green, red, yellow } from 'chalk';
5
+ import { readFileSync } from 'fs';
6
+ import * as yaml from 'js-yaml';
7
+ import { GetServiceDeployOptions } from './service-deploy-options';
8
+
9
+ const getAxiosInstance = (
10
+ hostingServiceBaseUrl: string,
11
+ serviceAccountToken: string,
12
+ ): AxiosInstance => {
13
+ return axios.create({
14
+ baseURL: new URL(hostingServiceBaseUrl).toString(),
15
+ headers: {
16
+ Authorization: `Bearer ${serviceAccountToken}`,
17
+ 'content-type': 'application/json',
18
+ },
19
+ });
20
+ };
21
+
22
+ export const deployService = async (
23
+ args: Required<GetServiceDeployOptions>,
24
+ ): Promise<void> => {
25
+ const serviceAccountToken = await getServiceAccountToken(
26
+ args.idServiceAuthBaseURL,
27
+ args.mosaicHostingClientId,
28
+ args.mosaicHostingClientSecret,
29
+ );
30
+
31
+ const axiosInstance: AxiosInstance = getAxiosInstance(
32
+ args.hostingServiceBaseURL,
33
+ serviceAccountToken.accessToken,
34
+ );
35
+
36
+ const deploymentDetails = await retrieveServiceDeploymentDetails(
37
+ args,
38
+ axiosInstance,
39
+ );
40
+
41
+ if (deploymentDetails === undefined) {
42
+ return;
43
+ }
44
+
45
+ const serviceDeployment = JSON.stringify({
46
+ query: `mutation initiateServiceDeployment($serviceDefinitionId: UUID!, $containerImageVersion: String!, $serviceDeploymentConfigurationId: UUID!, $servicePiletArtifactIds: [UUID!]!, $name: String) {
47
+ initiateServiceDeployment(
48
+ input: {serviceDefinitionId: $serviceDefinitionId, containerImageVersion: $containerImageVersion, serviceDeploymentConfigurationId: $serviceDeploymentConfigurationId, servicePiletArtifactIds: $servicePiletArtifactIds, name: $name}
49
+ ) {
50
+ serviceDeploymentId
51
+ name
52
+ status
53
+ }
54
+ }`,
55
+ variables: {
56
+ serviceDefinitionId: deploymentDetails.serviceDefinitionId,
57
+ containerImageVersion: args.containerImageTag,
58
+ serviceDeploymentConfigurationId: deploymentDetails.deploymentManifestId,
59
+ servicePiletArtifactIds: deploymentDetails.piletArtifactIds ?? [],
60
+ name: args.name,
61
+ },
62
+ });
63
+
64
+ try {
65
+ const response = await axiosInstance.post('graphql', serviceDeployment);
66
+ if (response.data.errors !== undefined && response.data.errors.length > 0) {
67
+ console.log(red(`Deployment for Service ID [${args.serviceId}] failed.`));
68
+ if (
69
+ response.data.errors[0].message ===
70
+ 'Attempt to create or update an element failed, as it would have resulted in a duplicate element.'
71
+ ) {
72
+ console.log(
73
+ yellow(
74
+ `Hint: The deployment likely failed because the deployment name [${args.name}] is already in use. Please try with a different name.`,
75
+ ),
76
+ );
77
+ } else {
78
+ console.log(red(response.data.errors[0].message));
79
+ }
80
+ console.log();
81
+ } else {
82
+ console.log(
83
+ green(
84
+ `Deployment for Service ID [${args.serviceId}] initiated successfully. It may take some time to complete the deployment.`,
85
+ ),
86
+ );
87
+ console.log(
88
+ yellow(JSON.stringify(response.data.data?.initiateServiceDeployment)),
89
+ );
90
+ console.log();
91
+ }
92
+ } catch (error) {
93
+ console.log(
94
+ red(
95
+ `Error while performing deployment for Service ID [${args.serviceId}].`,
96
+ ),
97
+ );
98
+ console.log(red(JSON.stringify((error as AxiosError).message)));
99
+ }
100
+ };
101
+
102
+ /**
103
+ * Validate arguments for Service Deployment
104
+ *
105
+ * @param args
106
+ * @returns
107
+ */
108
+ export const validateDeploymentArgs = (
109
+ args: GetServiceDeployOptions,
110
+ ): [Required<GetServiceDeployOptions>, string[]] => {
111
+ const errorMessages: string[] = [];
112
+
113
+ const idServiceAuthBaseURL =
114
+ args.idServiceAuthBaseURL ?? process.env.ID_SERVICE_AUTH_BASE_URL ?? '';
115
+ const mosaicHostingClientId =
116
+ args.mosaicHostingClientId ?? process.env.MOSAIC_HOSTING_CLIENT_ID ?? '';
117
+ const mosaicHostingClientSecret =
118
+ args.mosaicHostingClientSecret ??
119
+ process.env.MOSAIC_HOSTING_CLIENT_SECRET ??
120
+ '';
121
+ const serviceId = args.serviceId ?? getServiceIdFromManifest() ?? '';
122
+ const hostingServiceBaseURL =
123
+ args.hostingServiceBaseURL ?? process.env.HOSTING_SERVICE_BASE_URL ?? '';
124
+
125
+ const manifestName = args.manifestName;
126
+ const containerImageTag = args.containerImageTag;
127
+ let pilets = args.pilets;
128
+
129
+ if (isNullOrWhitespace(idServiceAuthBaseURL)) {
130
+ errorMessages.push('[idServiceAuthBaseURL] is required.');
131
+ }
132
+ if (isNullOrWhitespace(mosaicHostingClientId)) {
133
+ errorMessages.push('[clientId] is required.');
134
+ }
135
+ if (isNullOrWhitespace(mosaicHostingClientSecret)) {
136
+ errorMessages.push('[clientSecret] is required.');
137
+ }
138
+ if (isNullOrWhitespace(serviceId)) {
139
+ errorMessages.push(
140
+ '[serviceId] is required. If no serviceId is given through arguments, mosaic-hosting-deployment-manifest.yaml file in the current directory is checked to derive the serviceId.',
141
+ );
142
+ }
143
+ if (isNullOrWhitespace(hostingServiceBaseURL)) {
144
+ errorMessages.push('[hostingServiceBaseURL] is required.');
145
+ }
146
+ if (isNullOrWhitespace(manifestName)) {
147
+ errorMessages.push(
148
+ '[manifestName] is required and cannot be an empty string.',
149
+ );
150
+ }
151
+ if (isNullOrWhitespace(containerImageTag)) {
152
+ errorMessages.push(
153
+ '[containerImageTag] is required and cannot be an empty string.',
154
+ );
155
+ }
156
+
157
+ if (isNullOrWhitespace(pilets)) {
158
+ pilets = [];
159
+ }
160
+
161
+ return [
162
+ {
163
+ name: args.name,
164
+ idServiceAuthBaseURL,
165
+ mosaicHostingClientId,
166
+ mosaicHostingClientSecret,
167
+ serviceId,
168
+ manifestName,
169
+ pilets,
170
+ containerImageTag: containerImageTag,
171
+ hostingServiceBaseURL,
172
+ },
173
+ errorMessages,
174
+ ];
175
+ };
176
+
177
+ const getServiceIdFromManifest = (): string | undefined => {
178
+ try {
179
+ const doc = yaml.load(
180
+ readFileSync('./mosaic-hosting-deployment-manifest.yaml', 'utf8'),
181
+ );
182
+ if (!isNullOrWhitespace(doc)) {
183
+ return (doc as any).serviceId;
184
+ }
185
+ } catch (error) {
186
+ return undefined;
187
+ }
188
+ };
189
+
190
+ const retrieveServiceDeploymentDetails = async (
191
+ args: Required<GetServiceDeployOptions>,
192
+ axiosInstance: AxiosInstance,
193
+ ): Promise<
194
+ | {
195
+ serviceDefinitionId: string;
196
+ deploymentManifestId: string;
197
+ piletArtifactIds: string[];
198
+ }
199
+ | undefined
200
+ > => {
201
+ const piletNamingErrors: string[] = [];
202
+
203
+ const piletFilter = args.pilets.map((pilet) => {
204
+ const nameAndVersion = pilet.split('@');
205
+ if (nameAndVersion.length !== 2) {
206
+ piletNamingErrors.push(`Pilet name [${pilet}] is invalid.`);
207
+ return;
208
+ }
209
+ return {
210
+ name: { equalTo: nameAndVersion[0] },
211
+ packageVersion: { equalTo: nameAndVersion[1] },
212
+ serviceId: { equalTo: args.serviceId },
213
+ };
214
+ });
215
+
216
+ if (piletNamingErrors.length > 0) {
217
+ console.log(red(piletNamingErrors));
218
+ console.log(
219
+ yellow(
220
+ `Pilet names must match the pattern [pilet-name@package-version]. i.e.: media-workflows@2.0.1.`,
221
+ ),
222
+ );
223
+ console.log();
224
+ return;
225
+ }
226
+
227
+ const getDeploymentInfoQuery = `query GetDeploymentInfo($serviceId: String!, $manifestName: String!, $pilets: ServicePiletArtifactFilter ) {
228
+ serviceDefinitions(condition: {serviceId: $serviceId}) {
229
+ nodes {
230
+ id
231
+ }
232
+ }
233
+ serviceDeploymentManifests(
234
+ filter: {name: {equalTo: $manifestName}, serviceId: {equalTo: $serviceId}}
235
+ ) {
236
+ nodes {
237
+ id
238
+ }
239
+ }
240
+ servicePiletArtifacts(
241
+ filter: $pilets
242
+ ) {
243
+ nodes {
244
+ id
245
+ }
246
+ }
247
+ }`;
248
+
249
+ const deploymentInfo = JSON.stringify({
250
+ query: getDeploymentInfoQuery,
251
+ variables: {
252
+ serviceId: args.serviceId,
253
+ manifestName: args.manifestName,
254
+ pilets: {
255
+ and: [{ serviceId: { equalTo: args.serviceId } }, { or: piletFilter }],
256
+ },
257
+ },
258
+ });
259
+
260
+ let serviceDefinitionId: string | undefined;
261
+ let deploymentManifestId: string | undefined;
262
+ let piletArtifactIds: string[] = [];
263
+
264
+ try {
265
+ const response = await axiosInstance.post('graphql', deploymentInfo);
266
+ if (response.data.errors !== undefined && response.data.errors.length > 0) {
267
+ console.log(
268
+ red(`Error while retrieving deployment info for [${args.serviceId}].`),
269
+ );
270
+ console.log(response.data.errors);
271
+ console.log();
272
+ } else {
273
+ serviceDefinitionId =
274
+ response.data.data?.serviceDefinitions?.nodes[0]?.id;
275
+ deploymentManifestId =
276
+ response.data.data?.serviceDeploymentManifests?.nodes[0]?.id;
277
+ piletArtifactIds = response.data.data?.servicePiletArtifacts?.nodes?.map(
278
+ (pilet) => pilet.id,
279
+ );
280
+ console.log(
281
+ green(
282
+ `Deployment info retrieved successfully for [${args.serviceId}].`,
283
+ ),
284
+ );
285
+
286
+ const joinedPiletArtifactIds = piletArtifactIds?.join(',');
287
+ console.log(green(`Service Definition ID:\t${serviceDefinitionId}`));
288
+ console.log(green(`Deployment Manifest ID:\t${deploymentManifestId}`));
289
+ console.log(
290
+ green(
291
+ `Pilet Artifacts ID(s):\t${
292
+ joinedPiletArtifactIds === ''
293
+ ? '(No Pilet Artifacts)'
294
+ : joinedPiletArtifactIds
295
+ }`,
296
+ ),
297
+ );
298
+
299
+ console.log();
300
+ }
301
+ } catch (error) {
302
+ console.log(
303
+ red(`Error while retrieving deployment info for ${args.serviceId}.`),
304
+ );
305
+ console.log(JSON.stringify((error as AxiosError).message));
306
+ console.log();
307
+ }
308
+
309
+ if (isNullOrWhitespace(serviceDefinitionId)) {
310
+ console.log(
311
+ red(
312
+ `No Service Definition found for Service ID [${args.serviceId}]. Cannot proceed with deployment.`,
313
+ ),
314
+ );
315
+ console.log();
316
+ return;
317
+ }
318
+
319
+ if (isNullOrWhitespace(deploymentManifestId)) {
320
+ console.log(
321
+ red(
322
+ `No Deployment Manifest found for name [${args.manifestName}] and Service ID [${args.serviceId}]. Cannot proceed with deployment.`,
323
+ ),
324
+ );
325
+ console.log();
326
+ return;
327
+ }
328
+
329
+ if (
330
+ args.pilets.length > 0 &&
331
+ args.pilets.length !== piletArtifactIds.length
332
+ ) {
333
+ console.log(
334
+ red(
335
+ `Pilet artifacts were not found for the given pilet names. Cannot proceed with deployment.`,
336
+ ),
337
+ );
338
+ console.log();
339
+ return;
340
+ }
341
+
342
+ return {
343
+ serviceDefinitionId,
344
+ deploymentManifestId,
345
+ piletArtifactIds,
346
+ };
347
+ };
@@ -0,0 +1,23 @@
1
+ import { CommandModule } from 'yargs';
2
+ import { serviceDeploy } from './deploy/service-deploy-command';
3
+ import { serviceUndeploy } from './undeploy/service-undeploy-command';
4
+
5
+ export const serviceCommands: CommandModule<unknown, unknown> = {
6
+ builder: (yargs) =>
7
+ yargs
8
+ .command(
9
+ 'deploy',
10
+ 'Deploy a service through Hosting Service.',
11
+ serviceDeploy,
12
+ )
13
+ .demandCommand()
14
+ .command(
15
+ 'undeploy',
16
+ 'Undeploy a service through Hosting Service.',
17
+ serviceUndeploy,
18
+ )
19
+ .demandCommand(),
20
+ handler: () => {
21
+ console.log('Please pick an action related to hosting service command.');
22
+ },
23
+ };