From 8d2f03551576fb0e9d5abe1f001fd46483682a2e Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 15:21:34 +0300 Subject: [PATCH 01/21] update workflow --- .../workflows/microservices-version-sync.yml | 233 +++++++++++++----- 1 file changed, 174 insertions(+), 59 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 6508c240..5df24b08 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -41,7 +41,7 @@ jobs: python3 scripts/extract_services.py --verbose - name: Upload services artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: extracted-services path: | @@ -57,12 +57,12 @@ jobs: appstate-versions: ${{ steps.query.outputs.versions }} steps: - name: Download services artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: extracted-services - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -78,7 +78,7 @@ jobs: python3 scripts/query_appstate.py --verbose - name: Upload AppState versions - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: appstate-versions path: appstate_versions.json @@ -93,17 +93,17 @@ jobs: updates-needed: ${{ steps.compare.outputs.updates-needed }} steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: extracted-services - name: Download AppState versions - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: appstate-versions - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -117,7 +117,7 @@ jobs: python3 scripts/compare_versions.py --verbose - name: Upload comparison results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: comparison-results path: comparison_results.json @@ -135,12 +135,12 @@ jobs: fetch-depth: 0 - name: Download comparison results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: comparison-results - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -181,65 +181,27 @@ jobs: git commit -F commit_msg.txt git push - create-release: - name: Create GitHub Release + trigger-deployment: + name: Trigger Deployment Workflow runs-on: ubuntu-latest needs: [compare-and-summarize, update-helm-values] if: needs.compare-and-summarize.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' + outputs: + workflowRunId: ${{ steps.trigger.outputs.workflowRunId }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Download comparison results - uses: actions/download-artifact@v3 - with: - name: comparison-results - - - name: Generate release tag - id: tag - run: | - # Generate tag based on current date and time - TAG="sync-$(date +%Y%m%d-%H%M%S)" - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "Generated tag: $TAG" - - - name: Create Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.tag.outputs.tag }} - release_name: "Microservices Version Sync - ${{ steps.tag.outputs.tag }}" - body: | - # Microservices Version Sync - - This release contains automated updates to microservice versions based on AppState production versions. - - ${{ needs.compare-and-summarize.outputs.summary }} - - ## Changes Made - - Updated Helm values files with new AppVersion values - - Synchronized versions with AppState production environment - - ## Next Steps - This release will trigger the deployment workflow to update the customer environments. - draft: false - prerelease: false - - trigger-deployment: - name: Trigger Deployment Workflow - runs-on: ubuntu-latest - needs: [compare-and-summarize, create-release] - if: needs.compare-and-summarize.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' - steps: - name: Trigger terraform-private-env workflow - uses: actions/github-script@v6 + id: trigger + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | try { + // Trigger the workflow const response = await github.rest.actions.createWorkflowDispatch({ owner: 'frontegg', repo: 'terraform-private-env', @@ -253,6 +215,26 @@ jobs: console.log('Successfully triggered deployment workflow'); console.log('Response:', response.status); + + // Wait a moment for the workflow to start + await new Promise(resolve => setTimeout(resolve, 5000)); + + // Get the latest workflow run + const runs = await github.rest.actions.listWorkflowRuns({ + owner: 'frontegg', + repo: 'terraform-private-env', + workflow_id: 'Create Customer Environment', + per_page: 1 + }); + + if (runs.data.workflow_runs.length > 0) { + const runId = runs.data.workflow_runs[0].id; + console.log('Workflow run ID:', runId); + core.setOutput('workflowRunId', runId); + } else { + console.log('No workflow runs found'); + } + } catch (error) { console.error('Failed to trigger deployment workflow:', error); @@ -271,20 +253,146 @@ jobs: console.log('Successfully triggered deployment workflow with file name'); console.log('Response:', response.status); + + // Wait and get workflow run ID + await new Promise(resolve => setTimeout(resolve, 5000)); + + const runs = await github.rest.actions.listWorkflowRuns({ + owner: 'frontegg', + repo: 'terraform-private-env', + workflow_id: 'create-customer-environment.yml', + per_page: 1 + }); + + if (runs.data.workflow_runs.length > 0) { + const runId = runs.data.workflow_runs[0].id; + console.log('Workflow run ID:', runId); + core.setOutput('workflowRunId', runId); + } + } catch (secondError) { console.error('Failed to trigger deployment workflow with file name:', secondError); throw secondError; } } + wait-for-deployment: + name: Wait for Deployment Completion + runs-on: ubuntu-latest + needs: trigger-deployment + if: needs['trigger-deployment'].outputs.workflowRunId != '' + outputs: + deploymentSuccess: ${{ steps.wait.outputs.success }} + steps: + - name: Wait for deployment workflow to complete + id: wait + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const runId = '${{ needs['trigger-deployment'].outputs.workflowRunId }}'; + const maxWaitTime = 30 * 60 * 1000; // 30 minutes + const pollInterval = 30 * 1000; // 30 seconds + const startTime = Date.now(); + + console.log(`Waiting for workflow run ${runId} to complete...`); + + while (Date.now() - startTime < maxWaitTime) { + try { + const run = await github.rest.actions.getWorkflowRun({ + owner: 'frontegg', + repo: 'terraform-private-env', + run_id: runId + }); + + const status = run.data.status; + const conclusion = run.data.conclusion; + + console.log(`Workflow status: ${status}, conclusion: ${conclusion}`); + + if (status === 'completed') { + if (conclusion === 'success') { + console.log('✅ Deployment workflow completed successfully'); + core.setOutput('success', 'true'); + return; + } else { + console.log(`❌ Deployment workflow failed with conclusion: ${conclusion}`); + core.setOutput('success', 'false'); + core.setFailed(`Deployment workflow failed: ${conclusion}`); + return; + } + } + + // Wait before next poll + await new Promise(resolve => setTimeout(resolve, pollInterval)); + + } catch (error) { + console.error('Error checking workflow status:', error); + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + } + + console.log('⏰ Timeout waiting for deployment workflow'); + core.setOutput('success', 'false'); + core.setFailed('Timeout waiting for deployment workflow to complete'); + + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [compare-and-summarize, wait-for-deployment] + if: needs['wait-for-deployment'].outputs.deploymentSuccess == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download comparison results + uses: actions/download-artifact@v4 + with: + name: comparison-results + + - name: Generate release tag + id: tag + run: | + # Generate tag based on current date and time + TAG="sync-$(date +%Y%m%d-%H%M%S)" + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Generated tag: $TAG" + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.tag }} + name: "Microservices Version Sync - ${{ steps.tag.outputs.tag }}" + body: | + # Microservices Version Sync + + This release contains automated updates to microservice versions based on AppState production versions. + + ${{ needs.compare-and-summarize.outputs.summary }} + + ## Changes Made + - Updated Helm values files with new AppVersion values + - Synchronized versions with AppState production environment + + ## Deployment Status + ✅ Deployment pipeline completed successfully + + ## Workflow Run + - Triggered deployment workflow: ${{ needs['trigger-deployment'].outputs.workflowRunId }} + draft: false + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} + notify-results: name: Notify Results runs-on: ubuntu-latest - needs: [compare-and-summarize, update-helm-values, create-release, trigger-deployment] + needs: [compare-and-summarize, update-helm-values, trigger-deployment, wait-for-deployment, create-release] if: always() steps: - name: Download comparison results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: comparison-results continue-on-error: true @@ -304,8 +412,15 @@ jobs: if [ "${{ github.event.inputs.dry_run }}" != "true" ]; then echo "- ✅ Updated Helm values files" >> $GITHUB_STEP_SUMMARY - echo "- ✅ Created GitHub release" >> $GITHUB_STEP_SUMMARY echo "- ✅ Triggered deployment workflow" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs['wait-for-deployment'].outputs.deploymentSuccess }}" == "true" ]; then + echo "- ✅ Deployment completed successfully" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Created GitHub release" >> $GITHUB_STEP_SUMMARY + else + echo "- ❌ Deployment failed or timed out" >> $GITHUB_STEP_SUMMARY + echo "- ⏭️ Skipped release creation" >> $GITHUB_STEP_SUMMARY + fi else echo "- ⏭️ Skipped updates (dry-run mode)" >> $GITHUB_STEP_SUMMARY fi From d1e6be52952156fe72c24b2fff69ade5f571177b Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 15:31:01 +0300 Subject: [PATCH 02/21] fix path --- .github/workflows/microservices-version-sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 5df24b08..c577bebd 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -33,7 +33,7 @@ jobs: - name: Install dependencies run: | - pip install -r scripts/requirements.txt + pip install -r ${{ github.workspace }}/scripts/requirements.txt - name: Extract Services id: extract From 3b137141cb3fa0cabff787acb827eb4ead674cd4 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 15:43:34 +0300 Subject: [PATCH 03/21] full path --- .github/workflows/microservices-version-sync.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index c577bebd..e773a5e6 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -68,7 +68,7 @@ jobs: - name: Install dependencies run: | - pip install -r scripts/requirements.txt + pip install -r ${{ github.workspace }}/scripts/requirements.txt - name: Query AppState Repository id: query @@ -109,7 +109,7 @@ jobs: - name: Install dependencies run: | - pip install -r scripts/requirements.txt + pip install -r ${{ github.workspace }}/scripts/requirements.txt - name: Compare Versions id: compare @@ -146,7 +146,7 @@ jobs: - name: Install dependencies run: | - pip install -r scripts/requirements.txt + pip install -r ${{ github.workspace }}/scripts/requirements.txt - name: Update Values Files run: | From 701702d5136b305b3e8ad7e554088a375f7dc235 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 16:02:12 +0300 Subject: [PATCH 04/21] test with debug --- .github/workflows/microservices-version-sync.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index e773a5e6..52048d0d 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -33,6 +33,16 @@ jobs: - name: Install dependencies run: | + echo "=== DEBUG INFO ===" + echo "Current directory: $(pwd)" + echo "GitHub workspace: ${{ github.workspace }}" + echo "Listing workspace contents:" + ls -la ${{ github.workspace }}/ + echo "Checking if scripts directory exists:" + ls -la ${{ github.workspace }}/scripts/ || echo "Scripts directory not found" + echo "Checking for requirements.txt:" + ls -la ${{ github.workspace }}/scripts/requirements.txt || echo "requirements.txt not found" + echo "=== END DEBUG ===" pip install -r ${{ github.workspace }}/scripts/requirements.txt - name: Extract Services From 6d8d6f27ba0614fb8e80081ed2b4360f259c2934 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 16:17:18 +0300 Subject: [PATCH 05/21] test version --- .../workflows/microservices-version-sync.yml | 102 +++--------------- 1 file changed, 16 insertions(+), 86 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 52048d0d..a64a6082 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -15,11 +15,13 @@ env: WORKFLOW_NAME: 'Create Customer Environment' jobs: - extract-microservices: - name: Extract Microservices and Versions + extract-query-compare: + name: Extract, Query & Compare Microservices Versions runs-on: ubuntu-latest outputs: - all-services: ${{ steps.extract.outputs.all-services }} + has-updates: ${{ steps.compare.outputs.has-updates }} + summary: ${{ steps.compare.outputs.summary }} + updates-needed: ${{ steps.compare.outputs.updates-needed }} steps: - name: Checkout helm-charts repository uses: actions/checkout@v4 @@ -30,56 +32,18 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' + cache: 'pip' + cache-dependency-path: 'scripts/requirements.txt' - name: Install dependencies run: | - echo "=== DEBUG INFO ===" - echo "Current directory: $(pwd)" - echo "GitHub workspace: ${{ github.workspace }}" - echo "Listing workspace contents:" - ls -la ${{ github.workspace }}/ - echo "Checking if scripts directory exists:" - ls -la ${{ github.workspace }}/scripts/ || echo "Scripts directory not found" - echo "Checking for requirements.txt:" - ls -la ${{ github.workspace }}/scripts/requirements.txt || echo "requirements.txt not found" - echo "=== END DEBUG ===" - pip install -r ${{ github.workspace }}/scripts/requirements.txt + pip install -r scripts/requirements.txt - name: Extract Services id: extract run: | python3 scripts/extract_services.py --verbose - - name: Upload services artifacts - uses: actions/upload-artifact@v4 - with: - name: extracted-services - path: | - core_services.json - dashboard_services.json - all_services.json - - query-appstate-versions: - name: Query AppState Production Versions - runs-on: ubuntu-latest - needs: extract-microservices - outputs: - appstate-versions: ${{ steps.query.outputs.versions }} - steps: - - name: Download services artifacts - uses: actions/download-artifact@v4 - with: - name: extracted-services - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - pip install -r ${{ github.workspace }}/scripts/requirements.txt - - name: Query AppState Repository id: query env: @@ -87,40 +51,6 @@ jobs: run: | python3 scripts/query_appstate.py --verbose - - name: Upload AppState versions - uses: actions/upload-artifact@v4 - with: - name: appstate-versions - path: appstate_versions.json - - compare-and-summarize: - name: Compare Versions and Create Summary - runs-on: ubuntu-latest - needs: [extract-microservices, query-appstate-versions] - outputs: - has-updates: ${{ steps.compare.outputs.has-updates }} - summary: ${{ steps.compare.outputs.summary }} - updates-needed: ${{ steps.compare.outputs.updates-needed }} - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - name: extracted-services - - - name: Download AppState versions - uses: actions/download-artifact@v4 - with: - name: appstate-versions - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - pip install -r ${{ github.workspace }}/scripts/requirements.txt - - name: Compare Versions id: compare run: | @@ -135,8 +65,8 @@ jobs: update-helm-values: name: Update Helm Values Files runs-on: ubuntu-latest - needs: compare-and-summarize - if: needs.compare-and-summarize.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' + needs: extract-query-compare + if: needs.extract-query-compare.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' steps: - name: Checkout repository uses: actions/checkout@v4 @@ -194,8 +124,8 @@ jobs: trigger-deployment: name: Trigger Deployment Workflow runs-on: ubuntu-latest - needs: [compare-and-summarize, update-helm-values] - if: needs.compare-and-summarize.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' + needs: [extract-query-compare, update-helm-values] + if: needs.extract-query-compare.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' outputs: workflowRunId: ${{ steps.trigger.outputs.workflowRunId }} steps: @@ -349,7 +279,7 @@ jobs: create-release: name: Create GitHub Release runs-on: ubuntu-latest - needs: [compare-and-summarize, wait-for-deployment] + needs: [extract-query-compare, wait-for-deployment] if: needs['wait-for-deployment'].outputs.deploymentSuccess == 'true' steps: - name: Checkout repository @@ -380,7 +310,7 @@ jobs: This release contains automated updates to microservice versions based on AppState production versions. - ${{ needs.compare-and-summarize.outputs.summary }} + ${{ needs.extract-query-compare.outputs.summary }} ## Changes Made - Updated Helm values files with new AppVersion values @@ -398,7 +328,7 @@ jobs: notify-results: name: Notify Results runs-on: ubuntu-latest - needs: [compare-and-summarize, update-helm-values, trigger-deployment, wait-for-deployment, create-release] + needs: [extract-query-compare, update-helm-values, trigger-deployment, wait-for-deployment, create-release] if: always() steps: - name: Download comparison results @@ -412,7 +342,7 @@ jobs: echo "# Microservices Version Sync Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - if [ "${{ needs.compare-and-summarize.outputs.has-updates }}" == "true" ]; then + if [ "${{ needs.extract-query-compare.outputs.has-updates }}" == "true" ]; then echo "## ✅ Sync Completed Successfully" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "The following actions were completed:" >> $GITHUB_STEP_SUMMARY From 79d2682d84f3def6b6f146fe21d8b539f612de19 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 16:26:41 +0300 Subject: [PATCH 06/21] fix --- scripts/README.md | 11 +++-------- scripts/query_appstate.py | 25 +++---------------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index a8df55fc..54e11f3f 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -154,14 +154,9 @@ The scripts automatically detect services in the Helm values files based on: - Presence of `name` field - Valid service structure -For AppState queries, the scripts try multiple path patterns: -- `applications/{service-name}/production-global` (primary pattern) -- `applications/{service-name}/production-global.yaml` -- `production/{service}.yaml` (fallback) -- `prod/{service}.yaml` (fallback) -- `{service}/production.yaml` (fallback) -- `services/{service}/production.yaml` (fallback) -- And more... +For AppState queries, the scripts use the correct repository structure: +- `applications/{service-name}/production-global/values.yaml` +- `applications/{service-name}/production-global/values.yml` (fallback for .yml extension) ## Contributing diff --git a/scripts/query_appstate.py b/scripts/query_appstate.py index 9d8aa898..b4a25868 100755 --- a/scripts/query_appstate.py +++ b/scripts/query_appstate.py @@ -21,29 +21,10 @@ def get_appstate_version(service_name, token, repo='frontegg/AppState', verbose= 'Accept': 'application/vnd.github.v3+json' } - # Try different possible file paths in AppState repo - # Primary pattern: applications/{service-name}/production-global + # AppState repo structure: applications/{service-name}/production-global/values.yaml possible_paths = [ - f'applications/{service_name}/production-global', - f'applications/{service_name}/production-global.yaml', - f'applications/{service_name}/production-global.yml', - # Fallback patterns for different naming conventions - f'production/{service_name}.yaml', - f'production/{service_name}.yml', - f'prod/{service_name}.yaml', - f'prod/{service_name}.yml', - f'{service_name}/production.yaml', - f'{service_name}/production.yml', - f'{service_name}/prod.yaml', - f'{service_name}/prod.yml', - f'services/{service_name}/production.yaml', - f'services/{service_name}/production.yml', - f'services/{service_name}/prod.yaml', - f'services/{service_name}/prod.yml', - f'environments/production/{service_name}.yaml', - f'environments/production/{service_name}.yml', - f'apps/{service_name}/production.yaml', - f'apps/{service_name}/production.yml' + f'applications/{service_name}/production-global/values.yaml', + f'applications/{service_name}/production-global/values.yml' ] for path in possible_paths: From edbf54cd496286a5cef329de266870b63b2c6fa7 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 15 Sep 2025 16:34:53 +0300 Subject: [PATCH 07/21] test --- scripts/query_appstate.py | 67 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/scripts/query_appstate.py b/scripts/query_appstate.py index b4a25868..c073bdbe 100755 --- a/scripts/query_appstate.py +++ b/scripts/query_appstate.py @@ -33,12 +33,31 @@ def get_appstate_version(service_name, token, repo='frontegg/AppState', verbose= print(f" Trying path: {path}") try: response = requests.get(url, headers=headers, timeout=10) - if response.status_code == 200: + if verbose: + print(f" Response status: {response.status_code}") + + if response.status_code == 401: + if verbose: + print(f" ❌ Authentication failed - check GitHub token permissions") + continue + elif response.status_code == 403: + if verbose: + print(f" ❌ Access forbidden - token may not have repository access") + continue + elif response.status_code == 404: + if verbose: + print(f" ❌ Path not found: {path}") + continue + elif response.status_code == 200: content = response.json() if content.get('type') == 'file': # Decode base64 content file_content = base64.b64decode(content['content']).decode('utf-8') + if verbose: + print(f" Found file at {path}, content length: {len(file_content)}") + print(f" First 200 chars: {file_content[:200]}...") + # Try to extract version from YAML content try: yaml_content = yaml.safe_load(file_content) @@ -46,9 +65,13 @@ def get_appstate_version(service_name, token, repo='frontegg/AppState', verbose= version_fields = ['version', 'appVersion', 'tag', 'image_tag', 'imageTag'] if isinstance(yaml_content, dict): + if verbose: + print(f" YAML keys: {list(yaml_content.keys())}") # Direct fields for field in version_fields: if field in yaml_content: + if verbose: + print(f" Found {field}: {yaml_content[field]}") return str(yaml_content[field]) # Nested structures @@ -82,11 +105,45 @@ def get_appstate_version(service_name, token, repo='frontegg/AppState', verbose= return f"found-in-{path}" except Exception as e: + if verbose: + print(f" Error accessing {path}: {str(e)}") continue return None +def test_repository_access(repo, token, verbose=False): + """Test if we can access the AppState repository""" + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } + + url = f'https://api.github.com/repos/{repo}' + try: + response = requests.get(url, headers=headers, timeout=10) + if response.status_code == 200: + if verbose: + repo_info = response.json() + print(f"✅ Repository access OK: {repo_info.get('full_name')} (private: {repo_info.get('private')})") + return True + elif response.status_code == 401: + print(f"❌ Authentication failed for {repo}. Check your GitHub token.") + return False + elif response.status_code == 403: + print(f"❌ Access forbidden to {repo}. Token may not have repository access.") + return False + elif response.status_code == 404: + print(f"❌ Repository {repo} not found or not accessible.") + return False + else: + print(f"❌ Unexpected response {response.status_code} accessing {repo}") + return False + except Exception as e: + print(f"❌ Error accessing repository {repo}: {str(e)}") + return False + + def main(): parser = argparse.ArgumentParser(description='Query AppState repository for production versions') parser.add_argument('--services-file', @@ -125,6 +182,14 @@ def main(): print(f"Loaded {len(services)} services from {args.services_file}") print(f"Querying AppState repository: {args.repo}") + # Test repository access first + if not test_repository_access(args.repo, token, args.verbose): + print("\n❌ Cannot access AppState repository. Please check:") + print(" 1. GitHub token is valid and not expired") + print(" 2. Token has access to the frontegg/AppState repository") + print(" 3. Repository name is correct") + return 1 + appstate_versions = {} print("Querying AppState repository for production versions...") From 75cdc716ce76f34dfd5911a0f9d6167b76578538 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 10:36:51 +0300 Subject: [PATCH 08/21] update --- .../workflows/microservices-version-sync.yml | 25 +++- SETUP_APPSTATE_ACCESS.md | 125 ++++++++++++++++++ 2 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 SETUP_APPSTATE_ACCESS.md diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index a64a6082..a8de0898 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -44,10 +44,19 @@ jobs: run: | python3 scripts/extract_services.py --verbose + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + owner: frontegg + repositories: AppState,terraform-private-env + - name: Query AppState Repository id: query env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | python3 scripts/query_appstate.py --verbose @@ -128,17 +137,27 @@ jobs: if: needs.extract-query-compare.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' outputs: workflowRunId: ${{ steps.trigger.outputs.workflowRunId }} + deployToken: ${{ steps.deploy-app-token.outputs.token }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Generate GitHub App Token for Deployment + id: deploy-app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + owner: frontegg + repositories: terraform-private-env + - name: Trigger terraform-private-env workflow id: trigger uses: actions/github-script@v7 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ steps.deploy-app-token.outputs.token }} script: | try { // Trigger the workflow @@ -228,7 +247,7 @@ jobs: id: wait uses: actions/github-script@v7 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ needs['trigger-deployment'].outputs.deployToken }} script: | const runId = '${{ needs['trigger-deployment'].outputs.workflowRunId }}'; const maxWaitTime = 30 * 60 * 1000; // 30 minutes diff --git a/SETUP_APPSTATE_ACCESS.md b/SETUP_APPSTATE_ACCESS.md new file mode 100644 index 00000000..99103dc9 --- /dev/null +++ b/SETUP_APPSTATE_ACCESS.md @@ -0,0 +1,125 @@ +# AppState Access Setup with GitHub App + +## Problem +The microservices version sync workflow needs access to the private `frontegg/AppState` repository to query production versions. The default `secrets.GITHUB_TOKEN` provided by GitHub Actions **cannot access other repositories**, especially private ones. + +## Solution +We use a **GitHub App** for authentication, which is more secure and provides fine-grained permissions compared to Personal Access Tokens (PATs). + +## Step-by-Step Setup + +### 1. Create a GitHub App + +1. Go to GitHub Organization Settings: https://github.com/organizations/frontegg/settings/apps +2. Click **"New GitHub App"** +3. Configure the app: + - **GitHub App name**: `helm-charts-appstate-access` + - **Description**: `Access to AppState repository for microservices version sync` + - **Homepage URL**: `https://github.com/frontegg/helm-charts` + - **Webhook**: Uncheck "Active" (we don't need webhooks) + +### 2. Set Repository Permissions + +In the **Repository permissions** section: +- **Contents**: `Read` (to read AppState files) +- **Actions**: `Write` (to trigger workflows in terraform-private-env) +- **Metadata**: `Read` (required by GitHub) + +### 3. Install the App + +1. After creating the app, go to **Install App** tab +2. Click **"Install"** next to your organization +3. Select **"Only select repositories"** +4. Choose: + - ✅ `frontegg/AppState` + - ✅ `frontegg/terraform-private-env` +5. Click **"Install"** + +### 4. Generate and Add Private Key + +1. In your GitHub App settings, scroll to **Private keys** +2. Click **"Generate a private key"** +3. Download the `.pem` file +4. Go to your helm-charts repository settings +5. Navigate to **Settings** → **Secrets and variables** → **Actions** +6. Add two secrets: + - **Name**: `APPSTATE_APP_ID` + - **Secret**: Your GitHub App ID (found in app settings) + - **Name**: `APPSTATE_APP_PRIVATE_KEY` + - **Secret**: Contents of the `.pem` file (copy entire file including headers) + +### 5. Test the Setup + +Run the workflow manually to test: +1. Go to **Actions** tab in your repository +2. Select **"OnPrem Update Charts - Microservices Version Sync"** +3. Click **"Run workflow"** +4. Enable **"Run in dry-run mode"** for testing +5. Click **"Run workflow"** + +## Troubleshooting + +### Error: "Repository frontegg/AppState not found or not accessible" +- ✅ Verify the GitHub App is installed on the `frontegg/AppState` repository +- ✅ Check that the App ID and private key are correct +- ✅ Ensure the app has `Contents: Read` permission +- ✅ Confirm the secret names are exactly `APPSTATE_APP_ID` and `APPSTATE_APP_PRIVATE_KEY` + +### Error: "Authentication failed" +- ✅ Private key may be malformed (ensure entire .pem file is copied) +- ✅ App ID may be incorrect +- ✅ Check if the GitHub App still exists and is active + +### Error: "Access forbidden" +- ✅ GitHub App may not be installed on the target repository +- ✅ App permissions may be insufficient +- ✅ Repository may require specific app installation approval + +## Security Notes + +- 🔒 **Keep the private key secure** - never commit it to code +- 🔒 **Use minimal required permissions** - only `Contents: Read` and `Actions: Write` +- 🔒 **Rotate private keys regularly** - GitHub Apps support key rotation +- 🔒 **Monitor app usage** - GitHub provides installation and usage logs +- 🔒 **Limit repository access** - only install on required repositories + +## GitHub App Permissions Required + +| Repository | Permission | Reason | +|------------|------------|---------| +| `frontegg/AppState` | Contents: Read | Query production versions from `applications/*/production-global/values.yaml` | +| `frontegg/terraform-private-env` | Actions: Write | Trigger "Create Customer Environment" workflow | +| `frontegg/helm-charts` | N/A | Uses default `GITHUB_TOKEN` for local operations | + +## Workflow Configuration + +The workflow uses GitHub App authentication for external repositories: + +```yaml +# Generate GitHub App token for external repository access +- name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APPSTATE_APP_ID }} + private-key: ${{ secrets.APPSTATE_APP_PRIVATE_KEY }} + owner: frontegg + repositories: AppState,terraform-private-env + +# Use the generated token for external operations +env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + +# Local repository operations still use default token +with: + token: ${{ secrets.GITHUB_TOKEN }} +``` + +## Advantages of GitHub Apps over PATs + +- ✅ **Fine-grained permissions** - only the permissions you need +- ✅ **Repository-scoped** - access only to specific repositories +- ✅ **Organization-managed** - centralized control and auditing +- ✅ **No user dependency** - not tied to a specific user account +- ✅ **Better security** - shorter-lived tokens, automatic rotation +- ✅ **Rate limits** - higher API rate limits than PATs From 52ca0739f255aeed11927d222cea61e3ff16582b Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 15:46:33 +0300 Subject: [PATCH 09/21] debug --- .../workflows/microservices-version-sync.yml | 128 ++++++++++++ scripts/query_appstate.py | 14 +- scripts/requirements.txt | 1 + scripts/test_github_app.py | 168 ++++++++++++++++ scripts/test_github_app_simple.sh | 73 +++++++ scripts/verify_github_app_setup.py | 186 ++++++++++++++++++ 6 files changed, 566 insertions(+), 4 deletions(-) create mode 100644 scripts/test_github_app.py create mode 100755 scripts/test_github_app_simple.sh create mode 100644 scripts/verify_github_app_setup.py diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index a8de0898..f54ccb45 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -52,6 +52,134 @@ jobs: private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} owner: frontegg repositories: AppState,terraform-private-env + continue-on-error: false + + - name: Verify Token Generation + if: steps.app-token.outcome == 'failure' + run: | + echo "🚨 CRITICAL ERROR: GitHub App token generation failed!" + echo "❌ This indicates a problem with your GitHub App configuration." + echo "" + echo "🔧 Please check:" + echo "1. GH_APP_ID secret exists and contains the correct App ID (numeric)" + echo "2. GH_APP_PRIVATE_KEY secret exists and contains the full PEM private key" + echo "3. GitHub App exists and is accessible" + echo "4. Private key matches the GitHub App" + echo "" + echo "💡 Common issues:" + echo "- Private key missing '-----BEGIN PRIVATE KEY-----' header" + echo "- Private key missing '-----END PRIVATE KEY-----' footer" + echo "- App ID is incorrect or doesn't exist" + echo "- Private key was regenerated but secret not updated" + exit 1 + + - name: Debug GitHub App Token + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + echo "=== GitHub App Token Debug Information ===" + echo "Token length: ${#GITHUB_TOKEN}" + echo "Token prefix: $(echo $GITHUB_TOKEN | cut -c1-20)..." + echo "Token type: $(echo $GITHUB_TOKEN | cut -c1-4)" + echo "" + + # Set flag to track if any test fails + TESTS_FAILED=false + + echo "=== Testing GitHub API Access ===" + # Test authentication + echo "1. Testing authentication..." + AUTH_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/user) + HTTP_CODE="${AUTH_RESPONSE: -3}" + echo "Auth test HTTP code: $HTTP_CODE" + + if [ "$HTTP_CODE" != "200" ]; then + echo "❌ Authentication failed" + echo "Response: $(echo $AUTH_RESPONSE | head -c -4)" + TESTS_FAILED=true + else + echo "✅ Authentication successful" + echo "User: $(echo $AUTH_RESPONSE | head -c -4 | jq -r '.login // "unknown"')" + fi + echo "" + + # Test repository access + echo "2. Testing repository access to frontegg/AppState..." + REPO_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/frontegg/AppState) + HTTP_CODE="${REPO_RESPONSE: -3}" + echo "Repository access HTTP code: $HTTP_CODE" + + if [ "$HTTP_CODE" != "200" ]; then + echo "❌ Repository access failed" + echo "Response: $(echo $REPO_RESPONSE | head -c -4 | jq -r '.message // "No message"')" + TESTS_FAILED=true + else + echo "✅ Repository access successful" + REPO_DATA=$(echo $REPO_RESPONSE | head -c -4) + echo "Repository: $(echo $REPO_DATA | jq -r '.full_name')" + echo "Private: $(echo $REPO_DATA | jq -r '.private')" + echo "Permissions: $(echo $REPO_DATA | jq -r '.permissions // "No permissions info"')" + fi + echo "" + + # Test specific file access + echo "3. Testing file access..." + FILE_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/frontegg/AppState/contents/applications/admins-service/production-global/values.yaml) + HTTP_CODE="${FILE_RESPONSE: -3}" + echo "File access HTTP code: $HTTP_CODE" + + if [ "$HTTP_CODE" != "200" ]; then + echo "❌ File access failed" + echo "Response: $(echo $FILE_RESPONSE | head -c -4 | jq -r '.message // "No message"')" + TESTS_FAILED=true + else + echo "✅ File access successful" + echo "File found: applications/admins-service/production-global/values.yaml" + fi + echo "" + + # Test GitHub App installation + echo "4. Testing GitHub App installation..." + INSTALL_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/frontegg/AppState/installation) + HTTP_CODE="${INSTALL_RESPONSE: -3}" + echo "Installation check HTTP code: $HTTP_CODE" + + if [ "$HTTP_CODE" != "200" ]; then + echo "❌ Installation check failed" + echo "Response: $(echo $INSTALL_RESPONSE | head -c -4 | jq -r '.message // "No message"')" + TESTS_FAILED=true + else + echo "✅ Installation found" + INSTALL_DATA=$(echo $INSTALL_RESPONSE | head -c -4) + echo "Installation ID: $(echo $INSTALL_DATA | jq -r '.id')" + echo "App ID: $(echo $INSTALL_DATA | jq -r '.app_id')" + echo "Permissions: $(echo $INSTALL_DATA | jq -r '.permissions')" + fi + + echo "" + echo "=== Debug Summary ===" + if [ "$TESTS_FAILED" = true ]; then + echo "❌ One or more authentication tests failed!" + echo "🛑 STOPPING WORKFLOW - Fix GitHub App configuration before proceeding" + echo "" + echo "Common fixes:" + echo "1. Check GH_APP_ID secret is correct (numeric value)" + echo "2. Check GH_APP_PRIVATE_KEY secret contains full PEM content" + echo "3. Verify GitHub App is installed on frontegg organization" + echo "4. Ensure GitHub App has Contents:Read and Metadata:Read permissions" + exit 1 + else + echo "✅ All authentication tests passed!" + echo "🚀 Proceeding with AppState queries..." + fi - name: Query AppState Repository id: query diff --git a/scripts/query_appstate.py b/scripts/query_appstate.py index c073bdbe..b54ce4b2 100755 --- a/scripts/query_appstate.py +++ b/scripts/query_appstate.py @@ -184,10 +184,16 @@ def main(): # Test repository access first if not test_repository_access(args.repo, token, args.verbose): - print("\n❌ Cannot access AppState repository. Please check:") - print(" 1. GitHub token is valid and not expired") - print(" 2. Token has access to the frontegg/AppState repository") - print(" 3. Repository name is correct") + print("\n🚨 CRITICAL ERROR: Cannot access AppState repository!") + print("❌ This is likely a GitHub App authentication/permission issue.") + print("\n🔧 Please check:") + print(" 1. GitHub App ID (GH_APP_ID) is correct") + print(" 2. GitHub App Private Key (GH_APP_PRIVATE_KEY) contains full PEM content") + print(" 3. GitHub App is installed on the frontegg organization") + print(" 4. GitHub App has Contents:Read and Metadata:Read permissions") + print(" 5. GitHub App has access to the AppState repository") + print(f"\n📍 Repository being accessed: {args.repo}") + print("💡 Run the workflow's debug step for detailed diagnostics") return 1 appstate_versions = {} diff --git a/scripts/requirements.txt b/scripts/requirements.txt index a872969a..a19c9937 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,2 +1,3 @@ pyyaml>=6.0 requests>=2.28.0 +PyJWT>=2.8.0 diff --git a/scripts/test_github_app.py b/scripts/test_github_app.py new file mode 100644 index 00000000..5f6110ae --- /dev/null +++ b/scripts/test_github_app.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +""" +Test GitHub App configuration and permissions. +This script helps debug GitHub App issues before running the full workflow. +""" + +import os +import sys +import requests +import json +import jwt +import time +from datetime import datetime, timedelta + +def generate_jwt_token(app_id, private_key_path): + """Generate JWT token for GitHub App authentication""" + try: + with open(private_key_path, 'r') as f: + private_key = f.read() + except FileNotFoundError: + print(f"❌ Private key file not found: {private_key_path}") + return None + + # Create JWT payload + now = datetime.utcnow() + payload = { + 'iat': int(now.timestamp()), + 'exp': int((now + timedelta(minutes=10)).timestamp()), + 'iss': app_id + } + + try: + token = jwt.encode(payload, private_key, algorithm='RS256') + return token + except Exception as e: + print(f"❌ Failed to generate JWT token: {e}") + return None + +def get_installation_token(jwt_token, installation_id): + """Get installation access token using JWT""" + headers = { + 'Authorization': f'Bearer {jwt_token}', + 'Accept': 'application/vnd.github.v3+json' + } + + url = f'https://api.github.com/app/installations/{installation_id}/access_tokens' + response = requests.post(url, headers=headers) + + if response.status_code == 201: + return response.json()['token'] + else: + print(f"❌ Failed to get installation token: {response.status_code}") + print(f"Response: {response.text}") + return None + +def test_github_app(app_id, private_key_path, installation_id=None): + """Test GitHub App configuration""" + print("🔍 Testing GitHub App Configuration") + print("=" * 50) + + # Step 1: Generate JWT token + print("1. Generating JWT token...") + jwt_token = generate_jwt_token(app_id, private_key_path) + if not jwt_token: + return False + print("✅ JWT token generated successfully") + + # Step 2: Test JWT token + print("\n2. Testing JWT token...") + headers = { + 'Authorization': f'Bearer {jwt_token}', + 'Accept': 'application/vnd.github.v3+json' + } + + response = requests.get('https://api.github.com/app', headers=headers) + if response.status_code == 200: + app_info = response.json() + print(f"✅ GitHub App authenticated: {app_info['name']}") + print(f" App ID: {app_info['id']}") + print(f" Owner: {app_info['owner']['login']}") + else: + print(f"❌ JWT authentication failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + # Step 3: List installations + print("\n3. Listing installations...") + response = requests.get('https://api.github.com/app/installations', headers=headers) + if response.status_code == 200: + installations = response.json() + print(f"✅ Found {len(installations)} installation(s)") + + for install in installations: + print(f" Installation ID: {install['id']}") + print(f" Account: {install['account']['login']}") + print(f" Type: {install['account']['type']}") + + # If no specific installation ID provided, use the first one + if installation_id is None: + installation_id = install['id'] + print(f" Using installation ID: {installation_id}") + else: + print(f"❌ Failed to list installations: {response.status_code}") + return False + + if not installation_id: + print("❌ No installation ID available") + return False + + # Step 4: Get installation token + print(f"\n4. Getting installation token for ID {installation_id}...") + install_token = get_installation_token(jwt_token, installation_id) + if not install_token: + return False + print("✅ Installation token obtained") + + # Step 5: Test repository access + print("\n5. Testing repository access...") + headers = { + 'Authorization': f'token {install_token}', + 'Accept': 'application/vnd.github.v3+json' + } + + # Test AppState repository access + response = requests.get('https://api.github.com/repos/frontegg/AppState', headers=headers) + if response.status_code == 200: + repo_info = response.json() + print(f"✅ AppState repository access: {repo_info['full_name']}") + print(f" Private: {repo_info['private']}") + if 'permissions' in repo_info: + print(f" Permissions: {repo_info['permissions']}") + else: + print(f"❌ AppState repository access failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + # Step 6: Test file access + print("\n6. Testing file access...") + file_url = 'https://api.github.com/repos/frontegg/AppState/contents/applications/admins-service/production-global/values.yaml' + response = requests.get(file_url, headers=headers) + if response.status_code == 200: + print("✅ File access successful") + file_info = response.json() + print(f" File size: {file_info['size']} bytes") + else: + print(f"❌ File access failed: {response.status_code}") + print(f"Response: {response.text}") + return False + + print("\n🎉 All tests passed! GitHub App is configured correctly.") + return True + +def main(): + """Main function""" + import argparse + + parser = argparse.ArgumentParser(description='Test GitHub App configuration') + parser.add_argument('--app-id', required=True, help='GitHub App ID') + parser.add_argument('--private-key', required=True, help='Path to private key file') + parser.add_argument('--installation-id', type=int, help='Installation ID (optional)') + + args = parser.parse_args() + + success = test_github_app(args.app_id, args.private_key, args.installation_id) + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/scripts/test_github_app_simple.sh b/scripts/test_github_app_simple.sh new file mode 100755 index 00000000..bb870529 --- /dev/null +++ b/scripts/test_github_app_simple.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Simple GitHub App test script +# Usage: ./test_github_app_simple.sh + +set -e + +if [ $# -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 1960250 /path/to/private-key.pem" + exit 1 +fi + +APP_ID="$1" +PRIVATE_KEY_FILE="$2" + +echo "🔍 Testing GitHub App Configuration" +echo "==================================" +echo "App ID: $APP_ID" +echo "Private Key File: $PRIVATE_KEY_FILE" +echo "" + +# Check if private key file exists +if [ ! -f "$PRIVATE_KEY_FILE" ]; then + echo "❌ Private key file not found: $PRIVATE_KEY_FILE" + exit 1 +fi + +# Check private key format +echo "1. Checking private key format..." +if ! grep -q "BEGIN.*PRIVATE KEY" "$PRIVATE_KEY_FILE"; then + echo "❌ Private key file doesn't contain PEM header" + echo " Expected: -----BEGIN PRIVATE KEY-----" + exit 1 +fi + +if ! grep -q "END.*PRIVATE KEY" "$PRIVATE_KEY_FILE"; then + echo "❌ Private key file doesn't contain PEM footer" + echo " Expected: -----END PRIVATE KEY-----" + exit 1 +fi + +echo "✅ Private key format looks correct" + +# Test with GitHub CLI if available +if command -v gh &> /dev/null; then + echo "" + echo "2. Testing with GitHub CLI..." + + # Try to authenticate with the app (this won't work directly, but will validate the key format) + echo " Note: This test validates the key format, not full app authentication" + echo "✅ GitHub CLI is available for additional testing" +else + echo "" + echo "2. GitHub CLI not available, skipping CLI tests" +fi + +echo "" +echo "3. Key content preview:" +echo " First line: $(head -n1 "$PRIVATE_KEY_FILE")" +echo " Last line: $(tail -n1 "$PRIVATE_KEY_FILE")" +echo " Total lines: $(wc -l < "$PRIVATE_KEY_FILE")" + +echo "" +echo "✅ Basic validation complete!" +echo "" +echo "📋 Next steps:" +echo "1. Ensure these values are set as GitHub repository secrets:" +echo " - GH_APP_ID = $APP_ID" +echo " - GH_APP_PRIVATE_KEY = [full content of $PRIVATE_KEY_FILE]" +echo "" +echo "2. Verify GitHub App is installed on frontegg organization" +echo "3. Check GitHub App permissions (Contents: Read, Metadata: Read)" +echo "4. Run the workflow to test the full authentication flow" diff --git a/scripts/verify_github_app_setup.py b/scripts/verify_github_app_setup.py new file mode 100644 index 00000000..6818ca04 --- /dev/null +++ b/scripts/verify_github_app_setup.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +Verify GitHub App setup and configuration. +This script helps you verify your GitHub App secrets and configuration. +""" + +import os +import re +import sys + +def verify_app_id(app_id_str): + """Verify App ID format""" + print("🔍 Verifying GitHub App ID...") + + if not app_id_str: + print("❌ App ID is empty or not provided") + return False + + if not app_id_str.isdigit(): + print(f"❌ App ID should be numeric, got: {app_id_str}") + return False + + app_id = int(app_id_str) + if app_id < 1: + print(f"❌ App ID should be positive, got: {app_id}") + return False + + print(f"✅ App ID format is valid: {app_id}") + return True + +def verify_private_key(private_key_content): + """Verify private key format""" + print("\n🔍 Verifying GitHub App Private Key...") + + if not private_key_content: + print("❌ Private key is empty or not provided") + return False + + # Check for PEM format + if not private_key_content.startswith('-----BEGIN'): + print("❌ Private key must start with '-----BEGIN PRIVATE KEY-----' or similar") + print(f" Current start: {private_key_content[:50]}...") + return False + + if not private_key_content.rstrip().endswith('-----'): + print("❌ Private key must end with '-----END PRIVATE KEY-----' or similar") + print(f" Current end: ...{private_key_content.rstrip()[-50:]}") + return False + + # Check for common PEM headers + valid_headers = [ + '-----BEGIN PRIVATE KEY-----', + '-----BEGIN RSA PRIVATE KEY-----', + '-----BEGIN EC PRIVATE KEY-----' + ] + + header_found = False + for header in valid_headers: + if header in private_key_content: + header_found = True + print(f"✅ Found valid PEM header: {header}") + break + + if not header_found: + print("❌ No valid PEM header found") + print(" Expected one of:", valid_headers) + return False + + # Check for corresponding footer + if '-----BEGIN PRIVATE KEY-----' in private_key_content: + if '-----END PRIVATE KEY-----' not in private_key_content: + print("❌ Missing '-----END PRIVATE KEY-----' footer") + return False + elif '-----BEGIN RSA PRIVATE KEY-----' in private_key_content: + if '-----END RSA PRIVATE KEY-----' not in private_key_content: + print("❌ Missing '-----END RSA PRIVATE KEY-----' footer") + return False + elif '-----BEGIN EC PRIVATE KEY-----' in private_key_content: + if '-----END EC PRIVATE KEY-----' not in private_key_content: + print("❌ Missing '-----END EC PRIVATE KEY-----' footer") + return False + + # Check key length (should have base64 content) + lines = private_key_content.strip().split('\n') + content_lines = [line for line in lines if not line.startswith('-----')] + + if len(content_lines) < 5: + print("❌ Private key seems too short (less than 5 lines of content)") + return False + + # Check base64 format of content lines + base64_pattern = re.compile(r'^[A-Za-z0-9+/=]+$') + for i, line in enumerate(content_lines[:3]): # Check first 3 content lines + if not base64_pattern.match(line): + print(f"❌ Line {i+1} doesn't look like valid base64: {line[:20]}...") + return False + + print(f"✅ Private key format appears valid ({len(content_lines)} content lines)") + return True + +def check_workflow_secrets_usage(): + """Check how secrets are used in the workflow""" + print("\n🔍 Checking workflow secrets usage...") + + workflow_path = '.github/workflows/microservices-version-sync.yml' + if not os.path.exists(workflow_path): + print(f"❌ Workflow file not found: {workflow_path}") + return False + + with open(workflow_path, 'r') as f: + content = f.read() + + # Check for correct secret references + if '${{ secrets.GH_APP_ID }}' not in content: + print("❌ GH_APP_ID secret not found in workflow") + return False + else: + print("✅ GH_APP_ID secret reference found") + + if '${{ secrets.GH_APP_PRIVATE_KEY }}' not in content: + print("❌ GH_APP_PRIVATE_KEY secret not found in workflow") + return False + else: + print("✅ GH_APP_PRIVATE_KEY secret reference found") + + # Check for GitHub App token generation + if 'actions/create-github-app-token@v1' not in content: + print("❌ GitHub App token generation action not found") + return False + else: + print("✅ GitHub App token generation action found") + + return True + +def main(): + """Main verification function""" + print("🔧 GitHub App Configuration Verification") + print("=" * 50) + + # Get inputs + app_id = input("Enter your GitHub App ID: ").strip() + + print("\nEnter your private key content (paste the entire PEM content, then press Enter twice):") + private_key_lines = [] + while True: + try: + line = input() + if line == "" and private_key_lines: + break + private_key_lines.append(line) + except EOFError: + break + + private_key = '\n'.join(private_key_lines) + + print("\n" + "=" * 50) + + # Run verifications + app_id_valid = verify_app_id(app_id) + private_key_valid = verify_private_key(private_key) + workflow_valid = check_workflow_secrets_usage() + + print("\n" + "=" * 50) + print("📋 VERIFICATION SUMMARY") + print("=" * 50) + + print(f"App ID: {'✅ Valid' if app_id_valid else '❌ Invalid'}") + print(f"Private Key: {'✅ Valid' if private_key_valid else '❌ Invalid'}") + print(f"Workflow Config: {'✅ Valid' if workflow_valid else '❌ Invalid'}") + + if app_id_valid and private_key_valid and workflow_valid: + print("\n🎉 All verifications passed!") + print("\nNext steps:") + print("1. Ensure these exact values are set as GitHub repository secrets:") + print(f" - GH_APP_ID = {app_id}") + print(" - GH_APP_PRIVATE_KEY = [the full PEM content you provided]") + print("2. Verify GitHub App is installed on the frontegg organization") + print("3. Check GitHub App permissions (Contents: Read, Metadata: Read)") + return True + else: + print("\n❌ Some verifications failed. Please fix the issues above.") + return False + +if __name__ == '__main__': + success = main() + sys.exit(0 if success else 1) From a1d37778e977a357a099cb0d5b1ab6ee73f84cb9 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:00:41 +0300 Subject: [PATCH 10/21] test --- .../workflows/microservices-version-sync.yml | 130 +----------------- 1 file changed, 2 insertions(+), 128 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index f54ccb45..1f083a99 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -50,136 +50,9 @@ jobs: with: app-id: ${{ secrets.GH_APP_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }} owner: frontegg repositories: AppState,terraform-private-env - continue-on-error: false - - - name: Verify Token Generation - if: steps.app-token.outcome == 'failure' - run: | - echo "🚨 CRITICAL ERROR: GitHub App token generation failed!" - echo "❌ This indicates a problem with your GitHub App configuration." - echo "" - echo "🔧 Please check:" - echo "1. GH_APP_ID secret exists and contains the correct App ID (numeric)" - echo "2. GH_APP_PRIVATE_KEY secret exists and contains the full PEM private key" - echo "3. GitHub App exists and is accessible" - echo "4. Private key matches the GitHub App" - echo "" - echo "💡 Common issues:" - echo "- Private key missing '-----BEGIN PRIVATE KEY-----' header" - echo "- Private key missing '-----END PRIVATE KEY-----' footer" - echo "- App ID is incorrect or doesn't exist" - echo "- Private key was regenerated but secret not updated" - exit 1 - - - name: Debug GitHub App Token - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - echo "=== GitHub App Token Debug Information ===" - echo "Token length: ${#GITHUB_TOKEN}" - echo "Token prefix: $(echo $GITHUB_TOKEN | cut -c1-20)..." - echo "Token type: $(echo $GITHUB_TOKEN | cut -c1-4)" - echo "" - - # Set flag to track if any test fails - TESTS_FAILED=false - - echo "=== Testing GitHub API Access ===" - # Test authentication - echo "1. Testing authentication..." - AUTH_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/user) - HTTP_CODE="${AUTH_RESPONSE: -3}" - echo "Auth test HTTP code: $HTTP_CODE" - - if [ "$HTTP_CODE" != "200" ]; then - echo "❌ Authentication failed" - echo "Response: $(echo $AUTH_RESPONSE | head -c -4)" - TESTS_FAILED=true - else - echo "✅ Authentication successful" - echo "User: $(echo $AUTH_RESPONSE | head -c -4 | jq -r '.login // "unknown"')" - fi - echo "" - - # Test repository access - echo "2. Testing repository access to frontegg/AppState..." - REPO_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/frontegg/AppState) - HTTP_CODE="${REPO_RESPONSE: -3}" - echo "Repository access HTTP code: $HTTP_CODE" - - if [ "$HTTP_CODE" != "200" ]; then - echo "❌ Repository access failed" - echo "Response: $(echo $REPO_RESPONSE | head -c -4 | jq -r '.message // "No message"')" - TESTS_FAILED=true - else - echo "✅ Repository access successful" - REPO_DATA=$(echo $REPO_RESPONSE | head -c -4) - echo "Repository: $(echo $REPO_DATA | jq -r '.full_name')" - echo "Private: $(echo $REPO_DATA | jq -r '.private')" - echo "Permissions: $(echo $REPO_DATA | jq -r '.permissions // "No permissions info"')" - fi - echo "" - - # Test specific file access - echo "3. Testing file access..." - FILE_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/frontegg/AppState/contents/applications/admins-service/production-global/values.yaml) - HTTP_CODE="${FILE_RESPONSE: -3}" - echo "File access HTTP code: $HTTP_CODE" - - if [ "$HTTP_CODE" != "200" ]; then - echo "❌ File access failed" - echo "Response: $(echo $FILE_RESPONSE | head -c -4 | jq -r '.message // "No message"')" - TESTS_FAILED=true - else - echo "✅ File access successful" - echo "File found: applications/admins-service/production-global/values.yaml" - fi - echo "" - - # Test GitHub App installation - echo "4. Testing GitHub App installation..." - INSTALL_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/frontegg/AppState/installation) - HTTP_CODE="${INSTALL_RESPONSE: -3}" - echo "Installation check HTTP code: $HTTP_CODE" - - if [ "$HTTP_CODE" != "200" ]; then - echo "❌ Installation check failed" - echo "Response: $(echo $INSTALL_RESPONSE | head -c -4 | jq -r '.message // "No message"')" - TESTS_FAILED=true - else - echo "✅ Installation found" - INSTALL_DATA=$(echo $INSTALL_RESPONSE | head -c -4) - echo "Installation ID: $(echo $INSTALL_DATA | jq -r '.id')" - echo "App ID: $(echo $INSTALL_DATA | jq -r '.app_id')" - echo "Permissions: $(echo $INSTALL_DATA | jq -r '.permissions')" - fi - - echo "" - echo "=== Debug Summary ===" - if [ "$TESTS_FAILED" = true ]; then - echo "❌ One or more authentication tests failed!" - echo "🛑 STOPPING WORKFLOW - Fix GitHub App configuration before proceeding" - echo "" - echo "Common fixes:" - echo "1. Check GH_APP_ID secret is correct (numeric value)" - echo "2. Check GH_APP_PRIVATE_KEY secret contains full PEM content" - echo "3. Verify GitHub App is installed on frontegg organization" - echo "4. Ensure GitHub App has Contents:Read and Metadata:Read permissions" - exit 1 - else - echo "✅ All authentication tests passed!" - echo "🚀 Proceeding with AppState queries..." - fi - name: Query AppState Repository id: query @@ -278,6 +151,7 @@ jobs: with: app-id: ${{ secrets.GH_APP_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }} owner: frontegg repositories: terraform-private-env From f5ecd465ee142f3799faf302e86b4503a5dcae90 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:17:19 +0300 Subject: [PATCH 11/21] fix --- .../workflows/microservices-version-sync.yml | 28 +-- scripts/test_github_app.py | 168 ---------------- scripts/test_github_app_simple.sh | 73 ------- scripts/test_local.py | 183 ----------------- scripts/verify_github_app_setup.py | 186 ------------------ 5 files changed, 14 insertions(+), 624 deletions(-) delete mode 100644 scripts/test_github_app.py delete mode 100755 scripts/test_github_app_simple.sh delete mode 100755 scripts/test_local.py delete mode 100644 scripts/verify_github_app_setup.py diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 1f083a99..0fdb9023 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -121,12 +121,12 @@ jobs: echo "" >> commit_msg.txt echo "Updated services:" >> commit_msg.txt python3 -c " - import json - with open('comparison_results.json', 'r') as f: - results = json.load(f) - for update in results['updates_needed']: - print(f'- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}') - " >> commit_msg.txt +import json +with open('comparison_results.json', 'r') as f: + results = json.load(f) +for update in results['updates_needed']: + print(f'- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}') +" >> commit_msg.txt git commit -F commit_msg.txt git push @@ -398,12 +398,12 @@ jobs: echo "## Detailed Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY python3 -c " - import json - try: - with open('comparison_results.json', 'r') as f: - results = json.load(f) - print(results['summary']) - except: - print('Summary not available') - " >> $GITHUB_STEP_SUMMARY +import json +try: + with open('comparison_results.json', 'r') as f: + results = json.load(f) + print(results['summary']) +except: + print('Summary not available') +" >> $GITHUB_STEP_SUMMARY fi diff --git a/scripts/test_github_app.py b/scripts/test_github_app.py deleted file mode 100644 index 5f6110ae..00000000 --- a/scripts/test_github_app.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 -""" -Test GitHub App configuration and permissions. -This script helps debug GitHub App issues before running the full workflow. -""" - -import os -import sys -import requests -import json -import jwt -import time -from datetime import datetime, timedelta - -def generate_jwt_token(app_id, private_key_path): - """Generate JWT token for GitHub App authentication""" - try: - with open(private_key_path, 'r') as f: - private_key = f.read() - except FileNotFoundError: - print(f"❌ Private key file not found: {private_key_path}") - return None - - # Create JWT payload - now = datetime.utcnow() - payload = { - 'iat': int(now.timestamp()), - 'exp': int((now + timedelta(minutes=10)).timestamp()), - 'iss': app_id - } - - try: - token = jwt.encode(payload, private_key, algorithm='RS256') - return token - except Exception as e: - print(f"❌ Failed to generate JWT token: {e}") - return None - -def get_installation_token(jwt_token, installation_id): - """Get installation access token using JWT""" - headers = { - 'Authorization': f'Bearer {jwt_token}', - 'Accept': 'application/vnd.github.v3+json' - } - - url = f'https://api.github.com/app/installations/{installation_id}/access_tokens' - response = requests.post(url, headers=headers) - - if response.status_code == 201: - return response.json()['token'] - else: - print(f"❌ Failed to get installation token: {response.status_code}") - print(f"Response: {response.text}") - return None - -def test_github_app(app_id, private_key_path, installation_id=None): - """Test GitHub App configuration""" - print("🔍 Testing GitHub App Configuration") - print("=" * 50) - - # Step 1: Generate JWT token - print("1. Generating JWT token...") - jwt_token = generate_jwt_token(app_id, private_key_path) - if not jwt_token: - return False - print("✅ JWT token generated successfully") - - # Step 2: Test JWT token - print("\n2. Testing JWT token...") - headers = { - 'Authorization': f'Bearer {jwt_token}', - 'Accept': 'application/vnd.github.v3+json' - } - - response = requests.get('https://api.github.com/app', headers=headers) - if response.status_code == 200: - app_info = response.json() - print(f"✅ GitHub App authenticated: {app_info['name']}") - print(f" App ID: {app_info['id']}") - print(f" Owner: {app_info['owner']['login']}") - else: - print(f"❌ JWT authentication failed: {response.status_code}") - print(f"Response: {response.text}") - return False - - # Step 3: List installations - print("\n3. Listing installations...") - response = requests.get('https://api.github.com/app/installations', headers=headers) - if response.status_code == 200: - installations = response.json() - print(f"✅ Found {len(installations)} installation(s)") - - for install in installations: - print(f" Installation ID: {install['id']}") - print(f" Account: {install['account']['login']}") - print(f" Type: {install['account']['type']}") - - # If no specific installation ID provided, use the first one - if installation_id is None: - installation_id = install['id'] - print(f" Using installation ID: {installation_id}") - else: - print(f"❌ Failed to list installations: {response.status_code}") - return False - - if not installation_id: - print("❌ No installation ID available") - return False - - # Step 4: Get installation token - print(f"\n4. Getting installation token for ID {installation_id}...") - install_token = get_installation_token(jwt_token, installation_id) - if not install_token: - return False - print("✅ Installation token obtained") - - # Step 5: Test repository access - print("\n5. Testing repository access...") - headers = { - 'Authorization': f'token {install_token}', - 'Accept': 'application/vnd.github.v3+json' - } - - # Test AppState repository access - response = requests.get('https://api.github.com/repos/frontegg/AppState', headers=headers) - if response.status_code == 200: - repo_info = response.json() - print(f"✅ AppState repository access: {repo_info['full_name']}") - print(f" Private: {repo_info['private']}") - if 'permissions' in repo_info: - print(f" Permissions: {repo_info['permissions']}") - else: - print(f"❌ AppState repository access failed: {response.status_code}") - print(f"Response: {response.text}") - return False - - # Step 6: Test file access - print("\n6. Testing file access...") - file_url = 'https://api.github.com/repos/frontegg/AppState/contents/applications/admins-service/production-global/values.yaml' - response = requests.get(file_url, headers=headers) - if response.status_code == 200: - print("✅ File access successful") - file_info = response.json() - print(f" File size: {file_info['size']} bytes") - else: - print(f"❌ File access failed: {response.status_code}") - print(f"Response: {response.text}") - return False - - print("\n🎉 All tests passed! GitHub App is configured correctly.") - return True - -def main(): - """Main function""" - import argparse - - parser = argparse.ArgumentParser(description='Test GitHub App configuration') - parser.add_argument('--app-id', required=True, help='GitHub App ID') - parser.add_argument('--private-key', required=True, help='Path to private key file') - parser.add_argument('--installation-id', type=int, help='Installation ID (optional)') - - args = parser.parse_args() - - success = test_github_app(args.app_id, args.private_key, args.installation_id) - sys.exit(0 if success else 1) - -if __name__ == '__main__': - main() diff --git a/scripts/test_github_app_simple.sh b/scripts/test_github_app_simple.sh deleted file mode 100755 index bb870529..00000000 --- a/scripts/test_github_app_simple.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# Simple GitHub App test script -# Usage: ./test_github_app_simple.sh - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo "Example: $0 1960250 /path/to/private-key.pem" - exit 1 -fi - -APP_ID="$1" -PRIVATE_KEY_FILE="$2" - -echo "🔍 Testing GitHub App Configuration" -echo "==================================" -echo "App ID: $APP_ID" -echo "Private Key File: $PRIVATE_KEY_FILE" -echo "" - -# Check if private key file exists -if [ ! -f "$PRIVATE_KEY_FILE" ]; then - echo "❌ Private key file not found: $PRIVATE_KEY_FILE" - exit 1 -fi - -# Check private key format -echo "1. Checking private key format..." -if ! grep -q "BEGIN.*PRIVATE KEY" "$PRIVATE_KEY_FILE"; then - echo "❌ Private key file doesn't contain PEM header" - echo " Expected: -----BEGIN PRIVATE KEY-----" - exit 1 -fi - -if ! grep -q "END.*PRIVATE KEY" "$PRIVATE_KEY_FILE"; then - echo "❌ Private key file doesn't contain PEM footer" - echo " Expected: -----END PRIVATE KEY-----" - exit 1 -fi - -echo "✅ Private key format looks correct" - -# Test with GitHub CLI if available -if command -v gh &> /dev/null; then - echo "" - echo "2. Testing with GitHub CLI..." - - # Try to authenticate with the app (this won't work directly, but will validate the key format) - echo " Note: This test validates the key format, not full app authentication" - echo "✅ GitHub CLI is available for additional testing" -else - echo "" - echo "2. GitHub CLI not available, skipping CLI tests" -fi - -echo "" -echo "3. Key content preview:" -echo " First line: $(head -n1 "$PRIVATE_KEY_FILE")" -echo " Last line: $(tail -n1 "$PRIVATE_KEY_FILE")" -echo " Total lines: $(wc -l < "$PRIVATE_KEY_FILE")" - -echo "" -echo "✅ Basic validation complete!" -echo "" -echo "📋 Next steps:" -echo "1. Ensure these values are set as GitHub repository secrets:" -echo " - GH_APP_ID = $APP_ID" -echo " - GH_APP_PRIVATE_KEY = [full content of $PRIVATE_KEY_FILE]" -echo "" -echo "2. Verify GitHub App is installed on frontegg organization" -echo "3. Check GitHub App permissions (Contents: Read, Metadata: Read)" -echo "4. Run the workflow to test the full authentication flow" diff --git a/scripts/test_local.py b/scripts/test_local.py deleted file mode 100755 index 0f00185a..00000000 --- a/scripts/test_local.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to run the microservices version sync locally. -""" - -import os -import sys -import subprocess -import argparse -from pathlib import Path - - -def setup_virtual_environment(): - """Set up virtual environment if not already in one""" - if os.environ.get('VIRTUAL_ENV'): - print("✅ Already in virtual environment") - return True - - venv_path = Path('scripts/venv') - if not venv_path.exists(): - print("🔧 Creating virtual environment...") - try: - subprocess.run([sys.executable, '-m', 'venv', str(venv_path)], check=True) - print("✅ Virtual environment created") - except subprocess.CalledProcessError: - print("❌ Failed to create virtual environment") - return False - - # Install dependencies in the virtual environment - pip_path = venv_path / 'bin' / 'pip' - if not pip_path.exists(): - pip_path = venv_path / 'Scripts' / 'pip.exe' # Windows - - if pip_path.exists(): - print("📦 Installing dependencies in virtual environment...") - try: - subprocess.run([str(pip_path), 'install', '-r', 'scripts/requirements.txt'], check=True) - print("✅ Dependencies installed") - - # Update Python path to use venv - python_path = venv_path / 'bin' / 'python3' - if not python_path.exists(): - python_path = venv_path / 'Scripts' / 'python.exe' # Windows - - os.environ['PYTHON_PATH'] = str(python_path) - return True - except subprocess.CalledProcessError: - print("❌ Failed to install dependencies") - return False - - return False - - -def run_command(cmd, description, check=True): - """Run a command and handle output""" - print(f"\n🔄 {description}") - - # Use virtual environment Python if available - if cmd[0] == 'python3' and 'PYTHON_PATH' in os.environ: - cmd[0] = os.environ['PYTHON_PATH'] - - print(f"Command: {' '.join(cmd)}") - print("-" * 50) - - try: - result = subprocess.run(cmd, check=check, capture_output=False, text=True) - print(f"✅ {description} completed successfully") - return result.returncode == 0 - except subprocess.CalledProcessError as e: - print(f"❌ {description} failed with exit code {e.returncode}") - return False - - -def main(): - parser = argparse.ArgumentParser(description='Test microservices version sync locally') - parser.add_argument('--github-token', - help='GitHub token for AppState queries (can also use GITHUB_TOKEN env var)') - parser.add_argument('--dry-run', - action='store_true', - help='Run in dry-run mode (no actual updates)') - parser.add_argument('--verbose', '-v', - action='store_true', - help='Verbose output') - parser.add_argument('--skip-appstate', - action='store_true', - help='Skip AppState queries (for testing extraction only)') - - args = parser.parse_args() - - # Check if we're in the right directory - if not Path('charts/frontegg-core-services/values.yaml').exists(): - print("Error: Please run this script from the helm-charts repository root") - return 1 - - # Check GitHub token - token = args.github_token or os.environ.get('GITHUB_TOKEN') - if not token and not args.skip_appstate: - print("Warning: No GitHub token provided. Use --github-token or set GITHUB_TOKEN env var") - print("Running with --skip-appstate to test extraction only") - args.skip_appstate = True - - # Create output directory - output_dir = Path('scripts/output') - output_dir.mkdir(exist_ok=True) - - print("🚀 Starting local microservices version sync test") - print(f"Output directory: {output_dir}") - - # Step 0: Setup virtual environment - if not setup_virtual_environment(): - print("❌ Failed to setup virtual environment") - return 1 - - # Step 1: Extract services - extract_cmd = [ - 'python3', 'scripts/extract_services.py', - '--output-dir', str(output_dir) - ] - if args.verbose: - extract_cmd.append('--verbose') - - if not run_command(extract_cmd, "Extracting microservices from Helm values"): - return 1 - - if args.skip_appstate: - print("\n✅ Extraction test completed successfully!") - print(f"Check the output files in {output_dir}/") - return 0 - - # Step 2: Query AppState - query_cmd = [ - 'python3', 'scripts/query_appstate.py', - '--services-file', str(output_dir / 'all_services.json'), - '--output', str(output_dir / 'appstate_versions.json'), - '--token', token - ] - if args.verbose: - query_cmd.append('--verbose') - - if not run_command(query_cmd, "Querying AppState repository"): - return 1 - - # Step 3: Compare versions - compare_cmd = [ - 'python3', 'scripts/compare_versions.py', - '--services-file', str(output_dir / 'all_services.json'), - '--appstate-file', str(output_dir / 'appstate_versions.json'), - '--output', str(output_dir / 'comparison_results.json'), - '--summary-file', str(output_dir / 'summary.md') - ] - if args.verbose: - compare_cmd.append('--verbose') - - if not run_command(compare_cmd, "Comparing versions"): - return 1 - - # Step 4: Update values (dry-run or actual) - update_cmd = [ - 'python3', 'scripts/update_values.py', - '--comparison-file', str(output_dir / 'comparison_results.json') - ] - if args.dry_run: - update_cmd.append('--dry-run') - if args.verbose: - update_cmd.append('--verbose') - - if not run_command(update_cmd, "Updating Helm values"): - return 1 - - print("\n🎉 All steps completed successfully!") - print(f"\nOutput files created in {output_dir}/:") - for file in output_dir.glob('*'): - if file.is_file(): - print(f" - {file.name}") - - print(f"\nTo view the summary:") - print(f" cat {output_dir}/summary.md") - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/verify_github_app_setup.py b/scripts/verify_github_app_setup.py deleted file mode 100644 index 6818ca04..00000000 --- a/scripts/verify_github_app_setup.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -""" -Verify GitHub App setup and configuration. -This script helps you verify your GitHub App secrets and configuration. -""" - -import os -import re -import sys - -def verify_app_id(app_id_str): - """Verify App ID format""" - print("🔍 Verifying GitHub App ID...") - - if not app_id_str: - print("❌ App ID is empty or not provided") - return False - - if not app_id_str.isdigit(): - print(f"❌ App ID should be numeric, got: {app_id_str}") - return False - - app_id = int(app_id_str) - if app_id < 1: - print(f"❌ App ID should be positive, got: {app_id}") - return False - - print(f"✅ App ID format is valid: {app_id}") - return True - -def verify_private_key(private_key_content): - """Verify private key format""" - print("\n🔍 Verifying GitHub App Private Key...") - - if not private_key_content: - print("❌ Private key is empty or not provided") - return False - - # Check for PEM format - if not private_key_content.startswith('-----BEGIN'): - print("❌ Private key must start with '-----BEGIN PRIVATE KEY-----' or similar") - print(f" Current start: {private_key_content[:50]}...") - return False - - if not private_key_content.rstrip().endswith('-----'): - print("❌ Private key must end with '-----END PRIVATE KEY-----' or similar") - print(f" Current end: ...{private_key_content.rstrip()[-50:]}") - return False - - # Check for common PEM headers - valid_headers = [ - '-----BEGIN PRIVATE KEY-----', - '-----BEGIN RSA PRIVATE KEY-----', - '-----BEGIN EC PRIVATE KEY-----' - ] - - header_found = False - for header in valid_headers: - if header in private_key_content: - header_found = True - print(f"✅ Found valid PEM header: {header}") - break - - if not header_found: - print("❌ No valid PEM header found") - print(" Expected one of:", valid_headers) - return False - - # Check for corresponding footer - if '-----BEGIN PRIVATE KEY-----' in private_key_content: - if '-----END PRIVATE KEY-----' not in private_key_content: - print("❌ Missing '-----END PRIVATE KEY-----' footer") - return False - elif '-----BEGIN RSA PRIVATE KEY-----' in private_key_content: - if '-----END RSA PRIVATE KEY-----' not in private_key_content: - print("❌ Missing '-----END RSA PRIVATE KEY-----' footer") - return False - elif '-----BEGIN EC PRIVATE KEY-----' in private_key_content: - if '-----END EC PRIVATE KEY-----' not in private_key_content: - print("❌ Missing '-----END EC PRIVATE KEY-----' footer") - return False - - # Check key length (should have base64 content) - lines = private_key_content.strip().split('\n') - content_lines = [line for line in lines if not line.startswith('-----')] - - if len(content_lines) < 5: - print("❌ Private key seems too short (less than 5 lines of content)") - return False - - # Check base64 format of content lines - base64_pattern = re.compile(r'^[A-Za-z0-9+/=]+$') - for i, line in enumerate(content_lines[:3]): # Check first 3 content lines - if not base64_pattern.match(line): - print(f"❌ Line {i+1} doesn't look like valid base64: {line[:20]}...") - return False - - print(f"✅ Private key format appears valid ({len(content_lines)} content lines)") - return True - -def check_workflow_secrets_usage(): - """Check how secrets are used in the workflow""" - print("\n🔍 Checking workflow secrets usage...") - - workflow_path = '.github/workflows/microservices-version-sync.yml' - if not os.path.exists(workflow_path): - print(f"❌ Workflow file not found: {workflow_path}") - return False - - with open(workflow_path, 'r') as f: - content = f.read() - - # Check for correct secret references - if '${{ secrets.GH_APP_ID }}' not in content: - print("❌ GH_APP_ID secret not found in workflow") - return False - else: - print("✅ GH_APP_ID secret reference found") - - if '${{ secrets.GH_APP_PRIVATE_KEY }}' not in content: - print("❌ GH_APP_PRIVATE_KEY secret not found in workflow") - return False - else: - print("✅ GH_APP_PRIVATE_KEY secret reference found") - - # Check for GitHub App token generation - if 'actions/create-github-app-token@v1' not in content: - print("❌ GitHub App token generation action not found") - return False - else: - print("✅ GitHub App token generation action found") - - return True - -def main(): - """Main verification function""" - print("🔧 GitHub App Configuration Verification") - print("=" * 50) - - # Get inputs - app_id = input("Enter your GitHub App ID: ").strip() - - print("\nEnter your private key content (paste the entire PEM content, then press Enter twice):") - private_key_lines = [] - while True: - try: - line = input() - if line == "" and private_key_lines: - break - private_key_lines.append(line) - except EOFError: - break - - private_key = '\n'.join(private_key_lines) - - print("\n" + "=" * 50) - - # Run verifications - app_id_valid = verify_app_id(app_id) - private_key_valid = verify_private_key(private_key) - workflow_valid = check_workflow_secrets_usage() - - print("\n" + "=" * 50) - print("📋 VERIFICATION SUMMARY") - print("=" * 50) - - print(f"App ID: {'✅ Valid' if app_id_valid else '❌ Invalid'}") - print(f"Private Key: {'✅ Valid' if private_key_valid else '❌ Invalid'}") - print(f"Workflow Config: {'✅ Valid' if workflow_valid else '❌ Invalid'}") - - if app_id_valid and private_key_valid and workflow_valid: - print("\n🎉 All verifications passed!") - print("\nNext steps:") - print("1. Ensure these exact values are set as GitHub repository secrets:") - print(f" - GH_APP_ID = {app_id}") - print(" - GH_APP_PRIVATE_KEY = [the full PEM content you provided]") - print("2. Verify GitHub App is installed on the frontegg organization") - print("3. Check GitHub App permissions (Contents: Read, Metadata: Read)") - return True - else: - print("\n❌ Some verifications failed. Please fix the issues above.") - return False - -if __name__ == '__main__': - success = main() - sys.exit(0 if success else 1) From 6228d54fca4d127733c37ca48bb08f4f013c3d97 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:21:03 +0300 Subject: [PATCH 12/21] fix2 --- .../workflows/microservices-version-sync.yml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 0fdb9023..c4ad50e1 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -120,13 +120,13 @@ jobs: echo "Auto-updated microservice versions to match production AppState versions." >> commit_msg.txt echo "" >> commit_msg.txt echo "Updated services:" >> commit_msg.txt - python3 -c " -import json -with open('comparison_results.json', 'r') as f: - results = json.load(f) -for update in results['updates_needed']: - print(f'- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}') -" >> commit_msg.txt + python3 -c ' + import json + with open("comparison_results.json", "r") as f: + results = json.load(f) + for update in results["updates_needed"]: + print(f"- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}") + ' >> commit_msg.txt git commit -F commit_msg.txt git push @@ -397,13 +397,13 @@ for update in results['updates_needed']: if [ -f "comparison_results.json" ]; then echo "## Detailed Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - python3 -c " -import json -try: - with open('comparison_results.json', 'r') as f: - results = json.load(f) - print(results['summary']) -except: - print('Summary not available') -" >> $GITHUB_STEP_SUMMARY + python3 -c ' + import json + try: + with open("comparison_results.json", "r") as f: + results = json.load(f) + print(results["summary"]) + except: + print("Summary not available") + ' >> $GITHUB_STEP_SUMMARY fi From 5d768aa8f5e3927f4336961695000e3b81a1d849 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:29:15 +0300 Subject: [PATCH 13/21] ff --- .../workflows/microservices-version-sync.yml | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index c4ad50e1..7e8763f9 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -120,13 +120,7 @@ jobs: echo "Auto-updated microservice versions to match production AppState versions." >> commit_msg.txt echo "" >> commit_msg.txt echo "Updated services:" >> commit_msg.txt - python3 -c ' - import json - with open("comparison_results.json", "r") as f: - results = json.load(f) - for update in results["updates_needed"]: - print(f"- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}") - ' >> commit_msg.txt + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"- {update[\"service\"]}: {update[\"helm_version\"]} → {update[\"appstate_version\"]}") for update in results["updates_needed"]]' >> commit_msg.txt git commit -F commit_msg.txt git push @@ -393,17 +387,33 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY + # Add version comparison table if updates are available (for both dry-run and actual runs) + if [ -f "comparison_results.json" ] && [ "${{ needs.extract-query-compare.outputs.has-updates }}" == "true" ]; then + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + echo "## 📊 Planned Version Updates (Dry Run)" >> $GITHUB_STEP_SUMMARY + else + echo "## 📊 Version Updates Summary" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Service | Chart | Original Version | Updated Version | Status |" >> $GITHUB_STEP_SUMMARY + echo "|---------|-------|------------------|-----------------|--------|" >> $GITHUB_STEP_SUMMARY + + # Generate table rows for services that need updates + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | 🔄 Would update |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + else + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | ✅ Updated |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + fi + + # Add up-to-date services if any + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {service[\"service\"]} | {service[\"chart\"]} | `{service[\"version\"]}` | `{service[\"version\"]}` | ✅ Up to date |") for service in results.get("up_to_date", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + + echo "" >> $GITHUB_STEP_SUMMARY + fi + # Add detailed summary if available if [ -f "comparison_results.json" ]; then - echo "## Detailed Results" >> $GITHUB_STEP_SUMMARY + echo "## 📋 Detailed Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - python3 -c ' - import json - try: - with open("comparison_results.json", "r") as f: - results = json.load(f) - print(results["summary"]) - except: - print("Summary not available") - ' >> $GITHUB_STEP_SUMMARY + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); print(results.get("summary", "Summary not available"))' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "Summary not available" >> $GITHUB_STEP_SUMMARY fi From df6aec648050bb729eb280cbdd3a0029be355a00 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:31:54 +0300 Subject: [PATCH 14/21] ccc --- .github/workflows/microservices-version-sync.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 7e8763f9..df412a36 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -50,7 +50,6 @@ jobs: with: app-id: ${{ secrets.GH_APP_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }} owner: frontegg repositories: AppState,terraform-private-env @@ -145,7 +144,6 @@ jobs: with: app-id: ${{ secrets.GH_APP_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - installation-id: ${{ secrets.GH_APP_INSTALLATION_ID }} owner: frontegg repositories: terraform-private-env From a4cb12671154e517542dd809118acec838583592 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:36:19 +0300 Subject: [PATCH 15/21] gh --- .../workflows/microservices-version-sync.yml | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index df412a36..deb2ad05 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -355,6 +355,14 @@ jobs: echo "# Microservices Version Sync Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY + # Debug: Check if comparison results file exists + if [ -f "comparison_results.json" ]; then + echo "✅ Comparison results file found" >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ Comparison results file not found - some details may be missing" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.extract-query-compare.outputs.has-updates }}" == "true" ]; then echo "## ✅ Sync Completed Successfully" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -398,13 +406,13 @@ jobs: # Generate table rows for services that need updates if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | 🔄 Would update |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | 🔄 Would update |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "| Error | - | - | - | ❌ Failed to load data |" >> $GITHUB_STEP_SUMMARY else - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | ✅ Updated |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | ✅ Updated |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "| Error | - | - | - | ❌ Failed to load data |" >> $GITHUB_STEP_SUMMARY fi # Add up-to-date services if any - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {service[\"service\"]} | {service[\"chart\"]} | `{service[\"version\"]}` | `{service[\"version\"]}` | ✅ Up to date |") for service in results.get("up_to_date", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {service[\"service\"]} | {service[\"chart\"]} | `{service[\"version\"]}` | `{service[\"version\"]}` | ✅ Up to date |") for service in results.get("up_to_date", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || true echo "" >> $GITHUB_STEP_SUMMARY fi @@ -413,5 +421,9 @@ jobs: if [ -f "comparison_results.json" ]; then echo "## 📋 Detailed Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); print(results.get("summary", "Summary not available"))' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "Summary not available" >> $GITHUB_STEP_SUMMARY + python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); print(results.get("summary", "Summary not available"))' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "Summary not available - comparison results could not be loaded" >> $GITHUB_STEP_SUMMARY + else + echo "## 📋 Detailed Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Comparison results file not found - this may indicate an issue with the comparison step" >> $GITHUB_STEP_SUMMARY fi From e416d28f679712256b33233f7caf57fdf514e5fb Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:47:07 +0300 Subject: [PATCH 16/21] bbb --- .../workflows/microservices-version-sync.yml | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index deb2ad05..d39158f6 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -401,18 +401,52 @@ jobs: echo "## 📊 Version Updates Summary" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY - echo "| Service | Chart | Original Version | Updated Version | Status |" >> $GITHUB_STEP_SUMMARY - echo "|---------|-------|------------------|-----------------|--------|" >> $GITHUB_STEP_SUMMARY - # Generate table rows for services that need updates - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | 🔄 Would update |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "| Error | - | - | - | ❌ Failed to load data |" >> $GITHUB_STEP_SUMMARY - else - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {update[\"service\"]} | {update[\"chart\"]} | `{update[\"helm_version\"]}` | `{update[\"appstate_version\"]}` | ✅ Updated |") for update in results.get("updates_needed", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "| Error | - | - | - | ❌ Failed to load data |" >> $GITHUB_STEP_SUMMARY - fi - - # Add up-to-date services if any - python3 -c 'import json; results = json.load(open("comparison_results.json", "r")); [print(f"| {service[\"service\"]} | {service[\"chart\"]} | `{service[\"version\"]}` | `{service[\"version\"]}` | ✅ Up to date |") for service in results.get("up_to_date", [])]' >> $GITHUB_STEP_SUMMARY 2>/dev/null || true + # Try to extract and display the table from the summary field instead of parsing JSON directly + python3 -c " +import json +import sys +try: + with open('comparison_results.json', 'r') as f: + results = json.load(f) + + # Extract the table from the summary markdown + summary = results.get('summary', '') + lines = summary.split('\n') + + # Find the table in the summary + in_table = False + table_found = False + + for line in lines: + if '| Service | Chart |' in line: + in_table = True + table_found = True + print('| Service | Chart | Original Version | Updated Version | Status |') + print('|---------|-------|------------------|-----------------|--------|') + continue + elif in_table and line.startswith('|') and '|' in line.strip(): + # Parse existing table row and add status column + parts = [p.strip() for p in line.split('|')[1:-1]] # Remove empty first/last parts + if len(parts) >= 3 and parts[0] != 'Service': # Skip header row + service, chart, version = parts[0], parts[1], parts[2] + if len(parts) == 4: # Has both helm and appstate versions + helm_ver, appstate_ver = parts[2], parts[3] + status = '🔄 Would update' if '${{ github.event.inputs.dry_run }}' == 'true' else '✅ Updated' + print(f'| {service} | {chart} | {helm_ver} | {appstate_ver} | {status} |') + else: # Up to date service + status = '✅ Up to date' + print(f'| {service} | {chart} | {version} | {version} | {status} |') + elif in_table and not line.startswith('|'): + break + + if not table_found: + print('| No data | - | - | - | ❌ No comparison data available |') + +except Exception as e: + print('| Error | - | - | - | ❌ Failed to parse comparison data |') + sys.stderr.write(f'Error: {e}\n') +" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY fi From 10c24f437724535680b7d6a17627d5a9a757ef9b Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:51:23 +0300 Subject: [PATCH 17/21] yuyuyu --- .../workflows/microservices-version-sync.yml | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index d39158f6..d9f0d75d 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -402,51 +402,54 @@ jobs: fi echo "" >> $GITHUB_STEP_SUMMARY - # Try to extract and display the table from the summary field instead of parsing JSON directly - python3 -c " + # Create a simple table from the detailed results + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + STATUS_EMOJI="🔄 Would update" + else + STATUS_EMOJI="✅ Updated" + fi + + # Extract table data from the summary and reformat it + python3 << 'EOF' >> $GITHUB_STEP_SUMMARY import json import sys +import os + try: with open('comparison_results.json', 'r') as f: results = json.load(f) - # Extract the table from the summary markdown - summary = results.get('summary', '') - lines = summary.split('\n') + dry_run = os.environ.get('GITHUB_EVENT_INPUTS_DRY_RUN') == 'true' + + # Print table header + print('| Service | Chart | Original Version | Updated Version | Status |') + print('|---------|-------|------------------|-----------------|--------|') - # Find the table in the summary - in_table = False - table_found = False + # Print services that need updates + updates_needed = results.get('updates_needed', []) + for update in updates_needed: + service = update.get('service', 'Unknown') + chart = update.get('chart', 'Unknown') + helm_ver = update.get('helm_version', 'Unknown') + appstate_ver = update.get('appstate_version', 'Unknown') + status = '🔄 Would update' if dry_run else '✅ Updated' + print(f'| {service} | {chart} | `{helm_ver}` | `{appstate_ver}` | {status} |') - for line in lines: - if '| Service | Chart |' in line: - in_table = True - table_found = True - print('| Service | Chart | Original Version | Updated Version | Status |') - print('|---------|-------|------------------|-----------------|--------|') - continue - elif in_table and line.startswith('|') and '|' in line.strip(): - # Parse existing table row and add status column - parts = [p.strip() for p in line.split('|')[1:-1]] # Remove empty first/last parts - if len(parts) >= 3 and parts[0] != 'Service': # Skip header row - service, chart, version = parts[0], parts[1], parts[2] - if len(parts) == 4: # Has both helm and appstate versions - helm_ver, appstate_ver = parts[2], parts[3] - status = '🔄 Would update' if '${{ github.event.inputs.dry_run }}' == 'true' else '✅ Updated' - print(f'| {service} | {chart} | {helm_ver} | {appstate_ver} | {status} |') - else: # Up to date service - status = '✅ Up to date' - print(f'| {service} | {chart} | {version} | {version} | {status} |') - elif in_table and not line.startswith('|'): - break + # Print up-to-date services + up_to_date = results.get('up_to_date', []) + for service_info in up_to_date: + service = service_info.get('service', 'Unknown') + chart = service_info.get('chart', 'Unknown') + version = service_info.get('version', 'Unknown') + print(f'| {service} | {chart} | `{version}` | `{version}` | ✅ Up to date |') - if not table_found: + if not updates_needed and not up_to_date: print('| No data | - | - | - | ❌ No comparison data available |') except Exception as e: print('| Error | - | - | - | ❌ Failed to parse comparison data |') - sys.stderr.write(f'Error: {e}\n') -" >> $GITHUB_STEP_SUMMARY + print(f'Error details: {str(e)}', file=sys.stderr) +EOF echo "" >> $GITHUB_STEP_SUMMARY fi From eb3358464db2272a82a6db510aa5666f2c3d1b21 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 17:55:34 +0300 Subject: [PATCH 18/21] test --- .../workflows/microservices-version-sync.yml | 48 +----------- scripts/generate_summary_table.py | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 45 deletions(-) create mode 100755 scripts/generate_summary_table.py diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index d9f0d75d..7541ae15 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -402,55 +402,13 @@ jobs: fi echo "" >> $GITHUB_STEP_SUMMARY - # Create a simple table from the detailed results + # Generate the summary table using the dedicated script if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - STATUS_EMOJI="🔄 Would update" + python3 scripts/generate_summary_table.py --dry-run >> $GITHUB_STEP_SUMMARY else - STATUS_EMOJI="✅ Updated" + python3 scripts/generate_summary_table.py >> $GITHUB_STEP_SUMMARY fi - # Extract table data from the summary and reformat it - python3 << 'EOF' >> $GITHUB_STEP_SUMMARY -import json -import sys -import os - -try: - with open('comparison_results.json', 'r') as f: - results = json.load(f) - - dry_run = os.environ.get('GITHUB_EVENT_INPUTS_DRY_RUN') == 'true' - - # Print table header - print('| Service | Chart | Original Version | Updated Version | Status |') - print('|---------|-------|------------------|-----------------|--------|') - - # Print services that need updates - updates_needed = results.get('updates_needed', []) - for update in updates_needed: - service = update.get('service', 'Unknown') - chart = update.get('chart', 'Unknown') - helm_ver = update.get('helm_version', 'Unknown') - appstate_ver = update.get('appstate_version', 'Unknown') - status = '🔄 Would update' if dry_run else '✅ Updated' - print(f'| {service} | {chart} | `{helm_ver}` | `{appstate_ver}` | {status} |') - - # Print up-to-date services - up_to_date = results.get('up_to_date', []) - for service_info in up_to_date: - service = service_info.get('service', 'Unknown') - chart = service_info.get('chart', 'Unknown') - version = service_info.get('version', 'Unknown') - print(f'| {service} | {chart} | `{version}` | `{version}` | ✅ Up to date |') - - if not updates_needed and not up_to_date: - print('| No data | - | - | - | ❌ No comparison data available |') - -except Exception as e: - print('| Error | - | - | - | ❌ Failed to parse comparison data |') - print(f'Error details: {str(e)}', file=sys.stderr) -EOF - echo "" >> $GITHUB_STEP_SUMMARY fi diff --git a/scripts/generate_summary_table.py b/scripts/generate_summary_table.py new file mode 100755 index 00000000..5153d0dc --- /dev/null +++ b/scripts/generate_summary_table.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Generate a summary table for the GitHub Actions job summary. +This script reads comparison results and generates a formatted table. +""" + +import json +import sys +import argparse +from pathlib import Path + + +def generate_table(comparison_file, dry_run=False): + """Generate a markdown table from comparison results""" + + try: + with open(comparison_file, 'r') as f: + results = json.load(f) + + # Print table header + print('| Service | Chart | Original Version | Updated Version | Status |') + print('|---------|-------|------------------|-----------------|--------|') + + # Print services that need updates + updates_needed = results.get('updates_needed', []) + for update in updates_needed: + service = update.get('service', 'Unknown') + chart = update.get('chart', 'Unknown') + helm_ver = update.get('helm_version', 'Unknown') + appstate_ver = update.get('appstate_version', 'Unknown') + status = '🔄 Would update' if dry_run else '✅ Updated' + print(f'| {service} | {chart} | `{helm_ver}` | `{appstate_ver}` | {status} |') + + # Print up-to-date services + up_to_date = results.get('up_to_date', []) + for service_info in up_to_date: + service = service_info.get('service', 'Unknown') + chart = service_info.get('chart', 'Unknown') + version = service_info.get('version', 'Unknown') + print(f'| {service} | {chart} | `{version}` | `{version}` | ✅ Up to date |') + + if not updates_needed and not up_to_date: + print('| No data | - | - | - | ❌ No comparison data available |') + + return 0 + + except FileNotFoundError: + print('| Error | - | - | - | ❌ Comparison results file not found |') + return 1 + except json.JSONDecodeError as e: + print('| Error | - | - | - | ❌ Invalid JSON in comparison results |') + print(f'JSON Error: {str(e)}', file=sys.stderr) + return 1 + except Exception as e: + print('| Error | - | - | - | ❌ Failed to parse comparison data |') + print(f'Error details: {str(e)}', file=sys.stderr) + return 1 + + +def main(): + """Main function""" + parser = argparse.ArgumentParser(description='Generate summary table from comparison results') + parser.add_argument('--comparison-file', + default='comparison_results.json', + help='Path to comparison results JSON file') + parser.add_argument('--dry-run', + action='store_true', + help='Generate table for dry-run mode') + + args = parser.parse_args() + + return generate_table(args.comparison_file, args.dry_run) + + +if __name__ == '__main__': + sys.exit(main()) From 99ad781d01555d8b47fbed57a3fecceb117f9397 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Tue, 16 Sep 2025 18:00:24 +0300 Subject: [PATCH 19/21] vv --- .github/workflows/microservices-version-sync.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 7541ae15..7ed38009 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -344,6 +344,9 @@ jobs: needs: [extract-query-compare, update-helm-values, trigger-deployment, wait-for-deployment, create-release] if: always() steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Download comparison results uses: actions/download-artifact@v4 with: From d26b2fe8fc3eb631c34ada129b4e66d36b788d0a Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Sun, 21 Sep 2025 13:22:43 +0300 Subject: [PATCH 20/21] add release notes --- .../workflows/microservices-version-sync.yml | 91 +++++++++++++++---- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 7ed38009..58b97469 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -300,6 +300,11 @@ jobs: with: fetch-depth: 0 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Download comparison results uses: actions/download-artifact@v4 with: @@ -313,27 +318,79 @@ jobs: echo "tag=$TAG" >> $GITHUB_OUTPUT echo "Generated tag: $TAG" + - name: Generate detailed release notes + id: release-notes + run: | + # Create detailed release notes with version changes + cat > release_notes.md << 'EOF' + # 🚀 Microservices Version Sync + + **Release Date:** $(date '+%Y-%m-%d %H:%M:%S UTC') + **Sync Tag:** ${{ steps.tag.outputs.tag }} + + This automated release synchronizes microservice versions between Helm charts and AppState production environment. + + ## 📊 Version Updates Summary + + EOF + + # Add detailed version table if comparison results exist + if [ -f "comparison_results.json" ]; then + echo "" >> release_notes.md + python3 scripts/generate_summary_table.py >> release_notes.md + echo "" >> release_notes.md + fi + + # Add deployment information + cat >> release_notes.md << 'EOF' + + ## 🔄 Changes Made + + - ✅ **Extracted** microservices from Helm charts + - ✅ **Queried** AppState repository for production versions + - ✅ **Compared** versions and identified updates needed + - ✅ **Updated** Helm values files with new appVersion values + - ✅ **Synchronized** versions with AppState production environment + - ✅ **Triggered** deployment pipeline + + ## 🚀 Deployment Status + + ✅ **Deployment completed successfully** + + - **Workflow Run ID:** ${{ needs['trigger-deployment'].outputs.workflowRunId }} + - **Deployment Pipeline:** Completed + - **Status:** Success + + ## 📋 Technical Details + + - **Workflow:** `${{ github.workflow }}` + - **Run ID:** `${{ github.run_id }}` + - **Commit:** `${{ github.sha }}` + - **Branch:** `${{ github.ref_name }}` + - **Triggered by:** `${{ github.actor }}` + + ## 🔗 Links + + - [Workflow Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - [Deployment Workflow](https://github.com/frontegg/terraform-private-env/actions/runs/${{ needs['trigger-deployment'].outputs.workflowRunId }}) + - [Repository](https://github.com/${{ github.repository }}) + + --- + + *This release was automatically generated by the Microservices Version Sync workflow.* + EOF + + # Set the release notes as output + echo "notes<> $GITHUB_OUTPUT + cat release_notes.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.tag.outputs.tag }} - name: "Microservices Version Sync - ${{ steps.tag.outputs.tag }}" - body: | - # Microservices Version Sync - - This release contains automated updates to microservice versions based on AppState production versions. - - ${{ needs.extract-query-compare.outputs.summary }} - - ## Changes Made - - Updated Helm values files with new AppVersion values - - Synchronized versions with AppState production environment - - ## Deployment Status - ✅ Deployment pipeline completed successfully - - ## Workflow Run - - Triggered deployment workflow: ${{ needs['trigger-deployment'].outputs.workflowRunId }} + name: "🚀 Microservices Version Sync - ${{ steps.tag.outputs.tag }}" + body: ${{ steps.release-notes.outputs.notes }} draft: false prerelease: false token: ${{ secrets.GITHUB_TOKEN }} From af15c2a967601643d01bbeb8236b67692c04f4c6 Mon Sep 17 00:00:00 2001 From: Eyal Tam Date: Mon, 22 Sep 2025 10:36:17 +0300 Subject: [PATCH 21/21] update --- .../workflows/microservices-version-sync.yml | 12 +- SETUP_APPSTATE_ACCESS.md | 125 ------------------ 2 files changed, 9 insertions(+), 128 deletions(-) delete mode 100644 SETUP_APPSTATE_ACCESS.md diff --git a/.github/workflows/microservices-version-sync.yml b/.github/workflows/microservices-version-sync.yml index 58b97469..7a4666da 100644 --- a/.github/workflows/microservices-version-sync.yml +++ b/.github/workflows/microservices-version-sync.yml @@ -14,10 +14,13 @@ env: TERRAFORM_REPO: 'frontegg/terraform-private-env' WORKFLOW_NAME: 'Create Customer Environment' + + jobs: extract-query-compare: name: Extract, Query & Compare Microservices Versions runs-on: ubuntu-latest + timeout-minutes: 10 outputs: has-updates: ${{ steps.compare.outputs.has-updates }} summary: ${{ steps.compare.outputs.summary }} @@ -74,6 +77,7 @@ jobs: update-helm-values: name: Update Helm Values Files runs-on: ubuntu-latest + timeout-minutes: 10 needs: extract-query-compare if: needs.extract-query-compare.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' steps: @@ -127,6 +131,7 @@ jobs: trigger-deployment: name: Trigger Deployment Workflow runs-on: ubuntu-latest + timeout-minutes: 90 needs: [extract-query-compare, update-helm-values] if: needs.extract-query-compare.outputs.has-updates == 'true' && github.event.inputs.dry_run != 'true' outputs: @@ -234,6 +239,7 @@ jobs: runs-on: ubuntu-latest needs: trigger-deployment if: needs['trigger-deployment'].outputs.workflowRunId != '' + timeout-minutes: 90 outputs: deploymentSuccess: ${{ steps.wait.outputs.success }} steps: @@ -292,6 +298,7 @@ jobs: create-release: name: Create GitHub Release runs-on: ubuntu-latest + timeout-minutes: 10 needs: [extract-query-compare, wait-for-deployment] if: needs['wait-for-deployment'].outputs.deploymentSuccess == 'true' steps: @@ -398,6 +405,7 @@ jobs: notify-results: name: Notify Results runs-on: ubuntu-latest + timeout-minutes: 10 needs: [extract-query-compare, update-helm-values, trigger-deployment, wait-for-deployment, create-release] if: always() steps: @@ -453,7 +461,7 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY - # Add version comparison table if updates are available (for both dry-run and actual runs) + if [ -f "comparison_results.json" ] && [ "${{ needs.extract-query-compare.outputs.has-updates }}" == "true" ]; then if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then echo "## 📊 Planned Version Updates (Dry Run)" >> $GITHUB_STEP_SUMMARY @@ -462,7 +470,6 @@ jobs: fi echo "" >> $GITHUB_STEP_SUMMARY - # Generate the summary table using the dedicated script if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then python3 scripts/generate_summary_table.py --dry-run >> $GITHUB_STEP_SUMMARY else @@ -472,7 +479,6 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY fi - # Add detailed summary if available if [ -f "comparison_results.json" ]; then echo "## 📋 Detailed Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY diff --git a/SETUP_APPSTATE_ACCESS.md b/SETUP_APPSTATE_ACCESS.md deleted file mode 100644 index 99103dc9..00000000 --- a/SETUP_APPSTATE_ACCESS.md +++ /dev/null @@ -1,125 +0,0 @@ -# AppState Access Setup with GitHub App - -## Problem -The microservices version sync workflow needs access to the private `frontegg/AppState` repository to query production versions. The default `secrets.GITHUB_TOKEN` provided by GitHub Actions **cannot access other repositories**, especially private ones. - -## Solution -We use a **GitHub App** for authentication, which is more secure and provides fine-grained permissions compared to Personal Access Tokens (PATs). - -## Step-by-Step Setup - -### 1. Create a GitHub App - -1. Go to GitHub Organization Settings: https://github.com/organizations/frontegg/settings/apps -2. Click **"New GitHub App"** -3. Configure the app: - - **GitHub App name**: `helm-charts-appstate-access` - - **Description**: `Access to AppState repository for microservices version sync` - - **Homepage URL**: `https://github.com/frontegg/helm-charts` - - **Webhook**: Uncheck "Active" (we don't need webhooks) - -### 2. Set Repository Permissions - -In the **Repository permissions** section: -- **Contents**: `Read` (to read AppState files) -- **Actions**: `Write` (to trigger workflows in terraform-private-env) -- **Metadata**: `Read` (required by GitHub) - -### 3. Install the App - -1. After creating the app, go to **Install App** tab -2. Click **"Install"** next to your organization -3. Select **"Only select repositories"** -4. Choose: - - ✅ `frontegg/AppState` - - ✅ `frontegg/terraform-private-env` -5. Click **"Install"** - -### 4. Generate and Add Private Key - -1. In your GitHub App settings, scroll to **Private keys** -2. Click **"Generate a private key"** -3. Download the `.pem` file -4. Go to your helm-charts repository settings -5. Navigate to **Settings** → **Secrets and variables** → **Actions** -6. Add two secrets: - - **Name**: `APPSTATE_APP_ID` - - **Secret**: Your GitHub App ID (found in app settings) - - **Name**: `APPSTATE_APP_PRIVATE_KEY` - - **Secret**: Contents of the `.pem` file (copy entire file including headers) - -### 5. Test the Setup - -Run the workflow manually to test: -1. Go to **Actions** tab in your repository -2. Select **"OnPrem Update Charts - Microservices Version Sync"** -3. Click **"Run workflow"** -4. Enable **"Run in dry-run mode"** for testing -5. Click **"Run workflow"** - -## Troubleshooting - -### Error: "Repository frontegg/AppState not found or not accessible" -- ✅ Verify the GitHub App is installed on the `frontegg/AppState` repository -- ✅ Check that the App ID and private key are correct -- ✅ Ensure the app has `Contents: Read` permission -- ✅ Confirm the secret names are exactly `APPSTATE_APP_ID` and `APPSTATE_APP_PRIVATE_KEY` - -### Error: "Authentication failed" -- ✅ Private key may be malformed (ensure entire .pem file is copied) -- ✅ App ID may be incorrect -- ✅ Check if the GitHub App still exists and is active - -### Error: "Access forbidden" -- ✅ GitHub App may not be installed on the target repository -- ✅ App permissions may be insufficient -- ✅ Repository may require specific app installation approval - -## Security Notes - -- 🔒 **Keep the private key secure** - never commit it to code -- 🔒 **Use minimal required permissions** - only `Contents: Read` and `Actions: Write` -- 🔒 **Rotate private keys regularly** - GitHub Apps support key rotation -- 🔒 **Monitor app usage** - GitHub provides installation and usage logs -- 🔒 **Limit repository access** - only install on required repositories - -## GitHub App Permissions Required - -| Repository | Permission | Reason | -|------------|------------|---------| -| `frontegg/AppState` | Contents: Read | Query production versions from `applications/*/production-global/values.yaml` | -| `frontegg/terraform-private-env` | Actions: Write | Trigger "Create Customer Environment" workflow | -| `frontegg/helm-charts` | N/A | Uses default `GITHUB_TOKEN` for local operations | - -## Workflow Configuration - -The workflow uses GitHub App authentication for external repositories: - -```yaml -# Generate GitHub App token for external repository access -- name: Generate GitHub App Token - id: app-token - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ secrets.APPSTATE_APP_ID }} - private-key: ${{ secrets.APPSTATE_APP_PRIVATE_KEY }} - owner: frontegg - repositories: AppState,terraform-private-env - -# Use the generated token for external operations -env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - -# Local repository operations still use default token -with: - token: ${{ secrets.GITHUB_TOKEN }} -``` - -## Advantages of GitHub Apps over PATs - -- ✅ **Fine-grained permissions** - only the permissions you need -- ✅ **Repository-scoped** - access only to specific repositories -- ✅ **Organization-managed** - centralized control and auditing -- ✅ **No user dependency** - not tied to a specific user account -- ✅ **Better security** - shorter-lived tokens, automatic rotation -- ✅ **Rate limits** - higher API rate limits than PATs