From 2f973f57f8c1916eaada2f399c52fbb62de54cc0 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 21 Oct 2019 21:50:12 -0700 Subject: [PATCH] [core-http] Establish standard PipelineOptions pattern in core-http (#5669) * Extract NewPipelineOptions from keyvault-keys into core-http * Move keyvault-certificates to new PipelineOptions model * Move keyvault-secrets to new PipelineOptions model * Remove unused imports in ServiceClient * Remove userAgentHeaderName from UserAgentOptions * Remove the need for min retry interval * Add RetryMode to RetryOptions * Add InternalPipelineOptions for configuring internal options * Narrow KeyVault client constructor options to PipelineOptions type * Add LoggingOptions to propagate AzureLogger with LogPolicyOptions * Add requestPolicyFilter to PipelineOptions * Fix LoggerOptions logger propagation and KeyVault configuration * Simplify UserAgentOptions handling * Remove unused pipeline related artifacts in keyvault * Merge remote-tracking branch 'origin/master' into pr/daviwil/5669 * Revert "Merge remote-tracking branch 'origin/master' into pr/daviwil/5669" This reverts commit 126f6f57f33770f78743125e7ea92a3fca089e27. * Remove unused pipeline related artifacts in keyvault certificates * Remove KeepAliveOptions.maxSockets (to be added back in the future) * Remove unused isPipelineOptions * Remove unused DefaultLogPolicyOptions * Rename userAgentPrefix local var * Alias ProxySettings as ProxyOptions * Rename PipelineOptions.requestPolicyFilter to updatePipelinePolicies --- sdk/core/core-http/lib/coreHttp.ts | 14 +- sdk/core/core-http/lib/pipelineOptions.ts | 75 +++++++++ .../lib/policies/deserializationPolicy.ts | 18 +++ .../lib/policies/exponentialRetryPolicy.ts | 55 +++++-- .../core-http/lib/policies/keepAlivePolicy.ts | 62 ++++++++ sdk/core/core-http/lib/policies/logPolicy.ts | 24 +++ .../core-http/lib/policies/redirectPolicy.ts | 21 +++ .../core-http/lib/policies/userAgentPolicy.ts | 11 ++ sdk/core/core-http/lib/serviceClient.ts | 138 +++++++++++++++-- .../review/keyvault-certificates.api.md | 23 +-- .../src/core/keyVaultBase.ts | 51 +----- .../keyvault-certificates/src/index.ts | 139 +++++------------ .../keyvault-keys/review/keyvault-keys.api.md | 24 +-- .../keyvault-keys/src/core/keyVaultBase.ts | 49 +----- .../keyvault-keys/src/cryptographyClient.ts | 141 +++++------------ sdk/keyvault/keyvault-keys/src/index.ts | 145 +++++------------- .../review/keyvault-secrets.api.md | 21 +-- .../keyvault-secrets/src/core/keyVaultBase.ts | 50 +----- sdk/keyvault/keyvault-secrets/src/index.ts | 136 +++++----------- 19 files changed, 566 insertions(+), 631 deletions(-) create mode 100644 sdk/core/core-http/lib/pipelineOptions.ts create mode 100644 sdk/core/core-http/lib/policies/keepAlivePolicy.ts diff --git a/sdk/core/core-http/lib/coreHttp.ts b/sdk/core/core-http/lib/coreHttp.ts index 1232dfc54e9d..951c9603bd14 100644 --- a/sdk/core/core-http/lib/coreHttp.ts +++ b/sdk/core/core-http/lib/coreHttp.ts @@ -29,15 +29,18 @@ export { ServiceClient, ServiceClientOptions, flattenResponse, - ProxySettings + createPipelineFromOptions, + ProxySettings, + ProxyOptions } from "./serviceClient"; +export { PipelineOptions, InternalPipelineOptions } from "./pipelineOptions"; export { QueryCollectionFormat } from "./queryCollectionFormat"; export { Constants } from "./util/constants"; export { BearerTokenAuthenticationPolicy, bearerTokenAuthenticationPolicy } from "./policies/bearerTokenAuthenticationPolicy"; -export { logPolicy } from "./policies/logPolicy"; +export { LogPolicyOptions, LoggingOptions, logPolicy } from "./policies/logPolicy"; export { BaseRequestPolicy, RequestPolicy, @@ -45,13 +48,14 @@ export { RequestPolicyOptions } from "./policies/requestPolicy"; export { generateClientRequestIdPolicy } from "./policies/generateClientRequestIdPolicy"; -export { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy"; +export { exponentialRetryPolicy, RetryOptions, RetryMode } from "./policies/exponentialRetryPolicy"; export { systemErrorRetryPolicy } from "./policies/systemErrorRetryPolicy"; export { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; export { getDefaultProxySettings, proxyPolicy } from "./policies/proxyPolicy"; -export { redirectPolicy } from "./policies/redirectPolicy"; +export { redirectPolicy, RedirectOptions } from "./policies/redirectPolicy"; +export { keepAlivePolicy, KeepAliveOptions } from "./policies/keepAlivePolicy"; export { signingPolicy } from "./policies/signingPolicy"; -export { userAgentPolicy, getDefaultUserAgentValue } from "./policies/userAgentPolicy"; +export { userAgentPolicy, getDefaultUserAgentValue, UserAgentOptions } from "./policies/userAgentPolicy"; export { deserializationPolicy, deserializeResponseBody } from "./policies/deserializationPolicy"; export { tracingPolicy } from "./policies/tracingPolicy"; export { diff --git a/sdk/core/core-http/lib/pipelineOptions.ts b/sdk/core/core-http/lib/pipelineOptions.ts new file mode 100644 index 000000000000..b087710ded24 --- /dev/null +++ b/sdk/core/core-http/lib/pipelineOptions.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { HttpClient } from "./httpClient"; +import { RetryOptions } from './policies/exponentialRetryPolicy'; +import { KeepAliveOptions } from './policies/keepAlivePolicy'; +import { RedirectOptions } from './policies/redirectPolicy'; +import { ProxyOptions } from './serviceClient'; +import { UserAgentOptions } from './policies/userAgentPolicy'; +import { DeserializationOptions } from './policies/deserializationPolicy'; +import { LoggingOptions } from './policies/logPolicy'; +import { RequestPolicyFactory } from './policies/requestPolicy'; + +/** + * Defines options that are used to configure the HTTP pipeline for + * an SDK client. + */ +export interface PipelineOptions { + /** + * The HttpClient implementation to use for outgoing HTTP requests. Defaults + * to DefaultHttpClient. + */ + httpClient?: HttpClient; + + /** + * Options that control how to retry failed requests. + */ + retryOptions?: RetryOptions; + + /** + * Options to configure a proxy for outgoing requests. + */ + proxyOptions?: ProxyOptions; + + /* + * Options for how HTTP connections should be maintained for future + * requests. + */ + keepAliveOptions?: KeepAliveOptions; + + /** + * Options for how redirect responses are handled. + */ + redirectOptions?: RedirectOptions; + + /** + * Options for adding user agent details to outgoing requests. + */ + userAgentOptions?: UserAgentOptions; + + /** + * A function that accepts the array of RequestPolicyFactory created for + * this PipelineOptions instance and can return modified list of policies. + * This is useful for adding, inserting, or removing policies in special + * case scenarios. If the function does not modify the array of + * RequestPolicyFactory, it must return the original array it was given. + */ + updatePipelinePolicies?: (requestPolicyFactories: RequestPolicyFactory[]) => RequestPolicyFactory[]; +} + +/** + * Defines options that are used to configure internal options of + * the HTTP pipeline for an SDK client. + */ +export interface InternalPipelineOptions extends PipelineOptions { + /** + * Options to configure API response deserialization. + */ + deserializationOptions?: DeserializationOptions; + + /** + * Options to configure request/response logging. + */ + loggingOptions?: LoggingOptions; +} diff --git a/sdk/core/core-http/lib/policies/deserializationPolicy.ts b/sdk/core/core-http/lib/policies/deserializationPolicy.ts index b9bd7adaeb60..47f40e530ee1 100644 --- a/sdk/core/core-http/lib/policies/deserializationPolicy.ts +++ b/sdk/core/core-http/lib/policies/deserializationPolicy.ts @@ -16,6 +16,17 @@ import { RequestPolicyOptions } from "./requestPolicy"; +/** + * Options to configure API response deserialization. + */ +export interface DeserializationOptions { + /** + * Configures the expected content types for the deserialization of + * JSON and XML response bodies. + */ + expectedContentTypes: DeserializationContentTypes; +} + /** * The content-types that will indicate that an operation response should be deserialized in a * particular way. @@ -51,6 +62,13 @@ export function deserializationPolicy( export const defaultJsonContentTypes = ["application/json", "text/json", "text/plain"]; export const defaultXmlContentTypes = ["application/xml", "application/atom+xml"]; +export const DefaultDeserializationOptions: DeserializationOptions = { + expectedContentTypes: { + json: defaultJsonContentTypes, + xml: defaultXmlContentTypes + } +}; + /** * A RequestPolicy that will deserialize HTTP response bodies and headers as they pass through the * HTTP pipeline. diff --git a/sdk/core/core-http/lib/policies/exponentialRetryPolicy.ts b/sdk/core/core-http/lib/policies/exponentialRetryPolicy.ts index d3187d0fa761..473bffb724cd 100644 --- a/sdk/core/core-http/lib/policies/exponentialRetryPolicy.ts +++ b/sdk/core/core-http/lib/policies/exponentialRetryPolicy.ts @@ -28,7 +28,6 @@ export interface RetryError extends Error { export function exponentialRetryPolicy( retryCount?: number, retryInterval?: number, - minRetryInterval?: number, maxRetryInterval?: number ): RequestPolicyFactory { return { @@ -38,7 +37,6 @@ export function exponentialRetryPolicy( options, retryCount, retryInterval, - minRetryInterval, maxRetryInterval ); } @@ -48,7 +46,48 @@ export function exponentialRetryPolicy( const DEFAULT_CLIENT_RETRY_INTERVAL = 1000 * 30; const DEFAULT_CLIENT_RETRY_COUNT = 3; const DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 90; -const DEFAULT_CLIENT_MIN_RETRY_INTERVAL = 1000 * 3; + +/** + * Describes the Retry Mode type. Currently supporting only Exponential. + * @enum RetryMode + */ +export enum RetryMode { + Exponential +} + +/** + * Options that control how to retry failed requests. + */ +export interface RetryOptions { + /** + * The maximum number of retry attempts. Defaults to 3. + */ + maxRetries?: number; + + /** + * The amount of delay in milliseconds between retry attempts. Defaults to 30000 + * (30 seconds). The delay increases exponentially with each retry up to a maximum + * specified by maxRetryDelayInMs. + */ + retryDelayInMs?: number; + + /** + * The maximum delay in milliseconds allowed before retrying an operation. Defaults + * to 90000 (90 seconds). + */ + maxRetryDelayInMs?: number; + + /** + * Currently supporting only Exponential mode. + */ + mode?: RetryMode; +} + +export const DefaultRetryOptions: RetryOptions = { + maxRetries: DEFAULT_CLIENT_RETRY_COUNT, + retryDelayInMs: DEFAULT_CLIENT_RETRY_INTERVAL, + maxRetryDelayInMs: DEFAULT_CLIENT_MAX_RETRY_INTERVAL +} /** * @class @@ -63,10 +102,6 @@ export class ExponentialRetryPolicy extends BaseRequestPolicy { * The client retry interval in milliseconds. */ retryInterval: number; - /** - * The minimum retry interval in milliseconds. - */ - minRetryInterval: number; /** * The maximum retry interval in milliseconds. */ @@ -86,7 +121,6 @@ export class ExponentialRetryPolicy extends BaseRequestPolicy { options: RequestPolicyOptions, retryCount?: number, retryInterval?: number, - minRetryInterval?: number, maxRetryInterval?: number ) { super(nextPolicy, options); @@ -95,9 +129,6 @@ export class ExponentialRetryPolicy extends BaseRequestPolicy { } this.retryCount = isNumber(retryCount) ? retryCount : DEFAULT_CLIENT_RETRY_COUNT; this.retryInterval = isNumber(retryInterval) ? retryInterval : DEFAULT_CLIENT_RETRY_INTERVAL; - this.minRetryInterval = isNumber(minRetryInterval) - ? minRetryInterval - : DEFAULT_CLIENT_MIN_RETRY_INTERVAL; this.maxRetryInterval = isNumber(maxRetryInterval) ? maxRetryInterval : DEFAULT_CLIENT_MAX_RETRY_INTERVAL; @@ -181,7 +212,7 @@ function updateRetryData( incrementDelta *= boundedRandDelta; retryData.retryInterval = Math.min( - policy.minRetryInterval + incrementDelta, + incrementDelta, policy.maxRetryInterval ); diff --git a/sdk/core/core-http/lib/policies/keepAlivePolicy.ts b/sdk/core/core-http/lib/policies/keepAlivePolicy.ts new file mode 100644 index 000000000000..88dd0dc709b9 --- /dev/null +++ b/sdk/core/core-http/lib/policies/keepAlivePolicy.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BaseRequestPolicy, RequestPolicy, RequestPolicyOptions } from './requestPolicy'; +import { WebResource } from '../webResource'; +import { HttpOperationResponse } from '../httpOperationResponse'; + +/** + * Options for how HTTP connections should be maintained for future + * requests. + */ +export interface KeepAliveOptions { + /* + * When true, connections will be kept alive for multiple requests. + * Defaults to true. + */ + enable: boolean; +} + +export const DefaultKeepAliveOptions: KeepAliveOptions = { + enable: true +} + +export function keepAlivePolicy(keepAliveOptions?: KeepAliveOptions) { + return { + create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => { + return new KeepAlivePolicy(nextPolicy, options, keepAliveOptions || DefaultKeepAliveOptions); + } + }; +} + +/** + * KeepAlivePolicy is a policy used to control keep alive settings for every request. + */ +export class KeepAlivePolicy extends BaseRequestPolicy { + /** + * Creates an instance of KeepAlivePolicy. + * + * @param {RequestPolicy} nextPolicy + * @param {RequestPolicyOptions} options + * @param {KeepAliveOptions} [keepAliveOptions] + */ + constructor( + nextPolicy: RequestPolicy, + options: RequestPolicyOptions, + private readonly keepAliveOptions: KeepAliveOptions + ) { + super(nextPolicy, options); + } + + /** + * Sends out request. + * + * @param {WebResource} request + * @returns {Promise} + * @memberof KeepAlivePolicy + */ + public async sendRequest(request: WebResource): Promise { + request.keepAlive = this.keepAliveOptions.enable; + return this._nextPolicy.sendRequest(request); + } +} diff --git a/sdk/core/core-http/lib/policies/logPolicy.ts b/sdk/core/core-http/lib/policies/logPolicy.ts index 78b852ba3862..7ae00c23d498 100644 --- a/sdk/core/core-http/lib/policies/logPolicy.ts +++ b/sdk/core/core-http/lib/policies/logPolicy.ts @@ -10,6 +10,7 @@ import { RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy"; +import { Debugger } from "@azure/logger"; import { logger as coreLogger, logger } from "../log"; export interface LogPolicyOptions { @@ -28,6 +29,21 @@ export interface LogPolicyOptions { allowedQueryParameters?: string[]; } +/** + * Options to configure request/response logging. + */ +export interface LoggingOptions { + /** + * The Debugger (logger) instance to use for writing pipeline logs. + */ + logger?: Debugger, + + /** + * Options to pass to the logPolicy factory. + */ + logPolicyOptions?: LogPolicyOptions +} + const RedactedString = "REDACTED"; const defaultAllowedHeaderNames = [ @@ -60,6 +76,14 @@ const defaultAllowedQueryParameters: string[] = [ "api-version" ]; +export const DefaultLoggingOptions: LoggingOptions = { + logger: undefined, + logPolicyOptions: { + allowedHeaderNames: [], // These are empty lists because they are additive to + allowedQueryParameters: [] // the real defaultAllowed[HeaderNames|QueryParameters]. + } +} + export function logPolicy( logger: any = coreLogger.info.bind(coreLogger), logOptions: LogPolicyOptions = {} diff --git a/sdk/core/core-http/lib/policies/redirectPolicy.ts b/sdk/core/core-http/lib/policies/redirectPolicy.ts index 8128c9eb43d7..e7b71266d32d 100644 --- a/sdk/core/core-http/lib/policies/redirectPolicy.ts +++ b/sdk/core/core-http/lib/policies/redirectPolicy.ts @@ -11,6 +11,27 @@ import { RequestPolicyOptions } from "./requestPolicy"; +/** + * Options for how redirect responses are handled. + */ +export interface RedirectOptions { + /* + * When true, redirect responses are followed. Defaults to true. + */ + handleRedirects: boolean; + + /* + * The maximum number of times the redirect URL will be tried before + * failing. Defaults to 20. + */ + maxRetries?: number; +} + +export const DefaultRedirectOptions: RedirectOptions = { + handleRedirects: true, + maxRetries: 20 +} + export function redirectPolicy(maximumRetries = 20): RequestPolicyFactory { return { create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => { diff --git a/sdk/core/core-http/lib/policies/userAgentPolicy.ts b/sdk/core/core-http/lib/policies/userAgentPolicy.ts index 9409189d4bce..0ebc9732e9aa 100644 --- a/sdk/core/core-http/lib/policies/userAgentPolicy.ts +++ b/sdk/core/core-http/lib/policies/userAgentPolicy.ts @@ -15,6 +15,17 @@ import { export type TelemetryInfo = { key?: string; value?: string }; +/** + * Options for adding user agent details to outgoing requests. + */ +export interface UserAgentOptions { + /* + * String prefix to add to the user agent for outgoing requests. + * Defaults to an empty string. + */ + userAgentPrefix?: string; +} + function getRuntimeInfo(): TelemetryInfo[] { const msRestRuntime = { key: "core-http", diff --git a/sdk/core/core-http/lib/serviceClient.ts b/sdk/core/core-http/lib/serviceClient.ts index d19416fabbcc..d41eb40ac26e 100644 --- a/sdk/core/core-http/lib/serviceClient.ts +++ b/sdk/core/core-http/lib/serviceClient.ts @@ -6,7 +6,7 @@ import { DefaultHttpClient } from "./defaultHttpClient"; import { HttpClient } from "./httpClient"; import { HttpOperationResponse, RestResponse } from "./httpOperationResponse"; import { HttpPipelineLogger } from "./httpPipelineLogger"; -import { logPolicy } from "./policies/logPolicy"; +import { logPolicy, DefaultLoggingOptions } from "./policies/logPolicy"; import { OperationArguments } from "./operationArguments"; import { getPathStringFromParameter, @@ -17,16 +17,17 @@ import { import { isStreamOperation, OperationSpec } from "./operationSpec"; import { deserializationPolicy, - DeserializationContentTypes + DeserializationContentTypes, + DefaultDeserializationOptions } from "./policies/deserializationPolicy"; -import { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy"; +import { exponentialRetryPolicy, DefaultRetryOptions } from "./policies/exponentialRetryPolicy"; import { generateClientRequestIdPolicy } from "./policies/generateClientRequestIdPolicy"; import { userAgentPolicy, getDefaultUserAgentHeaderName, - getDefaultUserAgentValue + getDefaultUserAgentValue, } from "./policies/userAgentPolicy"; -import { redirectPolicy } from "./policies/redirectPolicy"; +import { redirectPolicy, DefaultRedirectOptions } from "./policies/redirectPolicy"; import { RequestPolicy, RequestPolicyFactory, @@ -42,23 +43,43 @@ import * as utils from "./util/utils"; import { stringifyXML } from "./util/xml"; import { RequestOptionsBase, RequestPrepareOptions, WebResource } from "./webResource"; import { OperationResponse } from "./operationResponse"; -import { ServiceCallback } from "./util/utils"; +import { ServiceCallback, isNode } from "./util/utils"; import { proxyPolicy, getDefaultProxySettings } from "./policies/proxyPolicy"; import { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; import { ServiceClientCredentials } from "./credentials/serviceClientCredentials"; import { signingPolicy } from "./policies/signingPolicy"; import { logger } from "./log"; +import { InternalPipelineOptions } from './pipelineOptions'; +import { DefaultKeepAliveOptions, keepAlivePolicy } from './policies/keepAlivePolicy'; +import { tracingPolicy } from './policies/tracingPolicy'; /** - * HTTP proxy settings (Node.js only) + * Options to configure a proxy for outgoing requests (Node.js only). */ export interface ProxySettings { + /* + * The proxy's host address. + */ host: string; + + /* + * The proxy host's port. + */ port: number; + + /** + * The user name to authenticate with the proxy, if required. + */ username?: string; + + /** + * The password to authenticate with the proxy, if required. + */ password?: string; } +export type ProxyOptions = ProxySettings; // Alias ProxySettings as ProxyOptions for future use. + /** * Options to be provided while creating the client. */ @@ -120,10 +141,6 @@ export interface ServiceClientOptions { * Proxy settings which will be used for every HTTP request (Node.js only). */ proxySettings?: ProxySettings; - /** - * When true, keeps the TCP socket alive across multiple requests (Node.js only). - */ - keepAlive?: boolean; } /** @@ -449,7 +466,7 @@ export class ServiceClient { operationSpec.responses[sendRequestError.statusCode] || operationSpec.responses["default"] ); - } + } result = Promise.reject( sendRequestError ); @@ -613,6 +630,103 @@ function createDefaultRequestPolicyFactories( return factories; } +export function createPipelineFromOptions( + pipelineOptions: InternalPipelineOptions, + authPolicyFactory?: RequestPolicyFactory +) : ServiceClientOptions { + let requestPolicyFactories: RequestPolicyFactory[] = []; + + let userAgentValue = undefined; + if (pipelineOptions.userAgentOptions && pipelineOptions.userAgentOptions.userAgentPrefix) { + const userAgentInfo: string[] = []; + userAgentInfo.push(pipelineOptions.userAgentOptions.userAgentPrefix); + + // Add the default user agent value if it isn't already specified + // by the userAgentPrefix option. + const defaultUserAgentInfo = getDefaultUserAgentValue(); + if (userAgentInfo.indexOf(defaultUserAgentInfo) === -1) { + userAgentInfo.push(defaultUserAgentInfo); + } + + userAgentValue = userAgentInfo.join(" "); + } + + const keepAliveOptions = { + ...DefaultKeepAliveOptions, + ...pipelineOptions.keepAliveOptions + }; + + const retryOptions = { + ...DefaultRetryOptions, + ...pipelineOptions.retryOptions + }; + + const redirectOptions = { + ...DefaultRedirectOptions, + ...pipelineOptions.redirectOptions + }; + + const proxySettings = pipelineOptions.proxyOptions || getDefaultProxySettings(); + if (isNode && proxySettings) { + requestPolicyFactories.push( + proxyPolicy(proxySettings) + ) + } + + const deserializationOptions = { + ...DefaultDeserializationOptions, + ...pipelineOptions.deserializationOptions + }; + + const loggingOptions = { + ...DefaultLoggingOptions, + ...pipelineOptions.loggingOptions + }; + + requestPolicyFactories.push( + tracingPolicy(), + keepAlivePolicy(keepAliveOptions), + userAgentPolicy({ value: userAgentValue }), + generateClientRequestIdPolicy(), + deserializationPolicy(deserializationOptions.expectedContentTypes), + throttlingRetryPolicy(), + systemErrorRetryPolicy(), + exponentialRetryPolicy( + retryOptions.maxRetries, + retryOptions.retryDelayInMs, + retryOptions.maxRetryDelayInMs + ) + ) + + if (redirectOptions.handleRedirects) { + requestPolicyFactories.push( + redirectPolicy(redirectOptions.maxRetries) + ); + } + + if (authPolicyFactory) { + requestPolicyFactories.push(authPolicyFactory); + } + + requestPolicyFactories.push( + logPolicy( + loggingOptions.logger, + loggingOptions.logPolicyOptions + ) + ); + + if (pipelineOptions.updatePipelinePolicies) { + // If the update function throws an exception, let it bubble up. + requestPolicyFactories = pipelineOptions.updatePipelinePolicies(requestPolicyFactories); + } + + return { + httpClient: pipelineOptions.httpClient, + requestPolicyFactories + }; +} + + export type PropertyParent = { [propertyName: string]: any }; /** diff --git a/sdk/keyvault/keyvault-certificates/review/keyvault-certificates.api.md b/sdk/keyvault/keyvault-certificates/review/keyvault-certificates.api.md index e500b805decd..75d2ade5e9db 100644 --- a/sdk/keyvault/keyvault-certificates/review/keyvault-certificates.api.md +++ b/sdk/keyvault/keyvault-certificates/review/keyvault-certificates.api.md @@ -5,16 +5,15 @@ ```ts import * as coreHttp from '@azure/core-http'; -import { HttpClient } from '@azure/core-http'; -import { HttpPipelineLogger } from '@azure/core-http'; import { PagedAsyncIterableIterator } from '@azure/core-paging'; +import { PipelineOptions } from '@azure/core-http'; import { RequestOptionsBase } from '@azure/core-http'; import { ServiceClientOptions } from '@azure/core-http'; import { TokenCredential } from '@azure/core-http'; // @public export class CertificateClient { - constructor(endPoint: string, credential: TokenCredential, pipelineOrOptions?: ServiceClientOptions | NewPipelineOptions); + constructor(endPoint: string, credential: TokenCredential, pipelineOptions?: PipelineOptions); // Warning: (ae-forgotten-export) The symbol "BackupCertificateResult" needs to be exported by the entry point index.d.ts backupCertificate(name: string, options?: RequestOptionsBase): Promise; cancelCertificateOperation(name: string, options?: RequestOptionsBase): Promise; @@ -31,7 +30,6 @@ export class CertificateClient { getCertificateOperation(name: string, options?: RequestOptionsBase): Promise; getCertificatePolicy(name: string, options?: RequestOptionsBase): Promise; getCertificateWithPolicy(name: string, options?: RequestOptionsBase): Promise; - static getDefaultPipeline(credential: TokenCredential, pipelineOptions?: NewPipelineOptions): ServiceClientOptions; getDeletedCertificate(name: string, options?: RequestOptionsBase): Promise; importCertificate(name: string, base64EncodedCertificate: string, options?: KeyVaultClientImportCertificateOptionalParams): Promise; listCertificateIssuers(options?: KeyVaultClientGetCertificateIssuersOptionalParams): PagedAsyncIterableIterator; @@ -72,7 +70,7 @@ export interface CertificateOperation { } // Warning: (ae-forgotten-export) The symbol "CertificateAttributes" needs to be exported by the entry point index.d.ts -// +// // @public export interface CertificatePolicy extends SecretProperties, CertificateAttributes { certificateTransparency?: boolean; @@ -222,19 +220,6 @@ export interface LifetimeAction { // @public export const logger: import("@azure/logger").AzureLogger; -// @public -export interface NewPipelineOptions { - // (undocumented) - HTTPClient?: HttpClient; - // (undocumented) - logger?: HttpPipelineLogger; - // (undocumented) - proxyOptions?: ProxyOptions; - // (undocumented) - retryOptions?: RetryOptions; - telemetry?: TelemetryOptions; -} - // @public export interface OrganizationDetails { // Warning: (ae-forgotten-export) The symbol "AdministratorDetails" needs to be exported by the entry point index.d.ts @@ -249,6 +234,8 @@ export interface ParsedKeyVaultEntityIdentifier { version?: string; } +export { PipelineOptions } + // @public export interface ProxyOptions { // (undocumented) diff --git a/sdk/keyvault/keyvault-certificates/src/core/keyVaultBase.ts b/sdk/keyvault/keyvault-certificates/src/core/keyVaultBase.ts index e223dc00421e..83eebf87e8ce 100644 --- a/sdk/keyvault/keyvault-certificates/src/core/keyVaultBase.ts +++ b/sdk/keyvault/keyvault-certificates/src/core/keyVaultBase.ts @@ -1,12 +1,5 @@ -import { ServiceClientOptions as Pipeline } from "@azure/core-http"; -import { - HttpClient as IHttpClient, - HttpPipelineLogger as IHttpPipelineLogger -} from "@azure/core-http"; - -import { RetryOptions, ProxyOptions, TelemetryOptions } from "."; - -export { Pipeline }; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. export interface ParsedKeyVaultEntityIdentifier { /** @@ -22,43 +15,3 @@ export interface ParsedKeyVaultEntityIdentifier { */ name: string; } - -/** - * Option interface for Pipeline.newPipeline method. - * - * Properties of this interface should not overlap with properties of {@link Pipeline} - * as we use them to differentiate instances of NewPipelineOptions from instances of Pipeline. - * If this interface is modified, the method isNewPipelineOptions() should also be updated - * to adapt the changes. - * - * @export - * @interface NewPipelineOptions - */ -export interface NewPipelineOptions { - /** - * Telemetry configures the built-in telemetry policy behavior. - * - * @type {TelemetryOptions} - * @memberof NewPipelineOptions - */ - telemetry?: TelemetryOptions; - retryOptions?: RetryOptions; - proxyOptions?: ProxyOptions; - - logger?: IHttpPipelineLogger; - HTTPClient?: IHttpClient; -} - -export function isNewPipelineOptions( - pipelineOrOptions: Pipeline | NewPipelineOptions -): pipelineOrOptions is NewPipelineOptions { - // An empty object is consider options - function isEmptyObject(obj: Pipeline | NewPipelineOptions) { - return Object.keys(obj).length === 0 && obj.constructor === Object; - } - const options = pipelineOrOptions as NewPipelineOptions; - return ( - isEmptyObject(pipelineOrOptions) || - !!(options.retryOptions || options.proxyOptions || options.logger || options.HTTPClient) - ); -} diff --git a/sdk/keyvault/keyvault-certificates/src/index.ts b/sdk/keyvault/keyvault-certificates/src/index.ts index 3155d82fa9b7..20bbb8cd45dc 100644 --- a/sdk/keyvault/keyvault-certificates/src/index.ts +++ b/sdk/keyvault/keyvault-certificates/src/index.ts @@ -1,22 +1,11 @@ import { - getDefaultUserAgentValue, TokenCredential, isTokenCredential, - RequestPolicyFactory, - deserializationPolicy, signingPolicy, - exponentialRetryPolicy, - redirectPolicy, - systemErrorRetryPolicy, - generateClientRequestIdPolicy, - proxyPolicy, - throttlingRetryPolicy, - getDefaultProxySettings, - isNode, - userAgentPolicy, RequestOptionsBase, - tracingPolicy, - logPolicy + PipelineOptions, + createPipelineFromOptions, + ServiceClientOptions as Pipeline } from "@azure/core-http"; import { getTracer, Span } from "@azure/core-tracing"; @@ -33,10 +22,7 @@ import { SubjectAlternativeNames } from "./certificatesModels"; import { - NewPipelineOptions, - isNewPipelineOptions, ParsedKeyVaultEntityIdentifier, - Pipeline } from "./core/keyVaultBase"; import { TelemetryOptions } from "./core/clientOptions"; import { @@ -91,7 +77,7 @@ import { } from "./core/models"; import { KeyVaultClient } from "./core/keyVaultClient"; import { ProxyOptions, RetryOptions } from "./core"; -import { RetryConstants, SDK_VERSION } from "./core/utils/constants"; +import { SDK_VERSION } from "./core/utils/constants"; import { parseKeyvaultIdentifier as parseKeyvaultEntityIdentifier } from "./core/utils"; import "@azure/core-paging"; import { PageSettings, PagedAsyncIterableIterator } from "@azure/core-paging"; @@ -117,7 +103,7 @@ export { KeyVaultClientUpdateCertificateIssuerOptionalParams, KeyVaultClientUpdateCertificateOptionalParams, LifetimeAction, - NewPipelineOptions, + PipelineOptions, OrganizationDetails, ParsedKeyVaultEntityIdentifier, SecretProperties, @@ -215,65 +201,6 @@ function toPublicPolicy(p: CoreCertificatePolicy = {}): CertificatePolicy { */ export class CertificateClient { - /** - * A static method used to create a new Pipeline object with the provided Credential. - * - * @static - * @param {TokenCredential} The credential to use for API requests. - * @param {NewPipelineOptions} [pipelineOptions] Optional. Options. - * @memberof CertificateClient - */ - public static getDefaultPipeline( - credential: TokenCredential, - pipelineOptions: NewPipelineOptions = {} - ): Pipeline { - // Order is important. Closer to the API at the top & closer to the network at the bottom. - // The credential's policy factory must appear close to the wire so it can sign any - // changes made by other factories (like UniqueRequestIDPolicyFactory) - const retryOptions = pipelineOptions.retryOptions || {}; - - const userAgentString: string = CertificateClient.getUserAgentString(pipelineOptions.telemetry); - - let requestPolicyFactories: RequestPolicyFactory[] = []; - if (isNode) { - requestPolicyFactories.push( - proxyPolicy(getDefaultProxySettings((pipelineOptions.proxyOptions || {}).proxySettings)) - ); - } - requestPolicyFactories = requestPolicyFactories.concat([ - tracingPolicy(), - userAgentPolicy({ value: userAgentString }), - generateClientRequestIdPolicy(), - deserializationPolicy(), // Default deserializationPolicy is provided by protocol layer - throttlingRetryPolicy(), - systemErrorRetryPolicy(), - exponentialRetryPolicy( - retryOptions.retryCount, - retryOptions.retryIntervalInMS, - RetryConstants.MIN_RETRY_INTERVAL_MS, // Minimum retry interval to prevent frequent retries - retryOptions.maxRetryDelayInMs - ), - redirectPolicy(), - isTokenCredential(credential) - ? challengeBasedAuthenticationPolicy(credential) - : signingPolicy(credential), - logPolicy( - logger.info, { - allowedHeaderNames: [ - "x-ms-keyvault-region", - "x-ms-keyvault-network-info", - "x-ms-keyvault-service-version" - ] - }) - ]); - - return { - httpClient: pipelineOptions.HTTPClient, - httpPipelineLogger: pipelineOptions.logger, - requestPolicyFactories - }; - } - /** * The base URL to the vault */ @@ -294,42 +221,52 @@ export class CertificateClient { * Creates an instance of CertificateClient. * @param {string} url the base url to the key vault. * @param {TokenCredential} The credential to use for API requests. - * @param {(Pipeline | NewPipelineOptions)} [pipelineOrOptions={}] Optional. A Pipeline, or options to create a default Pipeline instance. - * Omitting this parameter to create the default Pipeline instance. + * @param {PipelineOptions} [pipelineOptions={}] Optional. Pipeline options used to configure Key Vault API requests. + * Omit this parameter to use the default pipeline configuration. * @memberof CertificateClient */ constructor( endPoint: string, credential: TokenCredential, - pipelineOrOptions: Pipeline | NewPipelineOptions = {} + pipelineOptions: PipelineOptions = {} ) { this.vaultEndpoint = endPoint; this.credential = credential; - if (isNewPipelineOptions(pipelineOrOptions)) { - this.pipeline = CertificateClient.getDefaultPipeline(credential, pipelineOrOptions); + + const libInfo = `azsdk-js-keyvault-certificates/${SDK_VERSION}`; + if (pipelineOptions.userAgentOptions) { + pipelineOptions.userAgentOptions.userAgentPrefix !== undefined + ? `${pipelineOptions.userAgentOptions.userAgentPrefix} ${libInfo}` + : libInfo; } else { - this.pipeline = pipelineOrOptions; + pipelineOptions.userAgentOptions = { + userAgentPrefix: libInfo + } } - this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); - } - - private static getUserAgentString(telemetry?: TelemetryOptions) { - const userAgentInfo: string[] = []; - if (telemetry) { - if (userAgentInfo.indexOf(telemetry.value) === -1) { - userAgentInfo.push(telemetry.value); + const authPolicy = + isTokenCredential(credential) + ? challengeBasedAuthenticationPolicy(credential) + : signingPolicy(credential) + + const internalPipelineOptions = { + ...pipelineOptions, + ...{ + loggingOptions: { + logger: logger.info, + logPolicyOptions: { + allowedHeaderNames: [ + "x-ms-keyvault-region", + "x-ms-keyvault-network-info", + "x-ms-keyvault-service-version" + ] + } + } } } - const libInfo = `Azure-KeyVault-Certificates/${SDK_VERSION}`; - if (userAgentInfo.indexOf(libInfo) === -1) { - userAgentInfo.push(libInfo); - } - const defaultUserAgentInfo = getDefaultUserAgentValue(); - if (userAgentInfo.indexOf(defaultUserAgentInfo) === -1) { - userAgentInfo.push(defaultUserAgentInfo); - } - return userAgentInfo.join(" "); + + this.pipeline = createPipelineFromOptions(internalPipelineOptions, authPolicy); + this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); } private async *listCertificatesPage( diff --git a/sdk/keyvault/keyvault-keys/review/keyvault-keys.api.md b/sdk/keyvault/keyvault-keys/review/keyvault-keys.api.md index 463e6a40b71f..9c8db6cfaea4 100644 --- a/sdk/keyvault/keyvault-keys/review/keyvault-keys.api.md +++ b/sdk/keyvault/keyvault-keys/review/keyvault-keys.api.md @@ -5,10 +5,9 @@ ```ts import * as coreHttp from '@azure/core-http'; -import { HttpClient } from '@azure/core-http'; -import { HttpPipelineLogger } from '@azure/core-http'; import { PagedAsyncIterableIterator } from '@azure/core-paging'; import { PageSettings } from '@azure/core-paging'; +import { PipelineOptions } from '@azure/core-http'; import { PollerLike } from '@azure/core-lro'; import { PollOperationState } from '@azure/core-lro'; import { RequestOptionsBase } from '@azure/core-http'; @@ -45,11 +44,10 @@ export interface CreateRsaKeyOptions extends CreateKeyOptions { // @public export class CryptographyClient { constructor(url: string, key: string | JsonWebKey, // keyUrl or JsonWebKey - credential: TokenCredential, pipelineOrOptions?: ServiceClientOptions | NewPipelineOptions); + credential: TokenCredential, pipelineOptions?: PipelineOptions); protected readonly credential: ServiceClientCredentials | TokenCredential; decrypt(algorithm: JsonWebKeyEncryptionAlgorithm, ciphertext: Uint8Array, options?: DecryptOptions): Promise; encrypt(algorithm: JsonWebKeyEncryptionAlgorithm, plaintext: Uint8Array, options?: EncryptOptions): Promise; - static getDefaultPipeline(credential: ServiceClientCredentials | TokenCredential, pipelineOptions?: NewPipelineOptions): ServiceClientOptions; getKey(options?: GetKeyOptions): Promise; key: string | JsonWebKey; readonly pipeline: ServiceClientOptions; @@ -180,7 +178,7 @@ export interface Key { // @public export class KeyClient { - constructor(endPoint: string, credential: TokenCredential, pipelineOrOptions?: ServiceClientOptions | NewPipelineOptions); + constructor(endPoint: string, credential: TokenCredential, pipelineOptions?: PipelineOptions); backupKey(name: string, options?: RequestOptions): Promise; beginDeleteKey(name: string, options?: KeyPollerOptions): Promise, DeletedKey>>; beginRecoverDeletedKey(name: string, options?: KeyPollerOptions): Promise, DeletedKey>>; @@ -188,7 +186,6 @@ export class KeyClient { createKey(name: string, keyType: JsonWebKeyType, options?: CreateKeyOptions): Promise; createRsaKey(name: string, options?: CreateRsaKeyOptions): Promise; protected readonly credential: TokenCredential; - static getDefaultPipeline(credential: TokenCredential, pipelineOptions?: NewPipelineOptions): ServiceClientOptions; getDeletedKey(name: string, options?: RequestOptions): Promise; getKey(name: string, options?: GetKeyOptions): Promise; importKey(name: string, key: JsonWebKey, options: ImportKeyOptions): Promise; @@ -229,19 +226,6 @@ export type KeyWrapAlgorithm = "RSA-OAEP" | "RSA-OAEP-256" | "RSA1_5"; // @public export const logger: import("@azure/logger").AzureLogger; -// @public -export interface NewPipelineOptions { - // (undocumented) - HTTPClient?: HttpClient; - // (undocumented) - logger?: HttpPipelineLogger; - // (undocumented) - proxyOptions?: ProxyOptions; - // (undocumented) - retryOptions?: RetryOptions; - telemetry?: TelemetryOptions; -} - export { PagedAsyncIterableIterator } export { PageSettings } @@ -253,6 +237,8 @@ export interface ParsedKeyVaultEntityIdentifier { version?: string; } +export { PipelineOptions } + export { PollerLike } export { PollOperationState } diff --git a/sdk/keyvault/keyvault-keys/src/core/keyVaultBase.ts b/sdk/keyvault/keyvault-keys/src/core/keyVaultBase.ts index e223dc00421e..1de63ddc5160 100644 --- a/sdk/keyvault/keyvault-keys/src/core/keyVaultBase.ts +++ b/sdk/keyvault/keyvault-keys/src/core/keyVaultBase.ts @@ -1,12 +1,5 @@ -import { ServiceClientOptions as Pipeline } from "@azure/core-http"; -import { - HttpClient as IHttpClient, - HttpPipelineLogger as IHttpPipelineLogger -} from "@azure/core-http"; - -import { RetryOptions, ProxyOptions, TelemetryOptions } from "."; - -export { Pipeline }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. export interface ParsedKeyVaultEntityIdentifier { /** @@ -23,42 +16,4 @@ export interface ParsedKeyVaultEntityIdentifier { name: string; } -/** - * Option interface for Pipeline.newPipeline method. - * - * Properties of this interface should not overlap with properties of {@link Pipeline} - * as we use them to differentiate instances of NewPipelineOptions from instances of Pipeline. - * If this interface is modified, the method isNewPipelineOptions() should also be updated - * to adapt the changes. - * - * @export - * @interface NewPipelineOptions - */ -export interface NewPipelineOptions { - /** - * Telemetry configures the built-in telemetry policy behavior. - * - * @type {TelemetryOptions} - * @memberof NewPipelineOptions - */ - telemetry?: TelemetryOptions; - retryOptions?: RetryOptions; - proxyOptions?: ProxyOptions; - logger?: IHttpPipelineLogger; - HTTPClient?: IHttpClient; -} - -export function isNewPipelineOptions( - pipelineOrOptions: Pipeline | NewPipelineOptions -): pipelineOrOptions is NewPipelineOptions { - // An empty object is consider options - function isEmptyObject(obj: Pipeline | NewPipelineOptions) { - return Object.keys(obj).length === 0 && obj.constructor === Object; - } - const options = pipelineOrOptions as NewPipelineOptions; - return ( - isEmptyObject(pipelineOrOptions) || - !!(options.retryOptions || options.proxyOptions || options.logger || options.HTTPClient) - ); -} diff --git a/sdk/keyvault/keyvault-keys/src/cryptographyClient.ts b/sdk/keyvault/keyvault-keys/src/cryptographyClient.ts index 1cfd0af0c1ef..8ab99ee203a7 100644 --- a/sdk/keyvault/keyvault-keys/src/cryptographyClient.ts +++ b/sdk/keyvault/keyvault-keys/src/cryptographyClient.ts @@ -4,27 +4,16 @@ import { ServiceClientCredentials, TokenCredential, isNode, - RequestPolicyFactory, + PipelineOptions, + createPipelineFromOptions, + ServiceClientOptions as Pipeline, isTokenCredential, - deserializationPolicy, - signingPolicy, - exponentialRetryPolicy, - redirectPolicy, - systemErrorRetryPolicy, - generateClientRequestIdPolicy, - proxyPolicy, - throttlingRetryPolicy, - getDefaultProxySettings, - userAgentPolicy, - getDefaultUserAgentValue, - logPolicy + signingPolicy } from "@azure/core-http"; import { logger } from "./log"; import { parseKeyvaultIdentifier } from "./core/utils"; -import { TelemetryOptions } from "./core"; -import { RetryConstants, SDK_VERSION } from "./core/utils/constants"; -import { NewPipelineOptions, isNewPipelineOptions, Pipeline } from "./core/keyVaultBase"; +import { SDK_VERSION } from "./core/utils/constants"; import { KeyVaultClient } from "./core/keyVaultClient"; import { challengeBasedAuthenticationPolicy } from "./core/challengeBasedAuthenticationPolicy"; import * as crypto from "crypto"; @@ -503,84 +492,6 @@ export class CryptographyClient { return { result: result.value!, keyID: this.getKeyID() }; } - /** - * A static method used to create a new Pipeline object with the provided Credential. - * - * @static - * @param {TokenCredential} The credential to use for API requests. - * @param {NewPipelineOptions} [pipelineOptions] Optional. Options. - * @memberof CryptographyClient - */ - public static getDefaultPipeline( - credential: ServiceClientCredentials | TokenCredential, - pipelineOptions: NewPipelineOptions = {} - ): Pipeline { - // Order is important. Closer to the API at the top & closer to the network at the bottom. - // The credential's policy factory must appear close to the wire so it can sign any - // changes made by other factories (like UniqueRequestIDPolicyFactory) - const retryOptions = pipelineOptions.retryOptions || {}; - - const userAgentString: string = CryptographyClient.getUserAgentString( - pipelineOptions.telemetry - ); - - let requestPolicyFactories: RequestPolicyFactory[] = []; - if (isNode) { - requestPolicyFactories.push( - proxyPolicy(getDefaultProxySettings((pipelineOptions.proxyOptions || {}).proxySettings)) - ); - } - requestPolicyFactories = requestPolicyFactories.concat([ - userAgentPolicy({ value: userAgentString }), - generateClientRequestIdPolicy(), - deserializationPolicy(), // Default deserializationPolicy is provided by protocol layer - throttlingRetryPolicy(), - systemErrorRetryPolicy(), - exponentialRetryPolicy( - retryOptions.retryCount, - retryOptions.retryIntervalInMS, - RetryConstants.MIN_RETRY_INTERVAL_MS, // Minimum retry interval to prevent frequent retries - retryOptions.maxRetryDelayInMs - ), - redirectPolicy(), - isTokenCredential(credential) - ? challengeBasedAuthenticationPolicy(credential) - : signingPolicy(credential), - logPolicy( - logger.info, { - allowedHeaderNames: [ - "x-ms-keyvault-region", - "x-ms-keyvault-network-info", - "x-ms-keyvault-service-version" - ] - }) - ]); - - return { - httpClient: pipelineOptions.HTTPClient, - httpPipelineLogger: pipelineOptions.logger, - requestPolicyFactories - }; - } - - private static getUserAgentString(telemetry?: TelemetryOptions): string { - const userAgentInfo: string[] = []; - if (telemetry) { - if (userAgentInfo.indexOf(telemetry.value) === -1) { - userAgentInfo.push(telemetry.value); - } - } - const libInfo = `azsdk-js-keyvault-keys/${SDK_VERSION}`; - if (userAgentInfo.indexOf(libInfo) === -1) { - userAgentInfo.push(libInfo); - } - const defaultUserAgentInfo = getDefaultUserAgentValue(); - if (userAgentInfo.indexOf(defaultUserAgentInfo) === -1) { - userAgentInfo.push(defaultUserAgentInfo); - } - return userAgentInfo.join(" "); - } - private async fetchFullKeyIfPossible() { if (!this.hasTriedToGetKey) { try { @@ -656,24 +567,54 @@ export class CryptographyClient { * @param url The url of the key vault service * @param key The key to use during cryptography tasks * @param credential The login credentials of the service - * @param {(Pipeline | NewPipelineOptions)} [pipelineOrOptions={}] Optional. A Pipeline, or options to create a default Pipeline instance. - * Omitting this parameter to create the default Pipeline instance. + * @param {PipelineOptions} [pipelineOptions={}] Optional. Pipeline options used to configure Key Vault API requests. + * Omit this parameter to use the default pipeline configuration. * @memberof CryptographyClient */ constructor( url: string, key: string | JsonWebKey, // keyUrl or JsonWebKey credential: TokenCredential, - pipelineOrOptions: Pipeline | NewPipelineOptions = {} + pipelineOptions: PipelineOptions = {} ) { this.vaultBaseUrl = url; this.credential = credential; - if (isNewPipelineOptions(pipelineOrOptions)) { - this.pipeline = CryptographyClient.getDefaultPipeline(credential, pipelineOrOptions); + + const libInfo = `azsdk-js-keyvault-keys/${SDK_VERSION}`; + if (pipelineOptions.userAgentOptions) { + pipelineOptions.userAgentOptions.userAgentPrefix !== undefined + ? `${pipelineOptions.userAgentOptions.userAgentPrefix} ${libInfo}` + : libInfo; } else { - this.pipeline = pipelineOrOptions; + pipelineOptions.userAgentOptions = { + userAgentPrefix: libInfo + } + } + + const authPolicy = + isTokenCredential(credential) + ? challengeBasedAuthenticationPolicy(credential) + : signingPolicy(credential) + + const internalPipelineOptions = { + ...pipelineOptions, + ...{ + loggingOptions: { + logger: logger.info, + logPolicyOptions: { + allowedHeaderNames: [ + "x-ms-keyvault-region", + "x-ms-keyvault-network-info", + "x-ms-keyvault-service-version" + ] + } + } + } } + + this.pipeline = createPipelineFromOptions(internalPipelineOptions, authPolicy); this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); + this.key = key; let parsed; diff --git a/sdk/keyvault/keyvault-keys/src/index.ts b/sdk/keyvault/keyvault-keys/src/index.ts index 685ec9213d7a..14810f7c758d 100644 --- a/sdk/keyvault/keyvault-keys/src/index.ts +++ b/sdk/keyvault/keyvault-keys/src/index.ts @@ -3,24 +3,13 @@ /* eslint @typescript-eslint/member-ordering: 0 */ import { - getDefaultUserAgentValue, TokenCredential, - isTokenCredential, - RequestPolicyFactory, - deserializationPolicy, - signingPolicy, - exponentialRetryPolicy, - redirectPolicy, - systemErrorRetryPolicy, - generateClientRequestIdPolicy, - proxyPolicy, - throttlingRetryPolicy, - getDefaultProxySettings, - isNode, - userAgentPolicy, RequestOptionsBase, - tracingPolicy, - logPolicy + PipelineOptions, + createPipelineFromOptions, + ServiceClientOptions as Pipeline, + isTokenCredential, + signingPolicy } from "@azure/core-http"; import { getTracer, Span } from "@azure/core-tracing"; @@ -52,7 +41,7 @@ import { RestoreKeyResponse } from "./core/models"; import { KeyVaultClient } from "./core/keyVaultClient"; -import { RetryConstants, SDK_VERSION } from "./core/utils/constants"; +import { SDK_VERSION } from "./core/utils/constants"; import { challengeBasedAuthenticationPolicy } from "./core/challengeBasedAuthenticationPolicy"; import { DeleteKeyPoller } from "./lro/delete/poller"; @@ -61,9 +50,6 @@ import { DeleteKeyPollOperationState } from "./lro/delete/operation"; import { RecoverDeletedKeyPollOperationState } from "./lro/recover/operation"; import { - NewPipelineOptions, - isNewPipelineOptions, - Pipeline, ParsedKeyVaultEntityIdentifier } from "./core/keyVaultBase"; import { @@ -123,7 +109,7 @@ export { PollerLike, PollOperationState, KeyWrapAlgorithm, - NewPipelineOptions, + PipelineOptions, PageSettings, PagedAsyncIterableIterator, ParsedKeyVaultEntityIdentifier, @@ -146,65 +132,6 @@ const SERVICE_API_VERSION = "7.0"; * The client to interact with the KeyVault keys functionality */ export class KeyClient { - /** - * A static method used to create a new Pipeline object with the provided Credential. - * - * @static - * @param {TokenCredential} The credential to use for API requests. - * @param {NewPipelineOptions} [pipelineOptions] Optional. Options. - * @memberof KeyClient - */ - public static getDefaultPipeline( - credential: TokenCredential, - pipelineOptions: NewPipelineOptions = {} - ): Pipeline { - // Order is important. Closer to the API at the top & closer to the network at the bottom. - // The credential's policy factory must appear close to the wire so it can sign any - // changes made by other factories (like UniqueRequestIDPolicyFactory) - const retryOptions = pipelineOptions.retryOptions || {}; - - const userAgentString: string = KeyClient.getUserAgentString(pipelineOptions.telemetry); - - let requestPolicyFactories: RequestPolicyFactory[] = []; - if (isNode) { - requestPolicyFactories.push( - proxyPolicy(getDefaultProxySettings((pipelineOptions.proxyOptions || {}).proxySettings)) - ); - } - requestPolicyFactories = requestPolicyFactories.concat([ - tracingPolicy(), - userAgentPolicy({ value: userAgentString }), - generateClientRequestIdPolicy(), - deserializationPolicy(), // Default deserializationPolicy is provided by protocol layer - throttlingRetryPolicy(), - systemErrorRetryPolicy(), - exponentialRetryPolicy( - retryOptions.retryCount, - retryOptions.retryIntervalInMS, - RetryConstants.MIN_RETRY_INTERVAL_MS, // Minimum retry interval to prevent frequent retries - retryOptions.maxRetryDelayInMs - ), - redirectPolicy(), - isTokenCredential(credential) - ? challengeBasedAuthenticationPolicy(credential) - : signingPolicy(credential), - logPolicy( - logger.info, { - allowedHeaderNames: [ - "x-ms-keyvault-region", - "x-ms-keyvault-network-info", - "x-ms-keyvault-service-version" - ] - }) - ]); - - return { - httpClient: pipelineOptions.HTTPClient, - httpPipelineLogger: pipelineOptions.logger, - requestPolicyFactories - }; - } - /** * The base URL to the vault */ @@ -246,25 +173,51 @@ export class KeyClient { * ``` * @param {string} endPoint the base url to the key vault. * @param {TokenCredential} The credential to use for API requests. - * @param {(Pipeline | NewPipelineOptions)} [pipelineOrOptions={}] Optional. A Pipeline, or options to create a default Pipeline instance. - * Omitting this parameter to create the default Pipeline instance. + * @param {PipelineOptions} [pipelineOptions={}] Optional. Pipeline options used to configure Key Vault API requests. + * Omit this parameter to use the default pipeline configuration. * @memberof KeyClient */ constructor( endPoint: string, credential: TokenCredential, - pipelineOrOptions: Pipeline | NewPipelineOptions = {} + pipelineOptions: PipelineOptions = {} ) { this.vaultEndpoint = endPoint; this.credential = credential; - if (isNewPipelineOptions(pipelineOrOptions)) { - this.pipeline = KeyClient.getDefaultPipeline(credential, pipelineOrOptions); + + const libInfo = `azsdk-js-keyvault-keys/${SDK_VERSION}`; + if (pipelineOptions.userAgentOptions) { + pipelineOptions.userAgentOptions.userAgentPrefix !== undefined + ? `${pipelineOptions.userAgentOptions.userAgentPrefix} ${libInfo}` + : libInfo; } else { - this.pipeline = pipelineOrOptions; + pipelineOptions.userAgentOptions = { + userAgentPrefix: libInfo + } } - this.pipeline.requestPolicyFactories; + const authPolicy = + isTokenCredential(credential) + ? challengeBasedAuthenticationPolicy(credential) + : signingPolicy(credential) + + const internalPipelineOptions = { + ...pipelineOptions, + ...{ + loggingOptions: { + logger: logger.info, + logPolicyOptions: { + allowedHeaderNames: [ + "x-ms-keyvault-region", + "x-ms-keyvault-network-info", + "x-ms-keyvault-service-version" + ] + } + } + } + } + this.pipeline = createPipelineFromOptions(internalPipelineOptions, authPolicy); this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); } @@ -304,24 +257,6 @@ export class KeyClient { return this.getKeyFromKeyBundle(response); } - private static getUserAgentString(telemetry?: TelemetryOptions): string { - const userAgentInfo: string[] = []; - if (telemetry) { - if (userAgentInfo.indexOf(telemetry.value) === -1) { - userAgentInfo.push(telemetry.value); - } - } - const libInfo = `azsdk-js-keyvault-keys/${SDK_VERSION}`; - if (userAgentInfo.indexOf(libInfo) === -1) { - userAgentInfo.push(libInfo); - } - const defaultUserAgentInfo = getDefaultUserAgentValue(); - if (userAgentInfo.indexOf(defaultUserAgentInfo) === -1) { - userAgentInfo.push(defaultUserAgentInfo); - } - return userAgentInfo.join(" "); - } - // TODO: do we want Aborter as well? /** diff --git a/sdk/keyvault/keyvault-secrets/review/keyvault-secrets.api.md b/sdk/keyvault/keyvault-secrets/review/keyvault-secrets.api.md index d781d595313c..1abc3671bb76 100644 --- a/sdk/keyvault/keyvault-secrets/review/keyvault-secrets.api.md +++ b/sdk/keyvault/keyvault-secrets/review/keyvault-secrets.api.md @@ -5,10 +5,9 @@ ```ts import * as coreHttp from '@azure/core-http'; -import { HttpClient } from '@azure/core-http'; -import { HttpPipelineLogger } from '@azure/core-http'; import { PagedAsyncIterableIterator } from '@azure/core-paging'; import { PageSettings } from '@azure/core-paging'; +import { PipelineOptions } from '@azure/core-http'; import { PollerLike } from '@azure/core-lro'; import { PollOperationState } from '@azure/core-lro'; import { RequestOptionsBase } from '@azure/core-http'; @@ -49,19 +48,6 @@ export interface GetSecretOptions { // @public export const logger: import("@azure/logger").AzureLogger; -// @public -export interface NewPipelineOptions { - // (undocumented) - HTTPClient?: HttpClient; - // (undocumented) - logger?: HttpPipelineLogger; - // (undocumented) - proxyOptions?: ProxyOptions; - // (undocumented) - retryOptions?: RetryOptions; - telemetry?: TelemetryOptions; -} - export { PagedAsyncIterableIterator } export { PageSettings } @@ -73,6 +59,8 @@ export interface ParsedKeyVaultEntityIdentifier { version?: string; } +export { PipelineOptions } + export { PollerLike } export { PollOperationState } @@ -113,12 +101,11 @@ export interface Secret { // @public export class SecretClient { - constructor(endPoint: string, credential: TokenCredential, pipelineOrOptions?: ServiceClientOptions | NewPipelineOptions); + constructor(endPoint: string, credential: TokenCredential, pipelineOptions?: PipelineOptions); backupSecret(secretName: string, options?: RequestOptionsBase): Promise; beginDeleteSecret(name: string, options?: SecretPollerOptions): Promise, DeletedSecret>>; beginRecoverDeletedSecret(name: string, options?: SecretPollerOptions): Promise, SecretProperties>>; protected readonly credential: TokenCredential; - static getDefaultPipeline(credential: TokenCredential, pipelineOptions?: NewPipelineOptions): ServiceClientOptions; getDeletedSecret(secretName: string, options?: RequestOptionsBase): Promise; getSecret(secretName: string, options?: GetSecretOptions): Promise; listDeletedSecrets(options?: RequestOptions): PagedAsyncIterableIterator; diff --git a/sdk/keyvault/keyvault-secrets/src/core/keyVaultBase.ts b/sdk/keyvault/keyvault-secrets/src/core/keyVaultBase.ts index e223dc00421e..eab9dd1d8a0f 100644 --- a/sdk/keyvault/keyvault-secrets/src/core/keyVaultBase.ts +++ b/sdk/keyvault/keyvault-secrets/src/core/keyVaultBase.ts @@ -1,12 +1,5 @@ -import { ServiceClientOptions as Pipeline } from "@azure/core-http"; -import { - HttpClient as IHttpClient, - HttpPipelineLogger as IHttpPipelineLogger -} from "@azure/core-http"; - -import { RetryOptions, ProxyOptions, TelemetryOptions } from "."; - -export { Pipeline }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. export interface ParsedKeyVaultEntityIdentifier { /** @@ -23,42 +16,3 @@ export interface ParsedKeyVaultEntityIdentifier { name: string; } -/** - * Option interface for Pipeline.newPipeline method. - * - * Properties of this interface should not overlap with properties of {@link Pipeline} - * as we use them to differentiate instances of NewPipelineOptions from instances of Pipeline. - * If this interface is modified, the method isNewPipelineOptions() should also be updated - * to adapt the changes. - * - * @export - * @interface NewPipelineOptions - */ -export interface NewPipelineOptions { - /** - * Telemetry configures the built-in telemetry policy behavior. - * - * @type {TelemetryOptions} - * @memberof NewPipelineOptions - */ - telemetry?: TelemetryOptions; - retryOptions?: RetryOptions; - proxyOptions?: ProxyOptions; - - logger?: IHttpPipelineLogger; - HTTPClient?: IHttpClient; -} - -export function isNewPipelineOptions( - pipelineOrOptions: Pipeline | NewPipelineOptions -): pipelineOrOptions is NewPipelineOptions { - // An empty object is consider options - function isEmptyObject(obj: Pipeline | NewPipelineOptions) { - return Object.keys(obj).length === 0 && obj.constructor === Object; - } - const options = pipelineOrOptions as NewPipelineOptions; - return ( - isEmptyObject(pipelineOrOptions) || - !!(options.retryOptions || options.proxyOptions || options.logger || options.HTTPClient) - ); -} diff --git a/sdk/keyvault/keyvault-secrets/src/index.ts b/sdk/keyvault/keyvault-secrets/src/index.ts index 77e6fa6a09db..13f2e7c5013b 100644 --- a/sdk/keyvault/keyvault-secrets/src/index.ts +++ b/sdk/keyvault/keyvault-secrets/src/index.ts @@ -3,24 +3,13 @@ /* eslint @typescript-eslint/member-ordering: 0 */ import { - getDefaultUserAgentValue, TokenCredential, isTokenCredential, - RequestPolicyFactory, - deserializationPolicy, signingPolicy, RequestOptionsBase, - exponentialRetryPolicy, - redirectPolicy, - systemErrorRetryPolicy, - generateClientRequestIdPolicy, - proxyPolicy, - throttlingRetryPolicy, - getDefaultProxySettings, - isNode, - userAgentPolicy, - tracingPolicy, - logPolicy + PipelineOptions, + createPipelineFromOptions, + ServiceClientOptions as Pipeline } from "@azure/core-http"; import { getTracer, Span } from "@azure/core-tracing"; @@ -43,7 +32,7 @@ import { RestoreSecretResponse } from "./core/models"; import { KeyVaultClient } from "./core/keyVaultClient"; -import { RetryConstants, SDK_VERSION } from "./core/utils/constants"; +import { SDK_VERSION } from "./core/utils/constants"; import { challengeBasedAuthenticationPolicy } from "./core/challengeBasedAuthenticationPolicy"; import { DeleteSecretPoller } from "./lro/delete/poller"; @@ -63,7 +52,6 @@ import { SecretProperties } from "./secretsModels"; import { parseKeyvaultIdentifier as parseKeyvaultEntityIdentifier } from "./core/utils"; -import { NewPipelineOptions, isNewPipelineOptions, Pipeline } from "./core/keyVaultBase"; import { ProxyOptions, RetryOptions, @@ -78,7 +66,7 @@ export { DeletionRecoveryLevel, GetSecretOptions, RequestOptions, - NewPipelineOptions, + PipelineOptions, PagedAsyncIterableIterator, PageSettings, ParsedKeyVaultEntityIdentifier, @@ -102,64 +90,6 @@ const SERVICE_API_VERSION = "7.0"; * The client to interact with the KeyVault secrets functionality */ export class SecretClient { - /** - * A static method used to create a new Pipeline object with the provided Credential. - * @static - * @param {TokenCredential} The credential to use for API requests. - * @param {NewPipelineOptions} [pipelineOptions] Optional. Options. - * @memberof SecretClient - */ - public static getDefaultPipeline( - credential: TokenCredential, - pipelineOptions: NewPipelineOptions = {} - ): Pipeline { - // Order is important. Closer to the API at the top & closer to the network at the bottom. - // The credential's policy factory must appear close to the wire so it can sign any - // changes made by other factories (like UniqueRequestIDPolicyFactory) - const retryOptions = pipelineOptions.retryOptions || {}; - - const userAgentString: string = SecretClient.getUserAgentString(pipelineOptions.telemetry); - - let requestPolicyFactories: RequestPolicyFactory[] = []; - if (isNode) { - requestPolicyFactories.push( - proxyPolicy(getDefaultProxySettings((pipelineOptions.proxyOptions || {}).proxySettings)) - ); - } - requestPolicyFactories = requestPolicyFactories.concat([ - tracingPolicy(), - userAgentPolicy({ value: userAgentString }), - generateClientRequestIdPolicy(), - deserializationPolicy(), // Default deserializationPolicy is provided by protocol layer - throttlingRetryPolicy(), - systemErrorRetryPolicy(), - exponentialRetryPolicy( - retryOptions.retryCount, - retryOptions.retryIntervalInMS, - RetryConstants.MIN_RETRY_INTERVAL_MS, // Minimum retry interval to prevent frequent retries - retryOptions.maxRetryDelayInMs - ), - redirectPolicy(), - isTokenCredential(credential) - ? challengeBasedAuthenticationPolicy(credential) - : signingPolicy(credential), - logPolicy( - logger.info, { - allowedHeaderNames: [ - "x-ms-keyvault-region", - "x-ms-keyvault-network-info", - "x-ms-keyvault-service-version" - ] - }) - ]); - - return { - httpClient: pipelineOptions.HTTPClient, - httpPipelineLogger: pipelineOptions.logger, - requestPolicyFactories - }; - } - /** * The base URL to the vault */ @@ -201,42 +131,52 @@ export class SecretClient { * ``` * @param {string} endPoint the base url to the key vault. * @param {TokenCredential} The credential to use for API requests. - * @param {(Pipeline | NewPipelineOptions)} [pipelineOrOptions={}] Optional. A Pipeline, or options to create a default Pipeline instance. - * Omitting this parameter to create the default Pipeline instance. + * @param {PipelineOptions} [pipelineOptions={}] Optional. Pipeline options used to configure Key Vault API requests. + * Omit this parameter to use the default pipeline configuration. * @memberof SecretClient */ constructor( endPoint: string, credential: TokenCredential, - pipelineOrOptions: Pipeline | NewPipelineOptions = {} + pipelineOptions: PipelineOptions = {} ) { this.vaultEndpoint = endPoint; this.credential = credential; - if (isNewPipelineOptions(pipelineOrOptions)) { - this.pipeline = SecretClient.getDefaultPipeline(credential, pipelineOrOptions); + + const libInfo = `azsdk-js-keyvault-secrets/${SDK_VERSION}`; + if (pipelineOptions.userAgentOptions) { + pipelineOptions.userAgentOptions.userAgentPrefix !== undefined + ? `${pipelineOptions.userAgentOptions.userAgentPrefix} ${libInfo}` + : libInfo; } else { - this.pipeline = pipelineOrOptions; + pipelineOptions.userAgentOptions = { + userAgentPrefix: libInfo + } } - this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); - } - - private static getUserAgentString(telemetry?: TelemetryOptions): string { - const userAgentInfo: string[] = []; - if (telemetry) { - if (userAgentInfo.indexOf(telemetry.value) === -1) { - userAgentInfo.push(telemetry.value); + const authPolicy = + isTokenCredential(credential) + ? challengeBasedAuthenticationPolicy(credential) + : signingPolicy(credential) + + const internalPipelineOptions = { + ...pipelineOptions, + ...{ + loggingOptions: { + logger: logger.info, + logPolicyOptions: { + allowedHeaderNames: [ + "x-ms-keyvault-region", + "x-ms-keyvault-network-info", + "x-ms-keyvault-service-version" + ] + } + } } } - const libInfo = `azsdk-js-keyvault-secrets/${SDK_VERSION}`; - if (userAgentInfo.indexOf(libInfo) === -1) { - userAgentInfo.push(libInfo); - } - const defaultUserAgentInfo = getDefaultUserAgentValue(); - if (userAgentInfo.indexOf(defaultUserAgentInfo) === -1) { - userAgentInfo.push(defaultUserAgentInfo); - } - return userAgentInfo.join(" "); + + this.pipeline = createPipelineFromOptions(internalPipelineOptions, authPolicy); + this.client = new KeyVaultClient(credential, SERVICE_API_VERSION, this.pipeline); } /**