@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.
- package/dist/commands/hosting/index.js +2 -0
- package/dist/commands/hosting/index.js.map +1 -1
- package/dist/commands/hosting/manifest/upload-manifest.js +4 -0
- package/dist/commands/hosting/manifest/upload-manifest.js.map +1 -1
- package/dist/commands/hosting/pilet/pilet-commands.js +8 -13
- package/dist/commands/hosting/pilet/pilet-commands.js.map +1 -1
- package/dist/commands/hosting/pilet/register-pilet-options.d.ts +0 -2
- package/dist/commands/hosting/pilet/register-pilet.d.ts +1 -1
- package/dist/commands/hosting/pilet/register-pilet.js +62 -51
- package/dist/commands/hosting/pilet/register-pilet.js.map +1 -1
- package/dist/commands/hosting/service/deploy/service-deploy-command.d.ts +3 -0
- package/dist/commands/hosting/service/deploy/service-deploy-command.js +72 -0
- package/dist/commands/hosting/service/deploy/service-deploy-command.js.map +1 -0
- package/dist/commands/hosting/service/deploy/service-deploy-options.d.ts +14 -0
- package/dist/commands/hosting/service/deploy/service-deploy-options.js +3 -0
- package/dist/commands/hosting/service/deploy/service-deploy-options.js.map +1 -0
- package/dist/commands/hosting/service/deploy/service-deploy.d.ts +9 -0
- package/dist/commands/hosting/service/deploy/service-deploy.js +244 -0
- package/dist/commands/hosting/service/deploy/service-deploy.js.map +1 -0
- package/dist/commands/hosting/service/service-commands.d.ts +2 -0
- package/dist/commands/hosting/service/service-commands.js +16 -0
- package/dist/commands/hosting/service/service-commands.js.map +1 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-command.d.ts +3 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-command.js +53 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-command.js.map +1 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-options.d.ts +11 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-options.js +3 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy-options.js.map +1 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy.d.ts +9 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy.js +140 -0
- package/dist/commands/hosting/service/undeploy/service-undeploy.js.map +1 -0
- package/package.json +4 -4
- package/src/commands/hosting/index.ts +2 -0
- package/src/commands/hosting/manifest/upload-manifest.ts +7 -0
- package/src/commands/hosting/pilet/pilet-commands.ts +9 -13
- package/src/commands/hosting/pilet/register-pilet-options.ts +0 -2
- package/src/commands/hosting/pilet/register-pilet.ts +89 -57
- package/src/commands/hosting/service/deploy/service-deploy-command.ts +72 -0
- package/src/commands/hosting/service/deploy/service-deploy-options.ts +14 -0
- package/src/commands/hosting/service/deploy/service-deploy.ts +347 -0
- package/src/commands/hosting/service/service-commands.ts +23 -0
- package/src/commands/hosting/service/undeploy/service-undeploy-command.ts +53 -0
- package/src/commands/hosting/service/undeploy/service-undeploy-options.ts +11 -0
- 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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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/${
|
|
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 ??
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const serviceId = args.serviceId ?? process.env.SERVICE_ID;
|
|
77
|
-
const piletArtifactPath =
|
|
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
|
-
|
|
108
|
+
errorMessages.push('[idServiceAuthBaseURL] is required.');
|
|
84
109
|
}
|
|
85
110
|
if (isNullOrWhitespace(mosaicHostingClientId)) {
|
|
86
|
-
|
|
111
|
+
errorMessages.push('[clientId] is required.');
|
|
87
112
|
}
|
|
88
113
|
if (isNullOrWhitespace(mosaicHostingClientSecret)) {
|
|
89
|
-
|
|
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
|
-
|
|
117
|
+
errorMessages.push('[serviceId] is required.');
|
|
99
118
|
}
|
|
100
119
|
if (isNullOrWhitespace(piletArtifactPath)) {
|
|
101
|
-
|
|
120
|
+
errorMessages.push('[piletArtifactPath] is required,');
|
|
102
121
|
}
|
|
103
122
|
if (isNullOrWhitespace(microFrontendServiceBaseURL)) {
|
|
104
|
-
|
|
123
|
+
errorMessages.push('[microFrontendServiceBaseURL] is required.');
|
|
105
124
|
}
|
|
106
125
|
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
140
|
+
const findArtifactToPublish = (piletPath: string): string => {
|
|
121
141
|
if (!existsSync(piletPath)) {
|
|
122
|
-
throw new Error(
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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(
|
|
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
|
+
};
|