diff --git a/FETCH_MIGRATION.md b/FETCH_MIGRATION.md new file mode 100644 index 00000000000..8e9ced3a8e3 --- /dev/null +++ b/FETCH_MIGRATION.md @@ -0,0 +1,73 @@ +# Fetch migration + +Request is fully deprecated requiring us to switch libraries (see [#414](https://github.com/kubernetes-client/javascript/issues/414) for more information). There were a few [different options](https://github.com/kubernetes-client/javascript/issues/414#issuecomment-978031677) for how this swap should be implemented but moving to a new open-api generator option was chosen since this project will acquire the advantages of an up-to-date open-api generator version. + +Fetch was selected as the new HTTP request library for this project due to its widespread adoption across the JavaScript ecosystem. Additonally, potential future updates to this project could allow this client to be available with browser JavaScript since fetch is native to the browser ([#165](https://github.com/kubernetes-client/javascript/issues/165)). + +[Node-fetch](https://www.npmjs.com/package/node-fetch) is our specific fetch package since it is the largest Node.js compatable implementation. Fetch is not implemented by default in Node. + +For more details see the initial discussion ([#754](https://github.com/kubernetes-client/javascript/issues/754)). + +## Release cycle + +The fetch generator will create breaking changes to this project's API. Consumers will have to make small modifications to their code to upgrade. + +We will continue to support the request version of this project for three Kubernetes API versions (~9 months) to give users time to migrate. + +Versioning will follow [npm semantic versioning](https://docs.npmjs.com/about-semantic-versioning). + +### Old generator (request) + +Code will be on the `release-0.x` branch. + +- `0.17.x` == old generator, Kubernetes 1.23 API +- `0.18.x` == old generator, Kubernetes 1.24 API +- `0.19.x` == old generator, Kubernetes 1.25 API + +Support for old generator stops after 1.25 + +### New generator (fetch) + +Code will be on the `master` branch. + +- `1.0.x` == new generator, Kubernetes 1.23 API +- `1.1.x` == new generator, Kubernetes 1.24 API +- `1.2.x` == new generator, Kubernetes 1.25 API + Support for subsequent kubernetes versions continues with the new generator. + +## Implementation steps + +### Other repositories + +- [x] Update [kubernetes-client/gen](https://github.com/kubernetes-client/gen)'s typescript-fetch files to let us pass in the `typescriptThreePlus` config option [1](https://github.com/OpenAPITools/openapi-generator/issues/9973) [2](https://github.com/OpenAPITools/openapi-generator/issues/3869#issuecomment-584152932) +- [ ] Update [openapi-generator](https://github.com/OpenAPITools/openapi-generator)'s typescript-fetch flavor to mark parameters as optional if all parameters are optional [3](https://github.com/OpenAPITools/openapi-generator/issues/6440) + +### Kubernetes-client repository + +- [ ] Increment `OPENAPI_GENERATOR_COMMIT` to be [version 5.3.0](https://github.com/OpenAPITools/openapi-generator/releases/tag/v5.3.0) (with the optional parameters addition) +- [ ] `npm install node-fetch` to install node-fetch +- [ ] Switch generate-client script to use typescript-fetch +- [ ] Import and inject node-fetch in `src/api.ts` with the following + +```typescript +import fetch from 'node-fetch'; + +// inject node-fetch +if (!globalThis.fetch) { + // @ts-ignore + globalThis.fetch = fetch; + globalThis.Headers = Headers; + globalThis.Request = Request; + globalThis.Response = Response; +} +``` + +- [ ] Generate api with `npm run generate` +- [ ] Match src/gen/api.ts to new generated layout (it changes slightly) +- [ ] Fix errors in /src folder (due to new api) +- [ ] Fix errors in test (due to new api) +- [ ] Test all features +- [ ] Fix examples (due to new api) +- [ ] Update docs +- [ ] Document breaking changes for users +- [ ] Release initial version (1.0.0) diff --git a/README.md b/README.md index a1a23b3689e..0f751f04588 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,9 @@ The Javascript clients for Kubernetes is implemented in [typescript](https://typescriptlang.org), but can be called from either -Javascript or Typescript. +Javascript or Typescript. The client is implemented for server-side use with Node. -For now, the client is implemented for server-side use with node -using the `request` library. - -There are future plans to also build a jQuery compatible library but -for now, all of the examples and instructions assume the node client. +The `request` library is currently being swapped to `fetch`. See the [fetch migration docs](./FETCH_MIGRATION.md) for more information and progress. # Installation diff --git a/package-lock.json b/package-lock.json index b1577d69bee..ec6b2714288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@kubernetes/client-node", - "version": "0.16.1", + "version": "0.16.3", "lockfileVersion": 2, "requires": true, "packages": { @@ -24,9 +24,9 @@ "openid-client": "^4.1.1", "request": "^2.88.0", "rfc4648": "^1.3.0", - "shelljs": "^0.8.4", + "shelljs": "^0.8.5", "stream-buffers": "^3.0.2", - "tar": "^6.0.2", + "tar": "^6.1.11", "tmp-promise": "^3.0.2", "tslib": "^1.9.3", "underscore": "^1.9.1", @@ -3713,9 +3713,9 @@ } }, "node_modules/shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -3972,9 +3972,9 @@ } }, "node_modules/tar": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.9.tgz", - "integrity": "sha512-XjLaMNl76o07zqZC/aW4lwegdY07baOH1T8w3AEfrHAdyg/oYO4ctjzEBq9Gy9fEP9oHqLIgvx6zuGDGe+bc8Q==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -7765,9 +7765,9 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -7973,9 +7973,9 @@ } }, "tar": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.9.tgz", - "integrity": "sha512-XjLaMNl76o07zqZC/aW4lwegdY07baOH1T8w3AEfrHAdyg/oYO4ctjzEBq9Gy9fEP9oHqLIgvx6zuGDGe+bc8Q==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -8195,7 +8195,7 @@ "integrity": "sha512-ZQZnjJPrt0rjp216gp6FQC1QC4ojcoKikhfOJ/51CqaJunVDilRLlIO5tCGWj1tzlYYT9eOGhJv7MF3t7rxSmw==", "dev": true, "requires": { - "colors": "^1.4.0", + "colors": "1.4.0", "fs-extra": "^9.1.0", "handlebars": "^4.7.6", "lodash": "^4.17.20", diff --git a/package.json b/package.json index fee5e150a76..0a4ae3334ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kubernetes/client-node", - "version": "0.16.1", + "version": "0.16.3", "description": "NodeJS client for kubernetes", "repository": { "type": "git", @@ -69,9 +69,9 @@ "openid-client": "^4.1.1", "request": "^2.88.0", "rfc4648": "^1.3.0", - "shelljs": "^0.8.4", + "shelljs": "^0.8.5", "stream-buffers": "^3.0.2", - "tar": "^6.0.2", + "tar": "^6.1.11", "tmp-promise": "^3.0.2", "tslib": "^1.9.3", "underscore": "^1.9.1", @@ -118,5 +118,8 @@ "hooks": { "pre-push": "npm test && npm run lint" } + }, + "overrides": { + "colors": "1.4.0" } } diff --git a/settings b/settings index 588ca696ec3..ed18ad4f65c 100644 --- a/settings +++ b/settings @@ -15,7 +15,7 @@ # limitations under the License. # kubernetes-client/gen commit to use for code generation. -export GEN_COMMIT=a3aef4d +export GEN_COMMIT=82c3ff5 # GitHub username/organization to clone kubernetes repo from. export USERNAME=kubernetes diff --git a/src/gen/.openapi-generator/swagger.json-default.sha256 b/src/gen/.openapi-generator/swagger.json-default.sha256 index 96bd7db9fa3..15d9e5f68c7 100644 --- a/src/gen/.openapi-generator/swagger.json-default.sha256 +++ b/src/gen/.openapi-generator/swagger.json-default.sha256 @@ -1 +1 @@ -4dbc0536a9330c99ca0874d9b9d577fd581a1820517a27f20b4dbf5af0f6fb75 \ No newline at end of file +a5685a88a8d1b58674feda167bae22437c9580a1664511293db4508c164603d2 \ No newline at end of file diff --git a/src/gen/model/v1HTTPGetAction.ts b/src/gen/model/v1HTTPGetAction.ts index 919a44a1df9..4aa2419ee9b 100644 --- a/src/gen/model/v1HTTPGetAction.ts +++ b/src/gen/model/v1HTTPGetAction.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; import { V1HTTPHeader } from './v1HTTPHeader'; /** @@ -30,9 +31,9 @@ export class V1HTTPGetAction { */ 'path'?: string; /** - * Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'port': object; + 'port': IntOrString; /** * Scheme to use for connecting to the host. Defaults to HTTP. */ @@ -59,7 +60,7 @@ export class V1HTTPGetAction { { "name": "port", "baseName": "port", - "type": "object" + "type": "IntOrString" }, { "name": "scheme", diff --git a/src/gen/model/v1NetworkPolicyPort.ts b/src/gen/model/v1NetworkPolicyPort.ts index 2ce3a2b3adb..d9f7484d1ee 100644 --- a/src/gen/model/v1NetworkPolicyPort.ts +++ b/src/gen/model/v1NetworkPolicyPort.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; /** * NetworkPolicyPort describes a port to allow traffic on @@ -21,9 +22,9 @@ export class V1NetworkPolicyPort { */ 'endPort'?: number; /** - * The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'port'?: object; + 'port'?: IntOrString; /** * The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. */ @@ -40,7 +41,7 @@ export class V1NetworkPolicyPort { { "name": "port", "baseName": "port", - "type": "object" + "type": "IntOrString" }, { "name": "protocol", diff --git a/src/gen/model/v1PodDisruptionBudgetSpec.ts b/src/gen/model/v1PodDisruptionBudgetSpec.ts index 79914e8a429..982ad79fef2 100644 --- a/src/gen/model/v1PodDisruptionBudgetSpec.ts +++ b/src/gen/model/v1PodDisruptionBudgetSpec.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; import { V1LabelSelector } from './v1LabelSelector'; /** @@ -18,13 +19,13 @@ import { V1LabelSelector } from './v1LabelSelector'; */ export class V1PodDisruptionBudgetSpec { /** - * An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\". + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxUnavailable'?: object; + 'maxUnavailable'?: IntOrString; /** - * An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\". + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'minAvailable'?: object; + 'minAvailable'?: IntOrString; 'selector'?: V1LabelSelector; static discriminator: string | undefined = undefined; @@ -33,12 +34,12 @@ export class V1PodDisruptionBudgetSpec { { "name": "maxUnavailable", "baseName": "maxUnavailable", - "type": "object" + "type": "IntOrString" }, { "name": "minAvailable", "baseName": "minAvailable", - "type": "object" + "type": "IntOrString" }, { "name": "selector", diff --git a/src/gen/model/v1RollingUpdateDaemonSet.ts b/src/gen/model/v1RollingUpdateDaemonSet.ts index 26118977bc5..e81abfb8e26 100644 --- a/src/gen/model/v1RollingUpdateDaemonSet.ts +++ b/src/gen/model/v1RollingUpdateDaemonSet.ts @@ -11,19 +11,20 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; /** * Spec to control the desired behavior of daemon set rolling update. */ export class V1RollingUpdateDaemonSet { /** - * The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxSurge'?: object; + 'maxSurge'?: IntOrString; /** - * The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0 if MaxSurge is 0 Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxUnavailable'?: object; + 'maxUnavailable'?: IntOrString; static discriminator: string | undefined = undefined; @@ -31,12 +32,12 @@ export class V1RollingUpdateDaemonSet { { "name": "maxSurge", "baseName": "maxSurge", - "type": "object" + "type": "IntOrString" }, { "name": "maxUnavailable", "baseName": "maxUnavailable", - "type": "object" + "type": "IntOrString" } ]; static getAttributeTypeMap() { diff --git a/src/gen/model/v1RollingUpdateDeployment.ts b/src/gen/model/v1RollingUpdateDeployment.ts index ca0cb3a691c..a3b61a182bb 100644 --- a/src/gen/model/v1RollingUpdateDeployment.ts +++ b/src/gen/model/v1RollingUpdateDeployment.ts @@ -11,19 +11,20 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; /** * Spec to control the desired behavior of rolling update. */ export class V1RollingUpdateDeployment { /** - * The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxSurge'?: object; + 'maxSurge'?: IntOrString; /** - * The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxUnavailable'?: object; + 'maxUnavailable'?: IntOrString; static discriminator: string | undefined = undefined; @@ -31,12 +32,12 @@ export class V1RollingUpdateDeployment { { "name": "maxSurge", "baseName": "maxSurge", - "type": "object" + "type": "IntOrString" }, { "name": "maxUnavailable", "baseName": "maxUnavailable", - "type": "object" + "type": "IntOrString" } ]; static getAttributeTypeMap() { diff --git a/src/gen/model/v1ServicePort.ts b/src/gen/model/v1ServicePort.ts index 482d83fedeb..65d4fd14000 100644 --- a/src/gen/model/v1ServicePort.ts +++ b/src/gen/model/v1ServicePort.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; /** * ServicePort contains information on service\'s port. @@ -37,9 +38,9 @@ export class V1ServicePort { */ 'protocol'?: string; /** - * Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod\'s container ports. If this is not specified, the value of the \'port\' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the \'port\' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'targetPort'?: object; + 'targetPort'?: IntOrString; static discriminator: string | undefined = undefined; @@ -72,7 +73,7 @@ export class V1ServicePort { { "name": "targetPort", "baseName": "targetPort", - "type": "object" + "type": "IntOrString" } ]; static getAttributeTypeMap() { diff --git a/src/gen/model/v1TCPSocketAction.ts b/src/gen/model/v1TCPSocketAction.ts index 220bf481697..aa3efb606ff 100644 --- a/src/gen/model/v1TCPSocketAction.ts +++ b/src/gen/model/v1TCPSocketAction.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; /** * TCPSocketAction describes an action based on opening a socket @@ -21,9 +22,9 @@ export class V1TCPSocketAction { */ 'host'?: string; /** - * Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'port': object; + 'port': IntOrString; static discriminator: string | undefined = undefined; @@ -36,7 +37,7 @@ export class V1TCPSocketAction { { "name": "port", "baseName": "port", - "type": "object" + "type": "IntOrString" } ]; static getAttributeTypeMap() { diff --git a/src/gen/model/v1beta1PodDisruptionBudgetSpec.ts b/src/gen/model/v1beta1PodDisruptionBudgetSpec.ts index 386a3bbca01..10eb98ac85e 100644 --- a/src/gen/model/v1beta1PodDisruptionBudgetSpec.ts +++ b/src/gen/model/v1beta1PodDisruptionBudgetSpec.ts @@ -11,6 +11,7 @@ */ import { RequestFile } from './models'; +import { IntOrString } from '../../types'; import { V1LabelSelector } from './v1LabelSelector'; /** @@ -18,13 +19,13 @@ import { V1LabelSelector } from './v1LabelSelector'; */ export class V1beta1PodDisruptionBudgetSpec { /** - * An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\". + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'maxUnavailable'?: object; + 'maxUnavailable'?: IntOrString; /** - * An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\". + * IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number. */ - 'minAvailable'?: object; + 'minAvailable'?: IntOrString; 'selector'?: V1LabelSelector; static discriminator: string | undefined = undefined; @@ -33,12 +34,12 @@ export class V1beta1PodDisruptionBudgetSpec { { "name": "maxUnavailable", "baseName": "maxUnavailable", - "type": "object" + "type": "IntOrString" }, { "name": "minAvailable", "baseName": "minAvailable", - "type": "object" + "type": "IntOrString" }, { "name": "selector", diff --git a/src/gen/swagger.json b/src/gen/swagger.json index d9db67e3e44..0d112c00d60 100644 --- a/src/gen/swagger.json +++ b/src/gen/swagger.json @@ -1178,14 +1178,12 @@ "description": "Spec to control the desired behavior of daemon set rolling update.", "properties": { "maxSurge": { - "description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate." }, "maxUnavailable": { - "description": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0 if MaxSurge is 0 Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0 if MaxSurge is 0 Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update." } }, "type": "object" @@ -1194,14 +1192,12 @@ "description": "Spec to control the desired behavior of rolling update.", "properties": { "maxSurge": { - "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." }, "maxUnavailable": { - "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." } }, "type": "object" @@ -5992,9 +5988,8 @@ "type": "string" }, "port": { - "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." }, "scheme": { "description": "Scheme to use for connecting to the host. Defaults to HTTP.", @@ -9352,9 +9347,8 @@ "type": "string" }, "targetPort": { - "description": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service" } }, "required": [ @@ -9584,9 +9578,8 @@ "type": "string" }, "port": { - "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." } }, "required": [ @@ -11715,9 +11708,8 @@ "type": "integer" }, "port": { - "description": "The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched." }, "protocol": { "description": "The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.", @@ -12222,14 +12214,12 @@ "description": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.", "properties": { "maxUnavailable": { - "description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\"." }, "minAvailable": { - "description": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\"." }, "selector": { "$ref": "#/definitions/v1.LabelSelector", @@ -12467,14 +12457,12 @@ "description": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.", "properties": { "maxUnavailable": { - "description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\"." }, "minAvailable": { - "description": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".", - "format": "int-or-string", - "type": "object" + "$ref": "#/definitions/intstr.IntOrString", + "description": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\"." }, "selector": { "$ref": "#/definitions/v1.LabelSelector", @@ -16425,6 +16413,11 @@ } ] }, + "intstr.IntOrString": { + "description": "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", + "format": "int-or-string", + "type": "string" + }, "version.Info": { "description": "Info contains versioning information. how we'll want to distribute that information.", "properties": { diff --git a/src/object.ts b/src/object.ts index 9a0888dd3ad..350b8a733c9 100644 --- a/src/object.ts +++ b/src/object.ts @@ -20,7 +20,7 @@ type KubernetesObjectResponseBody = | V1APIResourceList; /** Kubernetes API verbs. */ -type KubernetesApiAction = 'create' | 'delete' | 'patch' | 'read' | 'replace'; +type KubernetesApiAction = 'create' | 'delete' | 'patch' | 'read' | 'list' | 'replace'; /** * Valid Content-Type header values for patch operations. See @@ -315,6 +315,96 @@ export class KubernetesObjectApi extends ApisApi { 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 @@ -403,7 +493,7 @@ export class KubernetesObjectApi extends ApisApi { if (!resource) { throw new Error(`Unrecognized API version and kind: ${spec.apiVersion} ${spec.kind}`); } - if (resource.namespaced && !spec.metadata.namespace) { + if (resource.namespaced && !spec.metadata.namespace && action !== 'list') { spec.metadata.namespace = this.defaultNamespace; } const parts = [this.apiVersionPath(spec.apiVersion)]; @@ -411,7 +501,7 @@ export class KubernetesObjectApi extends ApisApi { parts.push('namespaces', encodeURIComponent(String(spec.metadata.namespace))); } parts.push(resource.name); - if (action !== 'create') { + if (action !== 'create' && action !== 'list') { if (!spec.metadata.name) { throw new Error('Required spec property name is not set'); } diff --git a/src/object_test.ts b/src/object_test.ts index c5c33d75a34..77a258855d0 100644 --- a/src/object_test.ts +++ b/src/object_test.ts @@ -1747,6 +1747,85 @@ describe('KubernetesObject', () => { 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', () => { @@ -1921,5 +2000,31 @@ describe('KubernetesObject', () => { 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/top_test.ts b/src/top_test.ts index 429a150cf79..8d3e0cefbb1 100644 --- a/src/top_test.ts +++ b/src/top_test.ts @@ -29,7 +29,7 @@ const mockedPodMetrics: PodMetricsList = { }, timestamp: '2021-09-26T11:57:21Z', window: '30s', - containers: [{ name: 'nginx', usage: { cpu: '5000000n', memory: '3912Ki' } }], + containers: [{ name: 'nginx', usage: { cpu: '50000000n', memory: '3912Ki' } }], }, { metadata: { @@ -42,7 +42,7 @@ const mockedPodMetrics: PodMetricsList = { window: '30s', containers: [ { name: 'nginx', usage: { cpu: '0', memory: '4012Ki' } }, - { name: 'sidecar', usage: { cpu: '140000000n', memory: '3012Ki' } }, + { name: 'sidecar', usage: { cpu: '1400000000n', memory: '3012Ki' } }, ], }, ], diff --git a/src/util.ts b/src/util.ts index c0af5bd5e3e..19e46e5f82c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -28,9 +28,33 @@ export function quantityToScalar(quantity: string): number | bigint { } switch (suffix) { case 'n': - return Number(quantity.substr(0, quantity.length - 1)).valueOf() / 100_000_000.0; + return Number(quantity.substr(0, quantity.length - 1)).valueOf() / 1_000_000_000.0; + case 'u': + return Number(quantity.substr(0, quantity.length - 1)).valueOf() / 1_000_000.0; case 'm': return Number(quantity.substr(0, quantity.length - 1)).valueOf() / 1000.0; + case 'k': + return BigInt(quantity.substr(0, quantity.length - 1)) * BigInt(1000); + case 'M': + return BigInt(quantity.substr(0, quantity.length - 1)) * BigInt(1000 * 1000); + case 'G': + return BigInt(quantity.substr(0, quantity.length - 1)) * BigInt(1000 * 1000 * 1000); + case 'T': + return ( + BigInt(quantity.substr(0, quantity.length - 1)) * BigInt(1000 * 1000 * 1000) * BigInt(1000) + ); + case 'P': + return ( + BigInt(quantity.substr(0, quantity.length - 1)) * + BigInt(1000 * 1000 * 1000) * + BigInt(1000 * 1000) + ); + case 'E': + return ( + BigInt(quantity.substr(0, quantity.length - 1)) * + BigInt(1000 * 1000 * 1000) * + BigInt(1000 * 1000 * 1000) + ); case 'Ki': return BigInt(quantity.substr(0, quantity.length - 2)) * BigInt(1024); case 'Mi': diff --git a/src/util_test.ts b/src/util_test.ts index 322827aafdf..a68c5294f1e 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -54,11 +54,21 @@ describe('Utils', () => { it('should parse quantities', () => { expect(quantityToScalar('')).to.equal(0); + expect(quantityToScalar('2n')).to.equal(2 / 1_000_000_000); + expect(quantityToScalar('3u')).to.equal(3 / 1_000_000); expect(quantityToScalar('100m')).to.equal(0.1); + expect(quantityToScalar('3k')).to.equal(BigInt(3000)); + expect(quantityToScalar('3M')).to.equal(BigInt(3 * 1000 * 1000)); + expect(quantityToScalar('3G')).to.equal(BigInt(3 * 1000 * 1000 * 1000)); + expect(quantityToScalar('5T')).to.equal(BigInt(5 * 1000 * 1000 * 1000) * BigInt(1000)); + expect(quantityToScalar('3P')).to.equal(BigInt(3 * 1000 * 1000 * 1000) * BigInt(1000 * 1000)); + expect(quantityToScalar('14E')).to.equal( + BigInt(14 * 1000 * 1000 * 1000) * BigInt(1000 * 1000 * 1000), + ); + expect(quantityToScalar('0.2')).to.equal(0.2); expect(quantityToScalar('1976m')).to.equal(1.976); - expect(quantityToScalar('1024')).to.equal(1024); expect(quantityToScalar('1024')).to.equal(1024); expect(quantityToScalar('10e3')).to.equal(10000);