Skip to content
Closed
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
19 changes: 19 additions & 0 deletions pkg/workflow/action_pins.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ func extractActionVersion(uses string) string {

// ApplyActionPinsToSteps applies SHA pinning to a slice of step maps
// Returns a new slice with pinned references
// Deprecated: Use ApplyActionPinsToWorkflowSteps for better type safety
func ApplyActionPinsToSteps(steps []any, data *WorkflowData) []any {
result := make([]any, len(steps))
for i, step := range steps {
Expand All @@ -329,6 +330,24 @@ func ApplyActionPinsToSteps(steps []any, data *WorkflowData) []any {
return result
}

// ApplyActionPinsToWorkflowSteps applies SHA pinning to a slice of WorkflowStep
// Returns a new slice with pinned references
func ApplyActionPinsToWorkflowSteps(steps []WorkflowStep, data *WorkflowData) []WorkflowStep {
result := make([]WorkflowStep, len(steps))
for i, step := range steps {
stepMap := step.ToMap()
pinnedMap := ApplyActionPinToStep(stepMap, data)
pinnedStep, err := MapToStep(pinnedMap)
if err != nil {
// If conversion fails, keep the original step
result[i] = step
} else {
result[i] = *pinnedStep
}
}
return result
}

// GetActionPinByRepo returns the ActionPin for a given repository, if it exists
// When multiple versions exist for the same repo, it returns the latest version by semver
func GetActionPinByRepo(repo string) (ActionPin, bool) {
Expand Down
52 changes: 52 additions & 0 deletions pkg/workflow/action_pins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,3 +719,55 @@ func TestGetActionPinWithData_SemverPreference(t *testing.T) {
})
}
}

// TestApplyActionPinsToWorkflowSteps tests the ApplyActionPinsToWorkflowSteps function
func TestApplyActionPinsToWorkflowSteps(t *testing.T) {
steps := []WorkflowStep{
{
Name: "Checkout code",
Uses: "actions/checkout@v4",
},
{
Name: "Setup Node",
Uses: "actions/setup-node@v4",
},
{
Name: "Run script",
Run: "npm test",
},
}

data := &WorkflowData{}
result := ApplyActionPinsToWorkflowSteps(steps, data)

// Verify result has same length
if len(result) != len(steps) {
t.Errorf("ApplyActionPinsToWorkflowSteps() returned %d steps, want %d", len(result), len(steps))
}

// Verify first step is pinned
if !strings.Contains(result[0].Uses, "@") {
t.Errorf("First step not pinned: %s", result[0].Uses)
}
if !strings.HasPrefix(result[0].Uses, "actions/checkout@") {
t.Errorf("First step action changed: %s", result[0].Uses)
}

// Verify second step is pinned
if !strings.Contains(result[1].Uses, "@") {
t.Errorf("Second step not pinned: %s", result[1].Uses)
}
if !strings.HasPrefix(result[1].Uses, "actions/setup-node@") {
t.Errorf("Second step action changed: %s", result[1].Uses)
}

// Verify third step (run step) is unchanged
if result[2].Run != "npm test" {
t.Errorf("Third step run changed: %s", result[2].Run)
}

// Verify original steps are not modified
if steps[0].Uses != "actions/checkout@v4" {
t.Errorf("Original step was modified: %s", steps[0].Uses)
}
}
10 changes: 10 additions & 0 deletions pkg/workflow/runtime_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ func detectSerenaLanguages(serenaConfig *SerenaToolConfig, requirements map[stri
}

// detectFromEngineSteps scans engine steps for runtime commands
// Deprecated: Use detectFromWorkflowSteps for better type safety
func detectFromEngineSteps(steps []map[string]any, requirements map[string]*RuntimeRequirement) {
for _, step := range steps {
if run, hasRun := step["run"]; hasRun {
Expand All @@ -407,6 +408,15 @@ func detectFromEngineSteps(steps []map[string]any, requirements map[string]*Runt
}
}

// detectFromWorkflowSteps scans WorkflowStep slice for runtime commands
func detectFromWorkflowSteps(steps []WorkflowStep, requirements map[string]*RuntimeRequirement) {
for _, step := range steps {
if step.Run != "" {
detectRuntimeFromCommand(step.Run, requirements)
}
}
}

// filterExistingSetupActions removes runtimes from requirements if they already have setup actions in the custom steps
// updateRequiredRuntime updates the version requirement, choosing the highest version
func updateRequiredRuntime(runtime *Runtime, newVersion string, requirements map[string]*RuntimeRequirement) {
Expand Down
88 changes: 88 additions & 0 deletions pkg/workflow/runtime_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,91 @@ func TestDeduplicateErrorMessageFormat(t *testing.T) {
len(errMsg), errMsg)
}
}

// TestDetectFromWorkflowSteps tests the detectFromWorkflowSteps function
func TestDetectFromWorkflowSteps(t *testing.T) {
tests := []struct {
name string
steps []WorkflowStep
expected []string // Expected runtime IDs to be detected
}{
{
name: "detect node from npm command",
steps: []WorkflowStep{
{Name: "Install", Run: "npm install"},
{Name: "Test", Run: "npm test"},
},
expected: []string{"node"},
},
{
name: "detect python from pip command",
steps: []WorkflowStep{
{Name: "Install", Run: "pip install -r requirements.txt"},
},
expected: []string{"python"},
},
{
name: "detect go from go command",
steps: []WorkflowStep{
{Name: "Build", Run: "go build"},
},
expected: []string{"go"},
},
{
name: "detect multiple runtimes",
steps: []WorkflowStep{
{Name: "Node", Run: "npm install"},
{Name: "Python", Run: "python script.py"},
},
expected: []string{"node", "python"},
},
{
name: "no runtimes detected",
steps: []WorkflowStep{
{Name: "Echo", Run: "echo hello"},
{Name: "Checkout", Uses: "actions/checkout@v4"},
},
expected: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
requirements := make(map[string]*RuntimeRequirement)
detectFromWorkflowSteps(tt.steps, requirements)

// Check that expected runtimes were detected
for _, expectedID := range tt.expected {
if _, found := requirements[expectedID]; !found {
t.Errorf("Expected runtime %s to be detected, but it wasn't", expectedID)
}
}

// Check that no unexpected runtimes were detected
if len(tt.expected) != len(requirements) {
var detected []string
for id := range requirements {
detected = append(detected, id)
}
t.Errorf("Expected %d runtimes %v, got %d runtimes %v",
len(tt.expected), tt.expected, len(requirements), detected)
}
})
}
}

// TestDetectFromEngineSteps_BackwardCompatibility tests that the old function still works
func TestDetectFromEngineSteps_BackwardCompatibility(t *testing.T) {
steps := []map[string]any{
{"name": "Install", "run": "npm install"},
{"name": "Test", "run": "npm test"},
}

requirements := make(map[string]*RuntimeRequirement)
detectFromEngineSteps(steps, requirements)

// Check that node was detected
if _, found := requirements["node"]; !found {
t.Error("Expected node runtime to be detected")
}
}
Loading
Loading