diff --git a/.github/workflows/merge-validated-pr.yaml b/.github/workflows/merge-validated-pr.yaml new file mode 100644 index 0000000..3eb4e43 --- /dev/null +++ b/.github/workflows/merge-validated-pr.yaml @@ -0,0 +1,97 @@ +name: Merge valid PRs + +on: + workflow_dispatch: + pull_request_target: + branches: main + paths: + - 'model-output/**' + +jobs: + validate-user-and-merge: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Read authorized user + id: read_authorized_user + uses: jaywcjlove/github-action-read-file@main + with: + branch: main + path: 'auxiliary-data/authorized_users.txt' + + - name: Get changed files + id: get_changed_files_in_model_output + uses: tj-actions/changed-files@v45 + with: + path: 'model-output' + dir_names: 'True' + + - name: Get all changed files + id: get_all_changed_files + uses: tj-actions/changed-files@v45 + + - name: Check for changes outside model-output folder + id: check_changes_outside_model_output + run: | + echo "Changed files:" + echo "${{ steps.get_all_changed_files.outputs.all_modified_files }}" + + for file in ${{ steps.get_all_changed_files.outputs.all_modified_files }}; do + if [[ "$file" != model-output/* ]]; then + echo "Error: Changes detected outside 'model-output' folder. File changed: $file" + exit 1 + fi + done + + - name: Get Token + id: get_workflow_token + uses: peter-murray/workflow-application-token-action@v4 + with: + application_id: ${{ vars.GH_APP_ID }} + application_private_key: ${{ secrets.GH_APP_KEY }} + + - name: Approve PR if changes made by authorized user + run: | + readarray -t lines_arr <<< "${{ steps.read_authorized_user.outputs.content }}" + + for line in "${lines_arr[@]}"; do + read -r dir user_list <<< $line + IFS=', ' read -r -a user_array <<< "$user_list" + + for file in ${{ steps.get_changed_files_in_model_output.outputs.all_modified_files }}; do + if [[ "$file" == "$dir" ]]; then + is_authorized=false + + for user in "${user_array[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_authorized=true + break + fi + done + + if [[ "$is_authorized" == true ]]; then + gh pr review --approve "${{ github.event.pull_request.html_url }}" + exit 0 + else + echo "Error: Only following users are allowed to change files in '$dir/': ${user_array[*]}" + exit 1 + fi + + fi + done + + done + env: + GH_TOKEN: ${{ steps.get_workflow_token.outputs.token }} + + - name: Enable auto-merge for approved PRs + run: gh pr merge --auto --squash "${{ github.event.pull_request.html_url }}" + env: + GH_TOKEN: ${{ steps.get_workflow_token.outputs.token }} \ No newline at end of file diff --git a/.github/workflows/update-authorized-users.yaml b/.github/workflows/update-authorized-users.yaml new file mode 100644 index 0000000..9ef076c --- /dev/null +++ b/.github/workflows/update-authorized-users.yaml @@ -0,0 +1,46 @@ +name: Update authorized users + +on: + workflow_dispatch: + schedule: + - cron: '30 11 * * 3' + +jobs: + update-users-list: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup R + uses: r-lib/actions/setup-r@v2 + with: + install-r: false + use-public-rspm: true + + - name: Install dependencies + run: | + install.packages(c("yaml", "purrr", "glue")) + shell: Rscript {0} + + - name: generate users list + run: | + Rscript src/code/get_authorized_users.r + + - name: Commit changes + uses: EndBug/add-and-commit@v9 + with: + message: "Update authorized users" + default_author: github_actions + push: true + new_branch: update-authorized-users + + - name: Create pull request + id: create_pr + run: | + gh pr create --base main --head update-authorized-users --title "Update authorized users" --body "This PR updates the authorized users list based on the latest metadata." + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/src/code/get_authorized_users.r b/src/code/get_authorized_users.r new file mode 100644 index 0000000..a9cb5c2 --- /dev/null +++ b/src/code/get_authorized_users.r @@ -0,0 +1,36 @@ + +hub_path <- "." +output_path <- "auxiliary-data/" + +yml_files <- list.files(file.path(hub_path, "model-metadata"), + pattern = "\\.ya?ml$", full.names = TRUE +) + +extract_metadata <- function(file) { + yml_data <- yaml::yaml.load_file(file) + team_abbr <- ifelse("team_abbr" %in% names(yml_data), yml_data$team_abbr, NA) + model_abbr <- ifelse( + "model_abbr" %in% names(yml_data), yml_data$model_abbr, NA + ) + designated_user <- ifelse( + "designated_github_user" %in% names(yml_data), + paste(yml_data$designated_github_user, collapse = ", "), + NA + ) + + return(list( + team_abbr = team_abbr, + model_abbr = model_abbr, + designated_github_users = designated_user + )) +} + +metadata_list <- purrr::map(yml_files, extract_metadata) +data_df <- do.call(rbind, lapply(metadata_list, as.data.frame)) +colnames(data_df) <- c("team_name", "model_name", "designated_users") + +output <- glue::glue( + "{data_df$team_name}-{data_df$model_name} {data_df$designated_users}" +) + +writeLines(output, file.path(output_path, "authorized_users.txt")) \ No newline at end of file