diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6133a4124..c95eb4a9c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,9 @@ # Add here your WMS ID + +# Our SLAs +[Please review before contacting us](https://oracle-livelabs.github.io/common/sample-livelabs-templates/create-labs/labs/workshops/livelabs/?lab=sla) + # General requirements 1. Information in the workshop is adequate and updated 2. Code is correct and working @@ -8,11 +12,11 @@ 5. Please make sure WMS URLs are updated as needed after your PR is approved -# Checklist - Refer to the QA document for the complete list +# Checklist - Refer to the Self QA Checklist in WMS for the complete list Please confirm that the following is completed before submitting your PR -- [ ] All filenames are lower case (including folders, images, files, etc.) -- [ ] Filenames are descriptive -- [ ] Your workshop folder structure should be similar to the one used in the sample workshop (https://github.com/oracle-livelabs/common/tree/main/sample-livelabs-templates/sample-workshop) -- [ ] Are you using multiple versions (desktop/, sandbox/, tenancy/)? Make sure that each of them contains a manifest.json and an index.html -- [ ] Image references in markdown contain an alternate text +- [ ] [Complete the Self QA Checklist in WMS](https://oracle-livelabs.github.io/common/sample-livelabs-templates/create-labs/labs/workshops/livelabs/?lab=5-labs-qa-checks#Task3:SelfQA) +- [ ] Your workshop folder structure should be similar to the one used in the [sample workshop](https://github.com/oracle-livelabs/common/tree/main/sample-livelabs-templates/sample-workshop) +- [ ] All filenames are lowercase and descriptive (including folders, images, files, etc.) +- [ ] Image references in markdown contain an alternate text. +- [ ] Ensure the PR does not contain binary or install files and files larger than 100 MB. \ No newline at end of file diff --git a/.github/scripts/fix-livelabs-markdown.sh b/.github/scripts/fix-livelabs-markdown.sh new file mode 100755 index 000000000..9573f4ba9 --- /dev/null +++ b/.github/scripts/fix-livelabs-markdown.sh @@ -0,0 +1,333 @@ +#!/bin/bash +# LiveLabs Markdown Auto-Fixer +# Automatically fixes issues that can be safely resolved without human judgment. +# Run this before submitting a PR to fix common validation errors. +# +# Usage: +# ./fix-livelabs-markdown.sh /path/to/workshop # Fix all .md files in directory +# ./fix-livelabs-markdown.sh file1.md file2.md # Fix specific files +# ./fix-livelabs-markdown.sh # Fix all .md files in current directory + +set +e + +FIXED=0 +SKIPPED=0 + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +log_fixed() { + echo -e " ${GREEN}FIXED${NC}: $1" + ((FIXED++)) +} + +log_skipped() { + echo -e " ${YELLOW}MANUAL${NC}: $1" + ((SKIPPED++)) +} + +log_section() { + echo -e "${CYAN}$1${NC}" +} + +# Get markdown files from args, directory, or find all in current directory +if [ $# -gt 0 ]; then + if [ -d "$1" ]; then + TARGET_DIR="$1" + echo "Scanning directory: $TARGET_DIR" + echo "" + FILES=$(find "$TARGET_DIR" -name "*.md" -type f | grep -v node_modules | grep -v .github | sort) + else + FILES="$@" + fi +else + FILES=$(find . -name "*.md" -type f | grep -v node_modules | grep -v .github | sort) +fi + +if [ -z "$FILES" ]; then + echo "No markdown files found." + exit 0 +fi + +echo "================================================" +echo "LiveLabs Markdown Auto-Fixer" +echo "================================================" +echo "" + +for file in $FILES; do + [ ! -f "$file" ] && continue + + echo "Processing: $file" + FILE_FIXES=0 + + # ---------------------------------------------------------------- + # Fix 9b: Replace tab characters after numbered list items with a space + # "1.\t" → "1. " + # ---------------------------------------------------------------- + if grep -qE $'^[[:space:]]*[0-9]+\\.\t' "$file"; then + perl -i -pe 's/^(\s*[0-9]+\.)\t/$1 /g' "$file" + log_fixed "Replaced tab characters with spaces in numbered list items" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 9c: Replace multiple spaces after numbered list items with a single space + # "1. text" → "1. text" + # ---------------------------------------------------------------- + if grep -qE '^[[:space:]]*[0-9]+\. ' "$file"; then + perl -i -pe 's/^(\s*[0-9]+\.) {2,}/$1 /g' "$file" + log_fixed "Replaced multiple spaces with single space in numbered list items" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 5b: Convert HTML anchor tags to Markdown links + # text → [text](URL) + # ---------------------------------------------------------------- + if grep -qi '([^<]*)|[$2]($1)|gi' "$file" + log_fixed "Converted HTML anchor tags to Markdown links" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 5: Add placeholder alt text to empty image references + # ![]( → ![image]( + # Excludes YouTube embeds: ![](youtube:...) + # ---------------------------------------------------------------- + if grep -q '!\[\](' "$file" | grep -v 'youtube:' 2>/dev/null || \ + grep -qP '!\[\]\((?!youtube:)' "$file" 2>/dev/null; then + perl -i -pe 's/!\[\]\((?!youtube:)/![image](/g' "$file" + log_fixed "Added placeholder alt text 'image' to empty image references (review and replace with descriptive text)" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 14: Lowercase image file paths in markdown references + # ![alt](images/MyFile.PNG) → ![alt](images/myfile.png) + # ---------------------------------------------------------------- + if grep -oE 'images/[^)"]+' "$file" | grep -q '[A-Z]'; then + perl -i -pe 's|(!\[[^\]]*\]\()([^)]+)(\))| + my ($pre,$path,$post) = ($1,$2,$3); + $path =~ s{(images/[^"\s)]+)}{lc($1)}ge; + "$pre$path$post" + |ge' "$file" + log_fixed "Lowercased image file paths in references" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 3: Add Acknowledgements section if missing + # ---------------------------------------------------------------- + if ! grep -q "^## Acknowledgements" "$file"; then + echo "" >> "$file" + echo "## Acknowledgements" >> "$file" + echo "" >> "$file" + echo "* **Author** - TODO: Your Name, Your Title, Your Organization" >> "$file" + echo "* **Last Updated By/Date** - TODO: Your Name, Month Year" >> "$file" + log_fixed "Added '## Acknowledgements' section at end of file (update with your details)" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 12/13: Add Estimated Time if missing + # For introduction.md: add "Estimated Workshop Time: x minutes" + # For others: add "Estimated Time: x minutes" after Introduction header + # ---------------------------------------------------------------- + basename_file=$(basename "$file") + if [ "$basename_file" = "introduction.md" ]; then + if ! grep -qi "Estimated Workshop Time.*:" "$file"; then + # Insert after ## Introduction line + if grep -q "^## Introduction" "$file"; then + perl -i -0pe 's/(^## Introduction\n)/$1\nEstimated Workshop Time: TODO - x minutes\n/' "$file" + else + sed -i "2i\\Estimated Workshop Time: TODO - x minutes\\n" "$file" + fi + log_fixed "Added 'Estimated Workshop Time:' placeholder (update with actual time)" + ((FILE_FIXES++)) + fi + else + if ! grep -qi "Estimated.*Time.*:" "$file"; then + # Insert after ## Introduction line if it exists, else after H1 + if grep -q "^## Introduction" "$file"; then + perl -i -0pe 's/(^## Introduction\n)/$1\nEstimated Time: TODO - x minutes\n/' "$file" + else + perl -i -0pe 's/(^# [^\n]+\n)/$1\nEstimated Time: TODO - x minutes\n/' "$file" + fi + log_fixed "Added 'Estimated Time:' placeholder (update with actual time)" + ((FILE_FIXES++)) + fi + fi + + # ---------------------------------------------------------------- + # Fix 11: Add Objectives section if missing + # Inserted inside Introduction section if present, else after H1 + # ---------------------------------------------------------------- + if ! grep -q "^### Objectives" "$file" && ! grep -q "^## Objectives" "$file"; then + if grep -q "^## Introduction" "$file"; then + perl -i -0pe 's/(^## Introduction\n(?:.*\n)*?)(^## )/$1### Objectives\n\nIn this lab, you will:\n* TODO: Add objectives\n\n$2/m' "$file" + else + perl -i -0pe 's/(^# [^\n]+\n)/$1\n### Objectives\n\nIn this lab, you will:\n* TODO: Add objectives\n/' "$file" + fi + log_fixed "Added '### Objectives' section (update with actual objectives)" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 10: Add Introduction section if missing (only for files with Tasks) + # ---------------------------------------------------------------- + if grep -q "^## Task" "$file" && ! grep -q "^## Introduction" "$file"; then + # Insert Introduction before the first Task + perl -i -0pe 's/(^## Task )/${\"\n## Introduction\n\nTODO: Add introduction text here.\n\n\"}$1/m' "$file" + log_fixed "Added '## Introduction' section before first Task (update with actual content)" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Fix 16-18: Fix indentation inside numbered steps (Python handles this) + # ---------------------------------------------------------------- + indentation_fixed=$(python3 - "$file" <<'PY' +import sys, re + +path = sys.argv[1] +with open(path, encoding='utf-8') as f: + lines = f.readlines() + +original = lines[:] + +# Find all ## headings and ## Task headings +heading_indices = [] +task_indices = [] +for idx, raw in enumerate(lines): + if re.match(r'^## ', raw): + heading_indices.append(idx) + if re.match(r'^## Task', raw): + task_indices.append(idx) + +fixes = 0 +for start in task_indices: + section_start = start + 1 + next_headings = [h for h in heading_indices if h > start] + section_end = next_headings[0] if next_headings else len(lines) + block = lines[section_start:section_end] + if not block: + continue + + has_ordered_list = any(re.match(r'[0-9]+\. ', ln) for ln in block) + if not has_ordered_list: + continue + + first_step_offset = None + for offset, ln in enumerate(block): + if re.match(r'[0-9]+\. ', ln): + first_step_offset = offset + break + + if first_step_offset is None: + continue + + in_code_block = False + for offset in range(first_step_offset, len(block)): + ln = block[offset] + raw_line = ln.rstrip('\n\r') + stripped = raw_line.lstrip(' ') + indent = len(raw_line) - len(stripped) + abs_idx = section_start + offset + + if stripped.startswith('```'): + if not in_code_block: + in_code_block = True + if indent < 4: + lines[abs_idx] = ' ' + stripped + '\n' + fixes += 1 + else: + in_code_block = False + if indent < 4 and stripped.rstrip() == '```': + lines[abs_idx] = ' ' + stripped + '\n' + fixes += 1 + continue + + if in_code_block: + # Indent code block content too + if indent < 4 and stripped: + lines[abs_idx] = ' ' + stripped + '\n' + fixes += 1 + continue + + if not stripped: + continue + + if re.match(r'[0-9]+\. ', raw_line): + continue + + if indent < 4: + lines[abs_idx] = ' ' + stripped + '\n' + fixes += 1 + +if fixes > 0: + with open(path, 'w', encoding='utf-8') as f: + f.writelines(lines) + print(fixes) +PY +) + + indentation_fixed=$(echo "$indentation_fixed" | tr -d '[:space:]') + if [ -n "$indentation_fixed" ] && [[ "$indentation_fixed" =~ ^[0-9]+$ ]] && [ "$indentation_fixed" -gt 0 ]; then + log_fixed "Fixed indentation ($indentation_fixed lines) inside numbered steps" + ((FILE_FIXES++)) + fi + + # ---------------------------------------------------------------- + # Report issues that need manual fixing + # ---------------------------------------------------------------- + h1_count=$(grep -c "^# " "$file" 2>/dev/null || echo 0) + first_content=$(grep -v '^$' "$file" | head -1) + + if [[ ! "$first_content" =~ ^#[^#] ]]; then + log_skipped "Missing H1 title - add '# Your Lab Title' as the first line" + fi + + if [ "$h1_count" -gt 1 ]; then + log_skipped "Multiple H1 headers ($h1_count found) - manually remove extra H1s" + fi + + open_copy=$(grep -c '' "$file" 2>/dev/null || echo 0) + close_copy=$(grep -c '' "$file" 2>/dev/null || echo 0) + open_copy=$(echo "$open_copy" | tr -d '[:space:]') + close_copy=$(echo "$close_copy" | tr -d '[:space:]') + if [ "$open_copy" -ne "$close_copy" ]; then + log_skipped "Mismatched tags (open: $open_copy, close: $close_copy) - manually fix" + fi + + task_headers=$(grep -n "^## Task" "$file" || true) + if [ -n "$task_headers" ]; then + while IFS= read -r line; do + [ -z "$line" ] && continue + if [[ ! "$line" =~ ^[0-9]+:##\ Task\ [0-9]+: ]]; then + linenum=$(echo "$line" | cut -d: -f1) + log_skipped "Task header at line $linenum doesn't follow '## Task N: Description' format - manually fix" + fi + done <<< "$task_headers" + fi + + if [ $FILE_FIXES -eq 0 ]; then + echo " No auto-fixes needed" + fi + echo "" +done + +echo "================================================" +echo "Summary" +echo "================================================" +echo "Auto-fixes applied : $FIXED" +echo "Manual fixes needed: $SKIPPED" +echo "" + +if [ $SKIPPED -gt 0 ]; then + echo -e "${YELLOW}Some issues require manual attention. Run the validator to see remaining errors.${NC}" +else + echo -e "${GREEN}All fixable issues resolved. Run the validator to confirm.${NC}" +fi diff --git a/.github/scripts/validate-livelabs-markdown.sh b/.github/scripts/validate-livelabs-markdown.sh new file mode 100755 index 000000000..cd5a011bd --- /dev/null +++ b/.github/scripts/validate-livelabs-markdown.sh @@ -0,0 +1,308 @@ +#!/bin/bash +# LiveLabs Markdown Formatting Validator +# Validates markdown files against LiveLabs formatting standards + +# Don't exit on first error - we want to check all files +set +e + +ERRORS=0 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +log_error() { + echo -e "${RED}ERROR${NC}: $1" + ((ERRORS++)) +} + +log_success() { + echo -e "${GREEN}PASS${NC}: $1" +} + +# Get markdown files from args, directory, or find all in current directory +if [ $# -gt 0 ]; then + # Check if first argument is a directory + if [ -d "$1" ]; then + TARGET_DIR="$1" + echo "Scanning directory: $TARGET_DIR" + echo "" + FILES=$(find "$TARGET_DIR" -name "*.md" -type f | grep -v node_modules | grep -v .github | sort) + else + # Treat arguments as individual files + FILES="$@" + fi +else + FILES=$(find . -name "*.md" -type f | grep -v node_modules | grep -v .github | sort) +fi + +# Check if any files were found +if [ -z "$FILES" ]; then + echo "No markdown files found." + exit 0 +fi + +echo "================================================" +echo "LiveLabs Markdown Formatting Validator" +echo "================================================" +echo "" + +for file in $FILES; do + if [ ! -f "$file" ]; then + continue + fi + + echo "Checking: $file" + FILE_ERRORS=0 + + # Rule 1: Check for H1 title (must be first non-empty line) + first_content=$(grep -v '^$' "$file" | head -1) + if [[ ! "$first_content" =~ ^#[^#] ]]; then + log_error "$file: First line must be an H1 title (# Title)" + ((FILE_ERRORS++)) + fi + + # Rule 2: Check for only one H1 per file (excluding code blocks) + # Use awk to skip content inside fenced code blocks + # Only match proper fenced code blocks: ``` alone or ```language (not inline code spans) + h1_count=$(awk ' + /^[[:space:]]*```[[:space:]]*$/ || /^[[:space:]]*```[a-zA-Z]+[[:space:]]*$/ { + in_code = !in_code + next + } + !in_code && /^# / { count++ } + END { print count+0 } + ' "$file") + if [ "$h1_count" -gt 1 ]; then + log_error "$file: Multiple H1 headers found ($h1_count). Only one H1 allowed per file." + ((FILE_ERRORS++)) + fi + + # Rule 3: Check for Acknowledgements section + if ! grep -q "^## Acknowledgements" "$file"; then + log_error "$file: Missing '## Acknowledgements' section" + ((FILE_ERRORS++)) + fi + + # Rule 4: (Removed - no longer checking for Author format in Acknowledgements) + + # Rule 5: Check image references have alt text + # Pattern: ![](images/...) is invalid, should be ![alt text](images/...) + if grep -n '!\[\]\s*(' "$file" | grep -v '^[0-9]*:.*!\[\](youtube:' > /dev/null 2>&1; then + lines=$(grep -n '!\[\]\s*(' "$file" | grep -v '!\[\](youtube:' | cut -d: -f1 | tr '\n' ', ') + log_error "$file (line $lines): Image references must have alt text: ![alt text](images/file.png)" + ((FILE_ERRORS++)) + fi + + # Rule 5b: Disallow inline HTML anchor tags + anchor_lines=$(grep -ni ') are not allowed; use Markdown links instead." + ((FILE_ERRORS++)) + done <<< "$anchor_lines" + fi + + # Rule 6: Check YouTube format is correct + if grep -E '\[.*\]\(youtube:' "$file" | grep -v '^\[]\(youtube:' > /dev/null 2>&1; then + log_error "$file: YouTube embeds should use format: [](youtube:VIDEO_ID)" + ((FILE_ERRORS++)) + fi + + # Rule 7: Check for proper Task format (## Task N: Description) + task_headers=$(grep -n "^## Task" "$file" || true) + if [ -n "$task_headers" ]; then + while IFS= read -r line; do + [ -z "$line" ] && continue + if [[ ! "$line" =~ ^[0-9]+:##\ Task\ [0-9]+: ]]; then + linenum=$(echo "$line" | cut -d: -f1) + log_error "$file (line $linenum): Task headers should follow format '## Task N: Description'" + ((FILE_ERRORS++)) + fi + + done <<< "$task_headers" + fi + + # Rule 8: Check tags are properly closed + open_copy=$(grep -c '' "$file" || true) + close_copy=$(grep -c '' "$file" || true) + if [ "$open_copy" -ne "$close_copy" ]; then + log_error "$file: Mismatched tags (open: $open_copy, close: $close_copy)" + ((FILE_ERRORS++)) + fi + + # Rule 9: Check Note format (skipped - blockquotes used for other purposes) + + # Rule 9b: Check for tab characters after numbered list items (e.g. "1.\t" instead of "1. ") + tab_lines=$(grep -En $'^[[:space:]]*[0-9]+\\.\\t' "$file" | cut -d: -f1 | tr '\n' ',' | sed 's/,$//') + if [ -n "$tab_lines" ]; then + log_error "$file (line $tab_lines): Numbered list items use a tab after the period - use a space instead (e.g. '1. ' not '1.\t')" + ((FILE_ERRORS++)) + fi + + # Rule 9c: Check for multiple spaces after numbered list items (e.g. "1. " instead of "1. ") + multspace_lines=$(grep -En '^[[:space:]]*[0-9]+\. ' "$file" | cut -d: -f1 | tr '\n' ',' | sed 's/,$//') + if [ -n "$multspace_lines" ]; then + log_error "$file (line $multspace_lines): Numbered list items have multiple spaces after the period - use a single space (e.g. '1. ' not '1. ')" + ((FILE_ERRORS++)) + fi + + # Rule 10: Check for Introduction or About section in labs + if grep -q "^## Task" "$file"; then + if ! grep -q "^## Introduction" "$file"; then + log_error "$file: Labs with Tasks should have an '## Introduction' section" + ((FILE_ERRORS++)) + fi + fi + + # Rule 11: Check for Objectives section + if ! grep -q "^### Objectives" "$file" && ! grep -q "^## Objectives" "$file"; then + log_error "$file: Missing '### Objectives' section" + ((FILE_ERRORS++)) + fi + + # Rule 12 & 13: Check for Estimated Time + basename_file=$(basename "$file") + if [ "$basename_file" = "introduction.md" ]; then + # Rule 13: introduction.md must have "Estimated Workshop Time:" + if ! grep -q "Estimated Workshop Time.*:" "$file"; then + log_error "$file: introduction.md must contain 'Estimated Workshop Time:'" + ((FILE_ERRORS++)) + fi + else + # Rule 12: Other files must have "Estimated Time:" + if ! grep -qi "Estimated.*Time.*:" "$file"; then + log_error "$file: Missing 'Estimated Time:' information" + ((FILE_ERRORS++)) + fi + fi + + # Rule 14: Check filenames in image references are lowercase + image_refs=$(grep -oE '!\[.*?\]\((images/[^)]+)\)' "$file" | grep -oE 'images/[^)]+' || true) + for img in $image_refs; do + lowercase_img=$(echo "$img" | tr '[:upper:]' '[:lower:]') + if [ "$img" != "$lowercase_img" ]; then + log_error "$file: Image filename should be lowercase: $img" + ((FILE_ERRORS++)) + fi + done + + # Rule 16-18: Task sections with ordered lists need indented content inside numbered steps. + # Task sections without ordered lists are exempt from indentation rules. + indentation_errors=$(python3 - "$file" <<'PY' +import sys, re +path = sys.argv[1] +with open(path, encoding='utf-8') as handle: + lines = handle.readlines() + +# Find all ## headings and ## Task headings +heading_indices = [] +task_indices = [] +for idx, raw in enumerate(lines): + if re.match(r'^## ', raw): + heading_indices.append(idx) + if re.match(r'^## Task', raw): + task_indices.append(idx) + +errors = [] +for pos_index, start in enumerate(task_indices): + section_start = start + 1 + # Bound section at the next ## heading (not just next Task) + next_headings = [h for h in heading_indices if h > start] + section_end = next_headings[0] if next_headings else len(lines) + block = lines[section_start:section_end] + if not block: + continue + + # Check if this task section contains a top-level ordered list + has_ordered_list = any(re.match(r'[0-9]+\. ', ln) for ln in block) + + # If no ordered list, indentation rules do not apply + if not has_ordered_list: + continue + + # Find where the first numbered step begins + first_step_offset = None + for offset, ln in enumerate(block): + if re.match(r'[0-9]+\. ', ln): + first_step_offset = offset + break + + if first_step_offset is None: + continue + + # Check indentation for all content after the first numbered step + in_code_block = False + for offset in range(first_step_offset, len(block)): + ln = block[offset] + raw_line = ln.rstrip('\n\r') + stripped = raw_line.lstrip(' ') + indent = len(raw_line) - len(stripped) + line_no = section_start + offset + 1 + + # Track fenced code blocks + if stripped.startswith('```'): + if not in_code_block: + in_code_block = True + if indent < 4: + errors.append(f"line {line_no}: Code blocks inside numbered steps must be indented with 4 spaces.") + else: + in_code_block = False + continue + + # Skip lines inside code blocks + if in_code_block: + continue + + # Skip empty lines + if not stripped: + continue + + # Skip top-level numbered step lines (e.g. "1. Step description") + if re.match(r'[0-9]+\. ', raw_line): + continue + + # All other content after the first step must be indented >= 4 spaces + if indent < 4: + if stripped.startswith('!['): + errors.append(f"line {line_no}: Images inside numbered steps must be indented with 4 spaces.") + else: + errors.append(f"line {line_no}: Content inside numbered steps must be indented with 4 spaces.") + +if errors: + sys.stdout.write("\n".join(errors)) +PY +) + + if [ -n "$indentation_errors" ]; then + while IFS= read -r err_line; do + [ -z "$err_line" ] && continue + log_error "$file: $err_line" + ((FILE_ERRORS++)) + done <<< "$indentation_errors" + fi + + # Rule 15: Check for Learn More section (optional - no check) + + if [ $FILE_ERRORS -eq 0 ]; then + log_success "$file passed all required checks" + fi + echo "" +done + +echo "================================================" +echo "Summary" +echo "================================================" +echo "Errors: $ERRORS" +echo "" + +if [ $ERRORS -gt 0 ]; then + echo -e "${RED}Validation FAILED${NC}" + exit 1 +else + echo -e "${GREEN}Validation PASSED${NC}" + exit 0 +fi diff --git a/.github/workflows/enforce-image-size.yml b/.github/workflows/enforce-image-size.yml new file mode 100644 index 000000000..d20bb536d --- /dev/null +++ b/.github/workflows/enforce-image-size.yml @@ -0,0 +1,73 @@ +name: LiveLabs Image Validation + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + check-images: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install ffmpeg + run: | + sudo apt-get update + sudo apt-get install -y ffmpeg + + - name: Detect oversized images (>1280px) + shell: bash + run: | + set -euo pipefail + MAX=1280 + + git fetch --no-tags --prune origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" || true + + mapfile -d '' files < <( + git diff --name-only --diff-filter=AM -z "origin/${GITHUB_BASE_REF}...HEAD" -- \ + '*.png' '*.jpg' '*.jpeg' '*.PNG' '*.JPG' '*.JPEG' || true + ) + + bad=0 + for f in "${files[@]}"; do + [[ -f "$f" ]] || continue + + dims="$(ffprobe -v error -select_streams v:0 \ + -show_entries stream=width,height \ + -of csv=p=0:s=x "$f" || true)" + + if [[ -z "$dims" ]]; then + echo "Skipping unreadable image: $f" + continue + fi + + w="${dims%x*}" + h="${dims#*x}" + + if [[ "$w" -gt "$MAX" || "$h" -gt "$MAX" ]]; then + echo "::error file=$f::${f} is ${w}x${h}. Max allowed is ${MAX}px (either dimension)." + bad=1 + fi + done + + if [[ "$bad" -eq 1 ]]; then + echo "" + echo "One or more images exceed ${MAX}px." + echo "Please fix this locally in your fork." + echo "" + echo "Note: this script requires 'ffmpeg' to be installed on your system." + echo " macOS (Homebrew): brew install ffmpeg" + echo " Ubuntu/Debian: sudo apt-get install ffmpeg" + echo "" + echo "From the root directory of your workshop (not the repo root!), run:" + echo " ../_imgscript/resize-image.sh" + echo "" + echo "The script will only resize images whose width or height is greater than ${MAX}px." + exit 1 + fi diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 000000000..f172fa998 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,132 @@ +name: LiveLabs Markdown Validation + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + markdown-validation: + name: Validate Markdown Formatting + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get markdown files from PR + id: changed-files + shell: bash + run: | + set -euo pipefail + + BASE_SHA="${{ github.event.pull_request.base.sha }}" + PR_SHA="${{ github.sha }}" + + git fetch --no-tags --prune origin "$BASE_SHA:$BASE_SHA" || true + + # Get changed markdown files + MD_FILES=$(git diff --name-only --diff-filter=AM "$BASE_SHA" "$PR_SHA" -- '*.md' | grep -v '^\.github/' | grep -v '^node_modules/' || true) + + if [ -z "$MD_FILES" ]; then + echo "No markdown files changed" + echo "any_changed=false" >> $GITHUB_OUTPUT + else + echo "Changed markdown files:" + echo "$MD_FILES" + # Store as space-separated for later use + echo "all_changed_files<> $GITHUB_OUTPUT + echo "$MD_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "any_changed=true" >> $GITHUB_OUTPUT + fi + + - name: Setup Node.js + if: steps.changed-files.outputs.any_changed == 'true' + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install markdownlint-cli + if: steps.changed-files.outputs.any_changed == 'true' + run: npm install -g markdownlint-cli + + - name: Run markdownlint + if: steps.changed-files.outputs.any_changed == 'true' + run: | + echo "Running markdownlint on PR files..." + echo "${{ steps.changed-files.outputs.all_changed_files }}" | xargs markdownlint --config .markdownlint.json || true + + - name: Run LiveLabs format validation + if: steps.changed-files.outputs.any_changed == 'true' + run: | + curl -sSfL https://raw.githubusercontent.com/oracle-livelabs/common/refs/heads/main/.github/scripts/validate-livelabs-markdown.sh -o /tmp/validate-livelabs-markdown.sh + chmod +x /tmp/validate-livelabs-markdown.sh + echo "${{ steps.changed-files.outputs.all_changed_files }}" | xargs /tmp/validate-livelabs-markdown.sh + + - name: Check image references exist + if: steps.changed-files.outputs.any_changed == 'true' + run: | + echo "Checking if referenced images exist..." + MISSING=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + dir=$(dirname "$file") + # Extract image paths from markdown - handle optional title like ![alt](images/file.png "title") + # Use perl regex for non-greedy matching and extract just the path before any space or quote + grep -oP '\!\[[^\]]*\]\(images/[^"\s\)]+' "$file" 2>/dev/null | grep -oP 'images/[^"\s\)]+' | while IFS= read -r img; do + full_path="$dir/$img" + if [ ! -f "$full_path" ]; then + echo "ERROR: Missing image in $file: $img" + echo "MISSING" > /tmp/missing_flag + fi + done + done <<< "${{ steps.changed-files.outputs.all_changed_files }}" + if [ -f /tmp/missing_flag ]; then + rm -f /tmp/missing_flag + echo "Some referenced images are missing!" + exit 1 + fi + echo "All referenced images exist." + + - name: Check filename conventions + if: steps.changed-files.outputs.any_changed == 'true' + run: | + echo "Checking filename conventions..." + INVALID=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + filename=$(basename "$file") + lowercase=$(echo "$filename" | tr '[:upper:]' '[:lower:]') + if [ "$filename" != "$lowercase" ]; then + echo "ERROR: Filename must be lowercase: $file" + INVALID=1 + fi + if [[ "$filename" =~ [[:space:]] ]]; then + echo "ERROR: Filename contains spaces: $file" + INVALID=1 + fi + done <<< "${{ steps.changed-files.outputs.all_changed_files }}" + if [ $INVALID -eq 1 ]; then + exit 1 + fi + echo "All filenames follow conventions." + + - name: Summary + if: always() && steps.changed-files.outputs.any_changed == 'true' + run: | + echo "## Markdown Validation Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Checked files from PR:" >> $GITHUB_STEP_SUMMARY + while IFS= read -r file; do + [ -z "$file" ] && continue + echo "- $file" >> $GITHUB_STEP_SUMMARY + done <<< "${{ steps.changed-files.outputs.all_changed_files }}" + + - name: No markdown files in PR + if: steps.changed-files.outputs.any_changed != 'true' + run: echo "No markdown files in this PR." diff --git a/sample-workshop/data-load/data-load.md b/sample-workshop/data-load/data-load.md index 8845db49c..b69b4ac28 100644 --- a/sample-workshop/data-load/data-load.md +++ b/sample-workshop/data-load/data-load.md @@ -2,9 +2,9 @@ ## Introduction -*Describe the lab in one or two sentences, for example:* This lab walks you through the steps to ... +Summarize the outcome (e.g., "This lab loads sample data into ..."). -Estimated Time: -- minutes +Estimated Time: ## minutes ### About (Optional) Enter background information here about the technology/feature or product used in this lab - no need to repeat what you covered in the introduction. Keep this section fairly concise. If you find yourself needing more than two sections/paragraphs, please utilize the "Learn More" section. @@ -35,39 +35,39 @@ This lab assumes you have: 1. Step 1 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) - To create a link to local file you want the reader to download, use the following formats. _The filename must be in lowercase letters and CANNOT include any spaces._ + To create a link to local file you want the reader to download, use the following formats. _The filename must be in lowercase letters and CANNOT include any spaces._ - Download the [starter file](files/starter-file.sql) SQL code. + Download the [starter file](files/starter-file.sql) SQL code. - When the file type is recognized by the browser, it will attempt to render it. So you can use the following format to force the download dialog box. + When the file type is recognized by the browser, it will attempt to render it. So you can use the following format to force the download dialog box. - Download the [sample JSON code](files/sample.json?download=1). + Download the [sample JSON code](files/sample.json?download=1). - > Note: do not include zip files, CSV, PDF, PSD, JAR, WAR, EAR, bin, or exe files - you must have those objects stored somewhere else. We highly recommend using Oracle Cloud Object Store and creating a PAR URL instead. See [Using Pre-Authenticated Requests](https://docs.cloud.oracle.com/en-us/iaas/Content/Object/Tasks/usingpreauthenticatedrequests.htm) + > Note: do not include zip files, CSV, PDF, PSD, JAR, WAR, EAR, bin, or exe files - you must have those objects stored somewhere else. We highly recommend using Oracle Cloud Object Store and creating a PAR URL instead. See [Using Pre-Authenticated Requests](https://docs.cloud.oracle.com/en-us/iaas/Content/Object/Tasks/usingpreauthenticatedrequests.htm) 2. Step 2 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 4. Example with inline navigation icon ![Image alt text](images/sample2.png) click **Navigation**. 5. Example with bold **text**. - If you add another paragraph, add 3 spaces before the line. + If you add another paragraph, add 3 spaces before the line. ## Task 2: Concise Task Description 1. Step 1 - tables sample - Use tables sparingly: + Use tables sparingly: - | Column 1 | Column 2 | Column 3 | - | --- | --- | --- | - | 1 | Some text or a link | More text | - | 2 |Some text or a link | More text | - | 3 | Some text or a link | More text | + | Column 1 | Column 2 | Column 3 | + | --- | --- | --- | + | 1 | Some text or a link | More text | + | 2 |Some text or a link | More text | + | 3 | Some text or a link | More text | 2. You can also include bulleted lists - make sure to indent 4 spaces: @@ -78,16 +78,16 @@ This lab assumes you have: ``` Adding code examples - Indentation is important for the code example to appear inside the step + Indentation is important for the code example to appear inside the step Multiple lines of code - Enclose the text you want to copy in . + Enclose the text you want to copy in . ``` 4. Code examples that include variables - ``` - ssh -i - ``` + ``` + ssh -i + ``` ## Learn More diff --git a/sample-workshop/howtouse-deletewhenfinished.md b/sample-workshop/howtouse-deletewhenfinished.md index 5d1e34549..b425feacc 100644 --- a/sample-workshop/howtouse-deletewhenfinished.md +++ b/sample-workshop/howtouse-deletewhenfinished.md @@ -1,73 +1,84 @@ # Workshop with a single set of labs -## Instructions - Delete this file when finished +## Instructions—delete this file when finished -1. Open the sample-workshop template in Atom or Visual Studio Code -2. We pre-created 5 folders. A workshop is created out of multiple labs. -3. Remove the comments like this one: *List objectives for this lab* -4. Make sure you use lower case folder and file name and dashes for spaces (setup-adb NOT Setup_ADB) -5. Your image names should have descriptive names. Not just adb1, adb2, adb3. For handicap accessibility, we need the image descriptions to explain what the image looks like. Remember all lowercase and dashes. -6. Download our QA doc from WMS. We find workshops get in production quicker when you know what's needed to move to production up front and you use the skeleton. +Estimated Time: X -PS: You do not need a Readme.md. Readme's exist only at the top library levels. We direct all traffic to LiveLabs since we can't track usage on GitHub. Do not create any direct links to GitHub, your workshop may be super popular but we can't track it so no one will know. +### Objectives -## Absolute Path for Oracle Cloud menu Navigation +- Understand the required structure for single-workshop labs. +- Apply naming, accessibility, and linting standards before publishing. +- Know how to reference prerequisite labs and build manifests correctly. +- Reuse the sample folder layout to create parent/child workshop variants. -**Lab 1: Provision an Instance -> Step 0: Use these Standardized Pictures for Oracle Cloud Navigation (Commonly for Provisioning)** - We've included a list of common screenshots for navigating the Oracle Cloud Menu. Please read this section and use the relevant absolute path images where appropriate. This will future proof your workshop in case of Oracle Cloud user interface updates. -## Folder Structure +1. Open the sample-workshop template in your editor (VS Code or Atom). +2. Five lab folders are pre-created; a workshop is a sequence of multiple labs. +3. Remove placeholder comments such as *List objectives for this lab*. +4. Use lowercase folder/filenames with dashes (`setup-adb`, not `Setup_ADB`). +5. Give images descriptive, lowercase, dashed names (e.g., `adb-network-policy.png`) and write meaningful alt text. +6. Download the QA checklist from WMS before you begin; teams ship faster when they understand the production requirements and follow the skeleton early. -In this example, the goal is to create several "children" workshops from one longer "parent" workshop. The children are made up of parts from the parent. +> **Reminder:** Don’t add `README.md` files inside workshops. Readmes live at the repo root because we route traffic through LiveLabs for usage tracking. Avoid direct GitHub links; traffic that bypasses LiveLabs can’t be measured. -sample-workshop/ - -- individual labs - - provision/ - setup/ - dataload/ - query/ - introduction/ - introduction.md -- description of the everything workshop, note that it is a "lab" since there is only one +## Absolute path guidance for Oracle Cloud navigation - workshops/ - freetier/ -- freetier version of the workshop - index.html - manifest.json - livelabs/ -- livelabs version of the workshop - index.html - manifest.json +**Lab 1: Provision an Instance → Step 0.** Use the standardized Oracle Cloud menu screenshots we provide so future UI changes don’t break your lab. +## Folder structure -### FreeTier vs LiveLabs +One “parent” workshop can become several “child” workshops by reusing subsets of labs: -* "FreeTier" - includes Free Trials, Paid Accounts, and for some workshops, Always Free accounts (brown button) -* "LiveLabs" - these are workshops that use Oracle-provided tenancies (green button) -* "Desktop" - this is a new deployment where the workshop is encapsulated in a NoVNC environment running in a compute instance +``` +sample-workshop/ + provision/ + setup/ + dataload/ + query/ + introduction/ + introduction.md # Treats the full workshop as a lab -### About the Workshop + workshops/ + freetier/ + index.html + manifest.json + livelabs/ + index.html + manifest.json +``` -The workshop includes all 6 of the individual labs in a single sequence. +### Tenancy vs. Sandbox -The folder structure includes an Introduction "lab" that describes the workshop as a complete set of 6 labs. Note: you may not need to have a different introduction for each of the parent and child versions of the workshops, this is illustrative only. +- **Tenancy:** Free Trial, paid, or Always Free accounts (brown button) +- **Sandbox:** Oracle-provided tenancy reservations (green button) +- **Desktop:** NoVNC environment packaged inside a compute instance -Look at the product-name-workshop/freetier folder and look at the manifest.json file to see the structure. +### About the workshop -> **Note:** The use of "Lab n:" in the titles is optional +- All six labs run in sequence. +- The introduction lab describes the full set; reuse it for child workshops unless a unique intro is required. +- See `product-name-workshop/tenancy/manifest.json` for the manifest layout. +- “Lab n:” prefixes in titles are optional. -The Prerequisite "lab" is the first lab in a common folder on the oracle-livelabs/common repo. Because this lab already exists, we can use a RAW/absolute URL instead: +### Referencing prerequisite labs - ``` - "filename": "https://oracle-livelabs.github.io/common/labs/cloud-login/cloud-login-livelabs2.md" }, - ``` +Reuse existing labs via absolute URLs: -The manifest.json file needs to know the location of each lab relative to where it exists in the hierarchy. In this structure, labs are located two levels up, for example: +``` +"filename": "https://oracle-livelabs.github.io/common/labs/cloud-login/cloud-login-livelabs2.md" +``` - ``` - "filename": "../../provision/provision.md" - ``` +Reference local labs with relative paths: -### For example: +``` +"filename": "../../provision/provision.md" +``` -This [APEX Workshop](https://oracle-livelabs.github.io/apex/spreadsheet/workshops/freetier/) is a good example of a workshop with a single set of labs: [https://github.com/oracle-livelabs/apex/tree/main/spreadsheet](https://github.com/oracle-livelabs/apex/tree/main/spreadsheet). +### Example +- Live lab: https://oracle-livelabs.github.io/apex/spreadsheet/workshops/freetier/ +- Source: https://github.com/oracle-livelabs/apex/tree/main/spreadsheet +## Acknowledgements +* **Author** - LiveLabs Team +* **Last Updated By/Date** - LiveLabs Team, 2026 diff --git a/sample-workshop/introduction/introduction.md b/sample-workshop/introduction/introduction.md index f03bd733d..50cc9de7d 100644 --- a/sample-workshop/introduction/introduction.md +++ b/sample-workshop/introduction/introduction.md @@ -4,21 +4,21 @@ This introduction covers the complete "parent" workshop. Use this text to set up the story for the workshop. Be engaging - what will the learner get from spending their time on this workshop? -Estimated Workshop Time: -- hours -- minutes (This estimate is for the entire workshop - it is the sum of the estimates provided for each of the labs included in the workshop.) +Estimated Workshop Time: ## hours ## minutes *You may add an option video, using this format: [](youtube:YouTube video id)* [](youtube:zNKxJjkq0Pw) -### Objectives +Check the [documentation](https://livelabs.oracle.com/how-to) for more information on how to add videos and other content. -*List objectives for the workshop* +### Objectives -In this workshop, you will learn how to: -* Provision -* Setup -* Load -* Query +In this workshop you will: +- Provision the required environment. +- Configure (setup) the core components. +- Load data for a realistic scenario. +- Query and validate the results. ### Prerequisites (Optional) diff --git a/sample-workshop/other-livelabs/other-livelabs.md b/sample-workshop/other-livelabs/other-livelabs.md deleted file mode 100644 index cc9641dae..000000000 --- a/sample-workshop/other-livelabs/other-livelabs.md +++ /dev/null @@ -1,8 +0,0 @@ -# Other LiveLabs you might like - - -- [Autonomous Database Dedicated](https://livelabs.oracle.com/pls/apex/dbpm/r/livelabs/view-workshop?wid=677) - -- [Manage and Monitor Autonomous Database](https://livelabs.oracle.com/pls/apex/dbpm/r/livelabs/view-workshop?wid=553) - -- [Scaling and Performance in the Autonomous Database](https://livelabs.oracle.com/pls/apex/dbpm/r/livelabs/view-workshop?wid=608) diff --git a/sample-workshop/provision/images/pic1.png b/sample-workshop/provision/images/pic1.png index 05be3f04e..60448d19e 100644 Binary files a/sample-workshop/provision/images/pic1.png and b/sample-workshop/provision/images/pic1.png differ diff --git a/sample-workshop/provision/images/pic2.png b/sample-workshop/provision/images/pic2.png index feaf6c0e2..a232e270b 100644 Binary files a/sample-workshop/provision/images/pic2.png and b/sample-workshop/provision/images/pic2.png differ diff --git a/sample-workshop/provision/provision.md b/sample-workshop/provision/provision.md index b6d2c5c17..43ad8429c 100644 --- a/sample-workshop/provision/provision.md +++ b/sample-workshop/provision/provision.md @@ -2,9 +2,9 @@ ## Introduction -*Describe the lab in one or two sentences, for example:* This lab walks you through the steps to ... +Briefly state what the learner will accomplish (e.g., “This lab walks you through provisioning ...”). -Estimated Time: -- minutes +Estimated Time: ## minutes ### About (Optional) Enter background information here about the technology/feature or product used in this lab - no need to repeat what you covered in the introduction. Keep this section fairly concise. If you find yourself needing more than two sections/paragraphs, please utilize the "Learn More" section. @@ -31,35 +31,33 @@ This lab assumes you have: ## Task 1: Concise Task Description -(optional) Task 1 opening paragraph. - 1. Step 1 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) - > **Note:** Use this format for notes, hints, and tips. Only use one "Note" at a time in a step. + > **Note:** Use this format for notes, hints, and tips. Only use one "Note" at a time in a step. 2. Step 2 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 4. Example with inline navigation icon ![Image alt text](images/sample2.png) click **Navigation**. 5. Example with bold **text**. - If you add another paragraph, add 3 spaces before the line. + If you add another paragraph, add 4 spaces before the line. ## Task 2: Concise Task Description 1. Step 1 - tables sample - Use tables sparingly: + Use tables sparingly: - | Column 1 | Column 2 | Column 3 | - | --- | --- | --- | - | 1 | Some text or a link | More text | - | 2 |Some text or a link | More text | - | 3 | Some text or a link | More text | + | Column 1 | Column 2 | Column 3 | + | --- | --- | --- | + | 1 | Some text or a link | More text | + | 2 |Some text or a link | More text | + | 3 | Some text or a link | More text | 2. You can also include bulleted lists - make sure to indent 4 spaces: @@ -77,9 +75,9 @@ This lab assumes you have: 4. Code examples that include variables - ``` - ssh -i - ``` + ``` + ssh -i + ``` ## Learn More diff --git a/sample-workshop/query/query.md b/sample-workshop/query/query.md index fe5d3a51d..8acddb091 100644 --- a/sample-workshop/query/query.md +++ b/sample-workshop/query/query.md @@ -2,9 +2,9 @@ ## Introduction -*Describe the lab in one or two sentences, for example:* This lab walks you through the steps to ... +Summarize the outcome (e.g., “This lab walks you through running queries ...”). -Estimated Lab Time: -- minutes +Estimated Time: ## minutes ### About (Optional) Enter background information here about the technology/feature or product used in this lab - no need to repeat what you covered in the introduction. Keep this section fairly concise. If you find yourself needing more than two sections/paragraphs, please utilize the "Learn More" section. @@ -35,29 +35,29 @@ This lab assumes you have: 1. Step 1 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 2. Step 2 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 4. Example with inline navigation icon ![Image alt text](images/sample2.png) click **Navigation**. 5. Example with bold **text**. - If you add another paragraph, add 3 spaces before the line. + If you add another paragraph, add 4 spaces before the line. ## Task 2: Concise Task Description 1. Step 1 - tables sample - Use tables sparingly: + Use tables sparingly: - | Column 1 | Column 2 | Column 3 | - | --- | --- | --- | - | 1 | Some text or a link | More text | - | 2 |Some text or a link | More text | - | 3 | Some text or a link | More text | + | Column 1 | Column 2 | Column 3 | + | --- | --- | --- | + | 1 | Some text or a link | More text | + | 2 |Some text or a link | More text | + | 3 | Some text or a link | More text | 2. You can also include bulleted lists - make sure to indent 4 spaces: @@ -75,9 +75,9 @@ This lab assumes you have: 4. Code examples that include variables - ``` - ssh -i - ``` + ``` + ssh -i + ``` ## Learn More diff --git a/sample-workshop/setup/setup.md b/sample-workshop/setup/setup.md index fe5d3a51d..12d5dd243 100644 --- a/sample-workshop/setup/setup.md +++ b/sample-workshop/setup/setup.md @@ -35,29 +35,29 @@ This lab assumes you have: 1. Step 1 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 2. Step 2 - ![Image alt text](images/sample1.png) + ![Image alt text](images/sample1.png) 4. Example with inline navigation icon ![Image alt text](images/sample2.png) click **Navigation**. 5. Example with bold **text**. - If you add another paragraph, add 3 spaces before the line. + If you add another paragraph, add 3 spaces before the line. ## Task 2: Concise Task Description 1. Step 1 - tables sample - Use tables sparingly: + Use tables sparingly: - | Column 1 | Column 2 | Column 3 | - | --- | --- | --- | - | 1 | Some text or a link | More text | - | 2 |Some text or a link | More text | - | 3 | Some text or a link | More text | + | Column 1 | Column 2 | Column 3 | + | --- | --- | --- | + | 1 | Some text or a link | More text | + | 2 |Some text or a link | More text | + | 3 | Some text or a link | More text | 2. You can also include bulleted lists - make sure to indent 4 spaces: @@ -68,16 +68,16 @@ This lab assumes you have: ``` Adding code examples - Indentation is important for the code example to appear inside the step + Indentation is important for the code example to appear inside the step Multiple lines of code - Enclose the text you want to copy in . + Enclose the text you want to copy in . ``` 4. Code examples that include variables - ``` - ssh -i - ``` + ``` + ssh -i + ``` ## Learn More diff --git a/sample-workshop/tables/tables.md b/sample-workshop/tables/tables.md index 529dc6f7c..b1e75f009 100644 --- a/sample-workshop/tables/tables.md +++ b/sample-workshop/tables/tables.md @@ -1,54 +1,71 @@ # Tables in LiveLabs -## Tables are cool! +## Introduction -You can define a table in Markdown just like so: +Learn how to introduce and caption tables in LiveLabs. -``` -| Tables | Are | Cool | -| ------------- | :-----------: | ----: | -| **col 3 is** | right-aligned | $1600 | -| col 2 is | *centered* | $12 | -| zebra stripes | ~~are neat~~ | $1 | -``` -The result looks like this: +Estimated Time: ## minutes -| Tables | Are | Cool | -| ------------- | :-----------: | ----: | -| **col 3 is** | right-aligned | $1600 | -| col 2 is | *centered* | $12 | -| zebra stripes | ~~are neat~~ | $1 | +### Objectives -You can see that there is a default table caption provided which is by default a concatenation of the workshop title and the lab title. +- Create Markdown tables that render well in LiveLabs. +- Add custom titles/captions when needed. +- Reference the LiveLabs Markdown cheatsheet for formatting tips. -If you don't like the default, you can also provide your own table title by adding the below the table definition: -``` -{: title="My table title"} -``` +## Task 1: Create a table -The complete markdown looks like this: +1. You can define a table in Markdown just like so: -``` -| Tables | Are | Cool | -| ------------- | :-----------: | ----: | -| **col 3 is** | right-aligned | $1600 | -| col 2 is | *centered* | $12 | -| zebra stripes | ~~are neat~~ | $1 | -{: title="My table title"} -``` + ``` + | Tables | Are | Cool | + | ------------- | :-----------: | ----: | + | **col 3 is** | right-aligned | $1600 | + | col 2 is | *centered* | $12 | + | zebra stripes | ~~are neat~~ | $1 | + ``` + The result looks like this: -Now our table looks like this: + | Tables | Are | Cool | + | ------------- | :-----------: | ----: | + | **col 3 is** | right-aligned | $1600 | + | col 2 is | *centered* | $12 | + | zebra stripes | ~~are neat~~ | $1 | -| Tables | Are | Cool | -| ------------- | :-----------: | ----: | -| **col 3 is** | right-aligned | $1600 | -| col 2 is | *centered* | $12 | -| zebra stripes | ~~are neat~~ | $1 | -{: title="My table title"} + You can see that there is a default table caption provided which is by default a concatenation of the workshop title and the lab title. -As you can see, the numbering is added automatically. + If you don't like the default, you can also provide your own table title by adding the below the table definition: -Isn't that cool? + ``` + {: title="My table title"} + ``` -You can also refer to the [LiveLabs Markdown Cheatsheet](https://c4u04.objectstorage.us-ashburn-1.oci.customer-oci.com/p/EcTjWk2IuZPZeNnD_fYMcgUhdNDIDA6rt9gaFj_WZMiL7VvxPBNMY60837hu5hga/n/c4u04/b/livelabsfiles/o/labfiles/LiveLabs_MD_Cheat_Sheet.pdf) \ No newline at end of file + The complete markdown looks like this: + + ``` + | Tables | Are | Cool | + | ------------- | :-----------: | ----: | + | **col 3 is** | right-aligned | $1600 | + | col 2 is | *centered* | $12 | + | zebra stripes | ~~are neat~~ | $1 | + {: title="My table title"} + ``` + + Now our table looks like this: + + | Tables | Are | Cool | + | ------------- | :-----------: | ----: | + | **col 3 is** | right-aligned | $1600 | + | col 2 is | *centered* | $12 | + | zebra stripes | ~~are neat~~ | $1 | + {: title="My table title"} + + As you can see, the numbering is added automatically. + + Isn't that cool? + + You can also refer to the [LiveLabs Markdown Cheatsheet](https://c4u04.objectstorage.us-ashburn-1.oci.customer-oci.com/p/EcTjWk2IuZPZeNnD_fYMcgUhdNDIDA6rt9gaFj_WZMiL7VvxPBNMY60837hu5hga/n/c4u04/b/livelabsfiles/o/labfiles/LiveLabs_MD_Cheat_Sheet.pdf) + +## Acknowledgements +* **Author** - LiveLabs Team +* **Last Updated By/Date** - LiveLabs Team, 2026 diff --git a/sample-workshop/variables/variables.md b/sample-workshop/variables/variables.md index fd1c2cde8..bc26b21df 100644 --- a/sample-workshop/variables/variables.md +++ b/sample-workshop/variables/variables.md @@ -2,58 +2,68 @@ ## Introduction -You can specify variables in another file and refer to them in your Markdown. +Define variables in a JSON file and reference them inside your Markdown. +Estimated Time: ## minutes -## Task 1: +### Objectives -Add the following to your manifest.json in the top section: +- Understand how to define variables in JSON. +- Reference variables from manifests and markdown. +- Reuse variable files across labs. -``` - "variables": ["../../variables/variables.json", - "../../variables/variables-in-another-file.json"], -``` -## Task 2 +## Task 1: Add variables to your manifest.json -Specify the variables in the .json file like this: +1. Add the following to your manifest.json in the top section: -*Example: variables.json* -``` -{ - "var1": "Variable 1 value", - "var2": "Variable 2 value", - "random_name": "LiveLabs rocks!", - "number_of_ocpu_paid": "24" - "number_of_ocpu_always_free": "2" - } - ``` + ``` + "variables": ["../../variables/variables.json", + "../../variables/variables-in-another-file.json"], + ``` -You can also add multiple files that specify variables (see the example in Task 1). +## Task 2: Add a variable file - *Example: variables_in_another_file.json* -``` -{ - "var11": "Variable 1 value but yet different", - "var22": "Variable 2 value is different", - "random_name2": "LiveLabs rocks & rules!", - "name_of_database": "My-Database-Name-is-the-best", - "magic": "What is 2*2?" - } - ``` +1. Specify the variables in the .json file like this: -## Task 3 + *Example: variables.json* -Now, you can refer to those variables using the following syntax (**Please note that you can see the syntax only in markdown**): + ``` + { + "var1": "Variable 1 value", + "var2": "Variable 2 value", + "random_name": "LiveLabs rocks!", + "number_of_ocpu_paid": "24" + "number_of_ocpu_always_free": "2" + } + ``` -[](var:var1) +2. You can also add multiple files that specify variables (see the example in Task 1). -or + *Example: variables_in_another_file.json* -[](var:magic) + ``` + { + "var11": "Variable 1 value but yet different", + "var22": "Variable 2 value is different", + "random_name2": "LiveLabs rocks & rules!",`` + "name_of_database": "My-Database-Name-is-the-best", + "magic": "What is 2*2?" + } + ``` +## Task 3: Add a variable reference -### Examples +1. Now, you can refer to those variables using the following syntax (**Please note that you can see the syntax only in markdown**): + + [](var:var1) + + or + + [](var:magic) + + +## Examples (Check the markdown to see the syntax - the bold text is the value of the variable) @@ -66,3 +76,8 @@ or - What is the best name for my database? It is **[](var:name_of_database)** - Here you can find more info: **[](var:doc_link)** + + +## Acknowledgements +* **Author** - LiveLabs Team +* **Last Updated By/Date** - LiveLabs Team, 2026 \ No newline at end of file diff --git a/sample-workshop/workshops/tenancy/manifest.json b/sample-workshop/workshops/tenancy/manifest.json index 411806534..4c6c5aefa 100644 --- a/sample-workshop/workshops/tenancy/manifest.json +++ b/sample-workshop/workshops/tenancy/manifest.json @@ -17,6 +17,11 @@ "description": "This is the prerequisites for customers using Free Trial and Paid tenancies, and Always Free accounts (if applicable). The title of the lab and the Contents Menu title (the title above) match for Prerequisite lab. This lab is always first.", "filename": "https://oracle-livelabs.github.io/common/labs/cloud-login/cloud-login.md" }, + { + "title": "Get Started", + "description": "This is the prerequisites for customers using Free Trial and Paid tenancies, and Always Free accounts (if applicable). The title of the lab and the Contents Menu title (the title above) match for Prerequisite lab. This lab is always first.", + "filename": "../../../../labs/cloud-login/cloud-login-direct-signin.md" + }, { "title": "Lab 1: Provision an Instance", "description": "Labs that follow the introduction are numbered, starting with Lab 1",