2
0
This commit is contained in:
2026-01-25 11:42:24 -05:00
27 changed files with 247 additions and 241 deletions

14
.gitsecrets-ignore Normal file
View File

@@ -0,0 +1,14 @@
# GitSecrets Ignore File
# This file tracks false positives identified by AI evaluation or manually marked.
# Each line is a JSON object with the following fields:
# - contentHash: SHA256 hash prefix of the secret content
# - patternId: The pattern that detected this secret
# - filePath: Relative path where the secret was found
# - reason: Why this was marked as a false positive
# - confidence: AI confidence level (if from AI evaluation)
# - addedAt: Timestamp when this entry was added
#
# You can safely commit this file to share false positive markers with your team.
# To remove an entry, simply delete the corresponding line.
{"contentHash":"5af30500c6463ec4","patternId":"password-assignment","filePath":"..\\gitcaddy\\internal\\app\\cmd\\register.go","reason":"Manually marked as false positive","addedAt":1769249840525}

View File

@@ -1,53 +1,42 @@
version: "2"
linters:
default: none
enable:
- gosimple
- typecheck
- govet
- errcheck
- staticcheck
- unused
- dupl
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt
- misspell
- gocritic
- bidichk
- ineffassign
- revive
- gofumpt
- depguard
- nakedret
- unconvert
- wastedassign
- nolintlint
- stylecheck
enable-all: false
disable-all: true
fast: false
formatters:
enable:
- gofmt
- gofumpt
run:
go: 1.18
go: "1.23"
timeout: 10m
skip-dirs:
- node_modules
- public
- web_src
linters-settings:
stylecheck:
checks: ["all", "-ST1005", "-ST1003"]
nakedret:
max-func-lines: 0
gocritic:
disabled-checks:
- ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
- singleCaseSwitch
revive:
ignore-generated-header: false
severity: warning
confidence: 0.8
errorCode: 1
warningCode: 1
rules:
- name: blank-imports
- name: context-as-argument
@@ -72,94 +61,25 @@ linters-settings:
- name: modifies-value-receiver
gofumpt:
extra-rules: true
lang-version: "1.18"
depguard:
# TODO: use depguard to replace import checks in gitea-vet
list-type: denylist
# Check the list against standard lib.
include-go-root: true
packages-with-error-message:
- github.com/unknwon/com: "use gitea's util and replacements"
issues:
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- unparam
- staticcheck
- path: models/migrations/v
linters:
- gocyclo
- errcheck
- dupl
- gosec
- linters:
- dupl
text: "webhook"
- linters:
- gocritic
text: "`ID' should not be capitalized"
- path: modules/templates/helper.go
linters:
- gocritic
- linters:
- unused
text: "swagger"
- path: contrib/pr/checkout.go
linters:
- errcheck
- path: models/issue.go
linters:
- errcheck
- path: models/migrations/
linters:
- errcheck
- path: modules/log/
linters:
- errcheck
- path: routers/api/v1/repo/issue_subscription.go
linters:
- dupl
- path: routers/repo/view.go
linters:
- dupl
- path: models/migrations/
linters:
- unused
- linters:
- staticcheck
text: "argument x is overwritten before first use"
- path: modules/httplib/httplib.go
linters:
- staticcheck
# Enabling this would require refactoring the methods and how they are called.
- path: models/issue_comment_list.go
linters:
- dupl
- linters:
- misspell
text: '`Unknwon` is a misspelling of `Unknown`'
- path: models/update.go
linters:
- unused
- path: cmd/dump.go
linters:
- dupl
- text: "commentFormatting: put a space between `//` and comment text"
linters:
- gocritic
- text: "exitAfterDefer:"
linters:
- gocritic
- path: modules/graceful/manager_windows.go
linters:
- staticcheck
text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead."
- path: models/user/openid.go
linters:
- golint

