- 
        Couldn't load subscription status. 
- Fork 1
Feat add sync action #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat add sync action #165
Changes from 8 commits
b900cd1
              7af70c8
              e2dfdac
              009f698
              7c0b9bb
              64bb384
              83242a8
              52bb545
              1cdd9b0
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: "Google Sheet Sync" | ||
|  | ||
| on: | ||
| schedule: | ||
| - cron: "0 0 * * *" # Run once a day at midnight UTC | ||
| workflow_dispatch: | ||
| inputs: | ||
| spreadsheet_id: | ||
| description: "The ID of the Google Sheet to monitor." | ||
| required: true | ||
| stop_time: | ||
| description: "The UTC time (HH:MM) at which the action should stop." | ||
| required: true | ||
| stop_day: | ||
| description: "The day (YYYY-MM-DD) on which the action should stop." | ||
| required: true | ||
|  | ||
| jobs: | ||
| sync: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check out the repository | ||
| uses: actions/checkout@v3 | ||
|  | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: "3.x" | ||
|  | ||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install google-api-python-client PyGithub google-auth | ||
|  | ||
| - name: Create Google Credentials File | ||
| env: | ||
| GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} | ||
| run: | | ||
| echo "${GOOGLE_CREDENTIALS}" > google-credentials.json | ||
|  | ||
| - name: Run Google Sheet Sync | ||
| env: | ||
| INPUT_SPREADSHEET_ID: ${{ github.event.inputs.spreadsheet_id }} | ||
| INPUT_STOP_TIME: ${{ github.event.inputs.stop_time }} | ||
| INPUT_STOP_DAY: ${{ github.event.inputs.stop_day }} | ||
| GITHUB_TOKEN: ${{ secrets.RFC_TOKEN }} | ||
| run: python google-sheet-sync/sync.py | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| name: "Google Sheet Sync for RFC Integration" | ||
| description: "Syncs a Google Sheet with a GitHub repository as a CSV for the purpose of RFC Integration." | ||
| inputs: | ||
| spreadsheet_id: | ||
| description: "The ID of the Google Sheet to monitor." | ||
| required: true | ||
| stop_time: | ||
| description: "The UTC time (HH:MM) at which the action should stop." | ||
| required: true | ||
| stop_day: | ||
| description: "The day (YYYY-MM-DD) on which the action should stop." | ||
| required: true | ||
| runs: | ||
| using: "python" | ||
| main: "sync.py" | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| import os | ||
| import csv | ||
| from io import StringIO | ||
| from datetime import datetime | ||
| from google.oauth2.service_account import Credentials | ||
| from googleapiclient.discovery import build | ||
| 
      Comment on lines
    
      +5
     to 
      +6
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if you'd find the effort to switch over worth it, but I really like using gspread library to access and edit Google spreadsheets! It could make some of the functions below a bit more readable and easier to comprehend right away. | ||
| from github import Github, GithubException | ||
|  | ||
| def authenticate_google(credentials_file: str) -> build: | ||
| """ | ||
| Authenticate and create a Google Sheets API client. | ||
|  | ||
| Args: | ||
| credentials_file (str): Path to the Google service account JSON credentials file. | ||
|  | ||
| Returns: | ||
| googleapiclient.discovery.Resource: Authenticated client for interacting with Google Sheets API. | ||
| """ | ||
| creds = Credentials.from_service_account_file(credentials_file, scopes=["https://www.googleapis.com/auth/spreadsheets.readonly"]) | ||
| return build('sheets', 'v4', credentials=creds) | ||
|  | ||
| def get_sheet_name(service, spreadsheet_id: str) -> str: | ||
| """ | ||
| Retrieve the title of the Google Sheet and format it for use as a branch name. | ||
|  | ||
| Args: | ||
| service (googleapiclient.discovery.Resource): Authenticated Google Sheets API client. | ||
| spreadsheet_id (str): ID of the Google Sheet. | ||
|  | ||
| Returns: | ||
| str: Formatted sheet title with spaces replaced by dashes. | ||
| """ | ||
| sheet_metadata = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute() | ||
| return sheet_metadata.get("properties", {}).get("title", "UntitledSheet").replace(" ", "-") | ||
|  | ||
| def get_sheet_data_as_csv(service, spreadsheet_id: str, range: str = "A1:Z1000") -> str: | ||
| """ | ||
| Retrieve data from the specified Google Sheet range and return it as a CSV string. | ||
|  | ||
| Args: | ||
| service (googleapiclient.discovery.Resource): Authenticated Google Sheets API client. | ||
| spreadsheet_id (str): ID of the Google Sheet. | ||
| range (str, optional): Cell range to fetch data from. Defaults to "A1:Z1000". | ||
|  | ||
| Returns: | ||
| str: CSV formatted data from the sheet, or None if the sheet is empty. | ||
| """ | ||
| sheet = service.spreadsheets() | ||
| result = sheet.values().get(spreadsheetId=spreadsheet_id, range=range).execute() | ||
| data = result.get('values', []) | ||
| if not data: | ||
| return None | ||
| output = StringIO() | ||
| writer = csv.writer(output) | ||
| writer.writerows(data) | ||
| output.seek(0) | ||
| return output.read() | ||
|  | ||
| def main(): | ||
| spreadsheet_id = os.getenv("INPUT_SPREADSHEET_ID") | ||
| stop_time = os.getenv("INPUT_STOP_TIME") | ||
| stop_day = os.getenv("INPUT_STOP_DAY") | ||
| github_token = os.getenv("GITHUB_TOKEN") | ||
| repo_name = os.getenv("GITHUB_REPOSITORY") | ||
| credentials_file = "./google-credentials.json" # Path to dynamically created credentials file | ||
|  | ||
| service = authenticate_google(credentials_file) | ||
| sheet_name = get_sheet_name(service, spreadsheet_id) | ||
| branch_name = f"RFC-{sheet_name}" | ||
| file_path = f"RFC_DATA/{sheet_name}.csv" | ||
|  | ||
| github = Github(github_token) | ||
| repo = github.get_repo(repo_name) | ||
|  | ||
| current_day = datetime.utcnow().strftime("%Y-%m-%d") | ||
| current_time = datetime.utcnow().strftime("%H:%M") | ||
|  | ||
| if current_day > stop_day or (current_day == stop_day and current_time > stop_time): | ||
| print(f"Current day/time ({current_day} {current_time}) has exceeded the stop conditions ({stop_day} {stop_time}). Exiting.") | ||
| return | ||
|  | ||
| csv_content = get_sheet_data_as_csv(service, spreadsheet_id) | ||
| if not csv_content: | ||
| print("No data fetched from Google Sheet. Exiting.") | ||
| return | ||
|  | ||
| # Ensure branch exists | ||
| try: | ||
| repo.get_branch(branch_name) | ||
| except GithubException as e: | ||
| if e.status == 404: | ||
| main_branch = repo.get_branch("main") | ||
| repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=main_branch.commit.sha) | ||
| else: | ||
| raise | ||
|  | ||
| # Commit changes to the branch | ||
| try: | ||
| try: | ||
| # Attempt to get the file contents | ||
| contents = repo.get_contents(file_path, ref=branch_name) | ||
| # Update the file if it exists | ||
| repo.update_file( | ||
| file_path, | ||
| "Update CSV file from Google Sheet", | ||
| csv_content, | ||
| contents.sha, | ||
| branch=branch_name | ||
| ) | ||
| except GithubException as e: | ||
| # If the file doesn't exist (404), create it | ||
| if e.status == 404: | ||
| repo.create_file( | ||
| file_path, | ||
| "Create CSV file from Google Sheet", | ||
| csv_content, | ||
| branch=branch_name | ||
| ) | ||
| else: | ||
| raise # Re-raise other exceptions | ||
| except Exception as e: | ||
| print(f"Error committing changes: {e}") | ||
|  | ||
| # Create or reuse PR | ||
| try: | ||
| open_prs = repo.get_pulls(state="open", head=f"{repo.owner.login}:{branch_name}") | ||
| if open_prs.totalCount == 0: | ||
| repo.create_pull( | ||
| title=f"{branch_name} Updates", | ||
| body="This PR contains updates from the linked Google Sheet.", | ||
| head=branch_name, | ||
| base="main", | ||
| ) | ||
| except Exception as e: | ||
| print(f"Error creating PR: {e}") | ||
|  | ||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before merging, let's double-check that this step does not write the credentials to the Action logs. I'm 99.9% sure it won't print to the logs since it's a secret, but better safe than sorry 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK just verified that it does not - we good!