2
0

style(ui): add package docs and mark unused parameters
Some checks failed
CI / build-and-test (push) Has been cancelled
Release / build (arm64, darwin) (push) Has been cancelled
Release / build (amd64, windows) (push) Has been cancelled
Release / build (arm64, linux) (push) Has been cancelled
Release / build (amd64, darwin) (push) Has been cancelled
Release / build (amd64, linux) (push) Has been cancelled
Release / release (push) Has been cancelled

Adds package-level documentation comments across cmd and internal packages. Marks unused function parameters with underscore prefix to satisfy linter requirements. Replaces if-else chains with switch statements for better readability. Explicitly ignores os.Setenv return value where error handling is not needed.
This commit is contained in:
2026-01-19 01:16:47 -05:00
parent 22f1ea6e76
commit 63967eb6fa
23 changed files with 183 additions and 140 deletions

View File

@@ -1,6 +1,7 @@
// Copyright 2026 MarketAlly. All rights reserved. // Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package main provides the upload-helper CLI tool for reliable file uploads.
package main package main
import ( import (

View File

@@ -22,8 +22,8 @@ type cacheServerArgs struct {
Port uint16 Port uint16
} }
func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error { func runCacheServer(_ context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error { return func(_ *cobra.Command, _ []string) error {
cfg, err := config.LoadDefault(*configFile) cfg, err := config.LoadDefault(*configFile)
if err != nil { if err != nil {
return fmt.Errorf("invalid configuration: %w", err) return fmt.Errorf("invalid configuration: %w", err)

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors and MarketAlly. All rights reserved. // Copyright 2022 The Gitea Authors and MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package cmd provides the CLI commands for gitcaddy-runner.
package cmd package cmd
import ( import (
@@ -15,6 +16,7 @@ import (
"git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/ver" "git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/ver"
) )
// Execute runs the root command for gitcaddy-runner CLI.
func Execute(ctx context.Context) { func Execute(ctx context.Context) {
// ./gitcaddy-runner // ./gitcaddy-runner
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{

View File

@@ -58,7 +58,7 @@ var (
) )
func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) func(cmd *cobra.Command, args []string) error { func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error { return func(_ *cobra.Command, _ []string) error {
cfg, err := config.LoadDefault(*configFile) cfg, err := config.LoadDefault(*configFile)
if err != nil { if err != nil {
return fmt.Errorf("invalid configuration: %w", err) return fmt.Errorf("invalid configuration: %w", err)
@@ -132,7 +132,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
return err return err
} }
// if dockerSocketPath passes the check, override DOCKER_HOST with dockerSocketPath // if dockerSocketPath passes the check, override DOCKER_HOST with dockerSocketPath
os.Setenv("DOCKER_HOST", dockerSocketPath) _ = os.Setenv("DOCKER_HOST", dockerSocketPath)
// empty cfg.Container.DockerHost means act_runner need to find an available docker host automatically // empty cfg.Container.DockerHost means act_runner need to find an available docker host automatically
// and assign the path to cfg.Container.DockerHost // and assign the path to cfg.Container.DockerHost
if cfg.Container.DockerHost == "" { if cfg.Container.DockerHost == "" {
@@ -182,21 +182,22 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
capabilities := envcheck.DetectCapabilities(ctx, dockerHost, cfg.Container.WorkdirParent, globalConfig.Runner.Capacity) capabilities := envcheck.DetectCapabilities(ctx, dockerHost, cfg.Container.WorkdirParent, globalConfig.Runner.Capacity)
// Include initial bandwidth result if available // Include initial bandwidth result if available
capabilities.Bandwidth = bandwidthManager.GetLastResult() capabilities.Bandwidth = bandwidthManager.GetLastResult()
capabilitiesJson := capabilities.ToJSON() capabilitiesJSON := capabilities.ToJSON()
log.Infof("detected capabilities: %s", capabilitiesJson) log.Infof("detected capabilities: %s", capabilitiesJSON)
// Check disk space and warn if low // Check disk space and warn if low
checkDiskSpaceAndCleanup(ctx, capabilities) checkDiskSpaceAndCleanup(ctx, capabilities)
// declare the labels of the runner before fetching tasks // declare the labels of the runner before fetching tasks
resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJson) resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJSON)
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented { switch {
case err != nil && connect.CodeOf(err) == connect.CodeUnimplemented:
log.Errorf("Your GitCaddy version is too old to support runner declare, please upgrade to v1.21 or later") log.Errorf("Your GitCaddy version is too old to support runner declare, please upgrade to v1.21 or later")
return err return err
} else if err != nil { case err != nil:
log.WithError(err).Error("fail to invoke Declare") log.WithError(err).Error("fail to invoke Declare")
return err return err
} else { default:
log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully", log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully",
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels) resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
} }
@@ -261,14 +262,15 @@ func checkDiskSpaceAndCleanup(ctx context.Context, capabilities *envcheck.Runner
usedPercent := capabilities.Disk.UsedPercent usedPercent := capabilities.Disk.UsedPercent
freeGB := float64(capabilities.Disk.Free) / (1024 * 1024 * 1024) freeGB := float64(capabilities.Disk.Free) / (1024 * 1024 * 1024)
if usedPercent >= DiskSpaceCriticalThreshold { switch {
case usedPercent >= DiskSpaceCriticalThreshold:
log.Errorf("CRITICAL: Disk space critically low! %.1f%% used, only %.2f GB free. Runner may fail to execute jobs!", usedPercent, freeGB) log.Errorf("CRITICAL: Disk space critically low! %.1f%% used, only %.2f GB free. Runner may fail to execute jobs!", usedPercent, freeGB)
// Always try cleanup at critical level // Always try cleanup at critical level
triggerAutoCleanup(ctx) triggerAutoCleanup(ctx)
} else if usedPercent >= DiskSpaceAutoCleanupThreshold { case usedPercent >= DiskSpaceAutoCleanupThreshold:
log.Warnf("WARNING: Disk space at %.1f%% used (%.2f GB free). Triggering automatic cleanup.", usedPercent, freeGB) log.Warnf("WARNING: Disk space at %.1f%% used (%.2f GB free). Triggering automatic cleanup.", usedPercent, freeGB)
triggerAutoCleanup(ctx) triggerAutoCleanup(ctx)
} else if usedPercent >= DiskSpaceWarningThreshold { case usedPercent >= DiskSpaceWarningThreshold:
log.Warnf("WARNING: Disk space running low. %.1f%% used, %.2f GB free. Consider cleaning up disk space.", usedPercent, freeGB) log.Warnf("WARNING: Disk space running low. %.1f%% used, %.2f GB free. Consider cleaning up disk space.", usedPercent, freeGB)
} }
} }
@@ -330,13 +332,13 @@ func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNa
capabilities.Bandwidth = bandwidthManager.GetLastResult() capabilities.Bandwidth = bandwidthManager.GetLastResult()
} }
capabilitiesJson := capabilities.ToJSON() capabilitiesJSON := capabilities.ToJSON()
// Check for disk space warnings // Check for disk space warnings
checkDiskSpaceAndCleanup(ctx, capabilities) checkDiskSpaceAndCleanup(ctx, capabilities)
// Send updated capabilities to server // Send updated capabilities to server
_, err := runner.Declare(ctx, labelNames, capabilitiesJson) _, err := runner.Declare(ctx, labelNames, capabilitiesJSON)
if err != nil { if err != nil {
log.WithError(err).Debug("failed to update capabilities") log.WithError(err).Debug("failed to update capabilities")
} else { } else {

View File

@@ -264,7 +264,7 @@ func printList(plan *model.Plan) error {
return nil return nil
} }
func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *executeArgs) error { func runExecList(_ context.Context, planner model.WorkflowPlanner, execArgs *executeArgs) error {
// plan with filtered jobs - to be used for filtering only // plan with filtered jobs - to be used for filtering only
var filterPlan *model.Plan var filterPlan *model.Plan
@@ -286,19 +286,20 @@ func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *e
} }
var err error var err error
if execArgs.job != "" { switch {
case execArgs.job != "":
log.Infof("Preparing plan with a job: %s", execArgs.job) log.Infof("Preparing plan with a job: %s", execArgs.job)
filterPlan, err = planner.PlanJob(execArgs.job) filterPlan, err = planner.PlanJob(execArgs.job)
if err != nil { if err != nil {
return err return err
} }
} else if filterEventName != "" { case filterEventName != "":
log.Infof("Preparing plan for a event: %s", filterEventName) log.Infof("Preparing plan for a event: %s", filterEventName)
filterPlan, err = planner.PlanEvent(filterEventName) filterPlan, err = planner.PlanEvent(filterEventName)
if err != nil { if err != nil {
return err return err
} }
} else { default:
log.Infof("Preparing plan with all jobs") log.Infof("Preparing plan with all jobs")
filterPlan, err = planner.PlanAll() filterPlan, err = planner.PlanAll()
if err != nil { if err != nil {
@@ -312,7 +313,7 @@ func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *e
} }
func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command, args []string) error { func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error { return func(_ *cobra.Command, _ []string) error {
planner, err := model.NewWorkflowPlanner(execArgs.WorkflowsPath(), execArgs.noWorkflowRecurse) planner, err := model.NewWorkflowPlanner(execArgs.WorkflowsPath(), execArgs.noWorkflowRecurse)
if err != nil { if err != nil {
return err return err
@@ -331,18 +332,19 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
// collect all events from loaded workflows // collect all events from loaded workflows
events := planner.GetEvents() events := planner.GetEvents()
if len(execArgs.event) > 0 { switch {
case len(execArgs.event) > 0:
log.Infof("Using chosed event for filtering: %s", execArgs.event) log.Infof("Using chosed event for filtering: %s", execArgs.event)
eventName = execArgs.event eventName = execArgs.event
} else if len(events) == 1 && len(events[0]) > 0 { case len(events) == 1 && len(events[0]) > 0:
log.Infof("Using the only detected workflow event: %s", events[0]) log.Infof("Using the only detected workflow event: %s", events[0])
eventName = events[0] eventName = events[0]
} else if execArgs.autodetectEvent && len(events) > 0 && len(events[0]) > 0 { case execArgs.autodetectEvent && len(events) > 0 && len(events[0]) > 0:
// set default event type to first event from many available // set default event type to first event from many available
// this way user dont have to specify the event. // this way user dont have to specify the event.
log.Infof("Using first detected workflow event: %s", events[0]) log.Infof("Using first detected workflow event: %s", events[0])
eventName = events[0] eventName = events[0]
} else { default:
log.Infof("Using default workflow event: push") log.Infof("Using default workflow event: push")
eventName = "push" eventName = "push"
} }
@@ -388,7 +390,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
defer os.RemoveAll(tempDir) defer func() { _ = os.RemoveAll(tempDir) }()
execArgs.artifactServerPath = tempDir execArgs.artifactServerPath = tempDir
} }
@@ -454,7 +456,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
log.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort) log.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort)
ctx = common.WithDryrun(ctx, execArgs.dryrun) ctx = common.WithDryrun(ctx, execArgs.dryrun)
executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error { executor := r.NewPlanExecutor(plan).Finally(func(_ context.Context) error {
artifactCancel() artifactCancel()
return nil return nil
}) })

View File

@@ -28,7 +28,7 @@ import (
// runRegister registers a runner to the server // runRegister registers a runner to the server
func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) func(*cobra.Command, []string) error { func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error { return func(_ *cobra.Command, _ []string) error {
log.SetReportCaller(false) log.SetReportCaller(false)
isTerm := isatty.IsTerminal(os.Stdout.Fd()) isTerm := isatty.IsTerminal(os.Stdout.Fd())
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
@@ -80,6 +80,7 @@ type registerArgs struct {
type registerStage int8 type registerStage int8
// Register stage constants define the steps in the registration workflow.
const ( const (
StageUnknown registerStage = -1 StageUnknown registerStage = -1
StageOverwriteLocalConfig registerStage = iota + 1 StageOverwriteLocalConfig registerStage = iota + 1
@@ -250,7 +251,7 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist
if stage == StageWaitingForRegistration { if stage == StageWaitingForRegistration {
log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels) log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels)
if err := doRegister(ctx, cfg, inputs); err != nil { if err := doRegister(ctx, cfg, inputs); err != nil {
return fmt.Errorf("Failed to register runner: %w", err) return fmt.Errorf("failed to register runner: %w", err)
} }
log.Infof("Runner registered successfully.") log.Infof("Runner registered successfully.")
return nil return nil
@@ -311,7 +312,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
return err return err
} }
if err := doRegister(ctx, cfg, inputs); err != nil { if err := doRegister(ctx, cfg, inputs); err != nil {
return fmt.Errorf("Failed to register runner: %w", err) return fmt.Errorf("failed to register runner: %w", err)
} }
log.Infof("Runner registered successfully.") log.Infof("Runner registered successfully.")
return nil return nil

View File

@@ -1,6 +1,7 @@
// Copyright 2023 The Gitea Authors and MarketAlly. All rights reserved. // Copyright 2023 The Gitea Authors and MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package poll provides task polling functionality for CI runners.
package poll package poll
import ( import (
@@ -22,6 +23,7 @@ import (
"git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/envcheck" "git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/envcheck"
) )
// Poller handles task polling from the Gitea server.
type Poller struct { type Poller struct {
client client.Client client client.Client
runner *run.Runner runner *run.Runner
@@ -38,6 +40,7 @@ type Poller struct {
done chan struct{} done chan struct{}
} }
// New creates a new Poller instance.
func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller { func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller {
pollingCtx, shutdownPolling := context.WithCancel(context.Background()) pollingCtx, shutdownPolling := context.WithCancel(context.Background())
@@ -65,6 +68,7 @@ func (p *Poller) SetBandwidthManager(bm *envcheck.BandwidthManager) {
p.bandwidthManager = bm p.bandwidthManager = bm
} }
// Poll starts polling for tasks with the configured capacity.
func (p *Poller) Poll() { func (p *Poller) Poll() {
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1) limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
@@ -78,6 +82,7 @@ func (p *Poller) Poll() {
close(p.done) close(p.done)
} }
// PollOnce polls for a single task and then exits.
func (p *Poller) PollOnce() { func (p *Poller) PollOnce() {
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1) limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
@@ -87,18 +92,19 @@ func (p *Poller) PollOnce() {
close(p.done) close(p.done)
} }
// Shutdown gracefully stops the poller.
func (p *Poller) Shutdown(ctx context.Context) error { func (p *Poller) Shutdown(ctx context.Context) error {
p.shutdownPolling() p.shutdownPolling()
select { select {
// graceful shutdown completed succesfully // graceful shutdown completed successfully
case <-p.done: case <-p.done:
return nil return nil
// our timeout for shutting down ran out // our timeout for shutting down ran out
case <-ctx.Done(): case <-ctx.Done():
// when both the timeout fires and the graceful shutdown // when both the timeout fires and the graceful shutdown
// completed succsfully, this branch of the select may // completed successfully, this branch of the select may
// fire. Do a non-blocking check here against the graceful // fire. Do a non-blocking check here against the graceful
// shutdown status to avoid sending an error if we don't need to. // shutdown status to avoid sending an error if we don't need to.
_, ok := <-p.done _, ok := <-p.done
@@ -110,7 +116,7 @@ func (p *Poller) Shutdown(ctx context.Context) error {
p.shutdownJobs() p.shutdownJobs()
// wait for running jobs to report their status to Gitea // wait for running jobs to report their status to Gitea
_, _ = <-p.done <-p.done
return ctx.Err() return ctx.Err()
} }
@@ -173,13 +179,13 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
caps.Bandwidth = p.bandwidthManager.GetLastResult() caps.Bandwidth = p.bandwidthManager.GetLastResult()
} }
capsJson := caps.ToJSON() capsJSON := caps.ToJSON()
// Load the version value that was in the cache when the request was sent. // Load the version value that was in the cache when the request was sent.
v := p.tasksVersion.Load() v := p.tasksVersion.Load()
fetchReq := &runnerv1.FetchTaskRequest{ fetchReq := &runnerv1.FetchTaskRequest{
TasksVersion: v, TasksVersion: v,
CapabilitiesJson: capsJson, CapabilitiesJson: capsJSON,
} }
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(fetchReq)) resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(fetchReq))
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {

View File

@@ -1,6 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved. // Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package run provides the core runner functionality for executing tasks.
package run package run
import ( import (

View File

@@ -85,6 +85,7 @@ func (r *Runner) CleanStaleJobCaches(maxAge time.Duration) {
} }
} }
// NewRunner creates a new Runner with the given configuration, registration, and client.
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner { func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
ls := labels.Labels{} ls := labels.Labels{}
for _, v := range reg.Labels { for _, v := range reg.Labels {
@@ -133,6 +134,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
} }
} }
// Run executes a task from the server.
func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
if _, ok := r.runningTasks.Load(task.Id); ok { if _, ok := r.runningTasks.Load(task.Id); ok {
return fmt.Errorf("task %d is already running", task.Id) return fmt.Errorf("task %d is already running", task.Id)
@@ -161,7 +163,7 @@ func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
// getDefaultActionsURL // getDefaultActionsURL
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank, // when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
// it should be set to GithubMirror first. // it should be set to GithubMirror first.
func (r *Runner) getDefaultActionsURL(ctx context.Context, task *runnerv1.Task) string { func (r *Runner) getDefaultActionsURL(_ context.Context, task *runnerv1.Task) string {
giteaDefaultActionsURL := task.Context.Fields["gitea_default_actions_url"].GetStringValue() giteaDefaultActionsURL := task.Context.Fields["gitea_default_actions_url"].GetStringValue()
if giteaDefaultActionsURL == "https://github.com" && r.cfg.Runner.GithubMirror != "" { if giteaDefaultActionsURL == "https://github.com" && r.cfg.Runner.GithubMirror != "" {
return r.cfg.Runner.GithubMirror return r.cfg.Runner.GithubMirror
@@ -219,8 +221,8 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
preset.Token = t preset.Token = t
} }
if actionsIdTokenRequestUrl := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIdTokenRequestUrl != "" { if actionsIDTokenRequestURL := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIDTokenRequestURL != "" {
r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = actionsIdTokenRequestUrl r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = actionsIDTokenRequestURL
r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue() r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue()
task.Secrets["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] task.Secrets["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
} }
@@ -305,10 +307,11 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
return execErr return execErr
} }
func (r *Runner) Declare(ctx context.Context, labels []string, capabilitiesJson string) (*connect.Response[runnerv1.DeclareResponse], error) { // Declare sends the runner's labels and capabilities to the server.
func (r *Runner) Declare(ctx context.Context, labels []string, capabilitiesJSON string) (*connect.Response[runnerv1.DeclareResponse], error) {
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{ return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
Version: ver.Version(), Version: ver.Version(),
Labels: labels, Labels: labels,
CapabilitiesJson: capabilitiesJson, CapabilitiesJson: capabilitiesJSON,
})) }))
} }

View File

@@ -1,6 +1,7 @@
// Copyright 2026 MarketAlly. All rights reserved. // Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package artifact provides utilities for handling artifact uploads.
package artifact package artifact
import ( import (
@@ -88,7 +89,7 @@ func (u *UploadHelper) prewarmConnection(url string) error {
if err != nil { if err != nil {
return err return err
} }
resp.Body.Close() _ = resp.Body.Close()
return nil return nil
} }
@@ -98,7 +99,7 @@ func (u *UploadHelper) doUpload(client *http.Client, url, token, filepath string
if err != nil { if err != nil {
return fmt.Errorf("failed to open file: %w", err) return fmt.Errorf("failed to open file: %w", err)
} }
defer file.Close() defer func() { _ = file.Close() }()
stat, err := file.Stat() stat, err := file.Stat()
if err != nil { if err != nil {
@@ -118,7 +119,7 @@ func (u *UploadHelper) doUpload(client *http.Client, url, token, filepath string
if _, err := io.Copy(part, file); err != nil { if _, err := io.Copy(part, file); err != nil {
return fmt.Errorf("failed to copy file to form: %w", err) return fmt.Errorf("failed to copy file to form: %w", err)
} }
writer.Close() _ = writer.Close()
req, err := http.NewRequest("POST", url, body) req, err := http.NewRequest("POST", url, body)
if err != nil { if err != nil {
@@ -133,7 +134,7 @@ func (u *UploadHelper) doUpload(client *http.Client, url, token, filepath string
if err != nil { if err != nil {
return fmt.Errorf("upload request failed: %w", err) return fmt.Errorf("upload request failed: %w", err)
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(resp.Body) respBody, _ := io.ReadAll(resp.Body)

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package client provides the HTTP client for communicating with the runner API.
package client package client
import ( import (

View File

@@ -3,6 +3,7 @@
package client package client
// HTTP header constants for runner authentication and identification.
const ( const (
UUIDHeader = "x-runner-uuid" UUIDHeader = "x-runner-uuid"
TokenHeader = "x-runner-token" TokenHeader = "x-runner-token"

View File

@@ -63,10 +63,12 @@ func New(endpoint string, insecure bool, uuid, token, version string, opts ...co
} }
} }
// Address returns the endpoint URL of the client.
func (c *HTTPClient) Address() string { func (c *HTTPClient) Address() string {
return c.endpoint return c.endpoint
} }
// Insecure returns whether TLS verification is disabled.
func (c *HTTPClient) Insecure() bool { func (c *HTTPClient) Insecure() bool {
return c.insecure return c.insecure
} }

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package config provides configuration loading and management for the runner.
package config package config
import ( import (

View File

@@ -5,5 +5,7 @@ package config
import _ "embed" import _ "embed"
// Example contains the example configuration file content.
//
//go:embed config.example.yaml //go:embed config.example.yaml
var Example []byte var Example []byte

View File

@@ -23,12 +23,13 @@ type Registration struct {
Ephemeral bool `json:"ephemeral"` Ephemeral bool `json:"ephemeral"`
} }
// LoadRegistration loads the runner registration from a JSON file.
func LoadRegistration(file string) (*Registration, error) { func LoadRegistration(file string) (*Registration, error) {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer func() { _ = f.Close() }()
var reg Registration var reg Registration
if err := json.NewDecoder(f).Decode(&reg); err != nil { if err := json.NewDecoder(f).Decode(&reg); err != nil {
@@ -40,12 +41,13 @@ func LoadRegistration(file string) (*Registration, error) {
return &reg, nil return &reg, nil
} }
// SaveRegistration saves the runner registration to a JSON file.
func SaveRegistration(file string, reg *Registration) error { func SaveRegistration(file string, reg *Registration) error {
f, err := os.Create(file) f, err := os.Create(file)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer func() { _ = f.Close() }()
reg.Warning = registrationWarning reg.Warning = registrationWarning

View File

@@ -121,7 +121,7 @@ func testLatency(ctx context.Context, serverURL string) float64 {
if err != nil { if err != nil {
return 0 return 0
} }
resp.Body.Close() _ = resp.Body.Close()
latency := time.Since(start).Seconds() * 1000 // Convert to ms latency := time.Since(start).Seconds() * 1000 // Convert to ms
return float64(int(latency*100)) / 100 // Round to 2 decimals return float64(int(latency*100)) / 100 // Round to 2 decimals
@@ -169,7 +169,7 @@ func testDownloadSpeed(ctx context.Context, serverURL string) float64 {
} }
n, _ := io.Copy(io.Discard, resp.Body) n, _ := io.Copy(io.Discard, resp.Body)
resp.Body.Close() _ = resp.Body.Close()
cancel() cancel()
duration := time.Since(start) duration := time.Since(start)

View File

@@ -83,7 +83,7 @@ type CapabilityFeatures struct {
// DetectCapabilities detects the runner's capabilities // DetectCapabilities detects the runner's capabilities
// workingDir is the directory where builds will run (for disk space detection) // workingDir is the directory where builds will run (for disk space detection)
func DetectCapabilities(ctx context.Context, dockerHost string, workingDir string, capacity int) *RunnerCapabilities { func DetectCapabilities(ctx context.Context, dockerHost string, workingDir string, capacity int) *RunnerCapabilities {
cap := &RunnerCapabilities{ caps := &RunnerCapabilities{
Capacity: capacity, Capacity: capacity,
OS: runtime.GOOS, OS: runtime.GOOS,
Arch: runtime.GOARCH, Arch: runtime.GOARCH,
@@ -105,40 +105,40 @@ func DetectCapabilities(ctx context.Context, dockerHost string, workingDir strin
// Detect Linux distribution // Detect Linux distribution
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
cap.Distro = detectLinuxDistro() caps.Distro = detectLinuxDistro()
} }
// Detect macOS Xcode/iOS // Detect macOS Xcode/iOS
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
cap.Xcode = detectXcode(ctx) caps.Xcode = detectXcode(ctx)
} }
// Detect Docker // Detect Docker
cap.Docker, cap.ContainerRuntime = detectDocker(ctx, dockerHost) caps.Docker, caps.ContainerRuntime = detectDocker(ctx, dockerHost)
if cap.Docker { if caps.Docker {
cap.DockerCompose = detectDockerCompose(ctx) caps.DockerCompose = detectDockerCompose(ctx)
cap.Features.Services = true caps.Features.Services = true
} }
// Detect common tools // Detect common tools
detectTools(ctx, cap) detectTools(ctx, caps)
// Detect build tools // Detect build tools
detectBuildTools(ctx, cap) detectBuildTools(ctx, caps)
// Detect package managers // Detect package managers
detectPackageManagers(ctx, cap) detectPackageManagers(ctx, caps)
// Detect disk space on the working directory's filesystem // Detect disk space on the working directory's filesystem
cap.Disk = detectDiskSpace(workingDir) caps.Disk = detectDiskSpace(workingDir)
// Detect CPU load // Detect CPU load
cap.CPU = detectCPULoad() caps.CPU = detectCPULoad()
// Generate suggested labels based on detected capabilities // Generate suggested labels based on detected capabilities
cap.SuggestedLabels = generateSuggestedLabels(cap) caps.SuggestedLabels = generateSuggestedLabels(caps)
return cap return caps
} }
// detectXcode detects Xcode and iOS development capabilities on macOS // detectXcode detects Xcode and iOS development capabilities on macOS
@@ -227,18 +227,19 @@ func detectLinuxDistro() *DistroInfo {
if err != nil { if err != nil {
return nil return nil
} }
defer file.Close() defer func() { _ = file.Close() }()
distro := &DistroInfo{} distro := &DistroInfo{}
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if strings.HasPrefix(line, "ID=") { switch {
case strings.HasPrefix(line, "ID="):
distro.ID = strings.Trim(strings.TrimPrefix(line, "ID="), "\"") distro.ID = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
} else if strings.HasPrefix(line, "VERSION_ID=") { case strings.HasPrefix(line, "VERSION_ID="):
distro.VersionID = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"") distro.VersionID = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"")
} else if strings.HasPrefix(line, "PRETTY_NAME=") { case strings.HasPrefix(line, "PRETTY_NAME="):
distro.PrettyName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") distro.PrettyName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"")
} }
} }
@@ -251,7 +252,7 @@ func detectLinuxDistro() *DistroInfo {
} }
// generateSuggestedLabels creates industry-standard labels based on capabilities // generateSuggestedLabels creates industry-standard labels based on capabilities
func generateSuggestedLabels(cap *RunnerCapabilities) []string { func generateSuggestedLabels(caps *RunnerCapabilities) []string {
labels := []string{} labels := []string{}
seen := make(map[string]bool) seen := make(map[string]bool)
@@ -263,7 +264,7 @@ func generateSuggestedLabels(cap *RunnerCapabilities) []string {
} }
// OS labels // OS labels
switch cap.OS { switch caps.OS {
case "linux": case "linux":
addLabel("linux") addLabel("linux")
addLabel("linux-latest") addLabel("linux-latest")
@@ -276,17 +277,17 @@ func generateSuggestedLabels(cap *RunnerCapabilities) []string {
} }
// Distro labels (Linux only) // Distro labels (Linux only)
if cap.Distro != nil && cap.Distro.ID != "" { if caps.Distro != nil && caps.Distro.ID != "" {
distro := strings.ToLower(cap.Distro.ID) distro := strings.ToLower(caps.Distro.ID)
addLabel(distro) addLabel(distro)
addLabel(distro + "-latest") addLabel(distro + "-latest")
} }
// Xcode/iOS labels (macOS only) // Xcode/iOS labels (macOS only)
if cap.Xcode != nil { if caps.Xcode != nil {
addLabel("xcode") addLabel("xcode")
// Check for SDKs // Check for SDKs
for _, sdk := range cap.Xcode.SDKs { for _, sdk := range caps.Xcode.SDKs {
sdkLower := strings.ToLower(sdk) sdkLower := strings.ToLower(sdk)
if strings.Contains(sdkLower, "ios") { if strings.Contains(sdkLower, "ios") {
addLabel("ios") addLabel("ios")
@@ -302,24 +303,24 @@ func generateSuggestedLabels(cap *RunnerCapabilities) []string {
} }
} }
// If simulators available, add simulator label // If simulators available, add simulator label
if len(cap.Xcode.Simulators) > 0 { if len(caps.Xcode.Simulators) > 0 {
addLabel("ios-simulator") addLabel("ios-simulator")
} }
} }
// Tool-based labels // Tool-based labels
if _, ok := cap.Tools["dotnet"]; ok { if _, ok := caps.Tools["dotnet"]; ok {
addLabel("dotnet") addLabel("dotnet")
} }
if _, ok := cap.Tools["java"]; ok { if _, ok := caps.Tools["java"]; ok {
addLabel("java") addLabel("java")
} }
if _, ok := cap.Tools["node"]; ok { if _, ok := caps.Tools["node"]; ok {
addLabel("node") addLabel("node")
} }
// Build tool labels // Build tool labels
for _, tool := range cap.BuildTools { for _, tool := range caps.BuildTools {
switch tool { switch tool {
case "msbuild": case "msbuild":
addLabel("msbuild") addLabel("msbuild")
@@ -384,7 +385,7 @@ func detectDocker(ctx context.Context, dockerHost string) (bool, string) {
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer cli.Close() defer func() { _ = cli.Close() }()
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
@@ -420,7 +421,7 @@ func detectDockerCompose(ctx context.Context) bool {
return false return false
} }
func detectTools(ctx context.Context, cap *RunnerCapabilities) { func detectTools(ctx context.Context, caps *RunnerCapabilities) {
toolDetectors := map[string]func(context.Context) []string{ toolDetectors := map[string]func(context.Context) []string{
"node": detectNodeVersions, "node": detectNodeVersions,
"go": detectGoVersions, "go": detectGoVersions,
@@ -439,7 +440,7 @@ func detectTools(ctx context.Context, cap *RunnerCapabilities) {
for tool, detector := range toolDetectors { for tool, detector := range toolDetectors {
if versions := detector(ctx); len(versions) > 0 { if versions := detector(ctx); len(versions) > 0 {
cap.Tools[tool] = versions caps.Tools[tool] = versions
} }
} }
@@ -460,23 +461,23 @@ func detectTools(ctx context.Context, cap *RunnerCapabilities) {
for name, cmd := range simpleTools { for name, cmd := range simpleTools {
if v := detectSimpleToolVersion(ctx, cmd); v != "" { if v := detectSimpleToolVersion(ctx, cmd); v != "" {
cap.Tools[name] = []string{v} caps.Tools[name] = []string{v}
} }
} }
} }
func detectBuildTools(ctx context.Context, cap *RunnerCapabilities) { func detectBuildTools(ctx context.Context, caps *RunnerCapabilities) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
detectWindowsBuildTools(ctx, cap) detectWindowsBuildTools(ctx, caps)
case "darwin": case "darwin":
detectMacOSBuildTools(ctx, cap) detectMacOSBuildTools(caps)
case "linux": case "linux":
detectLinuxBuildTools(ctx, cap) detectLinuxBuildTools(caps)
} }
} }
func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) { func detectWindowsBuildTools(ctx context.Context, caps *RunnerCapabilities) {
// Check for Visual Studio via vswhere // Check for Visual Studio via vswhere
vswherePaths := []string{ vswherePaths := []string{
`C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`, `C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`,
@@ -486,7 +487,7 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
if _, err := os.Stat(vswhere); err == nil { if _, err := os.Stat(vswhere); err == nil {
cmd := exec.CommandContext(ctx, vswhere, "-latest", "-property", "displayName") cmd := exec.CommandContext(ctx, vswhere, "-latest", "-property", "displayName")
if output, err := cmd.Output(); err == nil && len(output) > 0 { if output, err := cmd.Output(); err == nil && len(output) > 0 {
cap.BuildTools = append(cap.BuildTools, "visual-studio") caps.BuildTools = append(caps.BuildTools, "visual-studio")
break break
} }
} }
@@ -503,7 +504,7 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
} }
for _, msbuild := range msbuildPaths { for _, msbuild := range msbuildPaths {
if _, err := os.Stat(msbuild); err == nil { if _, err := os.Stat(msbuild); err == nil {
cap.BuildTools = append(cap.BuildTools, "msbuild") caps.BuildTools = append(caps.BuildTools, "msbuild")
break break
} }
} }
@@ -517,14 +518,14 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
} }
for _, iscc := range innoSetupPaths { for _, iscc := range innoSetupPaths {
if _, err := os.Stat(iscc); err == nil { if _, err := os.Stat(iscc); err == nil {
cap.BuildTools = append(cap.BuildTools, "inno-setup") caps.BuildTools = append(caps.BuildTools, "inno-setup")
break break
} }
} }
// Also check PATH // Also check PATH
if _, err := exec.LookPath("iscc"); err == nil { if _, err := exec.LookPath("iscc"); err == nil {
if !contains(cap.BuildTools, "inno-setup") { if !contains(caps.BuildTools, "inno-setup") {
cap.BuildTools = append(cap.BuildTools, "inno-setup") caps.BuildTools = append(caps.BuildTools, "inno-setup")
} }
} }
@@ -535,13 +536,13 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
} }
for _, nsis := range nsisPaths { for _, nsis := range nsisPaths {
if _, err := os.Stat(nsis); err == nil { if _, err := os.Stat(nsis); err == nil {
cap.BuildTools = append(cap.BuildTools, "nsis") caps.BuildTools = append(caps.BuildTools, "nsis")
break break
} }
} }
if _, err := exec.LookPath("makensis"); err == nil { if _, err := exec.LookPath("makensis"); err == nil {
if !contains(cap.BuildTools, "nsis") { if !contains(caps.BuildTools, "nsis") {
cap.BuildTools = append(cap.BuildTools, "nsis") caps.BuildTools = append(caps.BuildTools, "nsis")
} }
} }
@@ -552,7 +553,7 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
} }
for _, wix := range wixPaths { for _, wix := range wixPaths {
if _, err := os.Stat(wix); err == nil { if _, err := os.Stat(wix); err == nil {
cap.BuildTools = append(cap.BuildTools, "wix") caps.BuildTools = append(caps.BuildTools, "wix")
break break
} }
} }
@@ -560,63 +561,63 @@ func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
// Check for signtool (Windows SDK) // Check for signtool (Windows SDK)
signtoolPaths, _ := filepath.Glob(`C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe`) signtoolPaths, _ := filepath.Glob(`C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe`)
if len(signtoolPaths) > 0 { if len(signtoolPaths) > 0 {
cap.BuildTools = append(cap.BuildTools, "signtool") caps.BuildTools = append(caps.BuildTools, "signtool")
} }
} }
func detectMacOSBuildTools(ctx context.Context, cap *RunnerCapabilities) { func detectMacOSBuildTools(caps *RunnerCapabilities) {
// Check for xcpretty // Check for xcpretty
if _, err := exec.LookPath("xcpretty"); err == nil { if _, err := exec.LookPath("xcpretty"); err == nil {
cap.BuildTools = append(cap.BuildTools, "xcpretty") caps.BuildTools = append(caps.BuildTools, "xcpretty")
} }
// Check for fastlane // Check for fastlane
if _, err := exec.LookPath("fastlane"); err == nil { if _, err := exec.LookPath("fastlane"); err == nil {
cap.BuildTools = append(cap.BuildTools, "fastlane") caps.BuildTools = append(caps.BuildTools, "fastlane")
} }
// Check for CocoaPods // Check for CocoaPods
if _, err := exec.LookPath("pod"); err == nil { if _, err := exec.LookPath("pod"); err == nil {
cap.BuildTools = append(cap.BuildTools, "cocoapods") caps.BuildTools = append(caps.BuildTools, "cocoapods")
} }
// Check for Carthage // Check for Carthage
if _, err := exec.LookPath("carthage"); err == nil { if _, err := exec.LookPath("carthage"); err == nil {
cap.BuildTools = append(cap.BuildTools, "carthage") caps.BuildTools = append(caps.BuildTools, "carthage")
} }
// Check for SwiftLint // Check for SwiftLint
if _, err := exec.LookPath("swiftlint"); err == nil { if _, err := exec.LookPath("swiftlint"); err == nil {
cap.BuildTools = append(cap.BuildTools, "swiftlint") caps.BuildTools = append(caps.BuildTools, "swiftlint")
} }
// Check for create-dmg or similar // Check for create-dmg or similar
if _, err := exec.LookPath("create-dmg"); err == nil { if _, err := exec.LookPath("create-dmg"); err == nil {
cap.BuildTools = append(cap.BuildTools, "create-dmg") caps.BuildTools = append(caps.BuildTools, "create-dmg")
} }
// Check for Packages (packagesbuild) // Check for Packages (packagesbuild)
if _, err := exec.LookPath("packagesbuild"); err == nil { if _, err := exec.LookPath("packagesbuild"); err == nil {
cap.BuildTools = append(cap.BuildTools, "packages") caps.BuildTools = append(caps.BuildTools, "packages")
} }
// Check for pkgbuild (built-in) // Check for pkgbuild (built-in)
if _, err := exec.LookPath("pkgbuild"); err == nil { if _, err := exec.LookPath("pkgbuild"); err == nil {
cap.BuildTools = append(cap.BuildTools, "pkgbuild") caps.BuildTools = append(caps.BuildTools, "pkgbuild")
} }
// Check for codesign (built-in) // Check for codesign (built-in)
if _, err := exec.LookPath("codesign"); err == nil { if _, err := exec.LookPath("codesign"); err == nil {
cap.BuildTools = append(cap.BuildTools, "codesign") caps.BuildTools = append(caps.BuildTools, "codesign")
} }
// Check for notarytool (built-in with Xcode) // Check for notarytool (built-in with Xcode)
if _, err := exec.LookPath("notarytool"); err == nil { if _, err := exec.LookPath("notarytool"); err == nil {
cap.BuildTools = append(cap.BuildTools, "notarytool") caps.BuildTools = append(caps.BuildTools, "notarytool")
} }
} }
func detectLinuxBuildTools(ctx context.Context, cap *RunnerCapabilities) { func detectLinuxBuildTools(caps *RunnerCapabilities) {
// Check for common Linux build tools // Check for common Linux build tools
tools := []string{ tools := []string{
"gcc", "g++", "clang", "clang++", "gcc", "g++", "clang", "clang++",
@@ -628,54 +629,54 @@ func detectLinuxBuildTools(ctx context.Context, cap *RunnerCapabilities) {
for _, tool := range tools { for _, tool := range tools {
if _, err := exec.LookPath(tool); err == nil { if _, err := exec.LookPath(tool); err == nil {
cap.BuildTools = append(cap.BuildTools, tool) caps.BuildTools = append(caps.BuildTools, tool)
} }
} }
} }
func detectPackageManagers(ctx context.Context, cap *RunnerCapabilities) { func detectPackageManagers(_ context.Context, caps *RunnerCapabilities) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
if _, err := exec.LookPath("choco"); err == nil { if _, err := exec.LookPath("choco"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "chocolatey") caps.PackageManagers = append(caps.PackageManagers, "chocolatey")
} }
if _, err := exec.LookPath("scoop"); err == nil { if _, err := exec.LookPath("scoop"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "scoop") caps.PackageManagers = append(caps.PackageManagers, "scoop")
} }
if _, err := exec.LookPath("winget"); err == nil { if _, err := exec.LookPath("winget"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "winget") caps.PackageManagers = append(caps.PackageManagers, "winget")
} }
case "darwin": case "darwin":
if _, err := exec.LookPath("brew"); err == nil { if _, err := exec.LookPath("brew"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "homebrew") caps.PackageManagers = append(caps.PackageManagers, "homebrew")
} }
if _, err := exec.LookPath("port"); err == nil { if _, err := exec.LookPath("port"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "macports") caps.PackageManagers = append(caps.PackageManagers, "macports")
} }
case "linux": case "linux":
if _, err := exec.LookPath("apt"); err == nil { if _, err := exec.LookPath("apt"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "apt") caps.PackageManagers = append(caps.PackageManagers, "apt")
} }
if _, err := exec.LookPath("yum"); err == nil { if _, err := exec.LookPath("yum"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "yum") caps.PackageManagers = append(caps.PackageManagers, "yum")
} }
if _, err := exec.LookPath("dnf"); err == nil { if _, err := exec.LookPath("dnf"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "dnf") caps.PackageManagers = append(caps.PackageManagers, "dnf")
} }
if _, err := exec.LookPath("pacman"); err == nil { if _, err := exec.LookPath("pacman"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "pacman") caps.PackageManagers = append(caps.PackageManagers, "pacman")
} }
if _, err := exec.LookPath("zypper"); err == nil { if _, err := exec.LookPath("zypper"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "zypper") caps.PackageManagers = append(caps.PackageManagers, "zypper")
} }
if _, err := exec.LookPath("apk"); err == nil { if _, err := exec.LookPath("apk"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "apk") caps.PackageManagers = append(caps.PackageManagers, "apk")
} }
if _, err := exec.LookPath("snap"); err == nil { if _, err := exec.LookPath("snap"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "snap") caps.PackageManagers = append(caps.PackageManagers, "snap")
} }
if _, err := exec.LookPath("flatpak"); err == nil { if _, err := exec.LookPath("flatpak"); err == nil {
cap.PackageManagers = append(cap.PackageManagers, "flatpak") caps.PackageManagers = append(caps.PackageManagers, "flatpak")
} }
} }
} }
@@ -813,13 +814,8 @@ func detectPwshVersion(ctx context.Context, cmd string) string {
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
// Use -Command to get version // Use -Command to get version (same command works for both pwsh and powershell)
var c *exec.Cmd c := exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
if cmd == "pwsh" {
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
} else {
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
}
output, err := c.Output() output, err := c.Output()
if err != nil { if err != nil {
@@ -1042,15 +1038,9 @@ func getContainerCPUUsage() float64 {
} }
} }
// Try reading /proc/stat for this process's CPU usage // Note: Reading /proc/self/stat could give us utime and stime (fields 14 and 15),
if data, err := os.ReadFile("/proc/self/stat"); err == nil { // but these are cumulative values, not instantaneous. For containers, we report 0
fields := strings.Fields(string(data)) // rather than misleading host data.
if len(fields) >= 15 {
// Fields 14 and 15 are utime and stime (in clock ticks)
// This is cumulative, not instantaneous
// For containers, we'll report 0 rather than misleading host data
}
}
return -1 // Unable to determine - caller should handle return -1 // Unable to determine - caller should handle
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
) )
// CheckIfDockerRunning verifies that the Docker daemon is running and accessible.
func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error { func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error {
opts := []client.Opt{ opts := []client.Opt{
client.FromEnv, client.FromEnv,
@@ -23,7 +24,7 @@ func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error {
if err != nil { if err != nil {
return err return err
} }
defer cli.Close() defer func() { _ = cli.Close() }()
_, err = cli.Ping(ctx) _, err = cli.Ping(ctx)
if err != nil { if err != nil {

View File

@@ -1,6 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package labels provides utilities for parsing and managing runner labels.
package labels package labels
import ( import (
@@ -8,17 +9,20 @@ import (
"strings" "strings"
) )
// Label scheme constants define the execution environments.
const ( const (
SchemeHost = "host" SchemeHost = "host"
SchemeDocker = "docker" SchemeDocker = "docker"
) )
// Label represents a parsed runner label with name, schema, and optional argument.
type Label struct { type Label struct {
Name string Name string
Schema string Schema string
Arg string Arg string
} }
// Parse parses a label string in the format "name:schema:arg" and returns a Label.
func Parse(str string) (*Label, error) { func Parse(str string) (*Label, error) {
splits := strings.SplitN(str, ":", 3) splits := strings.SplitN(str, ":", 3)
label := &Label{ label := &Label{
@@ -38,8 +42,10 @@ func Parse(str string) (*Label, error) {
return label, nil return label, nil
} }
// Labels is a slice of Label pointers.
type Labels []*Label type Labels []*Label
// RequireDocker returns true if any label uses the docker schema.
func (l Labels) RequireDocker() bool { func (l Labels) RequireDocker() bool {
for _, label := range l { for _, label := range l {
if label.Schema == SchemeDocker { if label.Schema == SchemeDocker {
@@ -49,6 +55,7 @@ func (l Labels) RequireDocker() bool {
return false return false
} }
// PickPlatform selects the appropriate platform based on the runsOn requirements.
func (l Labels) PickPlatform(runsOn []string) string { func (l Labels) PickPlatform(runsOn []string) string {
platforms := make(map[string]string, len(l)) platforms := make(map[string]string, len(l))
for _, label := range l { for _, label := range l {
@@ -82,6 +89,7 @@ func (l Labels) PickPlatform(runsOn []string) string {
return "docker.gitea.com/runner-images:ubuntu-latest" return "docker.gitea.com/runner-images:ubuntu-latest"
} }
// Names returns the names of all labels.
func (l Labels) Names() []string { func (l Labels) Names() []string {
names := make([]string, 0, len(l)) names := make([]string, 0, len(l))
for _, label := range l { for _, label := range l {
@@ -90,6 +98,7 @@ func (l Labels) Names() []string {
return names return names
} }
// ToStrings converts labels back to their string representation.
func (l Labels) ToStrings() []string { func (l Labels) ToStrings() []string {
ls := make([]string, 0, len(l)) ls := make([]string, 0, len(l))
for _, label := range l { for _, label := range l {

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package report provides task reporting functionality for communicating with the server.
package report package report
import ( import (
@@ -21,6 +22,7 @@ import (
"git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/client" "git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/client"
) )
// Reporter handles logging and state reporting for running tasks.
type Reporter struct { type Reporter struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@@ -42,6 +44,7 @@ type Reporter struct {
stopCommandEndToken string stopCommandEndToken string
} }
// NewReporter creates a new Reporter for the given task.
func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task) *Reporter { func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task) *Reporter {
var oldnew []string var oldnew []string
if v := task.Context.Fields["token"].GetStringValue(); v != "" { if v := task.Context.Fields["token"].GetStringValue(); v != "" {
@@ -72,6 +75,7 @@ func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.C
return rv return rv
} }
// ResetSteps initializes the step states with the given number of steps.
func (r *Reporter) ResetSteps(l int) { func (r *Reporter) ResetSteps(l int) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@@ -82,6 +86,7 @@ func (r *Reporter) ResetSteps(l int) {
} }
} }
// Levels returns all log levels that this hook should fire for.
func (r *Reporter) Levels() []log.Level { func (r *Reporter) Levels() []log.Level {
return log.AllLevels return log.AllLevels
} }
@@ -93,6 +98,7 @@ func appendIfNotNil[T any](s []*T, v *T) []*T {
return s return s
} }
// Fire processes a log entry and updates the task state accordingly.
func (r *Reporter) Fire(entry *log.Entry) error { func (r *Reporter) Fire(entry *log.Entry) error {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@@ -175,6 +181,7 @@ func (r *Reporter) Fire(entry *log.Entry) error {
return nil return nil
} }
// RunDaemon starts the periodic reporting of logs and state.
func (r *Reporter) RunDaemon() { func (r *Reporter) RunDaemon() {
if r.closed { if r.closed {
return return
@@ -189,6 +196,7 @@ func (r *Reporter) RunDaemon() {
time.AfterFunc(time.Second, r.RunDaemon) time.AfterFunc(time.Second, r.RunDaemon)
} }
// Logf adds a formatted log message to the report.
func (r *Reporter) Logf(format string, a ...interface{}) { func (r *Reporter) Logf(format string, a ...interface{}) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@@ -205,6 +213,7 @@ func (r *Reporter) logf(format string, a ...interface{}) {
} }
} }
// SetOutputs stores the job outputs to be reported to the server.
func (r *Reporter) SetOutputs(outputs map[string]string) { func (r *Reporter) SetOutputs(outputs map[string]string) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@@ -225,6 +234,7 @@ func (r *Reporter) SetOutputs(outputs map[string]string) {
} }
} }
// Close finalizes the report and sends any remaining logs and state.
func (r *Reporter) Close(lastWords string) error { func (r *Reporter) Close(lastWords string) error {
r.closed = true r.closed = true
@@ -260,6 +270,7 @@ func (r *Reporter) Close(lastWords string) error {
}, retry.Context(r.ctx)) }, retry.Context(r.ctx))
} }
// ReportLog sends accumulated log rows to the server.
func (r *Reporter) ReportLog(noMore bool) error { func (r *Reporter) ReportLog(noMore bool) error {
r.clientM.Lock() r.clientM.Lock()
defer r.clientM.Unlock() defer r.clientM.Unlock()
@@ -295,6 +306,7 @@ func (r *Reporter) ReportLog(noMore bool) error {
return nil return nil
} }
// ReportState sends the current task state to the server.
func (r *Reporter) ReportState() error { func (r *Reporter) ReportState() error {
r.clientM.Lock() r.clientM.Lock()
defer r.clientM.Unlock() defer r.clientM.Unlock()
@@ -373,7 +385,7 @@ func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) {
var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`) var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`)
func (r *Reporter) handleCommand(originalContent, command, parameters, value string) *string { func (r *Reporter) handleCommand(originalContent, command, _ /* parameters */, value string) *string {
if r.stopCommandEndToken != "" && command != r.stopCommandEndToken { if r.stopCommandEndToken != "" && command != r.stopCommandEndToken {
return &originalContent return &originalContent
} }

View File

@@ -1,11 +1,13 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package ver provides version information for the runner.
package ver package ver
// go build -ldflags "-X git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/ver.version=1.2.3" // go build -ldflags "-X git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/ver.version=1.2.3"
var version = "dev" var version = "dev"
// Version returns the current runner version.
func Version() string { func Version() string {
return version return version
} }

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// GitCaddy Runner is a CI/CD runner for Gitea Actions.
package main package main
import ( import (