18
LICENSE.md Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 gitcaddy
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

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 with the given context for shutdown propagation.
func New(ctx context.Context, cfg *config.Config, client client.Client, runner *run.Runner) *Poller {
// Inherit from parent context so shutdown signals propagate properly
pollingCtx, shutdownPolling := context.WithCancel(ctx)
@@ -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 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
// Package cleanup provides disk cleanup utilities for CI runners.
package cleanup
import (
@@ -15,18 +16,18 @@ import (
log "github.com/sirupsen/logrus"
)
// CleanupResult contains the results of a cleanup operation
type CleanupResult struct {
// Result contains the results of a cleanup operation.
type Result struct {
BytesFreed int64
FilesDeleted int
Errors []error
Duration time.Duration
}
// RunCleanup performs cleanup operations to free disk space
func RunCleanup(ctx context.Context, cfg *config.Config) (*CleanupResult, error) {
// RunCleanup performs cleanup operations to free disk space.
func RunCleanup(_ context.Context, cfg *config.Config) (*Result, error) {
start := time.Now()
result := &CleanupResult{}
result := &Result{}
log.Info("Starting runner cleanup...")
@@ -207,8 +208,15 @@ func cleanTempDir(maxAge time.Duration) (int64, int, error) {
return 0, 0, err
}
// Only clean files/dirs that look like runner/act artifacts
runnerPatterns := []string{"act-", "runner-", "gitea-", "workflow-", "go-build", "go-link", "node-compile-cache", "npm-", "yarn-", "pnpm-"}
// Only clean files/dirs that look like runner/act artifacts or build tool temp files
runnerPatterns := []string{
"act-", "runner-", "gitea-", "workflow-",
"go-build", "go-link",
"node-compile-cache", "npm-", "yarn-", "yarn--", "pnpm-",
"ts-node-", "tsx-", "jiti", "v8-compile-cache",
"text-diff-expansion-test", "DiagOutputDir",
"dugite-native-", "reorderCommitMessage-", "squashCommitMessage-",
}
for _, entry := range entries {
name := entry.Name()
isRunner := false
@@ -246,10 +254,10 @@ func cleanTempDir(maxAge time.Duration) (int64, int, error) {
return bytesFreed, filesDeleted, nil
}
// dirSize calculates the total size of a directory
// dirSize calculates the total size of a directory.
func dirSize(path string) int64 {
var size int64
filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
_ = filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
@@ -308,6 +316,9 @@ func cleanBuildCaches(maxAge time.Duration) (int64, int, error) {
{filepath.Join(os.Getenv("LOCALAPPDATA"), "Yarn", "Cache"), "Yarn cache (Windows)", 0},
{filepath.Join(os.Getenv("LOCALAPPDATA"), "NuGet", "v3-cache"), "NuGet cache (Windows)", 0},
{filepath.Join(os.Getenv("LOCALAPPDATA"), "pip", "Cache"), "pip cache (Windows)", 0},
// Windows custom paths used by some CI setups
{"C:\\L\\Yarn", "Yarn global cache (Windows)", 0},
{filepath.Join(os.TempDir(), "chocolatey"), "Chocolatey temp cache", 0},
}
for _, cache := range cacheDirs {
@@ -349,13 +360,13 @@ func cleanBuildCaches(maxAge time.Duration) (int64, int, error) {
}
// Also remove empty directories
filepath.Walk(cache.path, func(path string, info os.FileInfo, err error) error {
_ = filepath.Walk(cache.path, func(path string, info os.FileInfo, err error) error {
if err != nil || !info.IsDir() || path == cache.path {
return nil
}
entries, _ := os.ReadDir(path)
if len(entries) == 0 {
os.Remove(path)
_ = os.Remove(path)
}
return nil
})

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

@@ -72,10 +72,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
@@ -199,6 +206,7 @@ func (r *Reporter) 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()
@@ -215,6 +223,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()
@@ -235,6 +244,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
@@ -270,6 +280,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()
@@ -305,6 +316,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()
@@ -383,7 +395,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 (