diff --git a/tembo-cli/Cargo.lock b/tembo-cli/Cargo.lock index 20f73f727..e2edc2053 100644 --- a/tembo-cli/Cargo.lock +++ b/tembo-cli/Cargo.lock @@ -3741,6 +3741,7 @@ dependencies = [ "spinners", "sqlx", "temboclient", + "tembodataclient", "tera", "tokio", "toml", @@ -3759,6 +3760,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "tembodataclient" +version = "0.0.1" +dependencies = [ + "reqwest", + "serde", + "serde_derive", + "serde_json", + "url", + "uuid", +] + [[package]] name = "tempfile" version = "3.8.0" diff --git a/tembo-cli/Cargo.toml b/tembo-cli/Cargo.toml index 846aa8930..12bda7c32 100644 --- a/tembo-cli/Cargo.toml +++ b/tembo-cli/Cargo.toml @@ -1,4 +1,4 @@ -workspace = { members = ["temboclient"] } +workspace = { members = ["temboclient", "tembodataclient"] } [package] name = "tembo-cli" version = "0.10.0" @@ -45,6 +45,7 @@ log = "0.4.20" tera = "1.0" curl = "0.4.44" temboclient = { version = "1.0.0", path = "temboclient" } +tembodataclient = { version = "0.0.1", path = "tembodataclient" } tokio = { version = "1.26.0", features = [ "rt", "rt-multi-thread", diff --git a/tembo-cli/README.md b/tembo-cli/README.md index 625763ba3..c1ea9910c 100644 --- a/tembo-cli/README.md +++ b/tembo-cli/README.md @@ -47,6 +47,22 @@ Validates Tembo.toml (same as `tembo validate`) and applies the changes to the c Install OpenAPI Generator if not already by following steps [here](https://openapi-generator.tech/docs/installation) +### Data plane API client + +Go to `tembodataclient` directory in your terminal. + +Delete the contents of the directory first and then run following command to re-generate the rust client code for the API. + +```bash +openapi-generator generate -i https://api.data-1.use1.tembo.io/api-docs/openapi.json -g rust -o . --additional-properties=packageName=tembodataclient +``` + +* Go to `tembodataclient/src/lib.rs` & add followng line at the top to disable clippy for the generated code + +``` +#![allow(clippy::all)] +``` + ### Control plane API client Go to `temboclient` directory in your terminal. diff --git a/tembo-cli/rustfmt.toml b/tembo-cli/rustfmt.toml index 3303cef88..84fb8bd08 100644 --- a/tembo-cli/rustfmt.toml +++ b/tembo-cli/rustfmt.toml @@ -1 +1 @@ -ignore = ["temboclient"] \ No newline at end of file +ignore = ["temboclient", "tembodataclient"] diff --git a/tembo-cli/src/cli/context.rs b/tembo-cli/src/cli/context.rs index 611e4bec7..2bd4f2bc4 100644 --- a/tembo-cli/src/cli/context.rs +++ b/tembo-cli/src/cli/context.rs @@ -27,7 +27,8 @@ pub const CREDENTIALS_DEFAULT_TEXT: &str = "version = \"1.0\" [[profile]] name = 'prod' tembo_access_token = 'ACCESS_TOKEN' -tembo_host = 'https://api.coredb.io' +tembo_host = 'https://api.tembo.io' +tembo_data_host = 'https://api.data-1.use1.tembo.io' "; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -58,6 +59,7 @@ pub struct Profile { pub name: String, pub tembo_access_token: String, pub tembo_host: String, + pub tembo_data_host: String, } pub enum Target { diff --git a/tembo-cli/src/cli/sqlx_utils.rs b/tembo-cli/src/cli/sqlx_utils.rs index 4b95e7804..23313856f 100644 --- a/tembo-cli/src/cli/sqlx_utils.rs +++ b/tembo-cli/src/cli/sqlx_utils.rs @@ -1,5 +1,4 @@ use crate::Result; -use base64::{engine::general_purpose, Engine as _}; use spinners::Spinner; use spinners::Spinners; use sqlx::migrate::Migrator; @@ -12,23 +11,15 @@ pub struct SqlxUtils {} impl SqlxUtils { // run sqlx migrate - pub async fn run_migrations(connection_info: ConnectionInfo, decode: bool) -> Result { + pub async fn run_migrations(connection_info: ConnectionInfo) -> Result { let mut sp = Spinner::new(Spinners::Line, "Running SQL migration".into()); - let user: String; - let pwd: String; - - if decode { - user = SqlxUtils::b64_decode(&connection_info.user); - pwd = SqlxUtils::b64_decode(&connection_info.password); - } else { - user = connection_info.user; - pwd = connection_info.password; - } - let connection_string = format!( "postgresql://{}:{}@{}:{}", - user, pwd, connection_info.host, connection_info.port + connection_info.user, + connection_info.password, + connection_info.host, + connection_info.port ); let pool = Pool::::connect(connection_string.as_str()).await?; @@ -40,9 +31,4 @@ impl SqlxUtils { Ok(()) } - - fn b64_decode(b64_encoded: &str) -> String { - let bytes = general_purpose::STANDARD.decode(b64_encoded).unwrap(); - String::from_utf8(bytes).unwrap() - } } diff --git a/tembo-cli/src/cmd/apply.rs b/tembo-cli/src/cmd/apply.rs index 414abf09a..3a0413329 100644 --- a/tembo-cli/src/cmd/apply.rs +++ b/tembo-cli/src/cmd/apply.rs @@ -1,6 +1,6 @@ use crate::{ cli::{ - context::{get_current_context, Environment, Target}, + context::{get_current_context, Environment, Profile, Target}, sqlx_utils::SqlxUtils, tembo_config, }, @@ -28,6 +28,7 @@ use temboclient::{ StackType, State, Storage, TrunkInstall, UpdateInstance, }, }; +use tembodataclient::apis::secrets_api::get_secret_v1; use tokio::runtime::Runtime; use crate::cli::{docker::Docker, file_utils::FileUtils, tembo_config::InstanceSettings}; @@ -47,7 +48,7 @@ pub fn execute(_args: &ArgMatches) -> Result<()> { if env.target == Target::Docker.to_string() { return execute_docker(); } else if env.target == Target::TemboCloud.to_string() { - return execute_tembo_cloud(env); + return execute_tembo_cloud(env.clone()); } Ok(()) @@ -97,7 +98,7 @@ fn execute_docker() -> Result<()> { }; Runtime::new() .unwrap() - .block_on(SqlxUtils::run_migrations(conn_info, false))?; + .block_on(SqlxUtils::run_migrations(conn_info))?; } // If all of the above was successful, we can print the url to user @@ -111,13 +112,13 @@ pub fn execute_tembo_cloud(env: Environment) -> Result<()> { let profile = env.clone().selected_profile.unwrap(); let config = Configuration { - base_path: profile.tembo_host, - bearer_access_token: Some(profile.tembo_access_token), + base_path: profile.clone().tembo_host, + bearer_access_token: Some(profile.clone().tembo_access_token), ..Default::default() }; for (_key, value) in instance_settings.iter() { - let mut instance_id = get_instance_id(value.instance_name.clone(), &config, &env)?; + let mut instance_id = get_instance_id(value.instance_name.clone(), &config, env.clone())?; if let Some(env_instance_id) = instance_id.clone() { update_existing_instance(env_instance_id, value, &config, env.clone()); @@ -131,11 +132,21 @@ pub fn execute_tembo_cloud(env: Environment) -> Result<()> { let connection_info: Option> = is_instance_up(instance_id.as_ref().unwrap().clone(), &config, &env)?; + if connection_info.is_some() { + let conn_info = get_conn_info_with_creds( + profile.clone(), + &instance_id, + connection_info, + env.clone(), + )?; + Runtime::new() .unwrap() - .block_on(SqlxUtils::run_migrations(*connection_info.unwrap(), true))?; + .block_on(SqlxUtils::run_migrations(conn_info))?; + sp.stop_with_message("- Instance is now up!".to_string()); + break; } } @@ -144,10 +155,43 @@ pub fn execute_tembo_cloud(env: Environment) -> Result<()> { Ok(()) } +fn get_conn_info_with_creds( + profile: Profile, + instance_id: &Option, + connection_info: Option>, + env: Environment, +) -> Result { + let dataplane_config = tembodataclient::apis::configuration::Configuration { + base_path: profile.tembo_data_host, + bearer_access_token: Some(profile.tembo_access_token), + ..Default::default() + }; + + let result = Runtime::new().unwrap().block_on(get_secret_v1( + &dataplane_config, + env.org_id.clone().unwrap().as_str(), + instance_id.as_ref().unwrap(), + "superuser-role", + )); + + if result.is_err() { + return Err(Error::msg("Error fetching instance credentials!")); + } + + let mut conn_info = *connection_info.unwrap(); + + let map = result.as_ref().unwrap(); + + conn_info.user = map.get("username").unwrap().to_string(); + conn_info.password = map.get("password").unwrap().to_string(); + + Ok(conn_info) +} + pub fn get_instance_id( instance_name: String, config: &Configuration, - env: &Environment, + env: Environment, ) -> Result> { let v = Runtime::new() .unwrap() diff --git a/tembo-cli/src/cmd/delete.rs b/tembo-cli/src/cmd/delete.rs index bb0d76c55..adf319c83 100644 --- a/tembo-cli/src/cmd/delete.rs +++ b/tembo-cli/src/cmd/delete.rs @@ -47,7 +47,7 @@ fn execute_tembo_cloud(env: Environment) -> Result<()> { }; for (_key, value) in instance_settings.iter() { - let instance_id = get_instance_id(value.instance_name.clone(), &config, &env)?; + let instance_id = get_instance_id(value.instance_name.clone(), &config, env.clone())?; if let Some(env_instance_id) = instance_id { let v = Runtime::new().unwrap().block_on(delete_instance( &config, diff --git a/tembo-cli/tembodataclient/.gitignore b/tembo-cli/tembodataclient/.gitignore new file mode 100644 index 000000000..6aa106405 --- /dev/null +++ b/tembo-cli/tembodataclient/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/tembo-cli/tembodataclient/.openapi-generator-ignore b/tembo-cli/tembodataclient/.openapi-generator-ignore new file mode 100644 index 000000000..7484ee590 --- /dev/null +++ b/tembo-cli/tembodataclient/.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/tembo-cli/tembodataclient/.openapi-generator/FILES b/tembo-cli/tembodataclient/.openapi-generator/FILES new file mode 100644 index 000000000..a425f8bb7 --- /dev/null +++ b/tembo-cli/tembodataclient/.openapi-generator/FILES @@ -0,0 +1,16 @@ +.gitignore +.openapi-generator-ignore +.travis.yml +Cargo.toml +README.md +docs/AvailableSecret.md +docs/MetricsApi.md +docs/SecretsApi.md +git_push.sh +src/apis/configuration.rs +src/apis/metrics_api.rs +src/apis/mod.rs +src/apis/secrets_api.rs +src/lib.rs +src/models/available_secret.rs +src/models/mod.rs diff --git a/tembo-cli/tembodataclient/.openapi-generator/VERSION b/tembo-cli/tembodataclient/.openapi-generator/VERSION new file mode 100644 index 000000000..3769235d3 --- /dev/null +++ b/tembo-cli/tembodataclient/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.1.0 \ No newline at end of file diff --git a/tembo-cli/tembodataclient/.travis.yml b/tembo-cli/tembodataclient/.travis.yml new file mode 100644 index 000000000..22761ba7e --- /dev/null +++ b/tembo-cli/tembodataclient/.travis.yml @@ -0,0 +1 @@ +language: rust diff --git a/tembo-cli/tembodataclient/Cargo.toml b/tembo-cli/tembodataclient/Cargo.toml new file mode 100644 index 000000000..b60fd3ac2 --- /dev/null +++ b/tembo-cli/tembodataclient/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tembodataclient" +version = "0.0.1" +authors = ["OpenAPI Generator team and contributors"] +description = "In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane.

To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). " +# Override this license by providing a License Object in the OpenAPI. +license = "Unlicense" +edition = "2018" + +[dependencies] +serde = "^1.0" +serde_derive = "^1.0" +serde_json = "^1.0" +url = "^2.2" +uuid = { version = "^1.0", features = ["serde", "v4"] } +[dependencies.reqwest] +version = "^0.11" +features = ["json", "multipart"] diff --git a/tembo-cli/tembodataclient/README.md b/tembo-cli/tembodataclient/README.md new file mode 100644 index 000000000..ef076270a --- /dev/null +++ b/tembo-cli/tembodataclient/README.md @@ -0,0 +1,53 @@ +# Rust API client for tembodataclient + +In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane. +
+
+ To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). + + + +## Overview + +This API client 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 an API client. + +- API version: v0.0.1 +- Package version: v0.0.1 +- Build package: `org.openapitools.codegen.languages.RustClientCodegen` + +## Installation + +Put the package under your project folder in a directory named `tembodataclient` and add the following to `Cargo.toml` under `[dependencies]`: + +``` +tembodataclient = { path = "./tembodataclient" } +``` + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*MetricsApi* | [**query_range**](docs/MetricsApi.md#query_range) | **GET** /{namespace}/metrics/query_range | +*SecretsApi* | [**get_secret**](docs/SecretsApi.md#get_secret) | **GET** /{namespace}/secrets/{secret_name} | Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} +*SecretsApi* | [**get_secret_names**](docs/SecretsApi.md#get_secret_names) | **GET** /{namespace}/secrets | Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets +*SecretsApi* | [**get_secret_names_v1**](docs/SecretsApi.md#get_secret_names_v1) | **GET** /api/v1/orgs/{org_id}/instances/{instance_id}/secrets | +*SecretsApi* | [**get_secret_v1**](docs/SecretsApi.md#get_secret_v1) | **GET** /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} | + + +## Documentation For Models + + - [AvailableSecret](docs/AvailableSecret.md) + + +To get access to the crate's generated documentation, use: + +``` +cargo doc --open +``` + +## Author + + + diff --git a/tembo-cli/tembodataclient/docs/AvailableSecret.md b/tembo-cli/tembodataclient/docs/AvailableSecret.md new file mode 100644 index 000000000..d5d8461c2 --- /dev/null +++ b/tembo-cli/tembodataclient/docs/AvailableSecret.md @@ -0,0 +1,12 @@ +# AvailableSecret + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **String** | The name of an available secret | +**possible_keys** | **Vec** | For this secret, available keys | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/tembo-cli/tembodataclient/docs/MetricsApi.md b/tembo-cli/tembodataclient/docs/MetricsApi.md new file mode 100644 index 000000000..13ea1268f --- /dev/null +++ b/tembo-cli/tembodataclient/docs/MetricsApi.md @@ -0,0 +1,41 @@ +# \MetricsApi + +All URIs are relative to *http://localhost* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**query_range**](MetricsApi.md#query_range) | **GET** /{namespace}/metrics/query_range | + + + +## query_range + +> serde_json::Value query_range(namespace, query, start, end, step) + + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**namespace** | **String** | Instance namespace | [required] | +**query** | **String** | PromQL range query, must include a 'namespace' label matching the query path | [required] | +**start** | **i64** | Range start, unix timestamp | [required] | +**end** | Option<**i64**> | Range end, unix timestamp. Default is now. | | +**step** | Option<**String**> | Step size duration string, defaults to 60s | | + +### Return type + +[**serde_json::Value**](serde_json::Value.md) + +### Authorization + +[jwt_token](../README.md#jwt_token) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/tembo-cli/tembodataclient/docs/SecretsApi.md b/tembo-cli/tembodataclient/docs/SecretsApi.md new file mode 100644 index 000000000..8957064b3 --- /dev/null +++ b/tembo-cli/tembodataclient/docs/SecretsApi.md @@ -0,0 +1,132 @@ +# \SecretsApi + +All URIs are relative to *http://localhost* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**get_secret**](SecretsApi.md#get_secret) | **GET** /{namespace}/secrets/{secret_name} | Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} +[**get_secret_names**](SecretsApi.md#get_secret_names) | **GET** /{namespace}/secrets | Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets +[**get_secret_names_v1**](SecretsApi.md#get_secret_names_v1) | **GET** /api/v1/orgs/{org_id}/instances/{instance_id}/secrets | +[**get_secret_v1**](SecretsApi.md#get_secret_v1) | **GET** /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} | + + + +## get_secret + +> ::std::collections::HashMap get_secret(namespace, secret_name) +Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} + +Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**namespace** | **String** | Instance namespace | [required] | +**secret_name** | **String** | Secret name | [required] | + +### Return type + +**::std::collections::HashMap** + +### Authorization + +[jwt_token](../README.md#jwt_token) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + + +## get_secret_names + +> Vec get_secret_names(namespace) +Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets + +Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**namespace** | **String** | Instance namespace | [required] | + +### Return type + +[**Vec**](AvailableSecret.md) + +### Authorization + +[jwt_token](../README.md#jwt_token) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + + +## get_secret_names_v1 + +> Vec get_secret_names_v1(org_id, instance_id) + + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**org_id** | **String** | Tembo Cloud Organization ID | [required] | +**instance_id** | **String** | Tembo Cloud Instance ID | [required] | + +### Return type + +[**Vec**](AvailableSecret.md) + +### Authorization + +[jwt_token](../README.md#jwt_token) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + + +## get_secret_v1 + +> ::std::collections::HashMap get_secret_v1(org_id, instance_id, secret_name) + + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**org_id** | **String** | Tembo Cloud Organization ID | [required] | +**instance_id** | **String** | Tembo Cloud Instance ID | [required] | +**secret_name** | **String** | Secret name | [required] | + +### Return type + +**::std::collections::HashMap** + +### Authorization + +[jwt_token](../README.md#jwt_token) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/tembo-cli/tembodataclient/git_push.sh b/tembo-cli/tembodataclient/git_push.sh new file mode 100644 index 000000000..f53a75d4f --- /dev/null +++ b/tembo-cli/tembodataclient/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/tembo-cli/tembodataclient/src/apis/configuration.rs b/tembo-cli/tembodataclient/src/apis/configuration.rs new file mode 100644 index 000000000..d84f61de5 --- /dev/null +++ b/tembo-cli/tembodataclient/src/apis/configuration.rs @@ -0,0 +1,53 @@ +/* + * Tembo Data API + * + * In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane.

To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). + * + * The version of the OpenAPI document: v0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + + + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: reqwest::Client, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, + // TODO: take an oauth2 token source, similar to the go one +} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "http://localhost".to_owned(), + user_agent: Some("OpenAPI-Generator/v0.0.1/rust".to_owned()), + client: reqwest::Client::new(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + + } + } +} diff --git a/tembo-cli/tembodataclient/src/apis/metrics_api.rs b/tembo-cli/tembodataclient/src/apis/metrics_api.rs new file mode 100644 index 000000000..9dfeeaa32 --- /dev/null +++ b/tembo-cli/tembodataclient/src/apis/metrics_api.rs @@ -0,0 +1,67 @@ +/* + * Tembo Data API + * + * In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane.

To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). + * + * The version of the OpenAPI document: v0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + + +use reqwest; + +use crate::apis::ResponseContent; +use super::{Error, configuration}; + + +/// struct for typed errors of method [`query_range`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum QueryRangeError { + Status400(), + Status403(), + Status422(), + Status504(), + UnknownValue(serde_json::Value), +} + + +pub async fn query_range(configuration: &configuration::Configuration, namespace: &str, query: &str, start: i64, end: Option, step: Option<&str>) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/{namespace}/metrics/query_range", local_var_configuration.base_path, namespace=crate::apis::urlencode(namespace)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + local_var_req_builder = local_var_req_builder.query(&[("query", &query.to_string())]); + local_var_req_builder = local_var_req_builder.query(&[("start", &start.to_string())]); + if let Some(ref local_var_str) = end { + local_var_req_builder = local_var_req_builder.query(&[("end", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = step { + local_var_req_builder = local_var_req_builder.query(&[("step", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} + diff --git a/tembo-cli/tembodataclient/src/apis/mod.rs b/tembo-cli/tembodataclient/src/apis/mod.rs new file mode 100644 index 000000000..e2c6571c1 --- /dev/null +++ b/tembo-cli/tembodataclient/src/apis/mod.rs @@ -0,0 +1,96 @@ +use std::error; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + }, + serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())), + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +pub mod metrics_api; +pub mod secrets_api; + +pub mod configuration; diff --git a/tembo-cli/tembodataclient/src/apis/secrets_api.rs b/tembo-cli/tembodataclient/src/apis/secrets_api.rs new file mode 100644 index 000000000..a0b406e00 --- /dev/null +++ b/tembo-cli/tembodataclient/src/apis/secrets_api.rs @@ -0,0 +1,172 @@ +/* + * Tembo Data API + * + * In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane.

