Skip to content
Draft
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
4 changes: 4 additions & 0 deletions api/runners/runners.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ func RegisterRunner(w http.ResponseWriter, r *http.Request) {
Webhook: register.Webhook,
MaxParallelTasks: register.MaxParallelTasks,
PublicKey: register.PublicKey,
Name: register.Name,
Active: register.Active != nil && *register.Active, // Default to false if not specified
Tag: register.Tag,
// ProjectID will be nil for global runners, can be enhanced later for project name lookup
})

if err != nil {
Expand Down
50 changes: 50 additions & 0 deletions api/runners/runners_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package runners

import (
"testing"

"github.com/semaphoreui/semaphore/services/runners"
)

func TestRunnerRegistrationStructure(t *testing.T) {
// Test that the RunnerRegistration struct has the new fields
testActive := true
registration := runners.RunnerRegistration{
RegistrationToken: "test_token_123",
Webhook: "https://example.com/webhook",
MaxParallelTasks: 2,
Name: "test-runner-name",
Active: &testActive,
ProjectName: "test-project",
Tag: "linux,docker,test",
}

// Verify all fields are set correctly
if registration.RegistrationToken != "test_token_123" {
t.Errorf("Expected RegistrationToken 'test_token_123', got '%s'", registration.RegistrationToken)
}

if registration.Webhook != "https://example.com/webhook" {
t.Errorf("Expected Webhook 'https://example.com/webhook', got '%s'", registration.Webhook)
}

if registration.MaxParallelTasks != 2 {
t.Errorf("Expected MaxParallelTasks 2, got %d", registration.MaxParallelTasks)
}

if registration.Name != "test-runner-name" {
t.Errorf("Expected Name 'test-runner-name', got '%s'", registration.Name)
}

if registration.Active == nil || !*registration.Active {
t.Error("Expected Active to be true")
}

if registration.ProjectName != "test-project" {
t.Errorf("Expected ProjectName 'test-project', got '%s'", registration.ProjectName)
}

if registration.Tag != "linux,docker,test" {
t.Errorf("Expected Tag 'linux,docker,test', got '%s'", registration.Tag)
}
}
47 changes: 47 additions & 0 deletions cli/cmd/runner_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,22 @@ import (

var runnerRegisterArgs struct {
stdinRegistrationToken bool
hostname string
enabled bool
disabled bool
projectName string
webhook string
tags string
}

func init() {
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.stdinRegistrationToken, "stdin-registration-token", false, "Read registration token from stdin")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.hostname, "hostname", "", "Runner hostname or name")
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.enabled, "enabled", false, "Enable runner immediately (default true)")
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.disabled, "disabled", false, "Disable runner on registration")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.projectName, "project-name", "", "Associate runner with specific project")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.webhook, "webhook", "", "Webhook URL for the runner")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.tags, "tags", "", "Comma-separated list of tags for the runner")
runnerCmd.AddCommand(runnerRegisterCmd)
}

