From e45986892b65a4786beb79f779de00af895d70a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Filho?= Date: Thu, 15 Jan 2026 10:40:14 -0300 Subject: [PATCH] feat: add KV configuration support - Add AzionKV type definition with name property (6-63 characters) - Implement KvProcessConfigStrategy for config/manifest transformation - Add KV schema validation in azionConfigSchema and schemaManifest - Add KV configuration example in azion.config.example.ts - Update README with AzionKV documentation and usage examples - Add comprehensive unit tests for KvProcessConfigStrategy - Register KV strategy in processConfigContext factory --- packages/config/README.md | 14 ++ .../helpers/azion.config.example.ts | 39 +++-- .../src/configProcessor/helpers/schema.ts | 26 +++- .../configProcessor/helpers/schemaManifest.ts | 110 +++++++------ .../kvProcessConfigStrategy.test.ts | 145 ++++++++++++++++++ .../kvProcessConfigStrategy.ts | 35 +++++ .../configProcessor/processStrategy/index.ts | 2 + packages/config/src/types.ts | 10 ++ 8 files changed, 320 insertions(+), 61 deletions(-) create mode 100644 packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.test.ts create mode 100644 packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.ts diff --git a/packages/config/README.md b/packages/config/README.md index 8947b6b6..94d15357 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -28,6 +28,7 @@ This module provides comprehensive configuration and validation for the Azion Pl - [`AzionFirewall`](#azionfirewall) - [`AzionWaf`](#azionwaf) - [`AzionCustomPages`](#azioncustompages) + - [`AzionKV`](#azionkv) ## Installation @@ -359,6 +360,11 @@ const config = defineConfig({ layer: 'cache', }, ], + kv: [ + { + name: 'my-kv', + }, + ], }); ``` @@ -898,3 +904,11 @@ Type definition for individual custom pages. - `ttl?: number` - Time to live (0-31536000 seconds, default: 0). - `uri?: string | null` - URI path (must start with /, max 250 characters). - `customStatusCode?: number | null` - Custom status code (100-599). + +### `AzionKV` + +Type definition for the Key-Value (KV) storage configuration. + +**Properties:** + +- `name: string` - Name of the KV storage (1-255 characters). diff --git a/packages/config/src/configProcessor/helpers/azion.config.example.ts b/packages/config/src/configProcessor/helpers/azion.config.example.ts index 5cd9fd39..77b2d9a3 100644 --- a/packages/config/src/configProcessor/helpers/azion.config.example.ts +++ b/packages/config/src/configProcessor/helpers/azion.config.example.ts @@ -1,21 +1,21 @@ import type { - AzionConfig, - AzionConnector, - CacheByCookie, - CacheByQueryString, - ConnectorDnsResolution, - ConnectorHttpVersionPolicy, - ConnectorTransportPolicy, - ConnectorType, - CustomPageErrorCode, - CustomPageType, - NetworkListType, - WafSensitivity, - WafThreatType, - WorkloadInfrastructure, - WorkloadMTLSVerification, - WorkloadTLSCipher, - WorkloadTLSVersion, + AzionConfig, + AzionConnector, + CacheByCookie, + CacheByQueryString, + ConnectorDnsResolution, + ConnectorHttpVersionPolicy, + ConnectorTransportPolicy, + ConnectorType, + CustomPageErrorCode, + CustomPageType, + NetworkListType, + WafSensitivity, + WafThreatType, + WorkloadInfrastructure, + WorkloadMTLSVerification, + WorkloadTLSCipher, + WorkloadTLSVersion, } from 'azion/config'; const config: AzionConfig = { @@ -612,6 +612,11 @@ const config: AzionConfig = { ], }, ], + kv: [ + { + name: 'my-kv', + }, + ], }; export default config; diff --git a/packages/config/src/configProcessor/helpers/schema.ts b/packages/config/src/configProcessor/helpers/schema.ts index d1510ee6..9a2bb197 100644 --- a/packages/config/src/configProcessor/helpers/schema.ts +++ b/packages/config/src/configProcessor/helpers/schema.ts @@ -465,6 +465,25 @@ const schemaStorage = { }, }; +const schemaKV = { + type: 'object', + properties: { + name: { + type: 'string', + minLength: 6, + maxLength: 63, + pattern: '^.{6,63}$', + errorMessage: "The 'name' field must be a string between 6 and 63 characters.", + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in kv items.', + required: "The 'name' field is required.", + }, +}; + const azionConfigSchema = { $id: 'azionConfig', definitions: { @@ -1924,7 +1943,12 @@ const azionConfigSchema = { storage: { type: 'array', items: schemaStorage, - errorMessage: "The 'storage' field must be an array of storage items.", + errorMessage: "The 'storage' field must be an array of storage items.", + }, + kv: { + type: 'array', + items: schemaKV, + errorMessage: "The 'kv' field must be an array of kv items.", }, }, additionalProperties: false, diff --git a/packages/config/src/configProcessor/helpers/schemaManifest.ts b/packages/config/src/configProcessor/helpers/schemaManifest.ts index 25d32a62..b0fb9b72 100644 --- a/packages/config/src/configProcessor/helpers/schemaManifest.ts +++ b/packages/config/src/configProcessor/helpers/schemaManifest.ts @@ -1,44 +1,44 @@ import { - APPLICATION_DELIVERY_PROTOCOLS, - APPLICATION_HTTP_PORTS, - APPLICATION_HTTPS_PORTS, - APPLICATION_SUPPORTED_CIPHERS, - APPLICATION_TLS_VERSIONS, - CACHE_ADAPTIVE_DELIVERY, - CACHE_BROWSER_SETTINGS, - CACHE_BY_COOKIE, - CACHE_BY_QUERY_STRING, - CACHE_CDN_SETTINGS, - CACHE_VARY_BY_METHOD, - COOKIE_BEHAVIORS, - CUSTOM_PAGE_ERROR_CODES, - EDGE_CONNECTOR_DNS_RESOLUTION, - EDGE_CONNECTOR_HMAC_TYPE, - EDGE_CONNECTOR_HTTP_VERSION_POLICY, - EDGE_CONNECTOR_LOAD_BALANCE_METHOD, - EDGE_CONNECTOR_TRANSPORT_POLICY, - EDGE_CONNECTOR_TYPES, - FIREWALL_BEHAVIOR_NAMES, - FIREWALL_RATE_LIMIT_BY, - FIREWALL_RATE_LIMIT_TYPES, - FIREWALL_RULE_CONDITIONALS, - FIREWALL_RULE_OPERATORS, - FIREWALL_VARIABLES, - FIREWALL_WAF_MODES, - HEADER_BEHAVIORS, - ID_BEHAVIORS, - NETWORK_LIST_TYPES, - NO_ARGS_BEHAVIORS, - RULE_CONDITIONALS, - RULE_OPERATORS_WITH_VALUE, - RULE_OPERATORS_WITHOUT_VALUE, - RULE_VARIABLES, - STRING_BEHAVIORS, - TIERED_CACHE_TOPOLOGY, - WAF_ENGINE_VERSIONS, - WORKLOAD_HTTP_VERSIONS, - WORKLOAD_MTLS_VERIFICATION, - WORKLOAD_TLS_VERSIONS, + APPLICATION_DELIVERY_PROTOCOLS, + APPLICATION_HTTP_PORTS, + APPLICATION_HTTPS_PORTS, + APPLICATION_SUPPORTED_CIPHERS, + APPLICATION_TLS_VERSIONS, + CACHE_ADAPTIVE_DELIVERY, + CACHE_BROWSER_SETTINGS, + CACHE_BY_COOKIE, + CACHE_BY_QUERY_STRING, + CACHE_CDN_SETTINGS, + CACHE_VARY_BY_METHOD, + COOKIE_BEHAVIORS, + CUSTOM_PAGE_ERROR_CODES, + EDGE_CONNECTOR_DNS_RESOLUTION, + EDGE_CONNECTOR_HMAC_TYPE, + EDGE_CONNECTOR_HTTP_VERSION_POLICY, + EDGE_CONNECTOR_LOAD_BALANCE_METHOD, + EDGE_CONNECTOR_TRANSPORT_POLICY, + EDGE_CONNECTOR_TYPES, + FIREWALL_BEHAVIOR_NAMES, + FIREWALL_RATE_LIMIT_BY, + FIREWALL_RATE_LIMIT_TYPES, + FIREWALL_RULE_CONDITIONALS, + FIREWALL_RULE_OPERATORS, + FIREWALL_VARIABLES, + FIREWALL_WAF_MODES, + HEADER_BEHAVIORS, + ID_BEHAVIORS, + NETWORK_LIST_TYPES, + NO_ARGS_BEHAVIORS, + RULE_CONDITIONALS, + RULE_OPERATORS_WITH_VALUE, + RULE_OPERATORS_WITHOUT_VALUE, + RULE_VARIABLES, + STRING_BEHAVIORS, + TIERED_CACHE_TOPOLOGY, + WAF_ENGINE_VERSIONS, + WORKLOAD_HTTP_VERSIONS, + WORKLOAD_MTLS_VERIFICATION, + WORKLOAD_TLS_VERSIONS, } from '../../constants'; const schemaNetworkListManifest = { @@ -257,6 +257,25 @@ const schemaStorageManifest = { }, }; +const schemaKvManifest = { + type: 'object', + properties: { + name: { + type: 'string', + minLength: 6, + maxLength: 63, + pattern: '^.{6,63}$', + errorMessage: "The 'name' field must be a string between 6 and 63 characters.", + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in kv items.', + required: "The 'name' field is required.", + }, +}; + const schemaFirewallRuleBehaviorArguments = { set_rate_limit: { type: 'object', @@ -642,13 +661,13 @@ const schemaApplicationCacheSettings = { }, required: ['enabled'], if: { - properties: { enabled: { const: true } } + properties: { enabled: { const: true } }, }, then: { required: ['enabled', 'topology'], errorMessage: { - required: "When 'enabled' is true, 'topology' is required in the 'tiered_cache' object." - } + required: "When 'enabled' is true, 'topology' is required in the 'tiered_cache' object.", + }, }, additionalProperties: false, }, @@ -1945,6 +1964,11 @@ const schemaManifest = { items: schemaStorageManifest, errorMessage: "The 'storage' field must be an array of storage items.", }, + kv: { + type: 'array', + items: schemaKvManifest, + errorMessage: "The 'kv' field must be an array of kv items.", + }, }, required: ['build', 'applications', 'workloads', 'workload_deployments'], errorMessage: { diff --git a/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.test.ts new file mode 100644 index 00000000..bfd75812 --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.test.ts @@ -0,0 +1,145 @@ +import { AzionConfig } from '../../../types'; +import KvProcessConfigStrategy from './kvProcessConfigStrategy'; + +describe('KvProcessConfigStrategy', () => { + let strategy: KvProcessConfigStrategy; + + beforeEach(() => { + strategy = new KvProcessConfigStrategy(); + }); + + describe('transformToManifest', () => { + it('should return undefined when no kv is provided', () => { + const config: AzionConfig = {}; + const result = strategy.transformToManifest(config); + expect(result).toBeUndefined(); + }); + + it('should return undefined when kv array is empty', () => { + const config: AzionConfig = { kv: [] }; + const result = strategy.transformToManifest(config); + expect(result).toBeUndefined(); + }); + + it('should transform a basic kv configuration to manifest format', () => { + const config: AzionConfig = { + kv: [ + { + name: 'my-kv', + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result).toEqual([ + { + name: 'my-kv', + }, + ]); + }); + + it('should transform multiple kv configurations to manifest format', () => { + const config: AzionConfig = { + kv: [ + { + name: 'kv-1', + }, + { + name: 'kv-2', + }, + ], + }; + + const result = strategy.transformToManifest(config); + + expect(result).toHaveLength(2); + expect(result![0].name).toBe('kv-1'); + expect(result![1].name).toBe('kv-2'); + }); + }); + + describe('transformToConfig', () => { + it('should return undefined when no kv is provided', () => { + const payload = {}; + const transformedPayload: AzionConfig = {}; + const result = strategy.transformToConfig(payload, transformedPayload); + expect(result).toBeUndefined(); + }); + + it('should return undefined when kv array is empty', () => { + const payload = { kv: [] }; + const transformedPayload: AzionConfig = {}; + const result = strategy.transformToConfig(payload, transformedPayload); + expect(result).toBeUndefined(); + }); + + it('should transform a basic kv manifest to config format', () => { + const payload = { + kv: [ + { + name: 'my-kv', + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result).toEqual([ + { + name: 'my-kv', + }, + ]); + expect(transformedPayload.kv).toEqual([ + { + name: 'my-kv', + }, + ]); + }); + + it('should transform multiple kv manifests to config format', () => { + const payload = { + kv: [ + { + name: 'kv-1', + }, + { + name: 'kv-2', + }, + ], + }; + const transformedPayload: AzionConfig = {}; + + const result = strategy.transformToConfig(payload, transformedPayload); + + expect(result).toHaveLength(2); + expect(result![0].name).toBe('kv-1'); + expect(result![1].name).toBe('kv-2'); + expect(transformedPayload.kv).toEqual(result); + }); + + it('should handle existing kv in transformedPayload', () => { + const payload = { + kv: [ + { + name: 'new-kv', + }, + ], + }; + const transformedPayload: AzionConfig = { + kv: [ + { + name: 'existing-kv', + }, + ], + }; + + strategy.transformToConfig(payload, transformedPayload); + + // The transformToConfig method replaces the existing kv array + expect(transformedPayload.kv).toHaveLength(1); + expect(transformedPayload.kv![0].name).toBe('new-kv'); + }); + }); +}); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.ts new file mode 100644 index 00000000..dcbb2c7f --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/kvProcessConfigStrategy.ts @@ -0,0 +1,35 @@ +import { AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * KvProcessConfigStrategy + * @class KvProcessConfigStrategy + * @description This class is implementation of the KV ProcessConfig Strategy. + */ +class KvProcessConfigStrategy extends ProcessConfigStrategy { + transformToManifest(config: AzionConfig) { + if (!Array.isArray(config?.kv) || config?.kv.length === 0) { + return; + } + + return config.kv.map((item) => ({ + name: item.name, + })); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformToConfig(payload: any, transformedPayload: AzionConfig) { + const kvConfig = payload.kv; + if (!Array.isArray(kvConfig) || kvConfig.length === 0) { + return; + } + + transformedPayload.kv = kvConfig.map((item) => ({ + name: item.name, + })); + + return transformedPayload.kv; + } +} + +export default KvProcessConfigStrategy; diff --git a/packages/config/src/configProcessor/processStrategy/index.ts b/packages/config/src/configProcessor/processStrategy/index.ts index 7dcc100e..1dfeea2c 100644 --- a/packages/config/src/configProcessor/processStrategy/index.ts +++ b/packages/config/src/configProcessor/processStrategy/index.ts @@ -3,6 +3,7 @@ import BuildProcessConfigStrategy from './implementations/buildProcessConfigStra import ConnectorProcessConfigStrategy from './implementations/connectorProcessConfigStrategy'; import CustomPagesProcessConfigStrategy from './implementations/customPagesProcessConfigStrategy'; import FunctionsProcessConfigStrategy from './implementations/functionsProcessConfigStrategy'; +import KvProcessConfigStrategy from './implementations/kvProcessConfigStrategy'; import PurgeProcessConfigStrategy from './implementations/purgeProcessConfigStrategy'; import FirewallProcessConfigStrategy from './implementations/secure/firewallProcessConfigStrategy'; import NetworkListProcessConfigStrategy from './implementations/secure/networkListProcessConfigStrategy'; @@ -26,6 +27,7 @@ function factoryProcessContext() { processConfigContext.setStrategy('workloads', new WorkloadProcessConfigStrategy()); processConfigContext.setStrategy('workload_deployments', new WorkloadDeploymentsProcessConfigStrategy()); processConfigContext.setStrategy('custom_pages', new CustomPagesProcessConfigStrategy()); + processConfigContext.setStrategy('kv', new KvProcessConfigStrategy()); return processConfigContext; } diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 2103dce9..2ca840dc 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -1044,6 +1044,8 @@ export type AzionConfig = { workloads?: AzionWorkload[]; /** Custom pages configuration */ customPages?: AzionCustomPage[]; + /** KV configuration */ + kv?: AzionKV[]; }; // Rule Types - Separados corretamente baseado no behaviors.yml @@ -1225,3 +1227,11 @@ export type AzionManifestRule = { /** Rule data */ rule: AzionRule; }; + +/** + * KV + */ +export type AzionKV = { + /** KV name */ + name: string; +};