diff --git a/.gitignore b/.gitignore index 5c07fb1f5..9375f10cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store *.DS_Store .idea -*.idea \ No newline at end of file +*.idea +.vscode/* diff --git a/mule-gateway/modules/ROOT/pages/mule-gateway-org-credentials-mule4.adoc b/mule-gateway/modules/ROOT/pages/mule-gateway-org-credentials-mule4.adoc index 1e3df2edd..df5f0051f 100644 --- a/mule-gateway/modules/ROOT/pages/mule-gateway-org-credentials-mule4.adoc +++ b/mule-gateway/modules/ROOT/pages/mule-gateway-org-credentials-mule4.adoc @@ -26,6 +26,8 @@ Because you are required to provide credentials from time to time when using API To obtain environment credentials: . Log in to Anypoint Platform as an administrator, and click *Access Management*. +. In the Access Management navigation menu, click *Business Groups*. +. In the *Business Groups* menu, select your root organization. . Click the *Environments* tab and click the name of your environment within your desired organization. + image::environment-api-manager.png[align=center] diff --git a/pdk/1.2/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc b/pdk/1.2/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc index 704053a63..3274b5a63 100644 --- a/pdk/1.2/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc +++ b/pdk/1.2/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc @@ -1,4 +1,4 @@ -= Sharing Properties Across Requests += Sharing Data Between Policies ifndef::env-site,env-github[] include::_attributes.adoc[] endif::[] diff --git a/pdk/1.3/antora.yml b/pdk/1.3/antora.yml new file mode 100644 index 000000000..154b8cdd9 --- /dev/null +++ b/pdk/1.3/antora.yml @@ -0,0 +1,11 @@ +name: pdk +title: 'Flex Gateway Policy Development Kit (PDK)' +version: '1.3' +display_version: '1.3' +start_page: policies-pdk-overview.adoc +asciidoc: + attributes: + rust-ver-var: 'v1.74.0' + template-policies-url-ver-var: '1.3.0' +nav: +- modules/ROOT/nav.adoc diff --git a/pdk/1.3/modules/ROOT/nav.adoc b/pdk/1.3/modules/ROOT/nav.adoc new file mode 100644 index 000000000..1167675f5 --- /dev/null +++ b/pdk/1.3/modules/ROOT/nav.adoc @@ -0,0 +1,35 @@ +* xref:policies-pdk-overview.adoc[PDK Overview] +* xref:policies-pdk-release-notes.adoc[Release Notes] +* xref:policies-pdk-architecture.adoc[Architecture Overview] +* xref:policies-pdk-prerequisites.adoc[] +* xref:policies-pdk-upgrade-pdk.adoc[] +* xref:policies-pdk-configure-network-proxy.adoc[] +* xref:policies-pdk-develop-custom-policies.adoc[] +** xref:policies-pdk-create-project.adoc[] +** xref:policies-pdk-create-schema-definition.adoc[] +** xref:policies-pdk-configure-features.adoc[] +*** xref:policies-pdk-configure-features-logging.adoc[] +*** xref:policies-pdk-configure-features-headers.adoc[] +*** xref:policies-pdk-configure-features-inject-parameters.adoc[] +*** xref:policies-pdk-configure-features-http-request.adoc[] +*** xref:policies-pdk-configure-features-contracts.adoc[] +*** xref:policies-pdk-configure-features-cors.adoc[] +*** xref:policies-pdk-configure-features-jwt.adoc[] +*** xref:policies-pdk-configure-features-dataweave.adoc[] +*** xref:policies-pdk-configure-features-share-data.adoc[] +*** xref:policies-pdk-configure-features-libraries.adoc[] +*** xref:policies-pdk-configure-features-caching.adoc[] +*** xref:policies-pdk-configure-features-metadata.adoc[] +*** xref:policies-pdk-configure-features-streamproperties.adoc[] +*** xref:policies-pdk-configure-features-authentication.adoc[] +*** xref:policies-pdk-configure-features-timer.adoc[] +*** xref:policies-pdk-configure-features-stop.adoc[] +** xref:policies-pdk-compile-policies.adoc[] +** xref:policies-pdk-debug-local.adoc[] +** xref:policies-pdk-integration-tests.adoc[] +** xref:policies-pdk-publish-policies.adoc[] +* xref:policies-pdk-policy-templates.adoc[] +* xref:policies-pdk-apply-policies.adoc[] +* xref:policies-pdk-debug-deployed-policies.adoc[] +* xref:policies-pdk-troubleshooting.adoc[Troubleshooting] + diff --git a/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-contracts.adoc b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-contracts.adoc new file mode 100644 index 000000000..afd7d6c0c --- /dev/null +++ b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-contracts.adoc @@ -0,0 +1,311 @@ += Using Contracts Validation library functions +ifndef::env-site,env-github[] +include::_attributes.adoc[] +endif::[] +:imagesdir: ../assets/images + +NOTE: To view an example policy project that uses Flex Gateway Policy Development Kit (PDK)'s Contracts Validation library, see https://github.com/mulesoft/pdk-custom-policy-examples/blob/{template-policies-url-ver-var}/contracts-validation/README.md[Contracts Validation Policy Example^]. + +A https://docs.mulesoft.com/api-manager/latest/api-contracts-landing-page[contract] between the API instance and the application gives access to it based on SLA tiers. +Only one contract can exist per API instance and application at any time. +Client applications can validate their contract by providing credentials that consist of two keys: client ID and client secret. +Contracts Validation library provides functionalities to validate client credentials and is useful for implementing a custom client ID enforcement policy. + +[[contracts-module]] +== Contracts module +The Contracts Validation library is available from the `pdk::contracts` module. +The module provides the `ContractValidator` object to validate client credentials. +Client credentials are represented by `ClientId` and `ClientSecret` objects. +Authentication and authorization can be validated with `ContractValidator::authenticate()` and `ContractValidator::authorize()` methods: + +[source,Rust] +---- + +impl ContractValidator { + + pub fn authenticate(client_id: &ClientId, client_secret: &ClientSecret) -> Result; + + pub fn authorize(client_id: &ClientId, client_secret: &ClientSecret) -> Result; +} + +---- + +Both methods return a `ClientData` struct that holds client ID, client name and SLA ID fields. + +[source,Rust] +---- +pub struct ClientData { + pub client_id: String, + pub client_name: String, + pub sla_id: Option, +} +---- + +[[request-validation]] +== Request authentication and authorization + +A request can be authenticated by extracting client credentials from it, and invoking the `ClientValidation::authenticate()` method. +A helper function `basic_auth_credentials()` for extracting Basic Auth credentials from headers is provided with the `pdk::contracts` module. + +[source,Rust] +---- +pub fn basic_auth_credentials(request_headers_state: &RequestHeadersState) + -> Result<(ClientId, ClientSecret), BasicAuthError>; +---- + +An authentication request filter could be written as follows: + +[source,Rust] +---- +async fn my_authentication_filter( + state: RequestHeadersState, + authentication: Authentication, + validator: &ContractValidator, +) -> Flow<()> { + + // Extract credentials + let (client_id, client_secret) = match basic_auth_credentials(&state) { + Ok(credentials) => credentials, + Err(e) => { + logger::info!("Invalid credentials: {e}"); + + // For simplicity, we are using a user-defined `unathorized_response()` + // helper function for building responses. + return Flow::Break(unauthorized_response("Invalid credentials", 401)); + } + }; + + // Validate authentication + let validation = validator.authenticate(&client_id, &client_secret); + + let client_data = match validation { + Ok(client_data) => client_data, + Err(e) => { + logger::info!("Invalid authentication: {e}"); + return Flow::Break(unauthorized_response("Invalid authentication", 403)); + } + }; + + // Update the current authentication + if let Some(mut auth) = authentication.authentication() { + auth.client_id = Some(client_data.client_id); + auth.client_name = Some(client_data.client_name); + + authentication.set_authentication(Some(&auth)); + } + + Flow::Continue(()) +} +---- + +An authorization request filter can be written in a similar way: + +[source,Rust] +---- +async fn my_authorization_filter( + state: RequestHeadersState, + validator: &ContractValidator, +) -> Flow<()> { + + // Extract client id with a user defined helper `extract_client_id()` function, + let client_id = match extract_client_id(&state) { + Ok(credentials) => credentials, + Err(e) => { + logger::info!("Invalid credentials: {e}"); + + // For simplicity, we are using a user-defined `unathorized_response()` + // helper function for building responses. + return Flow::Break(unauthorized_response("Invalid credentials", 401)); + } + }; + + // Validate authorization + let validation = validator.authorize(&client_id); + + if let Err(e) = validation { + logger::info!("Invalid authentication: {e}"); + return Flow::Break(unauthorized_response("Invalid authentication", 403)); + } + + Flow::Continue(()) +} +---- + +[[custom-credentials-extraction]] +== Custom extraction for client credentials + +Credential extraction can be implemented by invoking `ClientId::new()` and `ClientSecret::new()` methods: + +[source,Rust] +---- +impl ClientId { + pub fn new(client_id: String) -> Self; +} + +impl ClientSecret { + pub fn new(client_secret: String) -> Self; +} +---- + +The next snippet shows how to initialize a set of credentials: + +[source,Rust] +---- + +fn initialize_credentials(raw_client_id: String, raw_client_secret) -> (ClientId, ClientSecret) { + let client_id = ClientId::new(raw_client_id); + let client_secret = ClientSecret::new(raw_client_secret); + + (client_id, client_secret) +} + +---- + +NOTE: `ClientSecret` is implemented in a secure way where it's content is zeroed after use. +It also implements the `Debug` trait without exposing his content. + +[[contract-validator-injection]] +== ContractValidator injection + +The `ContractValidator` object can be injected in the `configure()` function and share it by reference to the filter +as in the next snippet: + +[source,Rust] +---- +#[entrypoint] +async fn configure(launcher: Launcher, validator: ContractValidator) -> Result<(), LaunchError> { + + let filter = on_request(|state, authentication| my_authentication_filter(state, authentication, &validator)); + launcher.launch(filter).await?; + + OK(()) +} +---- + +[[contract-database-polling]] +=== Local Contracts Database polling + +`ContractValidator` object maintains a local copy of the contracts database. This local copy must be polled periodically +in order to maintain the local copy of contracts up to date by invoking the `ContractValidator::update_contracts()` method: + +[source,Rust] +---- + +impl ContractValidator { + pub async fn update_contracts(&self) -> Result<(), UpdateError>; +} + +---- + +The `ContractValidator::update_contracts()` method returns an error that specifies if some connectivity problem occurs +during the contracts database polling. It is responsibility of the programmer the handle retry and error handling heuristics. + +`ContractValidator::update_contracts()` must be invoked periodically in a period specified by the +`ContractValidator::UPDATE_PERIOD` constant: + +[source,Rust] +---- + +async fn update_my_contracts(validator: &ContractValidator, clock: Clock) { + + // Configure a new timer + let timer = clock.period(ContractValidator::UPDATE_PERIOD); + + loop { + // Update result handling should be customized by the programmer + let update_result = validator.update_contracts().await; + + // Wait for the next tick + if !timer.next_tick().await { + // If no more ticks are available, finish the task. + return; + } + } +} +---- + +[source,Rust] +---- +There is a contract database initialization period where the contracts database must be polled in a higher frequency +than update period. The `ContractValidator::INITIALIZATION_PERIOD` specifies the interval required by the initial polling. + +async fn initialize_my_contracts(validator: &ContractValidator, clock: Clock) { + + // Configure a new timer + let timer = clock.period(ContractValidator::UPDATE_PERIOD); + + loop { + // Update result handling should be customized by the programmer + let update_result = validator.update_contracts().await; + + // Wait for the next tick + if !timer.next_tick().await { + // If no more ticks are available, finish the task. + return; + } + } +} + +---- + +The next snippet shows a complete contract polling task, with initialization and update polling periods: + +[source,Rust] +---- +async fn update_my_contracts(validator: &ContractValidator, clock: Clock) { + let initialization_timer = clock.period(ContractValidator::INITIALIZATION_PERIOD); + + loop { + if validator.update_contracts().await.is_ok() { + logger::info!("Contracts storage initialized."); + break; + } + + if !initialization_timer.next_tick().await { + logger::info!("Tick event suspended."); + break; + } + } + + let update_timer = initialization_timer + .release() + .period(ContractValidator::UPDATE_PERIOD); + + loop { + let _ = validator.update_contracts().await; + + if !update_timer.next_tick().await { + logger::info!("Tick event suspended."); + break; + } + logger::info!("Retrying contracts storage initialization."); + } +} +---- + +Since contracts database polling task must run concurrently with `Launcher::launch()` task, the `join!()` macro +from the https://crates.io/crates/futures[futures] crate can help to join both tasks. +The next snippet shows a complete `configure()` function with polling and launching: + +[source,Rust] +---- +#[entrypoint] +async fn configure(launcher: Launcher, clock: Clock,validator: ContractValidator) -> Result<(), LauncherError> { + + let filter = on_request(|state, authentication| my_authentication_filter(state, authentication, &validator)); + + let (_, launcher_result) = join! { + update_my_contracts(&validator, clock), + launcher.launch(filter), + }; + + launcher_result +} +---- + +== See Also + +* xref:policies-pdk-configure-features.adoc[] +* xref:policies-pdk-configure-features-timer.adoc[] +* https://docs.mulesoft.com/api-manager/latest/api-contracts-landing-page[Client Applications, Contracts, and Credentials] + diff --git a/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc new file mode 100644 index 000000000..3274b5a63 --- /dev/null +++ b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-streamproperties.adoc @@ -0,0 +1,48 @@ += Sharing Data Between Policies +ifndef::env-site,env-github[] +include::_attributes.adoc[] +endif::[] +:imagesdir: ../assets/images + +The `StreamProperties` injectable provides an interface to: + +* Consume properties set by other policies that are processing the same request. +* Broadcast properties so that the other policies can consume them. + +To share data, `StreamProperties` implements the `PropertyAccessor` trait: + +[source,Rust] +---- +pub trait PropertyAccessor { + fn read_property(&self, path: &[&str]) -> Option; + fn set_property(&self, path: &[&str], value: Option<&[u8]>); +} +---- + +The following example shows a simple filter that reads a property from the stream and then writes another parameter to it: + +[source,Rust] +---- +async fn request_filter(stream: StreamProperties) -> Flow<()> { + let incoming = String::from_utf8(stream.read_property(&["incoming_property"]).unwrap_or_default()).unwrap_or_default(); + + logger::info!("Recieved incoming prop {}", incoming); + + let outgoing = "outgoing".as_bytes(); + + stream.set_property(&["outgoing_property"], Some(outgoing)); + + Flow::Continue(()) +} + +#[entrypoint] +async fn configure(launcher: Launcher) -> Result<()> { + let filter = on_request(|stream| request_filter(stream)); + launcher.launch(filter).await?; + Ok(()) +} +---- + +== See Also + +* xref:policies-pdk-configure-features-inject-parameters.adoc[] \ No newline at end of file diff --git a/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-timer.adoc b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-timer.adoc new file mode 100644 index 000000000..caf87f00a --- /dev/null +++ b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features-timer.adoc @@ -0,0 +1,186 @@ += Configuring Delayed, Periodic, and Synchronous Functions +ifndef::env-site,env-github[] +include::_attributes.adoc[] +endif::[] +:imagesdir: ../assets/images + +Use the `Clock` injectable to build a timer to enable delayed, periodic, and synchronous functions. Each policy supports only one timer. + +Each timer provides the following two async functions: + +[source,rust] +---- +pub async fn next_tick(&self) -> bool; +pub async fn sleep(&self, interval: Duration) -> bool; +---- + +* `next_tick`: Sleeps for the minimum amount of time the timer is capable of distinguishing. +* `sleep`: Sleeps for an amount of ticks where the elapsed time is greater or equal than the provided `Duration`. For example, if you configure the period of the clock to 10 seconds, calling `timer.sleep(Duration::from_secs(1))` sleeps for 10 seconds. + +Both functions return `true` when the specified time has passed or `false` if the policy is unapplied or edited during execution. + +== Configure the Timer + +To inject a timer in your policy, insert a `Clock` into your policy configuration function and define a timer with a time period: + +[source,rust] +---- +#[entrypoint] +async fn configure( + launcher: Launcher, + Configuration(bytes): Configuration, + clock: Clock, // Inject the clock to be able to launch async tasks. + client: HttpClient, +) -> Result<()> { + + + // set the period between ticks of the timer. + let timer = clock.period(Duration::from_secs(10)); +---- + +After you inject the timer, share a its reference with the `on_request` and `on_response` functions to begin creating delayed functions: + +[source,rust] +---- + launcher + .launch(on_request(|rs| request_filter(rs, &timer, &config))) + .await?; +---- + +You can now await timer functions in `on_request` and `on_response` functions, for example: + +[source,rust] +---- + let slept = timer.sleep(duration).await; + let tick = timer.next_tick().await; +---- + +NOTE: To view an example policy project that launches delayed functions, see https://github.com/mulesoft/pdk-custom-policy-examples/blob/{template-policies-url-ver-var}/spike/README.md[Spike Policy Example]. + +== Releasing the Clock + +The timer provides a method for releasing the `Clock`: + +[source,rust] +---- +pub fn release(self) -> Clock; +---- + +This method is useful if you want to reset the tick period in order to create a new timer. + +[source,rust] +---- + // Configure an initial tick period. + let initial_timer = clock.period(Duration::from_millis(500)); + some_initial_task(&other_timer).await; + + // Configure another tick period. + let other_timer = initial_timer.release().period(Duration::from_millis(3000); + some_other_task(&other_timer).await; + + // Get the clock back. + let clock = other_timer.release(); +---- + +== Launch Asynchronous Tasks + +Use the timer to execute tasks independent of the request flow. To execute asynchronous tasks: + +. Define your task inside an `async` function. +. In the policy configuration function, call your function but don't await the returned future. +. In the policy configuration function, call the `launch` function with the `on_request` and `on_response` filter functions but don't await the returned future. +. Use the `join!` macro to await both futures of your task function and `launch` function at the same time. ++ +NOTE: To use the `join` macro, add the ‘futures’ crate as dependency in your `cargo.toml` file. + +Each worker executes a copy of the task. Configure a loop inside the function to execute periodic tasks. For example, the following code example awaits both functions: + +[source,rust] +---- +let task = my_async_task(&timer, &config); +let launched = launcher.launch(filter); +let joined = join!(launched, task); +---- + +Adding a loop to the `my_async_task` function creates periodic tasks, for example: + +[source,rust] +---- +async fn my_async_task(timer: &Timer, config: &Config) { + // While the policy is still running. + // Wait for the next cycle. + while timer.next_tick().await { + // Execute periodic task + } +} +---- + +To launch multiple asynchronous tasks, send a reference of the timer to your `async` functions and join them with the `launch` task, for example: + +[source,rust] +---- +let task1 = my_async_task1(&timer, &config); +let task2 = my_async_task2(&timer, &config); +let launched = launcher.launch(filter); +let joined = join!(launched, task1, task2); +---- + +NOTE: To view an example policy project that launches asynchronous periodic tasks, see https://github.com/mulesoft/pdk-custom-policy-examples/blob/{template-policies-url-ver-var}/metrics/README.md[Metrics Policy Example^]. + +== Sync Asynchronous Tasks between Workers + +Use the PDK `LockBuilder` to sync workers. The lock ensures that only one worker is completing a task in a time period. Each policy supports multiple locks. + +Locks are useful when a policy consumes an HTTP service that has usage limits. A single worker can fetch data and then share the data with other workers to reduce service requests. + +To configure a lock: + +. Inject the `LockBuilder` into your policy configuration function: ++ +[source,rust] +---- +#[entrypoint] +async fn configure( + launcher: Launcher, + Configuration(bytes): Configuration, + clock: Clock, // Inject the clock to be able to launch async tasks. + lock: LockBuilder, // Inject the lock to be able to synchronize the workers. +) -> Result<()> { +---- + +. Configure the lock and share a reference of it with your asynchronous function: ++ +[source,rust] +---- + let lock = lock + .new(ID.to_string()) + .expiration(Duration::from_secs(20)) + .build(); + let task = my_async_task(&timer, &lock); +---- ++ +If the asynchronous task executes additional `async` function calls inside the task, configure the `expiration` of the lock to a duration longer than the total time needed to complete the nested `async` function calls. + +. Inside the asynchronous function, acquire the lock before executing the desired task. If no other worker has the lock, the current worker acquires the lock. Otherwise, you must omit the execution of the task and wait for the next task loop, for example: ++ +[source,rust] +---- +async fn my_async_task( + timer: &Timer, + lock: &TryLock, +) { + while timer.next_tick().await { + if let Some(acquired) = lock.try_lock() { + // execute task. + } + } +} +---- ++ +After obtaining the lock, if you execute code that requires you await a result, you must call the `refresh_lock` function to ensure the lock hasn’t expired. If the lock has expired, the worker is considered unresponsive. Abort the execution of your task because another worker is running the same task execution. + +NOTE: To view an example policy project that synchronizes asynchronous periodic tasks and shares information between workers, see https://github.com/mulesoft/pdk-custom-policy-examples/blob/{template-policies-url-ver-var}/block/README.md[Block Policy Example]. + +== See Also + +* xref:policies-pdk-configure-features-inject-parameters.adoc[] \ No newline at end of file diff --git a/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features.adoc b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features.adoc new file mode 100644 index 000000000..fb1dfb34b --- /dev/null +++ b/pdk/1.3/modules/ROOT/pages/policies-pdk-configure-features.adoc @@ -0,0 +1,197 @@ += Implementing Your Custom Policy Features in Rust +ifndef::env-site,env-github[] +include::_attributes.adoc[] +endif::[] +:imagesdir: ../assets/images + +The following pages provide Rust code examples that complete various policy tasks. Implement the Rust code for your policy in the `src/lib.rs` file. + +The code examples assume you have some prior knowledge of the Rust Programming Language. For more information about programming in Rust, see: + +* https://doc.rust-lang.org/book/[The Rust Programming Language Documentation ^] +* https://rust-lang-nursery.github.io/rust-cookbook/intro.html[Rust Cookbook ^] + +== Before You Begin + +* xref:policies-pdk-create-project.adoc[Create a New Project] +* xref:policies-pdk-create-schema-definition.adoc[Define a Schema Definition] ++ +NOTE: You may need to add or redefine configuration parameters while implementing your policy. + +* Review <>, <>, and <> to learn about the policy's source code structure before adding <>. + +[[policy-template]] +== Policy Template + +Flex Gateway Policy Development Kit (PDK) provides the initial `src/lib.rs` file in the policy project as a template to begin implementing your policy: + +[source,Rust] +---- +// Copyright 2024 Salesforce, Inc. All rights reserved. +mod generated; + +use anyhow::{anyhow, Result}; + +use pdk::hl::*; +use pdk::logger; + +use crate::generated::config::Config; + +// This filter shows how to log a specific request header. +// You can extend the function and use the configurations exposed in config.rs file +async fn request_filter(request_state: RequestState, _config: &Config) { + let headers_state = request_state.into_headers_state().await; + let token = headers_state.handler().header("Token").unwrap_or_default(); + // Log the header value + logger::info!("Header value: {token}"); +} + +#[entrypoint] +async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> { + let config: Config = serde_json::from_slice(&bytes).map_err(|err| { + anyhow!( + "Failed to parse configuration '{}'. Cause: {}", + String::from_utf8_lossy(&bytes), + err + ) + })?; + let filter = on_request(|rs| request_filter(rs, &config)); + launcher.launch(filter).await?; + Ok(()) +} +---- + +The policy performs some simple logging. For each incoming request, the policy logs the header `token` at the `info` log level. + +The following elements configure the policy: + +* `use pdk::api::hl::*;`: Imports all the components of PDK into the `lib.rs` source code. +* `#[entrypoint]` function: Executes when the policy is applied and calls the `request_filter` function. Variables defined inside this function are avaliable while the policy is applied. ++ +The `#[entrypoint]` function receives the following parameters: ++ +** `Launcher`: Sets the filter functions. +** `Configuration`: Provides the configuration parameters defined in xref:policies-pdk-create-schema-definition.adoc[]. + +* `request_filter`: Executes once per every request sent to the API instance to which the policy is applied. This function implements the filtering performed by the example policy. Variables defined inside this function are available for the duration of the request. ++ +The `request_filter` is an example of a wrapped function. ++ +The `#[entrypoint]` function executes wrapped functions: ++ +[source,Rust] +---- +let filter = on_request(|rs| request_filter(rs, &config)); +launcher.launch(filter).await?; +---- + +[[wrapped-functions]] +== Wrapped Functions + +The `on_response` or `on_request` wrapper defines when the filter function executes. + +You can define custom functions other than the provided `requests_filter` and `response_filter` in the `#[entrypoint]` function. The `requests_filter` and `response_filter` are only examples. + +Instead of filtering an incoming request as shown in the previous example, you can process the outgoing response by using the `on_response` wrapper: + +[source,Rust] +---- +async fn response_filter(response_state: ResponseState, _config: &Config) { + ... +} + +#[entrypoint] +async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> { + let config: Config = serde_json::from_slice(&bytes).map_err(|err| { + anyhow!( + "Failed to parse configuration '{}'. Cause: {}", + String::from_utf8_lossy(&bytes), + err + ) + })?; + let filter = on_response(|rs| response_filter(rs, &config)); + launcher.launch(filter).await?; + Ok(()) +} +---- + +NOTE: The `request_filter` function is now the `response_filter` function. + +To filter both requests and responses, use both wrappers: + +[source,Rust] +---- +async fn request_filter(request_state: RequestState,_config: &Config) { + ... +} + +async fn response_filter(response_state: ResponseState,_config: &Config) { + ... +} + +#[entrypoint] +async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> { + let config: Config = serde_json::from_slice(&bytes).map_err(|err| { + anyhow!( + "Failed to parse configuration '{}'. Cause: {}", + String::from_utf8_lossy(&bytes), + err + ) + })?; + let filter = on_request(|rs| request_filter(rs, &config)) + .on_response(|rs| response_filter(rs, &config)); + launcher.launch(filter).await?; + Ok(()) +} +---- + +[[flow-cancellation]] +== Flow Cancellation + +Custom policies run on a single-threaded environment. However, every set of wrapped functions (a `on_request` and a `on_response` `async` function) run as a concurrent task, meaning that a single policy instance handles multiple requests concurrently. + +To support this behavior, every `.await` is a potential task interruption point. When an `.await` is invoked, the underlying async runtime can set the current task +to sleep for a moment, and awake another task that has not finished. The async runtime can cancel a task and never run code that comes after the last `.await` invocation. `on_request` and `on_response` functions assume that `.await` invocations are potential cancellation points. + +The most common situation, as shown in the following code example, for task cancellation is when a request function waiting for a body and an upstream policy returns an early response: + +[source,Rust] +---- + +// Request function for upstream policy +async fn upstream_request(state: RequestState) -> Response { + Response::new(404) +} + +// Request function for downstream policy +async fn downstream_request(state: RequestState) { + + // Request function will be cancelled after this .await point. + let body_state = state.into_body_state().await; + + // Code here will never be executed +} + +---- + +[[provided-rust-source-code]] +== Discover PDK features with Provided Rust Source Code + +PDK provides code examples for the following policy features. Review <> and <> before adding the additional policy capabilities: + +* xref:policies-pdk-configure-features-logging.adoc[] +* xref:policies-pdk-configure-features-headers.adoc[] +* xref:policies-pdk-configure-features-inject-parameters.adoc[] +* xref:policies-pdk-configure-features-http-request.adoc[] +* xref:policies-pdk-configure-features-contracts.adoc[] +* xref:policies-pdk-configure-features-cors.adoc[] +* xref:policies-pdk-configure-features-jwt.adoc[] +* xref:policies-pdk-configure-features-dataweave.adoc[] +* xref:policies-pdk-configure-features-share-data.adoc[] +* xref:policies-pdk-configure-features-libraries.adoc[] +* xref:policies-pdk-configure-features-caching.adoc[] +* xref:policies-pdk-configure-features-metadata.adoc[] +* xref:policies-pdk-configure-features-streamproperties.adoc[] +* xref:policies-pdk-configure-features-authentication.adoc[] +* xref:policies-pdk-configure-features-timer.adoc[] +* xref:policies-pdk-configure-features-stop.adoc[] diff --git a/pdk/1.3/modules/ROOT/pages/policies-pdk-prerequisites.adoc b/pdk/1.3/modules/ROOT/pages/policies-pdk-prerequisites.adoc new file mode 100644 index 000000000..0e29af260 --- /dev/null +++ b/pdk/1.3/modules/ROOT/pages/policies-pdk-prerequisites.adoc @@ -0,0 +1,119 @@ += Reviewing PDK Prerequisites +ifndef::env-site,env-github[] +include::_attributes.adoc[] +endif::[] +:imagesdir: ../assets/images + +To begin using Flex Gateway Policy Development Kit (PDK), ensure you have the following prerequisites installed. + +== Flex Gateway + +PDK tutorials assume that you have xref:gateway::index.adoc[downloaded Flex Gateway] and have some prior knowledge of the product. + +include::release-notes::partial$pdk/pdk-supported-versions.adoc[tags=intro;1.3.0] + + +[[rust-requirements]] +== Rust Requirements for Using PDK + +. Git ++ +To download and install, see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git[Installing Git^]. + +. Rust programming language {rust-ver-var} or later ++ +To install or update Rust, run the following curl command: ++ +[source,ssh] +---- +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +---- ++ +NOTE: If you do not have curl installed, see https://curl.se/download.html[Install curl^]. + +. Rust WebAssembly System Interface (WASI) Crate ++ +To install or update Rust WASI, run the following command: ++ +[source,ssh] +---- +rustup target add wasm32-wasi +---- + +. Cargo Generate ++ +To install or update Cargo Generate, run the following command: ++ +[source,ssh] +---- +cargo install --locked cargo-generate +---- + + +== Supported Operating Systems + +* macOS +* Ubuntu ++ +To use Ubuntu, execute the following commands to install the required packages after installing the <>: ++ +[source,ssh] +---- +sudo apt install -y build-essential +sudo apt install -y pkg-config +sudo apt install -y libssl-dev +---- +* Windows ++ +IMPORTANT: PDK supports Windows to develop and test you custom policy. However, Flex Gateway does not support Windows in production environments. ++ +To execute the `make` commands included in PDK, install: ++ +. https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3[PowerShell on Windows ^] +. Make, with either: +** https://gnuwin32.sourceforge.net/packages/make.htm[Make for Windows^] +** Chocolatey: ++ +[source,ssh] +---- +choco install make +---- + +[[cli]] +== Anypoint CLI + +To configure the Anypoint CLI for PDK: + +. xref:4.x@anypoint-cli::install.adoc[Install Anypoint CLI 4.x v1.4.4 or later]. ++ +NOTE: To find your Anypoint CLI 4.x version, see xref:4.x@anypoint-cli::install.adoc#verify-installation[Verify the Core Package Version]. +. xref:4.x@anypoint-cli::auth.adoc[Authenticate to the Anypoint Platform CLI] using a xref:access-management::connected-apps-overview.adoc[Connected App]. ++ +Authenticate with a Connected App by providing the app's client ID and client secret. ++ +NOTE: In addition to the `View Organization` and `View Environment` scopes required to authenticate to Anypoint Platform, to complete xref:policies-pdk-publish-policies.adoc[], the Connected App must have the `Exchange Contributor` scope applied. + +. Run the following command to install or update the Anypoint CLI PDK Plugin: ++ +[source,ssh] +---- +anypoint-cli-v4 plugins:install anypoint-cli-pdk-plugin +---- + +== Docker + +Docker is required to use the xref:policies-pdk-debug-local.adoc[local debugging environment] and the xref:policies-pdk-integration-tests.adoc[integration testing framework] provided with PDK. You can still build and publish policies without docker. + +To install Docker, see https://docs.docker.com/get-docker/[Install Docker^]. + +IMPORTANT: You can run Flex Gateway in Local Mode on Docker in Windows only to develop and test your policies. However, Flex Gateway does not support Windows in production environments. + +== Visual Studio (Suggested IDE) + +You can use PDK in any IDE or text editor, such as https://code.visualstudio.com/[Microsoft Visual Studio^] with the https://code.visualstudio.com/docs/languages/rust[Rust in Visual Studio Code Plugin^] installed. + +== See Also + +* xref:policies-pdk-upgrade-pdk.adoc[] +* xref:policies-pdk-architecture.adoc[] +* xref:policies-pdk-develop-custom-policies.adoc[]