Skip to content

Commit 4a5bc47

Browse files
fixing Verrazzano app/component deployment issues (#187)
* fixing app/component deployment issues * Show placeholder text for ingress paths as gray; show path type label Co-authored-by: Richard Killen <richard.killen@oracle.com>
1 parent 0064499 commit 4a5bc47

22 files changed

+163
-102
lines changed

electron/app/js/ipcRendererPreload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ contextBridge.exposeInMainWorld(
233233
'get-verrazzano-secret-names',
234234
'get-verrazzano-cluster-names',
235235
'get-verrazzano-deployment-names-all-namespaces',
236+
'verify-verrazzano-components-exist',
236237
'deploy-verrazzano-project',
237238
'deploy-verrazzano-application',
238239
'undeploy-verrazzano-application',

electron/app/js/kubectlUtils.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ async function apply(kubectlExe, fileData, options) {
688688
fsUtils.writeTempFile(fileData, { baseName: 'k8sApplyData', extension: '.yaml' }).then(fileName => {
689689
const args = [ 'apply', '-f', fileName ];
690690
executeFileCommand(kubectlExe, args, env).then(stdout => {
691-
console.log('kubectl apply returned: %s', stdout);
691+
getLogger().debug('kubectl apply returned: %s', stdout);
692692
fsUtils.recursivelyRemoveTemporaryFileDirectory(fileName).then(() => resolve(result)).catch(err => {
693693
getLogger().warn('kubectlUtils.apply() failed to remove temporary file %s: %s', fileName, err);
694694
resolve(result);
@@ -860,7 +860,12 @@ async function isVerrazzanoInstalled(kubectlExe, options) {
860860
const vzObject = vzObjectList[0];
861861
result.isInstalled = true;
862862
result.name = vzObject.metadata?.name;
863-
result.version = vzObject.status?.version;
863+
864+
let statusVersion = vzObject.status?.version;
865+
if (statusVersion && statusVersion.startsWith('v')) {
866+
statusVersion = statusVersion.slice(1);
867+
}
868+
result.version = statusVersion;
864869
}
865870
resolve(result);
866871
}).catch(err => {
@@ -972,6 +977,36 @@ async function getKubernetesObjectsFromAllNamespaces(kubectlExe, kubectlOptions,
972977
});
973978
}
974979

980+
async function verifyVerrazzanoComponentsDeployed(kubectlExe, componentNames, namespace, kubectlOptions) {
981+
const httpsProxyUrl = getHttpsProxyUrl();
982+
const bypassProxyHosts = getBypassProxyHosts();
983+
984+
const env = getKubectlEnvironment(kubectlOptions, httpsProxyUrl, bypassProxyHosts);
985+
986+
const result = {
987+
isSuccess: true
988+
};
989+
990+
const missingComponentNames = [];
991+
for (const componentName of componentNames) {
992+
const args = [ 'get', 'Component', componentName, '-n', namespace ];
993+
try {
994+
const stdout = await executeFileCommand(kubectlExe, args, env);
995+
getLogger().debug('Found component %s in namespace %s: %s', componentName, namespace, stdout);
996+
} catch (err) {
997+
getLogger().warn('Error getting component %s in namespace %s: %s', componentName, namespace, getErrorMessage(err));
998+
missingComponentNames.push(componentName);
999+
}
1000+
}
1001+
1002+
if (missingComponentNames.length > 0) {
1003+
result.isSuccess = false;
1004+
result.reason = i18n.t('kubectl-verify-vz-components-deployed-error-message',
1005+
{ namespace: namespace, missingComponents: missingComponentNames.join(', ')});
1006+
}
1007+
return Promise.resolve(result);
1008+
}
1009+
9751010
async function doCreateSecret(kubectlExe, createArgs, env, namespace, secret, resolve, results, key) {
9761011
executeFileCommand(kubectlExe, createArgs, env, true).then(() => {
9771012
resolve(results);
@@ -1063,5 +1098,6 @@ module.exports = {
10631098
validateDomainExist,
10641099
validateApplicationExist,
10651100
verifyClusterConnectivity,
1101+
verifyVerrazzanoComponentsDeployed,
10661102
verifyVerrazzanoPlatformOperatorRollout,
10671103
};

electron/app/locales/en/electron.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
"kubectl-get-named-vz-install-error-message": "Unable to get Verrazzano installation with name {{name}}: {{error}}",
322322
"kubectl-get-vz-application-status-error-message": "Unable to get Verrazzano application status: {{error}}",
323323
"kubectl-get-operator-version-not-found-error-message": "Failed to find the operator version from its log entries",
324+
"kubectl-verify-vz-components-deployed-error-message": "Unable to find one or more components in namespace {{namespace}}: {{missingComponents}}",
324325

325326
"helm-not-specified-error-message": "Helm executable path was not provided",
326327
"helm-not-exists-error-message": "Helm executable {{filePath}} does not exist",

electron/app/locales/en/webui.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,7 @@
13281328
"flow-getting-vz-application-status-in-progress": "Getting Application Status",
13291329
"flow-checking-operator-version-in-progress": "Checking operator version",
13301330
"flow-checking-vz-already-installed-in-progress": "Checking if Verrazzano is already installed.",
1331+
"flow-checking-vz-app-components-deployed-in-progress": "Checking if Verrazzano application's components are deployed.",
13311332
"flow-install-verrazzano-name": "Install Verrazzano",
13321333
"flow-verrazzano-install-status-check-name": "Check Verrazzano Install Status",
13331334
"flow-verrazzano-get-install-version-name": "Get Verrazzano Installed Version",
@@ -1568,8 +1569,11 @@
15681569
"vz-application-design-project-name-label": "Verrazzano Project Name",
15691570
"vz-application-design-project-name-help": "The name to use when creating the new Verrazzano project for this multicluster application.",
15701571
"vz-application-design-components-title": "Components",
1572+
"vz-application-design-components-label": "Components",
15711573
"vz-application-design-component-name-label": "Component Name",
15721574
"vz-application-design-component-name-help": "The name of the Verrazzano component used by this application.",
1575+
"vz-application-design-delete-component-button-label": "Delete Component",
1576+
"vz-application-design-delete-component-button-help": "Delete the component reference from the application.",
15731577
"vz-application-design-add-component-button-label": "Add Component",
15741578
"vz-application.design-add-component-flow-nane": "Add Component",
15751579
"vz-application-design-add-component-validation-error-title": "Add Component to Application Aborted",
@@ -1666,11 +1670,13 @@
16661670
"vz-application-design-ingress-trait-rule-edit-paths-table-aria-label": "Paths table",
16671671
"vz-application-design-ingress-trait-rule-edit-path-label": "Path",
16681672
"vz-application-design-ingress-trait-rule-edit-path-help": "The path value to match using the specified Path Type value.",
1673+
"vz-application-design-ingress-trait-rule-edit-path-placeholder": "Enter Path Value",
16691674
"vz-application-design-ingress-trait-rule-edit-path-type-label": "Path Type",
16701675
"vz-application-design-ingress-trait-rule-edit-path-type-prefix-label": "Prefix",
16711676
"vz-application-design-ingress-trait-rule-edit-path-type-exact-label": "Exact",
16721677
"vz-application-design-ingress-trait-rule-edit-path-type-regex-label": "Regular Expression",
16731678
"vz-application-design-ingress-trait-rule-edit-path-type-help": "The expression type to use to process the Path value.",
1679+
"vz-application-design-ingress-trait-rule-edit-path-type-placeholder": "Select Path Type",
16741680
"vz-application-design-ingress-trait-rule-edit-path-add-row-tooltip": "Add Path",
16751681
"vz-application-design-ingress-trait-rule-edit-path-delete-row-tooltip": "Delete Path",
16761682
"vz-application-design-ingress-trait-rule-editASdd-destination-title": "Destination",
@@ -1695,6 +1701,7 @@
16951701
"vz-application-deployer-set-context-error-message": "Unable to deploy Verrazzano application because setting the Kubernetes client cluster context failed: {{error}}.",
16961702
"vz-application-deployer-install-check-failed-error-message": "Unable to deploy Verrazzano application because checking to see if Verrazzano is installed failed: {{error}}.",
16971703
"vz-application-deployer-not-installed-error-message": "Unable to deploy Verrazzano application because Verrazzano is not installed.",
1704+
"vz-application-deployer-verify-components-error-message": "Unable to deploy Verrazzano application because one or more referenced components are not deployed in the Kubernetes namespace {{namespace}}: {{error}}.",
16981705
"vz-application-deployer-deploy-project-in-progress": "Deploying Verrazzano Project",
16991706
"vz-application-deployer-deploy-project-error-message": "Unable to deploy Verrazzano application because an error occurred while deploying the Verrazzano project {{projectName}}: {{error}}.",
17001707
"vz-application-deployer-deploy-application-in-progress": "Deploying Application",

electron/app/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,11 @@ class Main {
10641064
ipcMain.handle('deploy-verrazzano-project', async (event, kubectlExe, project, kubectlOptions) => {
10651065
return deployProject(kubectlExe, project, kubectlOptions);
10661066
});
1067+
1068+
// eslint-disable-next-line no-unused-vars
1069+
ipcMain.handle('verify-verrazzano-components-exist',async (event, kubectlExe, componentNames, namespace, kubectlOptions) => {
1070+
return kubectlUtils.verifyVerrazzanoComponentsDeployed(kubectlExe, componentNames, namespace, kubectlOptions);
1071+
});
10671072
}
10681073

10691074
async getLatestWdtInstaller(targetWindow) {

webui/src/css/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,3 +756,7 @@ h6.wkt-panel-heading {
756756
color: red;
757757
font-weight: bold;
758758
}
759+
760+
.wkt-placeholder-text {
761+
color: var(--oj-label-inside-edge-color);
762+
}

webui/src/js/models/k8s-domain-definition.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,6 @@ define(['knockout', 'utils/observable-properties', 'utils/common-utilities', 'ut
113113
this.updateSecrets();
114114
});
115115

116-
this.configMapIsEmpty = () => {
117-
let result = true;
118-
for (const entry of wdtModel.getMergedPropertiesContent().observable()) {
119-
if (entry.Override) {
120-
result = false;
121-
break;
122-
}
123-
}
124-
return result;
125-
};
126-
127116
this.readFrom = (json) => {
128117
props.createGroup(name, this).readFrom(json);
129118
};

webui/src/js/models/vz-component-definition.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ define(['utils/observable-properties', 'utils/validation-helper'],
1717
this.componentName = props.createProperty('${1}', k8sDomain.uid.observable);
1818
this.componentName.addValidator(...validationHelper.getK8sNameValidators());
1919

20-
this.configMapIsEmpty = () => {
21-
return k8sDomain.configMapIsEmpty();
22-
};
23-
2420
this.readFrom = (json) => {
2521
props.createGroup(name, this).readFrom(json);
2622
};

webui/src/js/utils/k8s-domain-configmap-generator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ define(['models/wkt-project', 'js-yaml'],
1313
}
1414

1515
shouldCreateConfigMap() {
16-
return this.project.settings.targetDomainLocation.value === 'mii' && !this.project.k8sDomain.configMapIsEmpty();
16+
return this.project.settings.targetDomainLocation.value === 'mii';
1717
}
1818

1919
generate(generateYaml = true) {

webui/src/js/utils/k8s-domain-deployer.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -247,26 +247,24 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp
247247
{domainName: domainUid, domainNamespace: domainNamespace});
248248
dialogHelper.updateBusyDialog(busyDialogMessage, 12 / totalSteps);
249249
if (this.project.settings.targetDomainLocation.value === 'mii') {
250-
if (!this.project.k8sDomain.configMapIsEmpty()) {
251-
const configMapData = this.k8sDomainConfigMapGenerator.generate().join('\n');
252-
wktLogger.debug(configMapData);
253-
const mapResults = await (window.api.ipc.invoke('k8s-apply', kubectlExe, configMapData, kubectlOptions));
254-
if (!mapResults.isSuccess) {
255-
const configMapName = this.project.k8sDomain.modelConfigMapName.value;
256-
const errMessage = i18n.t('k8s-domain-deployer-create-config-map-failed-error-message',
257-
{configMapName: configMapName, domainNamespace: domainNamespace, error: mapResults.reason});
258-
dialogHelper.closeBusyDialog();
259-
await window.api.ipc.invoke('show-error-message', errTitle, errMessage);
260-
return Promise.resolve(false);
261-
}
250+
const configMapData = this.k8sDomainConfigMapGenerator.generate().join('\n');
251+
wktLogger.debug(configMapData);
252+
const mapResults = await (window.api.ipc.invoke('k8s-apply', kubectlExe, configMapData, kubectlOptions));
253+
if (!mapResults.isSuccess) {
254+
const configMapName = this.project.k8sDomain.modelConfigMapName.value;
255+
const errMessage = i18n.t('k8s-domain-deployer-create-config-map-failed-error-message',
256+
{configMapName: configMapName, domainNamespace: domainNamespace, error: mapResults.reason});
257+
dialogHelper.closeBusyDialog();
258+
await window.api.ipc.invoke('show-error-message', errTitle, errMessage);
259+
return Promise.resolve(false);
262260
}
263261
}
264262

265263
// Deploy domain
266264
busyDialogMessage = i18n.t('k8s-domain-deployer-deploy-in-progress',
267265
{domainName: domainUid, domainNamespace: domainNamespace});
268266
dialogHelper.updateBusyDialog(busyDialogMessage, 13 / totalSteps);
269-
const domainSpecData = this.k8sDomainResourceGenerator.generate().join('\n');
267+
const domainSpecData = new K8sDomainResourceGenerator(this.project.wko.installedVersion.value).generate().join('\n');
270268
const domainResult = await (window.api.ipc.invoke('k8s-apply', kubectlExe, domainSpecData, kubectlOptions));
271269
dialogHelper.closeBusyDialog();
272270
if (domainResult.isSuccess) {
@@ -415,10 +413,9 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp
415413
}
416414
}
417415

418-
if (!this.project.k8sDomain.configMapIsEmpty()) {
416+
if (this.project.settings.targetDomainLocation.value === 'mii') {
419417
validationObject.addField('domain-design-configmap-label',
420418
this.project.k8sDomain.modelConfigMapName.validate(true), domainFormConfig);
421-
// The fields in the table should not require validation since no empty override values should be in this computed table.
422419
}
423420

424421
return validationObject;

webui/src/js/utils/observable-properties.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ define(['knockout', 'utils/common-utilities', 'utils/validation-helper', 'utils/
442442
this.observable.push(this.createArrayItem(this, !!item ? item : {}));
443443
}
444444

445+
removeItemByIndex(index) {
446+
this.observable.splice(index, 1);
447+
}
448+
445449
get observable() {
446450
if (this._observable == null) {
447451
this._observable = ko.observableArray(this.createList(this._defaultValue));

webui/src/js/utils/vz-application-deployer.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ define(['utils/vz-actions-base', 'models/wkt-project', 'models/wkt-console', 'ut
99
'utils/dialog-helper', 'utils/validation-helper', 'utils/vz-application-resource-generator',
1010
'utils/vz-application-project-generator', 'utils/wkt-logger'],
1111
function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, validationHelper,
12-
VerrazzanoApplicationResourceGenerator, VerrazzanoProjectResourceGenerator) {
12+
VerrazzanoApplicationResourceGenerator, VerrazzanoProjectResourceGenerator, wktLogger) {
1313
class VzApplicationDeployer extends VzActionsBase {
1414
constructor() {
1515
super();
@@ -35,7 +35,7 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, vali
3535

3636
const createProject = this.project.vzApplication.useMultiClusterApplication.value && this.project.vzApplication.createProject.value;
3737

38-
const totalSteps = createProject ? 6.0 : 5.0;
38+
const totalSteps = createProject ? 7.0 : 6.0;
3939
const kubectlExe = this.getKubectlExe();
4040
try {
4141
let busyDialogMessage = i18n.t('flow-validate-kubectl-exe-in-progress');
@@ -86,7 +86,25 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, vali
8686
}
8787
}
8888

89-
let step = 4;
89+
// Verify that all referenced components are already deployed in the namespace.
90+
const namespace = this.project.k8sDomain.kubernetesNamespace.value;
91+
busyDialogMessage = i18n.t('flow-checking-vz-app-components-deployed-in-progress');
92+
dialogHelper.updateBusyDialog(busyDialogMessage, 4/totalSteps);
93+
if (!options.skipVzComponentsDeployedCheck) {
94+
const appComponentNames = this.getApplicationComponentNames();
95+
wktLogger.debug('appComponentNames = %s', appComponentNames);
96+
const result = await window.api.ipc.invoke('verify-verrazzano-components-exist',
97+
kubectlExe, appComponentNames, namespace, kubectlOptions);
98+
if (!result.isSuccess) {
99+
dialogHelper.closeBusyDialog();
100+
const errMessage = i18n.t('vz-application-deployer-verify-components-error-message',
101+
{ namespace: namespace, error: result.reason });
102+
await window.api.ipc.invoke('show-error-message', errTitle, errMessage);
103+
return Promise.resolve(false);
104+
}
105+
}
106+
107+
let step = 5;
90108
if (createProject) {
91109
const vzProjectGenerator = new VerrazzanoProjectResourceGenerator();
92110
const projectSpec = vzProjectGenerator.generate().join('\n');
@@ -153,8 +171,16 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, vali
153171
this.project.vzApplication.projectName.validate(true), vzApplicationFormConfig);
154172
}
155173

174+
// Don't allow an application with zero components...
175+
validationObject.addField('vz-application-design-components-label',
176+
validationHelper.validateRequiredField(this.project.vzApplication.components.value), vzApplicationFormConfig);
177+
156178
return validationObject;
157179
}
180+
181+
getApplicationComponentNames() {
182+
return this.project.vzApplication.components.value.map(component => component.name);
183+
}
158184
}
159185
return new VzApplicationDeployer();
160186
});

webui/src/js/utils/vz-component-configmap-generator.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ define(['models/wkt-project', 'utils/k8s-domain-configmap-generator', 'utils/vz-
1515
this.configMapComponentNamespace = '';
1616
}
1717

18+
shouldCreateConfigMap() {
19+
return this.k8sDomainConfigMapGenerator.shouldCreateConfigMap();
20+
}
21+
1822
generate() {
1923
const k8sDomainConfigMap = this.k8sDomainConfigMapGenerator.generate(false);
2024

webui/src/js/utils/vz-component-deployer.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, vali
205205
const vzResourceGenerator = new VerrazzanoComponentResourceGenerator();
206206
const vzConfigMapGenerator = new VerrazzanoComponentConfigMapGenerator();
207207
const components = [ vzResourceGenerator.generate().join('\n') ];
208-
if (!this.project.vzComponent.configMapIsEmpty()) {
208+
if (vzConfigMapGenerator.shouldCreateConfigMap()) {
209209
wktLogger.debug('Adding ConfigMap for component deployment');
210210
components.push(vzConfigMapGenerator.generate().join('\n'));
211211
}
@@ -326,10 +326,9 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, vali
326326
}
327327
}
328328

329-
if (!this.project.vzComponent.configMapIsEmpty()) {
329+
if (this.project.settings.targetDomainLocation.value === 'mii') {
330330
validationObject.addField('domain-design-configmap-label',
331331
this.project.k8sDomain.modelConfigMapName.validate(true), vzComponentFormConfig);
332-
// The fields in the table should not require validation since no empty override values should be in this computed table.
333332
}
334333

335334
return validationObject;

webui/src/js/utils/vz-component-undeployer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper) {
3434
// Prompt user to remove just the domain or the entire domain namespace.
3535
const componentName = this.project.vzComponent.componentName.value;
3636
const componentNamespace = this.project.k8sDomain.kubernetesNamespace.value;
37-
const configMapName = this.project.vzComponent.configMapIsEmpty() ? undefined : this.project.k8sDomain.modelConfigMapName.value;
37+
const configMapName = this.project.settings.targetDomainLocation.value === 'mii' ?
38+
this.project.k8sDomain.modelConfigMapName.value : undefined;
3839

3940
const promptTitle = i18n.t('vz-component-undeployer-remove-namespace-prompt-title');
4041
const promptQuestion = this._getPromptQuestion(componentName, componentNamespace, configMapName);

0 commit comments

Comments
 (0)