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.
// SPDX-License-Identifier: MIT
// Package main provides the upload-helper CLI tool for reliable file uploads.
package main
import (

View File

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

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors and MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
// Package cmd provides the CLI commands for gitcaddy-runner.
package cmd
import (
@@ -15,6 +16,7 @@ import (
"git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/ver"
)
// Execute runs the root command for gitcaddy-runner CLI.
func Execute(ctx context.Context) {
// ./gitcaddy-runner
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 {
return func(cmd *cobra.Command, args []string) error {
return func(_ *cobra.Command, _ []string) error {
cfg, err := config.LoadDefault(*configFile)
if err != nil {
return fmt.Errorf("invalid configuration: %w", err)
@@ -132,7 +132,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
return err
}
// 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
// and assign the path to 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)
// Include initial bandwidth result if available
capabilities.Bandwidth = bandwidthManager.GetLastResult()
capabilitiesJson := capabilities.ToJSON()
log.Infof("detected capabilities: %s", capabilitiesJson)
capabilitiesJSON := capabilities.ToJSON()
log.Infof("detected capabilities: %s", capabilitiesJSON)
// Check disk space and warn if low
checkDiskSpaceAndCleanup(ctx, capabilities)
// declare the labels of the runner before fetching tasks
resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJson)
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJSON)
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")
return err
} else if err != nil {
case err != nil:
log.WithError(err).Error("fail to invoke Declare")
return err
} else {
default:
log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully",
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
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)
// Always try cleanup at critical level
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)
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)
}
}
@@ -330,13 +332,13 @@ func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNa
capabilities.Bandwidth = bandwidthManager.GetLastResult()
}
capabilitiesJson := capabilities.ToJSON()
capabilitiesJSON := capabilities.ToJSON()
// Check for disk space warnings
checkDiskSpaceAndCleanup(ctx, capabilities)
// Send updated capabilities to server
_, err := runner.Declare(ctx, labelNames, capabilitiesJson)
_, err := runner.Declare(ctx, labelNames, capabilitiesJSON)
if err != nil {
log.WithError(err).Debug("failed to update capabilities")
} else {

View File

@@ -264,7 +264,7 @@ func printList(plan *model.Plan) error {
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
var filterPlan *model.Plan
@@ -286,19 +286,20 @@ func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *e
}
var err error
if execArgs.job != "" {
switch {
case execArgs.job != "":
log.Infof("Preparing plan with a job: %s", execArgs.job)
filterPlan, err = planner.PlanJob(execArgs.job)
if err != nil {
return err
}
} else if filterEventName != "" {
case filterEventName != "":
log.Infof("Preparing plan for a event: %s", filterEventName)
filterPlan, err = planner.PlanEvent(filterEventName)
if err != nil {
return err
}
} else {
default:
log.Infof("Preparing plan with all jobs")
filterPlan, err = planner.PlanAll()
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 {
return func(cmd *cobra.Command, args []string) error {
return func(_ *cobra.Command, _ []string) error {
planner, err := model.NewWorkflowPlanner(execArgs.WorkflowsPath(), execArgs.noWorkflowRecurse)
if err != nil {
return err
@@ -331,18 +332,19 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
// collect all events from loaded workflows
events := planner.GetEvents()
if len(execArgs.event) > 0 {
switch {
case len(execArgs.event) > 0:
log.Infof("Using chosed event for filtering: %s", 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])
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
// this way user dont have to specify the event.
log.Infof("Using first detected workflow event: %s", events[0])
eventName = events[0]
} else {
default:
log.Infof("Using default workflow event: push")
eventName = "push"
}
@@ -388,7 +390,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
if err != nil {
fmt.Println(err)
}
defer os.RemoveAll(tempDir)
defer func() { _ = os.RemoveAll(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)
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()
return nil
})

View File

@@ -28,7 +28,7 @@ import (
// runRegister registers a runner to the server
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)
isTerm := isatty.IsTerminal(os.Stdout.Fd())
log.SetFormatter(&log.TextFormatter{
@@ -80,6 +80,7 @@ type registerArgs struct {
type registerStage int8
// Register stage constants define the steps in the registration workflow.
const (
StageUnknown registerStage = -1
StageOverwriteLocalConfig registerStage = iota + 1
@@ -250,7 +251,7 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist
if stage == StageWaitingForRegistration {
log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels)
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.")
return nil
@@ -311,7 +312,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
return err
}
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.")
return nil

View File

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

View File

@@ -1,6 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package run provides the core runner functionality for executing tasks.
package run
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 {
ls := labels.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 {
if _, ok := r.runningTasks.Load(task.Id); ok {
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
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
// 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()
if giteaDefaultActionsURL == "https://github.com" && 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
}
if actionsIdTokenRequestUrl := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIdTokenRequestUrl != "" {
r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = 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_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue()
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
}
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{
Version: ver.Version(),
Labels: labels,
CapabilitiesJson: capabilitiesJson,
CapabilitiesJson: capabilitiesJSON,
}))
}

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
package client
// HTTP header constants for runner authentication and identification.
const (
UUIDHeader = "x-runner-uuid"
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 {
return c.endpoint
}
// Insecure returns whether TLS verification is disabled.
func (c *HTTPClient) Insecure() bool {
return c.insecure
}

View File

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

View File

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

View File

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

View File

@@ -121,7 +121,7 @@ func testLatency(ctx context.Context, serverURL string) float64 {
if err != nil {
return 0
}
resp.Body.Close()
_ = resp.Body.Close()
latency := time.Since(start).Seconds() * 1000 // Convert to ms
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)
resp.Body.Close()
_ = resp.Body.Close()
cancel()
duration := time.Since(start)

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package report provides task reporting functionality for communicating with the server.
package report
import (
@@ -21,6 +22,7 @@ import (
"git.marketally.com/gitcaddy/gitcaddy-runner/internal/pkg/client"
)
// Reporter handles logging and state reporting for running tasks.
type Reporter struct {
ctx context.Context
cancel context.CancelFunc
@@ -42,6 +44,7 @@ type Reporter struct {
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 {
var oldnew []string
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
}
// ResetSteps initializes the step states with the given number of steps.
func (r *Reporter) ResetSteps(l int) {
r.stateMu.Lock()
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 {
return log.AllLevels
}
@@ -93,6 +98,7 @@ func appendIfNotNil[T any](s []*T, v *T) []*T {
return s
}
// Fire processes a log entry and updates the task state accordingly.
func (r *Reporter) Fire(entry *log.Entry) error {
r.stateMu.Lock()
defer r.stateMu.Unlock()
@@ -175,6 +181,7 @@ func (r *Reporter) Fire(entry *log.Entry) error {
return nil
}
// RunDaemon starts the periodic reporting of logs and state.
func (r *Reporter) RunDaemon() {
if r.closed {
return
@@ -189,6 +196,7 @@ func (r *Reporter) RunDaemon() {
time.AfterFunc(time.Second, r.RunDaemon)
}
// Logf adds a formatted log message to the report.
func (r *Reporter) Logf(format string, a ...interface{}) {
r.stateMu.Lock()
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) {
r.stateMu.Lock()
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 {
r.closed = true
@@ -260,6 +270,7 @@ func (r *Reporter) Close(lastWords string) error {
}, retry.Context(r.ctx))
}
// ReportLog sends accumulated log rows to the server.
func (r *Reporter) ReportLog(noMore bool) error {
r.clientM.Lock()
defer r.clientM.Unlock()
@@ -295,6 +306,7 @@ func (r *Reporter) ReportLog(noMore bool) error {
return nil
}
// ReportState sends the current task state to the server.
func (r *Reporter) ReportState() error {
r.clientM.Lock()
defer r.clientM.Unlock()
@@ -373,7 +385,7 @@ func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) {
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 {
return &originalContent
}

View File

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

View File

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