Coding conventions for Firewall GitOps project following YAGNI-KISS-DRY principles.
- Two-space indentation
- Run
terraform fmt -recursivebefore commit
- Snake_case for variables/resources/locals/outputs
- Descriptive resource names:
address_objects,service_objects
# Use lookup() for optional YAML fields
ip_netmask = lookup(each.value, "ip_netmask", null)
description = lookup(each.value, "description", null)
tags = lookup(each.value, "tags", [])# Explicit depends_on when needed
resource "panos_security_policy_rules" "rules" {
depends_on = [panos_addresses.address_objects, panos_service.service_objects]
}- Prefer
for_eachovercountfor resources - Use
countfor conditional creation
- Two-space indentation (no tabs)
- Quote strings with special chars:
'192.168.1.100/32' - Single quotes preferred
- Lowercase with hyphens:
web-server-01,db-primary-prod - Descriptive rule names:
allow-web-traffic,deny-external-ssh
Single file: objects.yaml (addresses → services → rules)
Multi-file: objects/*.yaml (descriptive names: trust-zone.yaml, dmz-zone.yaml)
python scripts/validate_yaml.py- PEP 8: 4-space indentation, 88-char line length
- Snake_case functions/variables, PascalCase classes
def validate_cluster(cluster_path: Path) -> Dict[str, bool]:
"""Validate cluster configuration."""
# ...try:
with open(yaml_file, 'r') as f:
data = yaml.safe_load(f)
except FileNotFoundError:
print(f"Error: File not found: {yaml_file}")
sys.exit(1)if __name__ == "__main__":
main()- Follow standard Go formatting:
go fmt ./... - Use
golangci-lintfor linting - Maximum line length: 120 characters
- Use meaningful variable names, avoid abbreviations
project/
├── cmd/
│ └── servicename/ # Main application entry point
│ └── main.go
├── internal/ # Private application code
│ ├── config/ # Configuration management
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ └── repository/ # Data access
├── pkg/ # Public library code (if any)
└── go.mod
// Always handle errors explicitly
cfg, err := config.Load()
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
// Wrap errors with context
return fmt.Errorf("failed to process webhook: %w", err)// Use structured logging with slog
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
// Log with structured data
slog.Info("processing webhook",
"event_id", eventID,
"source_ip", sourceIP,
"event_type", eventType)// Table-driven tests for multiple scenarios
func TestConfigLoad(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
want *Config
wantErr bool
}{
{
name: "success with all env vars",
envVars: map[string]string{
"GITLAB_URL": "https://gitlab.example.com",
// ...
},
want: &Config{
GitLabURL: "https://gitlab.example.com",
// ...
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}// Use getEnv helper with defaults
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
// Validate required configuration
func (c *Config) Validate() error {
if c.GitLabURL == "" {
return fmt.Errorf("missing required env var: GITLAB_URL")
}
return nil
}// Always accept context in HTTP handlers and service methods
func (s *Service) ProcessWebhook(ctx context.Context, event WebhookEvent) error {
// Use context for cancellation and timeouts
select {
case <-ctx.Done():
return ctx.Err()
default:
// Process event
}
}#!/usr/bin/env bash
set -euo pipefail- UPPERCASE for env vars:
GITLAB_TOKEN - lowercase for local:
cluster_name - Always quote:
"${variable}"
setup_backend() {
local cluster_name="${1}"
# ...
}Format: <type>(<scope>): <description>
Types:
feat:- New featurefix:- Bug fixrefactor:- Code refactoringdocs:- Documentationchore:- Maintenancetest:- Tests
Examples:
feat(palo-alto): add security profile support
fix(checkpoint): correct host/network classification
docs: update README with multi-file config
- Max 50 chars for subject
- Imperative mood: "add" not "added"
- Lowercase after colon
- One logical change per commit
main.tf,variables.tf,outputs.tf- Module-specific: lowercase with hyphens
cluster.yaml(required exact name)objects.yamlORobjects/*.yaml- Multi-file: descriptive names (
addresses.yaml,trust-zone.yaml)
- Lowercase with extension:
deploy.sh,validate_yaml.py
- Lowercase with hyphens:
project-overview-pdr.md,code-standards.md
- API keys, passwords, tokens, certificates
export PANOS_PASSWORD="secret"
export GITLAB_TOKEN="token"def validate_ip_address(ip: str) -> bool:
pattern = r'^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$'
return bool(re.match(pattern, ip))- Default deny, explicit allow
- Specific zones/addresses (avoid "any")
- Log denied traffic
- validate - YAML + Terraform checks
- plan - Generate plans
- apply - Deploy changes
- cleanup - Remove artifacts
Pattern: <action>_<cluster>
Examples: validate_yaml, plan_production, apply_development
artifacts:
paths:
- plan-${CLUSTER_NAME}.tfplan
expire_in: 1 weekresource_group: terraform-${CLUSTER_NAME}- YAML validation passes
- Terraform fmt/validate passes
- Plan reviewed locally
- No secrets committed
- Commit messages follow conventions
- Changes match description
- Plan output reviewed
- No security risks
- Variable names follow conventions
- Documentation updated
vsys = "vsys1" # Bad - should come from config
vsys = try(var.location.vsys.vsys_name, null) # Good# Bad
action: allow
source_zones: [any]
services: [any]
# Good
action: allow
source_zones: [trust]
services: [https-service]# Bad
data = yaml.safe_load(open(file))
# Good
try:
with open(file, 'r') as f:
data = yaml.safe_load(f)
except FileNotFoundError:
print(f"Error: {file} not found")
sys.exit(1)- HashiCorp Terraform
- YAML
- Python
- ShellCheck
{
"editor.formatOnSave": true,
"terraform.format.enable": true,
"yaml.schemas": {
"schemas/cluster.schema.json": "clusters/*/cluster.yaml",
"schemas/rules.schema.json": ["clusters/*/objects.yaml", "clusters/*/objects/*.yaml"]
}
}- Terraform Style: https://developer.hashicorp.com/terraform/language/style
- Python PEP 8: https://peps.python.org/pep-0008/
- Conventional Commits: https://www.conventionalcommits.org/