diff --git a/src/languageserver/handlers/notificationHandlers.ts b/src/languageserver/handlers/notificationHandlers.ts index a273bdd0..4292ad96 100644 --- a/src/languageserver/handlers/notificationHandlers.ts +++ b/src/languageserver/handlers/notificationHandlers.ts @@ -8,6 +8,7 @@ import { LanguageService, SchemaConfiguration } from '../../languageservice/yaml import { CustomSchemaRequest, DynamicCustomSchemaRequestRegistration, + KubernetesSchemaURLsNotification, SchemaAssociationNotification, SchemaSelectionRequests, VSCodeContentRequestRegistration, @@ -36,10 +37,15 @@ export class NotificationHandlers { this.schemaAssociationNotificationHandler(associations) ); this.connection.onNotification(DynamicCustomSchemaRequestRegistration.type, () => this.dynamicSchemaRequestHandler()); + this.connection.onNotification(KubernetesSchemaURLsNotification.type, (url) => this.kubernetesSchemaURLsNotification(url)); this.connection.onNotification(VSCodeContentRequestRegistration.type, () => this.vscodeContentRequestHandler()); this.connection.onNotification(SchemaSelectionRequests.type, () => this.schemaSelectionRequestHandler()); } + private kubernetesSchemaURLsNotification(urls: string[]): void { + this.yamlSettings.kubernetesSchemaUrls = urls; + } + /** * Received a notification from the client with schema associations from other extensions * Update the associations in the server diff --git a/src/languageserver/handlers/settingsHandlers.ts b/src/languageserver/handlers/settingsHandlers.ts index e2351ea7..2072c9b3 100644 --- a/src/languageserver/handlers/settingsHandlers.ts +++ b/src/languageserver/handlers/settingsHandlers.ts @@ -6,7 +6,7 @@ import { configure as configureHttpRequests, xhr } from 'request-light'; import { Connection, DidChangeConfigurationNotification, DocumentFormattingRequest } from 'vscode-languageserver'; import { convertErrorToTelemetryMsg } from '../../languageservice/utils/objects'; import { isRelativePath, relativeToAbsolutePath } from '../../languageservice/utils/paths'; -import { checkSchemaURI, JSON_SCHEMASTORE_URL, KUBERNETES_SCHEMA_URL } from '../../languageservice/utils/schemaUrls'; +import { checkSchemaURI, JSON_SCHEMASTORE_URL } from '../../languageservice/utils/schemaUrls'; import { LanguageService, LanguageSettings, SchemaPriority } from '../../languageservice/yamlLanguageService'; import { SchemaSelectionRequests } from '../../requestTypes'; import { Settings, SettingsState } from '../../yamlSettings'; @@ -323,7 +323,7 @@ export class SettingsHandler { * @param languageSettings current server settings */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - private configureSchemas( + public configureSchemas( uri: string, fileMatch: string[], schema: unknown, @@ -338,14 +338,15 @@ export class SettingsHandler { languageSettings.schemas.push({ uri, fileMatch: fileMatch, schema: schema, priority: priorityLevel }); } - if (fileMatch.constructor === Array && uri === KUBERNETES_SCHEMA_URL) { - fileMatch.forEach((url) => { - this.yamlSettings.specificValidatorPaths.push(url); - }); - } else if (uri === KUBERNETES_SCHEMA_URL) { - this.yamlSettings.specificValidatorPaths.push(fileMatch); + if (this.yamlSettings.kubernetesSchemaUrls.includes(uri)) { + if (fileMatch.constructor === Array) { + fileMatch.forEach((url) => { + this.yamlSettings.specificValidatorPaths.push(url); + }); + } else { + this.yamlSettings.specificValidatorPaths.push(fileMatch); + } } - return languageSettings; } } diff --git a/src/requestTypes.ts b/src/requestTypes.ts index 5cdffa1c..2c8f99c0 100644 --- a/src/requestTypes.ts +++ b/src/requestTypes.ts @@ -44,6 +44,11 @@ export namespace DynamicCustomSchemaRequestRegistration { export const type: NotificationType = new NotificationType('yaml/registerCustomSchemaRequest'); } +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace KubernetesSchemaURLsNotification { + export const type: NotificationType = new NotificationType('yaml/kubernetesSchemaURLs'); +} + export namespace VSCodeContentRequestRegistration { export const type: NotificationType = new NotificationType('yaml/registerContentRequest'); } diff --git a/src/yamlSettings.ts b/src/yamlSettings.ts index fc026034..7ef967a3 100644 --- a/src/yamlSettings.ts +++ b/src/yamlSettings.ts @@ -4,7 +4,7 @@ import { ISchemaAssociations } from './requestTypes'; import { URI } from 'vscode-uri'; import { JSONSchema } from './languageservice/jsonSchema'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { JSON_SCHEMASTORE_URL } from './languageservice/utils/schemaUrls'; +import { JSON_SCHEMASTORE_URL, KUBERNETES_SCHEMA_URL } from './languageservice/utils/schemaUrls'; import { YamlVersion } from './languageservice/parser/yamlParser07'; // Client settings interface to grab settings relevant for the language server @@ -77,6 +77,7 @@ export class SettingsState { customTags = []; schemaStoreEnabled = true; schemaStoreUrl = JSON_SCHEMASTORE_URL; + kubernetesSchemaUrls = [KUBERNETES_SCHEMA_URL]; indentation: string | undefined = undefined; disableAdditionalProperties = false; disableDefaultProperties = false; diff --git a/test/schemaValidation.test.ts b/test/schemaValidation.test.ts index 71268db7..f3c4c278 100644 --- a/test/schemaValidation.test.ts +++ b/test/schemaValidation.test.ts @@ -21,11 +21,13 @@ import { Diagnostic, DiagnosticSeverity, Position } from 'vscode-languageserver- import { expect } from 'chai'; import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings'; import { ValidationHandler } from '../src/languageserver/handlers/validationHandlers'; -import { LanguageService } from '../src/languageservice/yamlLanguageService'; +import { LanguageService, SchemaPriority } from '../src/languageservice/yamlLanguageService'; import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; import { IProblem } from '../src/languageservice/parser/jsonParser07'; import { JSONSchema } from '../src/languageservice/jsonSchema'; import { TestTelemetry } from './utils/testsTypes'; +import { SettingsHandler } from '../src/languageserver/handlers/settingsHandlers'; +import { Connection } from 'vscode-languageserver'; describe('Validation Tests', () => { let languageSettingsSetup: ServiceSetup; @@ -1108,6 +1110,11 @@ obj: }); describe('Test with custom kubernetes schemas', function () { + afterEach(() => { + // remove Kubernetes setting not to affect next tests + languageService.configure(languageSettingsSetup.withKubernetes(false).languageSettings); + yamlSettings.specificValidatorPaths = []; + }); it('Test that properties that match multiple enums get validated properly', (done) => { languageService.configure(languageSettingsSetup.withKubernetes().languageSettings); yamlSettings.specificValidatorPaths = ['*.yml', '*.yaml']; @@ -1153,6 +1160,86 @@ obj: }) .then(done, done); }); + + it('single custom kubernetes schema version should return validation errors', async () => { + const customKubernetesSchemaVersion = + 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.26.1-standalone-strict/all.json'; + yamlSettings.kubernetesSchemaUrls = [customKubernetesSchemaVersion]; + const settingsHandler = new SettingsHandler({} as Connection, languageService, yamlSettings, validationHandler, telemetry); + const initialSettings = languageSettingsSetup.withKubernetes(true).languageSettings; + const kubernetesSettings = settingsHandler.configureSchemas( + customKubernetesSchemaVersion, + ['*k8s.yml'], + undefined, + initialSettings, + SchemaPriority.SchemaAssociation + ); + languageService.configure(kubernetesSettings); + const content = `apiVersion: apps/v1\nkind: Deployment\nfoo: bar`; + const result = await parseSetup(content, 'invalid-k8s.yml'); + expect(result.length).to.eq(1); + expect(result[0].message).to.eq('Property foo is not allowed.'); + }); + + it('single openshift schema version should return validation errors', async () => { + const customOpenshiftSchemaVersion = + 'https://raw.githubusercontent.com/tricktron/CRDs-catalog/f-openshift-v4.11/openshift.io/v4.11/all.json'; + yamlSettings.kubernetesSchemaUrls = [customOpenshiftSchemaVersion]; + const settingsHandler = new SettingsHandler({} as Connection, languageService, yamlSettings, validationHandler, telemetry); + const initialSettings = languageSettingsSetup.withKubernetes(true).languageSettings; + const kubernetesSettings = settingsHandler.configureSchemas( + customOpenshiftSchemaVersion, + ['*oc.yml'], + undefined, + initialSettings, + SchemaPriority.SchemaAssociation + ); + languageService.configure(kubernetesSettings); + const content = `apiVersion: route.openshift.io/v1\nkind: Route\nfoo: bar`; + const result = await parseSetup(content, 'invalid-oc.yml'); + expect(result.length).to.eq(2); + expect(result[0].message).to.eq('Missing property "spec".'); + expect(result[1].message).to.eq('Property foo is not allowed.'); + }); + + it('custom kubernetes schema version and openshift custom resource definition (CRD) should return validation errors', async () => { + const customKubernetesSchemaVersion = + 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.26.1-standalone-strict/all.json'; + const customOpenshiftSchemaVersion = + 'https://raw.githubusercontent.com/tricktron/CRDs-catalog/f-openshift-v4.11/openshift.io/v4.11/all.json'; + yamlSettings.kubernetesSchemaUrls = [customKubernetesSchemaVersion, customOpenshiftSchemaVersion]; + const settingsHandler = new SettingsHandler({} as Connection, languageService, yamlSettings, validationHandler, telemetry); + const initialSettings = languageSettingsSetup.withKubernetes(true).languageSettings; + const kubernetesSettings = settingsHandler.configureSchemas( + customKubernetesSchemaVersion, + ['*k8s.yml'], + undefined, + initialSettings, + SchemaPriority.SchemaAssociation + ); + const openshiftSettings = settingsHandler.configureSchemas( + customOpenshiftSchemaVersion, + ['*oc.yml'], + undefined, + initialSettings, + SchemaPriority.SchemaAssociation + ); + languageService.configure(kubernetesSettings); + languageService.configure(openshiftSettings); + const kubernetes = `apiVersion: apps/v1\nkind: Deployment\nfoo: bar`; + const openshift = `apiVersion: route.openshift.io/v1\nkind: Route\nbaz: abc`; + + const kubernetesResult = await parseSetup(kubernetes, 'invalid-k8s.yml'); + + expect(kubernetesResult.length).to.eq(1); + expect(kubernetesResult[0].message).to.eq('Property foo is not allowed.'); + + const openshiftResult = await parseSetup(openshift, 'invalid-oc.yml'); + + expect(openshiftResult.length).to.eq(2); + expect(openshiftResult[0].message).to.eq('Missing property "spec".'); + expect(openshiftResult[1].message).to.eq('Property baz is not allowed.'); + }); }); // https://github.com/redhat-developer/yaml-language-server/issues/118