Skip to content

the build new image step in github actions had an error, but the workflow didn't fail properly #177

the build new image step in github actions had an error, but the workflow didn't fail properly

the build new image step in github actions had an error, but the workflow didn't fail properly #177

name: Process Contribution Issues
permissions:
contents: read
pull-requests: write
on:
issues:
types: [opened]
jobs:
process-contribution:
if: contains(github.event.issue.title, '[CONTRIBUTION]')
runs-on: ubuntu-latest
permissions:
issues: write
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install js-yaml pako
- name: Parse base64 encoded deflate compressed YAML from issue body
id: parse-yaml
uses: actions/github-script@v7
with:
script: |
const yaml = require('js-yaml');
const pako = require('pako');
const fs = require('fs');
const path = require('path');
const issueBody = context.payload.issue.body;
// Extract base64 encoded content from issue body
const base64Regex = /```base64\n([\s\S]*?)\n```/;
const match = issueBody.match(base64Regex);
if (!match) {
core.setFailed('No base64 encoded content found in issue body');
return;
}
try {
// Decode base64
const base64Content = match[1].trim();
const compressedBuffer = Buffer.from(base64Content, 'base64');
// Decompress using pako (deflate)
const decompressed = pako.inflate(compressedBuffer, { to: 'string' });
const yamlContent = decompressed;
// Parse YAML to validate and get name
const parsedYaml = yaml.load(yamlContent);
if (!parsedYaml.name) {
core.setFailed('YAML must contain a "name" field');
return;
}
// Sanitize the name: allow lowercase letters and digits only
const rawName = String(parsedYaml.name);
const normalized = rawName.normalize('NFKD').toLowerCase();
if (!normalized.match(/^[a-z0-9]+$/)) {
core.setFailed('Invalid recipe name after sanitization: must contain only lowercase letters and numbers');
return;
}
// Ensure the path stays within the recipes directory
const recipesRoot = path.resolve('recipes');
const recipeDir = path.resolve(recipesRoot, normalized);
if (!recipeDir.startsWith(recipesRoot + path.sep)) {
core.setFailed('Recipe path escapes the recipes directory');
return;
}
// Create recipe directory and write file directly
fs.mkdirSync(recipeDir, { recursive: true });
fs.writeFileSync(path.join(recipeDir, 'build.yaml'), yamlContent);
// Expose sanitized name
core.setOutput('name', normalized);
core.setOutput('parsed', JSON.stringify({ ...parsedYaml, name: normalized }));
} catch (error) {
core.setFailed(`Failed to process encoded content: ${error.message}`);
}
- name: Create branch name
id: create-branch
run: |
set -euo pipefail
BRANCH_NAME="contribution-${{ github.event.issue.number }}-${{ steps.parse-yaml.outputs.name }}"
# Safely write multiline or special characters to GITHUB_OUTPUT
{
echo 'branch_name<<EOF'
echo "$BRANCH_NAME"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Create new branch
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git checkout -b "${{ steps.create-branch.outputs.branch_name }}"
- name: Check if file exists
id: check-file
run: |
set -euo pipefail
FILE_PATH="recipes/${{ steps.parse-yaml.outputs.name }}/build.yaml"
if [ -f "$FILE_PATH" ]; then
echo "file_exists=true" >> "$GITHUB_OUTPUT"
{ echo 'file_path<<EOF'; echo "$FILE_PATH"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
echo "action=update" >> "$GITHUB_OUTPUT"
else
echo "file_exists=false" >> "$GITHUB_OUTPUT"
{ echo 'file_path<<EOF'; echo "$FILE_PATH"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
echo "action=add" >> "$GITHUB_OUTPUT"
fi
- name: Commit changes
run: |
set -euo pipefail
git add recipes/
# Prepare commit message safely
cat > commitmsg.txt <<'EOF'
${{ steps.check-file.outputs.action == 'add' && 'Add' || 'Update' }} recipe for ${{ steps.parse-yaml.outputs.name }} from issue #${{ github.event.issue.number }}
EOF
AUTHOR="${{ github.event.issue.user.login }} <${{ github.event.issue.user.id }}+${{ github.event.issue.user.login }}@users.noreply.github.com>"
git commit --author="$AUTHOR" -F commitmsg.txt
- name: Push branch
run: |
set -euo pipefail
git push origin "${{ steps.create-branch.outputs.branch_name }}"
- name: Create pull request
id: create-pr
uses: actions/github-script@v7
with:
script: |
const { data: pr } = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${{ steps.check-file.outputs.action == 'add' && 'Add' || 'Update' }} recipe for ${{ steps.parse-yaml.outputs.name }}`,
body: `This PR was automatically generated from issue #${{ github.event.issue.number }}.
## Summary
- ${{ steps.check-file.outputs.action == 'add' && 'Adds new' || 'Updates existing' }} recipe for \`${{ steps.parse-yaml.outputs.name }}\`
- Recipe file: \`${{ steps.check-file.outputs.file_path }}\`
Closes #${{ github.event.issue.number }}`,
head: '${{ steps.create-branch.outputs.branch_name }}',
base: 'main'
});
core.setOutput('pr_number', pr.number);
core.setOutput('pr_url', pr.html_url);
- name: Comment on issue with PR link
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `✅ A pull request has been automatically created for your contribution!
🔗 Pull Request: #${{ steps.create-pr.outputs.pr_number }}
The recipe for \`${{ steps.parse-yaml.outputs.name }}\` has been ${{ steps.check-file.outputs.action == 'add' && 'added' || 'updated' }} in the PR. A maintainer will review and merge it soon.
Thank you for your contribution! 🎉`
});