Skip to content

Commit

Permalink
[SDK Suppressions labels] Add new action to update suppressions labels (
Browse files Browse the repository at this point in the history
Azure#31878)

* initial version

* use variables to be used in next steps

* removed unused code and test branch

* added function description

* updated dependencies

* removed test branch

* add comment to to explain this behavior

* Add trailing newline

---------

Co-authored-by: Mike Harder <mharder@microsoft.com>
Co-authored-by: Ray Chen <raychen@microsoft.com>
  • Loading branch information
3 people authored and santiagxf committed Jan 15, 2025
1 parent cd2b99f commit 6cb94a6
Show file tree
Hide file tree
Showing 14 changed files with 952 additions and 3 deletions.
104 changes: 104 additions & 0 deletions .github/workflows/SDK-Suppressions-Label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: SDK Suppressions

on:
pull_request:
branches:
- main
- RPSaaSMaster
- release*

jobs:
process-sdk-suppressions-labels:
name: Sdk Suppressions
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Required since "HEAD^" is passed to Get-ChangedFiles
fetch-depth: 2

- name: Setup Node and run `npm ci`
uses: ./.github/actions/setup-node-npm-ci

- name: Get GitHub PullRequest Changed Files
shell: pwsh
id: get-changedFiles
run: |
. eng/scripts/ChangedFiles-Functions.ps1
$changedFiles = @(Get-ChangedFiles)
echo "PR Changed files: $changedFiles"
Add-Content -Path $env:GITHUB_OUTPUT -Value "changedFiles=$changedFiles"
- name: Get GitHub PullRequest Context
uses: actions/github-script@v7
id: fetch-pullRequest-context
with:
script: |
const pr = context.payload.pull_request;
if (!pr) {
throw new Error("This workflow must run in the context of a pull request.");
}
console.log("This action trigger by ", context.eventName);
core.setOutput("prLabels", pr.labels.map(label => label.name));
result-encoding: string

- name: Run Get suppressions label script
id: run-suppressions-script
env:
OUTPUT_FILE: "output.json"
GITHUB_PULL_REQUEST_CHANGE_FILES: ${{ steps.get-changedFiles.outputs.changedFiles }}
GITHUB_PULL_REQUEST_LABELS: ${{ steps.fetch-pullRequest-context.outputs.prLabels }}
run: |
node eng/tools/sdk-suppressions/cmd/sdk-suppressions-label.js HEAD^ HEAD "$GITHUB_PULL_REQUEST_CHANGE_FILES" "$GITHUB_PULL_REQUEST_LABELS"
OUTPUT=$(cat $OUTPUT_FILE)
echo "Script output labels: $OUTPUT"
labelsToAdd=$(echo "$OUTPUT" | sed -n 's/.*"labelsToAdd":\[\([^]]*\)\].*/\1/p' | tr -d '" ')
labelsToRemove=$(echo "$OUTPUT" | sed -n 's/.*"labelsToRemove":\[\([^]]*\)\].*/\1/p' | tr -d '" ')
for label in $(echo $labelsToAdd | tr ',' '\n'); do
echo "Label to add: $label"
echo "$label=true" >> $GITHUB_OUTPUT
done
for label in $(echo $labelsToRemove | tr ',' '\n'); do
echo "Label to remove: $label"
echo "$label=false" >> $GITHUB_OUTPUT
done
# No Action or Add/Remove label ​​according to step run-suppressions-script output
# e.g.
# If the output of the step does not include the BreakingChange-Go-Sdk-Suppression, no action will be taken.
# If the step's output is "BreakingChange-Go-Sdk-Suppression='true'", the label "BreakingChange-Go-Sdk-Suppression" will be applied to the PR.
# If the step's output is "BreakingChange-Go-Sdk-Suppression='false'", the label "BreakingChange-Go-Sdk-Suppression" will be removed from the PR.
- uses: ./.github/actions/add-label-artifact
name: Upload artifact with results-go
if: ${{ steps.run-suppressions-script.outputs.BreakingChange-Go-Sdk-Suppression }}
with:
name: "BreakingChange-Go-Sdk-Suppression"
value: "${{ steps.run-suppressions-script.outputs.BreakingChange-Go-Sdk-Suppression == 'true' }}"

- uses: ./.github/actions/add-label-artifact
name: Upload artifact with results java
if: ${{ steps.run-suppressions-script.outputs.BreakingChange-Java-Sdk-Suppression }}
with:
name: "BreakingChange-Java-Sdk-Suppression"
value: "${{ steps.run-suppressions-script.outputs.BreakingChange-Java-Sdk-Suppression == 'true' }}"

- uses: ./.github/actions/add-label-artifact
name: Upload artifact with results js
if: ${{ steps.run-suppressions-script.outputs.BreakingChange-JavaScript-Sdk-Suppression }}
with:
name: "BreakingChange-JavaScript-Sdk-Suppression"
value: "${{ steps.run-suppressions-script.outputs.BreakingChange-JavaScript-Sdk-Suppression == 'true' }}"

- uses: ./.github/actions/add-label-artifact
name: Upload artifact with results python
if: ${{ steps.run-suppressions-script.outputs.BreakingChange-Python-Sdk-Suppression }}
with:
name: "BreakingChange-Python-Sdk-Suppression"
value: "${{ steps.run-suppressions-script.outputs.BreakingChange-Python-Sdk-Suppression == 'true' }}"
2 changes: 1 addition & 1 deletion .github/workflows/update-labels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
# types: [labeled, unlabeled]
# If an upstream workflow if completed, get only the artifacts from that workflow, and update labels
workflow_run:
workflows: ["TypeSpec Requirement"]
workflows: ["TypeSpec Requirement", "SDK Suppressions"]
types: [completed]
workflow_dispatch:
inputs:
Expand Down
3 changes: 2 additions & 1 deletion eng/tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"@azure-tools/suppressions": "file:suppressions",
"@azure-tools/tsp-client-tests": "file:tsp-client-tests",
"@azure-tools/typespec-requirement": "file:typespec-requirement",
"@azure-tools/typespec-validation": "file:typespec-validation"
"@azure-tools/typespec-validation": "file:typespec-validation",
"@azure-tools/sdk-suppressions": "file:sdk-suppressions"
},
"scripts": {
"build": "tsc --build",
Expand Down
5 changes: 5 additions & 0 deletions eng/tools/sdk-suppressions/cmd/sdk-suppressions-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

import { main } from "../dist/src/index.js";

await main();
30 changes: 30 additions & 0 deletions eng/tools/sdk-suppressions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@azure-tools/sdk-suppressions",
"private": true,
"type": "module",
"main": "dist/src/index.js",
"version": "1.0.0",
"bin": {
"get-sdk-suppressions-label": "cmd/sdk-suppressions-label.js"
},
"scripts": {
"build": "tsc --build",
"test": "vitest",
"test:ci": "vitest run --coverage --reporter=verbose"
},
"engines": {
"node": ">= 18.0.0"
},
"dependencies": {
"ajv": "^8.17.1",
"lodash": "^4.17.20",
"yaml": "^2.4.2"
},
"devDependencies": {
"@types/lodash": "^4.14.161",
"@types/node": "^18.19.31",
"@vitest/coverage-v8": "^2.0.4",
"typescript": "~5.6.2",
"vitest": "^2.0.4"
}
}
63 changes: 63 additions & 0 deletions eng/tools/sdk-suppressions/src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { parse as yamlParse } from "yaml";

