diff --git a/README.md b/README.md index 9e1f023..f668a28 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,14 @@ The GitHub actions included here are: Note that versioning applies to the collection as a whole. This means that if a breaking change is made to a single action, then the version is incremented on all actions, even though changes may not have been made to the other actions. + +Permissions +----------- + +These actions requires the following `GITHUB_TOKEN` permissions: + +```yaml +permissions: + contents: write # To create/update GitHub releases + packages: write # To push OCI image artifacts to GHCR +``` diff --git a/publish-multiple-workshops/README.md b/publish-multiple-workshops/README.md index 720015b..9c55346 100644 --- a/publish-multiple-workshops/README.md +++ b/publish-multiple-workshops/README.md @@ -140,5 +140,64 @@ GitHub action are as follows: |---------------------------------|----------|----------|------------------------------------| | `path` | False | String | Relative directory path under `$GITHUB_WORKSPACE` to the collection of workshops. Defaults to "`workshops`". | | `token` | True | String | GitHub access token. Must be set to `${{secrets.GITHUB_TOKEN}}` or appropriate personal access token variable reference. | +| `include` | False | String | Optional list of workshop directory names/patterns to include (one per line, or comma/space separated; supports glob patterns like "`lab-*`"). If empty, all workshops are included. | +| `exclude` | False | String | Optional list of workshop directory names/patterns to exclude (one per line, or comma/space separated; supports glob patterns like "`lab-*`"). `include` takes precedence over `exclude` on a per-workshop basis. | | `trainingportal-resource-file` | False | String | Relative path under `$GITHUB_WORKSPACE` to the `TrainingPortal` resource file. Defaults to "`resources/trainingportal.yaml`". | | `workshop-resource-file` | False | String | Relative path under workshop directory to the `Workshop` resource file. Defaults to "`resources/workshop.yaml`". Every workshop must have same directory structure. | + +Filtering workshops +------------------- + +Use `include` and/or `exclude` to filter which workshop directories under `path` are published. + +When both `include` and `exclude` are specified, `include` takes precedence on a per-workshop basis: any workshop that matches an `include` pattern is published regardless of `exclude` rules. The `exclude` rules only apply to workshops that are not explicitly included. + +Examples: + +```yaml +- name: Create release (only publish lab-* workshops) + uses: educates/educates-github-actions/publish-multiple-workshops@v7 + with: + token: ${{secrets.GITHUB_TOKEN}} + include: lab-* +``` + +```yaml +- name: Create release (publish all except some, comma separated) + uses: educates/educates-github-actions/publish-multiple-workshops@v7 + with: + token: ${{secrets.GITHUB_TOKEN}} + exclude: "lab-examiner-scripts, lab-docker-runtime" +``` + +```yaml +- name: Create release (publish all except some, multiline) + uses: educates/educates-github-actions/publish-multiple-workshops@v7 + with: + token: ${{secrets.GITHUB_TOKEN}} + exclude: | + lab-examiner-scripts + lab-docker-runtime +``` + +```yaml +- name: Create release (include takes precedence over exclude) + uses: educates/educates-github-actions/publish-multiple-workshops@v7 + with: + token: ${{secrets.GITHUB_TOKEN}} + include: | + lab-builtin-vcluster + lab-command-* + exclude: lab-* +``` + +Permissions +----------- + +This action requires the following `GITHUB_TOKEN` permissions: + +```yaml +permissions: + contents: write # To create/update GitHub releases + packages: write # To push OCI image artifacts to GHCR +``` diff --git a/publish-multiple-workshops/action.yaml b/publish-multiple-workshops/action.yaml index ee89920..b7d1e8b 100644 --- a/publish-multiple-workshops/action.yaml +++ b/publish-multiple-workshops/action.yaml @@ -9,6 +9,14 @@ inputs: description: "Relative directory path under $GITHUB_WORKSPACE to the collection of workshops." required: false default: 'workshops' + include: + description: "Optional list of workshop directory names/patterns to include (one per line, or comma/space separated; supports glob patterns like 'lab-*'). If empty, all workshops are included." + required: false + default: '' + exclude: + description: "Optional list of workshop directory names/patterns to exclude (one per line, or comma/space separated; supports glob patterns like 'lab-*'). Applied after include." + required: false + default: '' workshop-resource-file: description: "Relative path under workshop directory to the workshops resource file. All workshops must have same directory structure." required: false @@ -26,16 +34,6 @@ runs: using: composite steps: - - name: Install Carvel tools - shell: bash - run: curl -L https://carvel.dev/install.sh | bash - - - name: Install Educates CLI - shell: bash - run: | - imgpkg pull -i ghcr.io/educates/educates-client-programs:3.3.2 -o /tmp/client-programs - mv /tmp/client-programs/educates-linux-amd64 /usr/local/bin/educates - - name: Calculate release variables shell: bash run: | @@ -47,44 +45,112 @@ runs: - name: Publish workshop content as OCI image and create workshop definition shell: bash - run : | + run: | + matches_any_pattern() { + local value="$1" + local patterns="$2" + + [[ -z "${patterns}" ]] && return 1 + + patterns="${patterns//,/ }" + patterns="${patterns//$'\n'/ }" + set -f # disable pathname expansion to preserve glob patterns + for pattern in ${patterns}; do + [[ -z "${pattern}" ]] && continue + case "${value}" in + ${pattern}) return 0 ;; + esac + done + set +f + + return 1 + } + + WORKSHOPS_ROOT="${{inputs.path}}" + mkdir -p ${{runner.temp}}/release - for WORKSHOP_NAME in `ls ${{inputs.path}}`; do + + published=0 + + shopt -s nullglob + for WORKSHOP_DIR in "${WORKSHOPS_ROOT}"/*; do + [[ -d "${WORKSHOP_DIR}" ]] || continue + + WORKSHOP_NAME="$(basename "${WORKSHOP_DIR}")" + + if [[ -n "${{inputs.include}}" ]] && matches_any_pattern "${WORKSHOP_NAME}" "${{inputs.include}}"; then + echo "Workshop explicitly included: ${WORKSHOP_NAME}" + elif [[ -n "${{inputs.include}}" ]]; then + echo "Skipping workshop (not included): ${WORKSHOP_NAME}" + continue + elif matches_any_pattern "${WORKSHOP_NAME}" "${{inputs.exclude}}"; then + echo "Skipping workshop (excluded): ${WORKSHOP_NAME}" + continue + fi + mkdir -p ${{runner.temp}}/workshops/${WORKSHOP_NAME}/resources echo "Publishing workshop: ${WORKSHOP_NAME}" - educates publish-workshop ${{inputs.path}}/${WORKSHOP_NAME} \ - --export-workshop ${{runner.temp}}/workshops/${WORKSHOP_NAME}/resources/workshop.yaml \ - --image-repository=ghcr.io/${{env.REPOSITORY_OWNER}} \ - --workshop-version=${{env.REPOSITORY_TAG}} \ - --registry-username=${{github.actor}} \ - --registry-password=${{env.GITHUB_TOKEN}} + docker run --rm \ + -v ${{github.workspace}}:/workspace \ + -v ${{runner.temp}}:/tmp/output \ + -v /etc/ssl/certs:/etc/ssl/certs:ro \ + ghcr.io/educates/educates-cli:3.6.1 publish-workshop /workspace/${WORKSHOP_DIR} \ + --workshop-file '${{inputs.workshop-resource-file}}' \ + --export-workshop /tmp/output/workshops/${WORKSHOP_NAME}/resources/workshop.yaml \ + --image-repository=ghcr.io/${{env.REPOSITORY_OWNER}} \ + --workshop-version=${{env.REPOSITORY_TAG}} \ + --registry-username=${{github.actor}} \ + --registry-password=${{env.GITHUB_TOKEN}} echo "Make a releasable copy of the workshop definition" cp ${{runner.temp}}/workshops/${WORKSHOP_NAME}/resources/workshop.yaml ${{runner.temp}}/release/${WORKSHOP_NAME}.yaml + + published=$((published + 1)) done + if [[ ${published} -eq 0 ]]; then + echo "No workshops matched include/exclude filters under '${WORKSHOPS_ROOT}'." + exit 1 + fi + + echo "Published ${published} workshop(s)." + + if [[ -f "${{inputs.trainingportal-resource-file}}" ]]; then + cp "${{inputs.trainingportal-resource-file}}" ${{runner.temp}}/release/trainingportal.yaml + else + echo "::warning::TrainingPortal resource file '${{inputs.trainingportal-resource-file}}' not found. Skipping." + fi + - name: Generate archives containing the workshop definition shell: bash run: | - ytt -f ${{runner.temp}}/workshops > ${{runner.temp}}/workshops.yaml + first=true + for f in ${{runner.temp}}/workshops/*/resources/workshop.yaml; do + if [ "$first" = true ]; then + first=false + else + echo "---" + fi + cat "$f" + done > ${{runner.temp}}/workshops.yaml (cd ${{runner.temp}}; tar cvfz workshops.tar.gz workshops) (cd ${{runner.temp}}; zip workshops.zip -r workshops) - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: individual-workshops path: ${{runner.temp}}/release/*.yaml - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshops.yaml path: ${{runner.temp}}/workshops.yaml - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshops.tar.gz path: ${{runner.temp}}/workshops.tar.gz - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshops.zip path: ${{runner.temp}}/workshops.zip @@ -105,5 +171,4 @@ runs: ${{runner.temp}}/workshops.tar.gz ${{runner.temp}}/workshops.zip ${{runner.temp}}/workshops.yaml - ${{inputs.path}}/${{inputs.trainingportal-resource-file}} - ${{runner.temp}}/release/*.yaml \ No newline at end of file + ${{runner.temp}}/release/*.yaml diff --git a/publish-workshop/README.md b/publish-workshop/README.md index 350d5e5..266ce8b 100644 --- a/publish-workshop/README.md +++ b/publish-workshop/README.md @@ -105,3 +105,14 @@ GitHub action are as follows: | `token` | True | String | GitHub access token. Must be set to `${{secrets.GITHUB_TOKEN}}` or appropriate personal access token variable reference. | | `trainingportal-resource-file` | False | String | Relative path under workshop directory to the `TrainingPortal` resource file. Defaults to "`resources/trainingportal.yaml`". | | `workshop-resource-file` | False | String | Relative path under workshop directory to the `Workshop` resource file. Defaults to "`resources/workshop.yaml`". | + +Permissions +----------- + +This action requires the following `GITHUB_TOKEN` permissions: + +```yaml +permissions: + contents: write # To create/update GitHub releases + packages: write # To push OCI image artifacts to GHCR +``` diff --git a/publish-workshop/action.yaml b/publish-workshop/action.yaml index e3e7460..4336ca1 100644 --- a/publish-workshop/action.yaml +++ b/publish-workshop/action.yaml @@ -22,16 +22,6 @@ runs: using: composite steps: - - name: Install Carvel tools - shell: bash - run: curl -L https://carvel.dev/install.sh | bash - - - name: Install Educates CLI - shell: bash - run: | - imgpkg pull -i ghcr.io/educates/educates-client-programs:3.3.2 -o /tmp/client-programs - mv /tmp/client-programs/educates-linux-amd64 /usr/local/bin/educates - - name: Calculate release variables shell: bash run: | @@ -43,15 +33,25 @@ runs: - name: Publish workshop and create workshop definition shell: bash - run : | + run: | mkdir -p ${{runner.temp}}/workshops/${REPOSITORY_NAME}/resources - educates publish-workshop '${{inputs.path}}' \ - --workshop-file '${{inputs.workshop-resource-file}}' \ - --export-workshop ${{runner.temp}}/workshops/${REPOSITORY_NAME}/resources/workshop.yaml \ - --image-repository=ghcr.io/${REPOSITORY_OWNER} \ - --workshop-version=${REPOSITORY_TAG} \ - --registry-username=${{github.actor}} \ - --registry-password=${{env.GITHUB_TOKEN}} + docker run --rm \ + -v ${{github.workspace}}:/workspace \ + -v ${{runner.temp}}:/tmp/output \ + -v /etc/ssl/certs:/etc/ssl/certs:ro \ + ghcr.io/educates/educates-cli:3.6.1 publish-workshop /workspace/${{inputs.path}} \ + --workshop-file '${{inputs.workshop-resource-file}}' \ + --export-workshop /tmp/output/workshops/${REPOSITORY_NAME}/resources/workshop.yaml \ + --image-repository=ghcr.io/${REPOSITORY_OWNER} \ + --workshop-version=${REPOSITORY_TAG} \ + --registry-username=${{github.actor}} \ + --registry-password=${{env.GITHUB_TOKEN}} + + if [[ -f "${{inputs.path}}/${{inputs.trainingportal-resource-file}}" ]]; then + cp "${{inputs.path}}/${{inputs.trainingportal-resource-file}}" ${{runner.temp}}/workshops/${REPOSITORY_NAME}/resources/trainingportal.yaml + else + echo "::warning::TrainingPortal resource file '${{inputs.path}}/${{inputs.trainingportal-resource-file}}' not found. Skipping." + fi - name: Generate archives containing the workshop definition shell: bash @@ -59,17 +59,17 @@ runs: (cd ${{runner.temp}}; tar cvfz workshops.tar.gz workshops) (cd ${{runner.temp}}; zip workshops.zip -r workshops) - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshop.yaml path: ${{runner.temp}}/workshops/${{env.REPOSITORY_NAME}}/resources/workshop.yaml - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshops.tar.gz path: ${{runner.temp}}/workshops.tar.gz - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: workshops.zip path: ${{runner.temp}}/workshops.zip @@ -89,5 +89,4 @@ runs: files: | ${{runner.temp}}/workshops.tar.gz ${{runner.temp}}/workshops.zip - ${{runner.temp}}/workshops/${{env.REPOSITORY_NAME}}/resources/workshop.yaml - ${{inputs.path}}/${{inputs.trainingportal-resource-file}} + ${{runner.temp}}/workshops/${{env.REPOSITORY_NAME}}/resources/*.yaml