diff --git a/.github/actions/report-failure/action.yml b/.github/actions/report-failure/action.yml new file mode 100644 index 0000000..3998e33 --- /dev/null +++ b/.github/actions/report-failure/action.yml @@ -0,0 +1,80 @@ +name: "Report workflow failure" +description: "Open or comment on an issue when a workflow fails, and close it on the next green run." + +inputs: + result: + description: "'failure' opens or comments on the issue, 'success' closes it" + required: true + title: + description: "Issue title; it identifies the issue across runs" + required: true + subject: + description: "What failed, in lower case, for the issue text (e.g. 'security audit')" + required: true + labels: + description: "JSON array of {name, color, description} for a new issue; missing labels are created" + required: false + default: "[]" + +runs: + using: "composite" + steps: + - name: Open or close the issue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RESULT: ${{ inputs.result }} + TITLE: ${{ inputs.title }} + SUBJECT: ${{ inputs.subject }} + LABELS: ${{ inputs.labels }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const { RESULT: result, TITLE: title, SUBJECT: subject } = process.env; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + if (result === 'failure') { + const labels = JSON.parse(process.env.LABELS); + for (const label of labels) { + try { + await github.rest.issues.getLabel({ owner, repo, name: label.name }); + } catch (error) { + if (error.status !== 404) throw error; + await github.rest.issues.createLabel({ owner, repo, ...label }); + } + } + const date = new Date().toISOString().split('T')[0]; + const body = `The ${subject} failed on ${date}. See the [workflow run](${runUrl}).`; + const { data: open } = await github.rest.issues.listForRepo({ + owner, repo, state: 'open', labels: labels.map(label => label.name).join(','), per_page: 100, + }); + const existing = open.find(i => i.title === title); + if (existing) { + await github.rest.issues.createComment({ + owner, repo, + issue_number: existing.number, body, + }); + } else { + await github.rest.issues.create({ + owner, repo, + title, body, labels: labels.map(label => label.name), + }); + } + } else if (result === 'success') { + const passed = subject.charAt(0).toUpperCase() + subject.slice(1); + const { data: open } = await github.rest.issues.listForRepo({ + owner, repo, state: 'open', per_page: 100, + }); + for (const issue of open.filter(i => i.title === title)) { + await github.rest.issues.createComment({ + owner, repo, + issue_number: issue.number, + body: `${passed} passed. Closing. See the [workflow run](${runUrl}).`, + }); + await github.rest.issues.update({ + owner, repo, + issue_number: issue.number, state: 'closed', + }); + } + } else { + throw new Error(`result must be 'failure' or 'success', got '${result}'`); + } diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 2cc3f61..158e73c 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -88,15 +88,15 @@ runs: restore-keys: | ${{ runner.os }}-native-${{ inputs.rust-toolchain }}- - - name: Cache integration fixtures (model + audio) + - name: Cache integration fixtures (models) if: ${{ inputs.cache-fixtures == 'true' }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | test/fixtures/ggml-tiny.en.bin test/fixtures/ggml-tiny.bin - test/fixtures/jfk.wav - key: ${{ runner.os }}-fixtures-v1 + test/fixtures/ggml-silero-v5.1.2.bin + key: ${{ runner.os }}-fixtures-${{ hashFiles('test/support/fixtures.ex') }} - name: Install Hex/Rebar + fetch deps shell: bash diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 501d21c..8d47806 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -29,6 +29,7 @@ jobs: timeout-minutes: 25 permissions: contents: read # checkout the repository + issues: write # open an issue when the integration run fails concurrency: group: ${{ github.workflow }}-${{ github.ref }}-integration cancel-in-progress: true @@ -48,3 +49,22 @@ jobs: - name: Run integration tests run: task test:integration + + - name: Report integration failure + if: failure() + uses: $/.github/actions/report-failure + with: + result: failure + title: Integration Tests Failed + subject: integration run + labels: >- + [{"name": "integration", "color": "fbca04", "description": "Integration test run"}, + {"name": "automated", "color": "ededed", "description": "Created by automation"}] + + - name: Resolve integration issue on success + if: success() + uses: $/.github/actions/report-failure + with: + result: success + title: Integration Tests Failed + subject: integration run diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 562ae66..bb4da8c 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -66,63 +66,19 @@ jobs: - name: Report audit failure if: failure() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: $/.github/actions/report-failure with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const title = 'Security Audit Failed'; - const labels = [ - { name: 'security', color: 'd73a4a', description: 'Security-relevant' }, - { name: 'automated', color: 'ededed', description: 'Created by automation' }, - ]; - for (const label of labels) { - try { - await github.rest.issues.getLabel({ owner, repo, name: label.name }); - } catch (error) { - if (error.status !== 404) throw error; - await github.rest.issues.createLabel({ owner, repo, ...label }); - } - } - const date = new Date().toISOString().split('T')[0]; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - const body = `The security audit failed on ${date}. See the [workflow run](${runUrl}).`; - const { data: open } = await github.rest.issues.listForRepo({ - owner, repo, state: 'open', labels: labels.map(label => label.name).join(','), per_page: 100, - }); - const existing = open.find(i => i.title === title); - if (existing) { - await github.rest.issues.createComment({ - owner, repo, - issue_number: existing.number, body, - }); - } else { - await github.rest.issues.create({ - owner, repo, - title, body, labels: labels.map(label => label.name), - }); - } + result: failure + title: Security Audit Failed + subject: security audit + labels: >- + [{"name": "security", "color": "d73a4a", "description": "Security-relevant"}, + {"name": "automated", "color": "ededed", "description": "Created by automation"}] - name: Resolve audit issue on success if: success() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: $/.github/actions/report-failure with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const title = 'Security Audit Failed'; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - const { data: open } = await github.rest.issues.listForRepo({ - owner, repo, state: 'open', per_page: 100, - }); - for (const issue of open.filter(i => i.title === title)) { - await github.rest.issues.createComment({ - owner, repo, - issue_number: issue.number, - body: `Security audit passed. Closing. See the [workflow run](${runUrl}).`, - }); - await github.rest.issues.update({ - owner, repo, - issue_number: issue.number, state: 'closed', - }); - } + result: success + title: Security Audit Failed + subject: security audit