2
.env
2
.env
@@ -1,2 +1,4 @@
|
|||||||
RUST_LOG=notmad=debug,nodrift=debug,forge=trace,info
|
RUST_LOG=notmad=debug,nodrift=debug,forge=trace,info
|
||||||
|
|
||||||
FE_CONFIG_DIR=./templates/fe/configs/
|
FE_CONFIG_DIR=./templates/fe/configs/
|
||||||
|
FE_SCHEMA_FILE=./templates/fe/schema.json
|
||||||
|
|||||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -450,6 +450,12 @@ version = "0.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.16.9"
|
version = "0.16.9"
|
||||||
@@ -572,7 +578,9 @@ dependencies = [
|
|||||||
"notmad",
|
"notmad",
|
||||||
"octocrab",
|
"octocrab",
|
||||||
"regex",
|
"regex",
|
||||||
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
@@ -1473,6 +1481,26 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast"
|
||||||
|
version = "1.0.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||||
|
dependencies = [
|
||||||
|
"ref-cast-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast-impl"
|
||||||
|
version = "1.0.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.3"
|
||||||
@@ -1637,6 +1665,31 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schemars"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
|
||||||
|
dependencies = [
|
||||||
|
"dyn-clone",
|
||||||
|
"ref-cast",
|
||||||
|
"schemars_derive",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schemars_derive"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde_derive_internals",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1729,6 +1782,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive_internals"
|
||||||
|
version = "0.29.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.149"
|
version = "1.0.149"
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -1 +1,47 @@
|
|||||||
# forge-enforce
|
# forge-enforce
|
||||||
|
|
||||||
|
Enforce repository policies across forge providers (GitHub, etc.). Runs on a schedule, discovers repositories matching allow/deny filters, and applies configured policies.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
forge-enforce serve --host 127.0.0.1:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires `FE_CONFIG_DIR` (or `--config-dir`) pointing to a directory of TOML config files, one per forge.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Each file in the config directory defines a forge connection. Example (`github.com.toml`):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Schedule: pick one
|
||||||
|
# schedule.cron = "0 * * * * *"
|
||||||
|
# schedule.interval = "60" # minutes (default)
|
||||||
|
schedule.once = true
|
||||||
|
|
||||||
|
# Repository filters (regex patterns)
|
||||||
|
allow = ["^canopy-.*$"]
|
||||||
|
deny = ["^infrastructure-.*$", "^canopy-data-gateway$"]
|
||||||
|
|
||||||
|
# Forge connection
|
||||||
|
[github]
|
||||||
|
credentials.token_env = "GITHUB_ACCESS_TOKEN"
|
||||||
|
organisation = "understory-io"
|
||||||
|
|
||||||
|
# Policies to enforce
|
||||||
|
[policies]
|
||||||
|
squash_merge_only.enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|---|---|
|
||||||
|
| `schedule` | `cron`, `interval` (minutes), or `once` |
|
||||||
|
| `allow` | Regex list of repository names to include (default: `.*`) |
|
||||||
|
| `deny` | Regex list of repository names to exclude (default: none) |
|
||||||
|
| `[github]` | GitHub forge config: `credentials` and `organisation` |
|
||||||
|
| `credentials` | `token = "..."` or `token_env = "ENV_VAR"` |
|
||||||
|
| `[policies]` | Policy rules to enforce on matched repositories |
|
||||||
|
| `squash_merge_only.enabled` | Require squash merges only |
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ regex = "1.12.3"
|
|||||||
toml = "0.9.11"
|
toml = "0.9.11"
|
||||||
nodrift = "0.3.5"
|
nodrift = "0.3.5"
|
||||||
octocrab = "0.49.5"
|
octocrab = "0.49.5"
|
||||||
|
schemars = "1.2.1"
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use clap::{Parser, Subcommand};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{Config, State, cli::serve::ServeCommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use schemars::schema_for;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
use crate::{Config, State, cli::serve::ServeCommand, forge_config::ForgeConfig};
|
||||||
|
|
||||||
mod serve;
|
mod serve;
|
||||||
|
|
||||||
@@ -17,6 +21,10 @@ struct Command {
|
|||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
Serve(ServeCommand),
|
Serve(ServeCommand),
|
||||||
|
Schema {
|
||||||
|
#[arg(long, env = "FE_SCHEMA_FILE")]
|
||||||
|
schema_file: PathBuf,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute() -> anyhow::Result<()> {
|
pub async fn execute() -> anyhow::Result<()> {
|
||||||
@@ -26,5 +34,17 @@ pub async fn execute() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
match cli.command.expect("a subcommand") {
|
match cli.command.expect("a subcommand") {
|
||||||
Commands::Serve(cmd) => cmd.execute(&state).await,
|
Commands::Serve(cmd) => cmd.execute(&state).await,
|
||||||
|
Commands::Schema { schema_file } => {
|
||||||
|
let schema = schema_for!(ForgeConfig);
|
||||||
|
|
||||||
|
let output = serde_json::to_string_pretty(&schema)?;
|
||||||
|
|
||||||
|
let mut file = tokio::fs::File::create(&schema_file).await?;
|
||||||
|
|
||||||
|
file.write_all(output.as_bytes()).await?;
|
||||||
|
file.flush().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,67 @@
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ForgeConfig {
|
pub struct ForgeConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub forge_type: ForgeConfigType,
|
pub forge_type: ForgeConfigType,
|
||||||
|
|
||||||
|
#[serde(default = "Filter::default")]
|
||||||
|
pub filter: Filter,
|
||||||
|
|
||||||
#[serde(default = "ForgeSchedule::default")]
|
#[serde(default = "ForgeSchedule::default")]
|
||||||
pub schedule: ForgeSchedule,
|
pub schedule: ForgeSchedule,
|
||||||
|
|
||||||
#[serde(default = "allow_all")]
|
|
||||||
pub allow: Vec<ForgeRegex>,
|
|
||||||
#[serde(default = "Vec::new")]
|
|
||||||
pub deny: Vec<ForgeRegex>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub policies: Policies,
|
pub policies: Policies,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
/// # Filter
|
||||||
|
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Filter {
|
||||||
|
/// # Allow
|
||||||
|
/// Allowed repositories, uses regex patterns
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// allow = [".*", "^something-.*$"]
|
||||||
|
/// ````
|
||||||
|
#[serde(default = "allow_all")]
|
||||||
|
pub allow: Vec<ForgeRegex>,
|
||||||
|
|
||||||
|
/// # Deny
|
||||||
|
/// Denied repositories, uses regex patterns
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// deny = [".*", "^something-.*$"]
|
||||||
|
/// ````
|
||||||
|
#[serde(default = "deny_default")]
|
||||||
|
pub deny: Vec<ForgeRegex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Filter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
allow: allow_all(),
|
||||||
|
deny: deny_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deny_default() -> Vec<ForgeRegex> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Policies {
|
pub struct Policies {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub squash_merge_only: PolicyOption,
|
pub squash_merge_only: PolicyOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct PolicyOption {
|
pub struct PolicyOption {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -39,7 +74,7 @@ fn allow_all() -> Vec<ForgeRegex> {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize, JsonSchema)]
|
||||||
pub enum ForgeSchedule {
|
pub enum ForgeSchedule {
|
||||||
#[serde(rename = "cron")]
|
#[serde(rename = "cron")]
|
||||||
Cron(String),
|
Cron(String),
|
||||||
@@ -60,6 +95,16 @@ pub struct ForgeRegex {
|
|||||||
regex: Regex,
|
regex: Regex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JsonSchema for ForgeRegex {
|
||||||
|
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
"regex".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||||
|
schemars::schema_for!(String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ForgeRegex> for Regex {
|
impl From<ForgeRegex> for Regex {
|
||||||
fn from(value: ForgeRegex) -> Self {
|
fn from(value: ForgeRegex) -> Self {
|
||||||
value.regex
|
value.regex
|
||||||
@@ -99,20 +144,28 @@ impl<'de> Deserialize<'de> for ForgeRegex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
// #[serde(tag = "type")]
|
// #[serde(tag = "type")]
|
||||||
pub enum ForgeConfigType {
|
pub enum ForgeConfigType {
|
||||||
|
/// GitHub forge
|
||||||
#[serde(rename = "github")]
|
#[serde(rename = "github")]
|
||||||
GitHub {
|
GitHub {
|
||||||
|
/// Credentials
|
||||||
credentials: GitHubCredentials,
|
credentials: GitHubCredentials,
|
||||||
|
|
||||||
|
/// Organisation
|
||||||
organisation: String,
|
organisation: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
/// GitHubCredentials
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub enum GitHubCredentials {
|
pub enum GitHubCredentials {
|
||||||
|
/// Token
|
||||||
#[serde(rename = "token")]
|
#[serde(rename = "token")]
|
||||||
Token(String),
|
Token(String),
|
||||||
|
|
||||||
|
/// Get token from env key
|
||||||
#[serde(rename = "token_env")]
|
#[serde(rename = "token_env")]
|
||||||
TokenEnv(String),
|
TokenEnv(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ impl nodrift::Drifter for ForgeService {
|
|||||||
client
|
client
|
||||||
.get_repositories(
|
.get_repositories(
|
||||||
organisation,
|
organisation,
|
||||||
self.config.allow.iter().map(|a| a.into()).collect(),
|
self.config.filter.allow.iter().map(|a| a.into()).collect(),
|
||||||
self.config.deny.iter().map(|a| a.into()).collect(),
|
self.config.filter.deny.iter().map(|a| a.into()).collect(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ impl nodrift::Drifter for ForgeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load(path: &Path, state: &State) -> anyhow::Result<Vec<ForgeService>> {
|
pub async fn load(path: &Path, _state: &State) -> anyhow::Result<Vec<ForgeService>> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
anyhow::bail!("config path does not exist: {}", path.display());
|
anyhow::bail!("config path does not exist: {}", path.display());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# schedule.cron = "0 * * * * *"
|
#:schema ../schema.json
|
||||||
schedule.once = true
|
|
||||||
|
|
||||||
|
[github]
|
||||||
|
organisation = "understory-io"
|
||||||
|
credentials.token_env = "GITHUB_ACCESS_TOKEN"
|
||||||
|
|
||||||
|
|
||||||
|
[filter]
|
||||||
allow = ["^canopy-.*$"]
|
allow = ["^canopy-.*$"]
|
||||||
deny = ["^infrastructure-.*$", "^canopy-data-gateway$"]
|
deny = ["^infrastructure-.*$", "^canopy-data-gateway$"]
|
||||||
|
|
||||||
[github]
|
[schedule]
|
||||||
credentials.token_env = "GITHUB_ACCESS_TOKEN"
|
once = true
|
||||||
organisation = "understory-io"
|
|
||||||
|
|
||||||
[policies]
|
[policies]
|
||||||
squash_merge_only.enabled = true
|
squash_merge_only.enabled = true
|
||||||
|
|||||||
165
templates/fe/schema.json
Normal file
165
templates/fe/schema.json
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "ForgeConfig",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"filter": {
|
||||||
|
"$ref": "#/$defs/Filter"
|
||||||
|
},
|
||||||
|
"policies": {
|
||||||
|
"$ref": "#/$defs/Policies"
|
||||||
|
},
|
||||||
|
"schedule": {
|
||||||
|
"$ref": "#/$defs/ForgeSchedule"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "GitHub forge",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"github": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"credentials": {
|
||||||
|
"description": "Credentials",
|
||||||
|
"$ref": "#/$defs/GitHubCredentials"
|
||||||
|
},
|
||||||
|
"organisation": {
|
||||||
|
"description": "Organisation",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"credentials",
|
||||||
|
"organisation"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"github"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unevaluatedProperties": false,
|
||||||
|
"$defs": {
|
||||||
|
"Filter": {
|
||||||
|
"title": "Filter",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allow": {
|
||||||
|
"title": "Allow",
|
||||||
|
"description": "Allowed repositories, uses regex patterns\n\n```toml\nallow = [\".*\", \"^something-.*$\"]\n````",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/regex"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deny": {
|
||||||
|
"title": "Deny",
|
||||||
|
"description": "Denied repositories, uses regex patterns\n\n```toml\ndeny = [\".*\", \"^something-.*$\"]\n````",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/regex"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"ForgeSchedule": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cron": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"cron"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"interval": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"interval"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"once": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"once"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GitHubCredentials": {
|
||||||
|
"description": "GitHubCredentials",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Token",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Get token from env key",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"token_env": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"token_env"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Policies": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"squash_merge_only": {
|
||||||
|
"$ref": "#/$defs/PolicyOption"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"PolicyOption": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"regex": {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "string",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user