diff --git a/src/azure_auth.ts b/src/azure_auth.ts index 085c8bca37a..213a60bb457 100644 --- a/src/azure_auth.ts +++ b/src/azure_auth.ts @@ -82,7 +82,7 @@ export class AzureAuth implements Authenticator { try { output = proc.execSync(cmd); } catch (err) { - throw new Error('Failed to refresh token: ' + err.message); + throw new Error('Failed to refresh token: ' + err); } const resultObj = JSON.parse(output); diff --git a/src/gcp_auth_test.ts b/src/gcp_auth_test.ts index 04f0a438cb7..5849a7c1eee 100644 --- a/src/gcp_auth_test.ts +++ b/src/gcp_auth_test.ts @@ -5,8 +5,10 @@ import { join } from 'path'; import { User, Cluster } from './config_types'; import { GoogleCloudPlatformAuth } from './gcp_auth'; import { KubeConfig } from './config'; +import { HttpMethod, RequestContext } from './gen'; describe('GoogleCloudPlatformAuth', () => { + const testUrl1 = 'https://test-gcp.com'; var auth: GoogleCloudPlatformAuth; beforeEach(() => { auth = new GoogleCloudPlatformAuth(); @@ -53,18 +55,18 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } - opts.headers = []; - opts.headers.Host = 'foo.com'; - await config.applyToRequest(opts); - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); - }); + requestContext.setUrl('foo.com') + //opts.headers.Host = 'foo.com'; + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); + }); it('should populate from auth provider without expirty', async () => { const config = new KubeConfig(); @@ -80,12 +82,12 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } }); @@ -103,10 +105,10 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) - await config.applyToRequest(opts); - expect(opts.rejectUnauthorized).to.equal(false); + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()['rejectUnauthorized']).to.equal(false); }); it('should not set rejectUnauthorized if skipTLSVerify is not set', async () => { @@ -125,10 +127,10 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) - await config.applyToRequest(opts); - expect(opts.rejectUnauthorized).to.equal(undefined); + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()['rejectUnauthorized']).to.equal(undefined); }); it('should throw with expired token and no cmd', () => { @@ -144,9 +146,9 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) - return expect(config.applyToRequest(opts)).to.eventually.be.rejectedWith('Token is expired!'); + return expect(config.applySecurityAuthentication(requestContext)).to.eventually.be.rejectedWith('Token is expired!'); }); it('should throw with bad command', () => { @@ -164,8 +166,8 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - return expect(config.applyToRequest(opts)).to.eventually.be.rejectedWith(/Failed to refresh token/); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + return expect(config.applySecurityAuthentication(requestContext)).to.eventually.be.rejectedWith(/Failed to refresh token/); }); it('should exec with expired token', async () => { @@ -191,11 +193,11 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } }); it('should exec without access-token', async () => { @@ -220,11 +222,11 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } }); it('should exec without access-token', async () => { @@ -249,11 +251,11 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } }); it('should exec succesfully with spaces in cmd', async () => { @@ -278,11 +280,11 @@ describe('GoogleCloudPlatformAuth', () => { }, } as User, ); - const opts = {} as requestlib.Options; - await config.applyToRequest(opts); - expect(opts.headers).to.not.be.undefined; - if (opts.headers) { - expect(opts.headers.Authorization).to.equal(`Bearer ${token}`); + let requestContext = new RequestContext(testUrl1, HttpMethod.GET) + await config.applySecurityAuthentication(requestContext); + expect(requestContext.getHeaders()).to.not.be.undefined; + if (requestContext.getHeaders()) { + expect(requestContext.getHeaders()['Authorization']).to.equal(`Bearer ${token}`); } }); }); diff --git a/src/index.ts b/src/index.ts index a2aa4d5ce90..7df7307973e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,6 @@ export * from './yaml'; export * from './log'; export * from './informer'; export * from './top'; -export * from './object'; export * from './cp'; export * from './patch'; export * from './metrics'; diff --git a/src/integration_test.ts b/src/integration_test.ts index 4b312f2580d..daa702b2e26 100644 --- a/src/integration_test.ts +++ b/src/integration_test.ts @@ -42,7 +42,7 @@ describe('FullRequest', () => { .get('/api/v1/namespaces/default/pods') .reply(200, result); - const promise = k8sApi.listNamespacedPod('default'); + const promise = k8sApi.listNamespacedPod({namespace:'default'}); return expect(promise) .to.eventually.have.property('body') diff --git a/src/metrics.ts b/src/metrics.ts index ed538761204..5c988f96e6f 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -4,6 +4,7 @@ import { KubeConfig } from './config'; import { HttpException, ApiException, V1Status } from './gen'; import { ObjectSerializer } from './util'; import fetch from 'node-fetch' +import { RequestOptions } from 'https'; export interface Usage { cpu: string; @@ -85,9 +86,9 @@ export class Metrics { throw new Error('No currently active cluster'); } - const requestOptions: request.Options = { + const requestOptions: RequestOptions = { method: 'GET', - uri: cluster.server + path, + servername: cluster.server + path, }; const requestURL = cluster.server + path; diff --git a/src/object.ts b/src/object.ts deleted file mode 100644 index 350b8a733c9..00000000000 --- a/src/object.ts +++ /dev/null @@ -1,633 +0,0 @@ -import * as http from 'http'; -import request = require('request'); -import { - ApisApi, - HttpError, - ObjectSerializer, - V1APIResource, - V1APIResourceList, - V1DeleteOptions, - V1Status, -} from './api'; -import { KubeConfig } from './config'; -import { KubernetesListObject, KubernetesObject } from './types'; - -/** Union type of body types returned by KubernetesObjectApi. */ -type KubernetesObjectResponseBody = - | KubernetesObject - | KubernetesListObject - | V1Status - | V1APIResourceList; - -/** Kubernetes API verbs. */ -type KubernetesApiAction = 'create' | 'delete' | 'patch' | 'read' | 'list' | 'replace'; - -/** - * Valid Content-Type header values for patch operations. See - * https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/ - * for details. - */ -enum KubernetesPatchStrategies { - /** Diff-like JSON format. */ - JsonPatch = 'application/json-patch+json', - /** Simple merge. */ - MergePatch = 'application/merge-patch+json', - /** Merge with different strategies depending on field metadata. */ - StrategicMergePatch = 'application/strategic-merge-patch+json', -} - -/** - * Dynamically construct Kubernetes API request URIs so client does not have to know what type of object it is acting - * on. - */ -export class KubernetesObjectApi extends ApisApi { - /** - * Create a KubernetesObjectApi object from the provided KubeConfig. This method should be used rather than - * [[KubeConfig.makeApiClient]] so we can properly determine the default namespace if one is provided by the current - * context. - * - * @param kc Valid Kubernetes config - * @return Properly instantiated [[KubernetesObjectApi]] object - */ - public static makeApiClient(kc: KubeConfig): KubernetesObjectApi { - const client = kc.makeApiClient(KubernetesObjectApi); - client.setDefaultNamespace(kc); - return client; - } - - /** Initialize the default namespace. May be overwritten by context. */ - protected defaultNamespace: string = 'default'; - - /** Cache resource API response. */ - protected apiVersionResourceCache: Record = {}; - - /** - * Create any Kubernetes resource. - * @param spec Kubernetes resource spec. - * @param pretty If \'true\', then the output is pretty printed. - * @param dryRun When present, indicates that modifications should not be persisted. An invalid or unrecognized - * dryRun directive will result in an error response and no further processing of the request. Valid values - * are: - All: all dry run stages will be processed - * @param fieldManager fieldManager is a name associated with the actor or entity that is making these changes. The - * value must be less than or 128 characters long, and only contain printable characters, as defined by - * https://golang.org/pkg/unicode/#IsPrint. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and [[KubernetesObject]]. - */ - public async create( - spec: KubernetesObject, - pretty?: string, - dryRun?: string, - fieldManager?: string, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: KubernetesObject; response: http.IncomingMessage }> { - // verify required parameter 'spec' is not null or undefined - if (spec === null || spec === undefined) { - throw new Error('Required parameter spec was null or undefined when calling create.'); - } - - const localVarPath = await this.specUriPath(spec, 'create'); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (dryRun !== undefined) { - localVarQueryParameters.dryRun = ObjectSerializer.serialize(dryRun, 'string'); - } - - if (fieldManager !== undefined) { - localVarQueryParameters.fieldManager = ObjectSerializer.serialize(fieldManager, 'string'); - } - - const localVarRequestOptions: request.Options = { - method: 'POST', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - body: ObjectSerializer.serialize(spec, 'KubernetesObject'), - }; - - return this.requestPromise(localVarRequestOptions); - } - - /** - * Delete any Kubernetes resource. - * @param spec Kubernetes resource spec - * @param pretty If \'true\', then the output is pretty printed. - * @param dryRun When present, indicates that modifications should not be persisted. An invalid or unrecognized - * dryRun directive will result in an error response and no further processing of the request. Valid values - * are: - All: all dry run stages will be processed - * @param gracePeriodSeconds The duration in seconds before the object should be deleted. Value must be non-negative - * integer. The value zero indicates delete immediately. If this value is nil, the default grace period for - * the specified type will be used. Defaults to a per object value if not specified. zero means delete - * immediately. - * @param orphanDependents Deprecated: please use the PropagationPolicy, this field will be deprecated in - * 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be - * added to/removed from the object\'s finalizers list. Either this field or PropagationPolicy may be - * set, but not both. - * @param propagationPolicy Whether and how garbage collection will be performed. Either this field or - * OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in - * the metadata.finalizers and the resource-specific default policy. Acceptable values are: - * \'Orphan\' - orphan the dependents; \'Background\' - allow the garbage collector to delete - * the dependents in the background; \'Foreground\' - a cascading policy that deletes all dependents - * in the foreground. - * @param body See [[V1DeleteOptions]]. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and a Kubernetes [[V1Status]]. - */ - public async delete( - spec: KubernetesObject, - pretty?: string, - dryRun?: string, - gracePeriodSeconds?: number, - orphanDependents?: boolean, - propagationPolicy?: string, - body?: V1DeleteOptions, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: V1Status; response: http.IncomingMessage }> { - // verify required parameter 'spec' is not null or undefined - if (spec === null || spec === undefined) { - throw new Error('Required parameter spec was null or undefined when calling delete.'); - } - - const localVarPath = await this.specUriPath(spec, 'delete'); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (dryRun !== undefined) { - localVarQueryParameters.dryRun = ObjectSerializer.serialize(dryRun, 'string'); - } - - if (gracePeriodSeconds !== undefined) { - localVarQueryParameters.gracePeriodSeconds = ObjectSerializer.serialize( - gracePeriodSeconds, - 'number', - ); - } - - if (orphanDependents !== undefined) { - localVarQueryParameters.orphanDependents = ObjectSerializer.serialize( - orphanDependents, - 'boolean', - ); - } - - if (propagationPolicy !== undefined) { - localVarQueryParameters.propagationPolicy = ObjectSerializer.serialize( - propagationPolicy, - 'string', - ); - } - - const localVarRequestOptions: request.Options = { - method: 'DELETE', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - body: ObjectSerializer.serialize(body, 'V1DeleteOptions'), - }; - - return this.requestPromise(localVarRequestOptions, 'V1Status'); - } - - /** - * Patch any Kubernetes resource. - * @param spec Kubernetes resource spec - * @param pretty If \'true\', then the output is pretty printed. - * @param dryRun When present, indicates that modifications should not be persisted. An invalid or unrecognized - * dryRun directive will result in an error response and no further processing of the request. Valid values - * are: - All: all dry run stages will be processed - * @param fieldManager fieldManager is a name associated with the actor or entity that is making these changes. The - * value must be less than or 128 characters long, and only contain printable characters, as defined by - * https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests - * (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, - * StrategicMergePatch). - * @param force Force is going to \"force\" Apply requests. It means user will re-acquire conflicting - * fields owned by other people. Force flag must be unset for non-apply patch requests. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and [[KubernetesObject]]. - */ - public async patch( - spec: KubernetesObject, - pretty?: string, - dryRun?: string, - fieldManager?: string, - force?: boolean, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: KubernetesObject; response: http.IncomingMessage }> { - // verify required parameter 'spec' is not null or undefined - if (spec === null || spec === undefined) { - throw new Error('Required parameter spec was null or undefined when calling patch.'); - } - - const localVarPath = await this.specUriPath(spec, 'patch'); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers, 'PATCH'); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (dryRun !== undefined) { - localVarQueryParameters.dryRun = ObjectSerializer.serialize(dryRun, 'string'); - } - - if (fieldManager !== undefined) { - localVarQueryParameters.fieldManager = ObjectSerializer.serialize(fieldManager, 'string'); - } - - if (force !== undefined) { - localVarQueryParameters.force = ObjectSerializer.serialize(force, 'boolean'); - } - - const localVarRequestOptions: request.Options = { - method: 'PATCH', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - body: ObjectSerializer.serialize(spec, 'object'), - }; - - return this.requestPromise(localVarRequestOptions); - } - - /** - * Read any Kubernetes resource. - * @param spec Kubernetes resource spec - * @param pretty If \'true\', then the output is pretty printed. - * @param exact Should the export be exact. Exact export maintains cluster-specific fields like - * \'Namespace\'. Deprecated. Planned for removal in 1.18. - * @param exportt Should this value be exported. Export strips fields that a user can not - * specify. Deprecated. Planned for removal in 1.18. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and [[KubernetesObject]]. - */ - public async read( - spec: KubernetesObject, - pretty?: string, - exact?: boolean, - exportt?: boolean, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: KubernetesObject; response: http.IncomingMessage }> { - // verify required parameter 'spec' is not null or undefined - if (spec === null || spec === undefined) { - throw new Error('Required parameter spec was null or undefined when calling read.'); - } - - const localVarPath = await this.specUriPath(spec, 'read'); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (exact !== undefined) { - localVarQueryParameters.exact = ObjectSerializer.serialize(exact, 'boolean'); - } - - if (exportt !== undefined) { - localVarQueryParameters.export = ObjectSerializer.serialize(exportt, 'boolean'); - } - - const localVarRequestOptions: request.Options = { - method: 'GET', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - }; - - return this.requestPromise(localVarRequestOptions); - } - - /** - * List any Kubernetes resources. - * @param apiVersion api group and version of the form / - * @param kind Kubernetes resource kind - * @param namespace list resources in this namespace - * @param pretty If \'true\', then the output is pretty printed. - * @param exact Should the export be exact. Exact export maintains cluster-specific fields like - * \'Namespace\'. Deprecated. Planned for removal in 1.18. - * @param exportt Should this value be exported. Export strips fields that a user can not - * specify. Deprecated. Planned for removal in 1.18. - * @param fieldSelector A selector to restrict the list of returned objects by their fields. Defaults to everything. - * @param labelSelector A selector to restrict the list of returned objects by their labels. Defaults to everything. - * @param limit Number of returned resources. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and [[KubernetesListObject]]. - */ - public async list( - apiVersion: string, - kind: string, - namespace?: string, - pretty?: string, - exact?: boolean, - exportt?: boolean, - fieldSelector?: string, - labelSelector?: string, - limit?: number, - continueToken?: string, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: KubernetesListObject; response: http.IncomingMessage }> { - // verify required parameters 'apiVersion', 'kind' is not null or undefined - if (apiVersion === null || apiVersion === undefined) { - throw new Error('Required parameter apiVersion was null or undefined when calling list.'); - } - if (kind === null || kind === undefined) { - throw new Error('Required parameter kind was null or undefined when calling list.'); - } - - const localVarPath = await this.specUriPath( - { - apiVersion, - kind, - metadata: { - namespace, - }, - }, - 'list', - ); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (exact !== undefined) { - localVarQueryParameters.exact = ObjectSerializer.serialize(exact, 'boolean'); - } - - if (exportt !== undefined) { - localVarQueryParameters.export = ObjectSerializer.serialize(exportt, 'boolean'); - } - - if (fieldSelector !== undefined) { - localVarQueryParameters.fieldSelector = ObjectSerializer.serialize(fieldSelector, 'string'); - } - - if (labelSelector !== undefined) { - localVarQueryParameters.labelSelector = ObjectSerializer.serialize(labelSelector, 'string'); - } - - if (limit !== undefined) { - localVarQueryParameters.limit = ObjectSerializer.serialize(limit, 'number'); - } - - if (continueToken !== undefined) { - localVarQueryParameters.continue = ObjectSerializer.serialize(continueToken, 'string'); - } - - const localVarRequestOptions: request.Options = { - method: 'GET', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - }; - - return this.requestPromise(localVarRequestOptions); - } - - /** - * Replace any Kubernetes resource. - * @param spec Kubernetes resource spec - * @param pretty If \'true\', then the output is pretty printed. - * @param dryRun When present, indicates that modifications should not be persisted. An invalid or unrecognized - * dryRun directive will result in an error response and no further processing of the request. Valid values - * are: - All: all dry run stages will be processed - * @param fieldManager fieldManager is a name associated with the actor or entity that is making these changes. The - * value must be less than or 128 characters long, and only contain printable characters, as defined by - * https://golang.org/pkg/unicode/#IsPrint. - * @param options Optional headers to use in the request. - * @return Promise containing the request response and [[KubernetesObject]]. - */ - public async replace( - spec: KubernetesObject, - pretty?: string, - dryRun?: string, - fieldManager?: string, - options: { headers: { [name: string]: string } } = { headers: {} }, - ): Promise<{ body: KubernetesObject; response: http.IncomingMessage }> { - // verify required parameter 'spec' is not null or undefined - if (spec === null || spec === undefined) { - throw new Error('Required parameter spec was null or undefined when calling replace.'); - } - - const localVarPath = await this.specUriPath(spec, 'replace'); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders(options.headers); - - if (pretty !== undefined) { - localVarQueryParameters.pretty = ObjectSerializer.serialize(pretty, 'string'); - } - - if (dryRun !== undefined) { - localVarQueryParameters.dryRun = ObjectSerializer.serialize(dryRun, 'string'); - } - - if (fieldManager !== undefined) { - localVarQueryParameters.fieldManager = ObjectSerializer.serialize(fieldManager, 'string'); - } - - const localVarRequestOptions: request.Options = { - method: 'PUT', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - body: ObjectSerializer.serialize(spec, 'KubernetesObject'), - }; - - return this.requestPromise(localVarRequestOptions); - } - - /** Set default namespace from current context, if available. */ - protected setDefaultNamespace(kc: KubeConfig): string { - if (kc.currentContext) { - const currentContext = kc.getContextObject(kc.currentContext); - if (currentContext && currentContext.namespace) { - this.defaultNamespace = currentContext.namespace; - } - } - return this.defaultNamespace; - } - - /** - * Use spec information to construct resource URI path. If any required information in not provided, an Error is - * thrown. If an `apiVersion` is not provided, 'v1' is used. If a `metadata.namespace` is not provided for a - * request that requires one, the context default is used, if available, if not, 'default' is used. - * - * @param spec Kubernetes resource spec which must define kind and apiVersion properties. - * @param action API action, see [[K8sApiAction]]. - * @return tail of resource-specific URI - */ - protected async specUriPath(spec: KubernetesObject, action: KubernetesApiAction): Promise { - if (!spec.kind) { - throw new Error('Required spec property kind is not set'); - } - if (!spec.apiVersion) { - spec.apiVersion = 'v1'; - } - if (!spec.metadata) { - spec.metadata = {}; - } - const resource = await this.resource(spec.apiVersion, spec.kind); - if (!resource) { - throw new Error(`Unrecognized API version and kind: ${spec.apiVersion} ${spec.kind}`); - } - if (resource.namespaced && !spec.metadata.namespace && action !== 'list') { - spec.metadata.namespace = this.defaultNamespace; - } - const parts = [this.apiVersionPath(spec.apiVersion)]; - if (resource.namespaced && spec.metadata.namespace) { - parts.push('namespaces', encodeURIComponent(String(spec.metadata.namespace))); - } - parts.push(resource.name); - if (action !== 'create' && action !== 'list') { - if (!spec.metadata.name) { - throw new Error('Required spec property name is not set'); - } - parts.push(encodeURIComponent(String(spec.metadata.name))); - } - return parts.join('/').toLowerCase(); - } - - /** Return root of API path up to API version. */ - protected apiVersionPath(apiVersion: string): string { - const api = apiVersion.includes('/') ? 'apis' : 'api'; - return [this.basePath, api, apiVersion].join('/'); - } - - /** - * Merge default headers and provided headers, setting the 'Accept' header to 'application/json' and, if the - * `action` is 'PATCH', the 'Content-Type' header to [[KubernetesPatchStrategies.StrategicMergePatch]]. Both of - * these defaults can be overriden by values provided in `optionsHeaders`. - * - * @param optionHeaders Headers from method's options argument. - * @param action HTTP action headers are being generated for. - * @return Headers to use in request. - */ - protected generateHeaders( - optionsHeaders: { [name: string]: string }, - action: string = 'GET', - ): { [name: string]: string } { - const headers: { [name: string]: string } = Object.assign({}, this._defaultHeaders); - headers.accept = 'application/json'; - if (action === 'PATCH') { - headers['content-type'] = KubernetesPatchStrategies.StrategicMergePatch; - } - Object.assign(headers, optionsHeaders); - return headers; - } - - /** - * Get metadata from Kubernetes API for resources described by `kind` and `apiVersion`. If it is unable to find the - * resource `kind` under the provided `apiVersion`, `undefined` is returned. - * - * This method caches responses from the Kubernetes API to use for future requests. If the cache for apiVersion - * exists but the kind is not found the request is attempted again. - * - * @param apiVersion Kubernetes API version, e.g., 'v1' or 'apps/v1'. - * @param kind Kubernetes resource kind, e.g., 'Pod' or 'Namespace'. - * @return Promise of the resource metadata or `undefined` if the resource is not found. - */ - protected async resource(apiVersion: string, kind: string): Promise { - // verify required parameter 'apiVersion' is not null or undefined - if (apiVersion === null || apiVersion === undefined) { - throw new Error('Required parameter apiVersion was null or undefined when calling resource'); - } - // verify required parameter 'kind' is not null or undefined - if (kind === null || kind === undefined) { - throw new Error('Required parameter kind was null or undefined when calling resource'); - } - - if (this.apiVersionResourceCache[apiVersion]) { - const resource = this.apiVersionResourceCache[apiVersion].resources.find((r) => r.kind === kind); - if (resource) { - return resource; - } - } - - const localVarPath = this.apiVersionPath(apiVersion); - const localVarQueryParameters: any = {}; - const localVarHeaderParams = this.generateHeaders({}); - - const localVarRequestOptions: request.Options = { - method: 'GET', - qs: localVarQueryParameters, - headers: localVarHeaderParams, - uri: localVarPath, - useQuerystring: this._useQuerystring, - json: true, - }; - - try { - const getApiResponse = await this.requestPromise( - localVarRequestOptions, - 'V1APIResourceList', - ); - this.apiVersionResourceCache[apiVersion] = getApiResponse.body; - return this.apiVersionResourceCache[apiVersion].resources.find((r) => r.kind === kind); - } catch (e) { - e.message = `Failed to fetch resource metadata for ${apiVersion}/${kind}: ${e.message}`; - throw e; - } - } - - /** - * Standard Kubernetes request wrapped in a Promise. - */ - protected async requestPromise( - requestOptions: request.Options, - tipe: string = 'KubernetesObject', - ): Promise<{ body: T; response: http.IncomingMessage }> { - let authenticationPromise = Promise.resolve(); - if (this.authentications.BearerToken.apiKey) { - authenticationPromise = authenticationPromise.then(() => - this.authentications.BearerToken.applyToRequest(requestOptions), - ); - } - authenticationPromise = authenticationPromise.then(() => - this.authentications.default.applyToRequest(requestOptions), - ); - - let interceptorPromise = authenticationPromise; - for (const interceptor of this.interceptors) { - interceptorPromise = interceptorPromise.then(() => interceptor(requestOptions)); - } - await interceptorPromise; - - return new Promise<{ body: T; response: http.IncomingMessage }>((resolve, reject) => { - request(requestOptions, (error, response, body) => { - if (error) { - reject(error); - } else { - body = ObjectSerializer.deserialize(body, tipe); - if (response.statusCode && response.statusCode >= 200 && response.statusCode <= 299) { - resolve({ response, body }); - } else { - reject(new HttpError(response, body, response.statusCode)); - } - } - }); - }); - } -} diff --git a/src/object_test.ts b/src/object_test.ts deleted file mode 100644 index 77a258855d0..00000000000 --- a/src/object_test.ts +++ /dev/null @@ -1,2030 +0,0 @@ -import { expect } from 'chai'; -import nock = require('nock'); -import { V1APIResource, V1APIResourceList } from './api'; -import { KubeConfig } from './config'; -import { KubernetesObjectApi } from './object'; -import { KubernetesObject } from './types'; - -describe('KubernetesObject', () => { - const testConfigOptions = { - clusters: [{ name: 'dc', server: 'https://d.i.y' }], - users: [{ name: 'ian', password: 'mackaye' }], - contexts: [{ name: 'dischord', cluster: 'dc', user: 'ian' }], - currentContext: 'dischord', - }; - - describe('makeApiClient', () => { - it('should create the client', () => { - const kc = new KubeConfig(); - kc.loadFromOptions(testConfigOptions); - const c = KubernetesObjectApi.makeApiClient(kc); - expect(c).to.be.ok; - expect((c as any).defaultNamespace).to.equal('default'); - }); - - it('should set the default namespace from context', () => { - const kc = new KubeConfig(); - kc.loadFromOptions({ - clusters: [{ name: 'dc', server: 'https://d.i.y' }], - users: [{ name: 'ian', password: 'mackaye' }], - contexts: [{ name: 'dischord', cluster: 'dc', user: 'ian', namespace: 'straight-edge' }], - currentContext: 'dischord', - }); - const c = KubernetesObjectApi.makeApiClient(kc); - expect(c).to.be.ok; - expect((c as any).defaultNamespace).to.equal('straight-edge'); - }); - }); - - class KubernetesObjectApiTest extends KubernetesObjectApi { - public static makeApiClient(kc?: KubeConfig): KubernetesObjectApiTest { - if (!kc) { - kc = new KubeConfig(); - kc.loadFromOptions(testConfigOptions); - } - const client = kc.makeApiClient(KubernetesObjectApiTest); - client.setDefaultNamespace(kc); - return client; - } - public apiVersionResourceCache: Record = {}; - public async specUriPath(spec: KubernetesObject, method: any): Promise { - return super.specUriPath(spec, method); - } - public generateHeaders( - optionsHeaders: { [name: string]: string }, - action: string = 'GET', - ): { [name: string]: string } { - return super.generateHeaders(optionsHeaders, action); - } - public async resource(apiVersion: string, kind: string): Promise { - return super.resource(apiVersion, kind); - } - } - - const resourceBodies = { - core: `{ - "groupVersion": "v1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "Binding", - "name": "bindings", - "namespaced": true - }, - { - "kind": "ComponentStatus", - "name": "componentstatuses", - "namespaced": false - }, - { - "kind": "ConfigMap", - "name": "configmaps", - "namespaced": true - }, - { - "kind": "Endpoints", - "name": "endpoints", - "namespaced": true - }, - { - "kind": "Event", - "name": "events", - "namespaced": true - }, - { - "kind": "LimitRange", - "name": "limitranges", - "namespaced": true - }, - { - "kind": "Namespace", - "name": "namespaces", - "namespaced": false - }, - { - "kind": "Namespace", - "name": "namespaces/finalize", - "namespaced": false - }, - { - "kind": "Namespace", - "name": "namespaces/status", - "namespaced": false - }, - { - "kind": "Node", - "name": "nodes", - "namespaced": false - }, - { - "kind": "NodeProxyOptions", - "name": "nodes/proxy", - "namespaced": false - }, - { - "kind": "Node", - "name": "nodes/status", - "namespaced": false - }, - { - "kind": "PersistentVolumeClaim", - "name": "persistentvolumeclaims", - "namespaced": true - }, - { - "kind": "PersistentVolumeClaim", - "name": "persistentvolumeclaims/status", - "namespaced": true - }, - { - "kind": "PersistentVolume", - "name": "persistentvolumes", - "namespaced": false - }, - { - "kind": "PersistentVolume", - "name": "persistentvolumes/status", - "namespaced": false - }, - { - "kind": "Pod", - "name": "pods", - "namespaced": true - }, - { - "kind": "PodAttachOptions", - "name": "pods/attach", - "namespaced": true - }, - { - "kind": "Binding", - "name": "pods/binding", - "namespaced": true - }, - { - "group": "policy", - "kind": "Eviction", - "name": "pods/eviction", - "namespaced": true, - "version": "v1beta1" - }, - { - "kind": "PodExecOptions", - "name": "pods/exec", - "namespaced": true - }, - { - "kind": "Pod", - "name": "pods/log", - "namespaced": true - }, - { - "kind": "PodPortForwardOptions", - "name": "pods/portforward", - "namespaced": true - }, - { - "kind": "PodProxyOptions", - "name": "pods/proxy", - "namespaced": true - }, - { - "kind": "Pod", - "name": "pods/status", - "namespaced": true - }, - { - "kind": "PodTemplate", - "name": "podtemplates", - "namespaced": true - }, - { - "kind": "ReplicationController", - "name": "replicationcontrollers", - "namespaced": true - }, - { - "group": "autoscaling", - "kind": "Scale", - "name": "replicationcontrollers/scale", - "namespaced": true, - "version": "v1" - }, - { - "kind": "ReplicationController", - "name": "replicationcontrollers/status", - "namespaced": true - }, - { - "kind": "ResourceQuota", - "name": "resourcequotas", - "namespaced": true - }, - { - "kind": "ResourceQuota", - "name": "resourcequotas/status", - "namespaced": true - }, - { - "kind": "Secret", - "name": "secrets", - "namespaced": true - }, - { - "kind": "ServiceAccount", - "name": "serviceaccounts", - "namespaced": true - }, - { - "kind": "Service", - "name": "services", - "namespaced": true - }, - { - "kind": "ServiceProxyOptions", - "name": "services/proxy", - "namespaced": true - }, - { - "kind": "Service", - "name": "services/status", - "namespaced": true - } - ] -}`, - - apps: `{ - "apiVersion": "v1", - "groupVersion": "apps/v1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "ControllerRevision", - "name": "controllerrevisions", - "namespaced": true - }, - { - "kind": "DaemonSet", - "name": "daemonsets", - "namespaced": true - }, - { - "kind": "DaemonSet", - "name": "daemonsets/status", - "namespaced": true - }, - { - "kind": "Deployment", - "name": "deployments", - "namespaced": true - }, - { - "group": "autoscaling", - "kind": "Scale", - "name": "deployments/scale", - "namespaced": true, - "version": "v1" - }, - { - "kind": "Deployment", - "name": "deployments/status", - "namespaced": true - }, - { - "kind": "ReplicaSet", - "name": "replicasets", - "namespaced": true - }, - { - "group": "autoscaling", - "kind": "Scale", - "name": "replicasets/scale", - "namespaced": true, - "version": "v1" - }, - { - "kind": "ReplicaSet", - "name": "replicasets/status", - "namespaced": true - }, - { - "kind": "StatefulSet", - "name": "statefulsets", - "namespaced": true - }, - { - "group": "autoscaling", - "kind": "Scale", - "name": "statefulsets/scale", - "namespaced": true, - "version": "v1" - }, - { - "kind": "StatefulSet", - "name": "statefulsets/status", - "namespaced": true - } - ] -}`, - extensions: `{ - "groupVersion": "extensions/v1beta1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "DaemonSet", - "name": "daemonsets", - "namespaced": true - }, - { - "kind": "DaemonSet", - "name": "daemonsets/status", - "namespaced": true - }, - { - "kind": "Deployment", - "name": "deployments", - "namespaced": true - }, - { - "kind": "DeploymentRollback", - "name": "deployments/rollback", - "namespaced": true - }, - { - "group": "extensions", - "kind": "Scale", - "name": "deployments/scale", - "namespaced": true, - "version": "v1beta1" - }, - { - "kind": "Deployment", - "name": "deployments/status", - "namespaced": true - }, - { - "kind": "Ingress", - "name": "ingresses", - "namespaced": true - }, - { - "kind": "Ingress", - "name": "ingresses/status", - "namespaced": true - }, - { - "kind": "NetworkPolicy", - "name": "networkpolicies", - "namespaced": true - }, - { - "kind": "PodSecurityPolicy", - "name": "podsecuritypolicies", - "namespaced": false - }, - { - "kind": "ReplicaSet", - "name": "replicasets", - "namespaced": true - }, - { - "group": "extensions", - "kind": "Scale", - "name": "replicasets/scale", - "namespaced": true, - "version": "v1beta1" - }, - { - "kind": "ReplicaSet", - "name": "replicasets/status", - "namespaced": true - }, - { - "kind": "ReplicationControllerDummy", - "name": "replicationcontrollers", - "namespaced": true - }, - { - "kind": "Scale", - "name": "replicationcontrollers/scale", - "namespaced": true - } - ] -}`, - networking: `{ - "apiVersion": "v1", - "groupVersion": "networking.k8s.io/v1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "NetworkPolicy", - "name": "networkpolicies", - "namespaced": true - } - ] -}`, - rbac: `{ - "apiVersion": "v1", - "groupVersion": "rbac.authorization.k8s.io/v1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "ClusterRoleBinding", - "name": "clusterrolebindings", - "namespaced": false - }, - { - "kind": "ClusterRole", - "name": "clusterroles", - "namespaced": false - }, - { - "kind": "RoleBinding", - "name": "rolebindings", - "namespaced": true - }, - { - "kind": "Role", - "name": "roles", - "namespaced": true - } - ] -}`, - storage: `{ - "apiVersion": "v1", - "groupVersion": "storage.k8s.io/v1", - "kind": "APIResourceList", - "resources": [ - { - "kind": "StorageClass", - "name": "storageclasses", - "namespaced": false - }, - { - "kind": "VolumeAttachment", - "name": "volumeattachments", - "namespaced": false - }, - { - "kind": "VolumeAttachment", - "name": "volumeattachments/status", - "namespaced": false - } - ] -}`, - }; - - describe('specUriPath', () => { - it('should return a namespaced path', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: 'repeater', - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'patch'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/fugazi/services/repeater'); - scope.done(); - }); - - it('should default to apiVersion v1', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - kind: 'ServiceAccount', - metadata: { - name: 'repeater', - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'patch'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/fugazi/serviceaccounts/repeater'); - scope.done(); - }); - - it('should default to context namespace', async () => { - const kc = new KubeConfig(); - kc.loadFromOptions({ - clusters: [{ name: 'dc', server: 'https://d.i.y' }], - users: [{ name: 'ian', password: 'mackaye' }], - contexts: [{ name: 'dischord', cluster: 'dc', user: 'ian', namespace: 'straight-edge' }], - currentContext: 'dischord', - }); - const c = KubernetesObjectApiTest.makeApiClient(kc); - const o = { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'repeater', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'patch'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/straight-edge/pods/repeater'); - scope.done(); - }); - - it('should default to default namespace', async () => { - const kc = new KubeConfig(); - kc.loadFromOptions({ - clusters: [{ name: 'dc', server: 'https://d.i.y' }], - users: [{ name: 'ian', password: 'mackaye' }], - contexts: [{ name: 'dischord', cluster: 'dc', user: 'ian' }], - currentContext: 'dischord', - }); - const c = KubernetesObjectApiTest.makeApiClient(kc); - const o = { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'repeater', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'patch'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/default/pods/repeater'); - scope.done(); - }); - - it('should return a non-namespaced path', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Namespace', - metadata: { - name: 'repeater', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'delete'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/repeater'); - scope.done(); - }); - - it('should return a namespaced path without name', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'create'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces/fugazi/services'); - scope.done(); - }); - - it('should return a non-namespaced path without name', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Namespace', - metadata: { - name: 'repeater', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const r = await c.specUriPath(o, 'create'); - expect(r).to.equal('https://d.i.y/api/v1/namespaces'); - scope.done(); - }); - - it('should return a namespaced path for non-core resource', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'apps/v1', - kind: 'Deployment', - metadata: { - name: 'repeater', - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/apis/apps/v1') - .reply(200, resourceBodies.apps); - const r = await c.specUriPath(o, 'read'); - expect(r).to.equal('https://d.i.y/apis/apps/v1/namespaces/fugazi/deployments/repeater'); - scope.done(); - }); - - it('should return a non-namespaced path for non-core resource', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'ClusterRole', - metadata: { - name: 'repeater', - }, - }; - const scope = nock('https://d.i.y') - .get('/apis/rbac.authorization.k8s.io/v1') - .reply(200, resourceBodies.rbac); - const r = await c.specUriPath(o, 'read'); - expect(r).to.equal('https://d.i.y/apis/rbac.authorization.k8s.io/v1/clusterroles/repeater'); - scope.done(); - }); - - it('should handle a variety of resources', async () => { - const a = [ - { - apiVersion: 'v1', - kind: 'Service', - ns: true, - p: '/api/v1', - b: resourceBodies.core, - e: 'https://d.i.y/api/v1/namespaces/fugazi/services/repeater', - }, - { - apiVersion: 'v1', - kind: 'ServiceAccount', - ns: true, - p: '/api/v1', - b: resourceBodies.core, - e: 'https://d.i.y/api/v1/namespaces/fugazi/serviceaccounts/repeater', - }, - { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'Role', - ns: true, - p: '/apis/rbac.authorization.k8s.io/v1', - b: resourceBodies.rbac, - e: 'https://d.i.y/apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles/repeater', - }, - { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'ClusterRole', - ns: false, - p: '/apis/rbac.authorization.k8s.io/v1', - b: resourceBodies.rbac, - e: 'https://d.i.y/apis/rbac.authorization.k8s.io/v1/clusterroles/repeater', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'NetworkPolicy', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/networkpolicies/repeater', - }, - { - apiVersion: 'networking.k8s.io/v1', - kind: 'NetworkPolicy', - ns: true, - p: '/apis/networking.k8s.io/v1', - b: resourceBodies.networking, - e: 'https://d.i.y/apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies/repeater', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'Ingress', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/ingresses/repeater', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'DaemonSet', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/daemonsets/repeater', - }, - { - apiVersion: 'apps/v1', - kind: 'DaemonSet', - ns: true, - p: '/apis/apps/v1', - b: resourceBodies.apps, - e: 'https://d.i.y/apis/apps/v1/namespaces/fugazi/daemonsets/repeater', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'Deployment', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/deployments/repeater', - }, - { - apiVersion: 'apps/v1', - kind: 'Deployment', - ns: true, - p: '/apis/apps/v1', - b: resourceBodies.apps, - e: 'https://d.i.y/apis/apps/v1/namespaces/fugazi/deployments/repeater', - }, - { - apiVersion: 'storage.k8s.io/v1', - kind: 'StorageClass', - ns: false, - p: '/apis/storage.k8s.io/v1', - b: resourceBodies.storage, - e: 'https://d.i.y/apis/storage.k8s.io/v1/storageclasses/repeater', - }, - ]; - for (const k of a) { - const c = KubernetesObjectApiTest.makeApiClient(); - const o: KubernetesObject = { - apiVersion: k.apiVersion, - kind: k.kind, - metadata: { - name: 'repeater', - }, - }; - if (k.ns) { - o.metadata = o.metadata || {}; - o.metadata.namespace = 'fugazi'; - } - const scope = nock('https://d.i.y') - .get(k.p) - .reply(200, k.b); - const r = await c.specUriPath(o, 'patch'); - expect(r).to.equal(k.e); - scope.done(); - } - }); - - it('should handle a variety of resources without names', async () => { - const a = [ - { - apiVersion: 'v1', - kind: 'Service', - ns: true, - p: '/api/v1', - b: resourceBodies.core, - e: 'https://d.i.y/api/v1/namespaces/fugazi/services', - }, - { - apiVersion: 'v1', - kind: 'ServiceAccount', - ns: true, - p: '/api/v1', - b: resourceBodies.core, - e: 'https://d.i.y/api/v1/namespaces/fugazi/serviceaccounts', - }, - { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'Role', - ns: true, - p: '/apis/rbac.authorization.k8s.io/v1', - b: resourceBodies.rbac, - e: 'https://d.i.y/apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles', - }, - { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'ClusterRole', - ns: false, - p: '/apis/rbac.authorization.k8s.io/v1', - b: resourceBodies.rbac, - e: 'https://d.i.y/apis/rbac.authorization.k8s.io/v1/clusterroles', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'NetworkPolicy', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/networkpolicies', - }, - { - apiVersion: 'networking.k8s.io/v1', - kind: 'NetworkPolicy', - ns: true, - p: '/apis/networking.k8s.io/v1', - b: resourceBodies.networking, - e: 'https://d.i.y/apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'Ingress', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/ingresses', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'DaemonSet', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/daemonsets', - }, - { - apiVersion: 'apps/v1', - kind: 'DaemonSet', - ns: true, - p: '/apis/apps/v1', - b: resourceBodies.apps, - e: 'https://d.i.y/apis/apps/v1/namespaces/fugazi/daemonsets', - }, - { - apiVersion: 'extensions/v1beta1', - kind: 'Deployment', - ns: true, - p: '/apis/extensions/v1beta1', - b: resourceBodies.extensions, - e: 'https://d.i.y/apis/extensions/v1beta1/namespaces/fugazi/deployments', - }, - { - apiVersion: 'apps/v1', - kind: 'Deployment', - ns: true, - p: '/apis/apps/v1', - b: resourceBodies.apps, - e: 'https://d.i.y/apis/apps/v1/namespaces/fugazi/deployments', - }, - { - apiVersion: 'storage.k8s.io/v1', - kind: 'StorageClass', - ns: false, - p: '/apis/storage.k8s.io/v1', - b: resourceBodies.storage, - e: 'https://d.i.y/apis/storage.k8s.io/v1/storageclasses', - }, - ]; - for (const k of a) { - const c = KubernetesObjectApiTest.makeApiClient(); - const o: KubernetesObject = { - apiVersion: k.apiVersion, - kind: k.kind, - }; - if (k.ns) { - o.metadata = { namespace: 'fugazi' }; - } - const scope = nock('https://d.i.y') - .get(k.p) - .reply(200, k.b); - const r = await c.specUriPath(o, 'create'); - expect(r).to.equal(k.e); - scope.done(); - } - }); - - it('should throw an error if kind missing', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - metadata: { - name: 'repeater', - namespace: 'fugazi', - }, - }; - let thrown = false; - try { - await c.specUriPath(o, 'create'); - expect.fail('should have thrown error'); - } catch (e) { - thrown = true; - expect(e.message).to.equal('Required spec property kind is not set'); - } - expect(thrown).to.be.true; - }); - - it('should throw an error if name required and missing', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - let thrown = false; - try { - await c.specUriPath(o, 'read'); - expect.fail('should have thrown error'); - } catch (e) { - thrown = true; - expect(e.message).to.equal('Required spec property name is not set'); - } - expect(thrown).to.be.true; - scope.done(); - }); - - it('should throw an error if resource is not valid', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const o = { - apiVersion: 'v1', - kind: 'Ingress', - metadata: { - name: 'repeater', - namespace: 'fugazi', - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - let thrown = false; - try { - await c.specUriPath(o, 'create'); - expect.fail('should have thrown error'); - } catch (e) { - thrown = true; - expect(e.message).to.equal('Unrecognized API version and kind: v1 Ingress'); - } - expect(thrown).to.be.true; - scope.done(); - }); - }); - - describe('generateHeaders', () => { - let client: KubernetesObjectApiTest; - before(function(this: Mocha.Context): void { - client = KubernetesObjectApiTest.makeApiClient(); - }); - - it('should return default headers', () => { - const h = client.generateHeaders({}); - expect(h.accept).to.equal('application/json'); - }); - - it('should return patch headers', () => { - const h = client.generateHeaders({}, 'PATCH'); - expect(h.accept).to.equal('application/json'); - expect(h['content-type']).to.equal('application/strategic-merge-patch+json'); - }); - - it('should allow overrides', () => { - const h = client.generateHeaders( - { - accept: 'application/vnd.kubernetes.protobuf', - 'content-type': 'application/merge-patch+json', - }, - 'PATCH', - ); - expect(h.accept).to.equal('application/vnd.kubernetes.protobuf'); - expect(h['content-type']).to.equal('application/merge-patch+json'); - }); - - it('should retain provided headers', () => { - const h = client.generateHeaders({ - burden: 'of Proof', - simple: 'Matters', - }); - expect(h.accept).to.equal('application/json'); - expect(h.burden).to.equal('of Proof'); - expect(h.simple).to.equal('Matters'); - }); - }); - - describe('resource', () => { - let client: KubernetesObjectApiTest; - before(function(this: Mocha.Context): void { - client = KubernetesObjectApiTest.makeApiClient(); - }); - - it('should throw an error if apiVersion not set', async () => { - for (const a of [null, undefined]) { - let thrown = false; - try { - await client.resource((a as unknown) as string, 'Service'); - } catch (e) { - thrown = true; - expect(e.message).to.equal( - 'Required parameter apiVersion was null or undefined when calling resource', - ); - } - expect(thrown).to.be.true; - } - }); - - it('should throw an error if kind not set', async () => { - for (const a of [null, undefined]) { - let thrown = false; - try { - await client.resource('v1', (a as unknown) as string); - } catch (e) { - thrown = true; - expect(e.message).to.equal( - 'Required parameter kind was null or undefined when calling resource', - ); - } - expect(thrown).to.be.true; - } - }); - - it('should use an interceptor', async () => { - const c: any = KubernetesObjectApiTest.makeApiClient(); - let intercepted = false; - if (c.interceptors) { - c.interceptors.push(async () => { - intercepted = true; - }); - } else { - c.interceptors = [ - async () => { - intercepted = true; - }, - ]; - } - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - await c.resource('v1', 'Service'); - expect(intercepted).to.be.true; - scope.done(); - }); - - it('should cache API response', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const s = await c.resource('v1', 'Service'); - expect(s).to.be.ok; - if (!s) { - throw new Error('old TypeScript compiler'); - } - expect(s.kind).to.equal('Service'); - expect(s.name).to.equal('services'); - expect(s.namespaced).to.be.true; - expect(c.apiVersionResourceCache).to.be.ok; - expect(c.apiVersionResourceCache.v1).to.be.ok; - const sa = await c.resource('v1', 'ServiceAccount'); - expect(sa).to.be.ok; - if (!sa) { - throw new Error('old TypeScript compiler'); - } - expect(sa.kind).to.equal('ServiceAccount'); - expect(sa.name).to.equal('serviceaccounts'); - expect(sa.namespaced).to.be.true; - const p = await c.resource('v1', 'Pod'); - if (!p) { - throw new Error('old TypeScript compiler'); - } - expect(p).to.be.ok; - expect(p.kind).to.equal('Pod'); - expect(p.name).to.equal('pods'); - expect(p.namespaced).to.be.true; - const pv = await c.resource('v1', 'PersistentVolume'); - if (!pv) { - throw new Error('old TypeScript compiler'); - } - expect(pv).to.be.ok; - expect(pv.kind).to.equal('PersistentVolume'); - expect(pv.name).to.equal('persistentvolumes'); - expect(pv.namespaced).to.be.false; - scope.done(); - }); - - it('should re-request on cache miss', async () => { - const c = KubernetesObjectApiTest.makeApiClient(); - c.apiVersionResourceCache.v1 = { - groupVersion: 'v1', - kind: 'APIResourceList', - resources: [ - { - kind: 'Binding', - name: 'bindings', - namespaced: true, - }, - { - kind: 'ComponentStatus', - name: 'componentstatuses', - namespaced: false, - }, - ], - } as any; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core); - const s = await c.resource('v1', 'Service'); - expect(s).to.be.ok; - if (!s) { - throw new Error('old TypeScript compiler'); - } - expect(s.kind).to.equal('Service'); - expect(s.name).to.equal('services'); - expect(s.namespaced).to.be.true; - expect(c.apiVersionResourceCache).to.be.ok; - expect(c.apiVersionResourceCache.v1).to.be.ok; - expect(c.apiVersionResourceCache.v1.resources.length).to.deep.equal( - JSON.parse(resourceBodies.core).resources.length, - ); - scope.done(); - }); - }); - - describe('verbs', () => { - let client: KubernetesObjectApi; - before(() => { - const kc = new KubeConfig(); - kc.loadFromOptions(testConfigOptions); - client = KubernetesObjectApi.makeApiClient(kc); - (client as any).apiVersionResourceCache.v1 = JSON.parse(resourceBodies.core); - }); - - it('should modify resources with defaults', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: 'k8s-js-client-test', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - const methods = [ - { - m: client.create, - v: 'POST', - p: '/api/v1/namespaces/default/services', - c: 201, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.patch, - v: 'PATCH', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.read, - v: 'GET', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.delete, - v: 'DELETE', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "apiVersion": "v1", - "details": { - "kind": "services", - "name": "k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a" - }, - "kind": "Status", - "metadata": {}, - "status": "Success" -}`, - }, - ]; - for (const m of methods) { - const scope = nock('https://d.i.y') - .intercept(m.p, m.v, m.v === 'DELETE' || m.v === 'GET' ? undefined : s) - .reply(m.c, m.b); - // TODO: Figure out why Typescript barfs if we do m.call - const hack_m = m.m as any; - await hack_m.call(client, s); - scope.done(); - } - }); - - it('should modify resources with pretty set', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: 'k8s-js-client-test', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - const methods = [ - { - m: client.create, - v: 'POST', - p: '/api/v1/namespaces/default/services', - c: 201, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.patch, - v: 'PATCH', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.read, - v: 'GET', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.delete, - v: 'DELETE', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "apiVersion": "v1", - "details": { - "kind": "services", - "name": "k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a" - }, - "kind": "Status", - "metadata": {}, - "status": "Success" -}`, - }, - ]; - for (const p of ['true', 'false']) { - for (const m of methods) { - const scope = nock('https://d.i.y') - .intercept( - `${m.p}?pretty=${p}`, - m.v, - m.v === 'DELETE' || m.v === 'GET' ? undefined : s, - ) - .reply(m.c, m.b); - // TODO: Figure out why Typescript barfs if we do m.call - const hack_m = m.m as any; - await hack_m.call(client, s, p); - scope.done(); - } - } - }); - - it('should set dryRun', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: 'k8s-js-client-test', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - const methods = [ - { - m: client.create, - v: 'POST', - p: '/api/v1/namespaces/default/services', - c: 201, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.patch, - v: 'PATCH', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", - "resourceVersion": "32373", - "creationTimestamp": "2020-05-11T17:34:25Z" - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.97.191.144", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - }, - { - m: client.delete, - v: 'DELETE', - p: '/api/v1/namespaces/default/services/k8s-js-client-test', - c: 200, - b: `{ - "apiVersion": "v1", - "details": { - "kind": "services", - "name": "k8s-js-client-test", - "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a" - }, - "kind": "Status", - "metadata": {}, - "status": "Success" -}`, - }, - ]; - for (const m of methods) { - const scope = nock('https://d.i.y') - .intercept(`${m.p}?dryRun=All`, m.v, m.v === 'DELETE' || m.v === 'GET' ? undefined : s) - .reply(m.c, m.b); - // TODO: Figure out why Typescript barfs if we do m.call - const hack_m = m.m as any; - await hack_m.call(client, s, undefined, 'All'); - scope.done(); - } - }); - - it('should replace a resource', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - annotations: { - owner: 'test', - }, - name: 'k8s-js-client-test', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - const scope = nock('https://d.i.y') - .post('/api/v1/namespaces/default/services?fieldManager=ManageField', s) - .reply( - 201, - `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "a4fd7a65-2af5-4ef1-a0bc-cb34a308b821", - "resourceVersion": "41183", - "creationTimestamp": "2020-05-11T19:35:01Z", - "annotations": { - "owner": "test" - } - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.106.153.133", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - ) - .put('/api/v1/namespaces/default/services/k8s-js-client-test?pretty=true', { - kind: 'Service', - apiVersion: 'v1', - metadata: { - name: 'k8s-js-client-test', - namespace: 'default', - selfLink: '/api/v1/namespaces/default/services/k8s-js-client-test', - uid: 'a4fd7a65-2af5-4ef1-a0bc-cb34a308b821', - resourceVersion: '41183', - creationTimestamp: '2020-05-11T19:35:01Z', - annotations: { - owner: 'test', - test: '1', - }, - }, - spec: { - ports: [ - { - protocol: 'TCP', - port: 80, - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - clusterIP: '10.106.153.133', - type: 'ClusterIP', - sessionAffinity: 'None', - }, - status: { - loadBalancer: {}, - }, - }) - .reply( - 200, - `{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "k8s-js-client-test", - "namespace": "default", - "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", - "uid": "a4fd7a65-2af5-4ef1-a0bc-cb34a308b821", - "resourceVersion": "41185", - "creationTimestamp": "2020-05-11T19:35:01Z", - "annotations": { - "owner": "test", - "test": "1" - } - }, - "spec": { - "ports": [ - { - "protocol": "TCP", - "port": 80, - "targetPort": 80 - } - ], - "selector": { - "app": "sleep" - }, - "clusterIP": "10.106.153.133", - "type": "ClusterIP", - "sessionAffinity": "None" - }, - "status": { - "loadBalancer": {} - } -}`, - ) - .delete( - '/api/v1/namespaces/default/services/k8s-js-client-test?gracePeriodSeconds=7&propagationPolicy=Foreground', - ) - .reply( - 200, - `{ - "apiVersion": "v1", - "details": { - "kind": "services", - "name": "k8s-js-client-test", - "uid": "a4fd7a65-2af5-4ef1-a0bc-cb34a308b821" - }, - "kind": "Status", - "metadata": {}, - "status": "Success" -}`, - ); - const cr: any = await client.create(s, undefined, undefined, 'ManageField'); - const rs: any = cr.body; - rs.metadata.annotations.test = '1'; - const rr: any = await client.replace(rs, 'true'); - expect(rr.body.metadata.annotations.test).to.equal('1'); - expect(parseInt(rr.body.metadata.resourceVersion, 10)).to.be.greaterThan( - parseInt(cr.body.metadata.resourceVersion, 10), - ); - await client.delete(s, undefined, undefined, 7, undefined, 'Foreground'); - scope.done(); - }); - - it('should list resources in a namespace', async () => { - const scope = nock('https://d.i.y') - .get( - '/api/v1/namespaces/default/secrets?fieldSelector=metadata.name%3Dtest-secret1&labelSelector=app%3Dmy-app&limit=5&continue=abc', - ) - .reply(200, { - apiVersion: 'v1', - kind: 'SecretList', - items: [ - { - apiVersion: 'v1', - kind: 'Secret', - metadata: { - name: 'test-secret-1', - uid: 'a4fd7a65-2af5-4ef1-a0bc-cb34a308b821', - }, - }, - ], - metadata: { - resourceVersion: '216532459', - continue: 'abc', - }, - }); - const lr = await client.list( - 'v1', - 'Secret', - 'default', - undefined, - undefined, - undefined, - 'metadata.name=test-secret1', - 'app=my-app', - 5, - 'abc', - ); - const items = lr.body.items; - expect(items).to.have.length(1); - scope.done(); - }); - - it('should list resources in all namespaces', async () => { - const scope = nock('https://d.i.y') - .get( - '/api/v1/secrets?fieldSelector=metadata.name%3Dtest-secret1&labelSelector=app%3Dmy-app&limit=5', - ) - .reply(200, { - apiVersion: 'v1', - kind: 'SecretList', - items: [ - { - apiVersion: 'v1', - kind: 'Secret', - metadata: { - name: 'test-secret-1', - uid: 'a4fd7a65-2af5-4ef1-a0bc-cb34a308b821', - }, - }, - ], - metadata: { - resourceVersion: '216532459', - continue: 'abc', - }, - }); - const lr = await client.list( - 'v1', - 'Secret', - undefined, - undefined, - undefined, - undefined, - 'metadata.name=test-secret1', - 'app=my-app', - 5, - ); - const items = lr.body.items; - expect(items).to.have.length(1); - scope.done(); - }); - }); - - describe('errors', () => { - let client: KubernetesObjectApi; - before(() => { - const kc = new KubeConfig(); - kc.loadFromOptions(testConfigOptions); - client = KubernetesObjectApi.makeApiClient(kc); - }); - - it('should throw error if no spec', async () => { - const methods = [client.create, client.patch, client.read, client.replace, client.delete]; - for (const s of [null, undefined]) { - for (const m of methods) { - let thrown = false; - try { - // TODO: Figure out why Typescript barfs if we do m.call - const hack_m = m as any; - await hack_m.call(client, s); - expect.fail('should have thrown an error'); - } catch (e) { - thrown = true; - expect(e.message).to.contain( - 'Required parameter spec was null or undefined when calling ', - ); - } - expect(thrown).to.be.true; - } - } - }); - - it('should throw an error if request throws an error', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: 'valid-name', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - nock('https://d.i.y'); - let thrown = false; - try { - await client.read(s); - expect.fail('should have thrown error'); - } catch (e) { - thrown = true; - expect(e.message).to.contain('Nock: No match for request'); - } - expect(thrown).to.be.true; - }); - - it('should throw an error if name not valid', async () => { - const s = { - apiVersion: 'v1', - kind: 'Service', - metadata: { - name: '_not_a_valid_name_', - namespace: 'default', - }, - spec: { - ports: [ - { - port: 80, - protocol: 'TCP', - targetPort: 80, - }, - ], - selector: { - app: 'sleep', - }, - }, - }; - const scope = nock('https://d.i.y') - .get('/api/v1') - .reply(200, resourceBodies.core) - .post('/api/v1/namespaces/default/services', s) - .reply( - 422, - `{ - "kind": "Status", - "apiVersion": "v1", - "metadata": {}, - "status": "Failure", - "message": "Service \"_not_a_valid_name_\" is invalid: metadata.name: Invalid value: \"_not_a_valid_name_\": a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')", - "reason": "Invalid", - "details": { - "name": "_not_a_valid_name_", - "kind": "Service", - "causes": [ - { - "reason": "FieldValueInvalid", - "message": "Invalid value: \"_not_a_valid_name_\": a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')", - "field": "metadata.name" - } - ] - }, - "code": 422 -}`, - ); - let thrown = false; - try { - await client.create(s); - } catch (e) { - thrown = true; - expect(e.statusCode).to.equal(422); - expect(e.message).to.equal('HTTP request failed'); - } - expect(thrown).to.be.true; - scope.done(); - }); - - it('should throw an error if apiVersion not valid', async () => { - const d = { - apiVersion: 'applications/v1', - kind: 'Deployment', - metadata: { - name: 'should-not-be-created', - namespace: 'default', - }, - spec: { - selector: { - matchLabels: { - app: 'sleep', - }, - }, - template: { - metadata: { - labels: { - app: 'sleep', - }, - }, - spec: { - containers: [ - { - args: ['60'], - command: ['sleep'], - image: 'alpine', - name: 'sleep', - ports: [{ containerPort: 80 }], - }, - ], - }, - }, - }, - }; - const scope = nock('https://d.i.y') - .get('/apis/applications/v1') - .reply(404, `{}`); - let thrown = false; - try { - await client.create(d); - } catch (e) { - thrown = true; - expect(e.statusCode).to.equal(404); - expect(e.message).to.equal( - 'Failed to fetch resource metadata for applications/v1/Deployment: HTTP request failed', - ); - } - expect(thrown).to.be.true; - scope.done(); - }); - - it('should throw error if no apiVersion', async () => { - let thrown = false; - try { - await (client.list as any)(undefined, undefined); - expect.fail('should have thrown an error'); - } catch (e) { - thrown = true; - expect(e.message).to.contain( - 'Required parameter apiVersion was null or undefined when calling ', - ); - } - expect(thrown).to.be.true; - }); - - it('should throw error if no kind', async () => { - let thrown = false; - try { - await (client.list as any)('', undefined); - expect.fail('should have thrown an error'); - } catch (e) { - thrown = true; - expect(e.message).to.contain('Required parameter kind was null or undefined when calling '); - } - expect(thrown).to.be.true; - }); - }); -}); diff --git a/src/portforward_test.ts b/src/portforward_test.ts index 024db2e878a..013609baa7e 100644 --- a/src/portforward_test.ts +++ b/src/portforward_test.ts @@ -127,13 +127,13 @@ describe('PortForward', () => { await portForward.portForward('ns', 'pod', [], osStream, osStream, isStream); expect(false, 'should have thrown').to.equal(true); } catch (err) { - expect(err.toString()).to.equal('Error: You must provide at least one port to forward to.'); + expect(err).to.equal('Error: You must provide at least one port to forward to.'); } try { await portForward.portForward('ns', 'pod', [1, 2], osStream, osStream, isStream); expect(false, 'should have thrown').to.equal(true); } catch (err) { - expect(err.toString()).to.equal('Error: Only one port is currently supported for port-forward'); + expect(err).to.equal('Error: Only one port is currently supported for port-forward'); } }); }); diff --git a/src/top_test.ts b/src/top_test.ts index 8d3e0cefbb1..8971dbeb3c7 100644 --- a/src/top_test.ts +++ b/src/top_test.ts @@ -1,10 +1,9 @@ import { expect } from 'chai'; import nock = require('nock'); import { KubeConfig } from './config'; -import { V1Pod } from './gen/api'; import { Metrics, PodMetricsList } from './metrics'; import { topPods } from './top'; -import { CoreV1Api } from './gen/api'; +import { CoreV1Api,V1Pod } from './api'; const emptyPodMetrics: PodMetricsList = { kind: 'PodMetricsList',