import { exec } from "child_process";
import { promisify } from "util";

/**
* @param yamlContent
* @returns {result: string | object | undefined | null, message: string}
* special return
* if the content is empty, return {result: null, message: string
* if the file parse error, return {result: undefined, message: string
*/
export function parseYamlContent(yamlContent: string, path: string): {
result: string | object | undefined | null;
message: string;
}{
let content = undefined;
// if yaml file is not a valid yaml, catch error and return undefined
try {
content = yamlParse(yamlContent);
} catch (error) {
console.error(`The file parsing failed in the ${path}. Details: ${error}`);
return {
result: content,
message: `The file parsing failed in the ${path}. Details: ${error}`
};;
}

// if yaml file is empty, run yaml.safeload success but get undefined
// to identify whether it is empty return null to distinguish.
if (!content) {
console.info(`The file in the ${path} has been successfully parsed, but it is an empty file.`)
return {
result: null,
message: `The file in the ${path} has been successfully parsed, but it is an empty file.`
};;
}

return {
result: content,
message: 'The file has been successfully parsed.'
};

}

// Promisify the exec function
const execAsync = promisify(exec);

export async function runGitCommand(command: string): Promise<string> {
try {
const { stdout, stderr } = await execAsync(command);

if (stderr) {
console.error("Error Output:", stderr);
// throw new Error(stderr);
}

return stdout.trim();
} catch (error:any) {
console.error("Error details:", error.stderr || error);
throw error;
}
}
39 changes: 39 additions & 0 deletions eng/tools/sdk-suppressions/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

