From 4343387adc8e0a5b262ef53a1231cb641e129459 Mon Sep 17 00:00:00 2001 From: Adil Hafeez Date: Tue, 3 Dec 2024 19:22:31 -0800 Subject: [PATCH] Add demo for acm --- arch/arch_config_schema.yaml | 7 + crates/common/src/configuration.rs | 23 +- crates/common/src/lib.rs | 1 + crates/common/src/path.rs | 82 ++ crates/prompt_gateway/src/stream_context.rs | 32 +- demos/acm_k8s/README.md | 2 + demos/acm_k8s/acm_api.yaml | 117 ++ demos/acm_k8s/acm_service/.dockerignore | 72 + demos/acm_k8s/acm_service/.gitignore | 66 + .../acm_service/.openapi-generator-ignore | 23 + .../acm_service/.openapi-generator/FILES | 26 + .../acm_service/.openapi-generator/VERSION | 1 + demos/acm_k8s/acm_service/.travis.yml | 14 + demos/acm_k8s/acm_service/Dockerfile | 16 + demos/acm_k8s/acm_service/README.md | 49 + demos/acm_k8s/acm_service/git_push.sh | 57 + .../acm_service/openapi_server/__init__.py | 0 .../acm_service/openapi_server/__main__.py | 23 + .../openapi_server/controllers/__init__.py | 0 .../openapi_server/controllers/controller.py | 84 ++ .../controllers/default_controller.py | 88 ++ .../controllers/security_controller.py | 1 + .../acm_service/openapi_server/encoder.py | 19 + .../openapi_server/models/__init__.py | 8 + .../openapi_server/models/base_model.py | 70 + .../openapi_server/models/managed_cluster.py | 186 +++ .../models/managed_cluster_client_config.py | 81 ++ .../openapi_server/models/metadata.py | 81 ++ .../acm_service/openapi_server/models/spec.py | 98 ++ .../openapi_server/openapi/openapi.yaml | 162 +++ .../openapi_server/test/__init__.py | 15 + .../test/test_default_controller.py | 89 ++ .../openapi_server/typing_utils.py | 30 + .../acm_service/openapi_server/util.py | 149 ++ demos/acm_k8s/acm_service/requirements.txt | 13 + demos/acm_k8s/acm_service/setup.py | 34 + .../acm_k8s/acm_service/test-requirements.txt | 4 + demos/acm_k8s/acm_service/tox.ini | 11 + demos/acm_k8s/arch_config.yaml | 67 + demos/acm_k8s/docker-compose.yaml | 31 + demos/acm_k8s/generate_acm_service_stub.sh | 6 + demos/acm_k8s/petstore.yml | 1206 +++++++++++++++++ demos/acm_k8s/run_demo.sh | 47 + model_server/app/main.py | 6 + 44 files changed, 3194 insertions(+), 3 deletions(-) create mode 100644 crates/common/src/path.rs create mode 100644 demos/acm_k8s/README.md create mode 100644 demos/acm_k8s/acm_api.yaml create mode 100644 demos/acm_k8s/acm_service/.dockerignore create mode 100644 demos/acm_k8s/acm_service/.gitignore create mode 100644 demos/acm_k8s/acm_service/.openapi-generator-ignore create mode 100644 demos/acm_k8s/acm_service/.openapi-generator/FILES create mode 100644 demos/acm_k8s/acm_service/.openapi-generator/VERSION create mode 100644 demos/acm_k8s/acm_service/.travis.yml create mode 100644 demos/acm_k8s/acm_service/Dockerfile create mode 100644 demos/acm_k8s/acm_service/README.md create mode 100644 demos/acm_k8s/acm_service/git_push.sh create mode 100644 demos/acm_k8s/acm_service/openapi_server/__init__.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/__main__.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/controllers/__init__.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/controllers/controller.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/controllers/default_controller.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/controllers/security_controller.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/encoder.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/__init__.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/base_model.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/managed_cluster.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/managed_cluster_client_config.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/metadata.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/models/spec.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/openapi/openapi.yaml create mode 100644 demos/acm_k8s/acm_service/openapi_server/test/__init__.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/test/test_default_controller.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/typing_utils.py create mode 100644 demos/acm_k8s/acm_service/openapi_server/util.py create mode 100644 demos/acm_k8s/acm_service/requirements.txt create mode 100644 demos/acm_k8s/acm_service/setup.py create mode 100644 demos/acm_k8s/acm_service/test-requirements.txt create mode 100644 demos/acm_k8s/acm_service/tox.ini create mode 100644 demos/acm_k8s/arch_config.yaml create mode 100644 demos/acm_k8s/docker-compose.yaml create mode 100644 demos/acm_k8s/generate_acm_service_stub.sh create mode 100644 demos/acm_k8s/petstore.yml create mode 100644 demos/acm_k8s/run_demo.sh diff --git a/arch/arch_config_schema.yaml b/arch/arch_config_schema.yaml index a11e9562..0f7e55ac 100644 --- a/arch/arch_config_schema.yaml +++ b/arch/arch_config_schema.yaml @@ -92,6 +92,8 @@ properties: type: array items: type: string + in_path: + type: boolean additionalProperties: false required: - name @@ -108,6 +110,11 @@ properties: required: - name - path + http_method: + type: string + enum: + - GET + - POST system_prompt: type: string additionalProperties: false diff --git a/crates/common/src/configuration.rs b/crates/common/src/configuration.rs index 543849c9..7486e3d9 100644 --- a/crates/common/src/configuration.rs +++ b/crates/common/src/configuration.rs @@ -179,8 +179,6 @@ impl Display for LlmProvider { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Endpoint { pub endpoint: Option, - // pub connect_timeout: Option, - // pub timeout: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -193,6 +191,7 @@ pub struct Parameter { #[serde(rename = "enum")] pub enum_values: Option>, pub default: Option, + pub in_path: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -201,11 +200,31 @@ pub struct EndpointDetails { pub path: Option, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)] +pub enum HttpMethod { + #[serde(rename = "GET")] + Get, + #[default] + #[serde(rename = "POST")] + Post, +} + +impl Display for HttpMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HttpMethod::Get => write!(f, "GET"), + HttpMethod::Post => write!(f, "POST"), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PromptTarget { pub name: String, pub default: Option, pub description: String, + #[serde(rename = "http_method")] + pub method: Option, pub endpoint: Option, pub parameters: Option>, pub system_prompt: Option, diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index a41badf9..aa34f2fd 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -11,3 +11,4 @@ pub mod routing; pub mod stats; pub mod tokenizer; pub mod tracing; +pub mod path; diff --git a/crates/common/src/path.rs b/crates/common/src/path.rs new file mode 100644 index 00000000..2b289c9d --- /dev/null +++ b/crates/common/src/path.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +pub fn replace_params_in_path(path: &str, params: &HashMap) -> Result { + let mut result = String::new(); + let mut in_param = false; + let mut current_param = String::new(); + + for c in path.chars() { + if c == '{' { + in_param = true; + } else if c == '}' { + in_param = false; + let param_name = current_param.clone(); + if let Some(value) = params.get(¶m_name) { + result.push_str(value); + } else { + return Err(format!("Missing value for parameter `{}`", param_name)); + } + current_param.clear(); + } else { + if in_param { + current_param.push(c); + } else { + result.push(c); + } + } + } + + Ok(result) +} + +#[cfg(test)] +mod test { + #[test] + fn test_replace_path() { + let path = "/cluster.open-cluster-management.io/v1/managedclusters/{cluster_name}"; + let params = vec![("cluster_name".to_string(), "test1".to_string())] + .into_iter() + .collect(); + assert_eq!( + super::replace_params_in_path(path, ¶ms), + Ok("/cluster.open-cluster-management.io/v1/managedclusters/test1".to_string()) + ); + + let path = "/cluster.open-cluster-management.io/v1/managedclusters"; + let params = vec![].into_iter().collect(); + assert_eq!( + super::replace_params_in_path(path, ¶ms), + Ok("/cluster.open-cluster-management.io/v1/managedclusters".to_string()) + ); + + let path = "/foo/{bar}/baz"; + let params = vec![("bar".to_string(), "qux".to_string())] + .into_iter() + .collect(); + assert_eq!( + super::replace_params_in_path(path, ¶ms), + Ok("/foo/qux/baz".to_string()) + ); + + let path = "/foo/{bar}/baz/{qux}"; + let params = vec![ + ("bar".to_string(), "qux".to_string()), + ("qux".to_string(), "quux".to_string()), + ] + .into_iter() + .collect(); + assert_eq!( + super::replace_params_in_path(path, ¶ms), + Ok("/foo/qux/baz/quux".to_string()) + ); + + let path = "/foo/{bar}/baz/{qux}"; + let params = vec![("bar".to_string(), "qux".to_string())] + .into_iter() + .collect(); + assert_eq!( + super::replace_params_in_path(path, ¶ms), + Err("Missing value for parameter `qux`".to_string()) + ); + } +} diff --git a/crates/prompt_gateway/src/stream_context.rs b/crates/prompt_gateway/src/stream_context.rs index 6df5a4d1..1a4b6cba 100644 --- a/crates/prompt_gateway/src/stream_context.rs +++ b/crates/prompt_gateway/src/stream_context.rs @@ -29,6 +29,7 @@ use derivative::Derivative; use http::StatusCode; use log::{debug, info, trace, warn}; use proxy_wasm::traits::*; +use serde_yaml::Value; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -892,9 +893,38 @@ impl StreamContext { let endpoint = prompt_target.endpoint.unwrap(); let path: String = endpoint.path.unwrap_or(String::from("/")); + // only add params that are of string and number type + let url_params = tool_params + .iter() + .filter(|(_, value)| value.is_number() || value.is_string() || value.is_bool()) + .map(|(key, value)| match value { + Value::Number(n) => (key.clone(), n.to_string()), + Value::String(s) => (key.clone(), s.clone()), + Value::Bool(b) => (key.clone(), b.to_string()), + Value::Null => todo!(), + Value::Sequence(_) => todo!(), + Value::Mapping(_) => todo!(), + Value::Tagged(_) => todo!(), + }) + .collect::>(); + + let path = match common::path::replace_params_in_path(&path, &url_params) { + Ok(path) => path, + Err(e) => { + return self.send_server_error( + ServerError::BadRequest { + why: format!("error replacing params in path: {}", e), + }, + Some(StatusCode::BAD_REQUEST), + ); + } + }; + + let http_method = prompt_target.method.unwrap_or_default().to_string(); + info!("http_method: {}", http_method); let mut headers = vec![ (ARCH_UPSTREAM_HOST_HEADER, endpoint.name.as_str()), - (":method", "POST"), + (":method", &http_method), (":path", &path), (":authority", endpoint.name.as_str()), ("content-type", "application/json"), diff --git a/demos/acm_k8s/README.md b/demos/acm_k8s/README.md new file mode 100644 index 00000000..91fff783 --- /dev/null +++ b/demos/acm_k8s/README.md @@ -0,0 +1,2 @@ +# Function calling +This demo shows arch gateway interacting with ACM rest APIs. diff --git a/demos/acm_k8s/acm_api.yaml b/demos/acm_k8s/acm_api.yaml new file mode 100644 index 00000000..4b4ec9fd --- /dev/null +++ b/demos/acm_k8s/acm_api.yaml @@ -0,0 +1,117 @@ +openapi: 3.0.0 +info: + version: 2.12.0 + title: ACM API for cluster management + description: This is the API for managing clusters using ACM - https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/apis/apis#tags + contact: + name: Katanemo Labs Inc. + email: support@katanemo.com + url: https://katanemo.com +paths: + /cluster.open-cluster-management.io/v1/managedclusters: + get: + summary: Query your clusters for more details. + operationId: listManagedClusters + responses: + "200": + description: List of managed clusters + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ManagedCluster" + post: + summary: Create a cluster + operationId: createCluster + requestBody: + description: Details about the service, including the text-representation of the service APIs. + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCluster" + responses: + "200": + description: cluster created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCluster" + + /cluster.open-cluster-management.io/v1/managedclusters/{cluster_name}: + get: + summary: Query a single cluster for more details + operationId: getCluster + parameters: + - name: cluster_name + in: path + description: The name of the cluster to retrieve + required: true + schema: + type: string + responses: + "200": + description: Cluster details + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCluster" + delete: + summary: Delete a single cluster + responses: + "200": + description: Cluster details + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedCluster" + +components: + schemas: + ManagedCluster: + title: managed cluster details + type: object + properties: + apiVersion: + type: string + enum: + - cluster.open-cluster-management.io/v1 + kind: + type: string + enum: + - ManagedCluster + metadata: + $ref: "#/components/schemas/Metadata" + spec: + $ref: "#/components/schemas/Spec" + status: + type: string + Metadata: + title: metadata details + type: object + properties: + name: + type: string + labels: + type: object + additionalProperties: + type: string + Spec: + title: spec details + type: object + properties: + hubAcceptsClient: + type: boolean + managedClusterClientConfigs: + type: array + items: + $ref: "#/components/schemas/ManagedClusterClientConfig" + ManagedClusterClientConfig: + title: managed cluster client config details + type: object + properties: + url: + type: string + caBundle: + type: string diff --git a/demos/acm_k8s/acm_service/.dockerignore b/demos/acm_k8s/acm_service/.dockerignore new file mode 100644 index 00000000..f9619601 --- /dev/null +++ b/demos/acm_k8s/acm_service/.dockerignore @@ -0,0 +1,72 @@ +.travis.yaml +.openapi-generator-ignore +README.md +tox.ini +git_push.sh +test-requirements.txt +setup.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/demos/acm_k8s/acm_service/.gitignore b/demos/acm_k8s/acm_service/.gitignore new file mode 100644 index 00000000..43995bd4 --- /dev/null +++ b/demos/acm_k8s/acm_service/.gitignore @@ -0,0 +1,66 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.venv/ +.python-version +.pytest_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/demos/acm_k8s/acm_service/.openapi-generator-ignore b/demos/acm_k8s/acm_service/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/demos/acm_k8s/acm_service/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/demos/acm_k8s/acm_service/.openapi-generator/FILES b/demos/acm_k8s/acm_service/.openapi-generator/FILES new file mode 100644 index 00000000..5387d276 --- /dev/null +++ b/demos/acm_k8s/acm_service/.openapi-generator/FILES @@ -0,0 +1,26 @@ +.dockerignore +.gitignore +.travis.yml +Dockerfile +README.md +git_push.sh +openapi_server/__init__.py +openapi_server/__main__.py +openapi_server/controllers/__init__.py +openapi_server/controllers/default_controller.py +openapi_server/controllers/security_controller.py +openapi_server/encoder.py +openapi_server/models/__init__.py +openapi_server/models/base_model.py +openapi_server/models/managed_cluster.py +openapi_server/models/managed_cluster_client_config.py +openapi_server/models/metadata.py +openapi_server/models/spec.py +openapi_server/openapi/openapi.yaml +openapi_server/test/__init__.py +openapi_server/typing_utils.py +openapi_server/util.py +requirements.txt +setup.py +test-requirements.txt +tox.ini diff --git a/demos/acm_k8s/acm_service/.openapi-generator/VERSION b/demos/acm_k8s/acm_service/.openapi-generator/VERSION new file mode 100644 index 00000000..88411912 --- /dev/null +++ b/demos/acm_k8s/acm_service/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.11.0-SNAPSHOT diff --git a/demos/acm_k8s/acm_service/.travis.yml b/demos/acm_k8s/acm_service/.travis.yml new file mode 100644 index 00000000..ad71ee5c --- /dev/null +++ b/demos/acm_k8s/acm_service/.travis.yml @@ -0,0 +1,14 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: + - "3.2" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/demos/acm_k8s/acm_service/Dockerfile b/demos/acm_k8s/acm_service/Dockerfile new file mode 100644 index 00000000..ac9e33ab --- /dev/null +++ b/demos/acm_k8s/acm_service/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3-alpine + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ + +RUN pip3 install --no-cache-dir -r requirements.txt + +COPY . /usr/src/app + +EXPOSE 8080 + +ENTRYPOINT ["python3"] + +CMD ["-m", "openapi_server"] diff --git a/demos/acm_k8s/acm_service/README.md b/demos/acm_k8s/acm_service/README.md new file mode 100644 index 00000000..9525e3e1 --- /dev/null +++ b/demos/acm_k8s/acm_service/README.md @@ -0,0 +1,49 @@ +# OpenAPI generated server + +## Overview +This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This +is an example of building a OpenAPI-enabled Flask server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask. + +## Requirements +Python 3.5.2+ + +## Usage +To run the server, please execute the following from the root directory: + +``` +pip3 install -r requirements.txt +python3 -m openapi_server +``` + +and open your browser to here: + +``` +http://localhost:8080/ui/ +``` + +Your OpenAPI definition lives here: + +``` +http://localhost:8080/openapi.json +``` + +To launch the integration tests, use tox: +``` +sudo pip install tox +tox +``` + +## Running with Docker + +To run the server on a Docker container, please execute the following from the root directory: + +```bash +# building the image +docker build -t openapi_server . + +# starting up a container +docker run -p 8080:8080 openapi_server +``` diff --git a/demos/acm_k8s/acm_service/git_push.sh b/demos/acm_k8s/acm_service/git_push.sh new file mode 100644 index 00000000..f53a75d4 --- /dev/null +++ b/demos/acm_k8s/acm_service/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/demos/acm_k8s/acm_service/openapi_server/__init__.py b/demos/acm_k8s/acm_service/openapi_server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demos/acm_k8s/acm_service/openapi_server/__main__.py b/demos/acm_k8s/acm_service/openapi_server/__main__.py new file mode 100644 index 00000000..417a9020 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/__main__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +import connexion + +from openapi_server import encoder + + +def main(): + app = connexion.App(__name__, specification_dir="./openapi/") + app.app.json_encoder = encoder.JSONEncoder + app.add_api( + "openapi.yaml", + arguments={ + "title": "ACM API for cluster management - https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/apis/apis#tags" + }, + pythonic_params=True, + ) + + app.run(port=8080) + + +if __name__ == "__main__": + main() diff --git a/demos/acm_k8s/acm_service/openapi_server/controllers/__init__.py b/demos/acm_k8s/acm_service/openapi_server/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demos/acm_k8s/acm_service/openapi_server/controllers/controller.py b/demos/acm_k8s/acm_service/openapi_server/controllers/controller.py new file mode 100644 index 00000000..5b334a5d --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/controllers/controller.py @@ -0,0 +1,84 @@ +import connexion +from typing import Dict +from typing import Tuple +from typing import Union + +from openapi_server.models.managed_cluster import ManagedCluster # noqa: E501 +from openapi_server.models.metadata import Metadata # noqa: E501 +from openapi_server.models.spec import Spec # noqa: E501 +from openapi_server import util + +managed_cluster = { + "test1": ManagedCluster( + "cluster.open-cluster-management.io/v1", + "ManagedCluster", + Metadata("test1"), + Spec(True, None), + "test1 cluster in service", + ), +} + + +def cluster_open_cluster_management_io_v1_managedclusters_cluster_name_delete(): # noqa: E501 + """Delete a single cluster + + # noqa: E501 + + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + return "do some magic!" + + +def create_cluster( + api_version=None, kind=None, metadata=None, spec=None, status=None +): # noqa: E501 + """Create a cluster + + # noqa: E501 + + :param api_version: + :type api_version: str + :param kind: + :type kind: str + :param metadata: + :type metadata: dict | bytes + :param spec: + :type spec: dict | bytes + :param status: + :type status: str + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + if connexion.request.is_json: + metadata = Metadata.from_dict(connexion.request.get_json()) # noqa: E501 + if connexion.request.is_json: + spec = Spec.from_dict(connexion.request.get_json()) # noqa: E501 + return "do some magic!" + + +def get_cluster(cluster_name): # noqa: E501 + """Query a single cluster for more details + + # noqa: E501 + + :param cluster_name: The name of the cluster to retrieve + :type cluster_name: str + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + if cluster_name in managed_cluster: + return managed_cluster[cluster_name] + else: + return "Cluster not found", 404 + + +def list_managed_clusters(): # noqa: E501 + """Query your clusters for more details. + + # noqa: E501 + + + :rtype: Union[List[ManagedCluster], Tuple[List[ManagedCluster], int], Tuple[List[ManagedCluster], int, Dict[str, str]] + """ + return list(managed_cluster.values()) diff --git a/demos/acm_k8s/acm_service/openapi_server/controllers/default_controller.py b/demos/acm_k8s/acm_service/openapi_server/controllers/default_controller.py new file mode 100644 index 00000000..7984540a --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/controllers/default_controller.py @@ -0,0 +1,88 @@ +import connexion +from typing import Dict +from typing import Tuple +from typing import Union + +from openapi_server.models.managed_cluster import ManagedCluster # noqa: E501 +from openapi_server.models.metadata import Metadata # noqa: E501 +from openapi_server.models.spec import Spec # noqa: E501 +from openapi_server import util + +managed_cluster = { + "test1": ManagedCluster( + "cluster.open-cluster-management.io/v1", + "ManagedCluster", + Metadata("test1"), + Spec(True, None), + "test1 cluster in service", + ), +} + + +def cluster_open_cluster_management_io_v1_managedclusters_cluster_name_delete(): # noqa: E501 + """Delete a single cluster + + # noqa: E501 + + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + return "do some magic!" + + +def create_cluster( + api_version=None, kind=None, metadata=None, spec=None, status=None +): # noqa: E501 + """Create a cluster + + # noqa: E501 + + :param api_version: + :type api_version: str + :param kind: + :type kind: str + :param metadata: + :type metadata: dict | bytes + :param spec: + :type spec: dict | bytes + :param status: + :type status: str + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + if connexion.request.is_json: + request_json = connexion.request.get_json() + if "metadata" in request_json: + metadata = Metadata.from_dict(request_json["metadata"]) # noqa: E501 + if "spec" in request_json: + spec = Spec.from_dict(request_json["spec"]) # noqa: E501 + cluster = ManagedCluster(api_version, kind, metadata, spec, status) + managed_cluster[metadata.name] = cluster + return "cluster created", 200 + + +def get_cluster(cluster_name): # noqa: E501 + """Query a single cluster for more details + + # noqa: E501 + + :param cluster_name: The name of the cluster to retrieve + :type cluster_name: str + + :rtype: Union[ManagedCluster, Tuple[ManagedCluster, int], Tuple[ManagedCluster, int, Dict[str, str]] + """ + if cluster_name in managed_cluster: + return managed_cluster[cluster_name] + else: + return "Cluster not found", 404 + + +def list_managed_clusters(): # noqa: E501 + """Query your clusters for more details. + + # noqa: E501 + + + :rtype: Union[List[ManagedCluster], Tuple[List[ManagedCluster], int], Tuple[List[ManagedCluster], int, Dict[str, str]] + """ + return list(managed_cluster.values()) diff --git a/demos/acm_k8s/acm_service/openapi_server/controllers/security_controller.py b/demos/acm_k8s/acm_service/openapi_server/controllers/security_controller.py new file mode 100644 index 00000000..7d686cba --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/controllers/security_controller.py @@ -0,0 +1 @@ +from typing import List diff --git a/demos/acm_k8s/acm_service/openapi_server/encoder.py b/demos/acm_k8s/acm_service/openapi_server/encoder.py new file mode 100644 index 00000000..60f4fa67 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/encoder.py @@ -0,0 +1,19 @@ +from connexion.apps.flask_app import FlaskJSONEncoder + +from openapi_server.models.base_model import Model + + +class JSONEncoder(FlaskJSONEncoder): + include_nulls = False + + def default(self, o): + if isinstance(o, Model): + dikt = {} + for attr in o.openapi_types: + value = getattr(o, attr) + if value is None and not self.include_nulls: + continue + attr = o.attribute_map[attr] + dikt[attr] = value + return dikt + return FlaskJSONEncoder.default(self, o) diff --git a/demos/acm_k8s/acm_service/openapi_server/models/__init__.py b/demos/acm_k8s/acm_service/openapi_server/models/__init__.py new file mode 100644 index 00000000..a4294dbb --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/__init__.py @@ -0,0 +1,8 @@ +# flake8: noqa +# import models into model package +from openapi_server.models.managed_cluster import ManagedCluster +from openapi_server.models.managed_cluster_client_config import ( + ManagedClusterClientConfig, +) +from openapi_server.models.metadata import Metadata +from openapi_server.models.spec import Spec diff --git a/demos/acm_k8s/acm_service/openapi_server/models/base_model.py b/demos/acm_k8s/acm_service/openapi_server/models/base_model.py new file mode 100644 index 00000000..c6144bed --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/base_model.py @@ -0,0 +1,70 @@ +import pprint + +import typing + +from openapi_server import util + +T = typing.TypeVar("T") + + +class Model: + # openapiTypes: The key is attribute name and the + # value is attribute type. + openapi_types: typing.Dict[str, type] = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map: typing.Dict[str, str] = {} + + @classmethod + def from_dict(cls: typing.Type[T], dikt) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr in self.openapi_types: + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list( + map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value) + ) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict( + map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") + else item, + value.items(), + ) + ) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster.py b/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster.py new file mode 100644 index 00000000..be68653d --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster.py @@ -0,0 +1,186 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from openapi_server.models.base_model import Model +from openapi_server.models.metadata import Metadata +from openapi_server.models.spec import Spec +from openapi_server import util + +from openapi_server.models.metadata import Metadata # noqa: E501 +from openapi_server.models.spec import Spec # noqa: E501 + + +class ManagedCluster(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__( + self, api_version=None, kind=None, metadata=None, spec=None, status=None + ): # noqa: E501 + """ManagedCluster - a model defined in OpenAPI + + :param api_version: The api_version of this ManagedCluster. # noqa: E501 + :type api_version: str + :param kind: The kind of this ManagedCluster. # noqa: E501 + :type kind: str + :param metadata: The metadata of this ManagedCluster. # noqa: E501 + :type metadata: Metadata + :param spec: The spec of this ManagedCluster. # noqa: E501 + :type spec: Spec + :param status: The status of this ManagedCluster. # noqa: E501 + :type status: str + """ + self.openapi_types = { + "api_version": str, + "kind": str, + "metadata": Metadata, + "spec": Spec, + "status": str, + } + + self.attribute_map = { + "api_version": "apiVersion", + "kind": "kind", + "metadata": "metadata", + "spec": "spec", + "status": "status", + } + + self._api_version = api_version + self._kind = kind + self._metadata = metadata + self._spec = spec + self._status = status + + @classmethod + def from_dict(cls, dikt) -> "ManagedCluster": + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The ManagedCluster of this ManagedCluster. # noqa: E501 + :rtype: ManagedCluster + """ + return util.deserialize_model(dikt, cls) + + @property + def api_version(self) -> str: + """Gets the api_version of this ManagedCluster. + + + :return: The api_version of this ManagedCluster. + :rtype: str + """ + return self._api_version + + @api_version.setter + def api_version(self, api_version: str): + """Sets the api_version of this ManagedCluster. + + + :param api_version: The api_version of this ManagedCluster. + :type api_version: str + """ + allowed_values = ["cluster.open-cluster-management.io/v1"] # noqa: E501 + if api_version not in allowed_values: + raise ValueError( + "Invalid value for `api_version` ({0}), must be one of {1}".format( + api_version, allowed_values + ) + ) + + self._api_version = api_version + + @property + def kind(self) -> str: + """Gets the kind of this ManagedCluster. + + + :return: The kind of this ManagedCluster. + :rtype: str + """ + return self._kind + + @kind.setter + def kind(self, kind: str): + """Sets the kind of this ManagedCluster. + + + :param kind: The kind of this ManagedCluster. + :type kind: str + """ + allowed_values = ["ManagedCluster"] # noqa: E501 + if kind not in allowed_values: + raise ValueError( + "Invalid value for `kind` ({0}), must be one of {1}".format( + kind, allowed_values + ) + ) + + self._kind = kind + + @property + def metadata(self) -> Metadata: + """Gets the metadata of this ManagedCluster. + + + :return: The metadata of this ManagedCluster. + :rtype: Metadata + """ + return self._metadata + + @metadata.setter + def metadata(self, metadata: Metadata): + """Sets the metadata of this ManagedCluster. + + + :param metadata: The metadata of this ManagedCluster. + :type metadata: Metadata + """ + + self._metadata = metadata + + @property + def spec(self) -> Spec: + """Gets the spec of this ManagedCluster. + + + :return: The spec of this ManagedCluster. + :rtype: Spec + """ + return self._spec + + @spec.setter + def spec(self, spec: Spec): + """Sets the spec of this ManagedCluster. + + + :param spec: The spec of this ManagedCluster. + :type spec: Spec + """ + + self._spec = spec + + @property + def status(self) -> str: + """Gets the status of this ManagedCluster. + + + :return: The status of this ManagedCluster. + :rtype: str + """ + return self._status + + @status.setter + def status(self, status: str): + """Sets the status of this ManagedCluster. + + + :param status: The status of this ManagedCluster. + :type status: str + """ + + self._status = status diff --git a/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster_client_config.py b/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster_client_config.py new file mode 100644 index 00000000..bf362e05 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/managed_cluster_client_config.py @@ -0,0 +1,81 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from openapi_server.models.base_model import Model +from openapi_server import util + + +class ManagedClusterClientConfig(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, url=None, ca_bundle=None): # noqa: E501 + """ManagedClusterClientConfig - a model defined in OpenAPI + + :param url: The url of this ManagedClusterClientConfig. # noqa: E501 + :type url: str + :param ca_bundle: The ca_bundle of this ManagedClusterClientConfig. # noqa: E501 + :type ca_bundle: str + """ + self.openapi_types = {"url": str, "ca_bundle": str} + + self.attribute_map = {"url": "url", "ca_bundle": "caBundle"} + + self._url = url + self._ca_bundle = ca_bundle + + @classmethod + def from_dict(cls, dikt) -> "ManagedClusterClientConfig": + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The ManagedClusterClientConfig of this ManagedClusterClientConfig. # noqa: E501 + :rtype: ManagedClusterClientConfig + """ + return util.deserialize_model(dikt, cls) + + @property + def url(self) -> str: + """Gets the url of this ManagedClusterClientConfig. + + + :return: The url of this ManagedClusterClientConfig. + :rtype: str + """ + return self._url + + @url.setter + def url(self, url: str): + """Sets the url of this ManagedClusterClientConfig. + + + :param url: The url of this ManagedClusterClientConfig. + :type url: str + """ + + self._url = url + + @property + def ca_bundle(self) -> str: + """Gets the ca_bundle of this ManagedClusterClientConfig. + + + :return: The ca_bundle of this ManagedClusterClientConfig. + :rtype: str + """ + return self._ca_bundle + + @ca_bundle.setter + def ca_bundle(self, ca_bundle: str): + """Sets the ca_bundle of this ManagedClusterClientConfig. + + + :param ca_bundle: The ca_bundle of this ManagedClusterClientConfig. + :type ca_bundle: str + """ + + self._ca_bundle = ca_bundle diff --git a/demos/acm_k8s/acm_service/openapi_server/models/metadata.py b/demos/acm_k8s/acm_service/openapi_server/models/metadata.py new file mode 100644 index 00000000..a2833d6f --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/metadata.py @@ -0,0 +1,81 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from openapi_server.models.base_model import Model +from openapi_server import util + + +class Metadata(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, name=None, labels=None): # noqa: E501 + """Metadata - a model defined in OpenAPI + + :param name: The name of this Metadata. # noqa: E501 + :type name: str + :param labels: The labels of this Metadata. # noqa: E501 + :type labels: Dict[str, str] + """ + self.openapi_types = {"name": str, "labels": Dict[str, str]} + + self.attribute_map = {"name": "name", "labels": "labels"} + + self._name = name + self._labels = labels + + @classmethod + def from_dict(cls, dikt) -> "Metadata": + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Metadata of this Metadata. # noqa: E501 + :rtype: Metadata + """ + return util.deserialize_model(dikt, cls) + + @property + def name(self) -> str: + """Gets the name of this Metadata. + + + :return: The name of this Metadata. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this Metadata. + + + :param name: The name of this Metadata. + :type name: str + """ + + self._name = name + + @property + def labels(self) -> Dict[str, str]: + """Gets the labels of this Metadata. + + + :return: The labels of this Metadata. + :rtype: Dict[str, str] + """ + return self._labels + + @labels.setter + def labels(self, labels: Dict[str, str]): + """Sets the labels of this Metadata. + + + :param labels: The labels of this Metadata. + :type labels: Dict[str, str] + """ + + self._labels = labels diff --git a/demos/acm_k8s/acm_service/openapi_server/models/spec.py b/demos/acm_k8s/acm_service/openapi_server/models/spec.py new file mode 100644 index 00000000..b319c461 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/models/spec.py @@ -0,0 +1,98 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from openapi_server.models.base_model import Model +from openapi_server.models.managed_cluster_client_config import ( + ManagedClusterClientConfig, +) +from openapi_server import util + +from openapi_server.models.managed_cluster_client_config import ( + ManagedClusterClientConfig, +) # noqa: E501 + + +class Spec(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__( + self, hub_accepts_client=None, managed_cluster_client_configs=None + ): # noqa: E501 + """Spec - a model defined in OpenAPI + + :param hub_accepts_client: The hub_accepts_client of this Spec. # noqa: E501 + :type hub_accepts_client: bool + :param managed_cluster_client_configs: The managed_cluster_client_configs of this Spec. # noqa: E501 + :type managed_cluster_client_configs: List[ManagedClusterClientConfig] + """ + self.openapi_types = { + "hub_accepts_client": bool, + "managed_cluster_client_configs": List[ManagedClusterClientConfig], + } + + self.attribute_map = { + "hub_accepts_client": "hubAcceptsClient", + "managed_cluster_client_configs": "managedClusterClientConfigs", + } + + self._hub_accepts_client = hub_accepts_client + self._managed_cluster_client_configs = managed_cluster_client_configs + + @classmethod + def from_dict(cls, dikt) -> "Spec": + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Spec of this Spec. # noqa: E501 + :rtype: Spec + """ + return util.deserialize_model(dikt, cls) + + @property + def hub_accepts_client(self) -> bool: + """Gets the hub_accepts_client of this Spec. + + + :return: The hub_accepts_client of this Spec. + :rtype: bool + """ + return self._hub_accepts_client + + @hub_accepts_client.setter + def hub_accepts_client(self, hub_accepts_client: bool): + """Sets the hub_accepts_client of this Spec. + + + :param hub_accepts_client: The hub_accepts_client of this Spec. + :type hub_accepts_client: bool + """ + + self._hub_accepts_client = hub_accepts_client + + @property + def managed_cluster_client_configs(self) -> List[ManagedClusterClientConfig]: + """Gets the managed_cluster_client_configs of this Spec. + + + :return: The managed_cluster_client_configs of this Spec. + :rtype: List[ManagedClusterClientConfig] + """ + return self._managed_cluster_client_configs + + @managed_cluster_client_configs.setter + def managed_cluster_client_configs( + self, managed_cluster_client_configs: List[ManagedClusterClientConfig] + ): + """Sets the managed_cluster_client_configs of this Spec. + + + :param managed_cluster_client_configs: The managed_cluster_client_configs of this Spec. + :type managed_cluster_client_configs: List[ManagedClusterClientConfig] + """ + + self._managed_cluster_client_configs = managed_cluster_client_configs diff --git a/demos/acm_k8s/acm_service/openapi_server/openapi/openapi.yaml b/demos/acm_k8s/acm_service/openapi_server/openapi/openapi.yaml new file mode 100644 index 00000000..61202a34 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/openapi/openapi.yaml @@ -0,0 +1,162 @@ +openapi: 3.0.0 +info: + contact: + email: support@katanemo.com + name: Katanemo Labs Inc. + url: https://katanemo.com + title: ACM API for cluster management - https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/apis/apis#tags + version: 2.12.0 +servers: +- url: / +paths: + /cluster.open-cluster-management.io/v1/managedclusters: + get: + operationId: list_managed_clusters + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ManagedCluster' + type: array + description: List of managed clusters + summary: Query your clusters for more details. + x-openapi-router-controller: openapi_server.controllers.default_controller + post: + operationId: create_cluster + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ManagedCluster' + description: "Details about the service, including the text-representation\ + \ of the service APIs." + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ManagedCluster' + description: cluster created successfully + summary: Create a cluster + x-openapi-router-controller: openapi_server.controllers.default_controller + /cluster.open-cluster-management.io/v1/managedclusters/{cluster_name}: + delete: + operationId: cluster_open_cluster_management_io_v1_managedclusters_cluster_name_delete + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ManagedCluster' + description: Cluster details + summary: Delete a single cluster + x-openapi-router-controller: openapi_server.controllers.default_controller + get: + operationId: get_cluster + parameters: + - description: The name of the cluster to retrieve + explode: false + in: path + name: cluster_name + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ManagedCluster' + description: Cluster details + summary: Query a single cluster for more details + x-openapi-router-controller: openapi_server.controllers.default_controller +components: + schemas: + ManagedCluster: + example: + metadata: + name: name + labels: + key: labels + apiVersion: cluster.open-cluster-management.io/v1 + kind: ManagedCluster + spec: + hubAcceptsClient: true + managedClusterClientConfigs: + - caBundle: caBundle + url: url + - caBundle: caBundle + url: url + status: status + properties: + apiVersion: + enum: + - cluster.open-cluster-management.io/v1 + title: apiVersion + type: string + kind: + enum: + - ManagedCluster + title: kind + type: string + metadata: + $ref: '#/components/schemas/Metadata' + spec: + $ref: '#/components/schemas/Spec' + status: + title: status + type: string + title: managed cluster details + type: object + Metadata: + example: + name: name + labels: + key: labels + properties: + name: + title: name + type: string + labels: + additionalProperties: + type: string + title: labels + type: object + title: metadata details + type: object + Spec: + example: + hubAcceptsClient: true + managedClusterClientConfigs: + - caBundle: caBundle + url: url + - caBundle: caBundle + url: url + properties: + hubAcceptsClient: + title: hubAcceptsClient + type: boolean + managedClusterClientConfigs: + items: + $ref: '#/components/schemas/ManagedClusterClientConfig' + title: managedClusterClientConfigs + type: array + title: spec details + type: object + ManagedClusterClientConfig: + example: + caBundle: caBundle + url: url + properties: + url: + title: url + type: string + caBundle: + title: caBundle + type: string + title: managed cluster client config details + type: object diff --git a/demos/acm_k8s/acm_service/openapi_server/test/__init__.py b/demos/acm_k8s/acm_service/openapi_server/test/__init__.py new file mode 100644 index 00000000..aefd85c2 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/test/__init__.py @@ -0,0 +1,15 @@ +import logging + +import connexion +from flask_testing import TestCase + +from openapi_server.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + def create_app(self): + logging.getLogger("connexion.operation").setLevel("ERROR") + app = connexion.App(__name__, specification_dir="../openapi/") + app.app.json_encoder = JSONEncoder + app.add_api("openapi.yaml", pythonic_params=True) + return app.app diff --git a/demos/acm_k8s/acm_service/openapi_server/test/test_default_controller.py b/demos/acm_k8s/acm_service/openapi_server/test/test_default_controller.py new file mode 100644 index 00000000..2a669114 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/test/test_default_controller.py @@ -0,0 +1,89 @@ +import unittest + +from flask import json + +from openapi_server.models.managed_cluster import ManagedCluster # noqa: E501 +from openapi_server.models.metadata import Metadata # noqa: E501 +from openapi_server.models.spec import Spec # noqa: E501 +from openapi_server.test import BaseTestCase + + +class TestDefaultController(BaseTestCase): + """DefaultController integration test stubs""" + + def test_cluster_open_cluster_management_io_v1_managedclusters_cluster_name_delete( + self, + ): + """Test case for cluster_open_cluster_management_io_v1_managedclusters_cluster_name_delete + + Delete a single cluster + """ + headers = { + "Accept": "application/json", + } + response = self.client.open( + "/cluster.open-cluster-management.io/v1/managedclusters/{cluster_name}", + method="DELETE", + headers=headers, + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + + @unittest.skip("multipart/form-data not supported by Connexion") + def test_create_cluster(self): + """Test case for create_cluster + + Create a cluster + """ + headers = { + "Accept": "application/json", + "Content-Type": "multipart/form-data", + } + data = dict( + api_version="api_version_example", + kind="kind_example", + metadata=openapi_server.Metadata(), + spec=openapi_server.Spec(), + status="status_example", + ) + response = self.client.open( + "/cluster.open-cluster-management.io/v1/managedclusters", + method="POST", + headers=headers, + data=data, + content_type="multipart/form-data", + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + + def test_get_cluster(self): + """Test case for get_cluster + + Query a single cluster for more details + """ + headers = { + "Accept": "application/json", + } + response = self.client.open( + "/cluster.open-cluster-management.io/v1/managedclusters/{cluster_name}", + method="GET", + headers=headers, + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + + def test_list_managed_clusters(self): + """Test case for list_managed_clusters + + Query your clusters for more details. + """ + headers = { + "Accept": "application/json", + } + response = self.client.open( + "/cluster.open-cluster-management.io/v1/managedclusters", + method="GET", + headers=headers, + ) + self.assert200(response, "Response body is : " + response.data.decode("utf-8")) + + +if __name__ == "__main__": + unittest.main() diff --git a/demos/acm_k8s/acm_service/openapi_server/typing_utils.py b/demos/acm_k8s/acm_service/openapi_server/typing_utils.py new file mode 100644 index 00000000..c905b1db --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/typing_utils.py @@ -0,0 +1,30 @@ +import sys + +if sys.version_info < (3, 7): + import typing + + def is_generic(klass): + """Determine whether klass is a generic class""" + return type(klass) == typing.GenericMeta + + def is_dict(klass): + """Determine whether klass is a Dict""" + return klass.__extra__ == dict + + def is_list(klass): + """Determine whether klass is a List""" + return klass.__extra__ == list + +else: + + def is_generic(klass): + """Determine whether klass is a generic class""" + return hasattr(klass, "__origin__") + + def is_dict(klass): + """Determine whether klass is a Dict""" + return klass.__origin__ == dict + + def is_list(klass): + """Determine whether klass is a List""" + return klass.__origin__ == list diff --git a/demos/acm_k8s/acm_service/openapi_server/util.py b/demos/acm_k8s/acm_service/openapi_server/util.py new file mode 100644 index 00000000..182c4085 --- /dev/null +++ b/demos/acm_k8s/acm_service/openapi_server/util.py @@ -0,0 +1,149 @@ +import datetime + +import typing +from openapi_server import typing_utils + + +def _deserialize(data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in (int, float, str, bool, bytearray): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif typing_utils.is_generic(klass): + if typing_utils.is_list(klass): + return _deserialize_list(data, klass.__args__[0]) + if typing_utils.is_dict(klass): + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = data + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + if string is None: + return None + + try: + from dateutil.parser import parse + + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + if string is None: + return None + + try: + from dateutil.parser import parse + + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.openapi_types: + return data + + for attr, attr_type in instance.openapi_types.items(): + if ( + data is not None + and instance.attribute_map[attr] in data + and isinstance(data, (list, dict)) + ): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) for k, v in data.items()} diff --git a/demos/acm_k8s/acm_service/requirements.txt b/demos/acm_k8s/acm_service/requirements.txt new file mode 100644 index 00000000..2cb06891 --- /dev/null +++ b/demos/acm_k8s/acm_service/requirements.txt @@ -0,0 +1,13 @@ +connexion[swagger-ui] >= 2.6.0; python_version>="3.6" +# 2.3 is the last version that supports python 3.4-3.5 +connexion[swagger-ui] <= 2.3.0; python_version=="3.5" or python_version=="3.4" +# prevent breaking dependencies from advent of connexion>=3.0 +connexion[swagger-ui] <= 2.14.2; python_version>"3.4" +# connexion requires werkzeug but connexion < 2.4.0 does not install werkzeug +# we must peg werkzeug versions below to fix connexion +# https://github.com/zalando/connexion/pull/1044 +werkzeug == 0.16.1; python_version=="3.5" or python_version=="3.4" +swagger-ui-bundle >= 0.0.2 +python_dateutil >= 2.6.0 +setuptools >= 21.0.0 +Flask == 2.1.1 diff --git a/demos/acm_k8s/acm_service/setup.py b/demos/acm_k8s/acm_service/setup.py new file mode 100644 index 00000000..6eb19f15 --- /dev/null +++ b/demos/acm_k8s/acm_service/setup.py @@ -0,0 +1,34 @@ +import sys +from setuptools import setup, find_packages + +NAME = "openapi_server" +VERSION = "1.0.0" + +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["connexion>=2.0.2", "swagger-ui-bundle>=0.0.2", "python_dateutil>=2.6.0"] + +setup( + name=NAME, + version=VERSION, + description="ACM API for cluster management - https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/apis/apis#tags", + author_email="support@katanemo.com", + url="", + keywords=[ + "OpenAPI", + "ACM API for cluster management - https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/apis/apis#tags", + ], + install_requires=REQUIRES, + packages=find_packages(), + package_data={"": ["openapi/openapi.yaml"]}, + include_package_data=True, + entry_points={"console_scripts": ["openapi_server=openapi_server.__main__:main"]}, + long_description="""\ + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + """, +) diff --git a/demos/acm_k8s/acm_service/test-requirements.txt b/demos/acm_k8s/acm_service/test-requirements.txt new file mode 100644 index 00000000..58f51d6a --- /dev/null +++ b/demos/acm_k8s/acm_service/test-requirements.txt @@ -0,0 +1,4 @@ +pytest~=7.1.0 +pytest-cov>=2.8.1 +pytest-randomly>=1.2.3 +Flask-Testing==0.8.1 diff --git a/demos/acm_k8s/acm_service/tox.ini b/demos/acm_k8s/acm_service/tox.ini new file mode 100644 index 00000000..7663dfb6 --- /dev/null +++ b/demos/acm_k8s/acm_service/tox.ini @@ -0,0 +1,11 @@ +[tox] +envlist = py3 +skipsdist=True + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + {toxinidir} + +commands= + pytest --cov=openapi_server diff --git a/demos/acm_k8s/arch_config.yaml b/demos/acm_k8s/arch_config.yaml new file mode 100644 index 00000000..c7d315f8 --- /dev/null +++ b/demos/acm_k8s/arch_config.yaml @@ -0,0 +1,67 @@ +version: "0.1-beta" + +listener: + address: 0.0.0.0 + port: 10000 + message_format: huggingface + connect_timeout: 0.005s + +overrides: + # confidence threshold for prompt target intent matching + prompt_target_intent_matching_threshold: 0.6 + +endpoints: + acm_service: + endpoint: host.docker.internal:18083 + connect_timeout: 0.005s + +llm_providers: + - name: gpt-4o-mini + access_key: $OPENAI_API_KEY + provider: openai + model: gpt-4o-mini + default: true + + - name: gpt-3.5-turbo-0125 + access_key: $OPENAI_API_KEY + provider: openai + model: gpt-3.5-turbo-0125 + + - name: gpt-4o + access_key: $OPENAI_API_KEY + provider: openai + model: gpt-4o + +system_prompt: | + You are a helpful assistant. + +prompt_guards: + input_guards: + jailbreak: + on_exception: + message: Looks like you're curious about my abilities, but I can only provide assistance for weather forecasting. + +prompt_targets: + - name: listManagedClusters + description: Query your clusters for more details. + http_method: GET + endpoint: + name: acm_service + path: /cluster.open-cluster-management.io/v1/managedclusters + + - name: getCluster + description: Query a single cluster for more details + http_method: GET + endpoint: + name: acm_service + path: /cluster.open-cluster-management.io/v1/managedclusters/{cluster_name} + parameters: + - name: cluster_name + in_path: true + description: The name of the cluster to retrieve + required: true + type: str + +tracing: + random_sampling: 100 + trace_arch_internal: true diff --git a/demos/acm_k8s/docker-compose.yaml b/demos/acm_k8s/docker-compose.yaml new file mode 100644 index 00000000..21884159 --- /dev/null +++ b/demos/acm_k8s/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + acm_service: + build: + context: acm_service/ + environment: + - OLTP_HOST=http://jaeger:4317 + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "18083:8080" + + chatbot_ui: + build: + context: ../shared/chatbot_ui + ports: + - "18080:8080" + environment: + # this is only because we are running the sample app in the same docker container environemtn as archgw + - CHAT_COMPLETION_ENDPOINT=http://host.docker.internal:10000/v1 + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./arch_config.yaml:/app/arch_config.yaml + + jaeger: + build: + context: ../shared/jaeger + ports: + - "16686:16686" + - "4317:4317" + - "4318:4318" diff --git a/demos/acm_k8s/generate_acm_service_stub.sh b/demos/acm_k8s/generate_acm_service_stub.sh new file mode 100644 index 00000000..a3ead749 --- /dev/null +++ b/demos/acm_k8s/generate_acm_service_stub.sh @@ -0,0 +1,6 @@ +docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:latest generate \ + --skip-validate-spec \ + -i /local/acm_api.yaml \ + -g python-flask \ + -o /local/acm_server \ + # --additional-properties=defaultController=your.package.YourController \ diff --git a/demos/acm_k8s/petstore.yml b/demos/acm_k8s/petstore.yml new file mode 100644 index 00000000..853db5e2 --- /dev/null +++ b/demos/acm_k8s/petstore.yml @@ -0,0 +1,1206 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.swagger.io/v2' +info: + description: >- + This is a sample server Petstore server. For this sample, you can use the api key + `special-key` to test the authorization filters. + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - http_auth: [] + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: true # change to true for testing purpose + deprecated: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + # comment out for testing purpose + #security: + # - petstore_auth: + # - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - BearerToken: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + #security: + # - petstore_auth: + # - 'write:pets' + # - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet_header_test': + get: + tags: + - pet + summary: Header test + description: Header test + operationId: test_header + x-streaming: true + parameters: + - name: header_test_int + in: header + description: header test int + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + '/pet/{petId}?streaming': + get: + tags: + - pet + summary: Find pet by ID (streaming) + description: Returns a single pet + operationId: getPetByIdStreaming + x-streaming: true + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generate exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] + /fake/path_array/{path_array}/testing: + get: + tags: + - fake + summary: test array parameter in path + description: '' + operationId: fake_path_array + parameters: + - name: path_array + in: path + description: dummy path parameter + required: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + /fake/regular_expression: + get: + tags: + - fake + summary: test regular expression to ensure no exception + description: '' + operationId: fake_regular-expression + parameters: + - name: reg_exp_test + in: header + description: dummy required parameter + required: true + schema: + type: string + pattern: /^[A-Za-z0-9_]{1,15}$/ + responses: + '200': + description: successful operation + /fake/data_file: + get: + tags: + - fake + summary: test data_file to ensure it's escaped correctly + description: '' + operationId: fake_data_file + parameters: + - name: dummy + in: header + description: dummy required parameter + required: true + schema: + type: string + - name: data_file + in: header + description: header data file + required: false + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + /fake/set_query_parameter: + get: + tags: + - fake + summary: test set query parameter + description: '' + operationId: fake_set_query + parameters: + - name: set_dummy + in: query + description: set query + required: true + schema: + type: array + uniqueItems: true + items: + type: string + - name: array_dummy + in: query + description: array query + required: true + schema: + type: array + uniqueItems: false + items: + type: string + responses: + '200': + description: successful operation + /fake/test_optional_body_parameter: + post: + tags: + - fake + summary: Add a new pet to the store (optional body) + description: '' + operationId: addPetOptional + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - http_auth: [] + requestBody: + $ref: '#/components/requestBodies/OptionalPet' +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + multipart/related: # message with binary body part + schema: + type: object + properties: # Request parts + jsonData: + $ref: '#/components/schemas/Pet' + binaryDataN2Information: + type: string + format: binary + encoding: + jsonData: + contentType: application/json + binaryDataN2Information: + contentType: application/vnd.3gpp.ngap + headers: + Content-Id: + schema: + type: string + description: Pet object that needs to be added to the store + required: true + OptionalPet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + multipart/related: # message with binary body part + schema: + type: object + properties: # Request parts + jsonData: + $ref: '#/components/schemas/Pet' + binaryDataN2Information: + type: string + format: binary + encoding: + jsonData: + contentType: application/json + binaryDataN2Information: + contentType: application/vnd.3gpp.ngap + headers: + Content-Id: + schema: + type: string + description: Pet object that needs to be added to the store + required: false + securitySchemes: + http_auth: + type : http + scheme : basic + BearerToken: + type : http + scheme : bearer + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + deprecated: true + enum: + - available + - pending + - sold + xml: + name: Pet + PetMap: + title: a PetMap + description: A mock map of a pet and some properties + type: object + properties: + pet: + type: object + additionalProperties: + type: string + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + Special: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + set_test: + uniqueItems: true + type: array + items: + type: string + self: + type: integer + format: int32 + private: + type: string + super: + type: string + "123_number": + type: string + "array[test]": + type: string + "": + type: string + Dog: + allOf: + - $ref: '#/components/schemas/Animal' + - type: object + properties: + breed: + type: string + Cat: + allOf: + - $ref: '#/components/schemas/Animal' + - $ref: '#/components/schemas/Address' + - type: object + properties: + declawed: + type: boolean + Address: + type: object + additionalProperties: + type: integer + Animal: + type: object + discriminator: + propertyName: className + required: + - className + properties: + className: + type: string + color: + type: string + default: red + allof_tag_api_response: + allOf: + - $ref: '#/components/schemas/Tag' + - $ref: '#/components/schemas/ApiResponse' + AnyOfPig: + anyOf: + - $ref: '#/components/schemas/BasquePig' + - $ref: '#/components/schemas/DanishPig' + Pig: + oneOf: + - $ref: '#/components/schemas/BasquePig' + - $ref: '#/components/schemas/DanishPig' + discriminator: + propertyName: className + BasquePig: + type: object + properties: + className: + type: string + color: + type: string + required: + - className + - color + DanishPig: + type: object + properties: + className: + type: string + size: + type: integer + required: + - className + - size + NestedOneOf: + type: object + properties: + size: + type: integer + nested_pig: + $ref: '#/components/schemas/Pig' + OneOfPrimitiveTypeTest: + oneOf: + - type: "integer" + - type: "string" + AnyOfPrimitiveTypeTest: + oneOf: + - type: "integer" + - type: "string" + mammal: + oneOf: + - $ref: '#/components/schemas/whale' + - $ref: '#/components/schemas/zebra' + discriminator: + propertyName: className + mapping: + whale: '#/components/schemas/whale' + zebra: '#/components/schemas/zebra' + whale: + type: object + properties: + hasBaleen: + type: boolean + hasTeeth: + type: boolean + className: + type: string + required: + - className + zebra: + type: object + properties: + type: + type: string + enum: + - plains + - mountain + - grevys + className: + type: string + required: + - className + Date: + description: to test the model name `Date` + type: object + properties: + className: + type: string + percent_description: + description: using % in the description + type: string + url_property: + type: string + format: uri + required: + - className + - url_property + dummy_model: + description: to test the model name mapping + type: object + properties: + property: + type: string + format_test: + type: object + required: + - number + - byte + - date + - password + properties: + integer: + type: integer + maximum: 100 + minimum: 10 + int32: + type: integer + format: int32 + maximum: 200 + minimum: 20 + int64: + type: integer + format: int64 + number: + maximum: 543.2 + minimum: 32.1 + type: number + float: + type: number + format: float + maximum: 987.6 + minimum: 54.3 + double: + type: number + format: double + maximum: 123.4 + minimum: 67.8 + string: + type: string + pattern: '/[a-z]/i' + byte: + type: string + format: byte + binary: + type: string + format: binary + date: + type: string + format: date + default: 2019-07-19 + dateTime: + type: string + format: date-time + default: 2015-10-28T14:38:02Z + uuid: + type: string + format: uuid + example: 72f98069-206d-4f12-9f12-3d1e525a8e84 + password: + type: string + format: password + maxLength: 64 + minLength: 10 + pattern_with_digits: + description: A string that is a 10 digit number. Can have leading zeros. + type: string + pattern: '^\d{10}$' + pattern_with_digits_and_delimiter: + description: A string starting with 'image_' (case insensitive) and one to three digits following i.e. Image_01. + type: string + pattern: '/^image_\d{1,3}$/i' diff --git a/demos/acm_k8s/run_demo.sh b/demos/acm_k8s/run_demo.sh new file mode 100644 index 00000000..e6c678e8 --- /dev/null +++ b/demos/acm_k8s/run_demo.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +# Function to start the demo +start_demo() { + # Step 1: Check if .env file exists + if [ -f ".env" ]; then + echo ".env file already exists. Skipping creation." + else + # Step 2: Create `.env` file and set OpenAI key + if [ -z "$OPENAI_API_KEY" ]; then + echo "Error: OPENAI_API_KEY environment variable is not set for the demo." + exit 1 + fi + + echo "Creating .env file..." + echo "OPENAI_API_KEY=$OPENAI_API_KEY" > .env + echo ".env file created with OPENAI_API_KEY." + fi + + # Step 3: Start Arch + echo "Starting Arch with arch_config.yaml..." + archgw up arch_config.yaml + + # Step 4: Start Network Agent + echo "Starting Network Agent using Docker Compose..." + docker compose up -d # Run in detached mode +} + +# Function to stop the demo +stop_demo() { + # Step 1: Stop Docker Compose services + echo "Stopping Network Agent using Docker Compose..." + docker compose down + + # Step 2: Stop Arch + echo "Stopping Arch..." + archgw down +} + +# Main script logic +if [ "$1" == "down" ]; then + stop_demo +else + # Default action is to bring the demo up + start_demo +fi diff --git a/model_server/app/main.py b/model_server/app/main.py index 43be1f74..4f9337bd 100644 --- a/model_server/app/main.py +++ b/model_server/app/main.py @@ -220,6 +220,12 @@ async def hallucination(req: HallucinationRequest, res: Response): if "messages" in req.parameters: req.parameters.pop("messages") + if not req.parameters or len(req.parameters) == 0: + return { + "params_scores": {}, + "model": req.model, + } + candidate_labels = {f"{k} is {v}": k for k, v in req.parameters.items()} predictions = classifier(