Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7dfd1c149 | ||
|
|
9d0b60643c | ||
|
|
8fab783aa9 | ||
|
|
f1a473b362 | ||
|
|
90d11b8692 | ||
|
|
c4b57fbcb2 |
@@ -1,85 +0,0 @@
|
|||||||
---
|
|
||||||
name: release-nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKER_ORG: gitea
|
|
||||||
DOCKER_LATEST: nightly
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
goreleaser:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: "go.mod"
|
|
||||||
- name: goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
args: release --nightly
|
|
||||||
env:
|
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
S3_REGION: ${{ secrets.AWS_REGION }}
|
|
||||||
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
|
|
||||||
GORELEASER_FORCE_TOKEN: "gitea"
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
release-image:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
variant:
|
|
||||||
- target: basic
|
|
||||||
tag_suffix: ""
|
|
||||||
- target: dind
|
|
||||||
tag_suffix: "-dind"
|
|
||||||
- target: dind-rootless
|
|
||||||
tag_suffix: "-dind-rootless"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # all history for all branches and tags
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker BuildX
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Echo the tag
|
|
||||||
run: echo "${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}"
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
target: ${{ matrix.variant.target }}
|
|
||||||
platforms: |
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
name: release-tag
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
goreleaser:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # all history for all branches and tags
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: "go.mod"
|
|
||||||
- name: Import GPG key
|
|
||||||
id: import_gpg
|
|
||||||
uses: crazy-max/ghaction-import-gpg@v6
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
||||||
passphrase: ${{ secrets.PASSPHRASE }}
|
|
||||||
fingerprint: CC64B1DB67ABBEECAB24B6455FC346329753F4B0
|
|
||||||
- name: goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
args: release
|
|
||||||
env:
|
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
S3_REGION: ${{ secrets.AWS_REGION }}
|
|
||||||
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
|
|
||||||
GORELEASER_FORCE_TOKEN: "gitea"
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
|
|
||||||
release-image:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-latest
|
|
||||||
env:
|
|
||||||
DOCKER_ORG: gitea
|
|
||||||
DOCKER_LATEST: latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # all history for all branches and tags
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker BuildX
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Get Meta
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
|
||||||
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
target: basic
|
|
||||||
platforms: |
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
|
||||||
|
|
||||||
- name: Build and push dind
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
target: dind
|
|
||||||
platforms: |
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
|
|
||||||
|
|
||||||
- name: Build and push dind-rootless
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
target: dind-rootless
|
|
||||||
platforms: |
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind-rootless
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind-rootless
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
name: checks
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: check and test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: 'go.mod'
|
|
||||||
- name: vet checks
|
|
||||||
run: make vet
|
|
||||||
- name: build
|
|
||||||
run: make build
|
|
||||||
- name: test
|
|
||||||
run: make test
|
|
||||||
@@ -7,7 +7,7 @@ FROM golang:1.24-alpine AS builder
|
|||||||
RUN apk add --no-cache make git
|
RUN apk add --no-cache make git
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY=${GOPROXY:-}
|
ENV GOPROXY=${GOPROXY:-https://mirrors.aliyun.com/goproxy/,direct}
|
||||||
|
|
||||||
COPY . /opt/src/act_runner
|
COPY . /opt/src/act_runner
|
||||||
WORKDIR /opt/src/act_runner
|
WORKDIR /opt/src/act_runner
|
||||||
|
|||||||
17
Dockerfile.custom
Normal file
17
Dockerfile.custom
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# 安装必要的工具
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 复制自定义编译的 act_runner
|
||||||
|
# 注意:构建前需要先确保当前目录下有编译好的 act_runner 二进制文件
|
||||||
|
COPY act_runner /usr/local/bin/act_runner
|
||||||
|
RUN chmod +x /usr/local/bin/act_runner
|
||||||
|
|
||||||
|
# 创建数据目录
|
||||||
|
RUN mkdir -p /data/cache
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/act_runner"]
|
||||||
|
CMD ["daemon", "--config", "/config.yaml"]
|
||||||
3
Makefile
3
Makefile
@@ -1,7 +1,6 @@
|
|||||||
DIST := dist
|
DIST := dist
|
||||||
EXECUTABLE := act_runner
|
EXECUTABLE := act_runner
|
||||||
GOFMT ?= gofumpt -l
|
GOFMT ?= gofumpt -l
|
||||||
DIST := dist
|
|
||||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||||
GO ?= go
|
GO ?= go
|
||||||
SHASUM ?= shasum -a 256
|
SHASUM ?= shasum -a 256
|
||||||
@@ -22,6 +21,8 @@ DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
|||||||
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
||||||
|
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
|
GOPROXY ?= https://mirrors.aliyun.com/goproxy/,direct
|
||||||
|
export GOPROXY
|
||||||
|
|
||||||
ifneq ($(shell uname), Darwin)
|
ifneq ($(shell uname), Darwin)
|
||||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ type cacheServerArgs struct {
|
|||||||
|
|
||||||
func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error {
|
func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
|
fmt.Println("cache1")
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
fmt.Println("cache2")
|
||||||
initLogging(cfg)
|
initLogging(cfg)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -47,7 +48,7 @@ func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheSer
|
|||||||
if cacheArgs.Port != 0 {
|
if cacheArgs.Port != 0 {
|
||||||
port = cacheArgs.Port
|
port = cacheArgs.Port
|
||||||
}
|
}
|
||||||
|
fmt.Println("cache2")
|
||||||
cacheHandler, err := artifactcache.StartHandler(
|
cacheHandler, err := artifactcache.StartHandler(
|
||||||
dir,
|
dir,
|
||||||
host,
|
host,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Execute(ctx context.Context) {
|
func Execute(ctx context.Context) {
|
||||||
|
configFile := ""
|
||||||
|
|
||||||
// ./act_runner
|
// ./act_runner
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
||||||
@@ -22,8 +24,16 @@ func Execute(ctx context.Context) {
|
|||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
Version: ver.Version(),
|
Version: ver.Version(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Auto-detect config.yaml if not specified
|
||||||
|
if configFile == "" {
|
||||||
|
if _, err := os.Stat("config.yaml"); err == nil {
|
||||||
|
configFile = "config.yaml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
configFile := ""
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Config file path")
|
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Config file path")
|
||||||
|
|
||||||
// ./act_runner register
|
// ./act_runner register
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -38,6 +39,8 @@ type Runner struct {
|
|||||||
labels labels.Labels
|
labels labels.Labels
|
||||||
envs map[string]string
|
envs map[string]string
|
||||||
|
|
||||||
|
useHostDockerInternal bool
|
||||||
|
|
||||||
runningTasks sync.Map
|
runningTasks sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +55,14 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
|||||||
for k, v := range cfg.Runner.Envs {
|
for k, v := range cfg.Runner.Envs {
|
||||||
envs[k] = v
|
envs[k] = v
|
||||||
}
|
}
|
||||||
|
// Setup cache
|
||||||
|
useHostDockerInternal := false
|
||||||
if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled {
|
if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled {
|
||||||
if cfg.Cache.ExternalServer != "" {
|
if cfg.Cache.ExternalServer != "" {
|
||||||
envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer
|
envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer
|
||||||
|
if strings.Contains(cfg.Cache.ExternalServer, "host.docker.internal") {
|
||||||
|
useHostDockerInternal = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cacheHandler, err := artifactcache.StartHandler(
|
cacheHandler, err := artifactcache.StartHandler(
|
||||||
cfg.Cache.Dir,
|
cfg.Cache.Dir,
|
||||||
@@ -66,7 +74,16 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
|||||||
log.Errorf("cannot init cache server, it will be disabled: %v", err)
|
log.Errorf("cannot init cache server, it will be disabled: %v", err)
|
||||||
// go on
|
// go on
|
||||||
} else {
|
} else {
|
||||||
envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/"
|
// Use host.docker.internal for container access
|
||||||
|
externalURL := cacheHandler.ExternalURL()
|
||||||
|
if parsedURL, err := url.Parse(externalURL); err == nil {
|
||||||
|
parsedURL.Host = "host.docker.internal:" + parsedURL.Port()
|
||||||
|
envs["ACTIONS_CACHE_URL"] = parsedURL.String() + "/"
|
||||||
|
} else {
|
||||||
|
envs["ACTIONS_CACHE_URL"] = externalURL + "/"
|
||||||
|
}
|
||||||
|
useHostDockerInternal = true
|
||||||
|
log.Infof("Cache server started at %s, using host.docker.internal for container access", externalURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,11 +98,12 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
|||||||
envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version()
|
envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version()
|
||||||
|
|
||||||
return &Runner{
|
return &Runner{
|
||||||
name: reg.Name,
|
name: reg.Name,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
client: cli,
|
client: cli,
|
||||||
labels: ls,
|
labels: ls,
|
||||||
envs: envs,
|
envs: envs,
|
||||||
|
useHostDockerInternal: useHostDockerInternal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +215,17 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
maxLifetime = time.Until(deadline)
|
maxLifetime = time.Until(deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-add host.docker.internal if cache is enabled
|
||||||
|
containerOptions := r.cfg.Container.Options
|
||||||
|
if r.useHostDockerInternal {
|
||||||
|
if containerOptions == "" {
|
||||||
|
containerOptions = "--add-host=host.docker.internal:host-gateway"
|
||||||
|
} else if !strings.Contains(containerOptions, "--add-host=host.docker.internal") {
|
||||||
|
containerOptions += " --add-host=host.docker.internal:host-gateway"
|
||||||
|
}
|
||||||
|
log.Infof("Auto-added host.docker.internal mapping for cache: %s", containerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
runnerConfig := &runner.Config{
|
runnerConfig := &runner.Config{
|
||||||
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
|
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
|
||||||
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
|
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
|
||||||
@@ -219,7 +248,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||||
ContainerMaxLifetime: maxLifetime,
|
ContainerMaxLifetime: maxLifetime,
|
||||||
ContainerNetworkMode: container.NetworkMode(r.cfg.Container.Network),
|
ContainerNetworkMode: container.NetworkMode(r.cfg.Container.Network),
|
||||||
ContainerOptions: r.cfg.Container.Options,
|
ContainerOptions: containerOptions,
|
||||||
ContainerDaemonSocket: r.cfg.Container.DockerHost,
|
ContainerDaemonSocket: r.cfg.Container.DockerHost,
|
||||||
Privileged: r.cfg.Container.Privileged,
|
Privileged: r.cfg.Container.Privileged,
|
||||||
DefaultActionInstance: r.getDefaultActionsURL(ctx, task),
|
DefaultActionInstance: r.getDefaultActionsURL(ctx, task),
|
||||||
|
|||||||
Reference in New Issue
Block a user