Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,32 @@ jobs:

- name: Run Doc tests
run: cargo test --doc

openapi:
runs-on: ubuntu-latest

steps:
- name: Harden Runner
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install Rust
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable
with:
toolchain: stable
targets: ${{ matrix.target }}

- name: Rust Cache
uses: swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0

- name: Generate OpenAPI
run: cargo run --bin keystone -- --dump-openapi yaml > openapi.yaml

- name: Upload OpenAPI spec
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: openapi.spec
path: openapi.yaml
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ tower-http = { version = "0.6", features = ["compression-full", "request-id", "s
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3", features = [] }
url = { version = "2.5", features = ["serde"] }
utoipa = { version = "5.4", features = ["axum_extras", "chrono"] }
utoipa = { version = "5.4", features = ["axum_extras", "chrono", "yaml"] }
utoipa-axum = { version = "0.2" }
utoipa-swagger-ui = { version = "9.0", features = ["axum", "vendored"], default-features = false }
uuid = { version = "1.17", features = ["v4"] }
Expand Down
53 changes: 43 additions & 10 deletions src/bin/keystone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
//
// SPDX-License-Identifier: Apache-2.0

//! Main Keystone executable.
//!
//! This is the entry point of the `keystone` binary.

use axum::http::{self, HeaderName, Request, header};
use clap::Parser;
use clap::{Parser, ValueEnum};
use color_eyre::eyre::{Report, Result};
use sea_orm::ConnectOptions;
use sea_orm::Database;
Expand Down Expand Up @@ -45,17 +49,33 @@ use openstack_keystone::plugin_manager::PluginManager;
use openstack_keystone::policy::PolicyFactory;
use openstack_keystone::provider::Provider;

/// Simple program to greet a person
/// OpenStack Keystone.
///
/// Keystone is an OpenStack service that provides API client authentication, service discovery,
/// and distributed multi-tenant authorization by implementing OpenStack’s Identity API.
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Path to the keystone config file
#[arg(short, long)]
config: String,
/// Path to the keystone config file.
#[arg(short, long, required_unless_present("dump_openapi"))]
config: Option<String>,

/// Verbosity level. Repeat to increase level.
#[arg(short, long, global=true, action = clap::ArgAction::Count, display_order = 920)]
pub verbose: u8,

/// Print the OpenAPI schema json instead of running the Keystone.
#[arg(long)]
pub dump_openapi: Option<OpenApiFormat>,
}

#[derive(Clone, Debug, Default, PartialEq, ValueEnum)]
enum OpenApiFormat {
/// Json.
Json,
#[default]
/// Yaml.
Yaml,
}

// A `MakeRequestId` that increments an atomic counter
Expand Down Expand Up @@ -95,10 +115,27 @@ async fn main() -> Result<(), Report> {
// build the tracing registry
tracing_subscriber::registry().with(log_layer).init();

let openapi = api::ApiDoc::openapi();

let (router, api) = OpenApiRouter::with_openapi(openapi.clone())
.merge(api::openapi_router())
.split_for_parts();

if let Some(dump_format) = &args.dump_openapi {
println!(
"{}",
match dump_format {
OpenApiFormat::Yaml => api.to_yaml()?,
OpenApiFormat::Json => api.to_pretty_json()?,
}
);
return Ok(());
}

let token = CancellationToken::new();
let cloned_token = token.clone();

let cfg = Config::new(args.config.into())?;
let cfg = Config::new(args.config.expect("config file is required.").into())?;
let db_url = cfg.database.get_connection();
let mut opt = ConnectOptions::new(db_url.to_owned());
if args.verbose < 2 {
Expand Down Expand Up @@ -128,10 +165,6 @@ async fn main() -> Result<(), Report> {

spawn(cleanup(cloned_token, shared_state.clone()));

let (router, api) = OpenApiRouter::with_openapi(api::ApiDoc::openapi())
.merge(api::openapi_router())
.split_for_parts();

let x_request_id = HeaderName::from_static("x-openstack-request-id");
let sensitive_headers: Arc<[_]> = vec![
header::AUTHORIZATION,
Expand Down
Loading