import { exit } from "process";
import { updateSdkSuppressionsLabels } from "./updateSdkSuppressionsLabel.js";

function getArgsError(args: string[]): string {
return (
"Get args lengths: " + args.length + "\n" +
"Details: " + args.join(', ') + "\n" +
"Usage: node eng/tools/sdk-suppressions/cmd/sdk-suppressions-label.js baseCommitHash headCommitHash changeFiles prLabels\n" +
"Returns: {labelsToAdd: [label1, label2],labelsToRemove: [lable3, label4]}\n" +
"Parameters:\n" +
" baseCommitHash: The base commit hash. Example: HEAD^ \n" +
" headCommitHash: The head commit hash. Example: HEAD \n" +
" changeFiles: The changed files. Example: 'specification/workloads/Workloads.Operations.Management/sdk-suppressions.yaml specification/workloads/Workloads.Operations.Management/main.tsp'\n" +
" prLabels: The PR has added labels. Example: '['BreakingChange-Go-Sdk-Suppression', 'BreakingChange-Python-Sdk-Suppression']'\n"
);
}

export async function main() {
const args: string[] = process.argv.slice(2);
if (args.length === 4) {
const baseCommitHash: string = args[0];
const headCommitHash: string = args[1];
const changeFiles: string = args[2];
const lables: string = args[3];
const outputFile = process.env.OUTPUT_FILE as string;
const changedLabels: {labelsToAdd: String[], labelsToRemove: String[]} = await updateSdkSuppressionsLabels(lables, changeFiles, baseCommitHash, headCommitHash, outputFile);
console.log(JSON.stringify(changedLabels));
exit(0);
} else {
console.error(getArgsError(args));
exit(1);
}

}

export { updateSdkSuppressionsLabels };


59 changes: 59 additions & 0 deletions eng/tools/sdk-suppressions/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* This file is the single source of truth for the labels used by the SDK generation tooling
* in the Azure/azure-rest-api-specs and Azure/azure-rest-api-specs-pr repositories.
*
* For additional context, see:
* - https://gist.github.com/raych1/353949d19371b69fb82a10dd70032a51
* - https://github.com/Azure/azure-sdk-tools/issues/6327
* - https://microsoftapc-my.sharepoint.com/:w:/g/personal/raychen_microsoft_com/EbOAA9SkhQhGlgxtf7mc0kUB-25bFue0EFbXKXS3TFLTQA
*/
export type SdkName =
| "azure-sdk-for-go"
| "azure-sdk-for-java"
| "azure-sdk-for-js"
| "azure-sdk-for-net"
| "azure-sdk-for-python"

export const sdkLabels: {
[sdkName in SdkName]: {
breakingChange: string | undefined;
breakingChangeApproved: string | undefined;
breakingChangeSuppression: string | undefined;
breakingChangeSuppressionApproved: string | undefined;
};
} = {
"azure-sdk-for-go": {
breakingChange: "BreakingChange-Go-Sdk",
breakingChangeApproved: "BreakingChange-Go-Sdk-Approved",
breakingChangeSuppression: "BreakingChange-Go-Sdk-Suppression",
breakingChangeSuppressionApproved:
"BreakingChange-Go-Sdk-Suppression-Approved",
},
"azure-sdk-for-java": {
breakingChange: "BreakingChange-Java-Sdk",
breakingChangeApproved: "BreakingChange-Java-Sdk-Approved",
breakingChangeSuppression: "BreakingChange-Java-Sdk-Suppression",
breakingChangeSuppressionApproved:
"BreakingChange-Java-Sdk-Suppression-Approved"
},
"azure-sdk-for-js": {
breakingChange: "BreakingChange-JavaScript-Sdk",
breakingChangeApproved: "BreakingChange-JavaScript-Sdk-Approved",
breakingChangeSuppression: "BreakingChange-JavaScript-Sdk-Suppression",
breakingChangeSuppressionApproved:
"BreakingChange-JavaScript-Sdk-Suppression-Approved"
},
"azure-sdk-for-net": {
breakingChange: undefined,
breakingChangeApproved: undefined,
breakingChangeSuppression: undefined,
breakingChangeSuppressionApproved: undefined
},
"azure-sdk-for-python": {
breakingChange: "BreakingChange-Python-Sdk",
breakingChangeApproved: "BreakingChange-Python-Sdk-Approved",
breakingChangeSuppression: "BreakingChange-Python-Sdk-Suppression",
breakingChangeSuppressionApproved:
"BreakingChange-Python-Sdk-Suppression-Approved"
}
};
Loading

0 comments on commit 6cb94a6

Please sign in to comment.