autoschematic-connector-github 0.14.0

An Autoschematic connector for GitHub repository management
Documentation
use crate::{
    GitHubConnector,
    addr::GitHubResourceAddress,
    github_ext::{
        AddCollaboratorRequest, AddTeamCollaboratorRequest, BranchProtectionOpsExt, CollaboratorOpsExt,
        CreateBranchProtectionRequest, CreateRepositoryRequest, RepositoryOpsExt, UpdateRepositoryRequest,
    },
    op::GitHubConnectorOp,
};
use anyhow::bail;
use autoschematic_core::{
    connector::{ConnectorOp, OpExecResponse, ResourceAddress},
    error_util::invalid_op,
};
use std::path::Path;

impl GitHubConnector {
    pub async fn do_op_exec(&self, addr: &Path, op: &str) -> anyhow::Result<OpExecResponse> {
        let addr = GitHubResourceAddress::from_path(addr)?;
        let op = GitHubConnectorOp::from_str(op)?;

        match &addr {
            GitHubResourceAddress::Config => Err(invalid_op(&addr, &op)),
            GitHubResourceAddress::Repository { owner, repo } => {
                let client = self.client.read().await.clone();

                match op {
                    GitHubConnectorOp::CreateRepository(repo_config) => {
                        let create_request = CreateRepositoryRequest {
                            name: repo.clone(),
                            description: repo_config.description.clone(),
                            homepage: repo_config.homepage.clone(),
                            private: repo_config.private,
                            has_issues: repo_config.has_issues,
                            has_projects: repo_config.has_projects,
                            has_wiki: repo_config.has_wiki,
                            allow_squash_merge: repo_config.allow_squash_merge,
                            allow_merge_commit: repo_config.allow_merge_commit,
                            allow_rebase_merge: repo_config.allow_rebase_merge,
                            allow_auto_merge: repo_config.allow_auto_merge,
                            delete_branch_on_merge: repo_config.delete_branch_on_merge,
                            default_branch: Some(repo_config.default_branch.clone()),
                        };

                        match client.create_repository(owner, &create_request).await {
                            Ok(_) => Ok(OpExecResponse {
                                outputs: None,
                                friendly_message: Some(format!("Created GitHub repository {}/{}", owner, repo)),
                            }),
                            Err(e) => bail!("Failed to create repository {}/{}: {}", owner, repo, e),
                        }
                    }
                    GitHubConnectorOp::UpdateRepository(new_config) => {
                        let update_request = UpdateRepositoryRequest {
                            name: None, // Can't rename via this API
                            description: new_config.description.clone(),
                            homepage: new_config.homepage.clone(),
                            private: Some(new_config.private),
                            has_issues: Some(new_config.has_issues),
                            has_projects: Some(new_config.has_projects),
                            has_wiki: Some(new_config.has_wiki),
                            allow_squash_merge: Some(new_config.allow_squash_merge),
                            allow_merge_commit: Some(new_config.allow_merge_commit),
                            allow_rebase_merge: Some(new_config.allow_rebase_merge),
                            allow_auto_merge: Some(new_config.allow_auto_merge),
                            delete_branch_on_merge: Some(new_config.delete_branch_on_merge),
                            default_branch: Some(new_config.default_branch.clone()),
                            archived: Some(new_config.archived),
                        };

                        match client.update_repository(owner, repo, &update_request).await {
                            Ok(_) => Ok(OpExecResponse {
                                outputs: None,
                                friendly_message: Some(format!("Updated GitHub repository {}/{}", owner, repo)),
                            }),
                            Err(e) => bail!("Failed to update repository {}/{}: {:#?}", owner, repo, e),
                        }
                    }
                    GitHubConnectorOp::DeleteRepository => match client.delete_repository(owner, repo).await {
                        Ok(_) => Ok(OpExecResponse {
                            outputs: None,
                            friendly_message: Some(format!("Deleted GitHub repository {}/{}", owner, repo)),
                        }),
                        Err(e) => bail!("Failed to delete repository {}/{}: {:#?}", owner, repo, e),
                    },
                    GitHubConnectorOp::AddCollaborator(principal, role) => match principal {
                        crate::resource::CollaboratorPrincipal::User(username) => {
                            let request = AddCollaboratorRequest {
                                permission: role.to_string(),
                            };
                            match client.add_collaborator(owner, repo, &username, &request).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Added user {} as {} to repository {}/{}",
                                        username,
                                        role.to_string(),
                                        owner,
                                        repo
                                    )),
                                }),
                                Err(e) => bail!("Failed to add user {} to repository {}/{}: {:#?}", username, owner, repo, e),
                            }
                        }
                        crate::resource::CollaboratorPrincipal::Team(team_slug) => {
                            let request = AddTeamCollaboratorRequest {
                                permission: role.to_string(),
                            };
                            match client.add_team_to_repository(owner, repo, &team_slug, &request).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Added team {} with {} permission to repository {}/{}",
                                        team_slug,
                                        role.to_string(),
                                        owner,
                                        repo
                                    )),
                                }),
                                Err(e) => bail!("Failed to add team {} to repository {}/{}: {:#?}", team_slug, owner, repo, e),
                            }
                        }
                    },
                    GitHubConnectorOp::UpdateCollaborator(principal, role) => match principal {
                        crate::resource::CollaboratorPrincipal::User(username) => {
                            let request = AddCollaboratorRequest {
                                permission: role.to_string(),
                            };
                            match client.update_collaborator_permission(owner, repo, &username, &request).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Updated user {} to {} role in repository {}/{}",
                                        username,
                                        role.to_string(),
                                        owner,
                                        repo
                                    )),
                                }),
                                Err(e) => bail!(
                                    "Failed to update user {} in repository {}/{}: {:#?}",
                                    username,
                                    owner,
                                    repo,
                                    e
                                ),
                            }
                        }
                        crate::resource::CollaboratorPrincipal::Team(team_slug) => {
                            let request = AddTeamCollaboratorRequest {
                                permission: role.to_string(),
                            };
                            match client.update_team_permission(owner, repo, &team_slug, &request).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Updated team {} to {} permission in repository {}/{}",
                                        team_slug,
                                        role.to_string(),
                                        owner,
                                        repo
                                    )),
                                }),
                                Err(e) => bail!(
                                    "Failed to update team {} in repository {}/{}: {:#?}",
                                    team_slug,
                                    owner,
                                    repo,
                                    e
                                ),
                            }
                        }
                    },
                    GitHubConnectorOp::RemoveCollaborator(principal) => match principal {
                        crate::resource::CollaboratorPrincipal::User(username) => {
                            match client.remove_collaborator(owner, repo, &username).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Removed user {} from repository {}/{}",
                                        username, owner, repo
                                    )),
                                }),
                                Err(e) => bail!(
                                    "Failed to remove user {} from repository {}/{}: {:#?}",
                                    username,
                                    owner,
                                    repo,
                                    e
                                ),
                            }
                        }
                        crate::resource::CollaboratorPrincipal::Team(team_slug) => {
                            match client.remove_team_from_repository(owner, repo, &team_slug).await {
                                Ok(_) => Ok(OpExecResponse {
                                    outputs: None,
                                    friendly_message: Some(format!(
                                        "Removed team {} from repository {}/{}",
                                        team_slug, owner, repo
                                    )),
                                }),
                                Err(e) => bail!(
                                    "Failed to remove team {} from repository {}/{}: {:#?}",
                                    team_slug,
                                    owner,
                                    repo,
                                    e
                                ),
                            }
                        }
                    },
                    _ => Err(invalid_op(&addr, &op)),
                }
            }
            GitHubResourceAddress::BranchProtection { owner, repo, branch } => {
                let client = self.client.read().await.clone();

                match op {
                    GitHubConnectorOp::CreateBranchProtection(protection_config) => {
                        let create_request = CreateBranchProtectionRequest {
                            required_status_checks: protection_config.required_status_checks.as_ref().map(|checks| {
                                crate::github_ext::GitHubRequiredStatusChecks {
                                    strict: checks.strict,
                                    contexts: checks.contexts.clone(),
                                }
                            }),
                            enforce_admins: protection_config.enforce_admins,
                            required_pull_request_reviews: protection_config.required_pull_request_reviews.as_ref().map(
                                |reviews| crate::github_ext::GitHubPullRequestReviewEnforcement {
                                    required_approving_review_count: Some(reviews.required_approving_review_count),
                                    dismiss_stale_reviews: Some(reviews.dismiss_stale_reviews),
                                    require_code_owner_reviews: Some(reviews.require_code_owner_reviews),
                                    require_last_push_approval: Some(reviews.require_last_push_approval),
                                },
                            ),
                            restrictions: protection_config.restrictions.as_ref().map(|restrictions| {
                                crate::github_ext::GitHubBranchRestrictions {
                                    users: restrictions
                                        .users
                                        .iter()
                                        .map(|u| crate::github_ext::GitHubUser { login: u.clone() })
                                        .collect(),
                                    teams: restrictions
                                        .teams
                                        .iter()
                                        .map(|t| crate::github_ext::GitHubTeam { name: t.clone() })
                                        .collect(),
                                    apps: restrictions
                                        .apps
                                        .iter()
                                        .map(|a| crate::github_ext::GitHubApp { name: a.clone() })
                                        .collect(),
                                }
                            }),
                            required_linear_history: Some(protection_config.required_linear_history),
                            allow_force_pushes: Some(protection_config.allow_force_pushes),
                            allow_deletions: Some(protection_config.allow_deletions),
                            block_creations: Some(protection_config.block_creations),
                            required_conversation_resolution: Some(protection_config.required_conversation_resolution),
                            lock_branch: Some(protection_config.lock_branch),
                            allow_fork_syncing: Some(protection_config.allow_fork_syncing),
                        };

                        match client.create_branch_protection(owner, repo, branch, &create_request).await {
                            Ok(_) => Ok(OpExecResponse {
                                outputs: None,
                                friendly_message: Some(format!(
                                    "Created branch protection for {}/{} branch {}",
                                    owner, repo, branch
                                )),
                            }),
                            Err(e) => bail!(
                                "Failed to create branch protection for {}/{} branch {}: {:#?}",
                                owner,
                                repo,
                                branch,
                                e
                            ),
                        }
                    }
                    GitHubConnectorOp::UpdateBranchProtection(new_config) => {
                        let update_request = CreateBranchProtectionRequest {
                            required_status_checks: new_config.required_status_checks.as_ref().map(|checks| {
                                crate::github_ext::GitHubRequiredStatusChecks {
                                    strict: checks.strict,
                                    contexts: checks.contexts.clone(),
                                }
                            }),
                            enforce_admins: new_config.enforce_admins,
                            required_pull_request_reviews: new_config.required_pull_request_reviews.as_ref().map(|reviews| {
                                crate::github_ext::GitHubPullRequestReviewEnforcement {
                                    required_approving_review_count: Some(reviews.required_approving_review_count),
                                    dismiss_stale_reviews: Some(reviews.dismiss_stale_reviews),
                                    require_code_owner_reviews: Some(reviews.require_code_owner_reviews),
                                    require_last_push_approval: Some(reviews.require_last_push_approval),
                                }
                            }),
                            restrictions: new_config.restrictions.as_ref().map(|restrictions| {
                                crate::github_ext::GitHubBranchRestrictions {
                                    users: restrictions
                                        .users
                                        .iter()
                                        .map(|u| crate::github_ext::GitHubUser { login: u.clone() })
                                        .collect(),
                                    teams: restrictions
                                        .teams
                                        .iter()
                                        .map(|t| crate::github_ext::GitHubTeam { name: t.clone() })
                                        .collect(),
                                    apps: restrictions
                                        .apps
                                        .iter()
                                        .map(|a| crate::github_ext::GitHubApp { name: a.clone() })
                                        .collect(),
                                }
                            }),
                            required_linear_history: Some(new_config.required_linear_history),
                            allow_force_pushes: Some(new_config.allow_force_pushes),
                            allow_deletions: Some(new_config.allow_deletions),
                            block_creations: Some(new_config.block_creations),
                            required_conversation_resolution: Some(new_config.required_conversation_resolution),
                            lock_branch: Some(new_config.lock_branch),
                            allow_fork_syncing: Some(new_config.allow_fork_syncing),
                        };

                        match client.update_branch_protection(owner, repo, branch, &update_request).await {
                            Ok(_) => Ok(OpExecResponse {
                                outputs: None,
                                friendly_message: Some(format!(
                                    "Updated branch protection for {}/{} branch {}",
                                    owner, repo, branch
                                )),
                            }),
                            Err(e) => bail!(
                                "Failed to update branch protection for {}/{} branch {}: {:#?}",
                                owner,
                                repo,
                                branch,
                                e
                            ),
                        }
                    }
                    GitHubConnectorOp::DeleteBranchProtection => {
                        match client.delete_branch_protection(owner, repo, branch).await {
                            Ok(_) => Ok(OpExecResponse {
                                outputs: None,
                                friendly_message: Some(format!(
                                    "Removed branch protection for {}/{} branch {}",
                                    owner, repo, branch
                                )),
                            }),
                            Err(e) => bail!(
                                "Failed to remove branch protection for {}/{} branch {}: {:#?}",
                                owner,
                                repo,
                                branch,
                                e
                            ),
                        }
                    }
                    _ => Err(invalid_op(&addr, &op)),
                }
            }
        }
    }
}