Expand All @@ -41,6 +53,41 @@ func registerRunner() {

initRunnerRegistrationToken()

// Ensure Runner config is initialized
if util.Config.Runner == nil {
util.Config.Runner = &util.RunnerConfig{}
}

// Apply CLI flags to override config values
if runnerRegisterArgs.hostname != "" {
util.Config.Runner.Name = runnerRegisterArgs.hostname
}

// Handle enabled/disabled flags with proper precedence
if runnerRegisterArgs.disabled {
enabled := false
util.Config.Runner.Active = &enabled
} else if runnerRegisterArgs.enabled {
enabled := true
util.Config.Runner.Active = &enabled
} else if util.Config.Runner.Active == nil {
// Default to enabled if not specified
enabled := true
util.Config.Runner.Active = &enabled
}

if runnerRegisterArgs.projectName != "" {
util.Config.Runner.ProjectName = runnerRegisterArgs.projectName
}

if runnerRegisterArgs.webhook != "" {
util.Config.Runner.Webhook = runnerRegisterArgs.webhook
}

if runnerRegisterArgs.tags != "" {
util.Config.Runner.Tag = runnerRegisterArgs.tags
}

taskPool := createRunnerJobPool()

err := taskPool.Register(configFile)
Expand Down
96 changes: 96 additions & 0 deletions cli/cmd/runner_register_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cmd

import (
"testing"

"github.com/semaphoreui/semaphore/util"
)

func TestRunnerRegisterArgsProcessing(t *testing.T) {
// Reset config to defaults
util.Config = &util.ConfigType{
Runner: &util.RunnerConfig{},
}

// Test CLI flag processing
runnerRegisterArgs.hostname = "test-runner"
runnerRegisterArgs.enabled = true
runnerRegisterArgs.disabled = false
runnerRegisterArgs.projectName = "test-project"
runnerRegisterArgs.webhook = "https://example.com/webhook"
runnerRegisterArgs.tags = "linux,test,docker"

// Simulate the flag processing logic from registerRunner function
if runnerRegisterArgs.hostname != "" {
util.Config.Runner.Name = runnerRegisterArgs.hostname
}

if runnerRegisterArgs.disabled {
enabled := false
util.Config.Runner.Active = &enabled
} else if runnerRegisterArgs.enabled {
enabled := true
util.Config.Runner.Active = &enabled
} else if util.Config.Runner.Active == nil {
enabled := true
util.Config.Runner.Active = &enabled
}

if runnerRegisterArgs.projectName != "" {
util.Config.Runner.ProjectName = runnerRegisterArgs.projectName
}

if runnerRegisterArgs.webhook != "" {
util.Config.Runner.Webhook = runnerRegisterArgs.webhook
}

if runnerRegisterArgs.tags != "" {
util.Config.Runner.Tag = runnerRegisterArgs.tags
}

// Validate the config was set correctly
if util.Config.Runner.Name != "test-runner" {
t.Errorf("Expected Name to be 'test-runner', got '%s'", util.Config.Runner.Name)
}

if util.Config.Runner.Active == nil || !*util.Config.Runner.Active {
t.Errorf("Expected Active to be true")
}

if util.Config.Runner.ProjectName != "test-project" {
t.Errorf("Expected ProjectName to be 'test-project', got '%s'", util.Config.Runner.ProjectName)
}

if util.Config.Runner.Webhook != "https://example.com/webhook" {
t.Errorf("Expected Webhook to be 'https://example.com/webhook', got '%s'", util.Config.Runner.Webhook)
}

if util.Config.Runner.Tag != "linux,test,docker" {
t.Errorf("Expected Tag to be 'linux,test,docker', got '%s'", util.Config.Runner.Tag)
}
}

func TestRunnerRegisterArgsDisabled(t *testing.T) {
// Reset config to defaults
util.Config = &util.ConfigType{
Runner: &util.RunnerConfig{},
}

// Test disabled flag takes precedence
runnerRegisterArgs.enabled = true
runnerRegisterArgs.disabled = true

// Simulate the flag processing logic from registerRunner function
if runnerRegisterArgs.disabled {
enabled := false
util.Config.Runner.Active = &enabled
} else if runnerRegisterArgs.enabled {
enabled := true
util.Config.Runner.Active = &enabled
}

// Validate disabled takes precedence
if util.Config.Runner.Active == nil || *util.Config.Runner.Active {
t.Errorf("Expected Active to be false when disabled flag is set")
}
}
7 changes: 7 additions & 0 deletions services/runners/job_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ func (p *JobPool) tryRegisterRunner(configFilePath *string) (ok bool) {
Webhook: util.Config.Runner.Webhook,
MaxParallelTasks: util.Config.Runner.MaxParallelTasks,
PublicKey: &publicKey,
Name: util.Config.Runner.Name,
Active: util.Config.Runner.Active,
ProjectName: util.Config.Runner.ProjectName,
Tag: util.Config.Runner.Tag,
})

if err != nil {
Expand Down Expand Up @@ -424,6 +428,9 @@ func (p *JobPool) tryRegisterRunner(configFilePath *string) (ok bool) {
return
}

if config.Runner == nil {
config.Runner = &util.RunnerConfig{}
}
config.Runner.Token = res.Token
configFileBuffer, err = json.MarshalIndent(&config, " ", "\t")
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions services/runners/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ type RunnerRegistration struct {
Webhook string `json:"webhook,omitempty"`
MaxParallelTasks int `json:"max_parallel_tasks"`
PublicKey *string `json:"public_key,omitempty"`
Name string `json:"name,omitempty"`
Active *bool `json:"active,omitempty"`
ProjectName string `json:"project_name,omitempty"`
Tag string `json:"tag,omitempty"`
}

type jobLogRecord struct {
Expand Down
6 changes: 6 additions & 0 deletions util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ type RunnerConfig struct {
Webhook string `json:"webhook,omitempty" env:"SEMAPHORE_RUNNER_WEBHOOK"`

MaxParallelTasks int `json:"max_parallel_tasks,omitempty" default:"1" env:"SEMAPHORE_RUNNER_MAX_PARALLEL_TASKS"`

// New registration options
Name string `json:"name,omitempty" env:"SEMAPHORE_RUNNER_NAME"`
Active *bool `json:"active,omitempty" env:"SEMAPHORE_RUNNER_ACTIVE"`
ProjectName string `json:"project_name,omitempty" env:"SEMAPHORE_RUNNER_PROJECT_NAME"`
Tag string `json:"tag,omitempty" env:"SEMAPHORE_RUNNER_TAG"`
}

type TLSConfig struct {
Expand Down
Loading
Loading