diff --git a/README.md b/README.md index e147e0d36..b412f936e 100755 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The following settings are supported: - `yaml.hover`: Enable/disable hover - `yaml.completion`: Enable/disable autocompletion - `yaml.schemas`: Helps you associate schemas with files in a glob pattern +- `yaml.kubernetesVersion`: Kubernetes version used to build the schema URL when `yaml.schemas` maps files to the `Kubernetes` keyword. - `yaml.disableSchemaDetection`: Disables schema detection for matching YAML files. Modelines still apply. - `yaml.schemaStore.enable`: When set to true the YAML language server will pull in all available schemas from [JSON Schema Store](https://www.schemastore.org) - `yaml.schemaStore.url`: URL of a schema store catalog to use when downloading schemas. diff --git a/src/languageserver/handlers/settingsHandlers.ts b/src/languageserver/handlers/settingsHandlers.ts index 7ee11527d..37110e282 100644 --- a/src/languageserver/handlers/settingsHandlers.ts +++ b/src/languageserver/handlers/settingsHandlers.ts @@ -6,12 +6,7 @@ import { configure as configureHttpRequests, xhr } from 'request-light'; import { Connection, DidChangeConfigurationNotification, DocumentFormattingRequest } from 'vscode-languageserver'; import { CodeLensRefreshRequest } from 'vscode-languageserver-protocol'; import { isRelativePath, relativeToAbsolutePath } from '../../languageservice/utils/paths'; -import { - checkSchemaURI, - EMPTY_SCHEMA_URL, - JSON_SCHEMASTORE_URL, - KUBERNETES_SCHEMA_URL, -} from '../../languageservice/utils/schemaUrls'; +import { checkSchemaURI, EMPTY_SCHEMA_URL, isKubernetes, JSON_SCHEMASTORE_URL } from '../../languageservice/utils/schemaUrls'; import { equals } from '../../languageservice/utils/objects'; import { LanguageService, LanguageSettings, SchemaPriority, SchemasSettings } from '../../languageservice/yamlLanguageService'; import { SchemaSelectionRequests } from '../../requestTypes'; @@ -95,6 +90,13 @@ export class SettingsHandler { if (Object.prototype.hasOwnProperty.call(settings.yaml, 'hoverSchemaSource')) { this.yamlSettings.yamlHoverSchemaSource = settings.yaml.hoverSchemaSource; } + if (Object.prototype.hasOwnProperty.call(settings.yaml, 'kubernetesVersion')) { + const match = + typeof settings.yaml.kubernetesVersion === 'string' + ? /^v?(\d+)\.(\d+)\.(\d+)$/i.exec(settings.yaml.kubernetesVersion.trim()) + : undefined; + this.yamlSettings.kubernetesVersion = match ? `v${match[1]}.${match[2]}.${match[3]}` : undefined; + } this.yamlSettings.yamlDisableSchemaDetection = Array.isArray(settings.yaml.disableSchemaDetection) ? settings.yaml.disableSchemaDetection : settings.yaml.disableSchemaDetection @@ -179,7 +181,7 @@ export class SettingsHandler { const schemaObj = { fileMatch: Array.isArray(globPattern) ? globPattern : [globPattern], - uri: checkSchemaURI(this.yamlSettings.workspaceFolders, this.yamlSettings.workspaceRoot, uri, this.telemetry), + uri, }; this.yamlSettings.schemaConfigurationSettings.push(schemaObj); } @@ -411,7 +413,13 @@ export class SettingsHandler { languageSettings: LanguageSettings, priorityLevel: number ): LanguageSettings { - uri = checkSchemaURI(this.yamlSettings.workspaceFolders, this.yamlSettings.workspaceRoot, uri, this.telemetry); + uri = checkSchemaURI( + this.yamlSettings.workspaceFolders, + this.yamlSettings.workspaceRoot, + uri, + this.telemetry, + this.yamlSettings.kubernetesVersion + ); if (schema === null) { languageSettings.schemas.push({ uri, fileMatch: fileMatch, priority: priorityLevel }); @@ -419,12 +427,14 @@ 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 (isKubernetes(uri)) { + if (Array.isArray(fileMatch)) { + fileMatch.forEach((pattern) => { + this.yamlSettings.specificValidatorPaths.push(pattern); + }); + } else { + this.yamlSettings.specificValidatorPaths.push(fileMatch); + } } return languageSettings; diff --git a/src/languageservice/services/k8sSchemaUtil.ts b/src/languageservice/services/k8sSchemaUtil.ts index f29413221..828c05c4c 100644 --- a/src/languageservice/services/k8sSchemaUtil.ts +++ b/src/languageservice/services/k8sSchemaUtil.ts @@ -3,7 +3,6 @@ import { SingleYAMLDocument } from '../parser/yamlParser07'; import { ResolvedSchema } from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService'; import { JSONSchema } from '../jsonSchema'; -import { BASE_KUBERNETES_SCHEMA_URL } from '../utils/schemaUrls'; /** * Attempt to retrieve the schema for a given YAML document based on the Kubernetes GroupVersionKind (GVK). @@ -18,13 +17,14 @@ import { BASE_KUBERNETES_SCHEMA_URL } from '../utils/schemaUrls'; export function autoDetectKubernetesSchema( doc: SingleYAMLDocument | JSONDocument, kubernetesSchema: ResolvedSchema, + kubernetesSchemaURI: string, crdCatalogURI: string ): string | undefined { const gvk = getGroupVersionKindFromDocument(doc); if (!gvk || !gvk.group || !gvk.version || !gvk.kind) { return undefined; } - const builtinResource = autoDetectBuiltinResource(gvk, kubernetesSchema); + const builtinResource = autoDetectBuiltinResource(gvk, kubernetesSchema, kubernetesSchemaURI); if (builtinResource) { return builtinResource; } @@ -35,7 +35,11 @@ export function autoDetectKubernetesSchema( return undefined; } -function autoDetectBuiltinResource(gvk: GroupVersionKind, kubernetesSchema: ResolvedSchema): string | undefined { +function autoDetectBuiltinResource( + gvk: GroupVersionKind, + kubernetesSchema: ResolvedSchema, + kubernetesSchemaURI: string +): string | undefined { const { group, version, kind } = gvk; const groupWithoutK8sIO = group.replace('.k8s.io', '').replace('rbac.authorization', 'rbac'); @@ -57,7 +61,9 @@ function autoDetectBuiltinResource(gvk: GroupVersionKind, kubernetesSchema: Reso }); if (matchingBuiltin) { - return BASE_KUBERNETES_SCHEMA_URL + matchingBuiltin; + const lastSlash = kubernetesSchemaURI.lastIndexOf('/'); + const baseURL = lastSlash === -1 ? null : kubernetesSchemaURI.substring(0, lastSlash + 1); + return baseURL ? baseURL + matchingBuiltin : undefined; } return undefined; diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index ff3d6c3a3..3c0f16f9a 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -32,7 +32,7 @@ import Ajv2020 from 'ajv/dist/2020'; import type { Localize } from 'ajv-i18n/localize/types'; import * as Json from 'jsonc-parser'; import { parse } from 'yaml'; -import { CRD_CATALOG_URL, EMPTY_SCHEMA_URL, KUBERNETES_SCHEMA_URL } from '../utils/schemaUrls'; +import { CRD_CATALOG_URL, EMPTY_SCHEMA_URL, isKubernetes } from '../utils/schemaUrls'; import { autoDetectKubernetesSchema } from './k8sSchemaUtil'; const ajv4 = new Ajv4({ allErrors: true }); @@ -1100,18 +1100,21 @@ export class YAMLSchemaService extends JSONSchemaService { const seen: { [schemaId: string]: boolean } = Object.create(null); const schemas: string[] = []; let k8sAllSchema: ResolvedSchema = undefined; + let k8sSchemaUrl: string | undefined = undefined; for (const entry of this.filePatternAssociations) { if (entry.matchesPattern(resource)) { for (const schemaId of entry.getURIs()) { if (!seen[schemaId]) { - if (this.yamlSettings?.kubernetesCRDStoreEnabled && schemaId === KUBERNETES_SCHEMA_URL) { + if (this.yamlSettings?.kubernetesCRDStoreEnabled && isKubernetes(schemaId)) { if (!k8sAllSchema) { - k8sAllSchema = await this.getResolvedSchema(KUBERNETES_SCHEMA_URL); + k8sSchemaUrl = schemaId; + k8sAllSchema = await this.getResolvedSchema(schemaId); } const kubeSchema = autoDetectKubernetesSchema( doc, k8sAllSchema, + k8sSchemaUrl ?? schemaId, this.yamlSettings.kubernetesCRDStoreUrl ?? CRD_CATALOG_URL ); if (kubeSchema) { diff --git a/src/languageservice/services/yamlValidation.ts b/src/languageservice/services/yamlValidation.ts index 7c4b10eb2..ca2d5900c 100644 --- a/src/languageservice/services/yamlValidation.ts +++ b/src/languageservice/services/yamlValidation.ts @@ -20,6 +20,8 @@ import { AdditionalValidator } from './validation/types'; import { UnusedAnchorsValidator } from './validation/unused-anchors'; import { YAMLStyleValidator } from './validation/yaml-style'; import { MapKeyOrderValidator } from './validation/map-key-order'; +import { getSchemaFromModeline } from './modelineUtil'; +import { isKubernetes as isKubernetesSchemaURI } from '../utils/schemaUrls'; /** * Convert a YAMLDocDiagnostic to a language server Diagnostic @@ -80,6 +82,7 @@ export class YAMLValidation { } const validationResult = []; + let suppressKubernetesMatchesMultiple = isKubernetes; try { const yamlDocument: YAMLDocument = yamlDocumentsCache.getYamlDocument( textDocument, @@ -89,7 +92,9 @@ export class YAMLValidation { let index = 0; for (const currentYAMLDoc of yamlDocument.documents) { - currentYAMLDoc.isKubernetes = isKubernetes; + const currentDocumentIsKubernetes = isKubernetes || this.hasKubernetesModelineSchema(currentYAMLDoc); + currentYAMLDoc.isKubernetes = currentDocumentIsKubernetes; + suppressKubernetesMatchesMultiple = suppressKubernetesMatchesMultiple || currentDocumentIsKubernetes; currentYAMLDoc.currentDocIndex = index; currentYAMLDoc.disableAdditionalProperties = this.disableAdditionalProperties; currentYAMLDoc.uri = textDocument.uri; @@ -122,7 +127,7 @@ export class YAMLValidation { * 'Matches many schemas' error for kubernetes * for a better user experience. */ - if (isKubernetes && err.message === this.MATCHES_MULTIPLE) { + if (suppressKubernetesMatchesMultiple && err.message === this.MATCHES_MULTIPLE) { continue; } @@ -166,6 +171,12 @@ export class YAMLValidation { } ); } + + private hasKubernetesModelineSchema(currentYAMLDoc: SingleYAMLDocument): boolean { + const schemaFromModeline = getSchemaFromModeline(currentYAMLDoc); + return typeof schemaFromModeline === 'string' && isKubernetesSchemaURI(schemaFromModeline); + } + private runAdditionalValidators(document: TextDocument, yarnDoc: SingleYAMLDocument): Diagnostic[] { const result = []; diff --git a/src/languageservice/utils/schemaUrls.ts b/src/languageservice/utils/schemaUrls.ts index bdccf0405..bae5b431b 100644 --- a/src/languageservice/utils/schemaUrls.ts +++ b/src/languageservice/utils/schemaUrls.ts @@ -6,22 +6,34 @@ import { JSONSchema, JSONSchemaRef } from '../jsonSchema'; import { isBoolean } from './objects'; import { isRelativePath, relativeToAbsolutePath } from './paths'; -export const BASE_KUBERNETES_SCHEMA_URL = - 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.1-standalone-strict/'; -export const KUBERNETES_SCHEMA_URL = BASE_KUBERNETES_SCHEMA_URL + 'all.json'; +export const DEFAULT_KUBERNETES_SCHEMA_VERSION = 'v1.34.1'; export const JSON_SCHEMASTORE_URL = 'https://www.schemastore.org/api/json/catalog.json'; export const CRD_CATALOG_URL = 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main'; export const EMPTY_SCHEMA_URL = 'vscode://schemas/empty'; +const KUBERNETES_SCHEMA_URL_PATTERN = + /^https:\/\/raw\.githubusercontent\.com\/yannh\/kubernetes-json-schema\/master\/((?:v(\d+)\.(\d+)\.(\d+))-standalone-strict)\/all\.json$/; + +export function isKubernetes(uri: string): boolean { + if (uri.trim().toLowerCase() === 'kubernetes') return true; + return KUBERNETES_SCHEMA_URL_PATTERN.test(uri); +} + export function checkSchemaURI( workspaceFolders: WorkspaceFolder[], workspaceRoot: URI, uri: string, - telemetry: Telemetry + telemetry: Telemetry, + kubernetesVersion?: string ): string { - if (uri.trim().toLowerCase() === 'kubernetes') { + const k8sKeywordUsed = uri.trim().toLowerCase() === 'kubernetes'; + if (k8sKeywordUsed || KUBERNETES_SCHEMA_URL_PATTERN.test(uri)) { telemetry.send({ name: 'yaml.schema.configured', properties: { kubernetes: true } }); - return KUBERNETES_SCHEMA_URL; + if (k8sKeywordUsed) { + return `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${kubernetesVersion ?? DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`; + } else { + return uri; + } } else if (path.isAbsolute(uri) || /^[a-z]:[\\/]/i.test(uri)) { const localPath = uri.split('#', 2)[0]; return URI.file(localPath).toString() + uri.substring(localPath.length); diff --git a/src/yamlSettings.ts b/src/yamlSettings.ts index 1c1024b6a..527cf3183 100644 --- a/src/yamlSettings.ts +++ b/src/yamlSettings.ts @@ -25,6 +25,7 @@ export interface Settings { url: string; enable: boolean; }; + kubernetesVersion: string; disableDefaultProperties: boolean; disableAdditionalProperties: boolean; suggest: { @@ -90,6 +91,7 @@ export class SettingsState { schemaStoreUrl = JSON_SCHEMASTORE_URL; kubernetesCRDStoreEnabled = true; kubernetesCRDStoreUrl = CRD_CATALOG_URL; + kubernetesVersion: string | undefined = undefined; indentation: string | undefined = undefined; disableAdditionalProperties = false; disableDefaultProperties = false; diff --git a/test/schema.test.ts b/test/schema.test.ts index e0a5df453..0d01155bc 100644 --- a/test/schema.test.ts +++ b/test/schema.test.ts @@ -6,7 +6,7 @@ import * as url from 'url'; import * as path from 'path'; import { XHRResponse, xhr } from 'request-light'; import { MODIFICATION_ACTIONS, SchemaDeletions } from '../src/languageservice/services/yamlSchemaService'; -import { EMPTY_SCHEMA_URL, KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; +import { EMPTY_SCHEMA_URL, DEFAULT_KUBERNETES_SCHEMA_VERSION } from '../src/languageservice/utils/schemaUrls'; import { expect } from 'chai'; import { ServiceSetup } from './utils/serviceSetup'; import { @@ -25,6 +25,8 @@ import { LineCounter } from 'yaml'; import { getSchemaFromModeline } from '../src/languageservice/services/modelineUtil'; import { getGroupVersionKindFromDocument } from '../src/languageservice/services/k8sSchemaUtil'; +const KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`; + const requestServiceMock = function (uri: string): Promise { return Promise.reject(`Resource ${uri} not found.`); }; diff --git a/test/schema2019Validation.test.ts b/test/schema2019Validation.test.ts index 9c08c7761..ec31d70d1 100644 --- a/test/schema2019Validation.test.ts +++ b/test/schema2019Validation.test.ts @@ -8,7 +8,7 @@ import { Diagnostic } from 'vscode-languageserver-types'; import { expect } from 'chai'; import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings'; import { ValidationHandler } from '../src/languageserver/handlers/validationHandlers'; -import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; +import { DEFAULT_KUBERNETES_SCHEMA_VERSION } from '../src/languageservice/utils/schemaUrls'; import { JSONSchema } from '../src/languageservice/jsonSchema'; describe('Validation Tests', () => { @@ -17,6 +17,8 @@ describe('Validation Tests', () => { let yamlSettings: SettingsState; let schemaProvider: TestCustomSchemaProvider; + const KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`; + const toContent = (data: unknown): string => JSON.stringify(data, null, 2); before(() => { diff --git a/test/schema2020Validation.test.ts b/test/schema2020Validation.test.ts index 8f516e29b..b94f033ee 100644 --- a/test/schema2020Validation.test.ts +++ b/test/schema2020Validation.test.ts @@ -8,7 +8,7 @@ import { Diagnostic } from 'vscode-languageserver-types'; import { expect } from 'chai'; import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings'; import { ValidationHandler } from '../src/languageserver/handlers/validationHandlers'; -import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; +import { DEFAULT_KUBERNETES_SCHEMA_VERSION } from '../src/languageservice/utils/schemaUrls'; import { JSONSchema } from '../src/languageservice/jsonSchema'; describe('Validation Tests', () => { @@ -17,6 +17,8 @@ describe('Validation Tests', () => { let yamlSettings: SettingsState; let schemaProvider: TestCustomSchemaProvider; + const KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`; + const toContent = (data: unknown): string => JSON.stringify(data, null, 2); before(() => { diff --git a/test/schemaValidation.test.ts b/test/schemaValidation.test.ts index 5c1c18b57..0c8cdebb5 100644 --- a/test/schemaValidation.test.ts +++ b/test/schemaValidation.test.ts @@ -22,11 +22,13 @@ import { expect } from 'chai'; import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings'; import { ValidationHandler } from '../src/languageserver/handlers/validationHandlers'; import { LanguageService } from '../src/languageservice/yamlLanguageService'; -import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; import { IProblem } from '../src/languageservice/parser/schemaValidation/baseValidator'; import { JSONSchema } from '../src/languageservice/jsonSchema'; import { TestTelemetry } from './utils/testsTypes'; import { ErrorCode } from 'vscode-json-languageservice'; +import { DEFAULT_KUBERNETES_SCHEMA_VERSION } from '../src/languageservice/utils/schemaUrls'; + +const KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`; describe('Validation Tests', () => { let languageSettingsSetup: ServiceSetup; @@ -1396,6 +1398,75 @@ obj: .then(done, done); }); + it('does not report error for direct Kubernetes standalone-strict/all.json schema associations', async () => { + const schemaUri = + 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.9-standalone-strict/all.json'; + languageService.configure( + new ServiceSetup().withValidate().withSchemaFileMatch({ uri: schemaUri, fileMatch: ['*.yaml'] }).languageSettings + ); + yamlSettings.specificValidatorPaths = ['*.yaml']; + + try { + const result = await parseSetup( + `apiVersion: v1 +kind: Service +metadata: + name: longhorn-ui-nodeport + namespace: longhorn-system +spec: + type: NodePort + ports: + - port: 80 + targetPort: 80 + nodePort: 30080 + selector: + app: longhorn-ui`, + 'file://~/Desktop/vscode-yaml/service.yaml' + ); + + expect(result.map((diagnostic) => diagnostic.message)).not.include( + 'Matches multiple schemas when only one must validate.' + ); + expect(result.map((diagnostic) => diagnostic.message)).deep.equal([]); + } finally { + yamlSettings.specificValidatorPaths = []; + } + }); + + it('does not report error for direct Kubernetes standalone-strict/all.json modelines', async () => { + const schemaUri = + 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.9-standalone-strict/all.json'; + languageService.configure(languageSettingsSetup.withKubernetes(false).languageSettings); + yamlSettings.specificValidatorPaths = []; + + try { + const result = await parseSetup( + `# yaml-language-server: $schema=${schemaUri} +apiVersion: v1 +kind: Service +metadata: + name: longhorn-ui-nodeport + namespace: longhorn-system +spec: + type: NodePort + ports: + - port: 80 + targetPort: 80 + nodePort: 30080 + selector: + app: longhorn-ui`, + 'file://~/Desktop/vscode-yaml/service.yaml' + ); + + expect(result.map((diagnostic) => diagnostic.message)).not.include( + 'Matches multiple schemas when only one must validate.' + ); + expect(result.map((diagnostic) => diagnostic.message)).deep.equal([]); + } finally { + languageService.configure(languageSettingsSetup.withKubernetes().languageSettings); + } + }); + it('Test that it validates against the correct schema based on the GroupVersionKind', (done) => { languageService.configure( languageSettingsSetup.withKubernetes().withSchemaFileMatch({ uri: KUBERNETES_SCHEMA_URL, fileMatch: ['*.yml', '*.yaml'] }) diff --git a/test/settingsHandlers.test.ts b/test/settingsHandlers.test.ts index fb9842e37..b9e59689d 100644 --- a/test/settingsHandlers.test.ts +++ b/test/settingsHandlers.test.ts @@ -192,6 +192,56 @@ describe('Settings Handlers Tests', () => { }); }); + describe('Settings for Kubernetes version should ', () => { + it('accepts versions with or without a v prefix', async () => { + const settingsHandler = new SettingsHandler( + connection, + languageService as unknown as LanguageService, + settingsState, + validationHandler as unknown as ValidationHandler, + {} as Telemetry + ); + + workspaceStub.getConfiguration + .onFirstCall() + .resolves([{ kubernetesVersion: '1.36.1' }, {}, {}, {}, {}]) + .onSecondCall() + .resolves([{ kubernetesVersion: 'v1.37.2' }, {}, {}, {}, {}]); + + await settingsHandler.pullConfiguration(); + expect(settingsState.kubernetesVersion).to.equal('v1.36.1'); + + await settingsHandler.pullConfiguration(); + expect(settingsState.kubernetesVersion).to.equal('v1.37.2'); + }); + + it('resolves to undefined for invalid or removed values so the default version is used', async () => { + const settingsHandler = new SettingsHandler( + connection, + languageService as unknown as LanguageService, + settingsState, + validationHandler as unknown as ValidationHandler, + {} as Telemetry + ); + workspaceStub.getConfiguration + .onFirstCall() + .resolves([{ kubernetesVersion: '1.36.1' }, {}, {}, {}, {}]) + .onSecondCall() + .resolves([{ kubernetesVersion: 'invalid' }, {}, {}, {}, {}]) + .onThirdCall() + .resolves([{}, {}, {}, {}, {}]); + + await settingsHandler.pullConfiguration(); + expect(settingsState.kubernetesVersion).to.equal('v1.36.1'); + + await settingsHandler.pullConfiguration(); + expect(settingsState.kubernetesVersion).to.equal(undefined); + + await settingsHandler.pullConfiguration(); + expect(settingsState.kubernetesVersion).to.equal(undefined); + }); + }); + describe('Settings for file associations should ', () => { it('reflect to settings state', async () => { const settingsHandler = new SettingsHandler( @@ -351,12 +401,13 @@ describe('Settings Handlers Tests', () => { const testSchemaFileMatch = ['foo/*.yml']; async function configureSchemaSettingsTest(): Promise { + const telemetry = { send: sinon.stub(), sendError: sinon.stub() } as unknown as Telemetry; const settingsHandler = new SettingsHandler( connection, languageService, settingsState, validationHandler as unknown as ValidationHandler, - {} as Telemetry + telemetry ); const configureSpy = sinon.spy(languageService, 'configure'); await settingsHandler.pullConfiguration(); @@ -418,6 +469,27 @@ describe('Settings Handlers Tests', () => { priority: SchemaPriority.Settings, }); }); + + it('Schema Settings should treat direct Kubernetes standalone-strict/all.json URLs as Kubernetes associations', async () => { + xhrStub.resolves({ + responseText: '{"schemas":[]}', + }); + const schemaUri = + 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.9-standalone-strict/all.json'; + const schemas = {}; + schemas[schemaUri] = ['*.yaml']; + workspaceStub.getConfiguration.resolves([{ schemas }, {}, {}, {}]); + const configureSpy = await configureSchemaSettingsTest(); + + expect(configureSpy.schemas).deep.include({ + uri: schemaUri, + fileMatch: ['*.yaml'], + schema: undefined, + priority: SchemaPriority.Settings, + }); + expect(settingsState.specificValidatorPaths).deep.include('*.yaml'); + }); + it('Schema Settings should normalize multiple absolute local paths for the same file', async () => { xhrStub.resolves({ responseText: '{"schemas":[]}', diff --git a/test/yamlSchemaService.test.ts b/test/yamlSchemaService.test.ts index 78190f16c..5d90fd4ec 100644 --- a/test/yamlSchemaService.test.ts +++ b/test/yamlSchemaService.test.ts @@ -10,7 +10,10 @@ import * as url from 'url'; import * as SchemaService from '../src/languageservice/services/yamlSchemaService'; import { parse } from '../src/languageservice/parser/yamlParser07'; import { SettingsState } from '../src/yamlSettings'; -import { BASE_KUBERNETES_SCHEMA_URL, getSchemaUrls, KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls'; +import { DEFAULT_KUBERNETES_SCHEMA_VERSION, getSchemaUrls } from '../src/languageservice/utils/schemaUrls'; + +const BASE_KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/`; +const KUBERNETES_SCHEMA_URL = BASE_KUBERNETES_SCHEMA_URL + 'all.json'; const expect = chai.expect; chai.use(sinonChai);