diff --git a/.github/shared/cmd/detect-missing-rpaas-rp.js b/.github/shared/cmd/detect-missing-rpaas-rp.js new file mode 100644 index 000000000000..dac481dadef1 --- /dev/null +++ b/.github/shared/cmd/detect-missing-rpaas-rp.js @@ -0,0 +1,135 @@ + +import { getChangedFiles, getChangedFilesStatuses, swagger } from "../src/changed-files.js"; +import { parseArgs } from "node:util"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { readdir, access, stat } from "node:fs/promises"; +import { join } from "node:path"; + +function usage() { + console.log(`Usage: +npx api-doc-preview --output + +parameters: + --repoWithChanges Directory checked out with PR Changes + --referenceFolder The azure-rest-api-specs-pr directory checked out at RPSaaSMaster branch.`); +} + +const { + values: { + "repoWithChanges": repoWithChanges, + "referenceFolder": referenceFolder + }, +} = parseArgs({ + options: { + "repoWithChanges": { + type: "string", + default: process.env.BUILD_SOURCESDIRECTORY || "", + }, + "referenceFolder": { + type: "string", + default: process.env.PRIVATESPECSDIRECTORY || "", + }, + }, + allowPositionals: false, +}); + +let validArgs = true; + + +if (!repoWithChanges) { + console.log( + `Missing required parameter --repoWithChanges. Value given: ${repoWithChanges || ""}`, + ); + validArgs = false; +} + +if (!referenceFolder) { + console.log( + `Missing required parameter --referenceFolder. Value given: ${referenceFolder || ""}`, + ); + validArgs = false; +} + +if (!validArgs) { + usage(); + process.exit(1); +} + +const changedFiles = await getChangedFiles({ + cwd: repoWithChanges, + paths: ["specification"], +}); + +// Extract RP folders from changed files +// Files will be in format: specification// +const changedRpFolders = new Set(); + +for (const filePath of changedFiles) { + // Split the path and check if it starts with 'specification' + const pathParts = filePath.split('/'); + + if (pathParts.length >= 2 && pathParts[0] === 'specification') { + // The second part is the RP folder name + const rpFolder = pathParts[1]; + changedRpFolders.add(rpFolder); + } +} + +console.log(`Found ${changedRpFolders.size} changed RP folders:`, Array.from(changedRpFolders)); + +// Get the list of RP folders from the reference directory +const referenceSpecPath = join(referenceFolder, 'specification'); + +// Check if reference directory exists +try { + await access(referenceSpecPath); +} catch (error) { + console.error(`Reference specification directory does not exist: ${referenceSpecPath}`); + process.exit(1); +} + +let referenceRpFolders; +try { + // Get all items in the reference specification folder + const items = await readdir(referenceSpecPath); + + // Filter to only directories + referenceRpFolders = []; + for (const item of items) { + const itemPath = join(referenceSpecPath, item); + try { + const stats = await stat(itemPath); + if (stats.isDirectory()) { + referenceRpFolders.push(item); + } + } catch (statError) { + // Skip items we can't stat (permissions, etc.) + console.warn(`Warning: Could not stat ${itemPath}:`, statError.message); + } + } +} catch (error) { + console.error(`Error reading reference directory ${referenceSpecPath}:`, error.message); + process.exit(1); +} + +console.log(`Found ${referenceRpFolders.length} RP folders in reference directory`); + +// Check if all changed RP folders exist in the reference directory +const missingRpFolders = []; + +for (const rpFolder of changedRpFolders) { + if (!referenceRpFolders.includes(rpFolder)) { + missingRpFolders.push(rpFolder); + } +} + +if (missingRpFolders.length > 0) { + console.error(`ERROR: The following RP folders are missing from the reference directory:`); + for (const missingFolder of missingRpFolders) { + console.error(` - ${missingFolder}`); + } + console.error(`These folders must exist in the reference directory before changes can be processed.`); + process.exit(1); +} +process.exit(0); diff --git a/.github/workflows/rpaas-private-check-status.yml b/.github/workflows/rpaas-private-check-status.yml new file mode 100644 index 000000000000..f7330a6e0300 --- /dev/null +++ b/.github/workflows/rpaas-private-check-status.yml @@ -0,0 +1,57 @@ +name: "RPaaS in Private Check" + +on: + check_run: + types: [completed] + +permissions: + contents: read + checks: read + +jobs: + rpaas-in-private-check: + if: | + github.event.check_run.check_suite.app.name == 'Azure Pipelines' && + contains(github.event.check_run.name, 'PresentInRPaaSCheck') + name: "RPaaS In Private" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + .github + + # todo: how much is this stuff actually necessary for our rpass-private-check-status? + # Only run the login and get token steps when the respository is azure-rest-api-specs-pr + - if: github.event.repository.name == 'azure-rest-api-specs-pr' + name: Azure Login with Workload Identity Federation + uses: azure/login@v2 + with: + client-id: "936c56f0-298b-467f-b702-3ad5bf4b15c1" + tenant-id: "72f988bf-86f1-41af-91ab-2d7cd011db47" + allow-no-subscriptions: true + + - if: github.event.repository.name == 'azure-rest-api-specs-pr' + name: Get ADO Token via Managed Identity + run: | + # Get token for Azure DevOps resource + ADO_TOKEN=$(az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query "accessToken" -o tsv) + echo "ADO_TOKEN=$ADO_TOKEN" >> "$GITHUB_ENV" + + - name: "Get Issue Number" + id: get-issue-number + uses: actions/github-script@v7 + with: + script: | + const { extractInputs } = + await import('${{ github.workspace }}/.github/workflows/src/context.js'); + + const { issue_number } = await extractInputs({ github, context, core }); + core.setOutput("issue_number", issue_number); + + - if: ${{ always() && steps.get-issue-number.outputs.issue_number }} + name: Upload artifact with issue number + uses: ./.github/actions/add-empty-artifact + with: + name: issue-number + value: ${{ steps.get-issue-number.outputs.issue_number }} diff --git a/.github/workflows/summarize-checks.yaml b/.github/workflows/summarize-checks.yaml index a1d066b1df0d..b9e551f9ba5b 100644 --- a/.github/workflows/summarize-checks.yaml +++ b/.github/workflows/summarize-checks.yaml @@ -9,6 +9,7 @@ on: - "Swagger Avocado - Set Status" - "Swagger LintDiff - Set Status" - "SDK Validation Status" + - "RPaaS in Private Check" types: - completed pull_request_target: # when a PR is labeled. NOT pull_request, because that would run on the PR branch, not the base branch. @@ -55,8 +56,8 @@ jobs: uses: actions/github-script@v7 with: script: | - const { default: dumpTriggerMetadata } = - await import('${{ github.workspace }}/.github/workflows/src/summarize-checks/dump-trigger-metadata.js'); + const { extractInputs } = + await import('${{ github.workspace }}/.github/workflows/src/context.js'); return await dumpTriggerMetadata({ context, core }); - id: summarize-checks diff --git a/eng/pipelines/rpaas-private-check.yml b/eng/pipelines/rpaas-private-check.yml new file mode 100644 index 000000000000..be55b9c0b7e4 --- /dev/null +++ b/eng/pipelines/rpaas-private-check.yml @@ -0,0 +1,43 @@ +trigger: none +pr: + paths: + include: + # Trigger for files that will result in a specification change + - specification/** + +jobs: + - job: PresentInRPaaSCheck + name: Check RP Presenence in RPSaaSMaster + + pool: + name: $(LINUXPOOL) + vmImage: $(LINUXVMIMAGE) + + variables: + - template: /eng/pipelines/templates/variables/globals.yml + - template: /eng/pipelines/templates/variables/image.yml + + - name: PrivateSpecsDirectory + value: $(Pipeline.Workspace)/azure-rest-api-specs-pr + + steps: + - checkout: self + # Fetch depth required to get list of changed files + fetchDepth: 2 + + - template: /eng/common/pipelines/templates/steps/sparse-checkout.yml + parameters: + SkipCheckoutNone: true + # Path does not need to be set because sparse-checkout.yml already + # checks out files in the repo root + Repositories: + - Name: Azure/azure-rest-api-specs-pr + Commitish: RPSaaSMaster + WorkingDirectory: $(PrivateSpecsDirectory) + + - template: /eng/pipelines/templates/steps/npm-install.yml + parameters: + WorkingDirectory: .github/shared + + - script: .github/shared/cmd/detect-missing-rpaas-rp.js + displayName: Compare RP Folder Usage