A Rust library for managing GitHub App integrations with GitOps support.
- GitHub App Authentication: JWT creation and installation access token management with automatic caching
- Webhook Verification: HMAC-SHA256 signature verification for GitHub webhooks
- GitOps: Git-backed manifest loading from GitHub repositories
- Type-safe: Strongly typed webhook events and manifest parsing
| Module | Responsibility |
|---|---|
config |
Configuration for GitHub App, repository, and manifest settings |
error |
Unified error handling across all operations |
app_auth |
GitHub App JWT creation and installation token management with caching |
webhook |
Webhook signature verification and event parsing |
gitops |
Git operations for cloning, syncing, and loading manifests |
use github_app::GitHubAppConfig;
use std::path::PathBuf;
let config = GitHubAppConfig::new(
12345, // app_id
67890, // installation_id
std::fs::read_to_string("private-key.pem").unwrap(), // private_key_pem
"webhook_secret".to_string(), // webhook_secret
"owner/repo".to_string(), // repo
"main".to_string(), // branch
PathBuf::from("/tmp/repo"), // git_clone_path
"manifests/**/*.yaml".to_string(), // manifest_glob
);
config.validate()?;use github_app::GitHubTokenProvider;
let token_provider = GitHubTokenProvider::new(config.clone());
let token = token_provider.get_token().await?;The token provider automatically:
- Creates JWTs signed with your private key
- Fetches installation access tokens from GitHub
- Caches tokens and refreshes them before expiry
use github_app::{WebhookVerifier, WebhookEvent};
let verifier = WebhookVerifier::new(&config);
async fn handle_webhook(
event_type: &str,
signature: &str,
body: &[u8],
) -> Result<(), github_app::GitHubError> {
let event = verifier.parse_event(event_type, signature, body)?;
match event {
WebhookEvent::Push(push_event) => {
println!("Push to: {}", push_event.git_ref);
if let Some(branch) = push_event.branch() {
println!("Branch: {}", branch);
}
if let Some(sha) = push_event.commit_sha() {
println!("Commit: {}", sha);
}
}
WebhookEvent::Ping => {
println!("Ping event received");
}
WebhookEvent::Unknown(event_type) => {
println!("Unknown event: {}", event_type);
}
}
Ok(())
}// Extract headers and body from your HTTP framework
let event_type = request.headers().get("X-GitHub-Event").unwrap();
let signature = request.headers().get("X-Hub-Signature-256").unwrap();
let body = request.body().as_bytes();
// Verify and parse
match verifier.parse_event(event_type, signature, body) {
Ok(event) => {
// Handle event
Ok(())
}
Err(github_app::GitHubError::InvalidSignature) => {
// Return 401 Unauthorized
Err("Invalid signature")
}
Err(e) => {
// Return 500 Internal Server Error
Err(format!("Error processing webhook: {}", e))
}
}use github_app::GitHubGitOps;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct DeploymentManifest {
name: String,
version: String,
}
let gitops = GitHubGitOps::new(config.clone(), token_provider);
gitops.initialize().await?;
gitops.sync().await?;
let manifests: Vec<DeploymentManifest> = gitops.load_all_manifests()?;
for manifest in manifests {
println!("Found manifest: {} v{}", manifest.name, manifest.version);
}use github_app::{
GitHubAppConfig, GitHubTokenProvider, WebhookVerifier,
GitHubGitOps, WebhookEvent
};
use std::sync::Arc;
struct Broker {
config: GitHubAppConfig,
token_provider: Arc<GitHubTokenProvider>,
webhook_verifier: WebhookVerifier,
gitops: Arc<GitHubGitOps>,
}
impl Broker {
async fn new(config: GitHubAppConfig) -> Result<Self, github_app::GitHubError> {
config.validate()?;
let token_provider = Arc::new(GitHubTokenProvider::new(config.clone()));
let webhook_verifier = WebhookVerifier::new(&config);
let gitops = Arc::new(GitHubGitOps::new(
config.clone(),
GitHubTokenProvider::new(config.clone())
));
gitops.initialize().await?;
Ok(Self {
config,
token_provider,
webhook_verifier,
gitops,
})
}
async fn handle_webhook(
&self,
event_type: &str,
signature: &str,
body: &[u8],
) -> Result<(), github_app::GitHubError> {
let event = self.webhook_verifier.parse_event(event_type, signature, body)?;
match event {
WebhookEvent::Push(push_event) => {
if push_event.branch() == Some(self.config.branch.clone()) {
self.gitops.sync().await?;
self.reconcile().await?;
}
}
_ => {}
}
Ok(())
}
async fn reconcile(&self) -> Result<(), github_app::GitHubError> {
let manifests: Vec<DeploymentManifest> = self.gitops.load_all_manifests()?;
Ok(())
}
}Configuration for GitHub App integration.
Methods:
new(...)- Create new configurationvalidate()- Validate all required fields are present and valid
Manages GitHub App authentication and installation tokens.
Methods:
new(config: GitHubAppConfig)- Create new token providerasync get_token() -> Result<String, GitHubError>- Get valid installation token (cached)
Verifies and parses GitHub webhook events.
Methods:
new(config: &GitHubAppConfig)- Create new verifierverify_signature(signature: &str, body: &[u8]) -> Result<(), GitHubError>- Verify HMAC signatureparse_event(event_type: &str, signature: &str, body: &[u8]) -> Result<WebhookEvent, GitHubError>- Verify and parse event
Git operations for manifest management.
Methods:
new(config: GitHubAppConfig, token_provider: GitHubTokenProvider)- Create new GitOps managerasync initialize() -> Result<(), GitHubError>- Clone repository if not existsasync sync() -> Result<(), GitHubError>- Fetch and fast-forward to latest commitload_all_manifests<T: DeserializeOwned>() -> Result<Vec<T>, GitHubError>- Load and parse all manifestsrepo_path() -> &PathBuf- Get local repository path
All public APIs return Result<T, GitHubError> with variants for:
Http- HTTP/network errorsJwt- JWT creation errorsGit- Git operation errorsIo- File system errorsYaml- YAML parsing errorsJson- JSON parsing errorsInvalidSignature- Webhook signature verification failedUnknownEventType- Unknown webhook event typeConfig- Configuration validation errorsTokenExpired- Token expired or unavailablePattern- Glob pattern errorsOther- Other errors
Run tests with:
cargo testMIT