Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/patch-consolidate-validation-functions.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions .github/instructions/github-agentic-workflows.instructions.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions pkg/workflow/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,6 @@ func generateDefaultCacheKey(cacheID string) string {
return fmt.Sprintf("memory-%s-${{ github.workflow }}-${{ github.run_id }}", cacheID)
}

// validateNoDuplicateCacheIDs checks for duplicate cache IDs and returns an error if found
func validateNoDuplicateCacheIDs(caches []CacheMemoryEntry) error {
seen := make(map[string]bool)
for _, cache := range caches {
if seen[cache.ID] {
return fmt.Errorf("duplicate cache-memory ID '%s' found. Each cache must have a unique ID", cache.ID)
}
seen[cache.ID] = true
}
return nil
}

// extractCacheMemoryConfig extracts cache-memory configuration from tools section
func (c *Compiler) extractCacheMemoryConfig(tools map[string]any) (*CacheMemoryConfig, error) {
cacheMemoryValue, exists := tools["cache-memory"]
Expand Down
47 changes: 0 additions & 47 deletions pkg/workflow/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/githubnext/gh-aw/pkg/parser"
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
"github.com/goccy/go-yaml"
"github.com/santhosh-tekuri/jsonschema/v6"
)

var log = logger.New("workflow:compiler")
Expand Down Expand Up @@ -421,52 +420,6 @@ func (c *Compiler) CompileWorkflow(markdownPath string) error {
return nil
}

// validateGitHubActionsSchema validates the generated YAML content against the GitHub Actions workflow schema
func (c *Compiler) validateGitHubActionsSchema(yamlContent string) error {
// Convert YAML to any for JSON conversion
var workflowData any
if err := yaml.Unmarshal([]byte(yamlContent), &workflowData); err != nil {
return fmt.Errorf("failed to parse YAML for schema validation: %w", err)
}

// Convert to JSON for schema validation
jsonData, err := json.Marshal(workflowData)
if err != nil {
return fmt.Errorf("failed to convert YAML to JSON for validation: %w", err)
}

// Parse the embedded schema
var schemaDoc any
if err := json.Unmarshal([]byte(githubWorkflowSchema), &schemaDoc); err != nil {
return fmt.Errorf("failed to parse embedded GitHub Actions schema: %w", err)
}

// Create compiler and add the schema as a resource
loader := jsonschema.NewCompiler()
schemaURL := "https://json.schemastore.org/github-workflow.json"
if err := loader.AddResource(schemaURL, schemaDoc); err != nil {
return fmt.Errorf("failed to add schema resource: %w", err)
}

// Compile the schema
schema, err := loader.Compile(schemaURL)
if err != nil {
return fmt.Errorf("failed to compile GitHub Actions schema: %w", err)
}

// Validate the JSON data against the schema
var jsonObj any
if err := json.Unmarshal(jsonData, &jsonObj); err != nil {
return fmt.Errorf("failed to unmarshal JSON for validation: %w", err)
}

if err := schema.Validate(jsonObj); err != nil {
return fmt.Errorf("GitHub Actions schema validation failed: %w", err)
}

return nil
}

// ParseWorkflowFile parses a markdown workflow file and extracts all necessary data
func (c *Compiler) ParseWorkflowFile(markdownPath string) (*WorkflowData, error) {
log.Printf("Reading file: %s", markdownPath)
Expand Down
14 changes: 0 additions & 14 deletions pkg/workflow/redact_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,3 @@ func (c *Compiler) generateSecretRedactionStep(yaml *strings.Builder, yamlConten
yaml.WriteString(fmt.Sprintf(" SECRET_%s: ${{ secrets.%s }}\n", escapedSecretName, secretName))
}
}

// validateSecretReferences validates that secret references are valid
func validateSecretReferences(secrets []string) error {
// Secret names must be valid environment variable names
secretNamePattern := regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`)

for _, secret := range secrets {
if !secretNamePattern.MatchString(secret) {
return fmt.Errorf("invalid secret name: %s", secret)
}
}

return nil
}
76 changes: 76 additions & 0 deletions pkg/workflow/validation.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package workflow

import (
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
"strings"

"github.com/githubnext/gh-aw/pkg/console"
"github.com/githubnext/gh-aw/pkg/logger"
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
"github.com/goccy/go-yaml"
"github.com/santhosh-tekuri/jsonschema/v6"
)

var validationLog = logger.New("workflow:validation")
Expand Down Expand Up @@ -194,3 +198,75 @@ func collectPackagesFromWorkflow(

return packages
}

// validateGitHubActionsSchema validates the generated YAML content against the GitHub Actions workflow schema
func (c *Compiler) validateGitHubActionsSchema(yamlContent string) error {
// Convert YAML to any for JSON conversion
var workflowData any
if err := yaml.Unmarshal([]byte(yamlContent), &workflowData); err != nil {
return fmt.Errorf("failed to parse YAML for schema validation: %w", err)
}

// Convert to JSON for schema validation
jsonData, err := json.Marshal(workflowData)
if err != nil {
return fmt.Errorf("failed to convert YAML to JSON for validation: %w", err)
}

// Parse the embedded schema
var schemaDoc any
if err := json.Unmarshal([]byte(githubWorkflowSchema), &schemaDoc); err != nil {
return fmt.Errorf("failed to parse embedded GitHub Actions schema: %w", err)
}

// Create compiler and add the schema as a resource
loader := jsonschema.NewCompiler()
schemaURL := "https://json.schemastore.org/github-workflow.json"
if err := loader.AddResource(schemaURL, schemaDoc); err != nil {
return fmt.Errorf("failed to add schema resource: %w", err)
}

// Compile the schema
schema, err := loader.Compile(schemaURL)
if err != nil {
return fmt.Errorf("failed to compile GitHub Actions schema: %w", err)
}

// Validate the JSON data against the schema
var jsonObj any
if err := json.Unmarshal(jsonData, &jsonObj); err != nil {
return fmt.Errorf("failed to unmarshal JSON for validation: %w", err)
}

if err := schema.Validate(jsonObj); err != nil {
return fmt.Errorf("GitHub Actions schema validation failed: %w", err)
}

return nil
}

// validateNoDuplicateCacheIDs checks for duplicate cache IDs and returns an error if found
func validateNoDuplicateCacheIDs(caches []CacheMemoryEntry) error {
seen := make(map[string]bool)
for _, cache := range caches {
if seen[cache.ID] {
return fmt.Errorf("duplicate cache-memory ID '%s' found. Each cache must have a unique ID", cache.ID)
}
seen[cache.ID] = true
}
return nil
}

// validateSecretReferences validates that secret references are valid
func validateSecretReferences(secrets []string) error {
// Secret names must be valid environment variable names
secretNamePattern := regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`)

for _, secret := range secrets {
if !secretNamePattern.MatchString(secret) {
return fmt.Errorf("invalid secret name: %s", secret)
}
}

return nil
}
Loading