From acec9cbd5154acdff896f4971d97a56575fccdf6 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:49:00 +0000 Subject: [PATCH 01/19] feat: add subcategory support to codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Subcategory field to TerraformProviderConfig struct for YAML override support - Add Subcategory field to TerraformProviderSpecMetadata struct - Implement deriveSubcategoryFromPath() to extract subcategory from spec directory - Generate individual tfplugindocs templates per resource/data source with subcategory - Pass subcategory through to terraform provider metadata Subcategory derivation from directory structure: - specs/network/ → "Network" - specs/objects/ → "Objects" - specs/device/ → "Device" - specs/panorama/ → "Panorama" - specs/policies/ → "Policies" The generated templates in target/terraform/templates/ will have the subcategory hardcoded, eliminating the need for fix-docs.go post-processing script. --- pkg/commands/codegen/codegen.go | 151 ++++++++++++++++++ pkg/properties/normalized.go | 1 + pkg/properties/provider_file.go | 1 + .../terraform_provider_file.go | 1 + 4 files changed, 154 insertions(+) diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index 40657e28..d5e11617 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -4,6 +4,9 @@ import ( "context" "fmt" "log/slog" + "os" + "path/filepath" + "strings" "github.com/paloaltonetworks/pan-os-codegen/pkg/generate" "github.com/paloaltonetworks/pan-os-codegen/pkg/load" @@ -44,6 +47,141 @@ func NewCommand(ctx context.Context, commandType properties.CommandType, args .. }, nil } +// deriveSubcategoryFromPath extracts the subcategory from the spec file path. +// It maps directory names to proper subcategory names. +// For example: specs/network/interface.yaml -> "Network" +func deriveSubcategoryFromPath(specPath string) string { + // Extract the directory name between specs/ and the filename + dir := filepath.Dir(specPath) + parts := strings.Split(filepath.ToSlash(dir), "/") + + // Find the part after "specs" + var category string + for i, part := range parts { + if part == "specs" && i+1 < len(parts) { + category = parts[i+1] + break + } + } + + // Map directory names to subcategory names + subcategoryMap := map[string]string{ + "network": "Network", + "objects": "Objects", + "device": "Device", + "panorama": "Panorama", + "policies": "Policies", + "actions": "", // empty for actions + "schema": "", // empty for schema + } + + if subcategory, ok := subcategoryMap[category]; ok { + return subcategory + } + + // Default to empty string if no mapping found + return "" +} + +// generateTfplugindocsTemplates creates individual documentation templates for each resource/data source +// with the correct subcategory. These templates are used by terraform-plugin-docs when generating documentation. +func generateTfplugindocsTemplates(outputDir string, specMetadata map[string]properties.TerraformProviderSpecMetadata) error { + templatesDir := filepath.Join(outputDir, "templates") + resourcesDir := filepath.Join(templatesDir, "resources") + dataSourcesDir := filepath.Join(templatesDir, "data-sources") + + if err := os.MkdirAll(resourcesDir, 0755); err != nil { + return fmt.Errorf("error creating resources templates directory: %w", err) + } + if err := os.MkdirAll(dataSourcesDir, 0755); err != nil { + return fmt.Errorf("error creating data sources templates directory: %w", err) + } + + resourceCount := 0 + dataSourceCount := 0 + + for resourceSuffix, metadata := range specMetadata { + // Generate template for resources + if metadata.Flags&properties.TerraformSpecResource != 0 { + subcategory := metadata.Subcategory + if subcategory == "" { + subcategory = "Uncategorized" + } + + resourceTemplate := fmt.Sprintf(`--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "%s" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" .ImportFile }} +{{- end }} +`, subcategory) + + resourcePath := filepath.Join(resourcesDir, fmt.Sprintf("panos%s.md.tmpl", resourceSuffix)) + if err := os.WriteFile(resourcePath, []byte(resourceTemplate), 0644); err != nil { + return fmt.Errorf("error writing resource template %s: %w", resourcePath, err) + } + resourceCount++ + } + + // Generate template for data sources + if metadata.Flags&properties.TerraformSpecDatasource != 0 { + subcategory := metadata.Subcategory + if subcategory == "" { + subcategory = "Uncategorized" + } + + dataSourceTemplate := fmt.Sprintf(`--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "%s" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +`, subcategory) + + dataSourcePath := filepath.Join(dataSourcesDir, fmt.Sprintf("panos%s.md.tmpl", resourceSuffix)) + if err := os.WriteFile(dataSourcePath, []byte(dataSourceTemplate), 0644); err != nil { + return fmt.Errorf("error writing data source template %s: %w", dataSourcePath, err) + } + dataSourceCount++ + } + } + + slog.Info("Generated tfplugindocs templates", "resources", resourceCount, "dataSources", dataSourceCount, "templatesDir", templatesDir) + return nil +} + func (c *Command) Setup() error { var err error if c.specs == nil { @@ -94,6 +232,13 @@ func (c *Command) Execute() error { return fmt.Errorf("%s sanity failed: %s", specPath, err) } + // Extract subcategory: use YAML override if present, otherwise derive from path + if c.commandType == properties.CommandTypeTerraform { + if spec.TerraformProviderConfig.Subcategory == "" { + spec.TerraformProviderConfig.Subcategory = deriveSubcategoryFromPath(specPath) + } + } + if c.commandType == properties.CommandTypeTerraform { var singularVariant, pluralVariant bool // For specs that are missing resource_variants, default to generating @@ -198,6 +343,12 @@ func (c *Command) Execute() error { if err != nil { return fmt.Errorf("error generating terraform code: %w", err) } + + // Generate tfplugindocs templates with subcategory support + if err = generateTfplugindocsTemplates(config.Output.TerraformProvider, specMetadata); err != nil { + return fmt.Errorf("error generating tfplugindocs templates: %w", err) + } + slog.Debug("Generated Terraform resources", "resources", resourceList, "dataSources", dataSourceList) } diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 71479dfe..7d05c3fc 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -81,6 +81,7 @@ const ( type TerraformProviderConfig struct { Description string `json:"description" yaml:"description"` + Subcategory string `json:"subcategory" yaml:"subcategory"` Ephemeral bool `json:"ephemeral" yaml:"ephemeral"` Action bool `json:"action" yaml:"action"` CustomValidation bool `json:"custom_validation" yaml:"custom_validation"` diff --git a/pkg/properties/provider_file.go b/pkg/properties/provider_file.go index e4e86245..10a9e815 100644 --- a/pkg/properties/provider_file.go +++ b/pkg/properties/provider_file.go @@ -76,6 +76,7 @@ const ( type TerraformProviderSpecMetadata struct { ResourceSuffix string StructName string + Subcategory string Flags TerraformSpecFlags } diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index ffe8ceb8..88b5bf7c 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -102,6 +102,7 @@ func (g *GenerateTerraformProvider) appendResourceType(spec *properties.Normaliz terraformProvider.SpecMetadata[names.MetaName] = properties.TerraformProviderSpecMetadata{ ResourceSuffix: names.MetaName, StructName: names.StructName, + Subcategory: spec.TerraformProviderConfig.Subcategory, Flags: flags, } return nil From 313d1bd67133c75d10a56ec3a9b74fd9a9a8b4ae Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:55:34 +0000 Subject: [PATCH 02/19] feat: add release automation script Add scripts/release.sh to automate the PAN-OS release process with three modes: - --auto: Fully automated (runs codegen, versions, tags, pushes) - --manual: Interactive (prompts for confirmation, no auto-push) - --dry-run: Simulation (shows what would be done) The script handles: - Running codegen in pan-os-codegen - Copying generated code to pango and terraform-provider-panos - Version determination using standard-version (with fallback) - Creating commits and tags - Running gofix and terraform doc generation - Validating subcategories in documentation - Pushing to remote (in auto mode only) Uses npx standard-version for conventional commit-based versioning, with fallback to manual detection if standard-version is unavailable. Includes comprehensive logging with timestamps and colored output. --- scripts/release.sh | 411 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100755 scripts/release.sh diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..a4bf5265 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,411 @@ +#!/bin/bash +# scripts/release.sh - Automates the PAN-OS release process +# +# This script automates the process of releasing pango and terraform-provider-panos +# by running codegen, copying generated code, and creating version tags. +# +# Usage: +# ./scripts/release.sh [--auto|--manual|--dry-run] +# +# Modes: +# --auto Fully automated mode (runs everything, creates tags, pushes to remote) +# --manual Interactive mode (prompts for confirmation at key steps, no auto-push) +# --dry-run Simulation mode (shows what would be done without making changes) + +set -e # Exit on error +set -o pipefail + +# Configuration +MODE="manual" # Default mode +CODEGEN_DIR="$HOME/workspace/pan-os-codegen" +PANGO_DIR="$HOME/workspace/pango" +TERRAFORM_DIR="$HOME/workspace/terraform-provider-panos" +GOFIX_SCRIPT="$HOME/workspace/zsh-scripts/go-mod-replace.sh" +FIXDOCS_SCRIPT="$HOME/workspace/zsh-scripts/fix-docs.go" +FIXDOCS_CONFIG="$HOME/workspace/zsh-scripts/config.json" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${BLUE}$(date '+%Y-%m-%d %H:%M:%S')${NC} - $1" +} + +log_success() { + echo -e "${GREEN}$(date '+%Y-%m-%d %H:%M:%S')${NC} - $1" +} + +log_warning() { + echo -e "${YELLOW}$(date '+%Y-%m-%d %H:%M:%S')${NC} - $1" +} + +log_error() { + echo -e "${RED}$(date '+%Y-%m-%d %H:%M:%S')${NC} - $1" +} + +# Parse command-line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + --auto) + MODE="auto" + shift + ;; + --manual) + MODE="manual" + shift + ;; + --dry-run) + MODE="dry-run" + shift + ;; + -h|--help) + echo "Usage: $0 [--auto|--manual|--dry-run]" + echo "" + echo "Modes:" + echo " --auto Fully automated mode (runs everything, creates tags, pushes to remote)" + echo " --manual Interactive mode (prompts for confirmation at key steps, no auto-push)" + echo " --dry-run Simulation mode (shows what would be done without making changes)" + exit 0 + ;; + *) + log_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done +} + +# Execute command based on mode +execute() { + local cmd="$1" + local description="$2" + + if [ "$MODE" = "dry-run" ]; then + log_info "[DRY-RUN] $description" + log_info "[DRY-RUN] Would execute: $cmd" + else + log_info "$description" + eval "$cmd" + fi +} + +# Check if repository is clean +check_repo_clean() { + local repo_dir="$1" + local repo_name=$(basename "$repo_dir") + + cd "$repo_dir" + + if [ -n "$(git status --porcelain)" ]; then + log_warning "Repository $repo_name has uncommitted changes" + + if [ "$MODE" = "auto" ]; then + log_error "Auto mode requires clean repositories. Exiting." + exit 1 + elif [ "$MODE" = "manual" ]; then + echo -n "Stash changes in $repo_name? (y/n): " + read -r response + if [[ "$response" =~ ^[Yy]$ ]]; then + execute "git stash" "Stashing changes in $repo_name" + else + log_error "Please commit or stash changes before continuing" + exit 1 + fi + fi + else + log_success "Repository $repo_name is clean" + fi +} + +# Check if npx is available +check_npx() { + if ! command -v npx &> /dev/null; then + log_error "npx not found. Please install Node.js to use conventional-changelog tooling." + log_error "Alternatively, install with: brew install node" + exit 1 + fi +} + +# Determine next version using conventional commits with standard-version +determine_next_version() { + local repo_dir="$1" + cd "$repo_dir" + + # Get the last tag + local last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + log_info "Last tag: $last_tag" + + # Get commits since last tag + local commits=$(git log --oneline --no-merges "$last_tag..HEAD" --format="%s" 2>/dev/null || echo "") + + if [ -z "$commits" ]; then + log_warning "No new commits since $last_tag" + echo "$last_tag" + return + fi + + # Use npx standard-version to determine next version (dry-run mode) + log_info "Using standard-version to determine next version..." + local next_version=$(npx --yes standard-version --dry-run --silent 2>&1 | grep "tagging release" | sed -n 's/.*tagging release \(v[0-9.]*\)/\1/p' | head -1) + + # If standard-version didn't work, fall back to manual detection + if [ -z "$next_version" ]; then + log_warning "standard-version failed, using fallback version detection" + + # Parse current version + local version="${last_tag#v}" + IFS='.' read -r major minor patch <<< "$version" + + # Check for breaking changes (major bump) + if echo "$commits" | grep -qE "^[^:]+!:|BREAKING CHANGE:"; then + major=$((major + 1)) + minor=0 + patch=0 + # Check for features (minor bump) + elif echo "$commits" | grep -qE "^feat:"; then + minor=$((minor + 1)) + patch=0 + # Default to patch bump + else + patch=$((patch + 1)) + fi + + next_version="v${major}.${minor}.${patch}" + fi + + log_info "Next version: $next_version" + echo "$next_version" +} + +# Run codegen +run_codegen() { + log_info "Running codegen..." + cd "$CODEGEN_DIR" + + execute "make clean" "Cleaning previous build" + execute "make codegen" "Generating code" + + log_success "Codegen completed" +} + +# Copy generated code +copy_generated_code() { + local source_dir="$1" + local dest_dir="$2" + local name="$3" + + log_info "Copying generated code to $name..." + + if [ "$MODE" = "dry-run" ]; then + log_info "[DRY-RUN] Would copy: $source_dir -> $dest_dir" + else + cp -r "$source_dir"/* "$dest_dir"/ + log_success "Copied generated code to $name" + fi +} + +# Create commit and tag +create_commit_and_tag() { + local repo_dir="$1" + local version="$2" + local message="$3" + + cd "$repo_dir" + + if [ "$MODE" = "manual" ]; then + echo -n "Create commit and tag $version? (y/n): " + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + log_warning "Skipping commit and tag creation" + return + fi + fi + + execute "git add -A" "Staging all changes" + execute "git commit -m '$message'" "Creating commit" + execute "git tag -a '$version' -m 'Release $version'" "Creating tag $version" + + log_success "Created commit and tag $version" +} + +# Push to remote +push_to_remote() { + local repo_dir="$1" + local repo_name="$2" + + cd "$repo_dir" + + if [ "$MODE" = "auto" ]; then + execute "git push origin HEAD" "Pushing commits to remote" + execute "git push origin --tags" "Pushing tags to remote" + log_success "Pushed $repo_name to remote" + else + log_info "Skipping push in $MODE mode (push manually when ready)" + fi +} + +# Release pango +release_pango() { + log_info "=== Releasing pango ===" + + # Copy generated code + copy_generated_code "$CODEGEN_DIR/target/pango" "$PANGO_DIR" "pango" + + # Determine next version + local next_version=$(determine_next_version "$PANGO_DIR") + + if [ -z "$next_version" ] || [ "$next_version" = "$(cd $PANGO_DIR && git describe --tags --abbrev=0 2>/dev/null)" ]; then + log_warning "No version bump needed for pango" + return + fi + + # Create commit and tag + create_commit_and_tag "$PANGO_DIR" "$next_version" "chore(release): auto-generated $next_version" + + # Push to remote + push_to_remote "$PANGO_DIR" "pango" + + log_success "Pango release completed: $next_version" +} + +# Validate subcategories in docs +validate_subcategories() { + local docs_dir="$TERRAFORM_DIR/docs" + + log_info "Validating subcategories in documentation..." + + if [ ! -d "$docs_dir" ]; then + log_warning "Docs directory not found, skipping validation" + return 0 + fi + + local missing=$(grep -r "subcategory: \$" "$docs_dir" 2>/dev/null || true) + + if [ -n "$missing" ]; then + log_error "Found missing subcategories:" + echo "$missing" + + if [ "$MODE" = "auto" ]; then + log_error "Auto mode requires all subcategories to be present. Exiting." + exit 1 + elif [ "$MODE" = "manual" ]; then + echo -n "Continue anyway? (y/n): " + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + exit 1 + fi + fi + return 1 + else + log_success "All subcategories present" + return 0 + fi +} + +# Release terraform provider +release_terraform_provider() { + log_info "=== Releasing terraform-provider-panos ===" + + # Copy generated code + copy_generated_code "$CODEGEN_DIR/target/terraform" "$TERRAFORM_DIR" "terraform-provider-panos" + + cd "$TERRAFORM_DIR" + + # Run gofix + if [ -f "$GOFIX_SCRIPT" ]; then + log_info "Running gofix to update pango dependency..." + execute "bash '$GOFIX_SCRIPT'" "Running go-mod-replace.sh" + else + log_warning "gofix script not found at $GOFIX_SCRIPT, skipping" + fi + + # Run go mod tidy + execute "go mod tidy" "Running go mod tidy" + + # Generate docs + log_info "Generating terraform docs..." + execute "go generate ./..." "Running go generate" + + # Validate subcategories + validate_subcategories + + # Determine next version + local next_version=$(determine_next_version "$TERRAFORM_DIR") + + if [ -z "$next_version" ] || [ "$next_version" = "$(cd $TERRAFORM_DIR && git describe --tags --abbrev=0 2>/dev/null)" ]; then + log_warning "No version bump needed for terraform-provider-panos" + return + fi + + # Create commit and tag + create_commit_and_tag "$TERRAFORM_DIR" "$next_version" "chore(release): auto-generated $next_version" + + # Push to remote + push_to_remote "$TERRAFORM_DIR" "terraform-provider-panos" + + log_success "Terraform provider release completed: $next_version" +} + +# Display summary +display_summary() { + log_info "=== Release Summary ===" + echo "" + echo "Mode: $MODE" + echo "" + echo "Pango:" + echo " Directory: $PANGO_DIR" + echo " Latest tag: $(cd $PANGO_DIR && git describe --tags --abbrev=0 2>/dev/null || echo 'none')" + echo "" + echo "Terraform Provider:" + echo " Directory: $TERRAFORM_DIR" + echo " Latest tag: $(cd $TERRAFORM_DIR && git describe --tags --abbrev=0 2>/dev/null || echo 'none')" + echo "" + + if [ "$MODE" = "auto" ]; then + log_success "Releases pushed to remote" + else + log_info "Changes are local only. Push manually when ready:" + echo " cd $PANGO_DIR && git push origin HEAD && git push origin --tags" + echo " cd $TERRAFORM_DIR && git push origin HEAD && git push origin --tags" + fi +} + +# Main function +main() { + parse_args "$@" + + log_info "Running in $MODE mode..." + echo "" + + # Check for required tools + check_npx + + # Check all repositories are clean + check_repo_clean "$CODEGEN_DIR" + check_repo_clean "$PANGO_DIR" + check_repo_clean "$TERRAFORM_DIR" + + # Run codegen + run_codegen + + # Release pango + release_pango + + # Release terraform provider + release_terraform_provider + + # Display summary + display_summary + + log_success "Release automation completed!" +} + +# Run main +main "$@" From 49106095849acd97b12854f5f73bf12d94160bfb Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Thu, 12 Feb 2026 17:06:47 +0000 Subject: [PATCH 03/19] fix: correct Makefile config path and accumulate metadata flags - Fix Makefile to use correct config path (cmd/codegen/config.yaml) - Accumulate flags when same resource has both resource and datasource - Add debug logging for template generation This ensures tfplugindocs templates are generated correctly with proper subcategories for all resources and data sources. --- Makefile | 2 +- pkg/commands/codegen/codegen.go | 3 +++ pkg/translate/terraform_provider/terraform_provider_file.go | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 62233783..5fd9fa95 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ assets: $(ASSETS_DST) .PHONY: codegen codegen: codegen-stamp codegen-stamp: target/codegen $(CODEGEN_SPECS) - CODEGEN_LOG_LEVEL=$(CODEGEN_LOG_LEVEL) ./target/codegen -config config.yaml + CODEGEN_LOG_LEVEL=$(CODEGEN_LOG_LEVEL) ./target/codegen -config cmd/codegen/config.yaml touch $@ $(GENERATED_OUT_PATH)/%: assets/% diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index d5e11617..891afceb 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -101,6 +101,8 @@ func generateTfplugindocsTemplates(outputDir string, specMetadata map[string]pro dataSourceCount := 0 for resourceSuffix, metadata := range specMetadata { + slog.Debug("Processing spec metadata", "resourceSuffix", resourceSuffix, "subcategory", metadata.Subcategory, "flags", metadata.Flags) + // Generate template for resources if metadata.Flags&properties.TerraformSpecResource != 0 { subcategory := metadata.Subcategory @@ -345,6 +347,7 @@ func (c *Command) Execute() error { } // Generate tfplugindocs templates with subcategory support + slog.Debug("Generating tfplugindocs templates", "metadataCount", len(specMetadata)) if err = generateTfplugindocsTemplates(config.Output.TerraformProvider, specMetadata); err != nil { return fmt.Errorf("error generating tfplugindocs templates: %w", err) } diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index 88b5bf7c..062c00ca 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -99,6 +99,11 @@ func (g *GenerateTerraformProvider) appendResourceType(spec *properties.Normaliz case properties.ResourceCustom, properties.ResourceConfig: } + // Accumulate flags if metadata already exists for this resource + if existing, ok := terraformProvider.SpecMetadata[names.MetaName]; ok { + flags |= existing.Flags + } + terraformProvider.SpecMetadata[names.MetaName] = properties.TerraformProviderSpecMetadata{ ResourceSuffix: names.MetaName, StructName: names.StructName, From a8cb8050217479a1ca64fc7bb5b38a95a2adea79 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Thu, 12 Feb 2026 17:50:54 +0000 Subject: [PATCH 04/19] fix: remove panos prefix from template filenames terraform-plugin-docs automatically prepends the provider name when looking up templates. Template files should be named without the provider prefix: - Before: panos_address.md.tmpl (looked for panos_panos_address) - After: address.md.tmpl (correctly looks for panos_address) This fixes the 'does not exist' error when generating terraform docs. --- pkg/commands/codegen/codegen.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index 891afceb..91c6f93b 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -138,7 +138,10 @@ Import is supported using the following syntax: {{- end }} `, subcategory) - resourcePath := filepath.Join(resourcesDir, fmt.Sprintf("panos%s.md.tmpl", resourceSuffix)) + // Remove leading underscore from resourceSuffix for template filename + // terraform-plugin-docs automatically prepends the provider name (panos_) + templateName := strings.TrimPrefix(resourceSuffix, "_") + resourcePath := filepath.Join(resourcesDir, fmt.Sprintf("%s.md.tmpl", templateName)) if err := os.WriteFile(resourcePath, []byte(resourceTemplate), 0644); err != nil { return fmt.Errorf("error writing resource template %s: %w", resourcePath, err) } @@ -172,7 +175,10 @@ description: |- {{ .SchemaMarkdown | trimspace }} `, subcategory) - dataSourcePath := filepath.Join(dataSourcesDir, fmt.Sprintf("panos%s.md.tmpl", resourceSuffix)) + // Remove leading underscore from resourceSuffix for template filename + // terraform-plugin-docs automatically prepends the provider name (panos_) + templateName := strings.TrimPrefix(resourceSuffix, "_") + dataSourcePath := filepath.Join(dataSourcesDir, fmt.Sprintf("%s.md.tmpl", templateName)) if err := os.WriteFile(dataSourcePath, []byte(dataSourceTemplate), 0644); err != nil { return fmt.Errorf("error writing data source template %s: %w", dataSourcePath, err) } From 1a26abc63fa8f881f9bb831b28c1633e1665f3bf Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:56:26 +0000 Subject: [PATCH 05/19] feat(codegen): Mark sensitive variables with sensitive flag Add sensitive: true to API key in terraform provider config and to private-key field in certificate-import spec to ensure these values are handled securely in generated Terraform code. Co-Authored-By: Claude Sonnet 4.6 --- cmd/codegen/config.yaml | 1 + specs/device/certificate-import.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cmd/codegen/config.yaml b/cmd/codegen/config.yaml index e582f675..1fe03ef3 100644 --- a/cmd/codegen/config.yaml +++ b/cmd/codegen/config.yaml @@ -45,6 +45,7 @@ terraform_provider_config: description: "The API key for PAN-OS. Either specify this or give both username and password." env_name: "PANOS_API_KEY" optional: true + sensitive: true type: string protocol: description: "The protocol (https or http)." diff --git a/specs/device/certificate-import.yaml b/specs/device/certificate-import.yaml index 43a46b21..3a2f5296 100644 --- a/specs/device/certificate-import.yaml +++ b/specs/device/certificate-import.yaml @@ -243,6 +243,9 @@ spec: type: string profiles: - xpath: ["private-key"] + codegen_overrides: + terraform: + sensitive: true - name: passphrase type: string profiles: From 4c5e18edfa53c1932607225ab79a0f5cb3032959 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:04:18 +0100 Subject: [PATCH 06/19] feat(ci): Add automated release pipeline - release.yml: GitHub Actions workflow that generates code, runs tests, pushes pango SDK, and creates a provider PR with release notes - determine-version.sh: Detects next version from conventional commits with custom release rules (breaking=minor, feat=patch) - generate-release-notes.sh: Generates markdown release notes grouped by commit type Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 297 ++++++++++++++++++++++++++++++ scripts/determine-version.sh | 120 ++++++++++++ scripts/generate-release-notes.sh | 99 ++++++++++ 3 files changed, 516 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100755 scripts/determine-version.sh create mode 100755 scripts/generate-release-notes.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f0424784 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,297 @@ +# Release pipeline for PAN-OS Terraform provider and pango Go SDK. +# +# Generates code, runs tests, pushes pango to main, and creates a PR +# in terraform-provider-panos. Merging that PR triggers GoReleaser +# via the auto-release workflow in the provider repo. +# +# Prerequisites: +# - GitHub App configured with contents:write on pango and terraform-provider-panos +# - Repository secrets: CODEGEN_APP_ID, CODEGEN_PRIVATE_KEY, CODEGEN_INSTALLATION_ID + +name: Release +run-name: "Release ${{ inputs.version_override || 'auto-detect' }}" + +on: + workflow_dispatch: + inputs: + version_override: + description: "Override auto-detected version (e.g. v2.1.0). Leave empty for auto-detection." + required: false + type: string + +permissions: + contents: write + +jobs: + generate-and-test: + name: Generate & Test + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + last_tag: ${{ steps.version.outputs.last_tag }} + since_date: ${{ steps.version.outputs.since_date }} + steps: + - name: Checkout pan-os-codegen + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 + with: + go-version: "1.23" + + - name: Generate code + run: make codegen + + - name: Run codegen tests + run: make test/codegen + + - name: Run pango SDK tests + run: make test/pango + + - name: Determine version + id: version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + LAST_TAG=$(gh release view --repo PaloAltoNetworks/terraform-provider-panos --json tagName -q '.tagName' 2>/dev/null || echo "v0.0.0") + SINCE_DATE=$(gh release view "$LAST_TAG" --repo PaloAltoNetworks/terraform-provider-panos --json publishedAt -q '.publishedAt' 2>/dev/null || echo "") + + if [ -n "${{ inputs.version_override }}" ]; then + VERSION="${{ inputs.version_override }}" + else + VERSION=$(bash scripts/determine-version.sh --last-tag "$LAST_TAG") + if [ "$VERSION" = "NO_BUMP" ]; then + echo "::error::No version-bumping commits found since $LAST_TAG" + exit 1 + fi + fi + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT + echo "since_date=$SINCE_DATE" >> $GITHUB_OUTPUT + echo "## Version" >> $GITHUB_STEP_SUMMARY + echo "- Current: $LAST_TAG" >> $GITHUB_STEP_SUMMARY + echo "- Next: $VERSION" >> $GITHUB_STEP_SUMMARY + + - name: Generate release notes + run: | + bash scripts/generate-release-notes.sh \ + "${{ steps.version.outputs.version }}" \ + "${{ steps.version.outputs.since_date }}" \ + > target/release-notes.md + echo "## Release Notes" >> $GITHUB_STEP_SUMMARY + cat target/release-notes.md >> $GITHUB_STEP_SUMMARY + + - name: Validate subcategories + run: | + MISSING_VALUE=$(grep -rlE '^subcategory:\s*("")?\s*$' \ + target/terraform/docs/resources/ \ + target/terraform/docs/data-sources/ 2>/dev/null || true) + + MISSING_FIELD=$(find target/terraform/docs/resources target/terraform/docs/data-sources \ + -name "*.md" ! -exec grep -q "^subcategory:" {} \; -print 2>/dev/null || true) + + PROBLEMS="${MISSING_VALUE}${MISSING_FIELD}" + if [ -n "$PROBLEMS" ]; then + echo "::error::Resources missing subcategory:" + echo "$PROBLEMS" + exit 1 + fi + echo "All resources have valid subcategories" + + - name: Upload generated code + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: generated-code + path: | + target/pango/ + target/terraform/ + target/release-notes.md + retention-days: 3 + if-no-files-found: error + + push-pango: + name: Push Pango SDK + needs: generate-and-test + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.push.outputs.has_changes }} + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CODEGEN_APP_ID }} + private-key: ${{ secrets.CODEGEN_PRIVATE_KEY }} + installation-id: ${{ secrets.CODEGEN_INSTALLATION_ID }} + owner: PaloAltoNetworks + repositories: pango + + - name: Checkout pango + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: PaloAltoNetworks/pango + token: ${{ steps.app-token.outputs.token }} + path: pango + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: generated + + - name: Sync and push pango + id: push + run: | + # Copy generated SDK over (preserving non-generated files like .git) + rsync -av --exclude '.git' generated/pango/ pango/ + + cd pango + + if git diff --quiet && [ -z "$(git status --porcelain)" ]; then + echo "No changes in pango SDK" + echo "has_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: auto-generated pango SDK" + git push + + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Pango SDK pushed to main" + + create-provider-pr: + name: Create Provider PR + needs: [generate-and-test, push-pango] + runs-on: ubuntu-latest + outputs: + pr_url: ${{ steps.create-pr.outputs.pr_url }} + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CODEGEN_APP_ID }} + private-key: ${{ secrets.CODEGEN_PRIVATE_KEY }} + installation-id: ${{ secrets.CODEGEN_INSTALLATION_ID }} + owner: PaloAltoNetworks + repositories: terraform-provider-panos + + - name: Set up Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 + with: + go-version: "1.23" + + - name: Checkout provider + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: PaloAltoNetworks/terraform-provider-panos + token: ${{ steps.app-token.outputs.token }} + path: provider + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: generated + + - name: Sync generated code to provider + run: | + # Copy generated terraform code over, excluding repo-specific files + rsync -av --exclude '.git' --exclude '.github' --exclude '.goreleaser.yml' \ + --exclude 'GNUmakefile' --exclude 'LICENSE' --exclude 'README.md' \ + --exclude 'SUPPORT.md' --exclude 'terraform-registry-manifest.json' \ + --exclude '.gitignore' --exclude 'scripts' \ + generated/terraform/ provider/ + + - name: Update pango dependency and generate docs + working-directory: provider + run: | + # Fetch the latest pango from main (just pushed in previous job) + go get github.com/PaloAltoNetworks/pango@main + go mod tidy + + # Generate terraform plugin documentation + go generate ./... + + - name: Validate subcategories in provider + working-directory: provider + run: | + MISSING=$(grep -rlE '^subcategory:\s*("")?\s*$' \ + docs/resources/ docs/data-sources/ 2>/dev/null || true) + if [ -n "$MISSING" ]; then + echo "::error::Resources missing subcategory after doc generation: $MISSING" + exit 1 + fi + + - name: Create PR + id: create-pr + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ needs.generate-and-test.outputs.version }} + working-directory: provider + run: | + BRANCH="auto-release/${VERSION}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add . + + if git diff --staged --quiet; then + echo "::error::No changes to commit in provider" + exit 1 + fi + + git commit -m "chore(release): auto-generated ${VERSION}" + git push -u origin "$BRANCH" + + RELEASE_NOTES=$(cat ../generated/release-notes.md) + + PR_URL=$(gh pr create \ + --repo PaloAltoNetworks/terraform-provider-panos \ + --title "chore(release): ${VERSION}" \ + --body "$(cat < + ${RELEASE_NOTES} + + PREOF + )") + + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + echo "## Provider PR" >> $GITHUB_STEP_SUMMARY + echo "Created: $PR_URL" >> $GITHUB_STEP_SUMMARY + + tag-codegen: + name: Tag Codegen + needs: [generate-and-test, create-provider-pr] + runs-on: ubuntu-latest + steps: + - name: Checkout pan-os-codegen + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + + - name: Tag release + env: + VERSION: ${{ needs.generate-and-test.outputs.version }} + run: | + git tag "release/${VERSION}" + git push origin "release/${VERSION}" + echo "Tagged pan-os-codegen with release/${VERSION}" diff --git a/scripts/determine-version.sh b/scripts/determine-version.sh new file mode 100755 index 00000000..0e5beff9 --- /dev/null +++ b/scripts/determine-version.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# +# Determines the next version for terraform-provider-panos based on +# conventional commits in pan-os-codegen since the last provider release. +# +# Custom release rules (non-standard semver): +# feat(MAJOR): ... -> major bump +# BREAKING CHANGE in footer -> minor bump (not major!) +# feat: ... -> patch bump (not minor!) +# fix: ... -> patch bump +# +# Usage: +# determine-version.sh # auto-detect from local repos +# determine-version.sh --provider-dir # specify provider repo path +# determine-version.sh --last-tag # specify last tag directly (for CI) + +set -euo pipefail + +PROVIDER_DIR="" +LAST_TAG="" + +while [[ $# -gt 0 ]]; do + case $1 in + --provider-dir) PROVIDER_DIR="$2"; shift 2 ;; + --last-tag) LAST_TAG="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +# Resolve the last tag +if [ -z "$LAST_TAG" ]; then + if [ -n "$PROVIDER_DIR" ] && [ -d "$PROVIDER_DIR/.git" ]; then + LAST_TAG=$(cd "$PROVIDER_DIR" && git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + elif command -v gh &>/dev/null; then + LAST_TAG=$(gh release view --repo PaloAltoNetworks/terraform-provider-panos --json tagName -q '.tagName' 2>/dev/null || echo "v0.0.0") + else + echo "Error: cannot determine last tag. Provide --last-tag or --provider-dir." >&2 + exit 1 + fi +fi + +CURRENT_VERSION="${LAST_TAG#v}" + +# Determine the anchor date for codegen commits +if [ "$LAST_TAG" = "v0.0.0" ]; then + SINCE_FLAG="" +else + TAG_DATE="" + if [ -n "$PROVIDER_DIR" ] && [ -d "$PROVIDER_DIR/.git" ]; then + TAG_DATE=$(cd "$PROVIDER_DIR" && git log -1 --format="%aI" "$LAST_TAG" 2>/dev/null || echo "") + fi + if [ -z "$TAG_DATE" ] && command -v gh &>/dev/null; then + TAG_DATE=$(gh release view "$LAST_TAG" --repo PaloAltoNetworks/terraform-provider-panos --json publishedAt -q '.publishedAt' 2>/dev/null || echo "") + fi + if [ -n "$TAG_DATE" ]; then + SINCE_FLAG="--after=$TAG_DATE" + else + echo "Warning: could not determine tag date, scanning all commits" >&2 + SINCE_FLAG="" + fi +fi + +IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + +BUMP="none" + +while IFS= read -r line; do + [ -z "$line" ] && continue + + # Highest priority: feat(MAJOR) -> major bump + if echo "$line" | grep -qiE '^feat\(MAJOR\)'; then + BUMP="major" + break + fi + + # BREAKING CHANGE in commit body -> minor bump + if echo "$line" | grep -q 'BREAKING CHANGE'; then + if [ "$BUMP" != "major" ]; then + BUMP="minor" + fi + continue + fi + + # feat (but not feat(MAJOR)) -> patch bump + if echo "$line" | grep -qE '^feat(\(|:)' && ! echo "$line" | grep -qiE '^feat\(MAJOR\)'; then + if [ "$BUMP" = "none" ]; then + BUMP="patch" + fi + continue + fi + + # fix -> patch bump + if echo "$line" | grep -qE '^fix(\(|:)'; then + if [ "$BUMP" = "none" ]; then + BUMP="patch" + fi + continue + fi +done <<< "$(git log $SINCE_FLAG --format="%s%n%b" HEAD 2>/dev/null)" + +case $BUMP in + major) + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + none) + echo "NO_BUMP" + exit 0 + ;; +esac + +echo "v${MAJOR}.${MINOR}.${PATCH}" diff --git a/scripts/generate-release-notes.sh b/scripts/generate-release-notes.sh new file mode 100755 index 00000000..909b45c8 --- /dev/null +++ b/scripts/generate-release-notes.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Generates markdown release notes from conventional commits in pan-os-codegen. +# +# Usage: +# generate-release-notes.sh [since-date] +# +# If since-date is provided, only includes commits after that date. +# Otherwise includes all commits. + +set -euo pipefail + +VERSION="${1:?Usage: generate-release-notes.sh [since-date]}" +SINCE_DATE="${2:-}" + +LOG_ARGS="--format=%s" +if [ -n "$SINCE_DATE" ]; then + LOG_ARGS="$LOG_ARGS --after=$SINCE_DATE" +fi + +# Collect commits into arrays by type +FEATS=() +FIXES=() +BREAKING=() + +while IFS= read -r subject; do + [ -z "$subject" ] && continue + + # feat(MAJOR) -> breaking + if echo "$subject" | grep -qiE '^feat\(MAJOR\)'; then + msg=$(echo "$subject" | sed 's/^feat(MAJOR)[!]*: //') + BREAKING+=("- $msg") + continue + fi + + # feat -> feature + if echo "$subject" | grep -qE '^feat(\(|:)'; then + scope=$(echo "$subject" | sed -n 's/^feat(\([^)]*\))[!]*:.*/\1/p') + msg=$(echo "$subject" | sed 's/^feat([^)]*)[!]*: //' | sed 's/^feat[!]*: //') + if [ -n "$scope" ]; then + FEATS+=("- **$scope**: $msg") + else + FEATS+=("- $msg") + fi + continue + fi + + # fix -> bug fix + if echo "$subject" | grep -qE '^fix(\(|:)'; then + scope=$(echo "$subject" | sed -n 's/^fix(\([^)]*\))[!]*:.*/\1/p') + msg=$(echo "$subject" | sed 's/^fix([^)]*)[!]*: //' | sed 's/^fix[!]*: //') + if [ -n "$scope" ]; then + FIXES+=("- **$scope**: $msg") + else + FIXES+=("- $msg") + fi + continue + fi + + # Skip chore, docs, ci, test, refactor, style, build — internal commits +done < <(git log $LOG_ARGS HEAD 2>/dev/null) + +# Also scan commit bodies for BREAKING CHANGE +while IFS= read -r body_line; do + if echo "$body_line" | grep -q 'BREAKING CHANGE:'; then + msg=$(echo "$body_line" | sed 's/BREAKING CHANGE: //') + BREAKING+=("- $msg") + fi +done < <(git log ${SINCE_DATE:+--after=$SINCE_DATE} --format="%b" HEAD 2>/dev/null) + +# Output +echo "## What's Changed in ${VERSION}" +echo "" + +if [ ${#BREAKING[@]} -gt 0 ]; then + echo "### Breaking Changes" + echo "" + printf '%s\n' "${BREAKING[@]}" + echo "" +fi + +if [ ${#FEATS[@]} -gt 0 ]; then + echo "### Features" + echo "" + printf '%s\n' "${FEATS[@]}" + echo "" +fi + +if [ ${#FIXES[@]} -gt 0 ]; then + echo "### Bug Fixes" + echo "" + printf '%s\n' "${FIXES[@]}" + echo "" +fi + +if [ ${#BREAKING[@]} -eq 0 ] && [ ${#FEATS[@]} -eq 0 ] && [ ${#FIXES[@]} -eq 0 ]; then + echo "No notable changes." + echo "" +fi From ade3f614cd76f89bc6fe9539366131ab99088c3d Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:05:45 +0100 Subject: [PATCH 07/19] fix(ci): Pin all GitHub Actions to commit SHAs Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0424784..2d7048da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,7 +121,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 with: app-id: ${{ secrets.CODEGEN_APP_ID }} private-key: ${{ secrets.CODEGEN_PRIVATE_KEY }} @@ -137,7 +137,7 @@ jobs: path: pango - name: Download generated code - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: generated-code path: generated @@ -174,7 +174,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 with: app-id: ${{ secrets.CODEGEN_APP_ID }} private-key: ${{ secrets.CODEGEN_PRIVATE_KEY }} @@ -195,7 +195,7 @@ jobs: path: provider - name: Download generated code - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: generated-code path: generated From 5df7b0c129dc5fc224a884776869d46a92918078 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:47:18 +0100 Subject: [PATCH 08/19] feat(codegen): Add skip_subcategory support for specs without subcategory Specs can now set `skip_subcategory: true` to explicitly opt out of subcategory validation. This produces docs with an empty subcategory and records the resource in a .subcategory-skip file that CI uses to exclude them from validation. Missing subcategory without the flag is now an error. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 31 +++++++++++++++++++----- pkg/commands/codegen/codegen.go | 38 +++++++++++++++++++++++------- pkg/properties/normalized.go | 2 ++ pkg/schema/object/object.go | 1 + specs/actions/commit.yaml | 1 + specs/actions/push_to_devices.yaml | 1 + 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d7048da..86b64d75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,17 +86,28 @@ jobs: - name: Validate subcategories run: | - MISSING_VALUE=$(grep -rlE '^subcategory:\s*("")?\s*$' \ + SKIP_FILE="target/terraform/.subcategory-skip" + + # Find docs with empty or missing subcategory + MISSING=$(grep -rlE '^subcategory:\s*("")?\s*$' \ target/terraform/docs/resources/ \ target/terraform/docs/data-sources/ 2>/dev/null || true) MISSING_FIELD=$(find target/terraform/docs/resources target/terraform/docs/data-sources \ -name "*.md" ! -exec grep -q "^subcategory:" {} \; -print 2>/dev/null || true) - PROBLEMS="${MISSING_VALUE}${MISSING_FIELD}" - if [ -n "$PROBLEMS" ]; then + MISSING="${MISSING}${MISSING_FIELD}" + + # Filter out resources that explicitly opted out via skip_subcategory + if [ -f "$SKIP_FILE" ] && [ -n "$MISSING" ]; then + while IFS= read -r skip; do + MISSING=$(echo "$MISSING" | grep -v "/${skip}.md" || true) + done < "$SKIP_FILE" + fi + + if [ -n "$MISSING" ]; then echo "::error::Resources missing subcategory:" - echo "$PROBLEMS" + echo "$MISSING" exit 1 fi echo "All resources have valid subcategories" @@ -220,10 +231,18 @@ jobs: go generate ./... - name: Validate subcategories in provider - working-directory: provider run: | + SKIP_FILE="generated/terraform/.subcategory-skip" + MISSING=$(grep -rlE '^subcategory:\s*("")?\s*$' \ - docs/resources/ docs/data-sources/ 2>/dev/null || true) + provider/docs/resources/ provider/docs/data-sources/ 2>/dev/null || true) + + if [ -f "$SKIP_FILE" ] && [ -n "$MISSING" ]; then + while IFS= read -r skip; do + MISSING=$(echo "$MISSING" | grep -v "/${skip}.md" || true) + done < "$SKIP_FILE" + fi + if [ -n "$MISSING" ]; then echo "::error::Resources missing subcategory after doc generation: $MISSING" exit 1 diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index 91c6f93b..900e76ca 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -6,6 +6,7 @@ import ( "log/slog" "os" "path/filepath" + "sort" "strings" "github.com/paloaltonetworks/pan-os-codegen/pkg/generate" @@ -106,9 +107,6 @@ func generateTfplugindocsTemplates(outputDir string, specMetadata map[string]pro // Generate template for resources if metadata.Flags&properties.TerraformSpecResource != 0 { subcategory := metadata.Subcategory - if subcategory == "" { - subcategory = "Uncategorized" - } resourceTemplate := fmt.Sprintf(`--- page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" @@ -151,9 +149,6 @@ Import is supported using the following syntax: // Generate template for data sources if metadata.Flags&properties.TerraformSpecDatasource != 0 { subcategory := metadata.Subcategory - if subcategory == "" { - subcategory = "Uncategorized" - } dataSourceTemplate := fmt.Sprintf(`--- page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" @@ -187,6 +182,24 @@ description: |- } slog.Info("Generated tfplugindocs templates", "resources", resourceCount, "dataSources", dataSourceCount, "templatesDir", templatesDir) + + // Write skip-list of resources that intentionally have no subcategory + var skipList []string + for suffix, metadata := range specMetadata { + if metadata.Subcategory == "" { + templateName := strings.TrimPrefix(suffix, "_") + skipList = append(skipList, templateName) + } + } + if len(skipList) > 0 { + sort.Strings(skipList) + skipPath := filepath.Join(outputDir, ".subcategory-skip") + if err := os.WriteFile(skipPath, []byte(strings.Join(skipList, "\n")+"\n"), 0644); err != nil { + return fmt.Errorf("error writing subcategory skip list: %w", err) + } + slog.Info("Wrote subcategory skip list", "count", len(skipList), "path", skipPath) + } + return nil } @@ -240,10 +253,17 @@ func (c *Command) Execute() error { return fmt.Errorf("%s sanity failed: %s", specPath, err) } - // Extract subcategory: use YAML override if present, otherwise derive from path + // Extract subcategory: use YAML override if present, otherwise derive from path. + // If skip_subcategory is set, leave it empty intentionally. if c.commandType == properties.CommandTypeTerraform { - if spec.TerraformProviderConfig.Subcategory == "" { - spec.TerraformProviderConfig.Subcategory = deriveSubcategoryFromPath(specPath) + if spec.TerraformProviderConfig.SkipSubcategory { + spec.TerraformProviderConfig.Subcategory = "" + } else if spec.TerraformProviderConfig.Subcategory == "" { + subcategory := deriveSubcategoryFromPath(specPath) + if subcategory == "" { + return fmt.Errorf("%s: no subcategory found — set 'subcategory' in the spec or use 'skip_subcategory: true' if intentional", specPath) + } + spec.TerraformProviderConfig.Subcategory = subcategory } } diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 7d05c3fc..c10af2ba 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -82,6 +82,7 @@ const ( type TerraformProviderConfig struct { Description string `json:"description" yaml:"description"` Subcategory string `json:"subcategory" yaml:"subcategory"` + SkipSubcategory bool `json:"skip_subcategory" yaml:"skip_subcategory"` Ephemeral bool `json:"ephemeral" yaml:"ephemeral"` Action bool `json:"action" yaml:"action"` CustomValidation bool `json:"custom_validation" yaml:"custom_validation"` @@ -725,6 +726,7 @@ func schemaToSpec(object object.Object) (*Normalization, error) { SkipResource: object.TerraformConfig.SkipResource, SkipDatasource: object.TerraformConfig.SkipDatasource, SkipDatasourceListing: object.TerraformConfig.SkipdatasourceListing, + SkipSubcategory: object.TerraformConfig.SkipSubcategory, ResourceType: TerraformResourceType(object.TerraformConfig.ResourceType), XmlNode: object.TerraformConfig.XmlNode, CustomFuncs: object.TerraformConfig.CustomFunctions, diff --git a/pkg/schema/object/object.go b/pkg/schema/object/object.go index e544d49c..4951484c 100644 --- a/pkg/schema/object/object.go +++ b/pkg/schema/object/object.go @@ -43,6 +43,7 @@ type TerraformConfig struct { SkipResource bool `yaml:"skip_resource"` SkipDatasource bool `yaml:"skip_datasource"` SkipdatasourceListing bool `yaml:"skip_datasource_listing"` + SkipSubcategory bool `yaml:"skip_subcategory"` ResourceType TerraformResourceType `yaml:"resource_type"` XmlNode *string `yaml:"xml_node"` CustomFunctions map[string]bool `yaml:"custom_functions"` diff --git a/specs/actions/commit.yaml b/specs/actions/commit.yaml index 71022ba8..4a21e6fd 100644 --- a/specs/actions/commit.yaml +++ b/specs/actions/commit.yaml @@ -1,6 +1,7 @@ name: commit terraform_provider_config: description: Commit Action + skip_subcategory: true skip_resource: true skip_datasource: true resource_type: custom diff --git a/specs/actions/push_to_devices.yaml b/specs/actions/push_to_devices.yaml index dd09c8f2..bc7f7ec1 100644 --- a/specs/actions/push_to_devices.yaml +++ b/specs/actions/push_to_devices.yaml @@ -1,6 +1,7 @@ name: push_to_devices terraform_provider_config: description: Push to Devices Action + skip_subcategory: true skip_resource: true skip_datasource: true resource_type: custom From 6c8285933f55bc834c7006f480244feaf0555353 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:58:06 +0100 Subject: [PATCH 09/19] fix(examples): Fix undeclared resource reference in virtual_wire example The ethernet interface resources referenced panos_template.template but the template resource is named "tmpl". Co-Authored-By: Claude Opus 4.6 --- .../examples/resources/panos_virtual_wire/resource.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/terraform/examples/resources/panos_virtual_wire/resource.tf b/assets/terraform/examples/resources/panos_virtual_wire/resource.tf index 7e8bf4de..2df80e8d 100644 --- a/assets/terraform/examples/resources/panos_virtual_wire/resource.tf +++ b/assets/terraform/examples/resources/panos_virtual_wire/resource.tf @@ -4,13 +4,13 @@ resource "panos_template" "tmpl" { } resource "panos_ethernet_interface" "iface1" { - location = { template = { name = resource.panos_template.template.name, vsys = "vsys1" } } + location = { template = { name = panos_template.tmpl.name, vsys = "vsys1" } } name = var.interface1 virtual_wire = {} } resource "panos_ethernet_interface" "iface2" { - location = { template = { name = resource.panos_template.template.name, vsys = "vsys1" } } + location = { template = { name = panos_template.tmpl.name, vsys = "vsys1" } } name = var.interface2 virtual_wire = {} } From d54c9a1d9e4664b5631e7e38f1f17d18b626464c Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:30:17 +0100 Subject: [PATCH 10/19] fix(ci): Drop local pango replace directive before go get in CI The generated go.mod contains a replace directive pointing to ../pango for local development. In CI this path doesn't exist, so we need to drop it before fetching pango@main. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86b64d75..a8092a51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -223,6 +223,9 @@ jobs: - name: Update pango dependency and generate docs working-directory: provider run: | + # Remove local replace directive (used for local dev, not valid in CI) + go mod edit -dropreplace github.com/PaloAltoNetworks/pango + # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main go mod tidy From 3df5fc6cd44fd51ebaeb373b33a12476036c0ec1 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:33:54 +0100 Subject: [PATCH 11/19] fix(ci): Exclude go.mod/go.sum from rsync to avoid local replace directive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated go.mod has a local replace directive for pango that doesn't exist in CI. Instead of trying to drop it, exclude go.mod and go.sum from rsync entirely — the provider repo has its own, and go get/go mod tidy will reconcile dependencies. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8092a51..9c09a210 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -218,14 +218,12 @@ jobs: --exclude 'GNUmakefile' --exclude 'LICENSE' --exclude 'README.md' \ --exclude 'SUPPORT.md' --exclude 'terraform-registry-manifest.json' \ --exclude '.gitignore' --exclude 'scripts' \ + --exclude 'go.mod' --exclude 'go.sum' \ generated/terraform/ provider/ - name: Update pango dependency and generate docs working-directory: provider run: | - # Remove local replace directive (used for local dev, not valid in CI) - go mod edit -dropreplace github.com/PaloAltoNetworks/pango - # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main go mod tidy From 3323087764c32e68375c8a762f0edfb1e9a2ee78 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:36:27 +0100 Subject: [PATCH 12/19] fix(ci): Add Terraform setup for provider doc generation The go generate step in the provider PR job requires the terraform binary to generate plugin documentation. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c09a210..14b80006 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -198,6 +198,11 @@ jobs: with: go-version: "1.23" + - name: Set up Terraform + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3 + with: + terraform_wrapper: false + - name: Checkout provider uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: From 1a6b076ded4c65e8295d82056098188eea801c56 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:39:01 +0100 Subject: [PATCH 13/19] fix(ci): Include go.mod in rsync, strip replace directive with sed The generated go.mod includes tool dependencies (tfplugindocs) that the provider needs. Include it in rsync but strip the local replace directive (../pango) with sed before running go get. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14b80006..31c56a27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -223,12 +223,14 @@ jobs: --exclude 'GNUmakefile' --exclude 'LICENSE' --exclude 'README.md' \ --exclude 'SUPPORT.md' --exclude 'terraform-registry-manifest.json' \ --exclude '.gitignore' --exclude 'scripts' \ - --exclude 'go.mod' --exclude 'go.sum' \ generated/terraform/ provider/ - name: Update pango dependency and generate docs working-directory: provider run: | + # Remove local replace directive used for local dev (points to ../pango) + sed -i '/^replace.*pango.*=>.*\.\.\/pango/d' go.mod + # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main go mod tidy From 944655de9e38ab1a83fec026a8ac9dcc29b38e3b Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:46:04 +0100 Subject: [PATCH 14/19] fix(ci): Add tfplugindocs tool dependency for doc generation go mod tidy removes the tool dependency; explicitly fetch it before running go generate. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31c56a27..4d660962 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,6 +233,7 @@ jobs: # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main + go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs go mod tidy # Generate terraform plugin documentation From e63c296dc5be4b054aafd5b13facd95e6782ed02 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:48:23 +0100 Subject: [PATCH 15/19] fix(ci): Use go install for tfplugindocs to survive go mod tidy go mod tidy removes tool-only dependencies. Use go install to put the binary on PATH instead, so go generate can find it. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d660962..7d117ee3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,9 +233,11 @@ jobs: # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main - go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs go mod tidy + # Install tfplugindocs after tidy (tidy removes tool-only deps) + go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest + # Generate terraform plugin documentation go generate ./... From e5a9d1656ae5edda130d4487353a8f93146d3377 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:51:12 +0100 Subject: [PATCH 16/19] fix(ci): Run go generate before go mod tidy go generate uses go run for tfplugindocs which requires the module in go.mod. go mod tidy removes tool-only deps, so it must run after generate, not before. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d117ee3..398dc50f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,14 +233,13 @@ jobs: # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main - go mod tidy - - # Install tfplugindocs after tidy (tidy removes tool-only deps) - go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest - # Generate terraform plugin documentation + # Generate terraform plugin documentation (before tidy, which removes tool deps) go generate ./... + # Clean up after generation + go mod tidy + - name: Validate subcategories in provider run: | SKIP_FILE="generated/terraform/.subcategory-skip" From e292f07f8505ccb6b23e58dc38bca18ea1ede8f6 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:53:27 +0100 Subject: [PATCH 17/19] fix(ci): Add tfplugindocs dependency before go generate The generated go.mod doesn't include terraform-plugin-docs (it's only in go:generate directives). Explicitly go get it before running go generate. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 398dc50f..0f66c119 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -234,7 +234,10 @@ jobs: # Fetch the latest pango from main (just pushed in previous job) go get github.com/PaloAltoNetworks/pango@main - # Generate terraform plugin documentation (before tidy, which removes tool deps) + # Add tfplugindocs tool dependency (not in generated go.mod, needed by go:generate) + go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + + # Generate terraform plugin documentation go generate ./... # Clean up after generation From aab0bb327d1c6164298e4bdb41b39c7af4433174 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:14:17 +0100 Subject: [PATCH 18/19] fix(ci): Reuse existing PR instead of bumping version on re-runs When an unmerged auto-release PR exists, the workflow now reuses its version and force-pushes updated code to the same branch instead of creating a duplicate PR with a new version. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 67 ++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f66c119..31ebac5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,7 @@ jobs: version: ${{ steps.version.outputs.version }} last_tag: ${{ steps.version.outputs.last_tag }} since_date: ${{ steps.version.outputs.since_date }} + existing_pr: ${{ steps.version.outputs.existing_pr }} steps: - name: Checkout pan-os-codegen uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -58,9 +59,22 @@ jobs: LAST_TAG=$(gh release view --repo PaloAltoNetworks/terraform-provider-panos --json tagName -q '.tagName' 2>/dev/null || echo "v0.0.0") SINCE_DATE=$(gh release view "$LAST_TAG" --repo PaloAltoNetworks/terraform-provider-panos --json publishedAt -q '.publishedAt' 2>/dev/null || echo "") + # Check for existing open auto-release PR (skip if version is manually overridden) + EXISTING_PR="" + if [ -z "${{ inputs.version_override }}" ]; then + EXISTING_PR=$(gh pr list --repo PaloAltoNetworks/terraform-provider-panos \ + --state open --json number,headRefName \ + --jq '[.[] | select(.headRefName | startswith("auto-release/"))][0].number // empty' 2>/dev/null || echo "") + if [ -n "$EXISTING_PR" ]; then + EXISTING_BRANCH=$(gh pr view "$EXISTING_PR" --repo PaloAltoNetworks/terraform-provider-panos --json headRefName -q '.headRefName') + VERSION="${EXISTING_BRANCH#auto-release/}" + echo "::notice::Found existing unmerged PR #${EXISTING_PR} for ${VERSION} - reusing version" + fi + fi + if [ -n "${{ inputs.version_override }}" ]; then VERSION="${{ inputs.version_override }}" - else + elif [ -z "$EXISTING_PR" ]; then VERSION=$(bash scripts/determine-version.sh --last-tag "$LAST_TAG") if [ "$VERSION" = "NO_BUMP" ]; then echo "::error::No version-bumping commits found since $LAST_TAG" @@ -71,15 +85,20 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT echo "since_date=$SINCE_DATE" >> $GITHUB_OUTPUT + echo "existing_pr=$EXISTING_PR" >> $GITHUB_OUTPUT echo "## Version" >> $GITHUB_STEP_SUMMARY echo "- Current: $LAST_TAG" >> $GITHUB_STEP_SUMMARY echo "- Next: $VERSION" >> $GITHUB_STEP_SUMMARY + if [ -n "$EXISTING_PR" ]; then + echo "- Reusing existing PR: #$EXISTING_PR" >> $GITHUB_STEP_SUMMARY + fi - name: Generate release notes run: | bash scripts/generate-release-notes.sh \ "${{ steps.version.outputs.version }}" \ - "${{ steps.version.outputs.since_date }}" \ + --since-tag "${{ steps.version.outputs.last_tag }}" \ + --since-date "${{ steps.version.outputs.since_date }}" \ > target/release-notes.md echo "## Release Notes" >> $GITHUB_STEP_SUMMARY cat target/release-notes.md >> $GITHUB_STEP_SUMMARY @@ -261,11 +280,12 @@ jobs: exit 1 fi - - name: Create PR + - name: Create or update PR id: create-pr env: GH_TOKEN: ${{ steps.app-token.outputs.token }} VERSION: ${{ needs.generate-and-test.outputs.version }} + EXISTING_PR: ${{ needs.generate-and-test.outputs.existing_pr }} working-directory: provider run: | BRANCH="auto-release/${VERSION}" @@ -276,19 +296,23 @@ jobs: git add . if git diff --staged --quiet; then + if [ -n "$EXISTING_PR" ]; then + echo "::notice::No new changes; existing PR #${EXISTING_PR} is up to date" + PR_URL=$(gh pr view "$EXISTING_PR" --repo PaloAltoNetworks/terraform-provider-panos --json url -q '.url') + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + echo "## Provider PR" >> $GITHUB_STEP_SUMMARY + echo "Up to date: $PR_URL" >> $GITHUB_STEP_SUMMARY + exit 0 + fi echo "::error::No changes to commit in provider" exit 1 fi git commit -m "chore(release): auto-generated ${VERSION}" - git push -u origin "$BRANCH" RELEASE_NOTES=$(cat ../generated/release-notes.md) - PR_URL=$(gh pr create \ - --repo PaloAltoNetworks/terraform-provider-panos \ - --title "chore(release): ${VERSION}" \ - --body "$(cat < PREOF - )") + ) + + if [ -n "$EXISTING_PR" ]; then + # Force push to update existing branch and PR body + git push --force origin "$BRANCH" + gh pr edit "$EXISTING_PR" \ + --repo PaloAltoNetworks/terraform-provider-panos \ + --body "$PR_BODY" + PR_URL=$(gh pr view "$EXISTING_PR" --repo PaloAltoNetworks/terraform-provider-panos --json url -q '.url') + echo "::notice::Updated existing PR: $PR_URL" + else + git push -u origin "$BRANCH" + PR_URL=$(gh pr create \ + --repo PaloAltoNetworks/terraform-provider-panos \ + --title "chore(release): ${VERSION}" \ + --body "$PR_BODY") + fi echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT echo "## Provider PR" >> $GITHUB_STEP_SUMMARY - echo "Created: $PR_URL" >> $GITHUB_STEP_SUMMARY + echo "${EXISTING_PR:+Updated}${EXISTING_PR:-Created}: $PR_URL" >> $GITHUB_STEP_SUMMARY tag-codegen: name: Tag Codegen @@ -324,6 +364,7 @@ jobs: env: VERSION: ${{ needs.generate-and-test.outputs.version }} run: | - git tag "release/${VERSION}" - git push origin "release/${VERSION}" - echo "Tagged pan-os-codegen with release/${VERSION}" + TAG="release/${VERSION}" + git tag -f "$TAG" + git push origin "$TAG" --force + echo "Tagged pan-os-codegen with $TAG" From c3742d6da45236a527a121960c6226858179b5f2 Mon Sep 17 00:00:00 2001 From: Migara Ekanayake <2110772+migara@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:21:44 +0100 Subject: [PATCH 19/19] fix(examples): Replace undeclared variables with literals in virtual_wire example Co-Authored-By: Claude Opus 4.6 --- .../examples/resources/panos_virtual_wire/resource.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/terraform/examples/resources/panos_virtual_wire/resource.tf b/assets/terraform/examples/resources/panos_virtual_wire/resource.tf index 2df80e8d..af5bc270 100644 --- a/assets/terraform/examples/resources/panos_virtual_wire/resource.tf +++ b/assets/terraform/examples/resources/panos_virtual_wire/resource.tf @@ -5,13 +5,13 @@ resource "panos_template" "tmpl" { resource "panos_ethernet_interface" "iface1" { location = { template = { name = panos_template.tmpl.name, vsys = "vsys1" } } - name = var.interface1 + name = "ethernet1/1" virtual_wire = {} } resource "panos_ethernet_interface" "iface2" { location = { template = { name = panos_template.tmpl.name, vsys = "vsys1" } } - name = var.interface2 + name = "ethernet1/2" virtual_wire = {} }