To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). + * + * The version of the OpenAPI document: v0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + + +use reqwest; + +use crate::apis::ResponseContent; +use super::{Error, configuration}; + + +/// struct for typed errors of method [`get_secret`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetSecretError { + Status403(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_secret_names`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetSecretNamesError { + Status403(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_secret_names_v1`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetSecretNamesV1Error { + Status403(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_secret_v1`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetSecretV1Error { + Status403(), + UnknownValue(serde_json::Value), +} + + +/// Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name} +pub async fn get_secret(configuration: &configuration::Configuration, namespace: &str, secret_name: &str) -> Result<::std::collections::HashMap, Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/{namespace}/secrets/{secret_name}", local_var_configuration.base_path, namespace=crate::apis::urlencode(namespace), secret_name=crate::apis::urlencode(secret_name)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} + +/// Please use /api/v1/orgs/{org_id}/instances/{instance_id}/secrets +pub async fn get_secret_names(configuration: &configuration::Configuration, namespace: &str) -> Result, Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/{namespace}/secrets", local_var_configuration.base_path, namespace=crate::apis::urlencode(namespace)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn get_secret_names_v1(configuration: &configuration::Configuration, org_id: &str, instance_id: &str) -> Result, Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/api/v1/orgs/{org_id}/instances/{instance_id}/secrets", local_var_configuration.base_path, org_id=crate::apis::urlencode(org_id), instance_id=crate::apis::urlencode(instance_id)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn get_secret_v1(configuration: &configuration::Configuration, org_id: &str, instance_id: &str, secret_name: &str) -> Result<::std::collections::HashMap, Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/api/v1/orgs/{org_id}/instances/{instance_id}/secrets/{secret_name}", local_var_configuration.base_path, org_id=crate::apis::urlencode(org_id), instance_id=crate::apis::urlencode(instance_id), secret_name=crate::apis::urlencode(secret_name)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} + diff --git a/tembo-cli/tembodataclient/src/lib.rs b/tembo-cli/tembodataclient/src/lib.rs new file mode 100644 index 000000000..feea56ed9 --- /dev/null +++ b/tembo-cli/tembodataclient/src/lib.rs @@ -0,0 +1,11 @@ +#![allow(clippy::all)] +#[macro_use] +extern crate serde_derive; + +extern crate serde; +extern crate serde_json; +extern crate url; +extern crate reqwest; + +pub mod apis; +pub mod models; diff --git a/tembo-cli/tembodataclient/src/models/available_secret.rs b/tembo-cli/tembodataclient/src/models/available_secret.rs new file mode 100644 index 000000000..d596999b0 --- /dev/null +++ b/tembo-cli/tembodataclient/src/models/available_secret.rs @@ -0,0 +1,33 @@ +/* + * Tembo Data API + * + * In the case of large or sensitive data, we avoid collecting it into Tembo Cloud. Instead, there is a Tembo Data API for each region, cloud, or private data plane.

To find the Tembo Cloud API, please find it [here](https://api.tembo.io/swagger-ui/). + * + * The version of the OpenAPI document: v0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AvailableSecret { + /// The name of an available secret + #[serde(rename = "name")] + pub name: String, + /// For this secret, available keys + #[serde(rename = "possible_keys")] + pub possible_keys: Vec, +} + +impl AvailableSecret { + pub fn new(name: String, possible_keys: Vec) -> AvailableSecret { + AvailableSecret { + name, + possible_keys, + } + } +} + + diff --git a/tembo-cli/tembodataclient/src/models/mod.rs b/tembo-cli/tembodataclient/src/models/mod.rs new file mode 100644 index 000000000..c1994ebe3 --- /dev/null +++ b/tembo-cli/tembodataclient/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod available_secret; +pub use self::available_secret::AvailableSecret;